• 🏆 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!

[Extension] Unit Event

Level 18
Joined
Jan 21, 2006
Messages
2,552
So you've basically remade AutoIndex and AutoEvents, considering this requires your unit indexer, you don't even have optional support for AutoIndex. AutoIndex is about as good as unit indexing gets.

Will eventually have an AutoEvent API to support things running on that system : D.

I see, so you want to have support for that; I still think that this is not a necessity to JASS programming, you're remaking what has already been made.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
So you've basically remade AutoIndex and AutoEvents, considering this requires your unit indexer, you don't even have optional support for AutoIndex. AutoIndex is about as good as unit indexing gets.



I see, so you want to have support for that; I still think that this is not a necessity to JASS programming, you're remaking what has already been made.

I'd disagree with AutoIndex is about as good as unit indexing gets ; ). Also, originally, this and UnitIndexer were quite different. Later in the designs, they became more similar ; ).

It's up to the community to see what they want, but I prefer these since the modules for AutoIndex are incredibly cumbersome and since AutoIndex hooks RemoveUnit and ReplaceUnitBJ and since AutoIndex has extra vars and functions in it that are only useful if you actually use AutoEvent (it needs some static ifs or something >.>).

But THW should use what THW wants ; ). If you guys enjoy all of that extra stuff, even when you don't need it, as well as the hooks (will lag a game to heck if you mass RemoveUnit), go for it ^.^.

I like to think that this one and UnitIndexer have the pros of AutoIndex/AutoEvent and AIDS with the cons of neither ^.^. Well, this does have one con... onStartReincarnate ; (. Best way to do it is with a timer /cry.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I did a hack-job for Retro (the alternative version which uses AIDS) to produce a ghetto but near-flawless Reincarnation detection engine:

JASS:
        private static method addRessurrect takes nothing returns nothing   // Coming back to life -> allocate
            local unit u = GetTriggerUnit()
            local real decay = DECAY_TIME + 10.0
            if IsUnitType(u, UNIT_TYPE_HERO) then
                set decay = HERO_REVIVE_TIME + 100.0
            endif
            loop
                call TriggerSleepAction(0.25)
                if UnitAlive(u) then
                    call retro.add(u)
                    exitwhen true
                endif
                set decay = decay - 0.25
                exitwhen decay <= 0.00
                exitwhen u == null
            endloop
            set u = null
        endmethod
        private static method removeOnDeath takes nothing returns boolean   // Death -> deallocate
            local retro this = retro.create(GetTriggerUnit()) 
            if this > 0 then
                call .remove()
                call ExecuteFunc(addRessurrect.name)
            endif
            return false
        endmethod
        
        private static method addInitial takes nothing returns boolean      // Exists -> allocate
            local retro this = retro.add(AIDS_GetEnteringIndexUnit())
            if this > 0 and .deathEvent == null then
                set .deathEvent = CreateTrigger()
                call TriggerAddCondition(.deathEvent, Filter(function retro.removeOnDeath))
                call TriggerRegisterUnitStateEvent(.deathEvent, .subject, UNIT_STATE_LIFE, LESS_THAN, 0.405)
            endif
            return false
        endmethod
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I've already investigated the possibility of that stuff and it was this line that made me not do it-

call TriggerRegisterUnitStateEvent(.deathEvent, .subject, UNIT_STATE_LIFE, LESS_THAN, 0.405)

Handle leaks anyone? : P

You need to check and see if it actually destroys the event as the unit is cleared out (which I highly doubt it does).

Believe me, I worked for a few hours trying to find a good way to get rid of that timer. I was also staring at the events going, "WHY ISN'T THERE A PLAYER UNIT STATE EVENT!"

lol ; D

How to test for it?
Create a unit and register an event to that unit and then remove that unit on a periodic timer. Use GetHandleId to see if its using the same handle ids over and over again. If it's not, you got a handle leak (the event).

It's always been my understanding that the only way to actually destroy events was to destroy the trigger.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
epic failure on design : P (now that I read the code)

your loop is entirely unneeded... if the unit is alive and it wasn't marked as dead, then it reincarnated (mark it as dead in the death trigger). If not, you're done. This is done with a TimerStart and an array on mine rather than executing a function and putting that process to sleep for 0 seconds.

You also need to remember that mine is only ever running one timer whereas yours has something for every single unit ; |.

What's faster? A bundle of thingies going off as they become active again or a single thing firing off with a loop? : P

Furthermore, which has less overhead? (single timer/single trigger vs an array of triggers).

And something else interesting... I think UnitAlive is just a wrapper for not IsUnitType(whichUnit, UNIT_TYPE_DEAD) : P, so I think UnitAlive is actually slower, haha ; D. It's like DisplayTextToPlayer is a wrapper for DisplayTimedTextToPlayer.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Nestharus, are you on drugs?

This isn't for detecting the start of reincarnation (though it detects unit that reincarnate which EVENT_PLAYER_UNIT_DEATH can't catch), it's detecting when the unit regains its hit points.

