[System] Combat State

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Combat State

A quick, awesome library I made to register when a unit "enters combat" or "leaves combat". The idea has been around for a while, but I haven't seen any libraries/systems for it except for this one:
http://www.thehelper.net/forums/showthread.php/91529-In-Combat-Status

Which uses CSData and PUI. So I decided to make my own, and modeled it after that library a bit, and added some other things I thought would be good.

Requirements:

Here is the code:
JASS:
library CombatState requires UnitIndexer, Event, optional TimerUtils
    //******** COMBAT STATE ******************************************//
    //  - This library registers whether or not some unit is in combat.
    //  - To be registered as in combat, the unit must attack or have been attacked by an enemy unit.
    //  - Any spell that is cast by an opposing player onto the unit will flag them as in combat.
    //  - Being in combat only lasts a specific duration, in this case 5 seconds, before the unit leaves combat.
    //  - Once a unit dies, the dying unit will be taken out of combat. 
    
    //  Requirements:
    //    -- UnitIndexer by Nestharus
    //    -- Event by Nestharus
    //       - This will allow you to register when a unit enters or leaves combat.
    //    -- *OPTIONAL* TimerUtils by Vexorian
    //       - This will recycle timers instead of creating/destroying them, so it is a bit more optimal. As far as speed goes,
    //         it is pretty negligible. Without TimerUtils, it will use a hashtable. 
    
    //  API:
    //    Configurables:
    //       - COMBAT_DURATION: This determines the time before a unit is considered to be out of combat, default 5 seconds.
    //         The unit must remain unattacked and attack no one for COMBAT_DURATION to leave combat.
    
    //    Data Modify/Retrieve:
    //        CombatState[whichUnit].inCombat -> returns boolean 
    //        set CombatState[whichUnit].inCombat = flag
    
    //    Function Wrappers:
    //        function GetUnitCombatState takes unit whichUnit returns boolean
    //           - This returns the combat state of a unit. If it returns true, the unit is in combat. If it returns false, then the unit
    //             is not in combat.
    //        function SetUnitCombatState takes unit whichUnit, boolean flag returns nothing
    //           - This allows you to force a unit in or out of combat, and it will register the corresponding events.
    
    //    If you are using Event:
    //        call CombatState.EnterCombat.register(trigger)
    //           - Registers when some unit enters combat after being out of combat.
    //        call CombatState.LeaveCombat.register(trigger)
    //           - Registers when some unit leaves combat after having been just in combat.
    //        function GetTriggerCombatUnit takes nothing returns unit
    //           - When using registering an event, this will basically return the unit who entered or left combat. GetTriggerCombatUnit()
    
    //    Credits:
    //       - Nestharus for UnitIndexer, Event, and optimizations
    //       - Darthfett for the original combat library
    //       - Vexorian for TimerUtils
    //****************************************************************//

    globals
        private constant real COMBAT_DURATION = 5
    //**************DO NOT EDIT PAST THIS POINT***********************//
        private unit combatUnit = null
        private hashtable Hash
    endglobals
    
    function GetTriggerCombatUnit takes nothing returns unit
        return combatUnit
    endfunction
    
    private module Init 
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            static if not LIBRARY_TimerUtils then
                set Hash = InitHashtable()
            endif
            set thistype.ENTER = Event.create()
            set thistype.LEAVE = Event.create()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_ATTACKED)
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_DEATH)
            call UnitIndexer.DEINDEX.register(Condition(function thistype.deindex))
            call TriggerAddCondition(t,Condition(function thistype.CombatEnter))
        endmethod
    endmodule
    
    struct CombatState extends array
        private timer combatTimer
        private boolean inCombatV
        
        readonly static Event LEAVE = 0
        readonly static Event ENTER = 0
        
        static method operator [] takes unit u returns thistype
            return GetUnitUserData(u)
        endmethod
        
        private static method CombatLeave takes nothing returns nothing
            local timer t   = GetExpiredTimer()
            local unit prev = combatUnit
            static if LIBRARY_TimerUtils then
                local integer id  = GetTimerData(t)
                call ReleaseTimer(t)
            else
                local integer id  = LoadInteger(Hash,GetHandleId(t),0)
                call PauseTimer(t)
                call DestroyTimer(t)
                set t             = null
            endif
            set combatUnit               = GetUnitById(id)
            set thistype(id).inCombatV   = false
            set thistype(id).combatTimer = null
            call thistype.LEAVE.fire()
            set combatUnit               = prev
            set prev                     = null
        endmethod
        
        method operator inCombat takes nothing returns boolean
            return this.inCombatV
        endmethod
        
        method operator inCombat= takes boolean flag returns nothing
            local unit prev    = combatUnit
            set combatUnit     = GetUnitById(this)
            if flag then
                if this.combatTimer == null then
                    set this.inCombatV = true
                    call thistype.ENTER.fire()
                    static if LIBRARY_TimerUtils then
                        set this.combatTimer = NewTimer()
                        call SetTimerData(this.combatTimer,this)
                    else
                        set this.combatTimer = CreateTimer()
                        call SaveInteger(Hash,GetHandleId(this.combatTimer),0,this)
                    endif
                endif
                call TimerStart(this.combatTimer,COMBAT_DURATION,false,function thistype.CombatLeave)
            elseif (this.inCombatV) then
                set this.inCombatV = false
                static if LIBRARY_TimerUtils then
                    call ReleaseTimer(this.combatTimer)
                else
                    call PauseTimer(this.combatTimer)
                    call DestroyTimer(this.combatTimer)
                endif
                set this.combatTimer = null
                call thistype.LEAVE.fire()
            endif
            set combatUnit    = prev
            set prev          = null
        endmethod
        
        private static method CombatEnter takes nothing returns boolean
            local unit u = GetAttacker()
            if GetTriggerEventId()==EVENT_PLAYER_UNIT_DEATH then
                set thistype[GetTriggerUnit()].inCombat=false
                return false
            elseif u == null then
                set u = GetSpellTargetUnit()
            endif
            if u != null then
                if IsUnitEnemy(u,GetTriggerPlayer()) then
                    set thistype[GetTriggerUnit()].inCombat=true
                    set thistype[u].inCombat=true 
                elseif CombatState[u].inCombat then
                    set thistype[GetTriggerUnit()].inCombat=true
                endif
            endif
            set u = null
            return false
        endmethod
        
        private static method deindex takes nothing returns boolean
            set thistype(GetIndexedUnitId()).inCombat=false
            return false
        endmethod
        
        implement Init
    endstruct
    
    function GetUnitCombatState takes unit whichUnit returns boolean
        return CombatState[whichUnit].inCombat
    endfunction
    function SetUnitCombatState takes unit whichUnit, boolean flag returns nothing
        set CombatState[whichUnit].inCombat = flag
    endfunction
