HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Attack Height Advantage System-- Why No Worky?

11-02-2009, 08:28 PM#1
PsycoMarauder
So, over the summer, Pyrogasm was kind enough to write me a private system so that units would do more damage based on how high they are in comparison to the units they are fighting.

This will add huuuuuge strategical advantages to my map. Unfortunately, the code only works for the units that are in the map when the map begins, and if units are added while the game is in progress via any method, the system won't work for them.

I've talked to Pyrogasm about it, he said he'd get to it when he can, but, it'd be really helpful for this system to work properly sooner than later. I wanted this to be a private system for my map, but right now idc if others can see the code-- I just need this system to work.

If someone could fix the flaws in the system, (why it won't work for units entering the map after the map has began), and if possible make the system work for non-structure units only, that would be extremely helpful, and I'd definitely give credits in the map (Risk Next Gen, if anyone cares)


Required Libraries:

Collapse JASS:
library BoolexprUtils initializer init
    globals
        boolexpr BOOLEXPR_TRUE=null
        boolexpr BOOLEXPR_FALSE=null
    endglobals

    private function rettrue takes nothing returns boolean
        return true
    endfunction

    private function retfalse takes nothing returns boolean
        return false
    endfunction

    private function init takes nothing returns nothing
        set BOOLEXPR_TRUE=Condition(function rettrue)
        set BOOLEXPR_FALSE=Condition(function retfalse)
    endfunction
endlibrary

library LightLeaklessDamageDetect initializer Init
    
    // Creating threads off of this that last longer than the timeout below will likely cause issues, like everything blowing up (handle stack corruption)
    // It seems that threads created by timers, rather than executefunc / .evaluate / .execute are not affected. Any threads created from the timer thread are fine.
    // This being safe with even the usage laid out above isn't guarenteed. Use at own risk.
    // If you start getting random bugs, see if commenting out the timer line below (see comments) helps
    // If it does, report it in the thread for this script at [url]www.wc3campaigns.net[/url]
    
    globals
        private constant real SWAP_TIMEOUT = 600. // keep high; 600 should be about the right balance.
    endglobals
    
    globals
        private conditionfunc array func
        private integer funcNext = 0
        private trigger current = null
        private trigger toDestroy = null
        private group swapGroup
        private rect mapRect
    endglobals
    
    // One of the only accessible functions. Use it to add a condition. Must return boolean type, and then have return false at the end.
    // Note that it's technically a condition, so if you put a wait in there, it'll die. But waits are lame anyway.
    function AddOnDamageFunc takes conditionfunc cf returns nothing
        call TriggerAddCondition(current, cf)
        set func[funcNext] = cf
        set funcNext = funcNext + 1
    endfunction
    
    // These inline. For avoiding feedback loops. Feel free to make your own wrapper function for damage functions using this.
    function DisableDamageDetect takes nothing returns nothing
        call DisableTrigger(current)
    endfunction
    function EnableDamageDetect takes nothing returns nothing
        call EnableTrigger(current)
    endfunction
    
    // no more accessible functions, folks.
    
    //! textmacro CGLeaklessDamageDetectAddFilter takes UNIT
        
        // add here any conditions to add the unit to the trigger, example below, commented out:
        // if GetUnitTypeId($UNIT$) != 'h000' then // where 'h000' is a dummy unit
        call TriggerRegisterUnitEvent(current, $UNIT$, EVENT_UNIT_DAMAGED)
        // endif
        
    //! endtextmacro
    
    private function AddEx takes nothing returns boolean
        //! runtextmacro CGLeaklessDamageDetectAddFilter("GetFilterUnit()")
        return false
    endfunction

    private function Enters takes nothing returns boolean
        //! runtextmacro CGLeaklessDamageDetectAddFilter("GetTriggerUnit()")
        return false
    endfunction
    
    private function Swap takes nothing returns nothing
        local integer i = 0
        local boolean b = IsTriggerEnabled(current)
        
        call DisableTrigger(current)
        if toDestroy != null then
            call DestroyTrigger(toDestroy)
        endif
        set toDestroy = current
        set current = CreateTrigger()
        
        if not(b) then
            call DisableTrigger(current)
        endif
        
        call GroupEnumUnitsInRect(swapGroup, mapRect, Filter(function AddEx))
        
        loop
            exitwhen i >= funcNext
            call TriggerAddCondition(current, func[i])
            set i = i + 1
        endloop
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local region r = CreateRegion()
        local integer i = 0
        set mapRect = GetWorldBounds()
        call RegionAddRect(r, mapRect)
        call TriggerRegisterEnterRegion(t, r, null)
        call TriggerAddCondition(t, Condition(function Enters))
        
        set swapGroup = CreateGroup()
        
        set current = CreateTrigger()
        loop
            exitwhen i >= funcNext
            call TriggerAddCondition(current, func[i])
            set i = i + 1
        endloop
        
        call GroupEnumUnitsInRect(swapGroup, GetWorldBounds(), Filter(function AddEx))
        
        // Commenting out the next line will make the system leak indexes and events, but should make it safer.
        call TimerStart(CreateTimer(), SWAP_TIMEOUT, true, function Swap)
    endfunction
    
endlibrary

Actual Code:

Collapse JASS:
library AttackHeightAdvantage initializer Init requires LightLeaklessDamageDetect, BoolexprUtils
    //Written by Pyrogasm for Psycomarauder
    //
    //It's pretty simple: just modify the below calculation however you like. Use teh maths!
    //Only return the bonus damage you'd like to apply (or subtract, if you give it negative damage)

    globals
        private constant integer ORB_ABILITYID = 'Admg'
        private constant integer ORB_BUFFID = 'Bdmg'
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL

        private location ZLoc = null
    endglobals

    private function DamageCalculation takes unit Source, unit Target, real Damage returns real
        local real D

        call MoveLocation(ZLoc, GetUnitX(Source), GetUnitY(Source))
        set D = GetLocationZ(ZLoc)
        call MoveLocation(ZLoc, GetUnitX(Target), GetUnitY(Target))
        set D = D-GetLocationZ(ZLoc)

        //For every 80 height, the attacker gains 7% damage bonus
        return D/80.00*0.07*Damage
    endfunction

    private function OnDamage takes nothing returns boolean
        local unit T = GetTriggerUnit()
        local unit S

        if GetUnitAbilityLevel(T, ORB_BUFFID) > 0 then
            set S = GetEventDamageSource()
            call UnitRemoveAbility(T, ORB_BUFFID)

            call DisableDamageDetect()
            call UnitDamageTarget(S, T, DamageCalculation(S, T, GetEventDamage()), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            call EnableDamageDetect()

            set S = null
        endif

        set T = null
        return false
    endfunction

    private function EnterAdd takes nothing returns boolean
        local unit U = GetTriggerUnit()

        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)

        set U = null
        return false
    endfunction

    private function InitAdd takes nothing returns nothing
        local unit U = GetEnumUnit()

        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)

        set U = null
    endfunction


    private function Init takes nothing returns nothing
        local group G = CreateGroup()
        local trigger T = CreateTrigger()
        local region R = CreateRegion()

        call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, BOOLEXPR_TRUE)
        call ForGroup(G, function InitAdd)
        call DestroyGroup(G)

        call RegionAddRect(R, bj_mapInitialPlayableArea)
        call TriggerRegisterEnterRegion(T, R, BOOLEXPR_TRUE)
        call TriggerAddCondition(T, Condition(function EnterAdd))
        call RemoveRegion(R)

        set ZLoc = Location(0.00, 0.00)
        call AddOnDamageFunc(Condition(function OnDamage))

        set G = null
        set R = null
    endfunction
