• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Dummy

I don't see how this is possible:

JASS:
//min == max - 1
//max == min + 1

What if you take 2 dummies from angle 45, you only need to drag one dummy from a max queue to balance that out. But your max is still unchanged until you deplete all queues that are set to that max slot. So in that case the difference between min and max can be > 1.
 
???


If mag didn't have the extra division in there, I'd gladly use it.

I'll look at it to verify what mag said.

edit
looked at it and I don't like it in the slightest, lol


it's a lot more than a simple elapsed game time snippet, furthermore it has a division thing in it. If it were just a thing with a timer in it that returned the elapsed time of the timer, I'd use it, but it isn't that at all.
 
Ok, so people may be wondering what the difference is between this and Bribe's recycler is. The main difference is that this is O(1) and Bribe's is O(n) ^_-. I spent a long time developing an algorithm that would be O(1) ;o. If you look for loops in this, the only loop is in the init method when dummies are initially created ^_-.

Bribe later released one that was O(1), but it didn't have the great behavior that this one has because it didn't stay sorted for recycling (to ensure that all facings have around the same # of dummies).

Anyways, that's the big difference >: o.

This resource also influenced Bribe's a bit (the move to O(1) in order to stay competitive and the addition of PauseUnit in order to keep the resource running smoothly with lots of dummy units in the background). Ofc, Bribe's resource also influenced mine (recycling based on facing).

Anyways, no known bugs atm.
 
Modulo arithmetic is one of the greatest things available in programming. Before I learned about it I was too naive to come up with MissileRecycler but I knew it was possible in concept. After applying it and extensive debugging MissleRecycler was born and then later refined until becoming pretty much obsolete next to this one.
 
@Nes: But your system disables UnitIndexer before it creates the unit, and the INDEX event only fires when UnitIndexer is enabled, so it's impossible for AutoFly to give 'Amrf' to the units.

Hm.. that is true =).


Just needs this line pasted in 2 spots
UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf')

Essentially whenever a new unit is created =).
 
JASS:
library Dummy /* v1.0.0.5
*************************************************************************************
*
*   Allows one to create dummy units that are either at or are close
*   to the angle specified.
*
*   Dummy recycling minimizes the number of dummy units on the map while supporting near
*   instant SetUnitFacing.
*
*       Errors
*       ----------------------------
*
*           Any error will result in the system disabling itself and an error message
*
*           ->  May not kill dummies
*           ->  May not remove dummies
*           ->  May not attempt to recycle non dummies
*
*************************************************************************************
*
*   Credits
*
*       Vexorian for dummy.mdx
*
*       Bribe
*
*           Delayed recycling implemetation
*           ----------------------------
*
*               Bribe's delayed recycling implementation uses timestamps rather than timers, which
*               helps improve performance.
*
*           Stamps for queue node movement
*           ----------------------------
*
*               Convinced me that this was worth it
*
*           Time it takes to rotate 180 degrees
*           ----------------------------
*
*               Supplied me with the number .73
*
*************************************************************************************
*
*   */uses/*
*
*       */ UnitIndexer     /*  hiveworkshop.com/forums/jass-functions-413/unit-indexer-172090/
*
************************************************************************************
*
*   SETTINGS
*
*/
globals
    /*
    *   The unit id of dummy.mdx
    */
    private constant integer DUMMY_ID = 'h000'
    
    /*
    *   The space between angles for the recycler
    *
    *   Angles used are angles from 0 to 359 in intervals of ANGLE_SPACING
    *
    *   Higher spacing means less units but lower accuracy when creating the facing
    *
    */
    private constant integer ANGLE_SPACING = 15
    
    /*
    *   How many projectiles to preload per angle
    *
    *   Preloaded projectile count is 360/ANGLE_SPACING*MAX_PROJECTILES
    *
    */
    private constant integer PRELOAD_PROJECTILES_PER_ANGLE = 50
    
    /*
    *   How much to delay before recycling dummy
    */
    private constant real RECYCLE_DELAY = 2
