• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Snippet] [Needs work] OnUnitEvent

Level 6
Joined
Jun 20, 2011
Messages
249
JASS:
//! zinc
library OnUnitEvent requires /*

*/  DamageEvent             /*  hiveworkshop.com/forums/jass-functions-413/snippet-damageevent-186829/
*/ ,UnitIndexer             /*  hiveworkshop.com/forums/jass-functions-413/system-unit-indexer-172090/
*/ ,RegisterPlayerUnitEvent /*  hiveworkshop.com/forums/submissions-414/commonevent-203338/

//  A snippet that provides useful tools for incomplete events such as
//  Unit Takes Damage or Unit Attacked and completes them with their
//  respective opposite event (Unit Deals Damage or Unit Attacks)
***********************************************************************

    API

    function OnUnitDamage takes unit whichUnit, code callback returns triggercondition
    function OnUnitAttack takes unit whichUnit, code callback returns triggercondition
    function OnUnitKill takes unit whichUnit, code callback returns triggercondition
//      Everytime an unit deals damage, attacks or kills another unit the given condition
//      will be fired, use the DamageEvent API to return event values, such as DamageEvent.source.
//      Remember that it takes CODE but it must return boolean same as if it were a
//      condition. Returns triggercondition, store it in a var if you intend to remove the
//      event later.
    
    function RemoveOnUnitDamage takes unit whichUnit, triggercondition whichCondition returns nothing
    function RemoveOnUnitAttack takes unit whichUnit, triggercondition whichCondition returns nothing
    function RemoveOnUnitKill takes unit whichUnit, triggercondition whichCondition returns nothing
//      Takes the previously returned triggercondition and removes the proper event.
    
    function ClearOnUnitDamage takes unit whichUnit returns nothing
    function ClearOnUnitAttack takes unit whichUnit returns nothing
    function ClearOnUnitKill takes unit whichUnit returns nothing
//      This happens automatically when the unit is deindexed, but if you wish to
//      clear all the evaluation list for a given unit, use this.

**********************************************************************/

//  How does it work: it creates a new trigger the first time you attach a condition
//  to an unit, this trigger will never be destroyed, but constantly recycled, and it
//  adds the given boolexpr as a condition to it, this trigger is stored inside an array
//  with the unit's user data as it's index. Every time the global event fires it checks if
//  the unit triggering the event matches, if so, it fires it and therefore all the
//  conditions are evaluated. If the unit is deindexed the trigger's condition are cleared
//  and it's ready for reuse for the next unit.
{
    trigger   a[];
    trigger   b[];
    trigger   c[];
    
    //! textmacro OnUnitEvent_MethodSetUp takes NAME,VAR
    function OnUnit$NAME$(unit u,code f)->triggercondition{
        integer i=GetUnitUserData(u);
        if(null==$VAR$[i]) $VAR$[i]=CreateTrigger();
        return TriggerAddCondition($VAR$[i],Filter(f));}
        
    function RemoveOnUnit$NAME$(unit u, triggercondition tc){
        TriggerRemoveCondition($VAR$[GetUnitUserData(u)],tc);}
    
    function ClearOnUnit$NAME$(unit u){
        TriggerClearConditions($VAR$[GetUnitUserData(u)]);}
    //! endtextmacro
    public{
    //! runtextmacro OnUnitEvent_MethodSetUp("Damage","a")
    //! runtextmacro OnUnitEvent_MethodSetUp("Attack","b")
    //! runtextmacro OnUnitEvent_MethodSetUp("Kill" , "c")
    }
    
    function onDamage()->boolean{
        TriggerEvaluate(a[DamageEvent.sourceId]);
        return false;}
        
    function onAttack()->boolean{
        integer i=GetUnitUserData(GetAttacker());
        TriggerEvaluate(b[i]);
        return false;}
    
    function onDeath()->boolean{
        integer i=GetUnitUserData(GetKillingUnit());
        TriggerEvaluate(c[i]);
        return false;}
        
    function onRemoval()->boolean{
        TriggerClearConditions(a[GetIndexedUnitId()]);
        TriggerClearConditions(b[GetIndexedUnitId()]);
        TriggerClearConditions(c[GetIndexedUnitId()]);
        return false;}
        
    module Init{
        static method onInit(){
            RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED,function onAttack);
            RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH,function onDeath);
            RegisterEvent(Condition(function onDamage),DamageEvent.ANY);
            RegisterUnitIndexEvent(Condition(function onRemoval),UnitIndexer.DEINDEX);}
    }

    struct Initializer[]{
        module Init;}
}
//! endzinc
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Yes you can.

