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

This is useful for recycling dummies efficiently. You can have up to 8191 unit recyclers.
I'd suggest using Bribe's MissileRecycler if you have a projectile system because this system only deals with giving a dummy unit fresh off a stack.
Bribe's system actually takes into account facing.
If you need simple unit recycling, just use this.

JASS:
/**********************************************
*
*   DummyRecycler
*   v2.0.0.0
*   By Magtheridon96
*
*   - Allows the creation of a unit recycler given 
*   a unit id and a player.
*
*   Optionally Requires:
*   --------------------
*
*       - Table by Bribe:
*           - hiveworkshop.com/forums/jass-functions-413/snippet-new-table-188084/
*
*   API:
*   ----
*
*       - struct DummyRecycler
*
*           - static method register takes player whichPlayer, integer unitId returns thistype
*               - Creates a new recycler given a player, a unit Id and a facing angle.
*           - method get takes nothing returns unit
*               - Takes a unit from the stack.
*           - method release takes unit whichUnit returns nothing
*               - Throws a unit into the stack.
*           - method preload takes integer amount returns nothing
*               - Preloads a number of units into the stack.
*
**********************************************/
library DummyRecycler requires optional Table
    
    struct DummyRecycler extends array

        private static thistype current = 0

        static if LIBRARY_Table then
            private Table array stack
        else
            private static hashtable ht = InitHashtable()
        endif

        private static player array owner
        private static integer array id
        private static integer array index
        
        method preload takes integer amount returns nothing
            local integer i = 0
            local unit u
            loop
                set u = CreateUnit(owner[this], id[this], 0, 0, 0)
                static if LIBRARY_Table then
                    set stack[this].unit[index[this]] = u
                else
                    call SaveUnitHandle(ht, this, index[this], u)
                endif
                call ShowUnit(u, false)
                set index[this] = index[this] + 1
                set i = i + 1
                exitwhen i == amount
            endloop
            set u = null
        endmethod
        
        static method create takes player p, integer i returns thistype
            set current = current + 1
            set owner[current] = p
            set id[current] = i
            set stack[current] = Table.create()
            set index[current] = 0
            return current
        endmethod
        
        method get takes nothing returns unit
            static if LIBRARY_Table then
                if index[this] == 0 then
                    set stack[this].unit[0] = CreateUnit(owner[this], id[this], 0, 0, 0)
                else
                    set index[this] = index[this] - 1
                    call ShowUnit(stack[this].unit[index[this]], true)
                endif
                return stack[this].unit[index[this]]
            else
                if index[this] == 0 then
                    call SaveUnitHandle(ht, this, 0, CreateUnit(owner[this], id[this], 0, 0, 0))
                else
                    set index[this] = index[this] - 1
                    call ShowUnit(LoadUnitHandle(ht, this, index[this]), true)
                endif
                return LoadUnitHandle(ht, this, index[this])
            endif
        endmethod
        
        method release takes unit u returns nothing
            call ShowUnit(u, false)
            static if LIBRARY_Table then
                set stack[this].unit[index[this]] = u
            else
                call SaveUnitHandle(ht, this, index[this], u)
            endif
            set index[this] = index[this] + 1
        endmethod
        
    endstruct
    
endlibrary

Demo Code:

JASS:
struct Demo extends array

    private static DummyRecycler stack
    
    private static method lp takes nothing returns nothing
        local unit u = stack.get()
        call SetUnitX(u, 256)
        call SetUnitY(u, 0)
        // creating an effect to confirm the unit's presence :P
        call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl", GetUnitX(u), GetUnitY(u)))
        call stack.release(u)
        set u = null
    endmethod

    private static method onInit takes nothing returns nothing
        // create a new recycler type
        set stack = DummyRecycler.create(Player(15), 'Hpal')
        // preload units to increase performance later.
        call stack.preload(10)
        
        // just for the vision ;D
        call CreateUnit(Player(0), 'Hpal', 0, 0, 0)
        
        call TimerStart(CreateTimer(), 1, true, function thistype.lp)
    endmethod
    
endstruct

Feel free to comment..
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
A simple array would suffice. The lag of creating more than 1000 units let alone over 9000 omg.

This should also index recycled dummy units by facing angle, probably setting a facing angle of 0, 90, 180 and 270 would be enough. All it would need is half-second timer or whatever to get it in position. Otherwise arrows could do an in-air spin which looks ridiculous.

I might have to build that one for you because it sounds difficult when I explain it, but I don't think it's too much to code.

Ah, got to get back to reviewing contest entries.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
You didn't understand what Bribe meant.

