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

[vJASS] DummyUnit

Level 13
Joined
Nov 7, 2014
Messages
571
JASS:
library DummyUnit

//! novjass

General notes:
    Dummy units come up often because they are used for casting Blizzard spells (stuns, illusions, etc.),
    making custom spells that need to move special effects and in projectile systems.
    Of course one can simply create a unit, use it and remove it when done but its probably
    better to only create the unit once and use it as many times as needed instead, which is
    what this library tries to do.

Credits:
    Vexorian - xe, dummy.mdx (can not possibly enumerate all the other things): http://www.wc3c.net/showthread.php?t=101150
    Toadcop - Object Pre-Cacher
    Bribe - MissileRecycler

API:

//
// Borrowing/Obtaining a dummy unit:
//

static method get takes nothing returns thistype

// note: facing is in radians not degrees
static method get_facing takes real radians returns thistype

// Obtains a DummyUnit from a native unit.
// In debug mode it prints an error if `u` is not a DummyUnit.
static method from takes unit u returns thistype


//
// Setting the owning player
//

// Note: after borrowing we must call this method to change the owner of the dummy unit
// otherwise the owner will be DUMMY_OWNER (defined in the config section),
// also note that p is an integer, not a player type.
//
method with_owner takes integer p returns thistype


//
// Accessing the wrapped native unit, and setting user data
//
readonly unit u
integer data


//
// Returing a previously borrowed dummy unit
//

// When a dummy unit is returned "home":
//     - its owning player is set to DUMMY_OWNER
//     - is issued a "stop" order
//     - moved to "home" (defined in the config section)
//     - its vertex color is set to red: 255, green: 255, blue: 255, alpha: 255
//     - its scale is set to 1.0
//     - its time scale is set to 1.0
//     - it is paused
//     - its .data field is set to 0
//
method return_instantly takes nothing returns nothing

// delay is in seconds
method return_after takes real delay returns nothing

//! endnovjass


// These are just shortcuts for importing the dummy.mdx into the map and creating the dummy unit.
// You can do it manually if you want though.
//
// external FileImporter dummy.mdx war3mapImported\dummy.mdx

/*

//! externalblock extension=lua ObjectMerger $FILENAME$
    //! i setobjecttype("abilities")
        //! i createobject("ACrn", "rinc")
            //! i makechange(current, "anam", "reincarnation")
            //! i makechange(current, "ansf", "")
            //! i makechange(current, "aeat", "")
            //! i makechange(current, "aart", "ReplaceableTextures\\PassiveButtons\\PASBTNReincarnation.blp")
            //
            //! i makechange(current, "Ore1", "1", "0")
            //! i makechange(current, "acas", "1", "0")
            //! i makechange(current, "acdn", "1", "0")
            //! i makechange(current, "aeff", "1", "")
            //! i makechange(current, "atp1", "1", "")
            //! i makechange(current, "aub1", "1", "")

    //! i setobjecttype("units")
        //! i createobject("ewsp", "e000")
            //! i makechange(current, "uabi", "Aloc,Amrf,Avul,rinc")
            //! i makechange(current, "uble", "0")
            //! i makechange(current, "ucbs", "0")
            //! i makechange(current, "uept", "-1")
            //! i makechange(current, "uerd", "-1")
            //! i makechange(current, "uico", "UI\\Minimap\\MinimapIconCreepLoc2.blp")
            //! i makechange(current, "umxp", "-0.01")
            //! i makechange(current, "umxr", "-0.01")
            //! i makechange(current, "umdl", "war3mapImported\\dummy.mdl")
            //! i makechange(current, "uocc", "-1")
            //! i makechange(current, "uori", "6")
            //! i makechange(current, "uspa", "")
            //! i makechange(current, "udty", "normal")
            //! i makechange(current, "utar", "none")
            //! i makechange(current, "udro", "0")
            //! i makechange(current, "umvf", "-10000")
            //! i makechange(current, "umvs", "1")
            //! i makechange(current, "umas", "1")
            //! i makechange(current, "umis", "-10000")
            //! i makechange(current, "umvr", "-1")
            //! i makechange(current, "umvt", "")
            //! i makechange(current, "ucol", "32")
            //! i makechange(current, "ufle", "0")
            //! i makechange(current, "ufoo", "0")
            //! i makechange(current, "uhom", "1")
            //! i makechange(current, "uhpm", "1000000000")
            //! i makechange(current, "uhpr", "0")
            //! i makechange(current, "uhrt", "none")
            //! i makechange(current, "ubdg", "1")
            //! i makechange(current, "umpi", "1000000000")
            //! i makechange(current, "umpm", "1000000000")
            //! i makechange(current, "umpr", "100000000")
            //! i makechange(current, "usid", "0")
            //! i makechange(current, "usin", "0")
            //! i makechange(current, "utyp", "standon")
            //! i makechange(current, "upgr", "")
            //! i makechange(current, "unam", "dummy")
            //! i makechange(current, "utip", "")
            //! i makechange(current, "utub", "")

//! endexternalblock

*/