endlibrary

You don't have to give credits if you use it, but credits would be nice.

JASS Usage:
The library is nice in that it works for all units, so you don't have to manually add which units you want to track combat status of.

The key function is this:
JASS:
    function GetUnitCombatState takes unit whichUnit returns boolean
        return CombatState[whichUnit].inCombat
    endfunction
Or you can retrieve it directly using this:
CombatState[whichUnit].inCombat

If it returns true, then the unit is in combat. If it returns false, then the unit is out of combat.

Here is an example of the usage:
JASS:
function Example takes nothing returns boolean
    local unit u = GetTriggerUnit()
    if GetUnitCombatState(u) then
        call DisplayTextToPlayer(Player(0),0,0,"Unit is in combat!")
        call SetUnitExploded(u,true) 
        call KillUnit(u)
    else
        call DisplayTextToPlayer(Player(0),0,0,"Unit is out of combat!")
        call SetUnitState(u,UNIT_STATE_LIFE,GetWidgetLife(u)+500)
    endif
    set u = null
    return false
endfunction

function InitTrig_TestSpell takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Example))
endfunction

In that example, the function registers whenever a spell is cast. If the caster is in combat, he'll be exploded and it will say "Unit is in combat!", but if he is out of combat, it will heal him for 500 hit points and say "Unit is out of combat!".

The only configurable is this:
JASS:
        private constant real COMBAT_DURATION = 5

Modifying this will control how long it takes to leave combat after being in combat. However, if you enter combat it doesn't necessarily mean that you will leave combat in 5 seconds. Each time you attack or get attacked, the timer will be refreshed to 5 seconds, so you must not attack or be attacked to successfully leave combat.

You can also force a unit into or out of combat by doing this:
JASS:
set CombatState[whichUnit].inCombat = true //enter combat
set CombatState[whichUnit].inCombat = false //leave combat
Or:
JASS:
call SetUnitCombatState(whichUnit,flag)

If you use the Event Library:

If you use the Event library, then you can register when a unit enters or leaves combat. It is pretty neat. Example:
JASS:
function Example takes nothing returns boolean
    if GetUnitTypeId(GetTriggerCombatUnit())=='hfoo' then
        call SetUnitExploded(GetTriggerCombatUnit(),true)
        call KillUnit(GetTriggerCombatUnit())
    endif
    return false
endfunction

function InitTrig_TestEnterCombat takes nothing returns nothing
    local trigger t = CreateTrigger()
    call CombatState.ENTER.registerTrigger(t)
    call TriggerAddCondition(t,Condition(function Example))
endfunction

If you enter combat as a footman, it will explode you. Simple as that. :p
To register entering and leaving, you simply use this:
JASS:
call CombatState.ENTER.registerTrigger(t)
call CombatState.LEAVE.registerTrigger(t)

GUI Usage:

Yep, this is pretty easy to use in GUI as well.
  • Explode
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Acid Bomb
    • Actions
      • Custom script: set udg_InCombat = GetUnitCombatState(GetTriggerUnit())
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • InCombat Equal to True
        • Then - Actions
          • Set TempPoint = (Position of (Triggering unit))
          • Special Effect - Create a special effect at TempPoint using Abilities\Spells\Human\ThunderClap\ThunderClapCaster.mdl
          • Special Effect - Destroy (Last created special effect)
          • Custom script: call RemoveLocation(udg_TempPoint)
        • Else - Actions
          • Unit - Explode (Triggering unit)
In that example, if the unit casts Acid Bomb and is in combat, then it will create an effect at the position of the caster. If they cast Acid Bomb and are out of combat, it will, of course, explode them. lol. For this part:
  • Custom script: set udg_InCombat = GetUnitCombatState(GetTriggerUnit())
It sets a boolean variable named InCombat to whatever the combat state is of (Triggering Unit).

If you want to register when a unit enters combat, you'll have to do something along these lines.
  • AddCombatEvent
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set CombatTrigger = RegisterEnterCombat <gen>
      • Custom script: call CombatState.ENTER.register(udg_CombatTrigger)
      • Set CombatTrigger = RegisterLeaveCombat <gen>
      • Custom script: call CombatState.LEAVE.register(udg_CombatTrigger)
  • RegisterEnterCombat
    • Events
    • Conditions
    • Actions
      • Custom script: set udg_CombatUnit = GetTriggerCombatUnit()
      • Unit - Set life of CombatUnit to 100.00%
  • RegisterLeaveCombat
    • Events
    • Conditions
    • Actions
      • Custom script: set udg_CombatUnit = GetTriggerCombatUnit()
      • Unit - Explode CombatUnit
