- Joined
- Apr 27, 2008
- Messages
- 2,455
Introduction :
For instant enumeration, we have the classic GroupEnum with a null filter, and then the FirstOfGroup/GroupRemoveUnit loop.
Sadly, we can't use that for groups which are kept during time, because of ghost units (units removed of the game but not of the group).
Just because FirstOfGroup can and will return null with a ghost unit.
And even if it wouldn't, at the end of the loop the group will be empty and then couldn't be used again later.
The usual alternative is to use a ForGroup, because ghost units are not enumed with that.
But it's bad because it splits the code and also opens a new thread each time it's called, it's inefficient by all means.
So i've made this library (UnitLL stands for Unit Linked List).
It handles ghost units, there is no way that you will have some inside your UnitLL.
Il also keeps the unit relative order when they were added, you can know how many units you have in there (.count) at any time, the last and the first unit added.
Most of methods returns a boolean (if true the unit was removed/added to the group, whatever, if false not) or an integer.
It works as a group, in the sense that an unit can be added to an UnitLL only one time and no more.
Additional Notes :
- I'm never going to use a struct array and custom allocators, just for silly improvements
- Actually UnitLL is a double not circular linked list, i made it not circular because it makes sense to privilege the iteration over than the add and unit remove.
- I'm not going to make benchmarks, i can't believe that a ForGroup is faster than a not circular LL loop iteration.
Well, maybe i will make this only one if someone still believes it.
But anyway even if it's slower (why the hell ?!), speed is not the main feature here.
Also it's obvious that the custom methods are way slower than the native group functions.
Thx for reading, i'm open to all constructive comments.
The last "unofficial" jasshelper version by Cohadar is needed
It can be found here or there.
I use it because it has better loops, fix the madness vJass initializers order, faster to compile, and for future improvements.
Changelog :
v 1.1
v 2.0
v 2.1
v 2.2
v 2.25
For instant enumeration, we have the classic GroupEnum with a null filter, and then the FirstOfGroup/GroupRemoveUnit loop.
Sadly, we can't use that for groups which are kept during time, because of ghost units (units removed of the game but not of the group).
Just because FirstOfGroup can and will return null with a ghost unit.
And even if it wouldn't, at the end of the loop the group will be empty and then couldn't be used again later.
The usual alternative is to use a ForGroup, because ghost units are not enumed with that.
But it's bad because it splits the code and also opens a new thread each time it's called, it's inefficient by all means.
So i've made this library (UnitLL stands for Unit Linked List).
It handles ghost units, there is no way that you will have some inside your UnitLL.
Il also keeps the unit relative order when they were added, you can know how many units you have in there (.count) at any time, the last and the first unit added.
Most of methods returns a boolean (if true the unit was removed/added to the group, whatever, if false not) or an integer.
It works as a group, in the sense that an unit can be added to an UnitLL only one time and no more.
Additional Notes :
- I'm never going to use a struct array and custom allocators, just for silly improvements
- Actually UnitLL is a double not circular linked list, i made it not circular because it makes sense to privilege the iteration over than the add and unit remove.
- I'm not going to make benchmarks, i can't believe that a ForGroup is faster than a not circular LL loop iteration.
Well, maybe i will make this only one if someone still believes it.
But anyway even if it's slower (why the hell ?!), speed is not the main feature here.
Also it's obvious that the custom methods are way slower than the native group functions.
Thx for reading, i'm open to all constructive comments.
JASS:
library UnitLL uses UnitIndexer
globals
group GROUP = CreateGroup() // must be used for instant enumeration, use it with a FirstOfGroup/GroupRemoveUnit loop
// never destroy it
endglobals
/*
==========================================================================
UnitLL v2.25
--------------------------------------------------------------------------
"Easy" iteration on units, complete. It is designed to be as fast as possible for iteration.
It handles ghost units (you can't have an unit removed of the game inside an UnitLL)
But the backward is that is the slowest solution for all other operations.
UnitLL is basically a double not circular linked list, like groups you can't add an unit several times.
==========================================================================
Intro:
--------------------------------------------------------------------------
For instant enumeration we have the classic GroupEnum... with a null filter,
and then a FirstOfGroup/GroupRemoveUnit loop.
But when you need to keep units during time, you can't safely use a such loop,
because FirstOfGroup can and will return null with a ghost unit.
To be clear : this is not for speedfreaks, even if it's probably the fastest solution on loop iterating,
it's also obvious that is the slowest solution for all other operations.
I would say that the returned values of methods are nice and useful,
and be able to keep the relative unit order is definetely a plus.
You have to know how linked list work and especially in vJass to use it, i hope that the demo codes are enough.
Practicals limits (just for completeness reason) :
At any time this must be true :
Number of UnitLL head + Number of UnitLL instances < 8191
Number of units which are at least in one UnitLL head + Number of UnitLL head where an unit is inside < 8191
==========================================================================
API Guide:
*readonly UnitLL members :
--------------------------------------------------------------------------
* thistype head
Since most of methods work only with an UnitLL head, and i have the use of it in the internal code.
An UnitLL head is basically like the "groups", i mean that is the data structure where units are stored.
* thistype first & last
It's the first/last member of an UnitLL head
* thistype next & prev
Classic members of double linked list.
Note that an UnitLL is a double not circular linked list, so you can't directly use someUnitLLHead.next/prev
You have to use someUnitLLHead.first/last before starting the loop through the UnitLL
* integer count
It returns the number of unit contained in the UnitLL head
* unit enum
You still can use it, but it's deprecated, since the version 2.1, it's better to use the method getUnit()
It's simply the unit stored in an UnitLL, you catch them by looping through the UnitLL head instances
* boolean end
You must use it inside a loop, or you can also check if the UnitLL instance == 0, check the demo spell Life Drain.
* static method create takes nothing returns thistype Complexity : O(1)
--------------------------------------------------------------------------
Create a new UnitLL head. You must create it before anything else.
An UnitLL head is like a group, it will contain other UnitLL instances (basically units)
* method getUnit takes nothing returns unit Complexity : O(1)
--------------------------------------------------------------------------
It's simply the unit stored in an UnitLL instance, you catch them by looping through the UnitLL head
* method getRandomUnit takes nothing returns unit Complexity : O(N/2) at worst
--------------------------------------------------------------------------
returns a random unit from and UnitLL head.
* method addGroup takes group whichGroup, boolean safety returns integer Complexity : O(N)
--------------------------------------------------------------------------
Add a group to an UnitLL head. In case one unit was already inside the UnitLL, it is not added again.
It returns the number of unit added, useful in some cases.
Be aware, that if safety == false, it will use a FOG loop (FirstOfGroup/GroupRemoveUnit), so the group will be cleared.
If safety == true, it will use ForForce, so it will be less efficient but will work if the group has ghost units inside it.
You should use it only on groups which are instantly enumed, not keep during time, but meh.
* method removeGroup takes group whichGroup, boolean safety returns integer Complexity : O(N)
--------------------------------------------------------------------------
Same as addGroup, except that of course it removes unit from the UnitLL.
* method addUnit takes unit whichUnit returns boolean Complexity : O(1)
--------------------------------------------------------------------------
Add an unit to an UnitLL head. In case one unit was already inside the UnitLL, it is not added again.
It returns true if the unit is added, and false if not.
* method removeUnit takes unit whichUnit returns boolean Complexity : O(1)
--------------------------------------------------------------------------
Remove an unit to an UnitLL head.
It returns true if the unit is removed, and false if not.
* method addUnitLL takes UnitLL head returns integer Complexity : O(N)
--------------------------------------------------------------------------
Add an UnitLL head to the other UnitLL head.
It returns the number of unit added.
* method removeUnitLL takes UnitLL head returns integer Complexity : O(N)
--------------------------------------------------------------------------
Remove an UnitLL head to the other UnitLL head.
It returns the number of unit removed.
* method isUnitInside takes unit whichUnit returns boolean Complexity : O(1)
--------------------------------------------------------------------------
Returns true if the unit is inside the UnitLL head, and false if not
* method removeThis takes nothing returns nothing Complexity : O(1)
--------------------------------------------------------------------------
Remove an UnitLL instance from the UnitLL head
* method clear takes nothing returns nothing Complexity : O(N)
--------------------------------------------------------------------------
Clear the UnitLL head, but not destroy it.
* method destroy takes nothing returns nothing Complexity : O(N)
--------------------------------------------------------------------------
Clear the UnitLL head, and then destroy it.
* method onDeindex takes code toRegister returns nothing Complexity : O(1)
--------------------------------------------------------------------------
Will fire the code for an UnitLL head if one of its unit is being removed.
Note that you can use the UnitIndexer API related to the deindex event, such as GetIndexedUnitId() and GetIndexedUnit()
The registered code will be hopefully removed when the UnitLL will be destroyed (but not just if it's cleared).
Technically you can register only one code to an UnitLL, if there was already a previous one it will be erased.
I could allow several codes but i don't see the usefulness of it
Also, because it uses triggerconditions, don't use GetTriggeringTrigger(), and you're also not allowed to use TriggerSleepAction/PolledWait.
And some other functions like the sync natives functions and PauseGame.
* static method getDeindexing takes nothing returns thistype Complexity : O(1)
--------------------------------------------------------------------------
UnitLL.getDeindexing() : It must be used inside a code registred with the method onDeindex,
it returns the UnitLL head where an unit is being removed of the game
Note that if you don't need to do some stuff on unit deindex, these last two methods are useless.
The unit will still be removed of the UnitLL when it will be removed
*/
globals
private hashtable Hash_t = InitHashtable()
private integer array LL_main
endglobals
private struct LL // Linked list to link units and their UnitLL
thistype next
thistype prev
thistype head
UnitLL x
static method create takes nothing returns thistype
local thistype s = thistype.allocate()
set s.head = s
set s.next = s
set s.prev = s
return s
endmethod
method addUnitLL takes UnitLL x returns thistype
local thistype s = thistype.allocate()
set this.prev.next = s
set s.prev = this.prev
set this.prev = s
set s.next = this
set s.head = this.head
set s.x = x
return s
endmethod
method removeThisLL takes nothing returns nothing
local thistype s = this.head
set this.prev.next = this.next
set this.next.prev = this.prev
if s == s.next then // empty
set LL_main[GetUnitId(this.x.enum)] = 0
call s.deallocate()
set s.head = 0
set s.next = 0
set s.prev = 0
set s.x = 0
endif
call this.deallocate()
set this.head = 0
set this.x = 0
endmethod
endstruct
struct UnitLL
readonly unit enum
readonly thistype next
readonly thistype prev
readonly thistype first
readonly thistype last
readonly integer count
readonly thistype head
readonly boolean end
/* used internally (don't care about these comments)
this , id > whichUnitLL instance, according to the unit id and the UnitLL this
id ,- this >whichLL instance, according to the unit id and the UnitLL this
*/
/* I've managed to make all the debug stuff not mandatory.
I mean that it will work as expected in not debug mode even with invalid arguments,
but the debug mode helps the user to catch errors in his code
*/
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.head = this
set this.next = 0
set this.prev = 0
set this.count = 0
set this.last = 0
set this.first = 0
set this.enum = null
return this
endmethod
method getUnit takes nothing returns unit
debug if this == 0 then
debug call BJDebugMsg("UnitLL.getUnit() : the UnitLL instance is not valid (0)\n\n")
debug return null
debug endif
debug if this.head == this then
debug call BJDebugMsg("UnitLL.getUnit() : you must use an UnitLL instance for this method, not an UnitLL head")
debug return null
debug endif
return this.enum
endmethod
method getRandomUnit takes nothing returns unit
local integer i = GetRandomInt(1,this.count)
debug if this == 0 then
debug call BJDebugMsg("UnitLL.getRandomUnit() : the UnitLL instance is not valid (0)\n\n")
debug return null
debug endif
debug if this.head != this then
debug call BJDebugMsg("UnitLL.getRandomUnit() : you must use an UnitLL head for this method, not an UnitLL instance")
debug return null
debug endif
if i > this.count / 2 then
set i = i - this.count/2
set this = this.last
loop
exitwhen i == 0
set this = this.prev
set i = i-1
endloop
else
set this = this.first
loop
exitwhen i == 0
set this = this.next
set i = i-1
endloop
endif
return this.enum
endmethod
method isUnitInside takes unit whichUnit returns boolean
debug if this == 0 then
debug call BJDebugMsg("UnitLL.isUnitInside() : the UnitLL instance is not valid (0)\n\n")
debug return false
debug endif
debug if this.head != this then
debug call BJDebugMsg("UnitLL.isUnitInside() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
debug return false
debug endif
debug if not IsUnitIndexed(whichUnit) then
debug call BJDebugMsg("UnitLL.isUnitInside() : you must use an indexed unit through UnitIndexer\n\n")
debug return false
debug endif
return HaveSavedInteger(Hash_t,this,GetUnitId(whichUnit)) and IsUnitIndexed(whichUnit)
endmethod
method addUnit takes unit whichUnit returns boolean // returns true if the unit is added and false if not
local thistype id = thistype(GetUnitId(whichUnit))
local thistype s
local LL ll
debug if this == 0 then
debug call BJDebugMsg("UnitLL.addUnit() : the UnitLL instance is not valid (0)\n\n")
debug return false
debug endif
debug if not IsUnitIndexed(whichUnit) then
debug call BJDebugMsg("UnitLL.addUnit() : you must use an indexed unit by UnitIndexer\n\n")
debug return false
debug endif
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.addUnit() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return false
endif
if HaveSavedInteger(Hash_t,this,id) or not IsUnitIndexed(whichUnit) then // the unit is already inside the UnitLL or invalid
return false
endif
set s = thistype.allocate()
set s.enum = whichUnit
set s.head = this
set s.next = 0
set s.prev = 0
call SaveInteger(Hash_t,this,id,s)
set ll = LL(LL_main[id])
if ll == 0 then // LL was not already created for the unit
set ll = LL.create()
set LL_main[id] = ll
endif
if this.count == 0 then
set this.first = s
else
set this.last.next = s
set s.prev = this.last
endif
set this.last = s
set this.count = this.count + 1
call SaveInteger(Hash_t,id,-this,ll.addUnitLL(s))
return true
endmethod
method removeUnit takes unit whichUnit returns boolean // returns true if the unit is removed and false if not
local thistype id = thistype(GetUnitId(whichUnit))
local thistype s = thistype(LoadInteger(Hash_t,this,id))
local LL ll
debug if this == 0 then
debug call BJDebugMsg("UnitLL.removeUnit() : the UnitLL instance is not valid (0)\n\n")
debug return false
debug endif
debug if not IsUnitIndexed(whichUnit) then
debug call BJDebugMsg("UnitLL.removeUnit() : you must use an indexed unit by UnitIndexer\n\n")
debug return false
debug endif
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.removeUnit() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return false
endif
if s == 0 or not IsUnitIndexed(whichUnit) then // the unit is not inside the UnitLL or not valid
return false
endif
set this.count = this.count - 1
if this.count == 0 then
set this.last = 0
set this.first = 0
elseif s == this.last then
set this.last = s.prev
set s.prev.next = 0
elseif s == this.first then
set this.first = s.next
set s.next.prev = 0
else
set s.next.prev = s.prev
set s.prev.next = s.next
endif
set ll = LL(LoadInteger(Hash_t,id,-this))
if ll != 0 then
call ll.removeThisLL()
call RemoveSavedInteger(Hash_t,id,-this)
endif
call RemoveSavedInteger(Hash_t,this,id)
set s.enum = null
set s.head = 0
call s.deallocate()
return true
endmethod
private static integer I
private static thistype This
private static thistype Temp
private static method safetyAdd takes nothing returns nothing
if thistype.Temp.addUnit(GetEnumUnit()) then
set thistype.I = thistype.I + 1
endif
endmethod
method addGroup takes group whichGroup, boolean safety returns integer
local unit u
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.addGroup() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return 0
endif
set thistype.I = 0
if safety then
set thistype.Temp = this
call ForGroup(whichGroup,function thistype.safetyAdd)
else
for u in whichGroup
if this.addUnit(u) then
set thistype.I = thistype.I + 1
endif
endfor
endif
return thistype.I
endmethod
private static method safetyRemove takes nothing returns nothing
if thistype.Temp.removeUnit(GetEnumUnit()) then
set thistype.I = thistype.I + 1
endif
endmethod
method removeGroup takes group whichGroup, boolean safety returns integer
local unit u
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.removeGroup() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return 0
endif
set thistype.I = 0
if safety then
set thistype.Temp = this
call ForGroup(whichGroup,function thistype.safetyRemove)
else
for u in whichGroup
if this.removeUnit(u) then
set thistype.I = thistype.I + 1
endif
endfor
endif
return thistype.I
endmethod
method addUnitLL takes UnitLL head returns integer
local thistype s = head.first
local integer i = 0
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.addUnitLL() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return 0
endif
loop
exitwhen s == 0
if this.addUnit(s.enum) then
set i = i + 1
endif
set s = s.next
endloop
return i
endmethod
method removeUnitLL takes UnitLL head returns integer
local thistype s = head.first
local integer i = 0
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.removeUnitLL() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return 0
endif
loop
exitwhen s == 0
if this.removeUnit(s.enum) then
set i = i + 1
endif
set s = s.next
endloop
return i
endmethod
method removeThis takes nothing returns nothing
debug if this.head == this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.removeThis() : it is for remove an UnitLL instance from an UnitLL head, don't use it on a head")
debug call BJDebugMsg("if you want to clear a whole UnitLL, use the method clear() on an UnitLL head")
debug call BJDebugMsg("check also if it's a valid UnitLL instance\n\n")
debug return
debug endif
call this.head.removeUnit(this.enum)
endmethod
method clear takes nothing returns nothing
debug if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.clear() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
debug return
debug endif
set this = this.first
loop
exitwhen this == 0
call this.removeThis()
set this = this.next
endloop
endmethod
private trigger trig_deindex
method destroy takes nothing returns nothing
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.destroy() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return
endif
if this.trig_deindex != null then
call DestroyTrigger(this.trig_deindex)
set this.trig_deindex = null
endif
call this.clear()
call this.deallocate()
set this.head = 0
set this.first = 0
set this.last = 0
endmethod
method onDeindex takes code toRegister returns nothing
if this.head != this then // will even work with this == 0, check the initializer
debug call BJDebugMsg("UnitLL.onDeindex() : you must use an head for this method, not just an UnitLL instance")
debug call BJDebugMsg("check also if it's a valid UnitLL head\n\n")
return
endif
if this.trig_deindex == null then
set this.trig_deindex = CreateTrigger()
else
call TriggerClearConditions(this.trig_deindex)
endif
call TriggerAddCondition(this.trig_deindex,Filter(toRegister))
endmethod
static method getDeindexing takes nothing returns thistype
return thistype.This
endmethod
private static method onRemove takes nothing returns boolean
local LL this = LL(LL_main[GetIndexedUnitId()])
local LL s
if this == 0 then
return false
endif
set s = this.next
loop
exitwhen s == this
set thistype.This = s.x.head
call TriggerEvaluate(s.x.head.trig_deindex)
set thistype.This = 0
call s.x.removeThis()
set s = s.next
endloop
return false
endmethod
private static method onInit takes nothing returns nothing
call RegisterUnitIndexEvent(Filter(function thistype.onRemove),UnitIndexer.DEINDEX)
set thistype(0).head = 1 // this way "if this.head != this" will be enough even with this == 0
set thistype(0).end = true
endmethod
endstruct
endlibrary
The last "unofficial" jasshelper version by Cohadar is needed
It can be found here or there.
I use it because it has better loops, fix the madness vJass initializers order, faster to compile, and for future improvements.
JASS:
scope LifeDrain initializer init // uses UnitLL , TimerStack, UnitAPI
globals
private constant integer SPELL_ID = 'A000'
private constant real PERIOD = 2
private constant real RANGE = 150
private constant real SPELL_ANIMATION_TIME = 1.5
private constant real DAMAGE_OVER_WAVE = 100
private UnitLL array LL
private timer array Tim
endglobals
private struct Stack
lightning light
thistype next
endstruct
private function DestroyLights takes nothing returns nothing
local Stack s = TimerRelease(GetExpiredTimer())
local Stack temp
call s.destroy()
loop
set temp = s
set s = s.next
exitwhen s == 0
call DestroyLightning(s.light)
set s.light = null
call s.destroy()
set temp.next = 0
endloop
endfunction
private function Periodic takes nothing returns nothing
local integer id = TimerGetData(GetExpiredTimer())
local unit caster = GetUnitById(id)
local unit u
local UnitLL ll = LL[id]
local integer count = ll.count
local Stack s
set s = Stack.create()
call TimerNew(0.2,function DestroyLights,s)
set ll = ll.first
loop
exitwhen ll.end // or exitwhen ll == 0, it's the same
set s.next = Stack.create()
set s = s.next
set u = ll.getUnit()
if IsUnitType(u,UNIT_TYPE_DEAD) then
call UnitDamageTarget(caster,caster,DAMAGE_OVER_WAVE*3/count,false,false,null,null,null)
set s.light = AddLightningEx("AFOD",true,GetUnitX(caster),GetUnitY(caster),UnitGetZ(caster)+100,GetUnitX(u),GetUnitY(u),UnitGetZ(u))
else
call UnitDamageTarget(caster,u,DAMAGE_OVER_WAVE/count,false,false,null,null,null)
call UnitDamageTarget(caster,caster,-DAMAGE_OVER_WAVE/count,false,false,null,null,null)
set s.light = AddLightningEx("DRAB",true,GetUnitX(caster),GetUnitY(caster),UnitGetZ(caster)+100,GetUnitX(u),GetUnitY(u),UnitGetZ(u))
endif
set ll = ll.next
endloop
set caster = null
set u = null
endfunction
private function StopSpell takes nothing returns boolean
local unit caster
local integer id
if GetSpellAbilityId() != SPELL_ID then
return false
endif
set caster = GetSpellAbilityUnit()
set id = GetUnitId(caster)
call LL[id].destroy()
set LL[id] = 0
call SetUnitTimeScale(caster,1)
if Tim[id] != null then
call TimerRelease(Tim[id])
set Tim[id] = null
endif
set caster = null
return true
endfunction
private function StartSpell takes nothing returns boolean
local unit caster = GetSpellAbilityUnit()
local integer id = GetUnitId(caster)
local integer count
local UnitLL ll
local Stack s
local unit u
local player p = GetTriggerPlayer()
if GetSpellAbilityId() != SPELL_ID then
return false // yeah yeah, possible handle id leaks i don't care, it's just a quick demo
endif
// so first we enum unit as usually
call GroupEnumUnitsInRange(GROUP,GetSpellTargetX(),GetSpellTargetY(),RANGE,null)
call GroupRemoveUnit(GROUP,caster) // we don't want the caster in the group
set ll = UnitLL.create() // creates the UnitLL head
set LL[id] = ll // link the UnitLL and the caster
for u in GROUP // classic FirstOfGroup/GroupRemoveUnit loop
if IsUnitEnemy(u,p) then
call ll.addUnit(u) // addUnit is a method that must be used with the head UnitLL
endif
endfor
set count = ll.count // as the method addUnit, count is a member of the head only
if count == 0 then // no units were added
call IssueImmediateOrderById(caster,851973) // "stun" order
call SimError(p,"Spell aborted, no targeted units found")
set p = null
set caster = null
return true // at this point there is still an empty UnitLL which is created, but il will be destroyed when the endcast event will fire
// one thing is good with this event : it always fire, no matter how the spell end.
endif
call SetUnitTimeScale(caster,SPELL_ANIMATION_TIME/PERIOD)
set s = Stack.create()
call TimerNew(0.2,function DestroyLights,s) // start the timer and link it with the stack
set ll = ll.first // we start with the first unit added
loop // here we go, loop through the UnitLL
exitwhen ll.end // since UnitLL is a double not circular linked list, or exitwhen ll == 0, it's the same
set s.next = Stack.create()
set s = s.next
set u = ll.getUnit()
if IsUnitType(u,UNIT_TYPE_DEAD) then
call UnitDamageTarget(caster,caster,DAMAGE_OVER_WAVE*3/count,false,false,null,null,null)
set s.light = AddLightningEx("AFOD",true,GetUnitX(caster),GetUnitY(caster),UnitGetZ(caster)+100,GetUnitX(u),GetUnitY(u),UnitGetZ(u))
else
call UnitDamageTarget(caster,u,DAMAGE_OVER_WAVE/count,false,false,null,null,null)
call UnitDamageTarget(caster,caster,-DAMAGE_OVER_WAVE/count,false,false,null,null,null)
set s.light = AddLightningEx("DRAB",true,GetUnitX(caster),GetUnitY(caster),UnitGetZ(caster)+100,GetUnitX(u),GetUnitY(u),UnitGetZ(u))
endif
set ll = ll.next // here we go forward, but we can go backward with prev, but note that is not a circular linked list
endloop
set Tim[id] = TimerNew(PERIOD,function Periodic,id) // to do waves
set caster = null
set p = null
set u = null
return true
endfunction
private function init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig,function StartSpell)
set trig = CreateTrigger()
call TriggerAddCondition(trig,function StopSpell)
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_ENDCAST)
set trig = null
endfunction
endscope
JASS:
scope Ondeindex initializer init
/*
This is just to show how to use the UnitLL API, in case you need to do some stuff on unit deindexing.
Honestly, i can't find a concrete case where you would need that, i mean killing the unit first would be better in lot of cases,
and then you could catch the death event. But there is the possibility to do it.
*/
globals
private UnitLL LL1
private UnitLL LL2
private UnitLL LL3
endglobals
private function F1 takes nothing returns nothing
// hopefully we can use the UnitIndexer API related to the unit deindex event
local integer id = GetHandleId(GetIndexedUnit())
local UnitLL ll = UnitLL.getDeindexing()
call RemoveUnit(LL1.last.getUnit()) // yes recursive unit remove is allowed
call BJDebugMsg("unit handle id : " + I2S(id) + " is being removed of the UnitLL : " +I2S(ll) )
call BJDebugMsg("do some stuff related to the UnitLL LL1 \n\n")
endfunction
private function F2 takes nothing returns nothing
local integer id = GetIndexedUnitId()
call BJDebugMsg("unit handle id : " + I2S(id) + " is being removed of the UnitLL : " +I2S(LL2) )
call BJDebugMsg("do some stuff related to the UnitLL LL2 \n\n")
/* You can remove the unit from the UnitLL inside this code, but most of time it's pointless,
simply because it will be removed automatically anyway, right after all onDeindex codes registred for all UnitLL(s)
(without any delay)
*/
call LL2.removeUnit(GetIndexedUnit())
endfunction
private function F3 takes nothing returns nothing
local integer id = GetHandleId(GetIndexedUnit())
local UnitLL ll = UnitLL.getDeindexing()
call BJDebugMsg("unit handle id : " + I2S(id) + " is being removed of the UnitLL : " +I2S(ll) )
call BJDebugMsg("do some stuff related to the UnitLL LL3 \n\n")
endfunction
private function init takes nothing returns nothing
local unit u
// i'm just adding "random" units to the UnitLL(s), the revelant code is below the next comment
call TriggerSleepAction(0)
set LL1 = UnitLL.create()
set LL2 = UnitLL.create()
set LL3 = UnitLL.create()
call GroupEnumUnitsOfPlayer(GROUP,Player(1),null)
set u = FirstOfGroup(GROUP)
call GroupRemoveUnit(GROUP,u)
call LL1.addUnit(u)
set u = FirstOfGroup(GROUP)
call GroupRemoveUnit(GROUP,u)
call LL1.addUnit(u)
set u = FirstOfGroup(GROUP)
call GroupRemoveUnit(GROUP,u)
call LL2.addUnit(u)
set u = FirstOfGroup(GROUP)
call GroupRemoveUnit(GROUP,u)
call LL3.addUnit(u)
// here we go
call LL1.onDeindex(function F1)
call LL2.onDeindex(function F2)
call LL3.onDeindex(function F3)
call TriggerSleepAction(1)
// let's remove units
call RemoveUnit(LL1.first.enum)
call RemoveUnit(LL2.first.enum)
call RemoveUnit(LL3.first.enum)
/*
I don't have used the method removeUnit() inside the functions F1,F2,F3.
Once the registered code had fired, the unit will be removed of the UnitLL.
But nothing forbids you to use it in the registred code.
Technically you can register only one code to an UnitLL, if there was already a previous one it will be erased.
I could allow several codes but i don't see the usefulness of it
*/
endfunction
endscope
JASS:
scope UnitLLBoolean initializer init
globals
private UnitLL LL_first
private UnitLL LL_second
endglobals
private function Enter2 takes nothing returns nothing
local unit u = GetEnteringUnit()
if LL_first.isUnitInside(u) then // the unit is inside LL_first
call BJDebugMsg("the unit with the id : "+I2S(GetUnitId(u))+" will got his reward right now\n\n")
call KillUnit(u)
// in case the unit ressurect and you still want this "feature" we remove the unit from the 2 UnitLL
call LL_first.removeUnit(u)
call LL_second.removeUnit(u)
elseif LL_second.addUnit(u) then // the unit was not inside LL_second
call BJDebugMsg("the unit with the id : "+I2S(GetUnitId(u))+" needs to enter in region 1 before\n\n")
endif
set u = null
endfunction
private function Enter1 takes nothing returns nothing
local unit u = GetEnteringUnit()
if LL_first.addUnit(u) then // the unit was not already inside LL_first
call BJDebugMsg("the unit with the id : "+I2S(GetUnitId(u))+" enters in region1 for the first time")
call BJDebugMsg("If you want to get a special reward, go to region2\n\n")
else // the unit was already inside LL_first
endif
set u = null
endfunction
private function init takes nothing returns nothing
local rect zone
local region reg = CreateRegion()
local trigger trig = CreateTrigger()
set LL_first = UnitLL.create()
set LL_second = UnitLL.create()
set zone = Rect(500,-1300,1000,-900)
call SetBlightRect(Player(0),zone,true)
call RegionAddRect(reg,zone)
call RemoveRect(zone)
call TriggerRegisterEnterRegion(trig,reg,null)
call TriggerAddAction(trig,function Enter1)
set trig = CreateTrigger()
set reg = CreateRegion()
set zone = Rect(500,-700,1000,-300)
call SetBlightRect(Player(0),zone,true)
call RegionAddRect(reg,zone)
call RemoveRect(zone)
call TriggerRegisterEnterRegion(trig,reg,null)
call TriggerAddAction(trig,function Enter2)
set zone = null
set trig = null
set reg = null
endfunction
endscope
JASS:
scope UnitLLTest initializer init
private function init takes nothing returns nothing
local unit u
local integer i = 0
local UnitLL ll_1 = UnitLL.create()
local UnitLL ll_2 = UnitLL.create()
call TriggerSleepAction(0)
call GroupEnumUnitsOfPlayer(GROUP,Player(1),null)
call BJDebugMsg("number of Player(1)'s units == " + I2S( ll_1.addGroup(GROUP,false) ) )
call GroupEnumUnitsOfPlayer(GROUP,Player(0),null)
call BJDebugMsg("you should see 6 times the same number")
call BJDebugMsg("number of Player(0)'s units == " + I2S( ll_2.addGroup(GROUP,true) ) )
call BJDebugMsg("number of units in GROUP == " + I2S( CountUnitsInGroup(GROUP) ) )
call BJDebugMsg("number of units added to ll_1 == " + I2S( ll_1.addUnitLL(ll_2) ) )
call BJDebugMsg("number of units removed to ll_1 == " + I2S( ll_1.removeUnitLL(ll_2) ) )
call ll_1.addUnitLL(ll_2)
call GroupEnumUnitsOfPlayer(GROUP,Player(0),null)
call BJDebugMsg("number of units removed to ll_1 == " + I2S( ll_1.removeGroup(GROUP,false) ) )
call BJDebugMsg("number of units in GROUP == " + I2S( CountUnitsInGroup(GROUP) ) )
loop
exitwhen i == 10
set i = i+1
call BJDebugMsg("random unit : " + I2S(GetUnitId( ll_1.getRandomUnit() )))
endloop
call BJDebugMsg(" ")
endfunction
endscope
Changelog :
v 1.1
v 2.0
v 2.1
v 2.2
v 2.25
Attachments
Last edited: