| 12-19-2008, 03:27 AM | #1 |
After surveying a model pack of space Orcs by a user named General Frank, I got the impulse to create a "space map" using them, for a 'quick project'. So, for my map I want each player to control only 1 unit that has a certain role. They're supposed to 'rescue' outposts overran by aliens. I want players to work and act like SWAT teams in real life - duck behind cover, secure vantage points etc. So I made this script. It allows units to attack only other units that are not hidden to it behind some cover. I need an 'easy' way - one does not tax computer operations too much - implement the IsUnitCovered()/CoverFilter() function, particularly in checking for destructables between the attacker and its target. I've considered using geometry and algebra by getting all destructables within a certain range of the line between the attacker and its target and determining if a destructable is in range of the line if the length of the line perpendicular to the first line between each destructable and the point(R_X, R_Y) is <= CHECK_RANGE. This seems tedious. Any suggestions? Anything I should fix? It deals with multiple groups and it runs lots of loops at every Update() instance... this doesn't seem good... If this isn't good how should I ameliorate it? ***EDITED SCRIPT: i think the function object of the role of FilterGroundCover will be somewhat more efficient now since it checks if it SHOULD STILL CHECK if there are impeding destructables. If there is already a destructable impeding then the attack isn't gonna go, don't check for more; nothing will change. The 'If's should relieve the system of some work, but still there remains a lot of 'loopy' work for it to do. How can I consolidate? JASS:library TerrainFactor initializer Init scope GroundCover globals private constant real UPDATE_INTERVAL_DURATION = 0.50 private constant string TARGET_INDICATOR = "" private constant real TARGET_RANGE = 2000.00 private constant real COVER_RANGE = 250.00 private constant integer GROUND_COVER_ID = 'd000' private unit array gU[8190] private group array gUG[8190] private integer gINT_CountU = 0 private region gREG = CreateRegion() private group gUG_Target = CreateGroup() private effect array gFX[8190] private integer gINT_CountFX = 0 private integer gINT_CountCover private group gUG_Temp = CreateGroup() private unit gU_Temp = null private rect gRECT_Temp = null private string gSTR_Temp = null endglobals private function FilterGroundCover takes nothing returns boolean local destructable D = GetEnumDestructable() if gINT_CountCover == 0 and GetDestructableTypeId(D) == GROUND_COVER_ID and 1 == 1 then set gINT_CountCover = gINT_CountCover + 1 endif set D = null return false endfunction private function IsNotCovered takes unit attacker, unit target returns boolean local real R_XU = GetUnitX(attacker) local real R_YU = GetUnitY(attacker) local real R_XV = GetUnitX(target) local real R_YV = GetUnitY(target) local real R_XMin local real R_XMax local real R_YMin local real R_YMax //Set initial number of impeding destructables to 0. This will be increased as impeding destructables are discovered. set gINT_CountCover = 0 //Enumerate the destructables within a rectangular area containing the area at which destructables can be positioned to impede. //An area //---Determine the parameters of the rectangular area. if R_XU < R_XV then set R_XMin = R_XU set R_XMax = R_XV else set R_XMin = R_XV set R_XMax = R_XU endif if R_YU < R_YV then set R_YMin = R_YU set R_YMax = R_YV else set R_YMin = R_YV set R_YMax = R_YU endif //---Describe the area. set gRECT_Temp = Rect(R_XMin - COVER_RANGE, R_YMin - COVER_RANGE, R_XMax + COVER_RANGE, R_YMax + COVER_RANGE) //---Enumerate and filter. call EnumDestructablesInRect(gRECT_Temp, Filter(function FilterGroundCover), null) //Evaluate if the target is blocked from the attacker. If there are any impeding covers, then it is blocked. if gINT_CountCover > 0 then return false endif return true endfunction private function Update takes nothing returns nothing local integer INT_Index = 0 //Destroy old thumbs-up indicators. loop exitwhen gINT_CountFX == 0 set gINT_CountFX = gINT_CountFX - 1 call DestroyEffect(gFX[gINT_CountFX]) set gFX[gINT_CountFX] = null endloop //Update cover-affected indexes before updating targets and targetability. loop exitwhen INT_Index >= gINT_CountU if not IsUnitInRegion(gREG, gU[INT_Index]) then set gINT_CountU = gINT_CountU - 1 set gU[INT_Index] = gU[gINT_CountU] set gUG[INT_Index] = gUG[gINT_CountU] set gU[gINT_CountU] = null call DestroyGroup(gUG[gINT_CountU]) set gUG[gINT_CountU] = null set INT_Index = INT_Index - 1 endif set INT_Index = INT_Index + 1 endloop //Update targets and targetability. call GroupClear(gUG_Temp) call GroupAddGroup(gUG_Target, gUG_Temp) loop set gU_Temp = FirstOfGroup(gUG_Temp) exitwhen gU_Temp == null //If the query unit is still existent, run the stuff... if IsUnitInRegion(gREG, gU_Temp) then //Get the unit's targetability for every cover-affected unit within targetting range of it. set gSTR_Temp = "" //The "thumbs-up" effect starts out as nothing. This changed to the indicative model //as ascertained in the loop below. set INT_Index = 0 loop exitwhen INT_Index == gINT_CountU //If the unit is in range and targetable for the query cover-affected... if IsUnitInRange(gU_Temp, gU[INT_Index], TARGET_RANGE) and IsNotCovered(gU[INT_Index], gU_Temp) then //...register the former as a target for the latter if it is not yet registered... if not IsUnitInGroup(gU_Temp, gUG[INT_Index]) then call GroupAddUnit(gUG[INT_Index], gU_Temp) endif //Show the "thumbs-up" indicator for the owner of the query cover-affected that can target the query //target. if GetLocalPlayer() == GetOwningPlayer(gU[INT_Index]) then endif //...Or, if the query target it is not a qualified target //...unregister it as a target if it is registered.... //...^ these 2 conditions are merged in a - 'coz they incidentally occur concurrently - for practicality. elseif IsUnitInGroup(gU_Temp, gUG[INT_Index]) then call GroupRemoveUnit(gUG[INT_Index], gU_Temp) endif set INT_Index = INT_Index + 1 endloop //Apply "thumbs-up" indicator. This will show only for players whose units are able to target //the query target unit. set gFX[gINT_CountFX] = AddSpecialEffectTarget(gSTR_Temp, gU_Temp, "origin") set gINT_CountFX = gINT_CountFX + 1 //...Else, unregister it as a potential target. else call GroupRemoveUnit(gUG_Target, gU_Temp) endif call GroupRemoveUnit(gUG_Temp, gU_Temp) endloop endfunction private function EnforceGroundCover takes nothing returns boolean local unit U_Attacker = GetAttacker() local unit U_Target = GetTriggerUnit() local integer INT_Index = 0 //If the attacker is a ground-cover-affected unit then enforce ground covers for it. if GetUnitAbilityLevel(U_Attacker, 'Aloc') == 0 and IsUnitType(U_Attacker, UNIT_TYPE_GROUND) then //Find the attacker's group of qualified targets. loop exitwhen gU[INT_Index] == U_Attacker set INT_Index = INT_Index + 1 endloop //Check if the attacked unit is in the group; if it isn't stop the attacker from attacking it! if not IsUnitInGroup(U_Target, gUG[INT_Index]) then call PauseUnit(U_Attacker, true) call IssueImmediateOrder(U_Attacker, "stop") call PauseUnit(U_Attacker, false) endif endif set U_Attacker = null set U_Target = null return false endfunction private function RegisterTarget takes nothing returns boolean local unit U = GetFilterUnit() //Register... if GetUnitAbilityLevel(U, 'Aloc') == 0 then //...the unit as potentially targetable if it is... call GroupAddUnit(gUG_Target, U) //...and as ground-cover-affected if it is. if IsUnitType(U, UNIT_TYPE_GROUND) then set gU[gINT_CountU] = U set gUG[gINT_CountU] = CreateGroup() set gINT_CountU = gINT_CountU + 1 endif endif set U = null return false endfunction public function Init takes nothing returns nothing local trigger TRIG = CreateTrigger() //Setup trigger that registers potential targets for ground-cover-affected units, for qualification for //attack when they get attacked by ground-cover-affeted units. call RegionAddRect(gREG, bj_mapInitialPlayableArea) call TriggerRegisterEnterRegion(TRIG, gREG, Filter(function RegisterTarget)) //Setup on-attack event trigger that enforces ground cover simulation. set TRIG = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(TRIG, EVENT_PLAYER_UNIT_ATTACKED) call TriggerAddCondition(TRIG, Condition(function EnforceGroundCover)) //Start timer that updates targetability. call TimerStart(CreateTimer(), UPDATE_INTERVAL_DURATION, true, function Update) set TRIG = null endfunction endscope private function Init takes nothing returns nothing call GroundCover_Init() endfunction endlibrary |
| 12-19-2008, 05:09 AM | #2 |
EDIT: edited script above Here is what I have come up with for the problem bit: (computation may be wrong. If it isn't, I'll simplify it to relieve the computer a bit. But is there any 'easier' way?) here's my solution: a)slope of line between destructable and query (X,Y) = 1 / slope of line between attacker and target. b) y= mx + b 1)express y in terms of x (y = mx + b) 2)all b's (b1 and b2, for each respective line) will be cancelled 3)use quadratic equation to solve for x 4)solve for y ( y = f(X)) by tangentfunction(given the angle between attacker and target) JASS:private function CoverFilter takes nothing returns nothing local destructable D = GetEnumDestructable() local real R_XD = GetDestructableX(D) local real R_YD = GetDestructableY(D) local real R_M1 = (gR_YV - gR_YV)/(gR_XV - gR_X) local real R_M2 = (gR_XV - gR_X)/(gR_YV - gR_YV) local real R_X = (-(R_M1*(R_XD+gR_X)-R_M2(gR_X-R_XD)) + SquareRoot(Pow((R_M1*(R_XD+gR_X)-R_M2(gR_X-R_XD)),2) - 4*(R_M2-R_M1)*(-(R_M2-R_M1)*(gR_X,R_XD)))) / 2*(R_M2-R_M1) local real R_Y = X*Tan(gR_Angle) if GetDestructableTypeId(D) == COVER_DESTRUCTABLE_ID and SquareRoot(Pow(R_YD - R_Y,2) + Pow(R_XD - R_X,2)) <= CHECK_RANGE then set gINT_CountCover = gINT_CountCover + 1 endif set D = null endfunction private function IsNotCovered takes unit u, unit v returns boolean local boolean BOOL = true local real R_DX local real R_DY local location LOC set gR_X = GetUnitX(u) set gR_Y = GetUnitY(u) set gR_XV = GetUnitX(v) set gR_YV = GetUnitY(v) set gR_Angle = bj_RADTODEG * Atan2(R_YV - gR_Y, R_XV - gR_X) set R_DX = CHECK_INCREMENT * Cos(R_Angle) set R_DY = CHECK_INCREMENT * Sin(R_Angle) //Check if there are any cover-destructables between the attacker and the //target. Check at for existence of said destructable-type within CHECK_RADIUS range, every CHECK_INCREMENT distance between the attacker and the //target. loop exitwhen gR_X >= gR_XV set gR_X = gR_X + R_DX set gR_Y = gR_Y + R_DY set gINT_CountCover = 0 set LOC = Location(gR_X, gR_Y) call EnumDestructablesInCircleBJ(CHECK_RADIUS * 1.50, LOC, function CoverFilter) if gINT_CountCover > 0 then set BOOL = false endif endloop call RemoveLocation(LOC) set LOC = null return BOOL endfunction still, this is rather long. this operation will be ran in-loop(to at least the second level) so this won't be ez on the computer. Any simpler ways? |
| 12-19-2008, 10:54 AM | #3 |
How about using a pathing check to determine if the unit may shoot? (You could use a unit to check for any obstackles or items to exclude units) link |
| 12-19-2008, 11:13 AM | #4 |
That system checks if an area is walkable; I'm checking if an area can be shot-over, not necessarily only walkable. e.g. A unit can be standing on clear ground so that the gruond is not walkable, yet it can be shot-over since no ground cover (such as a big rock destructable) is located on it - otherwise, the unit would not be standing there. My method is a triggered "stop" order. So I need qualification for "stopping". The condition for this qualification is "is there such a destructable, a ground cover, in the way?". I need the cover to be destructables so I can adjust their height in terraining. |
| 12-27-2008, 11:04 PM | #5 |
Why can't the player's unit just shoot and if the bullet hits an obstacle before reaching the target then too bad? |
