• 🏆 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] DelayedDummyRecycler

Level 16
Joined
Dec 15, 2011
Messages
1,423
Extension for Nestharus' Dummy that addresses quite a few annoying issues.

Thanks to Geries and BPower for their help in improving this snippet.

JASS:
library DelayedDummyRecycler /* v1.0.0.7
*************************************************************************************
*
*   Delay dummy recycling for a Dummy. This is created as an extension for Nestharus' Dummy
*   library since it does not have a feature that scales special effects' death animation.
*   
*   All delayed effects will be resolved before the dummy is recycled.
*
*************************************************************************************
*
*   Credits
*
*       Nestharus
*       -----------------------
*
*           Dummy, Alloc
*
*       Vexorian
*       -----------------------
*
*           TimerUtils
*
*       Jesus4Lyf & PurgeandFire
*       -----------------------
*
*           Stack Safety
*
*
*************************************************************************************
*
*   Function
*   -----------------------
*
*        function RecycleDummyDelayed takes Dummy dummy, real timeout, effect fx, boolean fxDead returns nothing
*
*   Description
*   -----------------------
*
*       Delay the recycling of a Dummy.
*       Use this function instead of Dummy.destroy()
*
*   Takes
*   -----------------------
*
*       Dummy dummy
*           -   The target Dummy
*
*       real timeout
*           -   The delay time before recycling is executed
*
*       effect fx
*           -   The effect currently attached to the dummy
*
*       boolean fxDead
*           -   Set to true if you want to play the death animation of the effect
*
*************************************************************************************
*
*   Function
*   -----------------------
*
*       function RecycleDummyDelayedEx takes unit dummy, real timeout, effect fx, boolean fxDead returns nothing
*
*   Description
*   -----------------------
*
*       Delay the recycling of a Dummy.
*       For those who prefer to work with units.
*       Use this function instead of Dummy.destroy()
*
*   Takes
*   -----------------------
*
*       unit dummy
*           -   The target dummy unit
*
*       real timeout
*           -   The delay time before recycling is executed
*
*       effect fx
*           -   The effect currently attached to the dummy
*
*       boolean fxDead
*           -   Set to true if you want to play the death animation of the effect
*
*************************************************************************************
*
*   */ uses /*
*   
*       */ Alloc            /* hiveworkshop.com/forums/jass-resources-412/snippet-alloc-alternative-221493/
*       */ Dummy            /* hiveworkshop.com/forums/jass-resources-412/system-dummy-213908/
*       */ TimerUtils       /* wc3c.net/showthread.php?t=101322
*
************************************************************************************/

    globals
        private integer array stackLevel
    endglobals

    private struct DelayedDummyRecycler extends array
        implement Alloc
    
        private Dummy dum
        private effect sfx
        
        private static method onExpire takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(t)

            call SetUnitScale(this.dum.unit, 1., 0, 0)
            call SetUnitVertexColor(this.dum.unit, 255, 255, 255, 255)
            call SetUnitFlyHeight(this.dum.unit, 0., 0.)
            call SetUnitTimeScale(this.dum.unit, 1.)
            
            if this.sfx != null then
                call DestroyEffect(this.sfx)
                set this.sfx = null
            endif

            set stackLevel[this.dum] = stackLevel[this.dum] - 1

            if stackLevel[this.dum] == 0 then
                call this.dum.destroy()
            endif

            call ReleaseTimer(t)
            call this.deallocate()
            set t = null
        endmethod
        
        static method create takes Dummy dummy, real timeout, effect fx, boolean fxDead returns thistype
            local thistype this = thistype.allocate()
            local timer t = NewTimer()

            set this.dum = dummy
            set this.sfx = fx
            
            if fxDead then
                call DestroyEffect(this.sfx)
                set this.sfx = null
            endif
            
            set stackLevel[this.dum] = stackLevel[this.dum] + 1
            call SetTimerData(t, this)
            call TimerStart(t, timeout, false, function thistype.onExpire)

            set t = null
            return this
        endmethod
    endstruct
    
    function RecycleDummyDelayed takes Dummy dummy, real timeout, effect fx, boolean fxDead returns nothing
        call DelayedDummyRecycler.create(dummy, timeout, fx, fxDead)
    endfunction
    
    function RecycleDummyDelayedEx takes unit dummy, real timeout, effect fx, boolean fxDead returns nothing
        call DelayedDummyRecycler.create(Dummy[dummy], timeout, fx, fxDead)
    endfunction