endglobals
/*
************************************************************************************
*
*   library MissileRecycler uses Dummy
*   ----------------------------
*
*       For compatibility with Bribe's resource
*
*       function GetRecycledMissile takes real x, real y, real z, real facing returns unit
*       function RecycleMissile takes unit whichUnit returns nothing
*
************************************************************************************
*
*
*   struct Dummy extends array
*
*       Creators/Destructors
*       ----------------------------
*
*           static method create takes real x, real y, real facing returns Dummy
*               -   For those of you who really want this to return a unit, getting
*               -   the unit from this is very easy, so don't whine
*
*               -   Dummy.create().unit -> unit
*
*           method destroy takes nothing returns nothing
*               -   For those of you who really want this to take a unit, getting
*               -   the dummy index is very easy.
*
*               -   Dummy[whichUnit].destroy()
*
*       Fields
*       ----------------------------
*
*           readonly unit unit
*
*       Operators
*       ----------------------------
*
*           static method operator [] takes unit dummyUnit returns Dummy
*
************************************************************************************/
    private keyword Queue
    
    globals
        /*
        *   Used for dummy instancing
        *   Dummy indexes are never destroyed, so there is no need for a recycler
        */
        private Queue dummyCount = 0
        
        /*
        *   Used to retrieve unit handle via dummy index
        */
        private unit array dummies
        
        /*
        *   The owner of all dummy units. This shouldn't be changed.
        */
        private constant player DUMMY_OWNER = Player(15)
        
        /*
        *   Used to apply time stamps to dummies for recycling
        *   purposes. A dummy is only considered recycled if its
        *   stamp is less than the elapsed time of stamp timer.
        */
        private timer stampTimer
    endglobals
    
    /*
    *   min == max - 1
    *   max == min + 1
    *
    *   variance of counts must be 1
    */
    private struct ArrayStack extends array
        /*
        *   The minimum and maximum counts
        */
        static thistype max = 0
        static thistype min = 0
        
        /*
        *   list[count].first
        */
        thistype first
        
        /*
        *   queue.size
        */
        thistype count_p
        
        /*
        *   list[count].next
        */
        thistype next
        
        /*
        *   list[count].prev
        */
        thistype prev
        
        /*
        *   list[count].first -> queue of dummies
        */
        static method operator [] takes thistype index returns thistype
            return index.first
        endmethod
        
        /*
        *   list[count].add(queue of dummies)
        */
        private method add takes thistype node returns nothing
            /*
            *   Update min/max
            */
            if (integer(this) > integer(max)) then
                set max = this
            elseif (integer(this) < integer(min)) then
                set min = this
            endif
            
            /*
            *   Push on to front of list like a stack
            */
            set node.next = first
            set node.next.prev = node
            set node.prev = 0
            set first = node
            
            set node.count_p = this
        endmethod
        
        /*
        *   list[count].remove(list of dummies)
        */
        private method remove takes thistype node returns nothing
            /*
            *   If node is the first, update the first
            */
            if (node == first) then
                set first = node.next
                
                /*
                *   If lis tis empty, update min/max
                */
                if (0 == first) then
                    if (this == min) then
                        set min = max
                    else
                        set max = min
                    endif
                endif
            else
                /*
                *   Simple removal
                */
                set node.prev.next = node.next
                set node.next.prev = node.prev
            endif
        endmethod
        
        method operator count takes nothing returns integer
            return count_p
        endmethod
        method operator count= takes thistype value returns nothing
            /*
            *   Remove from list node was on
            */
            call count_p.remove(this)
            
            /*
            *   Add to new list
            */
            call value.add(this)
        endmethod
    endstruct
    
    /*
    *   queue = angle + 1
    */
    private struct Queue extends array
        private real stamp
        
        thistype next
        thistype last
        
        /*
        *   Update dummy count for queue
        */
        private method operator count takes nothing returns integer
            return ArrayStack(this).count
        endmethod
        private method operator count= takes integer value returns nothing
            set ArrayStack(this).count = value
        endmethod
        
        /*
        *   Queue with smallest number of dummies
        */
        private static method operator min takes nothing returns thistype
            return ArrayStack.min.first
        endmethod
        
        /*
        *   Queue with largest number of dummies
        */
        private static method operator max takes nothing returns thistype
            return ArrayStack.max.first
        endmethod
        
        static method add takes thistype dummy returns nothing
            /*
            *   Always add to the queue with the least amount of dummies
            */
            local thistype this = min
            
            /*
            *   Add to end of queue
            */
            set last.next = dummy
            set last = dummy
            set dummy.next = 0
            
            /*
            *   Update queue count
            */
            set count = count + 1
            
            /*
            *   Match unit angle with queue
            */
            call SetUnitFacing(dummies[dummy], this - 1)
            
            /*
            *   Apply stamp so that dummy isn't used until the stamp is expired
            */
            set dummy.stamp = TimerGetElapsed(stampTimer) + RECYCLE_DELAY - .01
        endmethod
        static method pop takes thistype this, real x, real y, real facing returns integer
            /*
            *   Retrieve queue and first dummy on queue given angle
            */
            local thistype dummy = next
            
            local thistype this2
            local thistype node

            local real stamp
            
            /*
            *   If the queue is empty, return new dummy
            */
            if (0 == dummy or dummy.stamp > TimerGetElapsed(stampTimer)) then
                
                /*
                *   Allocate new dummy
                */
                debug if (dummyCount == 8191) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DUMMY RECYCLER FATAL ERROR: DUMMY OVERLOAD")
                    debug set Dummy.enabled = false
                    debug set this = 1/0
                debug endif
                
                set dummy = dummyCount + 1
                set dummyCount = dummy
                
                /*
                *   Create and initialize new unit handle
                */
                set dummies[dummy] = CreateUnit(DUMMY_OWNER, DUMMY_ID, x, y, facing)
                call UnitAddAbility(dummies[dummy], 'Amrf')
                call UnitRemoveAbility(dummies[dummy], 'Amrf')
                call PauseUnit(dummies[dummy], true)
                
                return dummy
            endif
            
            /*
            *   Remove the dummy from the queue
            */
            set next = dummy.next
            if (0 == next) then
                set last = this
            endif
            
            /*
            *   Only remove from the count if the queue has most dummies in it
            *
            *   If queue doesn't have most dummies in it, take a dummy from the queue
            *   with most dummies in it and keep count the same
            */
            if (count == ArrayStack.max) then
                set count = count - 1
            else
                /*
                *   Retrieve the queue with most dummies in it as well as the
                *   first dummy in that queue
                */
                set this2 = max
                set node = this2.next
                
                /*
                *   Remove first dummy from largest queue
                */
                if (0 == node.next) then
                    set this2.last = this2
                else
                    set this2.next = node.next
                endif
                
                set this2.count = this2.count - 1
                
                /*
                *   Add first dummy to current queue
                */
                set last.next = node
                set last = node
                set node.next = 0
                
                /*
                *   Match unit angle with queue
                */
                call SetUnitFacing(dummies[node], this - 1)
                
                /*
                *   .73 seconds is how long it takes for a dummy to rotate 180 degrees
                *
                *   Credits to Bribe for these 4 lines of code and the .73 value
                */
                set stamp = TimerGetElapsed(stampTimer) + .73
                if (stamp > node.stamp) then
                    set node.stamp = stamp
                endif
            endif
            
            /*
            *   Move dummy to target position
            */
            call SetUnitX(dummies[dummy], x)
            call SetUnitY(dummies[dummy], y)
            call SetUnitFacing(dummies[dummy], facing)
            
            /*
            *   Return first dummy from current queue
            */
            return dummy
        endmethod
    endstruct
    
    struct Dummy extends array
        debug static boolean enabled = false
        debug private boolean allocated
        
        /*
        *   Retrieve index given unit handle
        */
        static method operator [] takes unit dummyUnit returns thistype
            debug if (not enabled) then
                debug return 1/0
            debug endif
            
            return GetUnitUserData(dummyUnit)
        endmethod
        
        /*
        *   Retrieve unit handle given index
        */
        method operator unit takes nothing returns unit
            debug if (not enabled) then
                debug set this = 1/0
            debug endif
            
            return dummies[this]
        endmethod
        
        /*
        *   Slightly faster than ModuloInteger due to less args + constants
        */
        private static method getClosestAngle takes integer angle returns integer
            set angle = angle - angle/360*360
            
            if (0 > angle) then
                set angle = angle + 360
            endif
            
            return angle/ANGLE_SPACING*ANGLE_SPACING
        endmethod
        
        /*
        *   Returns either a new or a recycled dummy index
        */
        static method create takes real x, real y, real facing returns Dummy
            static if DEBUG_MODE then
                local thistype this
                
                if (not enabled) then
                    set x = 1/0
                endif
                
                set this = Queue.pop(getClosestAngle(R2I(facing)) + 1, x, y, facing)
                
                debug set allocated = true
                
                return this
            else
                return Queue.pop(getClosestAngle(R2I(facing)) + 1, x, y, facing)
            endif
        endmethod
        
        /*
        *   Recycles dummy index
        */
        method destroy takes nothing returns nothing
            debug if (not enabled) then
                debug set this = 1/0
            debug endif
            
            /*
            *   If the recycled dummy was invalid, issue critical error
            */
            debug if (0 == GetUnitTypeId(unit) or 0 == GetWidgetLife(unit) or not allocated) then
                debug if (not allocated) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "DUMMY RECYCLER FATAL ERROR: DOUBLE FREE")
                debug elseif (0 == GetWidgetLife(unit)) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "DUMMY RECYCLER FATAL ERROR: KILLED A DUMMY")
                debug elseif (null != unit) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "DUMMY RECYCLER FATAL ERROR: REMOVED A DUMMY")
                debug else
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "DUMMY RECYCLER FATAL ERROR: ATTEMPTED TO RECYCLE NON DUMMY UNIT")
                debug endif
                
                debug set enabled = false
                debug set this = 1/0
            debug endif
            
            debug set allocated = false
            
            call Queue.add(this)
        endmethod
    endstruct
    
    /*
    *   Initialization
    */
    private function Initialize takes nothing returns nothing
        local unit dummy
        local integer last
        local integer angle
        local ArrayStack queue
        local integer count
        
        /*
        *   This timer 
        */
        set stampTimer = CreateTimer()
        call TimerStart(stampTimer, 604800, false, null)
        
        /*
        *   The highest possible angle
        */
        set last = 360/ANGLE_SPACING*ANGLE_SPACING
        if (360 == last) then
            set last = last - ANGLE_SPACING
            if (last < ANGLE_SPACING) then
                set last = 0
            endif
        endif
        
        /*
        *   The lowest possible angle
        */
        set angle = 0
        
        /*
        *   Start dummy count at the last possible angle so that
        *   angles don't overlap with dummy indexes. This is done
        *   to simplify queue algorithm and improve overall performance.
        *   At most 360 possible dummy unit indexes will be lost due to this.
        */
        set dummyCount = last + 1
        
        /*
        *   Initialize ArrayStack
        */
        set ArrayStack.min = PRELOAD_PROJECTILES_PER_ANGLE
        set ArrayStack.max = PRELOAD_PROJECTILES_PER_ANGLE
        set ArrayStack(PRELOAD_PROJECTILES_PER_ANGLE).first = 1
        
        loop
            /*
            *   queue pointer is angle + 1
            */
            set queue = angle + 1
            
            /*
            *   Only add projectiles to queue if MAX_PROJECTILES < 0
            */
            if (0 < PRELOAD_PROJECTILES_PER_ANGLE) then
                set count = PRELOAD_PROJECTILES_PER_ANGLE
                set queue.count_p = PRELOAD_PROJECTILES_PER_ANGLE
                
                set dummyCount = dummyCount + 1
                set Queue(queue).next = dummyCount
                
                /*
                *   Create and add all dummies to queue
                */
                loop
                    /*
                    *   Create and initialize unit handle
                    */
                    set dummy = CreateUnit(DUMMY_OWNER, DUMMY_ID, 0, 0, angle)
                    set dummies[dummyCount] = dummy
                    call UnitAddAbility(dummy, 'Amrf')
                    call UnitRemoveAbility(dummy, 'Amrf')
                    call PauseUnit(dummy, true)
                    
                    set count = count - 1
                    exitwhen 0 == count
                    
                    /*
                    *   Point to next
                    */
                    set dummyCount.next = dummyCount + 1
                    set dummyCount = dummyCount + 1
                endloop
                
                set Queue(queue).last = dummyCount
            else
                set Queue(queue).last = queue
            endif
            
            exitwhen last == angle
            
            /*
            *   Go to next angle
            */
            set angle = angle + ANGLE_SPACING
            
            /*
            *   Link queues together
            */
            set queue.next = angle + 1
            set ArrayStack(angle + 1).prev = queue
            
            /*
            *   Go to next queue
            */
            set queue = angle + 1
        endloop
        
        set dummy = null
        
        debug set Dummy.enabled = true
    endfunction
    private module Init
        private static method onInit takes nothing returns nothing
            static if DEBUG_MODE then
                call ExecuteFunc(SCOPE_PRIVATE + "Initialize")
                if (not Dummy.enabled) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DUMMY RECYCLER FATAL ERROR: INITIALIZATION CRASHED, LOWER PRELOAD DUMMY COUNT")
                endif
            else
                call Initialize()
            endif
        endmethod
    endmodule
    private struct Inits extends array
        implement Init
    endstruct