The first trigger adds the events to the other triggers. Basically modify "CombatTrigger" to whatever you want and then just put the custom script afterward. :) For the trigger itself, make a variable named "CombatUnit" and then use the custom script at the top of the trigger. This will basically set "CombatUnit" to the unit that is entering combat. In this example, it will set the life of the combat unit to 100.00% once he enters combat, and it will explode him once he leaves combat.

Demo Map:

Attached below is a demo map of the combat system. Basically, it has some nifty spells that only work if you are in combat, out of combat, or change based on whether or not you are in combat. Press ESCAPE to swap between being in combat or out of combat.
 

Attachments

  • CombatSysTHW.w3x
    58.7 KB · Views: 525
Last edited:
Level 30
Joined
Jul 10, 2007
Messages
6,307
Why AIDS when it is slower and less stable than UnitIndexer? >.<

Also, you never have to unregister events, so why use Event lib from TH? : |

Doesn't make sense ; D.

Also this lib will break with struct onInits

library CombatState initializer Init

Just like AIDS, what a charm.

problem with these lines too
JASS:
        timer combatTimer
        boolean inCombat

combatTimer should have a private keyword on it to make it internal

inCombat should be private with a method operator for reading. Either that or put all of your stuff into the struct and onInit into a module implemented by the struct and then make inCombat readonly, and combatTimer private.

These are unneeded as Event API can just as easily be used
JASS:
        function TriggerRegisterEnterCombat takes trigger t returns nothing
            call Event(EnterCombat).register(t)
        endfunction
        
        function TriggerRegisterLeaveCombat takes trigger t returns nothing
            call Event(LeaveCombat).register(t)
        endfunction

Just put the events into the struct and make them readonly. They should be initialized within a module initializer anyways.


If you were using the Event at THW, you wouldn't need to make Event optional as it's so dern light that it doesn't make much of a dif.


This is unstable, use a hashtable instead.
JASS:
           local integer id = CombatId[GetHandleId(t)-OFFSET]
            static if LIBRARY_Event then
                set combatUnit[0] = combatUnit[GetHandleId(t)-OFFSET]
            endif

These should be of type Event and in a struct as readonly fields so you can reduce overall functions.
JASS:
        private integer LeaveCombat
        private integer EnterCombat

Also, while your events aren't a problem with leaving combat, they are a problem with entering combat as they aren't recursive.


Those are all the problems I care to look for for right now ; P.
 
Last edited:

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Why AIDS when it is slower and less stable than UnitIndexer? >.<

Also, you never have to unregister events, so why use Event lib from TH? : |

Idk. I just like the interface and I was going to submit this to TH eventually. For the latter, it just uses Event because I started off with it before the second event was introduced. Tbh, if you change .register(t) in my system to .registerTrigger(t) it should work for your Event lib too, unless I'm mistaken.

Also this lib will break with struct onInits

library CombatState initializer Init

Just like AIDS, what a charm.

They can use library initializers for theirs to fix it.. But okay, I'll look into fixing that.

problem with these lines too
JASS:
        timer combatTimer
        boolean inCombat

combatTimer should have a private keyword on it to make it internal

inCombat should be private with a method operator for reading. Either that or put all of your stuff into the struct and onInit into a module implemented by the struct and then make inCombat readonly, and combatTimer private.

Will do.

These are unneeded as Event API can just as easily be used
JASS:
        function TriggerRegisterEnterCombat takes trigger t returns nothing
            call Event(EnterCombat).register(t)
        endfunction
        
        function TriggerRegisterLeaveCombat takes trigger t returns nothing
            call Event(LeaveCombat).register(t)
        endfunction

Just put the events into the struct and make them readonly. They should be initialized within a module initializer anyways.

I guess so, I like making the interface blizzlike but I guess that is better.


This is unstable, use a hashtable instead.
JASS:
           local integer id = CombatId[GetHandleId(t)-OFFSET]
            static if LIBRARY_Event then
                set combatUnit[0] = combatUnit[GetHandleId(t)-OFFSET]
            endif

Yeah.

These should be of type Event and in a struct as readonly fields so you can reduce overall functions.
JASS:
        private integer LeaveCombat
        private integer EnterCombat

Yeah, but the thing is that when I put static ifs in my globals, they produce a syntax error saying "expected a name." I don't remember them doing that before, so I had to make them integers. My JassHelper is updated.

Besides, no difference? It is just the look of the code, I don't think I need to change that.

Also, while your events aren't a problem with leaving combat, they are a problem with entering combat as they aren't recursive.

I don't understand what you mean.
 
Last edited:
Level 30
Joined
Jul 10, 2007
Messages
6,307
->I don't understand what you mean.
If you were to do DamageUnit within your EnterCombat trigger, the combatUnit would be incorrect.

Code:
-MyCode-
local unit u = GetEventUnit or w/e
DamageUnit
if (u != DamageUnit) then
    --error //this will run

Here is an example of proper recursion
JASS:
integer eventInt = 0

////////////////////////////////////
local integer prev = eventInt
set eventInt = new
call EVENT.fire()
set eventInt = prev

Yeah, but the thing is that when I put static ifs in my globals, they produce a syntax error saying "expected a name." I don't remember them doing that before, so I had to make them integers. My JassHelper is updated.

It won't do that if they are in a struct. Static ifs don't work on globals.