globals
    private constant integer DUMMY_ID = 'e000'

    // adds Amrf, Aloc and Avul to each dummy
    private constant boolean ADD_ABILITIES = true

    // dummies are created and returned at this position
    private constant real DUMMY_HOME_X = -2048.0
    private constant real DUMMY_HOME_Y = 2048.0
    private location home_loc = Location(DUMMY_HOME_X, DUMMY_HOME_Y)
    private constant real DUMMY_HOME_Z = 0.0 - GetLocationZ(home_loc)

    // for this player
    private constant player DUMMY_PLAYER = Player(15)

    // because SetUnitFacing is not exactly instant we use buckets of dummy units roughly
    // facing in those directions so instead of SetUnitFacing having to rotate 180 at most
    // it would only have to rotate DEGS_PER_BUCKET or less
    //
    private constant integer BUCKETS_COUNT = 16
    private constant real DEGS_PER_BUCKET = 360.0 / BUCKETS_COUNT
endglobals


private struct Timer extends array
    readonly static integer max_count = 0

    private static Timer head = 0
    private Timer next

    private timer t
    integer data
    real timeout

    static method start takes integer user_data, real timeout, code callback returns Timer
        local Timer this

        if head != 0 then
            set this = head
            set head = head.next

        else
            set max_count = max_count + 1
            set this = max_count
            set this.t = CreateTimer()
        endif

        set this.next = 0
        set this.data = user_data
        set this.timeout = timeout

        call TimerStart(this.t, this, false, null)
        call PauseTimer(this.t)
        call TimerStart(this.t, timeout, false, callback)

        return this
    endmethod

    method stop takes nothing returns nothing
        call TimerStart(this.t, 0.0, false, null)
        set this.next = head
        set head = this
    endmethod

    static method get_expired_data takes nothing returns integer
        local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
        local integer data = t.data
        call t.stop()
        return data
    endmethod
endstruct

static if DEBUG_MODE then
    private function panic takes string s returns nothing
        call BJDebugMsg("|cffFF0000" + s + "|r")
        if 1 / 0 == 1 then
        endif
    endfunction
endif

struct DummyUnit
    readonly unit u
    debug private boolean is_dummy
    integer data // user data because we use SetUnitUserData for the DummyUnit instance

    private integer bucket = -1
    private static thistype array buckets // each bucket stores a
    private thistype b_next // free list of dummy units

    private static method get_internal takes real facing returns thistype
        local thistype this = allocate()

        if this.u == null then
            set this.u = CreateUnit(DUMMY_PLAYER, DUMMY_ID, DUMMY_HOME_X, DUMMY_HOME_Y, facing)
            call SetUnitUserData(this.u, this)
            debug set this.is_dummy = true