endlibrary

library MissileRecycler uses Dummy
    function GetRecycledMissile takes real x, real y, real z, real facing returns unit
        local Dummy dummy = Dummy.create(x, y, facing)
        
        call SetUnitFlyHeight(dummy.unit, z, 0)
        
        return dummy.unit
    endfunction
    function RecycleMissile takes unit u returns nothing
        call Dummy[u].destroy()
    endfunction
endlibrary

There you go.
Now you don't have to graveyard this.
 
Updated and ready for approval

I did some mad testing here to make sure that I didn't screw anything up and to verify the algorithm.

The output... was very.. interesting

I would alternate between creating and destroying 1 dummy 1x a second with initial preloaded dummies of 1 and a recycle delay of 2.

This is the output I got.

Get Dummy from 0
Recycle dummy to 0
Create new Dummy for 0
Recycle dummy to 0
Get Dummy from 0
Recycle dummy to 0
Get dummy from 15
Recycle dummy to 0
Get dummy from 15
Recycle dummy to 0
Get dummy from 15
Recycle dummy to 0
Get dummy from 30
Recycle dummy to 0


etc etc... it always recycled the dummy to the 0 angle queue! Wild huh? Turns out, it was operating perfectly. I really had to disseminate this thing to figure out why the odd behavior was occurring.

