- 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