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