This is what happened. All of the angle queues had 1 dummy each. It tries to keep the queues in balance using an array indexed by count, each index being a stack of these queues.

In other words Array[count].stack = queues with count

This is how it achieves O(1) behavior ;o.

Anyways, so in the first scenario, I take from the first queue, it gets a new cont of 0, so it goes into Array[0]. The new minimum count becomes 0, the max is still 1.
I recycle back, it is always sent to the minimum queue, which in this case is q0, so q0 is moved back to Array[1]. Remember, these are stacks of queues, so q0 is the first element in Array[1].

I now get a dummy with angle 0 again. The 1 dummy in q0 is still recycling (delayed timestamp), so a new dummy is made. I send it back, now q0 has 2 elements in it (it's the first element in Array[1] remember?), so it gets sent to Array[2].

Now, I get a dummy from angle 15. Angle 15 is in Array[1]. It's count is going to be off by more than 1 from the maximum, so the system takes an element from the maximum (Array[2], q0) and shoves it into the element that would be pushed out of bounds. This means that q0's first element is sent to q15's first element. q15's count stays at 1 and q0's count drops to 1, meaning it moves from Array[2] to Array[1]. q0 is now the first queue in Array[1] (remember, the array stores stacks).

I now send my 15 degree unit back, it goes back to the first element of the smallest count, which in this case is q0 (Array[1].first = q0). q0's count goes back to 2, it's sent back to Array[2], and the process repeats. If I take from the 15 angle again, it's on a 1 second timer and the transfer delay is .73 seconds, so the element from q0 that is in q15 is ready to be used. This means that in this scenario, I will always be recycling to q0 and taking from whatever queue I happen to be using.

Pretty cool huh?

Each transfer is of course O(1).
 
Okay, approved.

I would highly recommend updating to this version because the semantics concerning the dummy units have changed in a way that could affect some of your resources that really take advantage of the old semantics.

The problem with the old one is that it complicated Damage source detection because of how dummy units are assigned unique IDs based on the struct instances.

We have discussed it, and we're banning all uses of UnitUserData no matter what the case unless the unit is totally excluded for the game. Like, totally.
Only Unit Indexing systems can use UnitUserData.

No excuses.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
Just thought I'd mention that this does not work with any indexer that doesn't use a module initializer. So it's basically only compatible with UnitIndexer, unless you hack the other ones. Took me like an hour to figure this out, I was blaming the units, but then one cast of the spell suddenly worked - so it became obvious that the issue is only with the preloaded ones.

[EDIT]

Also, SetUnitX/Y does not work with the movement type "None". I'd recommend using SetUnitPosition instead, it always works, and as far as I know, it has no downsides with plain effect dummies.
 
Last edited:
Level 16
Joined
Aug 7, 2009
Messages
1,406
Yup, I know it that we use module initializers for everything, even when it's totally unnecessary, but makes things incompatible with other people's resources, because it's "better" (and because those things that create dummies at map init would clearly want to recycle it).

I already tried out all the movement types for the dummies in the past, and "None" was the best one :) Don't quite remember why exactly, but something wasn't working properly with other types.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
I'm aware of the advantages of SetUnitX/Y, but as I said, something was not working properly back when I tried to change it. I just tried "Foot" out though and it seems to work, although I did not test it with all the spells. Well, I hope I'm not overlooking anyting :'D But I take that suggestion back.
 