This uses the same dynamic-trigger approach Jesus4Lyf uses in Damage, and has no more overhead than that spell.

UnitAlive returns false and IsUnitType(u, UNIT_TYPE_DEAD) returns false in the same condition sometimes, so UnitAlive is definitely its own thing. Nice try. Nestharus, are you on drugs?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Er... you can check when the unit regains its health with EVEN_UNIT_DEATH and reincarnating flag...

on undefend, if reincarnating == true, then it reincarnated... ...

the only thing in Unit event that needs improvement is start reincarnate. The rest are just simple boolean checks...

how can all of your stuff be faster than a boolean check eh? : P

if that is the case, I'd have to upgrade your design to super epic design failure ^.^.

Also, remember I said that UnitAlive was probably a wrapper, not that it was ; ).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Super epic design failure, eh? I fail to see how jumping on the bandwagon of "undefend" makes everything super epic fail, because I don't like to "test" things into existence, I like to see exactly what I'm working with so that if I come across a similar problem in the future I have a logical solution to it.

An "undefend" detection is not going to help in real scope.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Super epic design failure, eh? I fail to see how jumping on the bandwagon of "undefend" makes everything super epic fail, because I don't like to "test" things into existence, I like to see exactly what I'm working with so that if I come across a similar problem in the future I have a logical solution to it.

An "undefend" detection is not going to help in real scope.

Regardless, using it results in less overhead in memory and in execution time ; ).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Why'd you submit UnitEvent if you're basically copying what everyone else is doing?

For me, I use unit indexing from other peoples' libraries (AutoIndex, AIDS) because those are already in-practice by many and mostly I want access to UnitUserData. If UnitIndexer gets implemented by people, fine, I'll make a version for it, too, but the problem is is that there are so many of these kinds of things (one is more than enough) that less is truly more.

You've got a lot of skills in the JASS field that I feel are just being thrown away by creating something anew that's been done before, so I recommend using them towards improving the already-existing systems, like giving grim_001 some tips on improving his system or giving Jesus4Lyf tips to improve his system.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You've got a lot of skills in the JASS field that I feel are just being thrown away by creating something anew that's been done before

I sadly don't operate like that ; ). I mostly code things on a whim. For this entire project (this plus UnitIndexer), I was in the mood to create a unit indexing system. I was also in the mood to create an item indexing system, but that bore no fruit ; P.

I could have taken what I deigned and gone to each and every thread for systems in the same genre sharing my thoughts, but I just submitted my code because that was a lot less work plus I like to show what I did for the hell of it (makes it feel like it's not a waste of time, even though it is :p).
 
Level 8
Joined
Oct 3, 2008
Messages
367
This is rather nice, and pretty good naming. However, the fact that it requires your indexer makes it hard to decide what to do with this. :con:

Any way you could make it require something more generally used, or even nothing at all?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Why does this need to be a seperate library from UnitIndexer? I'm curious, if a hero dies with reincarnation, will the onDeath event fire?

I just need: HP goes below zero; unit comes back to life; unit enters/exits transport (and of course the events provided by unit indexer).

no, because reincarnation doesn't fire EVENT_UNIT_DEATH. You can use the start reincarnation event though : ).

JASS:
//
    private method unitStartReincarnate takes nothing returns nothing
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, I2S(this)+":"+ GetUnitName(unit) + " is reincarnating")
    endmethod
    
    private method unitReincarnate takes nothing returns nothing
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, I2S(this)+":"+ GetUnitName(unit) + " reincarnated")
    endmethod

And this isn't part of Unit Indexer because it is another system: Unit Indexing shouldn't be capturing unit events as well, it should only do unit indexing.

unit enters/exits transport

