Name | Type | is_array | initial_value |
library AttachUnit uses optional UnitDex, ListT, RiseAndFall
/*
AttachUnit v1.02
by
Spellbound
________DESCRIPTION______________________________________________________________________________________________________________________________
AttachUnit simply binds a unit to another, with respect to angle, distance and, optionally,
facing angle of the host. Carrier units are called Hosts and attached units are called Guests.
AttachUnit comes with a turret functionality that allows Guests to rotates and attack normally,
after which they will return to a specific facing position. They can also be made to slowly
rotate over time, which maybe be useful for sci-fi type attachements.
________REQUIREMENTS_____________________________________________________________________________________________________________________________
Any custom value unit indexer. Pick ONLY ONE of the following:
GUI Unit Indexer by Bribe - https://www.hiveworkshop.com/threads/gui-unit-indexer-1-4-0-0.197329/
GUI Unit Event by Bribe - https://www.hiveworkshop.com/threads/gui-unit-event-v2-4-0-0.201641/#resource-45995
UnitDex by TriggerHappy - https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
________INSTALLATION_____________________________________________________________________________________________________________________________
Simply copy-paste this trigger into your map and install a unit indexer that uses custom values. If you get an error telling you that UnitAlive
or BlzGetUnitMovementType have been re-declared, comment out the corresponding line:
*/
native UnitAlive takes unit u returns boolean
native BlzGetUnitMovementType takes unit whichUnit returns integer
/*
________API______________________________________________________________________________________________________________________________________
/* IMPORTANT */ For units to function as Guests, they must have a positive, non-zero movement speed. Otherwise they will not move along with
their Host.
call AttachUnitToHost takes unit guest, unit host, real angle, boolean staticAngle, real angleFacing, real distance, real zOffset, real offsetFix
^ This function will attach a unit to a host.
[_ARGUMENTS_]
unit guest - the unit to attach (aka the Guest)
unit host - the Host
real angle - the angle relative to the Host's facing angle at which the Guest is offset to. In degrees.
boolean staticAngle - if true, the Guest's angle offset will ignore the Host's facing direction
real angleFacing - the starting facing angle of the Guest
real distance - the distance between the Host and the Guest
real zOffset - the height difference from the Host
real offsetFix - if your Guest is off-center, try setting this to -16 or 16.
call SetGuestFacingProperties takes unit guest, real startAngle, real rate, real cooldown, boolean dynamicFacing, boolean turretMode
^ This sets an attached unit's facing properties. If this is not called, the unit will always face the same direction.
[_ARGUMENTS_]
unit guest - the attached unit (aka Guest)
real startAngle - the angle at which the Guest begins at. If dynamicFacing (see below) is false, the guest will always face this specific angle.
real rate - the speed at which the Guest rotates over time. Set to zero to ignore.
real cooldown - this is neccessary if the Guest can attack. It will resume it facing parameters after that cooldown has expired.
boolean dynamicFacing - if this is true, the facing angle of the Guest will depend on the facing of the Host.
boolean turretMode - Set this to true if you want your Guest to be able to attack. Otherwise, the facing parameters will keep interferring.
call DettachGuest takes unit guest, real time
^ Call this function when you want to Dettach a Guest from a Host. real time is for how long after Dettachement do you want the Guest to die.
Set real time to zero if you don't want the Guest to die. This function is called automatically when a Guest dies.
call DettachAllGuests takes unit host, real time
^ Call this function on a Host to Dettach all its Guests. This function will not get called automatically on Host death and must be done manually.
Similarly to DettachGuest, real time will determine how long after Dettachment will the Guest die. Set to zero to not kill your Dettached Guests.
call GetRandomGuest takes unit host, boolepx guestFilter returns unit
^ This will return a random Guest attached to a Host. Set guestFilter to null if you do not wish to filter your guests.
/* IMPORTANT */ In your filter you MUST use the variable 'FilterGuest' and NOT GetFilterUnit().
call GetNumberOfGuests takes unit host returns integer
^ This will return the number of Guests currently connected to a Host.
call GroupGuests takes unit host returns group
^ this will take all Guests attached to a Host and put them in a unit group via:
set YourGroup = GrouGuests(host)
call IsUnitHost takes unit host returns boolean
^ Returns true if a unit is a Host.
call IsUnitGuest takes unit guest returns boolean
^ Returns true if a unit is a Guest.
call ShowGuest takes unit guest returns nothing
^ Unhides a hidden Guest without breaking locust.
call ShowGuests takes unit host returns nothing
^ Cycles through all the Guests of a Host and calls ShowGuest() on them to unhide them. This function is called automatically when leaving a transport.
If you wish for Guests to remain hidden when unloading from transport, call HideGuests() on your Host after it unloads. This will not break locust.
call HideGuests takes unit host returns nothing
^ Cycles through all Guests a Host has and hides them. This function is called automatically when loading a Host in a transport, but not when hiding a unit by
triggers. Call this function manually then and it's corresponding ShowGuests() when unhiding.
*/
globals
// interval between Guest updates. Do not change this or things will look choppy.
private constant real TIMEOUT = .03125
// order attack for turret mode
private constant integer ORDER_ATTACK = 851983
// If true then Guests will reset to their default fly height when they are dettached.
private constant boolean RESET_HEIGHT = true
// the speed at which they rise/fall if RESET_HEIGHT is true.
private constant real FALL_SPEED = 400.
unit FilterGuest = null
private trigger FilterTrig = CreateTrigger()
private hashtable TurretStorage = InitHashtable()
private timer Clock = CreateTimer()
private iList GlobalGuestList = 0
private iList GlobalHostList = 0
private Host array H_ID
private Guest array G_ID
private group GuestGroup = CreateGroup()
endglobals
//! runtextmacro optional DEFINE_LIST("", "AUList", "integer")
static if LIBRARY_ListT then
private struct GlobalList
static AUList Guests = 0
static AUList Hosts = 0
endstruct
function ListTGetRandom takes AUList list returns AUListItem
local integer size = list.size()
local integer i
local AUListItem node = 0
debug if size == 0 then
debug call BJDebugMsg("the list is empty")
debug return 0
debug endif
// thanks to Wareditor for that bit of code
if size > 0 then
set i = GetRandomInt(1, size)
if i > size / 2 then
set i = size - i
set node = list.last
loop
exitwhen i == 0
set node = node.prev
set i = i - 1
endloop
else
set i = i - 1
set node = list.first
loop
exitwhen i == 0
set node = node.next
set i = i - 1
endloop
endif
endif
return node
endfunction
endif
// LINKED LIST
private struct iNode
iNode next
iNode prev
integer data
method destroy takes nothing returns nothing
set this.prev.next = this.next
set this.next.prev = this.prev
set this.next = 0
set this.prev = 0
set this.data = 0
call this.deallocate()
endmethod
static method create takes integer i returns thistype
local thistype this = allocate()
set this.next = 0
set this.prev = 0
set this.data = i
return this
endmethod
endstruct
struct iList
readonly iNode first
readonly iNode last
readonly integer count
method destroy takes nothing returns nothing
local iNode node = .first
local iNode nodeNext
loop
exitwhen node == 0
set nodeNext = node.next
call node.destroy()
endloop
set .first = 0
set .last = 0
set .count = 0
call this.deallocate()
endmethod
// returns the size of a list
method size takes nothing returns integer
return this.count
endmethod
// inserts an element at the end of the list
method push takes integer i returns nothing
local iNode node = allocate()
if .count == 0 then
set .first = node
else
set .last.next = node
endif
set node.prev = .last // .last is already set to 0 at list creation
set node.next = 0 // otherwise the list will cycle forever
set .last = node
set node.data = i
set .count = .count + 1
endmethod
// removes a node from the list
method erase takes iNode node returns nothing
call node.destroy()
set .count = .count - 1
endmethod
method getRandom takes nothing returns iNode
local integer i = GetRandomInt(1, .count)
local iNode node = 0
debug if .count == 0 then
debug call BJDebugMsg("the list is empty")
debug return 0
debug endif
// thanks to Wareditor for that bit of code
if .count > 0 then
if i > .count / 2 then
set i = .count - i
set node = .last
loop
exitwhen i == 0
set node = node.prev
set i = i - 1
endloop
else
set i = i - 1
set node = .first
loop
exitwhen i == 0
set node = node.next
set i = i - 1
endloop
endif
endif
return node
endmethod
method find takes integer i returns iNode
local iNode node = .first
loop
exitwhen node == 0 or node.data == i
set node = node.next
endloop
return node
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set this.first = 0
set this.last = 0
set this.count = 0
return this
endmethod
endstruct
struct Guest
real cooldown
real cooldownReset
trigger turretTrigger
boolean dynamicFacing
boolean updateFacing
real distance
real angle
real zOffset
real offsetFix
real angleRate
real facing
real guestFacing
boolean staticAngle
unit parent
unit u
method destroy takes nothing returns nothing
set this.u = null
set this.parent = null
set this.staticAngle = false
set this.dynamicFacing = false
set this.updateFacing = false
call this.deallocate()
endmethod
static method create takes unit g, unit h returns thistype
local thistype this = allocate()
static if LIBRARY_ListT then
call GlobalList.Guests.push(this)
else
call GlobalGuestList.push(this)
endif
set this.parent = h
set this.u = g
return this
endmethod
endstruct
struct Host
real x
real y
real z
real f
unit u
boolean hiddenState
static if LIBRARY_ListT then
AUList guestList
else
iList guestList
endif
method destroy takes nothing returns nothing
call this.guestList.destroy()
set this.hiddenState = false
static if LIBRARY_ListT then
call GlobalList.Hosts.removeElem(this)
else
call GlobalHostList.erase(this)
endif
call this.deallocate()
endmethod
method addGuest takes Guest guest returns nothing
call this.guestList.push(guest)
endmethod
static method create takes unit h returns thistype
local thistype this = allocate()
static if LIBRARY_ListT then
if this.guestList == 0 then
set this.guestList = AUList.create()
call GlobalList.Hosts.push(this)
endif
else
if this.guestList == 0 then
set this.guestList = iList.create()
call GlobalHostList.push(this)
endif
endif
set this.hiddenState = IsUnitHidden(h)
set this.u = h
return this
endmethod
endstruct
//REFERENCE AND OTHER USEFUL FUNCTIONS
function IsUnitHost takes unit h returns boolean
return H_ID[GetUnitUserData(h)] != 0
endfunction
function IsUnitGuest takes unit g returns boolean
return G_ID[GetUnitUserData(g)] != 0
endfunction
function GetNumberOfGuests takes unit h returns integer
local Host host = H_ID[GetUnitUserData(h)]
return host.guestList.size()
endfunction
function GroupGuests takes unit h returns group
local Host host = H_ID[GetUnitUserData(h)]
local Guest guest
static if LIBRARY_ListT then
local AUListItem node = host.guestList.first
else
local iNode node = host.guestList.first
endif
call GroupClear(GuestGroup)
if IsUnitHost(h) then
loop
exitwhen node == 0
set guest = node.data
call GroupAddUnit(GuestGroup, guest.u)
set node = node.next
endloop
endif
return GuestGroup
endfunction
function GetRandomGuest takes unit h, boolexpr guestFilter returns unit
local Host host = H_ID[GetUnitUserData(h)]
local Guest guest
static if LIBRARY_ListT then
local AUListItem node = ListTGetRandom(host.guestList)
else
local iNode node = host.guestList.getRandom()
endif
local unit u
local triggercondition tcnd
local boolean firstPass = false
local integer n = host.guestList.size()
if guestFilter == null then
set guest = node.data
return guest.u
else
set firstPass = (node == host.guestList.first)
set FilterGuest = null
loop
exitwhen node == 0 and firstPass
if node == 0 and not firstPass then
set node = host.guestList.first
set firstPass = true
endif
set guest = node.data
set u = guest.u
set tcnd = TriggerAddCondition(FilterTrig, guestFilter)
if TriggerEvaluate(FilterTrig) then
set FilterGuest = u
endif
call TriggerRemoveCondition(FilterTrig, tcnd)
set node = node.next
endloop
set u = null
set tcnd = null
return FilterGuest
endif
return null
endfunction
function ShowGuest takes unit g returns nothing
if IsUnitHidden(g) then
call ShowUnit(g, true)
if GetUnitAbilityLevel(g, 'Aloc') > 0 then
call UnitRemoveAbility(g, 'Aloc')
call UnitAddAbility(g, 'Aloc')
endif
endif
endfunction
function ShowGuestsEx takes unit h, boolean flag returns nothing
local Host host = H_ID[GetUnitUserData(h)]
local Guest guest
static if LIBRARY_ListT then
local AUListItem node = host.guestList.first
else
local iNode node = host.guestList.first
endif
loop
exitwhen node == 0
set guest = node.data
if flag then
call ShowGuest(guest.u)
else
call ShowUnit(guest.u, false)
endif
set node = node.next
endloop
endfunction
function ShowGuests takes unit h returns nothing
call ShowGuestsEx(h, true)
endfunction
function HideGuests takes unit h returns nothing
call ShowGuestsEx(h, false)
endfunction
//MAIN API FUNCITONS
//NB: Functions that start with 'private' cannot be called outside of this library
function DettachGuest takes unit g, real time returns nothing
local Guest guest = G_ID[GetUnitUserData(g)]
local Host host
static if LIBRARY_RiseAndFall then
local real hc // height current
local real hd // height default
local real duration
endif
if guest.turretTrigger != null then
call FlushChildHashtable(TurretStorage, GetHandleId(guest.turretTrigger))
call DestroyTrigger(guest.turretTrigger)
set guest.turretTrigger = null
endif
if guest.parent != null then
set host = H_ID[GetUnitUserData(guest.parent)]
static if LIBRARY_ListT then
call host.guestList.removeElem(guest)
else
call host.guestList.erase(guest)
endif
if UnitAlive(g) and time > 0. then
call UnitApplyTimedLife(g , 'BTLF', time)
endif
call SetUnitPropWindow(g, GetUnitDefaultPropWindow(g) * bj_DEGTORAD)
call UnitRemoveAbility(g, 'Aeth')
call SetUnitPathing(g, true)
static if RESET_HEIGHT then
static if LIBRARY_RiseAndFall then
set hc = GetUnitFlyHeight(g)
set hd = GetUnitDefaultFlyHeight(g)
set duration = (hc - hd) / FALL_SPEED
if duration < 0. then
set duration = -duration
endif
call RiseAndFall.start(g, hc, hd, duration, BlzGetUnitMovementType(g))
else
call SetUnitFlyHeight(g, GetUnitDefaultFlyHeight(g), FALL_SPEED)
endif
endif
if host.guestList.size() == 0 then
static if LIBRARY_ListT then
call GlobalList.Hosts.removeElem(host)
else
call GlobalHostList.erase(host)
endif
call host.destroy()
endif
endif
call guest.destroy()
endfunction
function DettachAllGuests takes unit h, real time returns nothing
local Host host = H_ID[GetUnitUserData(h)]
local Guest guest
static if LIBRARY_ListT then
local AUListItem node = host.guestList.first
local AUListItem nodeNext
else
local iNode node = host.guestList.first
local iNode nodeNext
endif
loop
exitwhen node == 0
set nodeNext = node.next
set guest = node.data
call DettachGuest(guest.u, time)
set node = nodeNext
endloop
endfunction
private function TurretActions takes nothing returns nothing
local Guest guest = LoadInteger(TurretStorage, GetHandleId(GetTriggeringTrigger()), 0)
set guest.cooldown = guest.cooldownReset
if GetUnitCurrentOrder(guest.u) != ORDER_ATTACK then
call IssuePointOrderById(guest.u, ORDER_ATTACK, GetUnitX(guest.u), GetUnitY(guest.u))
endif
endfunction
private function UpdateGuests takes nothing returns nothing
local real x
local real y
local real z
local real f
local real a
static if LIBRARY_ListT then
local AUListItem node = GlobalList.Hosts.first
local AUListItem nodeNext
local AUListItem nNode
local AUListItem nNodeNext
else
local iNode node = GlobalHostList.first
local iNode nodeNext
local iNode nNode
local iNode nNodeNext
endif
local Host host
local Guest guest
// This loop cycles through all the Hosts and a secondary loop inside cycles through their
// attached Guests, updating their x, y and z coordiates.
loop
exitwhen node == 0
set nodeNext = node.next
set host = node.data
set x = GetUnitX(host.u)
set y = GetUnitY(host.u)
set z = BlzGetLocalUnitZ(host.u)
set f = GetUnitFacing(host.u)
if not IsUnitHidden(host.u) then
// unhide guests if host was previously hidden
if host.hiddenState then
set host.hiddenState = false
call ShowGuestsEx(host.u, true)
endif
if x != host.x or y != host.y or z != host.z or f != host.f then
set nNode = host.guestList.first
loop
exitwhen nNode == 0
set nNodeNext = nNode.next
set guest = nNode.data
if guest.staticAngle then
set a = 0.
else
set a = f * bj_DEGTORAD
endif
call SetUnitX(guest.u, guest.offsetFix + x + Cos(guest.angle + a) * guest.distance)
call SetUnitY(guest.u, guest.offsetFix + y + Sin(guest.angle + a) * guest.distance)
call SetUnitFlyHeight(guest.u, z + guest.zOffset, 0.)
set nNode = nNodeNext
endloop
set host.x = x
set host.y = y
set host.z = z
set host.f = f * bj_RADTODEG
endif
else
// hide guests if host was not previously hidden
if not host.hiddenState then
set host.hiddenState = true
call ShowGuestsEx(host.u, false)
endif
endif
set node = nodeNext
endloop
// Updates the facing angle of Guests and when not to turn (eg when in combat). Guests that
// have no idle rotation will not get added to this list.
static if LIBRARY_ListT then
set node = GlobalList.Guests.first
else
set node = GlobalGuestList.first
endif
loop
exitwhen node == 0
set nodeNext = node.next
set guest = node.data
if not UnitAlive(guest.u) then
call DettachGuest(guest.u, 0.)
else
if guest.updateFacing then
if guest.dynamicFacing then
set f = GetUnitFacing(guest.parent)
else
set f = 0.
endif
if GetUnitCurrentOrder(guest.u) != 0 then
if guest.cooldown > 0. then
set guest.cooldown = guest.cooldown - TIMEOUT
else
set guest.cooldown = guest.cooldownReset
call IssueImmediateOrderById(guest.u, 851973) //order stunned
endif
if guest.angleRate != 0. then
// Store the facing of the unit if it needs to rotate later
set guest.facing = GetUnitFacing(guest.u)
endif
else
if guest.angleRate == 0. then
// If the unit doesn't rotate
call SetUnitFacing(guest.u, guest.facing + f)
else
set guest.facing = guest.facing + guest.angleRate
call SetUnitFacing(guest.u, guest.facing + f)
endif
endif
endif
endif
set node = nodeNext
endloop
endfunction
function SetGuestFacingProperties takes unit g, real startAngle, real rate, real cooldown, boolean dynamicFacing, boolean turretMode returns nothing
local Guest guest = G_ID[GetUnitUserData(g)]
set guest.cooldownReset = cooldown
set guest.cooldown = cooldown
if rate == 0. then
set guest.angleRate = 0.
else
set guest.angleRate = rate * TIMEOUT
endif
set guest.dynamicFacing = dynamicFacing
if dynamicFacing then
set guest.guestFacing = startAngle - GetUnitFacing(guest.parent)
else
set guest.guestFacing = startAngle
endif
call SetUnitFacing(g, startAngle)
set guest.updateFacing = true
if turretMode then
set guest.turretTrigger = CreateTrigger()
call TriggerRegisterUnitEvent(guest.turretTrigger, g, EVENT_UNIT_ACQUIRED_TARGET)
call TriggerRegisterUnitEvent(guest.turretTrigger, g, EVENT_UNIT_TARGET_IN_RANGE)
call SaveInteger(TurretStorage, GetHandleId(guest.turretTrigger), 0, guest)
call TriggerAddCondition(guest.turretTrigger, Condition(function TurretActions))
endif
endfunction
function AttachUnitToHost takes unit g, unit h, real angle, boolean staticAngle, real angleFacing, real distance, real zOffset, real offsetFix returns nothing
local integer idH = GetUnitUserData(h)
local integer idG = GetUnitUserData(g)
local Host host
local Guest guest
static if LIBRARY_ListT then
local AUListItem node = GlobalList.Hosts.first
else
local iNode node = GlobalHostList.first
endif
local real x = GetUnitX(h)
local real y = GetUnitY(h)
local real z = BlzGetLocalUnitZ(h)
local real f = GetUnitFacing(h)
local real a
// check if the Host has an instance
if H_ID[idH] == 0 then
set host = Host.create(h)
set host.x = x
set host.y = y
set host.z = z
set host.f = f
set H_ID[idH] = host
else
set host = H_ID[idH]
endif
// if guest instance is not null then it's already attached to a unit
if G_ID[idG] == 0 then
set guest = Guest.create(g, h)
set G_ID[idG] = guest
else
set guest = G_ID[idG]
if guest.parent == h then
return
else
call DettachGuest(g, 0.)
endif
endif
call host.addGuest(guest)
set guest.angle = angle * bj_DEGTORAD
set guest.staticAngle = staticAngle
set guest.facing = angleFacing
set guest.distance = distance
set guest.zOffset = zOffset
set guest.offsetFix = offsetFix
set guest.parent = h
if staticAngle then
set a = guest.angle
else
set a = (angle + f) * bj_DEGTORAD
endif
call SetUnitX(g, offsetFix + x + Cos(a) * distance)
call SetUnitY(g, offsetFix + y + Sin(a) * distance)
call SetUnitFlyHeight(g, z + zOffset, 0.)
call SetUnitFacing(g, angleFacing + f)
call SetUnitPropWindow(g, 0.)
call UnitAddAbility(g, 'Aeth')
call SetUnitPathing(g, false)
static if LIBRARY_ListT then
if GlobalList.Hosts.size() == 1 then
call TimerStart(Clock, TIMEOUT, true, function UpdateGuests)
endif
else
if GlobalHostList.size() == 1 then
call TimerStart(Clock, TIMEOUT, true, function UpdateGuests)
endif
endif
endfunction
// Initialisation
private module init
private static method onInit takes nothing returns nothing
static if LIBRARY_ListT then
set GlobalList.Guests = AUList.create()
set GlobalList.Hosts = AUList.create()
else
set GlobalGuestList = iList.create()
set GlobalHostList = iList.create()
endif
endmethod
endmodule
private struct Init
implement init
endstruct
endlibrary
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.1, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
// //! external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines except for UnitDexRemove
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeidex takes code func returns triggercondition
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
* - All public functions are inlined except UnitDexRemove.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove.evaluate(u, runEvents)
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 0
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
// Index preplaced units
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
// run init triggers
set i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TimerStart(CreateTimer(), 0, false, function thistype.onGameStart)
endmethod
endmodule
endlibrary
/*****************************************************************************
*
* List<T> v2.1.2.3
* by Bannar
*
* Doubly-linked list.
*
******************************************************************************
*
* Requirements:
*
* Table by Bribe
* hiveworkshop.com/threads/snippet-new-table.188084/
*
* Alloc - choose whatever you like
* e.g.: by Sevion hiveworkshop.com/threads/snippet-alloc.192348/
*
******************************************************************************
*
* Implementation:
*
* macro DEFINE_LIST takes ACCESS, NAME, TYPE
*
* macro DEFINE_STRUCT_LIST takes ACCESS, NAME, TYPE
*
* ACCESS - encapsulation, choose restriction access
* NAME - name of list type
* TYPE - type of values stored
*
* Implementation notes:
*
* - DEFINE_STRUCT_LIST macro purpose is to provide natural typecasting syntax for struct types.
* - <NAME>Item structs inline directly into hashtable operations thus generate basically no code.
* - Lists defined with DEFINE_STRUCT_LIST are inlined nicely into single create method and single integer array.
*
******************************************************************************
*
* struct API:
*
* struct <NAME>Item:
*
* | <TYPE> data
* | <NAME>Item next
* | <NAME>Item prev
*
*
* General:
*
* | static method create takes nothing returns thistype
* | Default ctor.
* |
* | static method operator [] takes thistype other returns thistype
* | Copy ctor.
* |
* | method destroy takes nothing returns nothing
* | Default dctor.
* |
* | method empty takes nothing returns boolean
* | Checks whether the list is empty.
* |
* | method size takes nothing returns integer
* | Returns size of a list.
*
*
* Access:
*
* | readonly <NAME>Item first
* | readonly <NAME>Item last
* |
* | method front takes nothing returns $TYPE$
* | Retrieves first element.
* |
* | method back takes nothing returns $TYPE$
* | Retrieves last element.
*
*
* Modifiers:
*
* | method clear takes nothing returns nothing
* | Flushes list and recycles its nodes.
* |
* | method push takes $TYPE$ value returns thistype
* | Adds elements to the end.
* |
* | method unshift takes $TYPE$ value returns thistype
* | Adds elements to the front.
* |
* | method pop takes nothing returns thistype
* | Removes the last element.
* |
* | method shift takes nothing returns thistype
* | Removes the first element.
* |
* | method find takes $TYPE$ value returns $NAME$Item
* | Returns the first node which data equals value.
* |
* | method erase takes $NAME$Item node returns boolean
* | Removes node from the list, returns true on success.
* |
* | method removeElem takes $TYPE$ value returns thistype
* | Removes first element that equals value from the list.
*
*
*****************************************************************************/
library ListT requires Table, Alloc
//! runtextmacro DEFINE_LIST("", "IntegerList", "integer")
// Run here any global list types you want to be defined.
//! textmacro_once DEFINE_LIST takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$Item extends array
// No default ctor and dctor due to limited array size
method operator data takes nothing returns $TYPE$
return Table(this).$TYPE$[-1] // hashtable[ node, -1 ] = data
endmethod
method operator data= takes $TYPE$ value returns nothing
set Table(this).$TYPE$[-1] = value
endmethod
method operator next takes nothing returns thistype
return Table(this)[-2] // hashtable[ node, -2 ] = next
endmethod
method operator next= takes thistype value returns nothing
set Table(this)[-2] = value
endmethod
method operator prev takes nothing returns thistype
return Table(this)[-3] // hashtable[ node, -3 ] = prev
endmethod
method operator prev= takes thistype value returns nothing
set Table(this)[-3] = value
endmethod
endstruct
$ACCESS$ struct $NAME$ extends array
readonly $NAME$Item first
readonly $NAME$Item last
private integer count
implement Alloc
private static method setNodeOwner takes $NAME$Item node, $NAME$ owner returns nothing
set Table(node)[-4] = owner
endmethod
private static method getNodeOwner takes $NAME$Item node returns thistype
return Table(node)[-4]
endmethod
private method createNode takes $TYPE$ value returns $NAME$Item
local $NAME$Item node = Table.create()
set node.data = value
call setNodeOwner(node, this) // ownership
return node
endmethod
private method deleteNode takes $NAME$Item node returns nothing
call Table(node).destroy() // also removes ownership
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set count = 0
return this
endmethod
method clear takes nothing returns nothing
local $NAME$Item node = first
local $NAME$Item temp
loop // recycle all Table indexes
exitwhen 0 == node
set temp = node.next
call deleteNode(node)
set node = temp
endloop
set first = 0
set last = 0
set count = 0
endmethod
method destroy takes nothing returns nothing
call clear()
call deallocate()
endmethod
method front takes nothing returns $TYPE$
return first.data
endmethod
method back takes nothing returns $TYPE$
return last.data
endmethod
method empty takes nothing returns boolean
return count == 0
endmethod
method size takes nothing returns integer
return count
endmethod
method push takes $TYPE$ value returns thistype
local $NAME$Item node = createNode(value)
if not empty() then
set last.next = node
set node.prev = last
else
set first = node
set node.prev = 0
endif
set last = node
set node.next = 0
set count = count + 1
return this
endmethod
method unshift takes $TYPE$ value returns thistype
local $NAME$Item node = createNode(value)
if not empty() then
set first.prev = node
set node.next = first
else
set last = node
set node.next = 0
endif
set first = node
set node.prev = 0
set count = count + 1
return this
endmethod
method pop takes nothing returns thistype
local $NAME$Item node
if not empty() then
set node = last
set last = last.prev
if last == 0 then
set first = 0
else
set last.next = 0
endif
call deleteNode(node)
set count = count - 1
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::pop failed for instance "+I2S(this)+". List is empty.")
endif
return this
endmethod
method shift takes nothing returns thistype
local $NAME$Item node
if not empty() then
set node = first
set first = first.next
if first == 0 then
set last = 0
else
set first.prev = 0
endif
call deleteNode(node)
set count = count - 1
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::shift failed for instance "+I2S(this)+". List is empty.")
endif
return this
endmethod
static method operator [] takes thistype other returns thistype
local thistype instance = create()
local $NAME$Item node = other.first
loop
exitwhen node == 0
call instance.push(node.data)
set node = node.next
endloop
return instance
endmethod
method find takes $TYPE$ value returns $NAME$Item
local $NAME$Item node = first
loop
exitwhen node == 0 or node.data == value
set node = node.next
endloop
return node
endmethod
method erase takes $NAME$Item node returns boolean
if getNodeOwner(node) == this then // match ownership
if node == first then
call shift()
elseif node == last then
call pop()
else
set node.prev.next = node.next
set node.next.prev = node.prev
call deleteNode(node)
set count = count - 1
endif
return true
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::erase failed for instance "+I2S(this)+". Attempted to remove invalid node "+I2S(node)+".")
endif
return false
endmethod
method remove takes $NAME$Item node returns boolean
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Method $NAME$::remove is obsolete, use $NAME$::erase instead.")
return erase(node)
endmethod
method removeElem takes $TYPE$ value returns thistype
local $NAME$Item node = find(value)
if node != 0 then
call erase(node)
endif
return this
endmethod
endstruct
//! endtextmacro
//! textmacro_once DEFINE_STRUCT_LIST takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$Item extends array
// Cannot inherit methods via delegate due to limited array size
method operator data takes nothing returns $TYPE$
return IntegerListItem(this).data
endmethod
method operator data= takes $TYPE$ value returns nothing
set IntegerListItem(this).data = value
endmethod
method operator next takes nothing returns thistype
return IntegerListItem(this).next
endmethod
method operator next= takes thistype value returns nothing
set IntegerListItem(this).next = value
endmethod
method operator prev takes nothing returns thistype
return IntegerListItem(this).prev
endmethod
method operator prev= takes thistype value returns nothing
set IntegerListItem(this).prev = value
endmethod
endstruct
$ACCESS$ struct $NAME$ extends array
private delegate IntegerList parent
static method create takes nothing returns thistype
local thistype this = IntegerList.create()
set parent = this
return this
endmethod
method front takes nothing returns $TYPE$
return parent.front()
endmethod
method back takes nothing returns $TYPE$
return parent.back()
endmethod
endstruct
//! endtextmacro
endlibrary
library Alloc /* v1.3.1.1
*************************************************************************************
*
* */ uses /*
*
* */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
* */ optional MemoryAnalysis /*
*
*************************************************************************************
*
* Minimizes code generation and global variables while maintaining
* excellent performance.
*
* local thistype this = recycler[0]
*
* if (recycler[this] == 0) then
* set recycler[0] = this + 1
* else
* set recycler[0] = recycler[this]
* endif
*
************************************************************************************
*
* module Alloc
*
* static method allocate takes nothing returns thistype
* method deallocate takes nothing returns nothing
*
* The Following Require Error Message To Be In The Map
* --------------------------------------------------------
*
* debug readonly boolean allocated
*
* The Following Require Memory Analysis To Be In The Map
* --------------------------------------------------------
*
* debug readonly integer monitorCount
* - the amount of global memory being monitored by this
* debug readonly integer monitorString
* - gets a string representation of all global memory being monitored by this
* debug readonly integer address
* - global memory address for debugging
* - used with monitor and stopMonitor
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
* debug method monitor takes string label, integer address returns nothing
* - monitor a global memory address with a label
* - used to identify memory leaks
* - should be memory that ought to be destroyed by the time this is destroyed
* debug method stopMonitor takes integer address returns nothing
* - stops monitoring global memory
* debug method stopMonitorValue takes handle monitoredHandle returns nothing
* - stops monitoring handle values
*
* The Following Are Used To Monitor Handle Values
*
* debug method monitor_widget takes string label, widget handleToTrack returns nothing
* debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
* debug method monitor_item takes string label, item handleToTrack returns nothing
* debug method monitor_unit takes string label, unit handleToTrack returns nothing
* debug method monitor_timer takes string label, timer handleToTrack returns nothing
* debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
* debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
* debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
* debug method monitor_force takes string label, force handleToTrack returns nothing
* debug method monitor_group takes string label, group handleToTrack returns nothing
* debug method monitor_location takes string label, location handleToTrack returns nothing
* debug method monitor_rect takes string label, rect handleToTrack returns nothing
* debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
* debug method monitor_effect takes string label, effect handleToTrack returns nothing
* debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
* debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
* debug method monitor_quest takes string label, quest handleToTrack returns nothing
* debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
* debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
* debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
* debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
* debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
* debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
* debug method monitor_button takes string label, button handleToTrack returns nothing
* debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
* debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
* debug method monitor_image takes string label, image handleToTrack returns nothing
* debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
* debug method monitor_region takes string label, region handleToTrack returns nothing
* debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
* debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
*
*
* Thanks to Ruke for the algorithm
************************************************************************************/
module Alloc
/*
* stack
*/
private static integer array recycler
static if LIBRARY_MemoryAnalysis then
debug private MemoryMonitor globalAddress
debug method operator address takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "address", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress
debug endmethod
endif
/*
* allocation
*/
static method allocate takes nothing returns thistype
local thistype this = recycler[0]
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 8192, "Alloc", "allocate", "thistype", 0, "Overflow.")
endif
if (recycler[this] == 0) then
set recycler[0] = this + 1
else
set recycler[0] = recycler[this]
endif
static if LIBRARY_ErrorMessage then
debug set recycler[this] = -1
endif
static if LIBRARY_MemoryAnalysis then
debug set globalAddress = MemoryMonitor.allocate("thistype")
endif
return this
endmethod
method deallocate takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(recycler[this] != -1, "Alloc", "deallocate", "thistype", this, "Attempted To Deallocate Null Instance.")
endif
static if LIBRARY_MemoryAnalysis then
debug call globalAddress.deallocate()
debug set globalAddress = 0
endif
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
static if LIBRARY_MemoryAnalysis then
debug method monitor takes string label, integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor(label, address)
debug endmethod
debug method stopMonitor takes integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitor(address)
debug endmethod
debug method stopMonitorValue takes handle monitoredHandle returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitorValue", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitorValue(monitoredHandle)
debug endmethod
debug method operator monitorCount takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorCount", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorCount
debug endmethod
debug method operator monitorString takes nothing returns string
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorString", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorString
debug endmethod
debug method monitor_widget takes string label, widget handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_widget", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_widget(label, handleToTrack)
debug endmethod
debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_destructable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_destructable(label, handleToTrack)
debug endmethod
debug method monitor_item takes string label, item handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_item", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_item(label, handleToTrack)
debug endmethod
debug method monitor_unit takes string label, unit handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unit", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unit(label, handleToTrack)
debug endmethod
debug method monitor_timer takes string label, timer handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timer", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timer(label, handleToTrack)
debug endmethod
debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_trigger", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_trigger(label, handleToTrack)
debug endmethod
debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggercondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggercondition(label, handleToTrack)
debug endmethod
debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggeraction", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggeraction(label, handleToTrack)
debug endmethod
debug method monitor_force takes string label, force handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_force", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_force(label, handleToTrack)
debug endmethod
debug method monitor_group takes string label, group handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_group", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_group(label, handleToTrack)
debug endmethod
debug method monitor_location takes string label, location handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_location", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_location(label, handleToTrack)
debug endmethod
debug method monitor_rect takes string label, rect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_rect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_rect(label, handleToTrack)
debug endmethod
debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_boolexpr", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_boolexpr(label, handleToTrack)
debug endmethod
debug method monitor_effect takes string label, effect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_effect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_effect(label, handleToTrack)
debug endmethod
debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unitpool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unitpool(label, handleToTrack)
debug endmethod
debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_itempool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_itempool(label, handleToTrack)
debug endmethod
debug method monitor_quest takes string label, quest handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_quest", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_quest(label, handleToTrack)
debug endmethod
debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_defeatcondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_defeatcondition(label, handleToTrack)
debug endmethod
debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timerdialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timerdialog(label, handleToTrack)
debug endmethod
debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_leaderboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_leaderboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboarditem", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboarditem(label, handleToTrack)
debug endmethod
debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_dialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_dialog(label, handleToTrack)
debug endmethod
debug method monitor_button takes string label, button handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_button", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_button(label, handleToTrack)
debug endmethod
debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_texttag", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_texttag(label, handleToTrack)
debug endmethod
debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_lightning", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_lightning(label, handleToTrack)
debug endmethod
debug method monitor_image takes string label, image handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_image", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_image(label, handleToTrack)
debug endmethod
debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_ubersplat", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_ubersplat(label, handleToTrack)
debug endmethod
debug method monitor_region takes string label, region handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_region", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_region(label, handleToTrack)
debug endmethod
debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_fogmodifier", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_fogmodifier(label, handleToTrack)
debug endmethod
debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_hashtable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_hashtable(label, handleToTrack)
debug endmethod
static if DEBUG_MODE then
//! runtextmacro optional MEMORY_ANALYSIS_STATIC_FIELD_NEW("recycler")
static method calculateMemoryUsage takes nothing returns integer
return calculateAllocatedMemory__recycler()
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
return allocatedMemoryString__recycler()
endmethod
endif
endif
/*
* analysis
*/
static if LIBRARY_ErrorMessage then
debug method operator allocated takes nothing returns boolean
debug return recycler[this] == -1
debug endmethod
endif
/*
* initialization
*/
private static method onInit takes nothing returns nothing
set recycler[0] = 1
endmethod
endmodule
endlibrary
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary
library WorldBounds /* v2.0.0.0
************************************************************************************
*
* struct WorldBounds extends array
*
* Fields
* -------------------------
*
* readonly static integer maxX
* readonly static integer maxY
* readonly static integer minX
* readonly static integer minY
*
* readonly static integer centerX
* readonly static integer centerY
*
* readonly static rect world
* readonly static region worldRegion
*
************************************************************************************/
private module WorldBoundInit
private static method onInit takes nothing returns nothing
set world=GetWorldBounds()
set maxX = R2I(GetRectMaxX(world))
set maxY = R2I(GetRectMaxY(world))
set minX = R2I(GetRectMinX(world))
set minY = R2I(GetRectMinY(world))
set centerX = R2I((maxX + minX)/2)
set centerY = R2I((minY + maxY)/2)
set worldRegion = CreateRegion()
call RegionAddRect(worldRegion, world)
endmethod
endmodule
struct WorldBounds extends array
readonly static integer maxX
readonly static integer maxY
readonly static integer minX
readonly static integer minY
readonly static integer centerX
readonly static integer centerY
readonly static rect world
readonly static region worldRegion
implement WorldBoundInit
endstruct
endlibrary
library RiseAndFall uses TimerUtils
globals
private constant real INTERVAL = .03125
endglobals
struct RiseAndFall
unit u
real height
real heightStart
real heightEnd
real speed
real s
boolean isDecelerate
boolean isAirborn
boolean terminate
private static method update takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local real calc
local real hs
if .isDecelerate then
set calc = (.s * .s * .s * (.s * (.s * 6 - 15) + 10))
else
set calc = (.s * .s)
endif
set .s = .s + .speed
set .height = .heightStart + ( .heightEnd - .heightStart ) * calc
call SetUnitFlyHeight(.u, .height, 0.)
if .s >= 1. or .terminate then
call SetUnitFlyHeight(.u, .heightEnd, 0.)
if .isAirborn then
set hs = .heightStart
set .heightStart = .heightEnd
set .heightEnd = hs
set .s = 0.
else
call ReleaseTimer(t)
set .u = null
call this.deallocate()
endif
endif
set t = null
endmethod
static method start takes unit u, real currentZ, real endZ, real duration, integer moveType returns nothing
local thistype this = allocate()
set this.u = u
set this.height = 0.
set this.heightStart = currentZ
set this.heightEnd = endZ
set this.s = 0.
set this.speed = INTERVAL / duration
set this.isDecelerate = moveType == 8 or moveType == 2 // 8 == hover, 2 == flying
set this.isAirborn = false
set this.terminate = false
call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.update)
endmethod
static method startAirborn takes unit u, real currentZ, real endZ, real duration returns thistype
local thistype this = allocate()
set this.u = u
set this.height = 0.
set this.heightStart = currentZ
set this.heightEnd = endZ
set this.s = 0.
set this.speed = INTERVAL / duration
set this.isDecelerate = true
set this.isAirborn = true
set this.terminate = false
call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.update)
return this
endmethod
endstruct
endlibrary
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = false
private constant boolean USE_FLEXIBLE_OFFSET = true
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
scope Init initializer init
globals
constant real DIST = 128. * 4
constant real REVERSE_ANGLE = 180.
constant player PLAY = Player(0)
unit TestUnit
endglobals
private function Actions takes nothing returns nothing
local real x
local real y
local real a
local unit u
local unit array guest
//Gryphon Rider
set a = 360. * bj_DEGTORAD
set x = Cos(a) * DIST
set y = Sin(a) * DIST
set u = CreateUnit(PLAY, 'hgry', x, y, a * bj_RADTODEG + REVERSE_ANGLE)
/*
This is an example of guests that face the same way as the host. They are allowed to
attack and cast spells but will after 4 seconds of inaction return to face the same
angle as their host.
*/
set guest[0] = CreateUnit(PLAY, 'hsor', 0., 0., 0.)
set guest[1] = CreateUnit(PLAY, 'hsor', 0., 0., 0.)
set guest[2] = CreateUnit(PLAY, 'hsor', 0., 0., 0.)
call AttachUnitToHost(guest[0], u, 90., false, 0., 96., 30., 16.)
call AttachUnitToHost(guest[1], u, -90., false, 0., 96., 30., 16.)
call AttachUnitToHost(guest[2], u, 180., false, 0., 96., 30., 16.)
call SetGuestFacingProperties(guest[0], 180., 0., 4., true, true)
call SetGuestFacingProperties(guest[1], 180., 0., 4., true, true)
call SetGuestFacingProperties(guest[2], 180., 0., 4., true, true)
set TestUnit = u //Used in Filter Test
//Knight
/*
This is an example of a static-angle, static-facing host. Its turret properties enable
it to attack and cast spells, but afterwards it will return to face a 225° angle at 315°
of the knight's location. This guest ignores the Knight's facing direction.
*/
set a = 45. * bj_DEGTORAD
set x = Cos(a) * DIST
set y = Sin(a) * DIST
set u = CreateUnit(PLAY, 'hkni', x, y, a * bj_RADTODEG + REVERSE_ANGLE)
set guest[0] = CreateUnit(PLAY, 'uban', 0., 0., 0.)
call AttachUnitToHost(guest[0], u, 315., true, 0., 128., 120., 0.)
call SetGuestFacingProperties(guest[0], 225., 0., 4., false, true)
//Witch Doctor
/*
These guests do not have any facing parameters set up and thus will not be subject to
any facing restrictions and will always face the their starting direction. Guests with
no facing parameters cannot attack.
*/
set a = 90. * bj_DEGTORAD
set x = Cos(a) * DIST
set y = Sin(a) * DIST
set u = CreateUnit(PLAY, 'odoc', x, y, a * bj_RADTODEG + REVERSE_ANGLE)
set guest[0] = CreateUnit(PLAY, 'osp1', 0., 0., 0.)
set guest[1] = CreateUnit(PLAY, 'osp1', 0., 0., 0.)
call AttachUnitToHost(guest[0], u, 90., false, 0., 32., 60., 0.)
call AttachUnitToHost(guest[1], u, -90., false, 0., 32., 60., 0.)
//Mountain Giant
/*
This is the same as with the gryphon rider.
*/
set a = 135. * bj_DEGTORAD
set x = Cos(a) * DIST
set y = Sin(a) * DIST
set u = CreateUnit(PLAY, 'emtg', x, y, a * bj_RADTODEG + REVERSE_ANGLE)
set guest[0] = CreateUnit(PLAY, 'earc', 0., 0., 0.)
set guest[1] = CreateUnit(PLAY, 'earc', 0., 0., 0.)
call AttachUnitToHost(guest[0], u, 80., false, 0., 60., 180., -16.)
call AttachUnitToHost(guest[1], u, -80., false, 0., 60., 180., -16.)
call SetGuestFacingProperties(guest[0], 315., 0., 4., true, true)
call SetGuestFacingProperties(guest[1], 315., 0., 4., true, true)
//Steam Tank
/*
This is an example of the rotating turret guest. This guest has been set to rotate with
the host's facing angle, but merely for demonstration purposes. It's recommend for the
dynamicFacing parameter to be set to false.
This guest can attack and cast spells, but after 4 seconds of inaction will resume its
rotation from its current facing angle.
*/
set a = 180. * bj_DEGTORAD
set x = Cos(a) * DIST
set y = Sin(a) * DIST
set u = CreateUnit(PLAY, 'hmtt', x, y, a * bj_RADTODEG + REVERSE_ANGLE)
set guest[0] = CreateUnit(PLAY, 'hgyr', 0., 0., 0.)
call AttachUnitToHost(guest[0], u, 0., false, 0., 0., 0., -16.)
call SetGuestFacingProperties(guest[0], 360., 60., 4., true, true)
// Airborn test
set guest[0] = CreateUnit(PLAY, 'hfoo', -550., -460., 0.)
call RiseAndFall.startAirborn(guest[0], 160., 320., 1.)
//____END OF DEMONSTATION___________________________________________________________________
call SetTimeOfDay(8)
call FogEnableOff()
call FogMaskEnableOff()
set u = null
set guest[0] = null
set guest[1] = null
set guest[2] = null
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, 0., false)
call TriggerAddAction(t, function Actions)
set t = null
endfunction
endscope
scope DemoCode
function Actions takes nothing returns nothing
local unit host = GetIndexedUnit()
local unit guest
local real facing = GetUnitFacing(host)
if GetUnitTypeId(host) == 'hspt' then
set guest = CreateUnit(GetOwningPlayer(host), 'edfr', GetUnitX(host), GetUnitY(host), facing)
call AttachUnitToHost(guest, host, 0., false, facing, 0., 200., -16.)
call SetGuestFacingProperties(guest, facing, 10., 4., false, true)
endif
set host = null
set guest = null
endfunction
private module init
private method onInit takes nothing returns nothing
call RegisterUnitIndexEvent(Filter(function Actions), EVENT_UNIT_INDEX)
endmethod
endmodule
struct Init
implement init
endstruct
endscope
library AutoFly requires UnitDex
private function Actions takes nothing returns nothing
local unit u = GetIndexedUnit()
// By Magtheridon96
// This trigger will run whenever a unit is indexed.
// This means that it runs whenever a new unit enters the map.
// First, I'm adding the crow form ability to the indexed unit.
// The reason we're using it inside an if block is because the "UnitAddAbility" function returns a boolean.
// This boolean represents the successfulness of the ability adding. We need this here, because if a unit
// already had a crow form ability, it couldn't be added. The reason we're also using UnitRemoveAbility
// in the same line is because JASS has a short-circuiting feature. If one of the expressions in an 'and'
// expression returns false, then it stops.
// We do not want to call UnitRemoveAbility for a unit that failed to have the ability added.
// Without this form of protection, units that already had the crow form ability would have it
// removed completely.
if UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf') then
endif
set u = null
endfunction
private module Init
private static method onInit takes nothing returns nothing
call RegisterUnitIndexEvent(Filter(function Actions), EVENT_UNIT_INDEX)
endmethod
endmodule
private struct init
implement Init
endstruct
endlibrary
/*
The following is an example of how to filter when using GetRandomGuest
*/
function FilterSorceresses takes nothing returns boolean
return GetUnitTypeId(FilterGuest) == 'hsor'
endfunction
function Trig_Filter_Test_Actions takes nothing returns nothing
local unit u = GetRandomGuest(TestUnit, Filter(function FilterSorceresses))
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\OrbOfDeath\\OrbOfDeathMissile.mdl", u, "chest"))
set u = null
endfunction
//===========================================================================
function InitTrig_Filter_Test takes nothing returns nothing
set gg_trg_Filter_Test = CreateTrigger( )
call TriggerRegisterTimerEvent( gg_trg_Filter_Test, 1.5, true )
call TriggerAddAction( gg_trg_Filter_Test, function Trig_Filter_Test_Actions )
endfunction
library AttachUnit
/*
AttachUnit v1.01
by
Spellbound
________DESCRIPTION______________________________________________________________________________________________________________________________
AttachUnit simply binds a unit to another, with respect to angle, distance and, optionally,
facing angle of the host. Carrier units are called Hosts and attached units are called Guests.
AttachUnit comes with a turret functionality that allows Guests to rotates and attack normally,
after which they will return to a specific facing position. They can also be made to slowly
rotate over time, which maybe be useful for sci-fi type attachements.
________REQUIREMENTS_____________________________________________________________________________________________________________________________
Unit Event by Bribe - https://www.hiveworkshop.com/threads/gui-unit-event-v2-4-0-0.201641/#resource-45995
________INSTALLATION_____________________________________________________________________________________________________________________________
Simply copy-paste this trigger into your map and install a unit indexer that uses custom values.
________API______________________________________________________________________________________________________________________________________
/* IMPORTANT */ For units to function as Guests, they must have a positive, non-zero movement speed. Otherwise they will not move along with
their Host.
call AttachUnitToHost takes unit guest, unit host, real angle, boolean staticAngle, real angleFacing, real distance, real zOffset, real offsetFix
^ This function will attach a unit to a host.
[_ARGUMENTS_]
unit guest - the unit to attach (aka the Guest)
unit host - the Host
real angle - the angle relative to the Host's facing angle at which the Guest is offset to.
boolean staticAngle - if true, the Guest's angle offset will ignore the Host's facing direction
real angleFacing - the starting facing angle of the Guest
real distance - the distance between the Host and the Guest
real zOffset - the height difference from the Host
real offsetFix - if your Guest is off-center, try setting this to -16 or 16.
call SetGuestFacingProperties takes unit guest, real startAngle, real rate, real cooldown, boolean dynamicFacing, boolean turretMode
^ This sets an attached unit's facing properties. If this is not called, the unit will always face the same direction.
[_ARGUMENTS_]
unit guest - the attached unit (aka Guest)
real startAngle - the angle at which the Guest begins at. If dynamicFacing (see below) is false, the guest will always face this specific angle.
real rate - the speed at which the Guest rotates over time. Set to zero to ignore.
real cooldown - this is neccessary if the Guest can attack. It will resume it facing parameters after that cooldown has expired.
boolean dynamicFacing - if this is true, the facing angle of the Guest will depend on the facing of the Host.
boolean turretMode - Set this to true if you want your Guest to be able to attack. Otherwise, the facing parameters will keep interferring.
call DettachGuest takes unit guest, real time
^ Call this function when you want to Dettach a Guest from a Host. real time is for how long after Dettachement do you want the Guest to die.
Set real time to zero if you don't want the Guest to die. This function is called automatically when a Guest dies.
call DettachAllGuests takes unit host, real time
^ Call this function on a Host to Dettach all its Guests. This function will not get called automatically on Host death and must be done manually.
Similarly to DettachUnit, real time will determine how long after Dettachment will the Guest die. Set to zero to not kill your Dettached Guests.
call GetRandomGuest takes unit host, boolepx guestFilter returns unit
^ This will return a random Guest attached to a Host. Set guestFilter to null if you do not wish to filter your guests.
/* IMPORTANT */ In your filter you MUST use the variable 'FilterGuest' and NOT GetFilterUnit().
call GetNumberOfGuests takes unit host returns integer
^ This will return the number of Guests currently connected to a Host.
call IsUnitHost takes unit host returns boolean
^ Returns true if a unit is a Host.
call IsUnitGuest takes unit guest returns boolean
^ Returns true if a unit is a Guest.
call ShowGuest takes unit guest returns nothing
^ Unhides a hidden Guest without breaking locust.
call ShowGuests takes unit host returns nothing
^ Cycles through all the Guests of a Host and calls ShowGuest() on them to unhide them. This function is called automatically when leaving a transport.
If you wish for Guests to remain hidden when unloading from transport, call HideGuests() on your Host after it unloads. This will not break locust.
call HideGuests takes unit host returns nothing
^ Cycles through all Guests a Host has and hides them. This function is called automatically when loading a transport, but not when hiding a unit by
triggers. Call this function manually then and it's corresponding ShowGuests() when unhiding.
*/
globals
private constant real TIMEOUT = .03125
private constant integer ORDER_ATTACK = 851983
unit FilterGuest = null
private trigger FilterTrig = CreateTrigger()
private trigger LoadUnloadTrig = CreateTrigger()
private hashtable GuestStorage = InitHashtable()
private hashtable TurretStorage = InitHashtable()
private timer Clock = CreateTimer()
private integer HostsCount = 0
private unit array Hosts
private integer array HostSlot
private unit array Host
private real array HostX
private real array HostY
private real array HostZ
private real array HostFacing
private integer array HostInvites //Keeps tab of the number of Guests a Host has.
private integer GuestsCount = 0
private unit array Guests
private integer array GuestSlot
private real array Cooldown
private real array CooldownReset
private trigger array TurretTrigger
private boolean array DynamicFacing
private boolean array UpdateFacing
private real array Distance
private real array Angle
private real array ZOffset
private real array OffsetFix
private real array GuestFacing
private real array AngleRate
private real array Facing
private boolean array StaticAngle
private integer array GuestSlotStorage
//private iList GlobalHostList
//private iList GlobalGuestList
endglobals
// LINKED LIST
/*private struct iNode
iNode next
iNode prev
integer data
method destroy takes nothing returns nothing
//set this.prev.next = this.next
//set this.next.prev = this.prev
set this.next = 0
set this.prev = 0
set this.data = 0
call this.deallocate()
endmethod
static method create takes integer i returns thistype
local thistype this = allocate()
set this.next = 0
set this.prev = 0
set this.data = i
return this
endmethod
endstruct
struct iList
readonly iNode first
readonly iNode last
readonly integer count
method destroy takes nothing returns nothing
local iNode node = .first
local iNode nodeNext
loop
exitwhen node == 0
set nodeNext = node.next
call node.destroy()
endloop
set .first = 0
set .last = 0
set .count = 0
call this.deallocate()
endmethod
// returns the size of a list
method size takes nothing returns integer
return this.count
endmethod
// inserts an element at the end of the list
method push takes integer i returns nothing
local iNode node = allocate()
if .count == 0 then
set .first = node
else
set .last.next = node
endif
set node.prev = .last // .last is already set to 0 a list creation
set node.next = 0 // otherwise the list will cycle forever
set .last = node
set node.data = i
set .count = .count - 1
endmethod
// removes a node from the list
method erase takes iNode node returns nothing
if node == .first then
set node.prev.next = 0
set node.next.prev = 0
set .first = node.next
elseif node == .last then
set node.prev.next = 0
set node.next.prev = 0
set .last = node.prev
else
set node.prev.next = node.next // set the next of the previous to...
set node.next.prev = node.prev // set the previous of the next to...
endif
call node.destroy()
set .count = .count + 1
endmethod
method getRandom takes nothing returns iNode
local integer i = GetRandomInt(1, .count)
local iNode node = 0
// thanks to Wareditor for that bit of code
if i > .count / 2 then
set i = .count - i
set node = .last
loop
exitwhen i == 0
set node = node.prev
set i = i - 1
endloop
else
set i = i - 1
set node = .first
loop
exitwhen i == 0
set node = node.next
set i = i - 1
endloop
endif
return node
endmethod
method find takes integer i returns iNode
local iNode node = .first
loop
exitwhen node == 0 or node.data == i
set node = node.next
endloop
return node
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set this.first = 0
set this.last = 0
set this.count = 0
return this
endmethod
endstruct*/
//REFERENCE AND OTHER USEFUL FUNCTIONS
function IsUnitHost takes unit host returns boolean
return HostInvites[GetUnitUserData(host)] > 0
endfunction
function IsUnitGuest takes unit guest returns boolean
return Host[GetUnitUserData(guest)] != null
endfunction
function GetNumberOfGuests takes unit host returns integer
return HostInvites[GetUnitUserData(host)]
endfunction
function GetRandomGuest takes unit host, boolexpr guestFilter returns unit
local integer idHost = GetUnitUserData(host)
local integer i
local boolean firstPass
local triggercondition tcnd
if guestFilter == null then
return LoadUnitHandle(GuestStorage, idHost, GetRandomInt(1, HostInvites[idHost]))
else
set i = GetRandomInt(1, HostInvites[idHost])
set firstPass = (i == 1)
loop
exitwhen i > HostInvites[idHost] and firstPass
set i = i + 1
if i > HostInvites[idHost] and not firstPass then
set i = 1
set firstPass = true
endif
set FilterGuest = LoadUnitHandle(GuestStorage, idHost, i)
set tcnd = TriggerAddCondition(FilterTrig, guestFilter)
if TriggerEvaluate(FilterTrig) then
return FilterGuest
endif
set FilterGuest = null
call TriggerRemoveCondition(FilterTrig, tcnd)
set tcnd = null
endloop
endif
return null
endfunction
function ShowGuest takes unit guest returns nothing
if IsUnitHidden(guest) then
call ShowUnit(guest, true)
if GetUnitAbilityLevel(guest, 'Aloc') > 0 then
call UnitRemoveAbility(guest, 'Aloc')
call UnitAddAbility(guest, 'Aloc')
endif
endif
endfunction
function ShowGuests takes unit host returns nothing
local integer idHost = GetUnitUserData(host)
local integer i = 0
local unit guest
loop
set i = i + 1
//exitwhen i > HostInvites[idHost]
set guest = LoadUnitHandle(GuestStorage, idHost, i)
exitwhen guest == null
call ShowGuest(guest)
endloop
endfunction
function HideGuests takes unit host returns nothing
local integer idHost = GetUnitUserData(host)
local integer i = 0
local unit guest
loop
set i = i + 1
//exitwhen i > HostInvites[idHost]
set guest = LoadUnitHandle(GuestStorage, idHost, i)
exitwhen guest == null
call ShowUnit(guest, false)
endloop
endfunction
//MAIN API FUNCITONS
//NB: Functions that start with 'private' cannot be called outside of this library
function DettachGuest takes unit guest, real time returns nothing
local integer idGuest = GetUnitUserData(guest)
local integer idHost
local integer i
local unit loopGuest
local integer loopId
set UpdateFacing[idGuest] = false
if TurretTrigger[idGuest] != null then
call FlushChildHashtable(TurretStorage, GetHandleId(TurretTrigger[idGuest]))
call DestroyTrigger(TurretTrigger[idGuest])
set TurretTrigger[idGuest] = null
endif
if Host[idGuest] != null then
set idHost = GetUnitUserData(Host[idGuest])
set i = GuestSlotStorage[idGuest]
set guest = LoadUnitHandle(GuestStorage, idHost, i)
if UnitAlive(guest) and time > 0. then
call UnitApplyTimedLife(guest , 'BTLF', time)
endif
call SetUnitPropWindow(guest, GetUnitDefaultPropWindow(guest) * bj_DEGTORAD)
call UnitRemoveAbility(guest, 'Aeth')
call SetUnitPathing(guest, true)
set guest = null
loop
call RemoveSavedHandle(GuestStorage, idHost, i)
exitwhen i == HostInvites[idHost]
set loopGuest = LoadUnitHandle(GuestStorage, idHost, i + 1)
set loopId = GetUnitUserData(loopGuest)
call SaveUnitHandle(GuestStorage, idHost, i, loopGuest)
set GuestSlotStorage[loopId] = GuestSlotStorage[loopId] - 1
set i = i + 1
endloop
set loopGuest = null
set HostInvites[idHost] = HostInvites[idHost] - 1
set Host[idGuest] = null
set GuestSlotStorage[idGuest] = 0
if HostInvites[idHost] == 0 then
call FlushChildHashtable(GuestStorage, idHost)
set i = HostSlot[idHost]
loop
exitwhen i > HostsCount
set Hosts[i] = Hosts[i + 1]
set loopId = GetUnitUserData(Hosts[i])
set HostSlot[loopId] = HostSlot[loopId] - 1
set i = i + 1
endloop
set HostsCount = HostsCount - 1
endif
if GuestSlot[idGuest] != 0 then
set i = GuestSlot[idGuest]
loop
exitwhen i > GuestsCount
set Guests[i] = Guests[i + 1]
set loopId = GetUnitUserData(Guests[i])
set GuestSlot[loopId] = GuestSlot[loopId] - 1
set i = i + 1
endloop
set GuestsCount = GuestsCount - 1
endif
endif
endfunction
function DettachAllGuests takes unit host, real time returns nothing
local integer idHost = GetUnitUserData(host)
loop
exitwhen HostInvites[idHost] == 0
call DettachGuest(LoadUnitHandle(GuestStorage, idHost, 1), time)
// DettachUnit is called on number 1 slot of the hashtable because DettachUnit
// automatically cascades all elements of the list down by 1. Therefore, when the
// oldest unit is Dettached, the second oldest becomes the oldest, and that one is
// then Dettached. This goes on until the list has been cleared, then the hashtable
// is flushed.
endloop
call FlushChildHashtable(GuestStorage, idHost)
endfunction
private function TurretActions takes nothing returns nothing
local unit guest = LoadUnitHandle(TurretStorage, GetHandleId(GetTriggeringTrigger()), 0)
local integer idGuest = GetUnitUserData(guest)
set Cooldown[idGuest] = CooldownReset[idGuest]
if GetUnitCurrentOrder(guest) != ORDER_ATTACK then
call IssuePointOrderById(guest, ORDER_ATTACK, GetUnitX(guest), GetUnitY(guest))
endif
set guest = null
endfunction
private function UpdateGuests takes nothing returns nothing
local integer i = 0
local integer idHost
local real x
local real y
local real z
local real a
local real facing
local unit guest
local integer idGuest
local integer iNested
//This loop cycles through all the Hosts and a secondary loop inside cycles through their
//attached Guests, updating their x, y and z coordiates.
loop
set i = i + 1
exitwhen i > HostsCount
set idHost = GetUnitUserData(Hosts[i])
set x = GetUnitX(Hosts[i])
set y = GetUnitY(Hosts[i])
set z = GetUnitFlyHeight(Hosts[i])
set facing = GetUnitFacing(Hosts[i])
if not IsUnitHidden(Hosts[i]) then
if not ( x == HostX[idHost] and y == HostY[idHost] and z == HostZ[idHost] and HostFacing[idHost] == facing ) then
set iNested = 0
loop
set iNested = iNested + 1
exitwhen iNested > HostInvites[idHost]
set guest = LoadUnitHandle(GuestStorage, idHost, iNested)
set idGuest = GetUnitUserData(guest)
if StaticAngle[idGuest] then
set a = 0.
else
set a = facing * bj_DEGTORAD
endif
call SetUnitX(guest, OffsetFix[idGuest] + x + Cos(Angle[idGuest] + a) * Distance[idGuest])
call SetUnitY(guest, OffsetFix[idGuest] + y + Sin(Angle[idGuest] + a) * Distance[idGuest])
call SetUnitFlyHeight(guest, z + ZOffset[idGuest], 0.)
endloop
set HostX[idHost] = x
set HostY[idHost] = y
set HostZ[idHost] = z
set HostFacing[idHost] = facing * bj_RADTODEG
endif
endif
endloop
//Updates the facing angle of Guests and when not to turn (eg when in combat). Guests that
//have no idle rotation will not get added to this list.
set i = 0
loop
set i = i + 1
exitwhen i > GuestsCount
set idGuest = GetUnitUserData(Guests[i])
if not UnitAlive(Guests[i]) then
call DettachGuest(Guests[i], 0.)
else
if UpdateFacing[idGuest] then
if DynamicFacing[idGuest] then
set facing = GetUnitFacing(Host[idGuest])
else
set facing = 0.
endif
if GetUnitCurrentOrder(Guests[i]) != 0 then
if Cooldown[idGuest] > 0. then
set Cooldown[idGuest] = Cooldown[idGuest] - TIMEOUT
else
set Cooldown[idGuest] = CooldownReset[idGuest]
call IssueImmediateOrderById(Guests[i], 851973) //order stunned
endif
if AngleRate[idGuest] != 0. then
//Store the facing of the unit if it needs to rotate later
set GuestFacing[idGuest] = GetUnitFacing(Guests[i])
endif
else
if AngleRate[idGuest] == 0. then
//If the unit doesn't rotate
call SetUnitFacing(Guests[i], GuestFacing[idGuest] + facing)
else
set GuestFacing[idGuest] = GuestFacing[idGuest] + AngleRate[idGuest]
call SetUnitFacing(Guests[i], GuestFacing[idGuest] + facing)
endif
endif
endif
endif
endloop
set guest = null
endfunction
function SetGuestFacingProperties takes unit guest, real startAngle, real rate, real cooldown, boolean dynamicFacing, boolean turretMode returns nothing
local integer idGuest = GetUnitUserData(guest)
set CooldownReset[idGuest] = cooldown
set Cooldown[idGuest] = cooldown
if rate == 0. then
set AngleRate[idGuest] = 0.
else
set AngleRate[idGuest] = rate * TIMEOUT
endif
set DynamicFacing[idGuest] = dynamicFacing
if dynamicFacing then
set GuestFacing[idGuest] = startAngle - GetUnitFacing(Host[idGuest])
else
set GuestFacing[idGuest] = startAngle
endif
call SetUnitFacing(guest, startAngle)
set UpdateFacing[idGuest] = true
if turretMode then
set TurretTrigger[idGuest] = CreateTrigger()
call TriggerRegisterUnitEvent(TurretTrigger[idGuest], guest, EVENT_UNIT_ACQUIRED_TARGET)
call TriggerRegisterUnitEvent(TurretTrigger[idGuest], guest, EVENT_UNIT_TARGET_IN_RANGE)
call SaveUnitHandle(TurretStorage, GetHandleId(TurretTrigger[idGuest]), 0, guest)
call TriggerAddCondition(TurretTrigger[idGuest], Condition(function TurretActions))
endif
endfunction
function AttachUnitToHost takes unit guest, unit host, real angle, boolean staticAngle, real angleFacing, real distance, real zOffset, real offsetFix returns nothing
local integer idHost = GetUnitUserData(host)
local integer idGuest = GetUnitUserData(guest)
local real x = GetUnitX(host)
local real y = GetUnitY(host)
local real z = GetUnitFlyHeight(host)
local real f = GetUnitFacing(host)
local real a
if Host[idGuest] != null then
if Host[idGuest] == host then
return
else
call DettachGuest(guest, 0.)
endif
endif
set HostInvites[idHost] = HostInvites[idHost] + 1
if HostInvites[idHost] == 1 then
set HostsCount = HostsCount + 1
set Hosts[HostsCount] = host
set HostX[idHost] = x
set HostY[idHost] = y
set HostZ[idHost] = z
set HostFacing[idHost] = f
set HostSlot[idHost] = HostsCount
endif
call SaveUnitHandle(GuestStorage, idHost, HostInvites[idHost], guest)
set Angle[idGuest] = angle * bj_DEGTORAD
set StaticAngle[idGuest] = staticAngle
set Facing[idGuest] = angleFacing
set Distance[idGuest] = distance
set ZOffset[idGuest] = zOffset
set OffsetFix[idGuest] = offsetFix
set GuestSlotStorage[idGuest] = HostInvites[idHost]
set Host[idGuest] = host
if staticAngle then
set a = Angle[idGuest]
else
set a = (angle + f) * bj_DEGTORAD
endif
call SetUnitX(guest, offsetFix + x + Cos(a) * distance)
call SetUnitY(guest, offsetFix + y + Sin(a) * distance)
call SetUnitFlyHeight(guest, z + zOffset, 0.)
call SetUnitFacing(guest, angleFacing + f)
call SetUnitPropWindow(guest, 0.)
call UnitAddAbility(guest, 'Aeth')
call SetUnitPathing(guest, false)
set GuestsCount = GuestsCount + 1
set Guests[GuestsCount] = guest
set GuestSlot[idGuest] = GuestsCount
if HostsCount == 1 then
call TimerStart(Clock, TIMEOUT, true, function UpdateGuests)
endif
endfunction
private function onLoad takes nothing returns nothing
local unit u = null//GetEventUnit()
if IsUnitHost(u) then
call HideGuests(u)
endif
set u = null
endfunction
private function onUnload takes nothing returns nothing
local unit u = null//GetEventUnit()
if IsUnitHost(u) then
call ShowGuests(u)
endif
set u = null
endfunction
// Initialisation
private module init
private static method onInit takes nothing returns nothing
call RegisterNativeEvent(EVENT_ON_CARGO_LOAD, function onLoad)
call RegisterNativeEvent(EVENT_ON_CARGO_UNLOAD, function onUnload)
//set GlobalGuestList = iList.create()
//set GlobalHostList = iList.create()
/*call TriggerRegisterVariableEvent(LoadUnloadTrig, "udg_CargoEvent", EQUAL, 1.00)
call TriggerRegisterVariableEvent(LoadUnloadTrig, "udg_CargoEvent", EQUAL, 2.00)
call TriggerAddCondition(LoadUnloadTrig, Condition(function LoadUnload))*/
endmethod
endmodule
private struct Init
implement init
endstruct
endlibrary
/*
This is mostly a code repository where I keep stuff I don't want to delete for one reason
or another.
*/
private struct iNode
iNode next
iNode prev
integer data
method destroy takes nothing returns nothing
//set this.prev.next = this.next
//set this.next.prev = this.prev
set this.next = 0
set this.prev = 0
set this.data = 0
call this.deallocate()
endmethod
// create method and stuff here
endstruct
method erase takes iNode node returns nothing
// the following was commented out because it doesn't appear to work but I'm not sure why
// Instead I used MyPad's suggestion to reconnect the link inside the destroy method above
// (as seen in the two lines commented out above) and that worked flawlessly.
if node == .first then
set node.next.prev = 0
set .first = node.next
elseif node == .last then
set node.prev.next = 0
set .last = node.prev
else
set node.prev.next = node.next // set the next of the previous to...
set node.next.prev = node.prev // set the previous of the next to...
endif
call node.destroy()
set .count = .count - 1
endmethod
//This function will hide/unhide guests if the host is loaded into a transport. Uses custom
//load/unload events from Bribe's Unit Event system. Links in the Requirement section above.
/*private function LoadUnload takes nothing returns boolean
if udg_CargoEvent == 1. then
if IsUnitHost(udg_UDexUnits[udg_UDex]) then
call HideGuests(udg_UDexUnits[udg_UDex])
endif
else
if IsUnitHost(udg_UDexUnits[udg_UDex]) then
call ShowGuests(udg_UDexUnits[udg_UDex])
endif
endif
return false
endfunction*/
/*call TriggerRegisterVariableEvent(LoadUnloadTrig, "udg_CargoEvent", EQUAL, 1.00)
call TriggerRegisterVariableEvent(LoadUnloadTrig, "udg_CargoEvent", EQUAL, 2.00)
call TriggerAddCondition(LoadUnloadTrig, Condition(function LoadUnload))*/
scope Test initializer init
native BlzGetUnitMovementType takes unit whichUnit returns integer
native BlzSetUnitMovementType takes unit whichUnit, integer movementType returns nothing
native BlzGetUnitArmorType takes unit whichUnit returns integer
native BlzSetHeroStatEx takes unit whichHero, integer whichStat, integer statValue, boolean permanent returns nothing
native BlzGetHeroPrimaryStat takes unit whichHero returns integer
native BlzGetHeroPrimaryStatById takes unit whichHero returns integer
native BlzGetHeroStat takes unit whichHero, integer whichStat, boolean includeBonuses returns integer
native BlzSetHeroPrimaryStat takes unit whichHero, integer whichStat returns nothing
private function Actions takes nothing returns nothing
local unit u
local group g = CreateGroup()
local string array stats
local integer targetMovetype = 2
local integer targetPrimaryStat = 1
set stats[1] = "Strength"
set stats[2] = "Intelligence"
set stats[3] = "Agility"
call GroupEnumUnitsSelected(g, GetTriggerPlayer(), null)
set u = FirstOfGroup(g)
call DestroyGroup(g)
set g = null
call ClearTextMessages()
call BJDebugMsg("[Hidden Natives]\n")
call BJDebugMsg("Test Unit: " + GetUnitName(u) + "\n\n")
call BJDebugMsg("STR = " + I2S(BlzGetHeroStat(u, 1, false)))
call BJDebugMsg("AGI = " + I2S(BlzGetHeroStat(u, 3, false)))
call BJDebugMsg("INT = " + I2S(BlzGetHeroStat(u, bj_HEROSTAT_INT, false)))
call BJDebugMsg("PRIMARY = " + stats[BlzGetHeroPrimaryStat(u)])
call BJDebugMsg("PRIMARY ID = " + I2S(BlzGetHeroPrimaryStatById(u)))
call BJDebugMsg("ARMOR TYPE = " + I2S(BlzGetUnitArmorType(u)))
call BJDebugMsg("MOVEMENT TYPE = " + I2S(BlzGetUnitMovementType(u)))
call BJDebugMsg("\nAttempting to increase primary stat...")
call TriggerSleepAction(1)
call BlzSetHeroStatEx(u, BlzGetHeroPrimaryStat(u), 99, true)
if (BlzGetHeroStat(u, BlzGetHeroPrimaryStat(u), false) == 99) then
call BJDebugMsg("Success.")
else
call BJDebugMsg("Failed.")
endif
call BJDebugMsg("\nAttempting change primary stat...")
call TriggerSleepAction(1)
if (BlzGetHeroPrimaryStat(u) == 1) then
set targetPrimaryStat = 2
else
set targetPrimaryStat = 1
endif
call BlzSetHeroPrimaryStat(u, targetPrimaryStat)
if (BlzGetHeroPrimaryStat(u) == targetPrimaryStat) then
call BJDebugMsg("Success.")
else
call BJDebugMsg("Failed.")
endif
call BJDebugMsg("\nAttempting to movement type...")
call TriggerSleepAction(1)
if (BlzGetUnitMovementType(u) == targetMovetype) then
if (targetMovetype != 1) then
set targetMovetype = 1
else
set targetMovetype = 8
endif
endif
call BlzSetUnitMovementType(u, targetMovetype)
if (BlzGetUnitMovementType(u) == targetMovetype) then
call BJDebugMsg("Success.")
else
call BJDebugMsg("Failed.")
endif
call SetUnitFlyHeight(u, 300, 0)
set u = null
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerEventEndCinematic( t, Player(0) )
call TriggerAddAction( t, function Actions )
set t = null
endfunction
endscope