sure, i'll do it tomorrow

edit
nvm, this is the way it's supposed to be

Might I recommend Spell?

http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spell-221454/

You have full access to the unit within the dummy. This does the minimal necessary. It assumes that the dummies are not going to be used for spells. If you want to use them for spells, you will need to unpause them and change the owner yourself. Spell does this for you.

edit
I also recommend SpellStruct
 
Last edited:
Spell builds on top of this to handle dummy abilities and the pausing/ownership

Spell == Dummy

They are one in the same

JASS:
*   struct Spell extends array
*
*       Creators/Destructors
*       -----------------------
*
*           static method create takes unit source, real x, real y, real z, real facing returns Spell
*               -   creates new dummy at location with source owner
*               -   a Dummy can just as easily be typecasted
*
*           method destroy takes nothing returns nothing
*               -   destroys the spell/dummy
*               -   this should be called instead of Dummy.destroy

I don't get what's so wrong with using Spell when it does exactly what you are wanting, lol




This isn't broken and doesn't require a fix. The pause/ownership changing stuff is meant to be handled by a different library.

Remember, I write stuff following the philosophy of minimalism
 
Yes...

dummy.unit

Let's say that you do this

Spell spell = Spell.create(...)

now you can do

Dummy(spell).unit

spell == dummy