That can be standalone. This pretty much only does the events that must be coupled together ^.^. I suppose I can look more into it (haven't actually tried it all, but I know there are plenty of LOAD and UNLOAD events in wc3).
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
How's

JASS:
struct MyStruct extends array
    implement UnitEventStruct
endstruct

ugly?

It's less ugly than the API by AIDS and around the same as that by AutoIndex and AutoEvent : | (though the modules are much more optimal in the background and the system code runs a lot faster ^_^)

Then you can make these methods inside of the struct to run the events
JASS:
//
    private method unitDeath takes nothing returns nothing
    private method unitRemove takes nothing returns nothing
    private method unitDecay takes nothing returns nothing
    private method unitExplode takes nothing returns nothing
    private method unitResurrect takes nothing returns nothing
    private method unitStartReincarnate takes nothing returns nothing
    private method unitReincarnate takes nothing returns nothing
    private method unitAnimate takes nothing returns nothing

And read this variable in the struct to get the triggering unit: readonly unit unit

Then, if you don't want to use structs, you can use the functions
JASS:
function OnUnitDeath takes boolexpr c returns nothing
function OnUnitRemove takes boolexpr c returns nothing
function OnUnitDecay takes boolexpr c returns nothing
function OnUnitExplode takes boolexpr c returns nothing
function OnUnitResurrect takes boolexpr c returns nothing
function OnUnitStartReincarnate takes boolexpr c returns nothing
function OnUnitReincarnate takes boolexpr c returns nothing
function OnUnitAnimate takes boolexpr c returns nothing

They seem pretty straight forward

To retrieve the triggering unit in the functions, you use
JASS:
function GetEventUnit takes nothing returns unit
function GetEventUnitId takes nothing returns integer

Let me know why you think the API looks bad : P.


Are you meaning that you prefer function interfaces, like that used by many at wc3c and TH?

Function interfaces would add quite the overhead, both in performance and in memory ; P.


Or are you referring to the current installation code ;D. Right now, a conversation is going on about that actually ^_^.

http://www.hiveworkshop.com/forums/triggers-scripts-269/future-vjass-coding-186203/

Although, the likelihood of that happening is largely dependent upon me figuring this out

http://www.hiveworkshop.com/forums/triggers-scripts-269/lua-how-get-maps-filename-186311/
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Well, that would require a trigger loop in the background vs a single TriggerEvaluate, thus the overhead. This is why most JASS code just takes a boolexpr instead of registering a trigger.

A trigger loop, according to people who have clocked it at wc3, is about 20% slower than a single TriggerEvaluate. Furthermore, it takes up a ton more memory as each of the trigger groups (a trigger is a group of triggers in the background) has to be both created and stored.

So this is n triggers on top of n boolexprs on top of a loop vs 1 trigger with n boolexpr and a TriggerEvaluate ; ).

The current method is the most optimal way to do it.

Trigger events can also be layered on top of the current API as well as TriggerUnitEvent, TriggerPlayerUnitEvent, etc.


To prove that I am not alone in doing this, I shall provide examples ; P

function OnUnitMove takes boolexpr b returns nothing from http://www.hiveworkshop.com/forums/jass-functions-413/isunitmoving-178341/

public function RegisterOnEnterAllocated takes boolexpr b returns triggercondition

From AIDS http://www.thehelper.net/forums/showthread.php/130752-Advanced-Indexing-Data-Storage

And I don't want to search for more ; P.

Now, if you look, when using Event, IsUnitMoving lib does support trigger registration =), but anyways.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
edit
Writing it up for you, I'll have the submission up soon : )

edit 2
do you want function interfaces as well? : |

edit 3
104 new global variables later, I have finished the 2 API extensions.

Sure, I could have done it in less, but this way you can have 8191 triggers to run on each type of event rather than 8191 total for all ;o.

edit 4
updating to only use like 15 globals >.>

So yea, I wouldn't use the extensions atm as I'm changing around the API pretty hardcore ; P. It'll run even faster than it did before and use way less vars ;D.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Updated Trigger Extension API : |.

I didn't use Event because I am totally insane >.>, rofl.

8 new event vars in Trigger event
JASS:
constant integer UNIT_EVENT_DEATH = 0
        constant integer UNIT_EVENT_REMOVE = 1
        constant integer UNIT_EVENT_DECAY = 2
        constant integer UNIT_EVENT_EXPLODE = 3
        constant integer UNIT_EVENT_RESURRECT = 4
        constant integer UNIT_EVENT_START_REINCARNATE = 5
        constant integer UNIT_EVENT_REINCARNATE = 6
        constant integer UNIT_EVENT_ANIMATE = 7

Rather than a huge chain of elseifs, it now uses each one as the head to a linked list. If you don't know what I'm talking about, it doesn't really matter.

Lowered the massive amount of code down to a small amount of code.

14 globals
4 functions

Down from like.. 12 functions and 57ish globals : P.

On the plus side, the fact that I didn't use Event means that (8191-8) custom unit events can be made ;D.

This is an extension because I don't see why anyone wuold use triggers for a system like this ; P, but someone demanded it and I did it : |. There is 0 extra code in UnitEvent w/o the lib, so no worries.

Triggers run properly, get destroyed properly, and etc, so no worries on triggers. If you destroy a trigger and it's in the system, it'll destroy itself.