->They can use library initializers for theirs to fix it.. But okay, I'll look into fixing that.
Impossible to fix when using AIDS as AIDS has a problem with initialization too : |,
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
On the note of initialization (since your thing will be messed up as long as it uses AIDS as I have a feeling AIDS is never going to get fixed)

Perhaps you could bring up the initialization problems on the AIDS thread, or just move to UnitIndexer, lol. UnitIndexer has been scanned over by dozens of people and has been rewritten 4x as new techniques and systems come out, lol.

Otherwise I look forward to seeing all the fixes ;D.

I wonder where azlier is...

Yoohoo, azlier, where are uuuuuuuuuuuuuu

*azlier's says in a faint voice from the background, "I'm right heere" *flickers and fades away like a ghost*
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
On the note of initialization (since your thing will be messed up as long as it uses AIDS as I have a feeling AIDS is never going to get fixed)

Perhaps you could bring up the initialization problems on the AIDS thread, or just move to UnitIndexer, lol. UnitIndexer has been scanned over by dozens of people and has been rewritten 4x as new techniques and systems come out, lol.

Otherwise I look forward to seeing all the fixes ;D.

I wonder where azlier is...

Yoohoo, azlier, where are uuuuuuuuuuuuuu

*azlier's says in a faint voice from the background, "I'm right heere" *flickers and fades away like a ghost*

Apparently, AIDS had an onInit method for that reason. lol, I just saw it a while ago.

Yeah, I'll probably add a UnitIndexer version eventually. Just give me some time to make sure my libs work and some time to study/live life. :p Maybe I'll submit an AIDS version on TH and a UnitIndexer one here.