lol...

I don't get why you are so averse to using Spell when it does exactly what you want

Here is the code from Spell if you really don't believe me

JASS:
        static method create takes unit source, real x, real y, real z, real facing returns Spell
            local thistype this = Dummy.create(x, y, facing)

No reason you can't do

Dummy dummy = Spell.create(...)

Just be sure that you use Spell(dummy).destroy() so that it pauses the unit and sets owner back to player 15.

Spell prepares spells... >.<. It sounds like you are working with Spells, so you use Spell : |, lol. It still uses Dummy in the background, it just has extra features that allow you to easily use dummies as casters.

edit
JASS:
        static method create takes unit source, real x, real y, real z, real facing returns Spell
            local thistype this = Dummy.create(x, y, facing)
            
            if (source != null) then
                set this.source = source
                call SetUnitOwner(dummy.unit, GetOwningPlayer(source), true)
            endif
            
            call SetUnitFlyHeight(dummy.unit, z, 0)
            
            call PauseUnit(dummy.unit, false)
            
            return this
        endmethod
        method destroy takes nothing returns nothing
            call IssueImmediateOrderById(dummy.unit, 851972)
            
            call UnitRemoveAbility(dummy.unit, abilityId)
            call SetUnitOwner(dummy.unit, Player(15), true)
            
            set abilityId = 0
            set abilityLevel = 0
            set abilityOrder = 0
            
            set source = null
            
            call PauseUnit(dummy.unit, true)
            
            call Dummy(this).destroy()
        endmethod

edit
ofc, if you wanna do a player spell with absolutely no source, like they type a chat command to cast a spell, then you can still use SetUnitOwner

edit
Hell, you can even do

JASS:
Dummy dummy = Dummy.create(...)
call Spell(dummy).destroy()
 
Dummy was made purely to be a dummy, as in an actor, purely an art effect.

Spell was built on top of Dummy to allow for some more things.

If you want all of that stuff, build another library? Otherwise, if all you need is the SetUnitOnwer and PauseUnit stuffs, Spell will be perfect. You can just use Spell.create and Spell.destroy and then use it for whatever you want.
 
Top