There is much less overhead in just passing in a boolexpr, so only do the triggers if you really need that dynamic code action (no idea why you would >.>).

I don't know yet if I'll do this for UnitIndexer because UnitIndexer is even more general and I want to be lazy right now... >.<. I guess if there is some serious demand, I could possibly do it ; P.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
exitwhen end == true

You must really be sleepy ;)

Also, you don't need two locals. Just do this:

JASS:
    public function RunEvent takes integer i returns nothing
        loop
            set i = next[i]
            exitwhen end[i]
            if (IsTriggerEnabled(trig[i])) then
                if (TriggerEvaluate(trig[i])) then
                    call TriggerExecute(trig[i])
                endif
            else
                call EnableTrigger(trig[i])
                if (IsTriggerEnabled(trig[i])) then
                    call DisableTrigger(trig[i])
                else
                    set previous[next[i]] = previous[i]
                    set next[previous[i]] = next[i]
                    set trig[i] = null
                    set next[i] = recycle
                    set recycle = i
                endif
            endif
        endloop
    endfunction

And please never do this for UnitIndexer. Tooltip seems to be a nutter for wanting a setup like this in the first place.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
exitwhen end[i] is correct, but exitwhen end[i] == true works as well, although it is slower.

But you are right about not needing 2 locals.

But I doubt many will use that API extension : P

I know I probably wouldn't use any resource that used that API extension : D, lol. In fact, I know I wouldn't use any resource that used that API extension. I guess it was added for more of the direct map making environment by the request of mappers ;p.

And please never do this for UnitIndexer. Tooltip seems to be a nutter for wanting a setup like this in the first place.

If I did it for UnitIndexer, it'd be an extension just like it is for UnitEvent, meaning if you don't have it, it's 0 extra code. This is the reason why I didn't protest to it. I would protest to it being added directly to UnitEvent though; that I will never do.

Furthermore, this setup is actually quite common. You use this setup too : P. Don't you know what jesus4lyf's Event does? You use it in IsUnitMoving.

I guess you didn't notice my creative use of the linked list with the recycler ; P. When I use a linked list or a stack or a queue, I always change the recycler around to take advantage of it so that it turns into a single variable rather than a variable + an array.

Anyways, I was trying to rewrite these systems, but the rewrites seemed worse. Nicer API sure, less functions, but more memory : |. I was thinking of OnUnitIndexEvent where it takes an event and then looks up the specific triggers to add the boolexpr to via an array, but you did mention that array reads are slower than var reads, so that'd turn the trigger evaluates into an array read. It wouldn't cut back any variables either due to the constants, but it would cut back in functions.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Updated UnitIndexer and UnitEvent to cut out one global variable array as per Bribe's suggestion ; ).

No need to reinstall for this update (just cnp). However, if you have not updated to LUA_GET_VAR_OBJECT 2.0.1.0, then you should update to that asap and reinstall both scripts. Be sure to delete the objects generated by these scripts (they should be quite obvious in object editor).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I suggest optimizer be fixed to support the features =). GetWidgetLife with a comparison would be slower than a simple UnitAlive.


There are other working natives in AI pack besides UnitAlive. Also keep in mind that RtC allows one to define new natives.

http://www.hiveworkshop.com/forums/jass-functions-413/snippet-working-ai-natives-184297/ for ai natives list, some of them being extremely useful.


Making this use GetWidgetLife is not the answer.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
CreateUnit could just be CreateCorpse so that you don't have to bother with killing it right
afterwards.

Also, this check is bad:

.405<GetWidgetLife(u) and 0!=GetUnitTypeId(u)

The unit's HP could have been set, and you don't need to check if it has a unit type id
because you already checked if it has the undefend ability or not. You also should know
that a unit without a type id is already out of scope and couldn't possibly have fired the
event.

The solution is to just use IsUnitType(u, UNIT_TYPE_DEAD). It's definitely the most
suitable method here.

Also, IsUnitDeadById, IsUnitReincarnatingById, IsUnitAnimatedById.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Well thanks for clearing that up, that's the beauty of warcraft 3 you learn something new
all the time.

Anyway, that wouldn't apply for UnitEvent anyway. CreateCorpse creates a dead unit that fires an undefend right after it enters the world bounds, then decays 90 seconds (or
whatever) afterwards.

Is it really that important to detect when a unit decays versus when it's just removed or
exploded? This decay detection should really just be its own resource. Or you could hook
RemoveUnit/ReplaceUnitBJ like I recommended before. That way Unit Indexer won't bug
with removing paused units from transports as well. In fact hooking RemoveUnit will
probably be faster considering how seldom it's used compared to all these checks in your system.
 
Top