endlibrary

Demo #1: Scaling an effect that must be attached.

JASS:
struct Test extends array
    
    static method onEsc takes nothing returns nothing
        local unit v
        local integer i = 0
        
        loop
            exitwhen i == 12
            set v = Dummy.create(GetRandomReal(100, 300)*Cos(GetRandomReal(-bj_PI, bj_PI)), GetRandomReal(100, 300)*Sin(GetRandomReal(-bj_PI, bj_PI)), 270).unit
            call SetUnitScale(v, 3., 0, 0)
            call SetUnitFlyHeight(v, 100, 0)
            call RecycleDummyDelayedEx(v, 4, AddSpecialEffectTarget("war3mapImported\\EMPBomb.mdx", v, "origin"))
            set v = null
            set i = i + 1
        endloop
    endmethod

    static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t, function thistype.onEsc)
        set t = null
    endmethod
    
endstruct

Demo #2: Playing the death animation of an effect

JASS:
struct TestEx extends array
    
    static method onEsc takes nothing returns nothing
        local unit v
        local integer i = 0
        
        loop
            exitwhen i == 12
            set v = Dummy.create(GetRandomReal(100, 300)*Cos(GetRandomReal(-bj_PI, bj_PI)), GetRandomReal(100, 300)*Sin(GetRandomReal(-bj_PI, bj_PI)), 270).unit
            call SetUnitScale(v, 3., 0, 0)
            call SetUnitFlyHeight(v, 100, 0)
            call RecycleDummyDelayedEx(v, 7, AddSpecialEffectTarget("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl", v, "origin"), true)
            set v = null
            set i = i + 1
        endloop
    endmethod

    static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerKeyEventBJ(t, Player(0), bj_KEYEVENTTYPE_DEPRESS, bj_KEYEVENTKEY_UP)
        call TriggerAddAction(t, function thistype.onEsc)
        set t = null
    endmethod
    
endstruct
 

Attachments

  • DDR.w3x
    52.1 KB · Views: 110
Last edited:
Level 16
Joined
Dec 15, 2011
Messages
1,423
Question
What is the code variable for?

and also, why you need to code this?

Nes said it is required. I am sorry but I never work with TimerTools before so I only plugged my code into his demo code.

I need to code this because, as I said above, Nes' Dummy doesn't support scaling death animation of effects.

edit

There seems to be some pretty serious bugs with this. I will get down to fixing them immediately.
 
Last edited:
Level 12
Joined
Feb 22, 2010
Messages
1,115
If the purpose of this snippet is playing some dummy model's death animations, use a constant integer instead of timeout variable (something like LONGEST_DEATH_ANIMATION_DURATION_IN_YOUR_MAP, nice name I know)It doesnt matter the unit recycled after 10 seconds, or 100, or 1 as long as it succesfully plays death animation.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
That's because Mag hadn't been patient enough to wait Vex' update XD

Patience is not the thing. Vexorian is just not interested in warcraft 3 modding anymore. I have spoken with him about updating his systems or updating JassHelper, but he has bowed out of this game. Blame Vexorian all you want, but you'd be beating a dead horse. Taking matters into our own hands has been mandatory for a long time now.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
On a second thought, the only major problem of dummy is that death animations of attached effects are not displayed properly.
DelayedDummyRecycler doesn't solve the problem.

Nestharus decided to move the unit to the edge of the map, once one call destroy().
It's of course a neat move, because wc3 hides units outside the screen to save perfomance.
The problem is now, that you need a timeout between dummy.destroy and destroy(effect), otherwise the effect isn't displayed (it can be found at the edge of map bounds).

I my mind it's the only feature a dummy extension should offer. Scaling, color, etc ... can be done easily before you destroy the dummy.
Imo you only have to do those operations, if you change them in the first place.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
On a second thought, the only major problem of dummy is that death animations of attached effects are not displayed properly.
DelayedDummyRecycler doesn't solve the problem.

I am sorry but I don't think I understand why you said so. Would you mind elaborating? Your statement isn't supported at all by the paragraphs that follow.

Nestharus decided to move the unit to the edge of the map, once one call destroy().
It's of course a neat move, because wc3 hides units outside the screen to save perfomance.
The problem is now, that you need a timeout between dummy.destroy and destroy(effect), otherwise the effect isn't displayed (it can be found at the edge of map bounds).

I my mind it's the only feature a dummy extension should offer. Scaling, color, etc ... can be done easily before you destroy the dummy.
Imo you only have to do those operations, if you change them in the first place.