endlibrary
11-02-2009, 08:54 PM#2
Anitarf
Try removing this line in the Init function and see if it works:
call RemoveRegion(R)
11-02-2009, 08:57 PM#3
TaintedReality
Just from glancing over it, here's what I saw.

Collapse JASS:
        call RegionAddRect(R, bj_mapInitialPlayableArea)
        call TriggerRegisterEnterRegion(T, R, BOOLEXPR_TRUE)
        call TriggerAddCondition(T, Condition(function EnterAdd))
        call RemoveRegion(R)

Try taking out the RemoveRegion(R) call and see if it works. Right now it's detecting when units enter region R but then deletes region R..so nobody will ever enter it.
11-02-2009, 09:28 PM#4
PsycoMarauder
Oh, the irony. A thousand thanks to both of you, it worked, lol. And thats what I waited 3 months to solve, a single line of code.

Is it a quick fix to make this not work for buildings, and make this system optional? (Optional via mode on/off chosen by a host)
11-02-2009, 09:43 PM#5
Fireeye
It should be quite easy to make it not to work for buildings and to turn on/off the system, just put the Init Actions into a if block.
For the building part, add these
Collapse JASS:
    private function IsNotBuildingFilter takes nothing returns boolean
        return IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)
    endfunction
    private function IsNotBuildingAction takes nothing returns boolean
        return IsUnitType(GetTriggerUnit(),UNIT_TYPE_STRUCTURE)
    endfunction