Since you know it, you just should have say that it will stops the whole trigger evaluation if you remove the condition during its evaluation.
So yeah he should edit his code in order to prevent this case, but an explanation on the "why" was needed.
(or i'm missing something, but you are still not clear though)

Hurrah : short ugly names and RegisterAnyUnitEventBJ inlined, awesome !
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
JASS:
            local integer i=0
            local trigger trig=CreateTrigger()
            loop
                exitwhen i==bj_MAX_PLAYER_SLOTS
                call TriggerRegisterPlayerUnitEvent(trig,Player(i),EVENT_PLAYER_UNIT_ATTACKED,null)
                set i=i+1
            endloop
            set trig=null

->>
JASS:
            local trigger t=CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
            set t = null
Inlining AnyUnitEventBJ gives you only one advantage: you lose more memory.
.. woops, shouldn't that be disadvantage?

Actually, couldn't this be just: call TriggerRegisterAnyUnitEventBJ(CreateTrigger(), EVENT_PLAYER_UNIT_ATTACKED)?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
What ?!

There is only one trigger, i don't get what you are trying to mean.
Or if you're saying that Spinnaker didn't add the TriggerAddCondition and the other stuff, it's just because it was obvious, the point was to say that the inline of the BJ is useless, no more, no less.
 
I meant that he has to use a local trigger because Dirac is referring to the trigger twice when he does this:

JASS:
local trigger trig=CreateTrigger()
            loop
                exitwhen i==bj_MAX_PLAYER_SLOTS
                call TriggerRegisterPlayerUnitEvent(trig,Player(i),EVENT_PLAYER_UNIT_ATTACKED,null)
                set i=i+1
            endloop
            call TriggerAddCondition(trig,Condition(function onAttack))

Plus, since he's registering it in a loop, he needs the local or else he'd be creating 16 seperate triggers.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Ok, so there is two solutions.

1) I have some shit on my eyes
2) You have some shit on your eyes.

Spinnaker used the name "t" when Dirac used "trig", except the BJ inline or not that's pretty much the only difference.
And his post has not be edited (or the third solution is that he has edited just before you posted your comment)

EDIT : Ok nervermind it's the first solution i didn't read Spinnaker's last line, only the first codes ...

is such an evil tag (i could'nt find a better excuse)
 
Level 6
Joined
Jun 20, 2011
Messages
249
So should i really inline the PlayerUnitEvent calls? All i know is that function calling in jass is quite slow and i see no reason of why it shouldn't be inlined. Other than that, is there anything else the snippet needs?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
You realize that it's called only one time during the map initialization ?
And anyway if you inline it you will win few ms and even probably much less, while you will increase the script size.

There is really no reason to inline it ...
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Inlining it increases your map size by quite a bit (half a kilobyte per call)
Usually, you'd have about 300-500 calls to that function at map initialization in huge maps.
You could save 250KB ;)

This.

Although 300-500 calls is a bit excessive, especially if you are not being smart and using something like CommonEvent! I remembered this time ;)
 
Level 6
Joined
Jun 20, 2011
Messages
249
Very well then, updated the resource.
-Instead of boolexpr the functions now take code.
-Instead of a triggercondition the functions now return an integer.
-The snippet now uses RegisterPlayerUnitEvent or CommonEvent
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Looks really good.

So the "Unit Damaged" and "Unit Attacked" triggers runs off of
the attacking unit, that's an interesting approach for me, but it
makes sense right, if you have a hero that you want to apply a
critical strike for.

I'd recommend using Filter() instead of Condition() because it
is not so long a function name, meaning slightly less map file
size and slightly better performance.

Though I am unsure when the "unit is attacked" event is even
useful.
 
Level 6
Joined
Jun 20, 2011
Messages
249
Well there's no "Unit Attacks" nor "Unit Deals Damage" that would be the opposite events to "Unit is Attacked" or "Unit Takes Damage". But i just realized there's no opposite event to "Unit Dies" so i might end up adding "Unit Kills" which would be OnUnitKill

EDIT: updated. Added OnUnitKill, now coded in Zinc. Reverted the change that made functions return integers instead of triggerconditions because the recycle wasn't being properly performed when all the conditions were removed from the trigger.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
    function OnUnit$NAME$(unit u,code f)->triggercondition{
        integer i=GetUnitUserData(u);
        boolexpr be=Filter(f);
        triggercondition tc;
        if(null==$VAR$[i]) $VAR$[i]=CreateTrigger();
        tc=TriggerAddCondition($VAR$[i],be);
        be=null;
        return tc;}
->
JASS:
    function OnUnit$NAME$(unit u,code f)->triggercondition{
        integer i=GetUnitUserData(u);
        if(null==$VAR$[i]) $VAR$[i]=CreateTrigger();
        return TriggerAddCondition($VAR$[i],Filter(f));}
 
JASS:
module Init{
        static method onInit(){
            boolexpr b1=Condition(function onDamage);
            boolexpr b2=Condition(function onRemoval);
            RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED,function onAttack);
            RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH,function onDeath);
            RegisterEvent(b1,DamageEvent.ANY);
            RegisterUnitIndexEvent(b2,UnitIndexer.DEINDEX);
            b1=null;
            b2=null;}
    }

->

JASS:
module Init{
        static method onInit(){
            RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED,function onAttack);
            RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH,function onDeath);
            RegisterEvent(Condition(function onDamage),DamageEvent.ANY);
            RegisterUnitIndexEvent(Condition(function onRemoval),UnitIndexer.DEINDEX);
        }
    }
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
This name sounds too similar to UnitEvent. Maybe it should be named something unique like BattleEvent or something, because it only has to do with killing and attacking.

The triggers being recycled could be optional, like "static if RECYCLE_TRIGGERS".

Also your curly brackets are bleh, either make it vJass or do the curly brackets right:

JASS:
nothing foo() {
    //content
}
 
Top