static if ADD_ABILITIES then
            // set the "flying flag"
            call UnitAddAbility(this.u, 'Amrf')
            call UnitRemoveAbility(this.u, 'Amrf')

            call UnitAddAbility(this.u, 'Aloc') // unselectable
            call UnitAddAbility(this.u, 'Avul') // invulnerable
endif

            call SetUnitFlyHeight(u, DUMMY_HOME_Z, 0.0)
        endif

        call PauseUnit(this.u, false)
        call SetUnitPathing(this.u, false)

        return this
    endmethod

    static method get takes nothing returns thistype
        return get_internal(0.0)
    endmethod

    static method get_facing takes real radians returns thistype
        local real ang = radians * bj_RADTODEG
        local integer b // bucket
        local thistype this

        // we want ang to be in the range 0.0 .. 359.0
        set ang = ang - R2I(ang / 360.0) * 360.0
        if ang < 0.0 then
            set ang = ang + 360.0
        endif

        set b = R2I(ang / DEGS_PER_BUCKET)

        if buckets[b] != 0 then
            set this = buckets[b]
            set buckets[b] = buckets[b].b_next

        else
            set this = get_internal(ang)
        endif

        set this.bucket = b // doesn't matter as long as it != -1
        call SetUnitFacing(this.u, ang)

        return this
    endmethod

    method with_owner takes integer p returns thistype
        call SetUnitOwner(this.u, Player(p), /*change-color:*/ true)
        return this
    endmethod

    static method from takes unit u returns thistype
static if DEBUG_MODE then
        local thistype this = GetUnitUserData(u)
        if not (1 <= this and this <= 8190) or not this.is_dummy then
            call panic("unit(" + I2S(GetHandleId(u)) + ") is not a thistype instance")
        endif
        return this
else
        return GetUnitUserData(u)
endif
    endmethod

    method return_instantly takes nothing returns nothing
        local unit u = this.u
        local real ang
        local integer b // bucket

        call SetUnitOwner(u, DUMMY_PLAYER, /*change-color:*/ true)
        call IssueImmediateOrder(u, "stop")

        call SetUnitX(u, DUMMY_HOME_X)
        call SetUnitY(u, DUMMY_HOME_Y)
        call SetUnitFlyHeight(u, DUMMY_HOME_Z, 0.0)

        call SetUnitVertexColor(u, 255, 255, 255, 255)
        call SetUnitScale(u, 1.0, 1.0, 1.0)
        call SetUnitTimeScale(u, 1.0)

        call PauseUnit(u, true)

        set this.data = 0

        if this.bucket == -1 then
            call this.deallocate()

        else
            set ang = GetUnitFacing(this.u)
            set b = R2I(ang / DEGS_PER_BUCKET)

            set this.b_next = buckets[b]
            set buckets[b] = this
        endif

        set u = null
    endmethod

    private static method delayed_return takes nothing returns nothing
        call thistype(Timer.get_expired_data()).return_instantly()
    endmethod

    method return_after takes real delay returns nothing
        call Timer.start(this, delay, function thistype.delayed_return)
    endmethod

endstruct

endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
MissileRecycler already does the things included here (and more), though it has no timed recycling.
What more does it do?

Could you defend this over existing resource such as these two? - MissileRecycler, DummyUnitStack

The one doesn't have timed recycling, as you said, and "dummy home", the other doesn't seem support retrieving dummies [roughly] facing some angle.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
MissileRecycler has a built-in feature that prevents dummies that just got recycled from being retrieved - the purpose is mainly similar to timed recycling. The only thing I thought could be added to it is the dummy home option, anything else is already good. To make a replacement would require some significant improvements or additions from the original because it's hard to deprecate a resource that has already been widely used and is also performing well. Btw, it also has a dummy preloading option and a dummy population limiter.
Another important thing is to consider is the API. Public resources should strive to follow the JPAG, atleast in their public APIs for users.

With that said, I still favor MissileRecycler over this.
 
Top