in the top of your library and change these 2 lines:
Collapse JASS:
    //...
    call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, BOOLEXPR_TRUE)
    //...
    call TriggerRegisterEnterRegion(T, R, BOOLEXPR_TRUE)
    //..
a bit and they should look this way
Collapse JASS:
    //...
    call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea,Condition(function IsNotBuildingFilter))
    //...
    call TriggerRegisterEnterRegion(T, R,Condition(function IsNotBuildingAction))
    //...
The entire library should look this way:
Collapse JASS:
library AttackHeightAdvantage initializer Init requires LightLeaklessDamageDetect
    //Written by Pyrogasm for Psycomarauder
    //
    //It's pretty simple: just modify the below calculation however you like. Use teh maths!
    //Only return the bonus damage you'd like to apply (or subtract, if you give it negative damage)

    globals
        private constant boolean ENABLE_SYSTEM = true
        private constant integer ORB_ABILITYID = 'Admg'
        private constant integer ORB_BUFFID = 'Bdmg'
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL

        private location ZLoc = null
    endglobals
    private function IsNotBuildingFilter takes nothing returns boolean
        return IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)
    endfunction
    private function IsNotBuildingAction takes nothing returns boolean
        return IsUnitType(GetTriggerUnit(),UNIT_TYPE_STRUCTURE)
    endfunction
    private function DamageCalculation takes unit Source, unit Target, real Damage returns real
        local real D

        call MoveLocation(ZLoc, GetUnitX(Source), GetUnitY(Source))
        set D = GetLocationZ(ZLoc)
        call MoveLocation(ZLoc, GetUnitX(Target), GetUnitY(Target))
        set D = D-GetLocationZ(ZLoc)

        //For every 80 height, the attacker gains 7% damage bonus
        return D/80.00*0.07*Damage
    endfunction

    private function OnDamage takes nothing returns boolean
        local unit T = GetTriggerUnit()
        local unit S

        if GetUnitAbilityLevel(T, ORB_BUFFID) > 0 then
            set S = GetEventDamageSource()
            call UnitRemoveAbility(T, ORB_BUFFID)

            call DisableDamageDetect()
            call UnitDamageTarget(S, T, DamageCalculation(S, T, GetEventDamage()), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            call EnableDamageDetect()

            set S = null
        endif

        set T = null
        return false
    endfunction

    private function EnterAdd takes nothing returns boolean
        local unit U = GetTriggerUnit()
        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)
        set U = null
        return false
    endfunction

    private function InitAdd takes nothing returns nothing
        local unit U = GetEnumUnit()
        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)
        set U = null
    endfunction

    private function Init takes nothing returns nothing
        local group G = CreateGroup()
        local trigger T = CreateTrigger()
        local region R = CreateRegion()
        if(ENABLE_SYSTEM)then
            call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea,Condition(function IsNotBuildingFilter))
            call ForGroup(G, function InitAdd)
            call RegionAddRect(R, bj_mapInitialPlayableArea)
            call TriggerRegisterEnterRegion(T, R,Condition(function IsNotBuildingAction))
            call TriggerAddCondition(T, Condition(function EnterAdd))
            set ZLoc = Location(0.00, 0.00)
            call AddOnDamageFunc(Condition(function OnDamage))
        endif
        call DestroyGroup(G)
        set G = null
        set R = null
    endfunction
endlibrary
I'm sorry if there are any errors, don't got WE atm, so i can not check the syntax.

