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: 345

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.
  • Ghan Ghan:
    Howdy
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top