Scaling, color, etc shouldn't be changed before the dummy is destroyed because there will be a noticeable change. If you are aiming for maximum visual, those should be done only when the dummy is sent to the map bound and ready to be recycled (aka out of sight, out of mind). With that in mind, I coded this lib so that it fulfilled its intended purpose exactly.

Is there any way you think this library can be improved? I am always welcoming suggestions to better my resources.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Yeah of course, the core problem the dummy library has is, that it doesn't display attached effects death animations,
if you destroy the dummy and the effect during the same time . I.e:
JASS:
set this.dummy = Dummy.create(...)
set this.effect = AddSpecialEffectTarget("...", this.dummy.unit, "origin")
call DestroyEffect(this.effect)
call this.dummy.destroy()
--> This will not show the death animation of .effect, because the dummy unit is already at the edge of the map.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Yeah of course, the core problem the dummy library has is, that it doesn't display attached effects death animations,
if you destroy the dummy and the effect during the same time . I.e:
JASS:
set this.dummy = Dummy.create(...)
set this.effect = AddSpecialEffectTarget("...", this.dummy.unit, "origin")
call DestroyEffect(this.effect)
call this.dummy.destroy()
--> This will not show the death animation of .effect, because the dummy unit is already at the edge of the map.

Ah yes. I see your point now. My bad, I didn't notice that in the first place.

The issue has been addressed in the update I just posted. This is how I think the problem should be resolved. If I am mistaken somewhere, go ahead and speak up. Thanks in advance!
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
You're missing an API on top : (. Had to search your code for the functions /sad face.


If I understand this right, you call Recycle function for each effect you have? The dummy itself won't be destroyed until all of the timers have expired. Amirite?

You might want to document that

Sorry, my bad. I thought it would be more clear to put the explanation next to the function itself.

Yes. The dummy won't be recycled until all delayed effects have been resolved.

Documentation has been updated.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
There's a better way.

Do 1 timer and make it's timeout the timeout of the last run for the given dummy.

Put a stack on the timer. When that timer expires, destroy all of the effects in the stack.

Collections can be found in my signature. I only recommend a stack as you don't need order or random removal capabilities ;).

Forgive me if I am wrong but that doesn't seem to be an improvement. Each of the effect has a different timeout so if they are destroyed at the same time (you said destroy all effects in the stack when the timer expires) the visual would obviously look bad.

I don't think this is a resource that is so performance-intensive it needs to be heavily optimized (let's not forget that TimerUtils is chosen in the first place because it recycles timer effectively, therefore optimize this library somewhat already). This is purely for handling effects that don't work well with the Dummy lib and as far as I know there are only a few such effects. Besides, you could simply expand your own library so that it supports this feature and thus improve its overall usefulness :)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Forgive me if I am wrong but that doesn't seem to be an improvement. Each of the effect has a different timeout so if they are destroyed at the same time (you said destroy all effects in the stack when the timer expires) the visual would obviously look bad.

If an effect is timed, it should destroy itself visually no? You'd just be cleaning up the leaks ;).
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
If an effect is timed, it should destroy itself visually no? You'd just be cleaning up the leaks ;).

Ah yes of course. I didn't see that in the first place. Thanks.

Well the next issue would be choosing the current highest timeout as the stack timer. Then for example the user could add a new effect to the stack before it is destroyed. I would then need to refresh the stack, extend the timer, etc. Imho that is probably too much work for just a simple library extension.

I thank you for your enthusiasm to help me improve this but I reckon that maybe simplicity is sometimes better than efficiency, don't you think so?

I may be wrong here so feel free to speak up if I indeed am :)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
If you set boolean this.b to true, it will also be true once you allocate that instance again.
You have to set it back to false, before you call deallocate().
Another option would be to null this.sfx in RecycleDummyDelayedEx right away and check onExpire if not this.sfx == null then
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
If you set boolean this.b to true, it will also be true once you allocate that instance again.
You have to set it back to false, before you call deallocate().
Another option would be to null this.sfx in RecycleDummyDelayedEx right away and check onExpire if not this.sfx == null then

I originally thought that deallocated instances will have their data erased. Well, you learn new things everyday. Although I hope you wouldn't mind giving some in-depth explanation on why that issue happens :) (I already forgot many, many things after a long period of no coding :p)

Thanks for the tip, I decided to use the latter method since a new struct member won't be needed.

Snippet updated.
 
Top