Sorry ... misread the choice with the host, this one is for the map maker.
Let me think a bit about it.
Alright, here would a fast solution, however you can only choose to activate the system once per game.
When it is activated you can not deactivate it, but i would only require some more slightly modifactions to enable that too.
To activate it, you have to run the function AttackHeightAdvantage_ToogleSystem().
Gonna edit this post later again.
Collapse JASS:
library AttackHeightAdvantage requires LightLeaklessDamageDetect
    //Written by Pyrogasm for Psycomarauder
    //
    //It's pretty simple: just modify the below calculation however you like. Use teh maths!
    //Only return the bonus damage you'd like to apply (or subtract, if you give it negative damage)

    globals
        private boolean SYSTEM_ENABLED = false
        private boolean FIRST_START = true
        private constant integer ORB_ABILITYID = 'Admg'
        private constant integer ORB_BUFFID = 'Bdmg'
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL

        private location ZLoc = null
    endglobals
    private function IsNotBuildingFilter takes nothing returns boolean
        return IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)
    endfunction
    private function IsNotBuildingAction takes nothing returns boolean
        return IsUnitType(GetTriggerUnit(),UNIT_TYPE_STRUCTURE)
    endfunction
    private function DamageCalculation takes unit Source, unit Target, real Damage returns real
        local real D

        call MoveLocation(ZLoc, GetUnitX(Source), GetUnitY(Source))
        set D = GetLocationZ(ZLoc)
        call MoveLocation(ZLoc, GetUnitX(Target), GetUnitY(Target))
        set D = D-GetLocationZ(ZLoc)

        //For every 80 height, the attacker gains 7% damage bonus
        return D/80.00*0.07*Damage
    endfunction

    private function OnDamage takes nothing returns boolean
        local unit T = GetTriggerUnit()
        local unit S

        if GetUnitAbilityLevel(T, ORB_BUFFID) > 0 then
            set S = GetEventDamageSource()
            call UnitRemoveAbility(T, ORB_BUFFID)

            call DisableDamageDetect()
            call UnitDamageTarget(S, T, DamageCalculation(S, T, GetEventDamage()), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            call EnableDamageDetect()

            set S = null
        endif

        set T = null
        return false
    endfunction

    private function EnterAdd takes nothing returns boolean
        local unit U = GetTriggerUnit()
        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)
        set U = null
        return false
    endfunction

    private function InitAdd takes nothing returns nothing
        local unit U = GetEnumUnit()
        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)
        set U = null
    endfunction

    public function ToogleSystem takes nothing returns nothing
        local group G = null
        local trigger T = null
        local region R = null
        if(not SYSTEM_ENABLED)then
            set SYSTEM_ENABLED = true
            if(FIRST_START)then
                set FIRST_START = false
                set G = CreateGroup()
                set T = CreateTrigger()
                set R = CreateRegion()
                call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea,Condition(function IsNotBuildingFilter))
                call ForGroup(G, function InitAdd)
                call RegionAddRect(R, bj_mapInitialPlayableArea)
                call TriggerRegisterEnterRegion(T, R,Condition(function IsNotBuildingAction))
                call TriggerAddCondition(T, Condition(function EnterAdd))
                set ZLoc = Location(0.00, 0.00)
                call AddOnDamageFunc(Condition(function OnDamage))
                call DestroyGroup(G)
                set G = null
                set R = null
            endif
        else
            set SYSTEM_ENABLED = false
        endif
    endfunction