The problem is, you cannot instantaneously change a unit's facing angle (SetUnitLookAt() is bugged).

And because of that - when you get a recycled dummy in a projectile system or something similar (where the dummy needs to be facing a certain angle from the very beginning), it'd potentially need a considerable amount of time to turn to that angle, which can look very ugly.

That's exactly what Bribe said you should look into (and solve) - when a player requests a new dummy unit from the stack, he should also specify the staring facing angle that's needed, so that your snippet can pop up a dummy unit which facing angle is already close to what the user momentarily needs, in order to significantly shorten the amount of time the dummy unit would otherwise need to turn around.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
If you are thinking about storing three hundred and sixty degrees of units, that is not a good idea at all. It only needs 0, 90, 180 and 270 because a tiny <45 degree error margin is hardly noticable in most cases compared to how much you'd suffer from having too many stored dummies.

The quickest a unit can turn is in 0.73 seconds (I am saving this for reference tomorrow).
 
Level 7
Joined
Dec 3, 2006
Messages
339
As far as I've heard the only way to make instant facing is by recreating the unit. But that's not an great idea in this case. As far as making turn instant I'm not even sure turn is instant even if you change the facing of the unit with triggers.

I wonder if you set animation speed to an extremely high number it might have an effect on this though. Also just an idea-- someone should make an whole SetUnitLookAt() with animations sort of library cause it has other uses rather than projectiles like making bone's face certain ways. (Imagine a dragon that tilts his head to cast fire breath on the ground in a certain area. : D )

Also I'm not sure what dummy your using but you should probably be using this one (since it has animations for all degrees):
 

Attachments

  • 360deg200.mdx
    31.4 KB · Views: 66
Level 7
Joined
Dec 3, 2006
Messages
339
Either way that dummy is still relevant since you were talking about the limits of facing degrees speed. It could be done in a different system ofc.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
I think get would be more useful if it also took coordinates since you'd almost always want to set the dummy's coordinates when you get one.

JASS:
            call SetUnitFacing(.stack.unit[.index], .angle)
in get should be in the else.

Not very important since it's unlikely: this could easily support dummy units that don't have locust since you could do something like
JASS:
                if UnitRemoveAbility(.stack.unit[.index],'Aloc') then
                    call UnitAddAbility(.stack.unit[.index],'Aloc')
                endif
I just want to point out that you actually don't need to immediately remove 'Aloc' after hiding the unit.

Otherwise, good job. :p
This should deprecate my resource when that angling approximation is added.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
The angling approximation was a lot of work, but I have finally cracked it.

I will be submitting it as my own resource after the documentation is written and it's tested for syntax errors of course, but I want to let you guys have a look at it before I release it.