I think I did everything you said. I might of forgotten something aside from switching to the other Event. (I guess mine "technically" supports yours now, it just requires you to register it differently, so maybe I'll just add some documentation on that later) But overall, it looks better now. :)
 
Last edited:
Level 30
Joined
Jul 10, 2007
Messages
6,307
AIDS only has a library initializer ;o. For stability, units aren't given ids until library initialization and previously units are enumerated over. But again, you run into the but I mentioned where GetUnitUserData(CreateUnit(...)) in a struct onInit or module onInit would return 0.

I'll check over your code, just in the middle of something atm ;D.

Also you are missing the Event requirement in your header if you didn't notice.

Like i said, I doubt jesus4lyf will ever change the way AIDS initializes, preferring to say to not use onInit in modules or structs. That's just a bleh excuse. Instability is instability, y'know? : P

Me 'n Bribe worked at proper initialization for unit indexing together in the UnitIndexer thread ^_^.


edits
If you're using the Event here, Event might as well not be optional because of how lightweight it is : |. I'm sure most scripts using this would run events anyways.

JASS:
        static if LIBRARY_Event then
            static Event LeaveCombat 
            static Event EnterCombat
        endif

Yea ; P. These should also be initialized to zero in case they are used by a script. It's always good practice to initialize all of your public static variables so that they don't crash scripts that try to access them. Furthermore, these should be readonly. Also, since these are readonly (pretty much like constants), you should make them all uppercase (following normal JASS events). Also, JASS naming convention for events is EVENT_EVENTNAME. Since your events are within the UnitCombat struct, the Combat portion of the name is extraneous as that is already obvious. It could either be LEAVE and ENTER, or EVENT_LEAVE and EVENT_ENTER. Those are your best options.

So

JASS:
readonly static Event LEAVE = 0
readonly static Event ENTER = 0

or
JASS:
readonly static Event EVENT_LEAVE = 0
readonly static Event EVENT_ENTER = 0

This is bad struct CombatState

And you already know why : P. Not only do vjass structs have tons of crabby overhead, but you don't even need the allocation/deallocation in there for any reason since your thing's instantiation depends on unit indexing.

Is there a reason these are public?
JASS:
static method SetUnitEnterCombat takes unit whichUnit returns nothing
static method CombatEnter takes nothing returns boolean

Your LeaveEvent stuff doesn't need recursion on the units as it's impossible to force a unit to leave combat outside of the timers, right? : P

Well, I guess it would be nice to be able to set a unit's combat state (like if they enter town or something, force them out of combat). In that case, make inCombat a method operator rather than a readonly field where the getter just returns the field and the setter fires off your events and what not. If you were to add that feature, leaveCombat would need to be recursive. If not, leaveCombat doesn't need the recursion, that's just extra overhead that serves no purpose.
 
Last edited:

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Also you are missing the Event requirement in your header if you didn't notice.

Whoops, lol.


If you're using the Event here, Event might as well not be optional because of how lightweight it is : |. I'm sure most scripts using this would run events anyways.

Yeah, I'll probably update it to be required.


JASS:
readonly static Event LEAVE = 0
readonly static Event ENTER = 0

Okay.

This is bad struct CombatState

Whoops, I forgot that too. ;P

Is there a reason these are public?
JASS:
static method SetUnitEnterCombat takes unit whichUnit returns nothing
static method CombatEnter takes nothing returns boolean

No reason, I guess I forgot to privatize it. I used to refer to it outside the struct, until I moved everything in, that's why I forgot. :p


Well, I guess it would be nice to be able to set a unit's combat state (like if they enter town or something, force them out of combat). In that case, make inCombat a method operator rather than a readonly field where the getter just returns the field and the setter fires off your events and what not. If you were to add that feature, leaveCombat would need to be recursive. If not, leaveCombat doesn't need the recursion, that's just extra overhead that serves no purpose.

Yeah, I will probably add some sort of forceOut or forceIn thing to allow you to make a unit in combat. For example, abilities like Vanish will force you out of combat, which is partially one of the reasons I made this library in the first place. :p

Anyway, thanks for the review. :D I'll update it when I can and then hopefully it will be good to go.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
There could be an option to check whether there are enemy units in close range when trying to exit combat. When the timer expires, do the check and refresh timer if true.

Yeah, it is a good idea and I might add that eventually, but for now, I'll keep it a bit WoWlike. ( at least from a pvp standpoint :p )

Anyway, I updated it. Hopefully it should be good now. I updated it so it uses UnitIndexer, and it uses the Event and works like a charm.

As for forcing a unit in or out of combat, I added that, BUT it is not a method operator because I need to get the unit and the boolean, and tbh it is a lot easier to have the user input than keep track of it just so it could become an operator. I can always have one for force in and one for forcing out, but oh well, whatever.
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
Since you have forcing, this has to be recursive ;|
JASS:
        private static method CombatLeave takes nothing returns nothing
            local timer t = GetExpiredTimer()
            static if LIBRARY_TimerUtils then
                local integer id = GetTimerData(t)
                set combatUnit[0] = combatUnit[id]
                call ReleaseTimer(t)
            else
                local integer id = LoadInteger(Hash,GetHandleId(t),0)
                set combatUnit[0] = LoadUnitHandle(Hash,GetHandleId(t),1)
                call PauseTimer(t)
                call DestroyTimer(t)
                set t = null
            endif
            set thistype(id).inCombat = false
            set thistype(id).combatTimer = null
            call thistype.LEAVE.fire()
        endmethod

Make this private static method SetUnitEnterCombat takes unit whichUnit returns nothing into method operator inCombat= takes boolean b returns nothing where the instance is the unit user data.

This way this
JASS:
                call thistype.SetUnitEnterCombat(GetTriggerUnit())
                call thistype.SetUnitEnterCombat(u)
can change to
JASS:
set thistype(GetUnitUserData(GetTriggerUnit()).inCombat = true
set thistype(GetUnitUserData(u)).inComat = true

Change this readonly boolean inCombat

to
JASS:
private boolean inCombat_p
method operator inCombat takes nothing returns boolean
    return inCombat_p
endmethod

This set thistype(id).inCombat = false to set thistype(id).inCombat_p = false

And this set thistype(id).inCombat = true to set thistype(id).inCombat_p = true

And boom, easier API :\.

Aslo add
JASS:
static method operator [] takes unit u returns integer
    return GetUnitUserData(u)
endmethod

That way people can do like set CombatState[u].inCombat = true

Then
JASS:
function GetUnitCombatState takes unit whichUnit returns boolean
function SetUnitCombatState takes unit whichUnit, boolean inCombat returns nothing

Which would just be function wrappers.

Otherwise it's looking better ;D. The chaotic API and documentation made me dizzy though ;P.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Since you have forcing, this has to be recursive ;|

Forgot about that one, done.

Make this private static method SetUnitEnterCombat takes unit whichUnit returns nothing into method operator inCombat= takes boolean b returns nothing where the instance is the unit user data.

Awesome, done. I completely overlooked indexing systems' other functions, doh. It looks a lot better now.

Aslo add
JASS:
static method operator [] takes unit u returns integer
    return GetUnitUserData(u)
endmethod

UnitIndexer already added that to the struct, so it should work. :)

---

Hopefully this will be the final version. It is tiring to update. ;o

The primary API now looks like:
JASS:
set CombatState[whichUnit].inCombat = flag
call CombatState.ENTER.registerTrigger(whichTrigger)
call CombatState.LEAVE.registerTrigger(whichTrigger)
call SetUnitCombatState(whichUnit,flag)
call GetUnitCombatState(whichUnit)
call GetTriggerCombatUnit()
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Brilliant. Also didn't see that you implemented UnitIndexedStruct. I typically assume people do UnitIndexer.INDEX.register() etc, lol, but the module works too ;P.

Now if only the JASS section had a mod ; (.

And a question, now that I believe you've tried UnitIndexer, AutoIndex, and AIDS, which one is your fav? lol

I didn't try AutoIndex yet so the world may never know. ;D
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Pretty neat system. =D

  • What does the local variable prev do in both CombatLeave and inCombat=? Wouldn't it just make combatUnit null?
  • Is it really necessary to use PauseTimer before starting the timer again? (This is in the inCombat= operator.)
  • I think your documentation is wrong about Event. CombatState.EnterCombat.register(trigger) and CombatState.LeaveCombat.register(trigger) should be CombatState.ENTER.registerTrigger(trigger) and CombatState.LEAVE.registerTrigger(trigger). Never mind, this is only in the demo map. =P
  • Not that important, but your demo spells bug if you learn them in different combat states. For example. having a disabled Charge would cause learning Charge to be level 1 again.
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
->What does the local variable prev do in both CombatLeave and inCombat=? Wouldn't it just make combatUnit null?
http://www.hiveworkshop.com/forums/1810299-post172.html

->Is it really necessary to use PauseTimer before starting the timer again?
no

Purge, I've been making it a habit to not use the unit handle anymore. I immediately convert to integer and store. Just think, if your combatUnit was an integer and not a unit, you wouldn't have a local to null would you? : P

It's been benched that nulling locals is slower than calling a function and using them as a parameter (Bribe says so, shh)

This is what I did in UnitIndexer
JASS:
    private integer indexedUnit = 0

    function GetIndexedUnitId takes nothing returns integer
        return indexedUnit
    endfunction
    function GetIndexedUnit takes nothing returns unit
        return units[indexedUnit]
    endfunction


But this isn't a big deal. In fact, it's controversial. If you were to do that, then your GetEventCombatUnit or w/e would be an array read. But most systems do use the GetUnitUserData, so it would probably lower the overhead (no masses of GetUnitUserData calls).

Your call.

and your onInit needs to go into a module. As it stands, it's still unstable because of order of initialization.

If I were to register events on it in a module initializer, it'd register to 0.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Pretty neat system. =D

  • What does the local variable prev do in both CombatLeave and inCombat=? Wouldn't it just make combatUnit null?
  • Is it really necessary to use PauseTimer before starting the timer again? (This is in the inCombat= operator.)
  • I think your documentation is wrong about Event. CombatState.EnterCombat.register(trigger) and CombatState.LeaveCombat.register(trigger) should be CombatState.ENTER.registerTrigger(trigger) and CombatState.LEAVE.registerTrigger(trigger). Never mind, this is only in the demo map. =P
  • Not that important, but your demo spells bug if you learn them in different combat states. For example. having a disabled Charge would cause learning Charge to be level 1 again.

1. Yeah, I added that for recursion as Nes said.
2. I am not sure, I guess I can remove it, I just remember something about pausing a timer before interacting with it. I can remove it though, since I am probably just not remembering correctly. :ogre_haosis:
3. Whoops, I guess I didn't update the demo map, lol. I'll fix that once I update it.
4. Yep ;D I didn't want to bother to debug them, so I left it as is. I meant to learn the skill on initialization so that no one would notice, but I forgot.

Thanks for the feedback. :D

Purge, I've been making it a habit to not use the unit handle anymore. I immediately convert to integer and store. Just think, if your combatUnit was an integer and not a unit, you wouldn't have a local to null would you? : P

It's been benched that nulling locals is slower than calling a function and using them as a parameter (Bribe says so, shh)

This is what I did in UnitIndexer

I could, but it is negligible. I am not too much of a speed freak; I will make optimizations when I can and improve code, but I won't go out of my way to save a call. :p

and your onInit needs to go into a module. As it stands, it's still unstable because of order of initialization.

Okay, I'll update that soon.
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
JASS:
        private method index takes nothing returns nothing
            set this.inCombat = false
        endmethod

That is silly.. don't implement the UnitIndexStruct just for that >.>.

on unit death, set inCombat = false (cuz obvioiusly left combat). Not sure what you want to do if the unit rezzes or reincarnates, but anyways, you can figure that out.

As it stands, if a unit dies, it'll still show them as in combat (funny huh).

You could use UnitEvent for the on death for all, or you could just do EVENT_PLAYER_UNIT_DEATH or w/e.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
JASS:
        private method index takes nothing returns nothing
            set this.inCombat = false
        endmethod

That is silly.. don't implement the UnitIndexStruct just for that >.>.

Whoops, I clearly don't proofread my code properly. Anyway, I removed that entirely since bools default false.

As it stands, if a unit dies, it'll still show them as in combat (funny huh).

Fixed.

I also removed the PauseTimer() call.
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
Ok, next fix...

You should also set unitCombat equal to false on deindex... if a unit is removed and was in combat, it'll still be true after creation and not technically in combat. Yes, keep the on death in there, but add a deindex too. Indexing will already default at false, so that'll waste on operation on first index.

edit
Get rid of the spell event. Spells fire the attack event if they are an attack spell.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Ok, next fix...

You should also set unitCombat equal to false on deindex... if a unit is removed and was in combat, it'll still be true after creation and not technically in combat. Yes, keep the on death in there, but add a deindex too. Indexing will already default at false, so that'll waste on operation on first index.

edit
Get rid of the spell event. Spells fire the attack event if they are an attack spell.

Will do. However, for the second part, I am going to keep the spell event because of things like debuffs. (eg: faerie fire or tornado)

EDIT: Done. I ended up implementing the module anyway, not sure of a way around it. :p
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
This isn't accurate then ; |...

It is modeled off of WoW, so it is pretty accurate. :p It will register debuffs and things like that because they fire the spell event.

Doesn't tornado fire an attack event still? furthermore, what about units that come under damage from an aura? Or stat effects? : o

They don't fire attack events, but they fire damage events.

As for auras and AoE's, I will have to rely on people triggering them. That is why the method operator inCombat exists. It is so that people will be able to do that kind of stuff.

I can add a damage detection system and all that to basically make the user have to do nothing, but in my opinion most would like to control things like that. Overall, the implementation will become 200 on a scale of 1 to 10 in terms of annoyance, and although it is a one-time thing, people generally won't use the system. :p
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
->I can add a damage detection system and all that to basically make the user have to do nothing, but in my opinion most would like to control things like that. Overall, the implementation will become 200 on a scale of 1 to 10 in terms of annoyance, and although it is a one-time thing, people generally won't use the system. :p

Just a spell like heal shouldn't put a unit into combat...

I guess this is a good start, but a generic working combat state for wc3 seems impossible ;D.

Also, onDamage would be able to detect custom abilities and whatever else a user might put into a map ;o. But then again, some damages aren't technically combat (in mmo's, I don't believe the players are put into combat when damaged by a trap).

This might require a custom damage sysetm and so on to be working properly, and if that's the case, then what's here is pretty useless as it'd just get in the way ; O

Those are my opinions. Yes, I've had much time to think about this ;D.

At least the script is much more accurate than it was initially and it runs a lot faster plus has a better API, so those are all pluses =D

edit
Here's another thing that seems impossible to fix

Let's say I fire my yamamoto canon from 9000000 miles away and it won't hit for like 2 days. The targets won't technically be in combat when I fired it (they won't even know about it). Not in combat until damaged.

Let's say I fired a projectile, but the unit dodges it. No damage, but still in combat regardless.

Funny scenarios eh?

Try to make it as accurate as possible and we'll go from there (just a simple cnp with and installation script if you end up needing one). When it's as accurate as possible, then nothing else can be done and the rest is up to the users.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Just a spell like heal shouldn't put a unit into combat...

Yeah, I did an IsUnitEnemy check so heals and positive spells hopefully won't trigger the combat. ;P

Try to make it as accurate as possible and we'll go from there (just a simple cnp with and installation script if you end up needing one). When it's as accurate as possible, then nothing else can be done and the rest is up to the users.

Yep, that is my goal. ;D
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
Then the next issue is dealing with ranged attacks... perhaps detect the range of an attack? If the range is too long, then a unit won't enter combat until it is damaged. If the range is short, then it auto enters combat.

In fact, better yet, base it on the unit's acquisition range ;O (probably not possible, lol).

Boom, came up with a wonderful solution ;D.

This doesn't require attack indexing at all. On attack with long ranged units, add a simple ability and don't put the target into a combat state. On damage, if they have the buff associated with that ability, then put them into combat state. Boom, no attack indexing required ;D.

Then next we gotta deal with auras. Should be easy as you can count buffs on units (seeing if negative buff count > 0).
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Then the next issue is dealing with ranged attacks... perhaps detect the range of an attack? If the range is too long, then a unit won't enter combat until it is damaged. If the range is short, then it auto enters combat.

In fact, better yet, base it on the unit's acquisition range ;O (probably not possible, lol).

Boom, came up with a wonderful solution ;D.

This doesn't require attack indexing at all. On attack with long ranged units, add a simple ability and don't put the target into a combat state. On damage, if they have the buff associated with that ability, then put them into combat state. Boom, no attack indexing required ;D.

Then next we gotta deal with auras. Should be easy as you can count buffs on units (seeing if negative buff count > 0).

Okay, I'll add that tomorrow.
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
On your else in the setting of inCombat (setting it to false), that should only run if inCombat is true.

so elseif (inCombat) or w/e ; )