endlibrary
De-/Reactivating should be possible now...
11-02-2009, 09:52 PM#6
PsycoMarauder
Well, its not necessarily for the host, but I planned to make it so that if its not being hosted by Ghost++, the player in slot 1 will decide via modes I allow them to configure. I don't pretend to know anything about (v)Jass, but it seems that IF the system can be turned off, I should be able to easily determine whether or not to turn it off via other triggers. Is there a way to turn the system on/off via something simple like a GUI integer variable, and the AHA system will check whether or not the integer is set to x in order for it to work?
11-03-2009, 04:09 PM#7
Element of Water
Collapse JASS:
library AttackHeightAdvantage initializer Init requires LightLeaklessDamageDetect, BoolexprUtils
    //Written by Pyrogasm for Psycomarauder
    //
    //It's pretty simple: just modify the below calculation however you like. Use teh maths!
    //Only return the bonus damage you'd like to apply (or subtract, if you give it negative damage)

    globals
        private constant integer ORB_ABILITYID = 'Admg'
        private constant integer ORB_BUFFID = 'Bdmg'
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL

        private boolean SystemActive = false
        
        private location ZLoc = null
    endglobals

    private function DamageCalculation takes unit Source, unit Target, real Damage returns real
        local real D

        call MoveLocation(ZLoc, GetUnitX(Source), GetUnitY(Source))
        set D = GetLocationZ(ZLoc)
        call MoveLocation(ZLoc, GetUnitX(Target), GetUnitY(Target))
        set D = D-GetLocationZ(ZLoc)

        //For every 80 height, the attacker gains 7% damage bonus
        return D/80.00*0.07*Damage
    endfunction
    
    function AttackHeightAdvantage_Activate takes boolean activate returns nothing
        set SystemActive = activate
    endfunction

    private function OnDamage takes nothing returns boolean
        local unit T = GetTriggerUnit()
        local unit S = GetEventDamageSource()

        if GetUnitAbilityLevel(T, ORB_BUFFID) > 0 and SystemActive and not IsUnitType(S, UNIT_TYPE_BUILDING) then
            call UnitRemoveAbility(T, ORB_BUFFID)

            call DisableDamageDetect()
            call UnitDamageTarget(S, T, DamageCalculation(S, T, GetEventDamage()), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            call EnableDamageDetect()
        endif

        set T = null
        set S = null
        return false
    endfunction

    private function EnterAdd takes nothing returns boolean
        local unit U = GetTriggerUnit()

        if not IsUnitType(U, UNIT_TYPE_BUILDING) then
            call UnitAddAbility(U, ORB_ABILITYID)
            call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)
        endif

        set U = null
        return false
    endfunction

    private function InitAdd takes nothing returns nothing
        local unit U = GetEnumUnit()

        call UnitAddAbility(U, ORB_ABILITYID)
        call UnitMakeAbilityPermanent(U, true, ORB_ABILITYID)

        set U = null
    endfunction


    private function Init takes nothing returns nothing
        local group G = CreateGroup()
        local trigger T = CreateTrigger()
        local region R = CreateRegion()

        call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, BOOLEXPR_TRUE)
        call ForGroup(G, function InitAdd)
        call DestroyGroup(G)

        call RegionAddRect(R, bj_mapInitialPlayableArea)
        call TriggerRegisterEnterRegion(T, R, BOOLEXPR_TRUE)
        call TriggerAddCondition(T, Condition(function EnterAdd))

        set ZLoc = Location(0.00, 0.00)
        call AddOnDamageFunc(Condition(function OnDamage))

        set G = null
        set R = null
    endfunction
endlibrary
It can now be turned on/off at any time and it doesn't affect buildings in any way (they can't deal or take extra damage based on their fly height).

Just do
Trigger:
Custom Script: call AttackHeightAdvantage_Activate(true)
to turn it on and use (false) to turn it off.
11-03-2009, 08:12 PM#8
PsycoMarauder
Thanks everyone. 3 questions: Is this system pretty efficient? My map is a risk map; there can be up to thousands of units attacking all over. From the tests I've ran it seems to have done pretty well, just wondering what you guys thought. Also, when a unit is dealing damage, is this system giving a bonus based on how high the unit is overall, or how high in comparison to the unit being attacked? Also, are units that are attacking from below dealing less damage, or the normal amount?

EDIT: Element, your version of the code doesnt compile properly: Line 3739, 3755- Undeclared Variable UNIT_TYPE_BUILDING
11-03-2009, 10:35 PM#9
Anopob
2. More damage based on comparison between the units' height, as you can see here:
Collapse JASS:
local real D

call MoveLocation(ZLoc, GetUnitX(Source), GetUnitY(Source))
set D = GetLocationZ(ZLoc)
call MoveLocation(ZLoc, GetUnitX(Target), GetUnitY(Target))
set D = D-GetLocationZ(ZLoc)

//For every 80 height, the attacker gains 7% damage bonus
return D/80.00*0.07*Damage

3. Units attacking from a lower height deals the same damage, since there is nothing that is actually reducing the attack itself. The bonus damage is given, if I'm not mistaken, by triggers and is dealt through triggers (the unit does not actually have +X attack).
11-05-2009, 03:16 AM#10
Vexorian
hmnn I think it is a bad habit to use capitals for local variables.
11-05-2009, 03:37 AM#11
PsycoMarauder
Vex, if they are in all lowercase, will that make it compile correctly and work properly?
11-05-2009, 03:41 AM#12
Vexorian
The good karma may help, but not really.

It is UNIT_TYPE_STRUCTURE, it was a typo.