JASS:
library ReProjectile requires TimerUtils
    
    globals
        //--------------------------------------------------------------------
        // You should configure the dummy unit to have the 'Amrv' ability, use
        // the dummy.mdx model, have the 'Aloc' ability and have a few other
        // good object editor settings which are all generated by an object
        // merger statement which I will write up soon.
        // 
        private constant integer DUMMY_ID = 'n000'         //The rawcode of the dummy unit.
        private          player  OWNER    = Player(15)     //The owner of the dummy unit.
        
        private constant integer ANGLES = 8                //# of indexed angles. Higher value increases realism but decreases recycle frequency.
        private constant integer MAX_STORED_PER_ANGLE = 12 //Max dummies per indexed angle. I recommend lowering this if you increase ANGLES.
        private constant boolean PRELOAD_DUMMIES = true    //If true, preloads ANGLES x MAX_STORED_PER_ANGLE dummy units.
    endglobals
    
    //========================================================================
    // This module helps to initialize the system in highest priority.
    // 
    private module Init
        private static method onInit takes nothing returns nothing
            static if PRELOAD_DUMMIES then
                local integer i
                local integer j = ANGLES
                local integer angle
                local unit u
                loop
                    set j = j - 1
                    set i = MAX_STORED_PER_ANGLE
                    set .index[j] = i
                    set angle = j * .ANGLE + .ROUNDED
                    loop
                        set i = i - 1
                        set u = CreateUnit(OWNER, DUMMY_ID, 0, 0, angle)
                        call ShowUnit(u, false)
                        set .dummy[.getArrayIndex(j, i)] = u
                        exitwhen i == 0
                    endloop
                    exitwhen j == 0
                endloop
                set u = null
            endif
            set .expireCode = function thistype.onExpire
        endmethod
    endmodule
    
    private struct Data //It doesn't extend array, oh my god
        
        static unit new               //Returning a local unit leaks the handle index.
        static unit array dummy       //Used as a 2-D array of dummy units.
        static integer array index    //Used to track multiple indices for the 2-D array.
        
        static unit array tempDummy   //Used for the timer stack.
        static code expireCode = null //Prevents trigger evaluations or cloned functions
        
        static constant integer ANGLE = 360 / ANGLES //Generate angle from # of angles.
        static constant integer ROUNDED = .ANGLE / 2 //The middle of 2 angle indices.
        
        //====================================================================
        // Returns the index for the given angle. In my configuration, I have
        // it like so:
        // 
        // - index[0] = 22 degrees
        // - index[1] = 67 degrees
        // - index[2] = 112 degrees
        // - index[3] = 157 degrees
        // - index[4] = 202 degrees
        // - index[5] = 247 degrees
        // - index[6] = 292 degrees
        // - index[7] = 337 degrees
        // 
        static method getAngleIndex takes real angle returns integer
            return R2I(angle) / .ANGLE
        endmethod
        
        //====================================================================
        // 2-D array allows all numbers to be easily user-configurable.
        // 
        static method getArrayIndex takes integer angleIndex, integer dummyIndex returns integer
            return angleIndex * MAX_STORED_PER_ANGLE + .index[i]
        endmethod
        
        //===================================================================
        // Prepares a unit to be recycled after it can completely adjust its
        // facing angle.
        // 
        static method rDummy takes unit u, integer i returns nothing
            local timer t = NewTimer()        //TimerUtils is the best option here.
            local thistype this = .allocate() //I use built-in allocate holy smokes.
            set .tempDummy[this] = u          //Index the dummy unit to this instance.
            set i = i * .ANGLE + .ROUNDED     //Turn the small integer into a true angle.
            call ShowUnit(u, false)           //Hide the dummy just in case.
            call SetTimerData(t, this)        //Remember the dummy unit.
            call SetUnitFacing(u, i)          //Set its facing to the new angle.
            
            //Get the smallest angle between the unit's current facing and the angle it
            //is going to face, because that is how the wc3 engine will turn the unit.
            set this = R2I(GetUnitFacing(u))
            if i > this then
                set i = i - this
            else
                set i = this - i
            endif
            
            //Format angle to < 180.
            if i > 180 then
                set i = 180 - i
            endif
            
            //The value "0.00405" is 0.73 / 180.
            //0.73 is the time it takes the dummy to turn 180 degrees.
            //I use a timer per dummy to detect when it is done turning.
            call TimerStart(t, 0.00405 * i, false, .expireCode)
            
            set t = null
        endmethod
        
        //===================================================================
        // Recycle a dummy unit after it has finished turning. If the stack
        // is full, try again on a different stack. If all stacks are full,
        // remove the dummy unit.
        // 
        static method onExpire takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer()) //Get the relevant dummy index
            local integer i = GetAngleIndex(GetUnitFacing(.tempDummy[this]) + 2) //Add 2 to reduce error margin.
            call this.deallocate() //Deallocate instance as it's useless now.
            call ReleaseTimer(GetExpiredTimer()) //Release the timer of course.
            if .index[i] == MAX_STORED_PER_ANGLE then
                set i = .ANGLES
                loop
                    set i = i - 1
                    if .index[i] < MAX_STORED_PER_ANGLE then
                        call .rDummy(.tempDummy[this], i)
                        return //Recycled the instance, skip remaining actions.
                    endif
                    exitwhen i == 0
                endloop
                call RemoveUnit(.tempDummy[this])
            else
                set .dummy[.getArrayIndex(i, .index[i])] = .tempDummy[this]
                set .index[i] = .index[i] + 1
            endif
            set .tempDummy[this] = null
        endmethod
        
        //Initialize the system via module.
        implement Init
        
    endstruct
    
    //=======================================================================
    // Get a new dummy unit.
    // 
    function NewProjectileDummy takes real x, real y, real facing returns unit
        local integer i = Data.getAngleIndex(facing)
        if Data.index[i] == 0 then
            return CreateUnit(OWNER, DUMMY_ID, x, y, facing)
        endif
        set Data.index[i] = Data.index[i] - 1
        set Data.new = Data.dummy[Data.getArrayIndex(i, Data.index[i])]
        call ShowUnit(Data.new, true)
        call UnitRemoveAbility(Data.new, 'Aloc')
        call UnitAddAbility(Data.new, 'Aloc')
        call SetUnitX(Data.new, x)
        call SetUnitY(Data.new, y)
        call SetUnitFacing(Data.new, facing)
        return Data.new
    endfunction
    
    //=======================================================================
    // Release the dummy unit when its job is done.
    // 
    function ReleaseProjectileDummy takes unit u returns nothing
        call Data.rDummy(u, Data.getAngleIndex(GetUnitFacing(u)))
    endfunction
    