JASS:
            elseif (inCombat) then
                if this.combatTimer != null then
                    static if LIBRARY_TimerUtils then
                        call ReleaseTimer(this.combatTimer)
                    else
                        call PauseTimer(this.combatTimer)
                        call DestroyTimer(this.combatTimer)
                    endif
                endif
                set this.combatTimer = null
                call thistype.LEAVE.fire()
            endif

You can also remove this check
if this.combatTimer != null then

If they're inCombat, the combatTimer obviously won't be null. If they aren't in combat, the thing shouldn't fire in the first place ; P.
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
Sorry, only did a few silent updates. D; I have been busy lately, and I have been working another project. I made a few optimizations is all. I also made a quick bug fix so that if you cast something like a heal or some spell on an ally unit in combat, it will put the caster in combat as well. Currently everything works fine but auras, which I'll eventually get to. I'll probably add optional support to it and just have to resort to damage detection. :ogre_icwydt:
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
So when you going to update with the buff for long ranged checking and acquisition ranged stuff?

Also, this would be good on TimerQueue

edit
Nevermind, gotta think how best to do the timers... because you are restarting the timers, TimerQueue isn't the best option. What you could do is destroy the timer and re add it, but that might end up running slower as it'd be running more than once then.

Gotta think about it ;D, but I do know that the timers could be vastly improved. One timer for each unit is crap ;P. You have a constant time, so think about it.
 
