System Combat State

PurgeandFire

zxcvmkgdfg
Reaction score
509
Combat State

It is a quick, awesome library that allows units to enter and leave combat, and allows you to register it.

It is based off of Darthfett's library, which is sadly outdated: :(
http://www.thehelper.net/forums/showthread.php/91529-In-Combat-Status

So I decided to make one, and I added a few features.

Basically, if you attack something with your unit or get attacked by an opposing player, you enter combat. To leave combat, your unit must not be attacked and must not attack anyone for COMBAT_DURATION, which is default 5 seconds.

Requirements:

If you have TimerUtils, it will recycle timers. If you don't have TimerUtils, then it will use hashtable attachment and destroy/create timers each time a unit enters combat.

Code:
JASS:
library CombatState requires AIDS, 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.
    
    //  Requirements:
    //    -- AIDS by Jesus4Lyf
    //    -- Event by Jesus4Lyf
    //       - 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.ENTER.register(trigger)
    //           - Registers when some unit enters combat after being out of combat.
    //        call CombatState.LEAVE.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:
    //       - Jesus4Lyf for Event and AIDS
    //       - Darthfett for the original combat library
    //       - Vexorian for TimerUtils
    //       - Nestharus for optimizations
    //****************************************************************//

    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
    
    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               = GetIndexUnit(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 this.inCombatV = flag
            set combatUnit     = GetIndexUnit(this)
            if flag then
                if this.combatTimer == null then
                    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)
            else
                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
            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 and IsUnitEnemy(u,GetTriggerPlayer()) then
                set thistype[GetTriggerUnit()].inCombat=true
                set thistype<u>.inCombat=true       
            endif
            set u = null
            return false
        endmethod
        
        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 TriggerAddCondition(t,Condition(function thistype.CombatEnter))
        endmethod
    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
    function TriggerRegisterEnterCombat takes trigger t returns nothing
        call CombatState.ENTER.register(t)
    endfunction
    function TriggerRegisterLeaveCombat takes trigger t returns nothing
        call CombatState.LEAVE.register(t)
    endfunction
endlibrary</u>


Usage:

The documentation should explain most of it, but here is the main API:
JASS:
set CombatState[whichUnit].inCombat = flag //force unit in/out of combat
call CombatState.ENTER.register(whichTrigger) //register enter combat
call CombatState.LEAVE.register(whichTrigger) //register leave combat
call TriggerRegisterEnterCombat(whichTrigger) //wrapper for above
call TriggerRegisterLeaveCombat(whichTrigger) //wrapper for above
call SetUnitCombatState(whichUnit,flag) //force unit in/out of combat, wrapper
call GetUnitCombatState(whichUnit) //retrieves whether or not a unit is in combat
call GetTriggerCombatUnit() //triggering unit corresponding to the combat events


When creating a trigger and registering an enter or leave event, the unit corresponding to the event can be found by using GetTriggerCombatUnit().

Example of Usage:
JASS:
function Example takes nothing returns boolean
    if GetUnitTypeId(GetTriggerCombatUnit())==&#039;hfoo&#039; then //if the combat unit is a footman
        call SetUnitExploded(GetTriggerCombatUnit(),true) //explode him
        call KillUnit(GetTriggerCombatUnit())
    endif
    return false
endfunction

function InitTrig_TestEnterCombat takes nothing returns nothing
    local trigger t = CreateTrigger()
    call CombatState.ENTER.register(t) //register a unit entering combat
    call TriggerAddCondition(t,Condition(function Example))
endfunction

You don't have to give credits if you use this in your map, but you can if you want.

Attached below is a demo map that features some spells that change based on whether or not you are in combat. You can press ESCAPE to force-swap whether you are in or out of combat. You automatically leave combat after 5 seconds.

Enjoy. :D
 

Attachments

  • CombatSysTH.w3x
    71.3 KB · Views: 340

tooltiperror

Super Moderator
Reaction score
231
Why Event? Just fire off your own triggers, it's simple and easier and doesn't really deserve a library requirement.

And super ugly interface.

Code:
// Force API
function CombatForceUnit(unit u, boolean flag) // Force the unit in or out of combat

// Event API
function TriggerRegisterUnitCombatEvent(trigger t)
function TriggerUnRegisterUnitCombatEvent(trigger t, boolean flag)

// Event Responses
function GetCombatChange() // true for entering combat, false for leaving combat
function GetCombatUnit() // The entering/leaving unit
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Why Event? Just fire off your own triggers, it's simple and easier and doesn't really deserve a library requirement.

What is wrong with Event? I guess I can remove it as a requirement, but that is annoying work. >:\

And super ugly interface.

At least my interface makes sense. :p
I can add wrappers I guess for TriggerRegister, but that is the only thing that needs a wrapper. I had to make the names very explicit to make sure that the users would know what they are doing. CombatForceUnit doesn't make as much sense as SetUnitCombatState. =P

But I'll look into it.
 

tooltiperror

Super Moderator
Reaction score
231
Yeah, they were just suggestions to how it should look, not very good names themselves, hah.
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Okay, here is the wrapper API:
Code:
call TriggerRegisterEnterCombat(whichTrigger) //register when any unit enters combat, wrapper
call TriggerRegisterLeaveCombat(whichTrigger) //register when any unit leaves combat, wrapper
call SetUnitCombatState(whichUnit,flag) //force unit in/out of combat, wrapper
call GetUnitCombatState(whichUnit) //retrieves whether or not a unit is in combat
call GetTriggerCombatUnit() //triggering unit corresponding to the combat events
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Bump. I updated it so now the unit leaves combat when they die. :p Also, removed the AIDS textmacro as it wasn't needed.
 

jrhetf4xb

Member
Reaction score
12
Somehow I think that this will also put a unit in combat when it's targeted by a healing spell, right?
When in fact it doesn't really enter combat... :p
 

Nestharus

o-o
Reaction score
84
Somehow I think that this will also put a unit in combat when it's targeted by a healing spell, right?
When in fact it doesn't really enter combat... :p

PurgeandFire already knows about the current accuracy problems with the script and I've been helping him fix them.

This script was submitted to both TH and THW, so to keep updated on the current problems and previous fixes, you should check out both threads ; ).

http://www.hiveworkshop.com/forums/submissions-414/combat-state-186673/

He's currently applying another fix I brought up... neither of us have thought up a good way to detect between combat and non-combat spells, but we're doing what we can ; P. Just putting this up so that people know that PurgeandFire already knows about all of the script's current problems, so no need to bring them up ; ).

These are the last two posts containing fixes and problems if you don't want to read through the thread ; )

->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.

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).

And yes, for anyone wondering where I ran off to, it was THW : P.
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Yep, I'm a little busy at the moment but I'll be sure to update it asap around this weekend.

Somehow I think that this will also put a unit in combat when it's targeted by a healing spell, right?
When in fact it doesn't really enter combat...

At the moment, this only considers spells cast against an enemy unit to be putting yourself in combat. Heals should work fine afaik.
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
For the most part, I stopped updating the system because it became so intertwined with Forest CTF. I have a struct wrapper for the Unit type, so I have saved many variables, including their 'combat state' in this system.

While it may not be in the best state, as Forest CTF is not functional, you may find the code helpful.

It doesn't have the events in this file, but the onCast method is run whenever a unit uses a spell. In it, I detect whether there is no target, and whether the unit is also in combat. If both are true, it puts the unit in combat. You could consider creating a boolean option for this.

It also detects whether the target is friendly (both owners have the same team), and if the target is friendly and in combat, it will put him in combat.

Just a couple ideas for you.
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
I updated it a bit. I changed the test map a bit and added support for this:
It also detects whether the target is friendly (both owners have the same team), and if the target is friendly and in combat, it will put him in combat.

I had previously forgot to implement that, but now I've added it. =D

At the moment, this should work pretty accurately for anything but point casts, instant casts, and auras. (it doesn't support them since the combat state really depends on the spell) So, at the moment you will have to force the caster in combat manually if you want supports for non-unit-cast spells.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top