endlibrary

Edit: Looking at this further I will probably develop it a bit more so that it recycles by unit-type as well. Or maybe I should tell the user to use the passive transformation trick if they want that?
 
Level 7
Joined
Dec 3, 2006
Messages
339
All in all what you guys are trying to do for this dummy system is make the facing instant which solves the directional issue of a projectile relative to the unit angle but it doesn't do anything for arch. Are you going to consider trying to use arch at all?

If you want to you could pull it off with arch you can probably do it very similar to this; just with more dummies using that model i posted. Just an idea though really.

Heck it could even be two flavors flavors of this-- one for flat projectiles and another for one with arch.
 
Level 7
Joined
Dec 3, 2006
Messages
339
If you set the animation-blend time to 0.00 in object editor the animation changes instantly when you set it by index.

If that's true. Then you don't even need 12 dummy units with facing degrees if you have a single unit with those degree type animations. I don't get why Magtherion is so wary of the dummy model I link'd. It's from this old thread that people talked about this stuff way before many other people: http://www.wc3c.net/showthread.php?t=105830

Open it in wc3 model editor you can see it's got a hell of a lot of animations. I think there is 360 degrees for the pitch and the 200 degrees for the facing degrees; but i'm not sure it's best to just test and figure out how the animations are set especially since the animations are all in letters. You can easily modify it to use numbers for the animation names if that's easier as well (Though it'd take some time).

I think those maps got corrupt but it was in one of the maps i think. So credit goes to either Infrane, Grim001, or Vexorian probably.
 
Level 7
Joined
Dec 3, 2006
Messages
339
Even if you used your angle approximation method? I mean it doesn't need to be 100% accurate(like an animation per degree is a bit rediculous). I'm just saying maybe the better alternative to dealing with this is to make a model because it's a lot less intensive to change animations rather than recycling so many units.

Edit: I mean if you split it like you did for that script it'd be 7 angles for facing and 7 angles for pitch-- it'd only be 49 animations which isn't that bad. Heck I think the real reason no one made a really good as heck projectile system that incorporated arch and pretty much perfectly simulated it with the correct facing (with nearly no lag for 600+ projectiles on a good computer) was because there wasn't an said such model ever made so many people went to projectile recycling which is much more cpu intensive and usually kills even the idea of arch.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Projectile recycling is not CPU intensive at all, you have to think about how much overhead there is in the CreateUnit function, not to mention the overhead if there are any region-events that fire and any unit indexing systems that run a dozen or so trigger evaluations. You'd want to get about 30-40 different projectile facings minimum for realism, and about that number as well would be needed for projectile pitch, otherwise the unit be jumpy and not gradual enough when it transitions. And that's the minimum. The model file would have to be gigantic and I don't even know if wc3 supports that many animations.
 
Level 7
Joined
Dec 3, 2006
Messages
339
As far as I know there are no limitations to the number of animations a model can have (I think read in some tutorial that there isn't one for wc3; but i bet it was untested).

However if your comment about accuracy of the animations holds foot then the size aspect of a model would interfere probably.
And if 180 animations = 35kb then i'm guessing the 40x40 animations or 1600 animations would be roughly 310 kb or so.
And loading an model with that size and number of animations will likely cause lag when used in great amounts. I was just thinking that arch wouldn't need to be supremely accurate as long as you have an animation set going up, slightly up, down, slightly down, and just straight with the other degrees. I guess were going with your method then.
 
Level 7
Joined
Dec 3, 2006
Messages
339
I have a better idea.
Since the point of this system is to recycle dummies, how about I remove all the code that doesn't have to do with recycling and let the users fix this 'angle' problem xD
Probably with another resource or something :p
There has to be a way to set unit facing instantly D;

The 3 methods dealing with facing that have been established have always been: setting an animation(has limits on accuracy since it gets screwed over by facing), setting facing(has limits on pitch or arch), and forcing an bone to face a certain way(suffers the same fate as animations). You could possibly use modified values of roll and pitch in the object editor but those are even messier to deal with as well and would require again too many dummies. I kinda like bribe's idea the more I keep looking at it (even though it's annoying to have to abide by some of wc3's limitations for realism in projectiles).

I'd say that your comment would be fine about this recycling dummies; but sadly i can't even think of another example really in which you need to recycle lots of dummies. It's much more often that you only need 1 caster type unit (aside for channeling spells but those are rare enough they could be coded into the script) and that the only thing that requires a bunch of dummies is really just projectiles using dummy recycling.
 
Last edited:
Top