HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

++++++ this code

12-19-2008, 03:27 AM#1
fX_
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?

Collapse 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
fX_
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)

Collapse 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
tamisrah
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
fX_
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
Anitarf
Why can't the player's unit just shoot and if the bullet hits an obstacle before reaching the target then too bad?