Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Initialization_Actions takes nothing returns nothing
//Initializing Detachments
call InitDetachment('h001', 'h000', 'A002', 'A000', 100, 0, 2, 3)
call InitDetachment('e001', 'e000', 'A003', 'A001', 90, 10, 2, 3)
call SetPlayerStateBJ(Player(0), PLAYER_STATE_RESOURCE_GOLD, 3000)
call SetPlayerStateBJ(Player(0), PLAYER_STATE_RESOURCE_LUMBER, 2000)
call CreateFogModifierRectBJ(true, Player(0), FOG_OF_WAR_VISIBLE, GetPlayableMapRect())
call BJDebugMsg("|c001B8914De|c001B8A14tach|c001B8B14ment|c001B8C14 S|c001C8C15y|c001C8D15stem"+/*
*/"|c001C8E15 by |c001C8F15Gar|c001C9015fiel|c001C9115d133|c001C92157.|r\n\n")
call BJDebugMsg("|c001C9215P|c001C9315r|c001D9316ess|c001D9416 Esc|c001D9516 to|c001D9616 spa|c001D9716wn a|c001D9816 wa"+/*
*/"|c001D9916ve |c001E9917o|c001E9A17f en|c001E9B17emi|c001E9C17es |c001E9D17comi|c001E9E17ng |c001E9F17from"+/*
*/"|c001EA017 |c001FA018sou|c001FA118th |c001FA218west|c001FA318.|r\n")
call BJDebugMsg("|c001FA318You|c001FA418 ma|c001FA518y al|c001FA618so |c0020A619a|c0020A719tta|c0020A819ck c"+/*
*/"|c0020A919amps|c0020AA19 of|c0020AB19 the|c0020AC19 for|c0020AD19e|c0021AD1Ast|c0021AE1A dwe|c0021AF1Aller|c0021B01As.|r")
endfunction
//===========================================================================
function InitTrig_Initialization takes nothing returns nothing
set gg_trg_Initialization = CreateTrigger( )
call TriggerAddAction( gg_trg_Initialization, function Trig_Initialization_Actions )
endfunction
//TESH.scrollpos=70
//TESH.alwaysfold=0
library II //Iterates struct instances v2.0.0.0
//! textmacro II takes FREQUENCY, PERIOD
globals
private trigger trigger$FREQUENCY$ = CreateTrigger()
private integer globalCount$FREQUENCY$ = 0
private timer timer$FREQUENCY$ = CreateTimer()
constant real II$FREQUENCY$period = $PERIOD$
endglobals
private function eval$FREQUENCY$ takes nothing returns nothing
call TriggerEvaluate(trigger$FREQUENCY$)
endfunction
module II$FREQUENCY$
private static integer localCount = 0
private static conditionfunc iterationFunction
private static triggercondition iterationCondition
boolean enabled$FREQUENCY$ = false
private thistype next
private thistype prev
private static method iteration takes nothing returns boolean
local thistype this = thistype(0).next
loop
exitwhen this == 0
call .iterate$FREQUENCY$()
set this = this.next
endloop
return false
endmethod
method start$FREQUENCY$ takes nothing returns boolean
if not .enabled$FREQUENCY$ then
set thistype(0).next.prev = this
set .next = thistype(0).next
set thistype(0).next = this
set .prev = 0
if .localCount == 0 then
set .iterationCondition = TriggerAddCondition(trigger$FREQUENCY$, .iterationFunction)
endif
set .localCount = .localCount + 1
if globalCount$FREQUENCY$ == 0 then
call TimerStart(timer$FREQUENCY$, $PERIOD$, true, function eval$FREQUENCY$)
endif
set globalCount$FREQUENCY$ = globalCount$FREQUENCY$ + 1
set .enabled$FREQUENCY$ = true
return true
endif
return false
endmethod
method stop$FREQUENCY$ takes nothing returns boolean
if .enabled$FREQUENCY$ then
set .prev.next = .next
set .next.prev = .prev
set .localCount = .localCount - 1
if .localCount == 0 then
call TriggerRemoveCondition(trigger$FREQUENCY$, .iterationCondition)
set .iterationCondition = null
endif
set globalCount$FREQUENCY$ = globalCount$FREQUENCY$ - 1
if globalCount$FREQUENCY$ == 0 then
call PauseTimer(timer$FREQUENCY$)
endif
set .enabled$FREQUENCY$ = false
return true
endif
return false
endmethod
private static method onInit takes nothing returns nothing
set .iterationFunction = Condition(function thistype.iteration)
endmethod
endmodule
//! endtextmacro
//! runtextmacro II("32", "0.031250")
// runtextmacro II("16", "0.062500")
// runtextmacro II("10", "0.100000")
// runtextmacro II("8", "0.125000")
// runtextmacro II("5", "0.200000")
// runtextmacro II("2", "0.500000")
//! runtextmacro II("1", "1.000000")
endlibrary
//TESH.scrollpos=106
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
// to get get the unique value assigned to a particular unit. The GetUnitId
// function is extremely fast because it inlines directly to a GetUnitUserData
// call. AutoIndex automatically assigns an ID to each unit as it enters the
// map, and instantly frees that ID as the unit leaves the map. Detection of
// leaving units is accomplished in constant time without a periodic scan.
//
// AutoIndex uses UnitUserData by default. If something else in your map
// would conflict with that, you can set the UseUnitUserData configuration
// constant to false, and a hashtable will be used instead. Note that hash-
// tables are about 60% slower.
//
// If you turn on debug mode, AutoIndex will be able to display several
// helpful error messages. The following issues will be detected:
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game. Also
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
// you to fully utilize AutoIndex's enter/leave detection capabilities in
// conjunction with your structs.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the delay while saving.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
/*
globals
integer array IntegerData
real array RealData
SomeStruct array SomeStructData
englobals
function Example takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local integer id = GetUnitId(u)
//You now have a unique index for the unit, so you can
//attach or retrieve data about the unit using arrays.
set IntegerData[id] = 5
set RealData[id] = 25.0
set SomeStructData[id] = SomeStruct.create()
//If you have access to the same unit in another function, you can
//retrieve the data by using GetUnitId() and reading the arrays.
endfunction
*/
// The UnitFilter function in the configuration section is provided so that
// you can make AutoIndex completely ignore certain unit-types. Ignored units
// won't be indexed or fire indexed/deindexed events. You may want to filter out
// dummy casters or system-private units, especially ones that use UnitUserData
// internally. xe dummy units are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
/*
function UnitEntersMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
endfunction //Using GetUnitId() during Indexed events works fine...
function UnitLeavesMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
endfunction //So does using GetUnitId() during Deindexed events.
function Init takes nothing returns nothing
call OnUnitIndexed(UnitEntersMap)
call OnUnitDeindexed(UnitLeavesMap)
endfunction
*/
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from the need
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Returns 0 if a null unit was passed. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, this function will print
// an error message when passed a decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit has been indexed. The only time this will return false is
// for units you have filtered using the UnitFilter function, or
// for xe dummy units. You can use this function to easily detect
// dummy units and avoid performing certain actions on them.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// How to use AutoData:
//======================
//
// The AutoData module allows you to associate one or more instances
// of the implementing struct with units, as well as iterate through all
// of the instances associated with each unit.
//
// This association is accomplished through the "me" instance member,
// which the module will place in the implementing struct. Whichever unit
// you assign to "me" becomes the owner of that instance. You may change
// ownership by reassigning "me" to another unit at any time, or you may
// make the instance unowned by assigning "me" to null.
//
// AutoData implements the static method operator [] in your struct
// to allow you to access instances from their owning units. For example,
// you may type: local StructName s = StructName[u]. If u has been set
// to own an instance of StructName, s will be set to that instance.
//
// So, what happens if you assign the same owning unit to multiple
// instances? You may use 2D array syntax to access instances assigned to
// the same unit: local StructName s = StructName[u][n], where u is the
// owning unit, and n is the index beginning with 0 for each unit. You
// can access the size of a unit's instance list (i.e. the number of
// instances belonging to the unit) by using the .size instance member.
/*
struct Example
implement AutoData
static method create takes unit u returns Example
local Example this = allocate()
set me = u //Assigning the "me" member from AutoData.
return this
endmethod
endstruct
function Test takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local Example e1 = Example.create(u)
local Example e2 = Example.create(u)
local Example e3 = Example.create(u)
local Example e
call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
set e = Example[u] //This is the fastest way to iterate the instances belonging
loop //to a specific unit, starting with the first instance.
exitwhen e == 0 //e will be assigned to 0 when no instances remain.
call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
endloop //Thus, index + 1 is next, and index - 1 is previous.
endfunction //This trick allows you to avoid a local counter.
*/
// AutoData restrictions:
// -You may not implement AutoData in any struct which has already
// declared static or non-static method operator [].
// -AutoData will conflict with anything named "me", "size", or
// "index" in the implementing struct.
// -AutoData may not be implemented in structs that extend array.
// -You may not declare your own destroy method. (This restriction
// can be dropped as soon as JassHelper supports module onDestroy).
//
// AutoData information:
// -You do not need to null the "me" member when destroying an
// instance. That is done for you automatically during destroy().
// (But if you use deallocate(), you must null "me" manually.)
// -StructName[u] and StructName[u][0] refer to the same instance,
// which is the first instance that was associated with unit u.
// -StructName[u][StructName[u].size - 1] refers to the instance that
// was most recently associated with unit u.
// -Instances keep their relative order in the list when one is removed.
//
//===========================================================================
// How to use AutoCreate:
//=======================
//
// The AutoCreate module allows you to automatically create instances
// of the implementing struct for units as they enter the game. AutoCreate
// automatically implements AutoData into your struct. Any time an instance
// is automatically created for a unit, that instance's "me" member will be
// assigned to the entering unit.
//
// AutoCreate restrictions:
// -All of the same restrictions as AutoData.
// -If your struct's allocate() method takes parameters (i.e. the parent
// type's create method takes parameters), you must declare a create
// method and pass those extra parameters to allocate yourself.
//
// AutoCreate information:
// -You may optionally declare the createFilter method, which specifies
// which units should recieve an instance as they enter the game. If
// you do not declare it, all entering units will recieve an instance.
// -You may optionally declare the onCreate method, which will run when
// AutoCreate automatically creates an instance. (This is just a stand-
// in until JassHelper supports the onCreate method.)
// -You may declare your own create method, but it must take a single
// unit parameter (the entering unit) if you do so.
/*
struct Example
private static method createFilter takes unit u returns boolean
return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
endmethod
private method onCreate takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" entered the game!")
endmethod
implement AutoCreate
endstruct
*/
//===========================================================================
// How to use AutoDestroy:
//=========================
//
// The AutoDestroy module allows you to automatically destroy instances
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
// automatically implements AutoData into your struct. You must assign a unit
// to the "me" member of an instance for this module to have any effect.
//
// AutoDestroy restrictions:
// -All of the same restrictions as AutoData.
//
// AutoDestroy information:
// -If you also implement AutoCreate in the same struct, remember that it
// assigns the "me" unit automatically. That means you can have fully
// automatic creation and destruction.
/*
struct Example
static method create takes unit u returns Example
local Example this = allocate()
set me = u //You should assign a unit to "me",
return this //otherwise AutoDestroy does nothing.
endmethod //Not necessary if using AutoCreate.
private method onDestroy takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" left the game!")
endmethod
implement AutoDestroy
endstruct
*/
//===========================================================================
// Configuration:
//================
// external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMerger macro above. You may change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
private constant boolean SafeMode = true
//This is set to true by default so that GetUnitId() will ALWAYS work.
//If if this is set to false, GetUnitId() may fail to work in a very
//rare circumstance: creating a unit that has a default-on autocast
//ability, and using GetUnitId() on that unit as it enters the game,
//within a trigger that detects any order. Set this to false for a
//performance boost only if you think you can avoid this issue.
private constant boolean AutoDataFastMode = true
//If this is set to true, AutoData will utilize one hashtable per time
//it is implemented. If this is set to false, all AutoDatas will share
//a single hashtable, but iterating through the instances belonging to
//a unit will become about 12.5% slower. Your map will break if you
//use more than 255 hashtables simultaneously. Only set this to false
//if you suspect you will run out of hashtable instances.
endglobals
private function UnitFilter takes unit u returns boolean
return true
endfunction
//Make this function return false for any unit-types you want to ignore.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. The unit parameter "u" to refers to the unit being filtered.
//Do not filter out xe dummy units; they are automatically filtered.
//===========================================================================
// AutoData / AutoCreate / AutoDestroy modules:
//==============================================
function interface AutoCreator takes unit u returns nothing
function interface AutoDestroyer takes unit u returns nothing
globals
hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
endglobals //initialized and shared between all AutoData implementations.
module AutoData
private static hashtable ht
private static thistype array data
private static integer array listsize
private static key typeid //Good thing keys exist to identify each implementing struct.
private unit meunit
private integer id
readonly integer index //The user can avoid using a local counter because this is accessable.
static method operator [] takes unit u returns thistype
return data[GetUnitId(u)]
endmethod //This is as fast as retrieving an instance from a unit gets.
method operator [] takes integer index returns thistype
static if AutoDataFastMode then //If fast mode is enabled...
return LoadInteger(ht, id, index)
else //Each instance has its own hashtable to associate unit and index.
return LoadInteger(AutoData, id, index*8190+typeid)
endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
endmethod //Somehow, this version is 12.5% slower just because of the math.
private method setIndex takes integer index, thistype data returns nothing
static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
call SaveInteger(ht, id, index, data)
else
call SaveInteger(AutoData, id, index*8190+typeid, data)
endif
endmethod
private method remove takes nothing returns nothing
if meunit == null then //If the struct doesn't have an owner...
return //Nothing needs to be done.
endif
loop
exitwhen index == listsize[id] //The last value gets overwritten by 0.
call setIndex(index, this[index + 1]) //Shift each element down by one.
set this[index].index = index //Update the shifted instance's index.
set index = index + 1
endloop
set listsize[id] = listsize[id] - 1
set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
set meunit = null
endmethod
private method add takes unit u returns nothing
if meunit != null then //If the struct has an owner...
call remove() //remove it first.
endif
set meunit = u
set id = GetUnitId(u) //Cache GetUnitId for slight performance boost.
if data[id] == 0 then //If this is the first instance for this unit...
set data[id] = this //Update the value that thistype[u] returns.
endif
set index = listsize[id] //Remember the index for removal.
call setIndex(index, this) //Add to the array.
set listsize[id] = index + 1
endmethod
method operator me takes nothing returns unit
return meunit
endmethod
method operator me= takes unit u returns nothing
if u != null then //If assigning "me" a non-null value...
call add(u) //Add this instance to that unit's array.
else //If assigning "me" a null value...
call remove() //Remove this instance from that unit's array.
endif
endmethod
method operator size takes nothing returns integer
return listsize[id]
endmethod
method destroy takes nothing returns nothing
call deallocate()
call remove() //This makes removal automatic when an instance is destroyed.
endmethod
private static method onInit takes nothing returns nothing
static if AutoDataFastMode then //If fast mode is enabled...
set ht = InitHashtable() //Initialize one hashtable per instance.
else //If fast mode is disabled...
if AutoData == null then //If the hashtable hasn't been initialized yet...
set AutoData = InitHashtable() //Initialize the shared hashtable.
endif
endif
endmethod
endmodule
module AutoCreate
implement AutoData //AutoData is necessary for AutoCreate.
private static method creator takes unit u returns nothing
local thistype this
local boolean b = true //Assume that the instance will be created.
static if thistype.createFilter.exists then //If createFilter exists...
set b = createFilter(u) //evaluate it and update b.
endif
if b then //If the instance should be created...
static if thistype.create.exists then //If the create method exists...
set this = create(u) //Create the instance, passing the entering unit.
else //If the create method doesn't exist...
set this = allocate() //Just allocate the instance.
endif
set me = u //Assign the instance's owner as the entering unit.
static if thistype.onCreate.exists then //If onCreate exists...
call onCreate() //Call it, because JassHelper should do this anyway.
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoCreate(thistype.creator)
endmethod //During module initialization, pass the creator function to AutoIndex.
endmodule
module AutoDestroy
implement AutoData //AutoData is necessary for AutoDestroy.
static method destroyer takes unit u returns nothing
loop
exitwhen thistype[u] == 0
call thistype[u].destroy()
endloop
endmethod //Destroy each instance owned by the unit until none are left.
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoDestroy(thistype.destroyer)
endmethod //During module initialization, pass the destroyer function to AutoIndex.
endmodule
//===========================================================================
// AutoIndex struct:
//===================
function interface IndexFunc takes unit u returns nothing
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
private keyword getIndex
private keyword getIndexDebug
private keyword isUnitIndexed
private keyword onUnitIndexed
private keyword onUnitDeindexed
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger order = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static AutoCreator array creators
private static integer creators_n = -1
private static AutoDestroyer array destroyers
private static integer destroyers_n = -1
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
if u == null then
return 0
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return getIndex(u)
endmethod //If debug mode is enabled, use the getIndex method that shows errors.
private static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
static method addAutoCreate takes AutoCreator func returns nothing
set creators_n = creators_n + 1
set creators[creators_n] = func
endmethod
static method addAutoDestroy takes AutoDestroyer func returns nothing
set destroyers_n = destroyers_n + 1
set destroyers[destroyers_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if idunit[getIndex(whichUnit)] == whichUnit then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
endmethod //Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod //Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunAutoEvent("Resurrect")
//If AutoEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunAutoEvent("AnimateDead")
//If AutoEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunAutoEvent("Death")
//If AutoEvents is in the map, run the Death events.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
set n = destroyers_n
loop //Destroy AutoDestroy structs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
endif
set u = null
return false
endmethod
//===========================================================================
private static method unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then
return //Don't index a unit that already has an ID.
endif
static if LIBRARY_xebasic then
if GetUnitTypeId(u) == XE_DUMMY_UNITID then
return //Don't index xe dummy units.
endif
endif
if not UnitFilter(u) then
return //Don't index units that fail the unit filter.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering unit.
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //Each of these flags are necessary to detect
set animated[index] = false //when a unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
if duringinit then //If a unit enters the map during initialization...
call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
endif //all units are noticed by OnUnitIndexed during initialization.
loop //Create AutoCreate structs for the entering unit.
exitwhen n > creators_n
call creators[n].evaluate(u)
set n = n + 1
endloop
set n = 0
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method onIssuedOrder takes nothing returns boolean
static if SafeMode then //If SafeMode is enabled, perform this extra check.
if getIndex(GetTriggerUnit()) == 0 then //If the unit doesn't already have
call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
endif
endif
return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
endmethod
private static method initEnteringUnit takes nothing returns boolean
call unitEntersMap(GetFilterUnit())
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
//The filter function of an EnterRegion trigger runs instantly when a unit is created.
call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
endstruct
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then //If debug mode is enabled...
return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
else //If debug mode is disabled...
return AutoIndex.getIndex(u) //call the normal, inlinable version.
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
endlibrary
//TESH.scrollpos=21
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 3.1.0.1
One map, one hashtable. Welcome to NewTable 3.1
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
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")
//! 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 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)
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb)
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, 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
endlibrary
//TESH.scrollpos=9
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library SimError initializer init
//**************************************************************************************************
//*
//* SimError
//*
//* Mimic an interface error message
//* call SimError(ForPlayer, msg)
//* ForPlayer : The player to show the error
//* msg : The error
//*
//* To implement this function, copy this trigger and paste it in your map.
//* Unless of course you are actually reading the library from wc3c's scripts section, then just
//* paste the contents into some custom text trigger in your map.
//*
//**************************************************************************************************
//==================================================================================================
globals
private sound error
endglobals
//====================================================================================================
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
//call StartSound( error ) //apparently the bug in which you play a sound for the first time
//and it doesn't work is not there anymore in patch 1.22
endfunction
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
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
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Detachments /* v1.2.0.0
****************************************************************************************
*
* Detachments by Garfield1337
*
* Allows grouping unit types into lined up detachments or squads.
*
****************************************************************************************
*
* */uses/*
* */ AutoIndex /* wc3c.net/showthread.php?t=105643
* */ Table /* hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
* */ SimError /* wc3c.net/showthread.php?t=101260
* */ TimerUtils /* wc3c.net/showthread.php?t=101322
* */ II /* hiveworkshop.com/forums/pastebin.php?id=x178a8
* */optional/*
* */ RegisterPlayerUnitEvent /* hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
****************************************************************************************
*
* How to use:
* ----------
*
* For each detachment type...
* - Create a leading and a member unit type (possibly based on same unit, but using different model.)
* - Give member units ward classification.
* - Create a spell for transforming leader into member using bear form, you can copy the spell from this map.
* In this spell, change the fields "Alternate Form Unit" and "Normal Form Unit" to member and leader unit type respectively.
* - Create a spell for reinforcing the detachment, you can copy the spell from this map.
* Other than aesthetics and text fields, nothing needs change here.
* - Create a spell for canceling training, you can copy the spell from this map.
* Same as for reinforcing, there are no important field changes here.
* - Initialize detachments using functions from the API.
*
* Tips:
* - When the leader dies, a member turns into a leader and takes over. For balance, leader and members should have
* same or similar stats.
* - Giving detachment units smaller collision than usual will help them move more freely and not bump one into another.
* - Members mimic the leader; if the leader casts a spell, they will too if they have it.
*
* API:
* ----
*
* To set up a detachment type, call the following function (Soldiers are detachment members.):
*
* function InitDetachment takes integer LeaderRawCode, integer SoldierRawCode, integer ReinforceRawcode,
* integer TransformRawcode, integer SoldierGoldCost, integer SoldierLumberCost, integer SoldierFoodCost, integer TrainTime
*
* You may optionally alter detachment size settings with the following function,
* otherwise the detachment will use default values:
*
* function SetDetachmentSize takes integer LeaderRawCode, integer MaximumMemberCount, integer SoldiersPerRow
* real VerticalSpacing*, real HorizontalSpacing*
*
* *Spacing determines distance between soldiers
*
****************************************************************************************
*
* Settings
*/
globals
private constant integer CANCEL = 'A004' //Rawcode of the cancel ability.
private constant string BAR = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
private constant string BAR_COLOR = "|c00006988" //Color code for the training bar.
private constant real TRAIN_RANGE = 600.0 //Range from the nearest friendly building at which a unit will finish training.
private constant real ARRIVAL_RANGE = 200.0 //How close will the newly trained unit to the leader.
private constant real WANDER_DISTANCE = 900.0 //How far may a unit go away from the leader before automatically going back.
private constant integer DEFAULT_SIZE = 12 //Default detachment size; can be changed per detachment on initialization.
private constant integer DEFAULT_SOLDIERS_PER_ROW = 4 //Member count in every row
private constant real DEFAULT_VERTICAL_SPACING = 150.0 //Distance between soldiers; Vertical is away from leader.
private constant real DEFAULT_HORIZONTAL_SPACING = 140.0 //Distance between soldiers; Horizontal is between soldiers in a row.
private constant string ERROR_DETACHMENT_FULL = "Detachment is full." //Error messages
private constant string ERROR_INSUFFICIENT_GOLD = "Not enough gold."
private constant string ERROR_INSUFFICIENT_LUMBER = "Not enough lumber."
private constant string ERROR_INSUFFICIENT_FOOD = "Not enough food."
private group enumGroup = CreateGroup() //Group used for dynamic enumeration
endglobals //If there is a global group with such role present in map, assign it here
/****************************************************************************************/
globals
private TableArray detachmentData
private constant real BAR_CONSTANT = StringLength(BAR)/32.00
endglobals
function InitDetachment takes integer leader, integer soldier, integer reinforce, integer transform, integer gold, integer lumber, integer food, integer time returns nothing
//Saving detachment type data into table
set detachmentData[0].boolean[leader] = true
set detachmentData[1].boolean[soldier] = true
set detachmentData[2].boolean[reinforce] = true
set detachmentData[0][leader] = soldier
set detachmentData[1][leader] = transform
set detachmentData[2][leader] = gold
set detachmentData[3][leader] = lumber
set detachmentData[4][leader] = food
set detachmentData[5][leader] = time
set detachmentData[6][leader] = DEFAULT_SIZE
set detachmentData[7][leader] = DEFAULT_SOLDIERS_PER_ROW
set detachmentData[0].real[leader] = DEFAULT_VERTICAL_SPACING
set detachmentData[1].real[leader] = DEFAULT_HORIZONTAL_SPACING
endfunction
function SetDetachmentSize takes integer leader, integer size, integer row, real vS, real hS returns nothing
//Additional detachment settings
set detachmentData[6][leader] = size
set detachmentData[7][leader] = row
set detachmentData[0].real[leader] = vS
set detachmentData[1].real[leader] = hS
endfunction
//Functions for retrieving detachment data
private function isLeader takes unit u returns boolean
return detachmentData[0].boolean[GetUnitTypeId(u)]
endfunction
private function isSoldier takes unit u returns boolean
return detachmentData[1].boolean[GetUnitTypeId(u)]
endfunction
private function isReinforce takes integer a returns boolean
return detachmentData[2].boolean[a]
endfunction
private function soldierCode takes integer l returns integer
return detachmentData[0][l]
endfunction
private function transformSoldier takes unit s, integer l returns nothing
call UnitAddAbility(s, detachmentData[1][l])
call UnitRemoveAbility(s, detachmentData[1][l])
endfunction
private function soldierGoldCost takes integer l returns integer
return detachmentData[2][l]
endfunction
private function soldierLumberCost takes integer l returns integer
return detachmentData[3][l]
endfunction
private function soldierFoodCost takes integer l returns integer
return detachmentData[4][l]
endfunction
private function soldierTrainTime takes integer l returns integer
return detachmentData[5][l]
endfunction
private function detachmentSize takes integer l returns integer
return detachmentData[6][l]
endfunction
private function detachmentSPR takes integer l returns integer
return detachmentData[7][l]
endfunction
private function vSpacing takes integer l returns real
return detachmentData[0].real[l]
endfunction
private function hSpacing takes integer l returns real
return detachmentData[1].real[l]
endfunction
private keyword Leader
private struct Soldier
//Soldier struct instances are attached to members of each detachment
//It holds pointers to next and previous soldiers in the linked list and its parent Leader struct
thistype next = 0
thistype prev = 0
Leader leader
//These fields are used to prevent orders issued by player and resume previous one
integer orderId = OrderId("stop")
boolean orderPoint = false
real orderX = 0.00
real orderY = 0.00
widget orderTarget = null
static boolean checkOrder = true //If true, the orders will be resumed (prevents infinite loops)
static method order takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
set .checkOrder = false
call IssueImmediateOrderById(.me, .orderId)
set .checkOrder = true
call ReleaseTimer(GetExpiredTimer())
endmethod
method resumeOrder takes nothing returns nothing
set .checkOrder = false
if .orderPoint then
call IssuePointOrderById(.me, .orderId, .orderX, .orderY)
elseif .orderTarget != null then
call IssueTargetOrderById(.me, .orderId, .orderTarget)
else
call TimerStart(NewTimerEx(this), 0.00, false, function thistype.order)
endif
set .checkOrder = true
endmethod
static method create takes unit s, Leader l returns thistype
local thistype this = thistype.allocate()
set .me = s
set .leader = l
return this
endmethod
//AutoData is neeeded to attach structs to units
implement AutoData
method remove takes nothing returns nothing
//On removal, the two adjacent nodes in the list are joined together
set .next.prev = .prev
set .prev.next = .next
set .leader.memberCount = .leader.memberCount - 1
if this == .leader.first then
//If the dying member is first in the list, the next member becomes first
set .leader.first = .next
endif
call .destroy()
endmethod
endstruct
private struct Leader
//Leader struct instances are attached to leading units
integer memberCount = 0
Soldier first = 0 //First member in the list from which the iteration begins
//Fields used during training
boolean training = false
texttag tag
integer trainCount = 0
string bar
string filled
real trainSpeed
real trainProgress = 0.00
//Although these can be retrieved anytime, since they're used in calculations, for optimization, they're saved here
integer spr
real vDistance
real hDistance
static method createFilter takes unit u returns boolean
return isLeader(u) //Unit of leading type that enter map are automatically assigned a Leader struct instance
endmethod
method iterate1 takes nothing returns nothing
//This method is executed every second and is used to check if a member wanders far off
local Soldier soldier = .first //Starting for first soldier in the list
local real lx = GetUnitX(.me)
local real ly = GetUnitY(.me)
local real a
loop
exitwhen soldier == 0 //When hitting the end of the list, the loop finishes
if not IsUnitInRange(soldier.me, .me, WANDER_DISTANCE) then
//If the member is too far, he/she is issued an order to go back
set a = Atan2(GetUnitY(soldier.me) - ly, GetUnitX(soldier.me) - lx)
set Soldier.checkOrder = false //To prevent infinite loop
set soldier.orderId = OrderId("move")
set soldier.orderPoint = true
set soldier.orderX = lx + ARRIVAL_RANGE*Cos(a)
set soldier.orderY = ly + ARRIVAL_RANGE*Sin(a)
set soldier.orderTarget = null
call IssuePointOrderById(soldier.me, soldier.orderId, soldier.orderX, soldier.orderY)
set Soldier.checkOrder = true
endif
set soldier = soldier.next
endloop
endmethod
static method create takes unit u returns thistype
local thistype this = thistype.allocate()
local integer id = GetUnitTypeId(u)
set .spr = detachmentSPR(id)
set .vDistance = vSpacing(id)
set .hDistance = hSpacing(id)
set .trainSpeed = BAR_CONSTANT/soldierTrainTime(id)
call .start1() //Starts the one second interval iteration to prevent wandering
return this
endmethod
static player owner
static unit building
static real distance
static thistype dis
static method filter takes nothing returns boolean
//This method is used in a unit enumeration to find the nearest friendly building
local unit f = GetFilterUnit()
local real x
local real y
local real d
local thistype this = .dis
if IsUnitType(f, UNIT_TYPE_STRUCTURE) and IsUnitAlly(f, .owner) then
set x = GetUnitX(.me) - GetUnitX(f)
set y = GetUnitY(.me) - GetUnitY(f)
set d = SquareRoot(x*x + y*y)
if d < .distance then
set .building = f
set .distance = d
endif
endif
set f = null
return false
endmethod
method iterate32 takes nothing returns nothing
//This method is executed 32 times a second and handles training bars
local Soldier new
local real a
call SetTextTagPos(.tag, GetUnitX(.me) - 100.0, GetUnitY(.me), GetUnitFlyHeight(.me) + 160.0)
if .bar == "" then //When the bar finishes, the training is done
set .owner = GetOwningPlayer(.me)
set .building = null
set .distance = TRAIN_RANGE + 100.0
set .dis = this
//Finding nearest friendly building
call GroupEnumUnitsInRange(enumGroup, GetUnitX(.me), GetUnitY(.me), TRAIN_RANGE, Filter(function thistype.filter))
if .building != null then //If there is one
//A unit is trained
set .trainCount = .trainCount - 1
call SetPlayerState(.owner, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(.owner, PLAYER_STATE_RESOURCE_FOOD_USED) - soldierFoodCost(GetUnitTypeId(.me)))
set new = Soldier.create(CreateUnit(.owner, soldierCode(GetUnitTypeId(.me)), GetUnitX(.building), GetUnitY(.building), 270.0), this)
//This unit becomes the first in the list
set new.next = .first
set new.prev = 0
set .first.prev = new
set .first = new
set .memberCount = .memberCount + 1
set a = Atan2(GetUnitY(new.me) - GetUnitY(.me), GetUnitX(new.me) - GetUnitX(.me))
set Soldier.checkOrder = false
//Unit is issued to move close to the leader
set new.orderId = OrderId("move")
set new.orderPoint = true
set new.orderX = GetUnitX(.me) + ARRIVAL_RANGE*Cos(a)
set new.orderY = GetUnitY(.me) + ARRIVAL_RANGE*Sin(a)
call IssuePointOrderById(new.me, new.orderId, new.orderX, new.orderY)
set Soldier.checkOrder = true
if .trainCount == 0 then
//If all the training is done, the periodic execution is stopped and bar removed
call .stop32()
call DestroyTextTag(.tag)
set .training = false
else
//Otherwise it starts a new bar
set .bar = BAR
set .filled = "|r"
set .trainProgress = 0.00
call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
endif
endif
else
//The training bar consists of two strings, a filled bar (.filled) and empty, original bar (.bar)
//Progress consists of extending the filled bar and shortening the original one
set .trainProgress = .trainProgress + .trainSpeed
if .trainProgress >= 1.00 then
set .trainProgress = 0.00
set .filled = SubString(.filled, 0, StringLength(.filled) - 2) + "||" + "|r"
set .bar = SubString(.bar, 0, StringLength(.bar) - 2)
call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
endif
endif
endmethod
//Modules for attaching instances and periodic iterations
implement AutoCreate
implement II32
implement II1
method queue takes nothing returns nothing
//Queueing a unit for training
set .trainCount = .trainCount + 1
if not .training then
//If there are no other units being trained, create the bar for it
set .training = true
set .tag = CreateTextTag()
set .bar = BAR
set .filled = "|r"
set .trainProgress = 0.00
call SetTextTagPos(.tag, GetUnitX(.me) - 100.0, GetUnitY(.me), GetUnitFlyHeight(.me) + 160.0)
call SetTextTagVisibility(.tag, GetLocalPlayer() == GetOwningPlayer(.me))
call .start32()
endif
call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
endmethod
method cancel takes nothing returns nothing
//Canceling a training unit
local player p
local integer id
if .training then
set p = GetOwningPlayer(.me)
set id = GetUnitTypeId(.me)
//Returning the resources back to player
call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) + soldierGoldCost(id))
call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) + soldierLumberCost(id))
call SetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED) - soldierFoodCost(id))
set p = null
set .trainCount = .trainCount - 1
if .trainCount == 0 then
//If that was the last unit, stop the periodic execution and remove the bar
call .stop32()
call DestroyTextTag(.tag)
set .training = false
else
//Otherwise start with next unit
set .bar = BAR
set .filled = "|r"
set .trainProgress = 0.00
call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
endif
endif
endmethod
method replace takes nothing returns nothing
//When the leader dies, a replacement has to be found
local Soldier newLeader = .first //The first available member is picked
if newLeader == 0 then //If the list is empty
//Stop any training and running methods and destroy the instance
if .training then
call .stop32()
call DestroyTextTag(.tag)
endif
call .stop1()
call .destroy()
else
//If there is an available member, then transform it into leading unit type
call transformSoldier(newLeader.me, GetUnitTypeId(.me))
//Assign the Leader instance to the new leader
set .me = newLeader.me
set .first = newLeader.next //The next member becomes the new first
call newLeader.remove() //Removing Soldier instance attached to the new leader
endif
endmethod
static method train takes nothing returns boolean
//Issuing training for a detachment
local thistype this
local integer id
local player p
//Checking if the casted ability is one of the reinforcing abilities
if isReinforce(GetSpellAbilityId()) then
set this = thistype[GetTriggerUnit()]
set id = GetUnitTypeId(.me)
set p = GetTriggerPlayer()
//Inspecting the requirements and whether they are fulfilled
if .memberCount + .trainCount < detachmentSize(id) then
if GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) >= soldierGoldCost(id) then
if GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) >= soldierLumberCost(id) then
if GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_CAP) - GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED) >= soldierFoodCost(id) then
//After all the checks, the resources are being deducted
call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) - soldierGoldCost(id))
call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) - soldierLumberCost(id))
call SetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED) + soldierFoodCost(id))
//And a unit is queued for training
call .queue()
else
call SimError(p, ERROR_INSUFFICIENT_FOOD)
endif
else
call SimError(p, ERROR_INSUFFICIENT_LUMBER)
endif
else
call SimError(p, ERROR_INSUFFICIENT_GOLD)
endif
else
call SimError(p, ERROR_DETACHMENT_FULL)
endif
set p = null
elseif GetSpellAbilityId() == CANCEL then
//If the casted ability was cancel, call the cancel method
call thistype[GetTriggerUnit()].cancel()
endif
return false
endmethod
static method death takes nothing returns boolean
//If a dying unit is a leader or a soldier, corresponding procedures are performed
if isSoldier(GetTriggerUnit()) then
call Soldier[GetTriggerUnit()].remove()
elseif isLeader(GetTriggerUnit()) then
call thistype[GetTriggerUnit()].replace()
endif
return false
endmethod
static method order takes nothing returns boolean
//Mimicking leader's immediate orders
local thistype this
local Soldier soldier
local integer id
if isLeader(GetTriggerUnit()) then //If the issued unit is a leader
set this = thistype[GetTriggerUnit()]
if .memberCount > 0 then
set id = GetIssuedOrderId()
set soldier = .first
set Soldier.checkOrder = false
loop
exitwhen soldier == 0
//Issue the same order to every member
if IssueImmediateOrderById(soldier.me, id) then
//Issuing defend order alone interrupts previous orders so it has to be resumed
if id == OrderId("defend") or id == OrderId("undefend") then
call soldier.resumeOrder()
//Resuming order sets .checkOrder to true so it has to be set back to false
set Soldier.checkOrder = false
else
//If it wasn't a defend order, the standard procedure of saving issued order is carried out
set soldier.orderId = id
set soldier.orderPoint = false
set soldier.orderTarget = null
endif
endif
set soldier = soldier.next
endloop
set Soldier.checkOrder = true
endif
elseif isSoldier(GetTriggerUnit()) and Soldier.checkOrder then
//If the issued unit was soldier and the order did not come from the leader, the previous order is resumed
call Soldier[GetTriggerUnit()].resumeOrder()
endif
return false
endmethod
static method orderPoint takes nothing returns boolean
//Mimicking leader's point orders
local thistype this
local Soldier soldier
local integer id
local integer i
local integer rows
local real leaderx
local real leadery
local real facing
local real x
local real y
local real d
local real a
local integer c
if isLeader(GetTriggerUnit()) then
set this = thistype[GetTriggerUnit()]
if .memberCount > 0 then
set id = GetIssuedOrderId()
//Caching order coordinates and angle towards it
set leaderx = GetOrderPointX()
set leadery = GetOrderPointY()
set facing = Atan2(leadery - GetUnitY(.me), leaderx - GetUnitX(.me))
set soldier = .first
set rows = (.memberCount + (.spr - 1)) / .spr //Getting number of rows (.spr - soldiers per row)
set i = 1
set Soldier.checkOrder = false
loop
//Iterating every row
exitwhen i > rows
set x = i*.vDistance //Vertical distance to the row
//Getting the number of soldiers in current row
if i == rows then //If it's the last row
//Then the number needs to be calculated
set c = .memberCount - (rows - 1)*.spr
else
//If it's not last row, then the number is standard
set c = .spr
endif
set y = (c - 1)*.hDistance/2 //Distance from the first soldier's position in the row to the middle of the row
set d = SquareRoot(x*x + y*y) //Distance between the leader and the soldier's position
set a = Atan2(y, x) + facing + bj_PI //Angle between the leader and the soldier's position
//Coordinates of the first soldier in the row
set x = leaderx + d*Cos(a)
set y = leadery + d*Sin(a)
set a = facing + bj_PI/2 //Row angle, used for lining soldiers up
loop
//Iterating row's soldiers and their positions
set c = c - 1
//Saving issued order and giving it to the soldiers
set soldier.orderPoint = true
set soldier.orderX = x + c*.hDistance*Cos(a)
set soldier.orderY = y + c*.hDistance*Sin(a)
set soldier.orderTarget = null
if IssuePointOrderById(soldier.me, id, soldier.orderX, soldier.orderY) then
set soldier.orderId = id
else
call IssuePointOrderById(soldier.me, OrderId("smart"), soldier.orderX, soldier.orderY)
set soldier.orderId = OrderId("smart")
endif
set soldier = soldier.next
exitwhen c == 0
endloop
set i = i + 1
endloop
set Soldier.checkOrder = true
endif
elseif isSoldier(GetTriggerUnit()) and Soldier.checkOrder then
call Soldier[GetTriggerUnit()].resumeOrder()
endif
return false
endmethod
static method orderTarget takes nothing returns boolean
//Mimicking leader's target orders
local thistype this
local Soldier soldier
local integer id
local widget target
if isLeader(GetTriggerUnit()) then
set this = thistype[GetTriggerUnit()]
if .memberCount > 0 then
set id = GetIssuedOrderId()
set target = GetOrderTarget()
set soldier = .first
set Soldier.checkOrder = false
loop
exitwhen soldier == 0
//Saving leader's order and issuing it to members
set soldier.orderPoint = false
set soldier.orderTarget = target
if IssueTargetOrderById(soldier.me, id, target) then
set soldier.orderId = id
else
call IssueTargetOrderById(soldier.me, OrderId("smart"), target)
set soldier.orderId = OrderId("smart")
endif
set soldier = soldier.next
endloop
set Soldier.checkOrder = true
set target = null
endif
elseif isSoldier(GetTriggerUnit()) and Soldier.checkOrder then
call Soldier[GetTriggerUnit()].resumeOrder()
endif
return false
endmethod
static method onInit takes nothing returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.train)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.death)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function thistype.order)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function thistype.orderPoint)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.orderTarget)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.train))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, Condition(function thistype.death))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddCondition(t, Condition(function thistype.order))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerAddCondition(t, Condition(function thistype.orderPoint))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerAddCondition(t, Condition(function thistype.orderTarget))
set t = null
endif
endmethod
endstruct
private module initModule
private static method onInit takes nothing returns nothing
//Initializing table and soldier linked list end
set detachmentData = TableArray[8]
set Soldier(0).next = 0
set Soldier(0).prev = 0
endmethod
endmodule
private struct initStruct
implement initModule
endstruct
endlibrary