Last edited:
Level 34
Joined
Sep 26, 2009
Messages
8,435
TimerQueue works just fine :)

JASS:
library CombatState requires UnitIndexer, Tq
    //******** COMBAT STATE ******************************************//
    //  - This library registers whether or not some unit is in combat.
    //  - To be registered as in combat, the unit must attack or have been attacked by an enemy unit.
    //  - Any spell that is cast by an opposing player onto the unit will flag them as in combat.
    //  - Being in combat only lasts a specific duration, in this case 5 seconds, before the unit leaves combat.
    //  - Once a unit dies, the dying unit will be taken out of combat.
    
    //  Requirements:
    //    -- UnitIndexer by Nestharus
    //    -- TimerQueue by Nestharus
    
    //  API:
    //    Configurables:
    //       - COMBAT_DURATION: This determines the time before a unit is considered to be out of combat, default 5 seconds.
    //         The unit must remain unattacked and attack no one for COMBAT_DURATION to leave combat.
    
    //    Data Modify/Retrieve:
    //        CombatState[whichUnit].inCombat -> returns boolean
    //        set CombatState[whichUnit].inCombat = flag
    
    //    Function Wrappers:
    //        function GetUnitCombatState takes unit whichUnit returns boolean
    //           - This returns the combat state of a unit. If it returns true, the unit is in combat. If it returns false, then the unit
    //             is not in combat.
    //        function SetUnitCombatState takes unit whichUnit, boolean flag returns nothing
    //           - This allows you to force a unit in or out of combat, and it will register the corresponding events.
    
    //    If you are using Event:
    //        call CombatState.EnterCombat.register(trigger)
    //           - Registers when some unit enters combat after being out of combat.
    //        call CombatState.LeaveCombat.register(trigger)
    //           - Registers when some unit leaves combat after having been just in combat.
    //        function GetTriggerCombatUnit takes nothing returns unit
    //           - When using registering an event, this will basically return the unit who entered or left combat. GetTriggerCombatUnit()
    
    //    Credits:
    //       - Nestharus for UnitIndexer, Event, TimerQueue and optimizations
    //       - Darthfett for the original combat library
    //****************************************************************//
    
    globals
        private constant real COMBAT_DURATION = 5.
    //**************DO NOT EDIT PAST THIS POINT***********************//
        private unit combatUnit = null
        private keyword q
    endglobals
    
    function GetTriggerCombatUnit takes nothing returns unit
        return combatUnit
    endfunction
    
    private struct Leave extends array
        static constant real INTERVAL = COMBAT_DURATION
        
        CombatState cs
        
        method expire takes nothing returns nothing
            local unit p = combatUnit
            set combatUnit = GetUnitById(this.cs)
            set Tq_z[this] = true
            set this.cs.q = 0
            set this.cs = 0
            call thistype.LEAVE.fire()
            set combatUnit = p
            set p = null
        endmethod
        
        implement Tq
    endstruct
    
    private module Init
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            set thistype.ENTER = Event.create()
            set thistype.LEAVE = Event.create()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_ATTACKED)
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_DEATH)
            call UnitIndexer.DEINDEX.register(Condition(function thistype.deindex))
            call TriggerAddCondition(t, Condition(function thistype.CombatEnter))
            set t = null
        endmethod
    endmodule
    
    struct CombatState extends array
        readonly static Event LEAVE = 0
        readonly static Event ENTER = 0
        
        Leave q
        
        static method operator [] takes unit u returns thistype
            return GetUnitUserData(u)
        endmethod
        
        method operator inCombat takes nothing returns boolean
            return this.q != 0
        endmethod
        
        method operator inCombat= takes boolean flag returns nothing
            local unit p
            if (flag) then
                if (this.q != 0) then
                    call this.q.deallocate()
                endif
                set this.q = Leave.allocate()
                set this.q.cs = this
                set p = combatUnit
                set combatUnit = GetUnitById(this)
                call thistype.ENTER.fire()
                set combatUnit = p
                set p = null
            elseif (this.q != 0) then
                call this.q.expire()
            endif
        endmethod
        
        private static method CombatEnter takes nothing returns boolean
            local unit u
            if (GetTriggerEventId() == EVENT_PLAYER_UNIT_DEATH) then
                set thistype[GetTriggerUnit()].inCombat = false
                return false
            endif
            set u = GetAttacker()
            if (u == null) then
                set u = GetSpellTargetUnit()
                if (u == null) then
                    return false
                endif
            endif
            if (IsUnitEnemy(u, GetTriggerPlayer())) then
                set thistype[u].inCombat = true
                set thistype[GetTriggerUnit()].inCombat = true
            elseif (thistype[u].inCombat) then
                set thistype[GetTriggerUnit()].inCombat = true
            endif
            set u = null
            return false
        endmethod
        
        private static method deindex takes nothing returns boolean
            set thistype(GetIndexedUnitId()).inCombat = false
            return false
        endmethod
        
        implement Init
    endstruct
    
    function GetUnitCombatState takes unit whichUnit returns boolean
        return CombatState[whichUnit].inCombat
    endfunction
    
    function SetUnitCombatState takes unit whichUnit, boolean flag returns nothing
        set CombatState[whichUnit].inCombat = flag
    endfunction
    
endlibrary
 
Last edited:

Fud

Fud

Level 3
Joined
Aug 12, 2011
Messages
42
FFS someone get Nestharus moderator status :p I feel like i should have to give him credits too...
 
FFS someone get Nestharus moderator status :p I feel like i should have to give him credits too...

Given the large number of resources he owns, you're likely going to have to give him
credits anyways. UnitIndexer is a requirement for this, so you should give him credits
for that, but you shouldn't give him credits for CombatState because that would be
totally wrong ;)
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
Given the large number of resources he owns, you're likely going to have to give him
credits anyways. UnitIndexer is a requirement for this, so you should give him credits
for that, but you shouldn't give him credits for CombatState because that would be
totally wrong ;)

I honestly could care less about credits. Do it if you want, but don't care either way.


This resource still handles long range attacks pretty meh : p. Actually, it can't handle multi shot or bounce or aoe spells as well.
 
Level 6
Joined
Oct 23, 2011
Messages
182
Sweet system.

Few things:

1.

JASS:
    private module Init 
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            static if not LIBRARY_TimerUtils then
                set Hash = InitHashtable()
            endif
            set thistype.ENTER = Event.create()
            set thistype.LEAVE = Event.create()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_ATTACKED)
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_DEATH)
            call UnitIndexer.DEINDEX.register(Condition(function thistype.deindex))
            call TriggerAddCondition(t,Condition(function thistype.CombatEnter))
        endmethod
    endmodule

    struct CombatState extends array
        //...
        implement CombatStateInit
    endstruct

:ogre_hurrhurr:

2. I believe Nestharus' Event uses boolexpr instead of trigger

3. API needs to be updated (ex. LeaveCombat -> LEAVE)
 
At the moment, Warcraft 3 doesn't work on Lion so I don't have it installed atm. (unless I run bootcamp) I'll update it later on during the week if I can.

Running Bootcamp and installing Windows 7 on a Partition of my HDD was the best thing I ever did :D

I believe Nestharus' Event uses boolexpr instead of trigger

It supports triggers too :p
In fact, if it only supported triggers, it would be much faster when executing code ^.^
This is because boolexprs require enqueuing in a trigger condition queue and a trigger evaluation for execution, while triggers only require event registration and for execution, you'd have to set an integer to 0 and then to a value to execute it :D
 

PurgeandFire

Spell Moderator
Level 43
Joined
Nov 11, 2006
Messages
7,502
I do need a system to check the combat status of a unit but unfortunately this one requires Nes' Event library which requires other libraries... I wish the requirement for Event is optional. lol

I can make a no-dependency version. Just send me a PM so I remember. This script needs an update anyway.
 
Top