14 years
edited 8 years
Unit Recycler & Uber simple damage detection
By moyack - 2017
By moyack - 2017
Introduction:
This system is the result of working with a damage detection system that actually can be safe (AKA avoid totally the DestroyTrigger() function), so one solution to this issue is to avoid as much as possible (hopefully completely) the destruction of units and therefore the EVENT_DAMAGED triggers related to them, and the only way to make it possible is by recycling the units. The current result is a system that recycle most type of units (summoned and buildings simply don't work for their harcoded properties).
Why is convenient this system?
because the game won't need to allocate memory creating units, instead, they'll be backed up for a later use, which can improve your map performance. This can be very useful in maps where the unit spawning is a very common task (like AoS, footies, etc).
What's the issue with buildings and summoned units??
Buildings have limitations in moving them (they can be moved but their basement remains in the original place, which won't allow the construction of new units in those places). About summoned units, their dead is totally harcoded by the summoning abilities and can't be stopped by detecting the damage, therefore they can't be recycled.
How it works?
This library takes into account units that are being damaged, and detects if they're going to die (if the damage dealt to a unit is enough to kill them), if that situation happens, then it will be moved to the heaven, a place that you define in your map where the units are restored, cleaned and left ready for later use. Additionally this system offer a set of replacement functions which will help to this system to recycle units more efficiently.
How to install & final comments
- Create a trigger and call it "UnitRecycler" or whatever you consider it best.
- Convert it to custom text
- Paste the content below.
- In the configuration globals, set the AUTO_LOC = true if you want the system set automatically the Heaven location. Set it to false if you need to set it manually in the HEAVEN_POS variable.
- That's all

That's all??? not actually. By doing only this the library will be able to add damage detection to units and store the fallen units into the heaven. But it's up to you to develop the code to recycle the units. How?? using the functions provided (you can see them in the library comments).
Core Library
1 library UnitRecycler2 //******************************************************************************3 //*4 //* Unit Recycler5 //* By moyack. 20176 //*7 //* ============================================================================8 //* Credits to Litany, DioD, Captain Griffen, Troll Brain and other nice guys *9 //* for their suggestions, comments and ideas. *10 //* ============================================================================11 //*12 //* This library allows to your map to reuse died units, which saves memory.13 //* It's very useful in AoS or footies games, where the unit spawning is a14 //* common task.15 //*16 //* How it works?17 //* =============18 //*19 //* The script detects if a unit reach the dying point (defined by the MIN_LIFE20 //* constant), and if the damage can kill the unit, then it is sent to the dump21 //* for further recycling. For custom situations like summoning or when a unit22 //* enters to the map, you just have to use the functions provided by this 23 //* library.24 //*25 //* Functions26 //* =========27 //* 28 //* in order to recycle a unit, you can use these function:29 //*30 //* => RecycleUnit(<unit variable>) returns unit31 //* -----------------------------------------32 //* This function takes as argument a unit, and returns the unit recycled.33 //* if there's a unit of the same typeid in the dump, it will use this unit34 //* instead of the one from the function input, removing it immediately, 35 //* otherwise this script will return the same unit.36 //* 37 //* => CreateUnitEx(player, unitid, x, y, angle) returns unit38 //* ------------------------------------------------------39 //* Like the native function but tries to recycle the unit if avaliable in the dump40 //*41 //* => KillUnitEx(<unit variable>) returns nothing42 //* -------------------------------------------43 //* Like the native function but it recycles the unit before killing it.44 //*45 //* => RemoveUnitEx(<unit variable>) returns nothing46 //* ---------------------------------------------47 //* Like the native function but it recycles the unit.48 //*49 //* => IsUnitDead(<unit variable>) returns boolean50 //* -------------------------------------------51 //* This function returns a boolean argument that indicates if the unit is dead or not.52 //* Remember that a unit is dead if it's in the Heaven group.53 //*54 //* => ReplaceDummy(<unit variable>) returns unit55 //* -------------------------------------------56 //* This function returns a replacement unit without killing the output unit.57 //*58 //* => CreateDummy(player, unitid, x, y, angle) returns unit59 //* ------------------------------------------------------60 //* Like the CreateUnit function but the returned unit won't be affected by the damage61 //* detection nor will be recycled automatically62 //*63 //* => TriggerRegisterAnyUnitRecycleEvent(trigger t) returns nothing64 //* -------------------------------------------------------------65 //* All the trigger registered in this way will activate when a unit is about to be recycled66 //* (in other words, it triggers before it dies). You can use this functions to retrieve the67 //* the units involved in this event:68 //*69 //* # GetRecycledUnit() returns the units that is going to be recycled70 //* # GetRecycleDummyUnit() returns the dummy unit which will die instead of the recycled unit71 //* # GetRecycleAttacker() returns the unit that "kills" the recycled unit72 //*73 //* => IsUnitDummy(unit u) returns boolean74 //* -----------------------------------75 //* Checks if a unit can be recycled or not. Dummy units are not recycled, they're used as 76 //* placeholder of the unit about to die...77 //*78 //* => GetUnitsInHeaven() returns group 79 //* --------------------------------80 //* Returns the group which contains the units in the heaven. Useful to do checks and81 //* operations on them.82 //*83 //*84 //* For Damage detection, you just need to use these functions:85 //*86 //* => AddDamageCondition(<Boolexpr variable>) returns nothing87 //* -------------------------------------------------------88 //* Just add a condition function which manage the damaged unit and the script89 //* will use it with all the the units in the DD.D group.90 //*91 //* => DoNonDetectableDamage(unit, widget, damage, boolean_attack, boolean_ranged, attacktype, damagetype, weapontype) returns boolean92 //* -------------------------------------------------------------------------------------------------------------------------------93 //* Like the UnitDamageTarget function, but it can be used inside condition functions.94 //* How to know if you need to use it? if you use UnitDamageTarget() inside a DD function and 95 //* it freezes the game until it kills the attacked unit(s), then you have to replace that96 //* function by this custom one.97 //********************************************************************************98 //* CONFIGURATION PART99 globals100 private constant real MIN_LIFE = 0.405 // the experimental death value that will be used to activate
101 // the fake death of units. units that reach this value or less102 // won't die and they will be recycled.103 private constant boolean AUTO_LOC = false// If it's set to true, it will place automatically the heaven in one
104 // non visible corner of the map, else, it will use the HEAVEN_POS105 // as a heaven location106 private constant player DUMMY_PLAYER = Player(15) // sets the player owner of the unit's heaven
107 private constant real MANA_FACTOR = 0.5 // sets the initial mana amount (as percentage of maximum mana)
108 // to recycled units when they are just placed in the game.109 // This constant is used only by CreateUnitEx function.110
111 private location HEAVEN_POS = Location(-2500., 2700.) // Indicates the heaven's location.
112 endglobals113 //* END CONFIGURATION PART114 115 globals116 private group Heaven // where worthy units go when they die...
117 endglobals118 119 // this is a key functions part just to make homogeneous the unit management, please don't edit this unless120 // you know what are you doing, ok??121 private keyword Kill
122 123 private function PrepareUnit takes unit u returns nothing
124 call SelectUnit(u, false)
125 call UnitRemoveBuffs(u, true, true)
126 call UnitResetCooldown(u)
127 call SetUnitInvulnerable(u, true)
128 call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
129 call PauseUnit(u, true)
130 call GroupAddUnit(Heaven, u)
131 endfunction132 133 private function MoveUnit takes unit u returns nothing
134 call SetUnitOwner(u, DUMMY_PLAYER, true)
135 call SetUnitX(u, GetLocationX(HEAVEN_POS))
136 call SetUnitY(u, GetLocationY(HEAVEN_POS))
137 endfunction138 139 private function PlaceDummy takes unit d, unit u returns nothing
140 call GroupAddUnit(Kill.corpse, d)
141 call SetUnitUseFood(d, false)
142 call SetUnitState(d, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA))
143 call SetUnitFlyHeight(d, GetUnitFlyHeight(u), 0.)
144 //call SetCSData(d, GetCSData(u)) //used to pass attached data via CSData to the corpse...145 call SetUnitPathing(d, false)
146 call SetUnitX(d, GetUnitX(u))
147 call SetUnitY(d, GetUnitY(u))
148 endfunction149 // end key functions...150 151 private struct Trigger // struct to manage the EventRecycleUnit
152 private static integer i = 0
153 static unit R = null // recycled unit
154 static unit D = null // dummy unit
155 static unit A = null // attacker unit
156 trigger t
157
158 static method AddTrigger takes trigger t returns nothing
159 local thistype T = thistype.allocate()
160 set T.t = t
161 set thistype.i = integer(T)
162 endmethod163
164 static method evaluate takes unit r, unit d, unit a returns nothing
165 local integer i = 1
166 local Trigger T167 set thistype.R = r
168 set thistype.D = d
169 set thistype.A = a
170 loop171 exitwhen i > Trigger.i
172 set T =thistype(i)
173 if T.t != null and TriggerEvaluate(T.t) then
174 call TriggerExecute(T.t)
175 endif176 set i = i + 1
177 endloop178 endmethod179 endstruct180 181 private struct Kill // struct used to manage the killed units...
182 static group corpse // group of units that won't be touched by the damage detection
183 //(corpses and dummy units for instance)184
185 static method Do takes unit u, unit killer returns nothing
186 local unit d = CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
187 call SetUnitInvulnerable(u, true)
188 call PlaceDummy(d, u)
189 call SetWidgetLife(d, 1.)
190 call Trigger.evaluate(u, d, killer)
191 call UnitDamageTarget(killer, d, 2., true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
192 call PrepareUnit(u)
193 call MoveUnit(u)
194 set d = null
195 endmethod196
197 private static method ManageSummoned takes nothing returns nothing
198 if IsUnitInGroup(GetSummonedUnit(), Kill.corpse) then
199 // this part will run when a corpse is resurrected, so this units are suitable to200 // be controlled by the damage detection.201 call GroupRemoveUnit(Kill.corpse, GetSummonedUnit())
202 call SetUnitPathing(GetSummonedUnit(), true)
203 endif204 endmethod205
206 private static method onInit takes nothing returns nothing
207 local trigger t = CreateTrigger()
208 call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SUMMON)
209 call TriggerAddAction(t, function Kill.ManageSummoned)
210 set Kill.corpse = CreateGroup()
211 set t = null
212 endmethod213 endstruct214 215 private struct DD // damage detection struct, because damage detection can be so easy...
216 static group D // group of unit that will have damage detection
217 static trigger T
218 static timer Tm = CreateTimer() // used to control the infinite looping when is used UnitDamageTarget over the same triggering unit
219
220 // Add the attacked unit to the damage detection if the unit is not in the unit group221 private static method AddUnit takes unit d returns nothing
222 if not IsUnitInGroup(d, thistype.D) and not IsUnitInGroup(d, Kill.corpse) then
223 debug call DisplayTimedTextToForce(bj_FORCE_ALL_PLAYERS,2,"Added " + GetUnitName(d) + " to the DD")
224 call TriggerRegisterUnitEvent(thistype.T, d, EVENT_UNIT_DAMAGED)
225 call GroupAddUnit(thistype.D, d)
226 endif227 endmethod228
229 private static method Attacked takes nothing returns nothing
230 call thistype.AddUnit(GetTriggerUnit())
231 endmethod232
233 private static method Spelled takes nothing returns nothing
234 if GetSpellTargetUnit() != null then
235 call thistype.AddUnit(GetSpellTargetUnit())
236 endif237 endmethod238
239 static method AntiLoop takes nothing returns nothing
240 call EnableTrigger(thistype.T)
241 endmethod242
243 private static method onInit takes nothing returns nothing
244 set thistype.T = CreateTrigger()
245 set thistype.D = CreateGroup()
246 call TriggerRegisterAnyUnitEventBJ(thistype.T, EVENT_PLAYER_UNIT_ATTACKED)
247 call TriggerAddAction(thistype.T, function thistype.Attacked)
248 set thistype.T = CreateTrigger()
249 call TriggerRegisterAnyUnitEventBJ(thistype.T, EVENT_PLAYER_UNIT_SPELL_EFFECT)
250 call TriggerAddAction(thistype.T, function thistype.Spelled)
251 set thistype.T = CreateTrigger()
252 endmethod253 254 endstruct // you see, it's not bigger than this 
255 256 private struct UR // unit recycling struct
257 static group Temp = CreateGroup()
258 static unit U = null
259
260 static method FromDump takes nothing returns boolean
261 if IsUnitInGroup(GetFilterUnit(), Heaven) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false then
262 set thistype.U = GetFilterUnit()
263 endif264 return false
265 endmethod266
267 static method UseRecycled takes unit u returns unit
268 local real x = GetUnitX(u)
269 local real y = GetUnitY(u)
270 local real f = GetUnitFacing(u)
271 local real m = GetUnitState(u, UNIT_STATE_MANA)
272 local player p = GetOwningPlayer(u)
273 set thistype.U = null
274 call GroupEnumUnitsOfTypeCounted(thistype.Temp, UnitId2String(GetUnitTypeId(u)), Condition(function thistype.FromDump), 1)
275 if thistype.U != null then
276 debug call DisplayTimedTextToForce(bj_FORCE_ALL_PLAYERS,2, GetUnitName(thistype.U) + " is being reused...")
277 call SetUnitInvulnerable(thistype.U, false)
278 call GroupRemoveUnit(Heaven, thistype.U)
279 call PauseUnit(thistype.U, true)
280 call SetUnitPosition(thistype.U, x, y)
281 call SetUnitFacing(thistype.U, f)
282 call SetUnitOwner(thistype.U, p, true)
283 call SetUnitState(thistype.U, UNIT_STATE_MANA, m)
284 call SetUnitPathing(thistype.U, true)
285 call PauseUnit(thistype.U, false)
286 call RemoveUnit(u)
287 set u = null
288 return thistype.U
289 else290 return u291 endif292 endmethod293
294 private static method onRecycle takes nothing returns boolean
295 local unit u = GetTriggerUnit()
296 if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false and IsUnitType(u, UNIT_TYPE_SUMMONED) == false and GetWidgetLife(u) - GetEventDamage() <= MIN_LIFE then
297 call Kill.Do(u, GetEventDamageSource()) // place a dummy to replace the unit
298 // and returns the corpse299 debug call DisplayTimedTextFromPlayer(Player(15),0,0,2, GetUnitName(u) + " has been sent to the dump...")
300 endif301 set u = null
302 return false
303 endmethod304
305 private static method onInit takes nothing returns nothing
306 local rect r = GetWorldBounds()
307 set Heaven = CreateGroup()
308 call TriggerAddCondition(DD.T, Condition(function thistype.onRecycle))
309 if AUTO_LOC then
310 call MoveLocation(HEAVEN_POS, GetRectMinX(r), GetRectMinY(r))
311 endif312 call RemoveRect(r)
313 set r = null
314 endmethod315 endstruct316 317 //*====================318 //* User functions... *319 //*====================320 function RecycleUnit takes unit u returns unit
321 return UR.UseRecycled(u)
322 endfunction323 324 function CreateUnitEx takes player p, integer id, real x, real y, real f returns unit
325 set UR.U = null
326 call GroupEnumUnitsOfTypeCounted(UR.Temp, UnitId2String(id), Condition(function UR.FromDump), 1)
327 if UR.U != null then
328 debug call DisplayTimedTextToForce(bj_FORCE_ALL_PLAYERS,2, GetUnitName(UR.U) + " is being reused...")
329 call SetUnitInvulnerable(UR.U, false)
330 call GroupRemoveUnit(Heaven, UR.U)
331 call PauseUnit(UR.U, true)
332 call SetUnitPosition(UR.U, x, y)
333 call SetUnitFacing(UR.U, f)
334 call SetUnitOwner(UR.U, p, true)
335 call SetUnitState(UR.U, UNIT_STATE_MANA, GetUnitState(UR.U, UNIT_STATE_MAX_MANA) * MANA_FACTOR)
336 call SetUnitPathing(UR.U, true)
337 call PauseUnit(UR.U, false)
338 return UR.U
339 endif340 return CreateUnit(p, id, x, y, f)
341 endfunction342 343 function KillUnitEx takes unit u returns nothing
344 local unit d
345 if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then
346 set d = CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
347 call Trigger.evaluate(u, d, null)
348 call PrepareUnit(u)
349 call PlaceDummy(d, u)
350 call KillUnit(d)
351 call MoveUnit(u)
352 else353 call KillUnit(u)
354 endif355 set d = null
356 endfunction357 358 function RemoveUnitEx takes unit u returns nothing
359 if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then
360 call Trigger.evaluate(u, null, null)
361 call PrepareUnit(u)
362 call MoveUnit(u)
363 else364 call RemoveUnit(u)
365 endif366 endfunction367 368 function IsUnitDead takes unit u returns boolean
369 return IsUnitInGroup(u, Heaven)
370 endfunction371 372 function ReplaceDummy takes unit u returns unit
373 local unit d
374 if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then
375 set d = CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
376 call PlaceDummy(d, u)
377 call SetWidgetLife(d, GetWidgetLife(u))
378 call PrepareUnit(u)
379 call MoveUnit(u)
380 call SetUnitPathing(d, true)
381 else382 set d = null
383 endif384 return d385 endfunction386 387 function CreateDummy takes player p, integer id, real x, real y, real f returns unit
388 local unit u = CreateUnit(p, id, x, y, f)
389 call GroupAddUnit(Kill.corpse, u)
390 return u391 endfunction392 393 // TriggerRecyclefunctions. this trigger happens before the triggered unit is going to die...394 function TriggerRegisterAnyUnitRecycleEvent takes trigger t returns nothing
395 call Trigger.AddTrigger(t)
396 endfunction397 398 constant function GetRecycledUnit takes nothing returns unit
399 // returns the recycled unit...400 return Trigger.R
401 endfunction402 403 constant function GetRecycleDummyUnit takes nothing returns unit
404 // returns the dummy unit...405 return Trigger.D
406 endfunction407 408 constant function GetRecycleAttacker takes nothing returns unit
409 // returns the attacker who "kills" the recycled unit...410 return Trigger.A
411 endfunction412 413 constant function IsUnitDummy takes unit u returns boolean
414 // Checks if a unit can be recycled or not. Dummy units are not recycled, they're used as placeholder...415 return IsUnitInGroup(u, Kill.corpse)
416 endfunction417 418 constant function GetUnitsInHeaven takes nothing returns group
419 // Checks if a unit can be recycled or not. Dummy units are not recycled, they're used as placeholder...420 return Heaven421 endfunction422 423 // Damage detection system functions...424 function AddDamageCondition takes boolexpr b returns nothing
425 call TriggerAddCondition(DD.T, b)
426 endfunction427 428 function DoNonDetectableDamage takes unit u, widget t, real damage, boolean attack, boolean ranged, attacktype AT, damagetype DT, weapontype WT returns boolean
429 call DisableTrigger(DD.T)
430 call TimerStart(DD.Tm, 0., false, function DD.AntiLoop)
431 return UnitDamageTarget(u, t, damage, attack, ranged, AT, DT, WT)
432 endfunction433 434 endlibrary
Changelog
(14/10/2008):
- Small improvements in the core library
- Developed a better example (a tiny AOS, much better than AotZ, DoE and ToB together
)
(09/09/2008):
- Added a custom trigger event so the system can detect when a unit is about to be recycled
- Added some improvements
(31/12/2008):
- Other minor and final improvements (hopefully)
(06/01/2009):
- Now you can set up the system so it automatically set the heaven in a non playable area of the map (Thanks Troll brain for the idea)
- Added IsUnitDummy function to define if a unit is a placeholder or not.
- Added GetUnitsInHeaven function to retrieve the group that stores the units in Heaven. Useful to do operations with those units.
- Improved the test map with a leaderboard which shows data about units killed per player, units in map, units taht are being recycled, and the percentage of units recycled respect the total of active units.
(29/01/2009):
- Minor improvement in the DD code, now it's more efficient getting units in to the DD group.
(05/04/2009):
- Fixed an unit leak (thanks a lot Litany
) and improved the CreateUnitEx function.
(18/12/2017):
- Redefined some variables, fixed some bugs with KillUnitEx()
- Added demo map (an endless battle)
- Small improvements in the core library
- Developed a better example (a tiny AOS, much better than AotZ, DoE and ToB together
)(09/09/2008):
- Added a custom trigger event so the system can detect when a unit is about to be recycled
- Added some improvements
(31/12/2008):
- Other minor and final improvements (hopefully)
(06/01/2009):
- Now you can set up the system so it automatically set the heaven in a non playable area of the map (Thanks Troll brain for the idea)
- Added IsUnitDummy function to define if a unit is a placeholder or not.
- Added GetUnitsInHeaven function to retrieve the group that stores the units in Heaven. Useful to do operations with those units.
- Improved the test map with a leaderboard which shows data about units killed per player, units in map, units taht are being recycled, and the percentage of units recycled respect the total of active units.
(29/01/2009):
- Minor improvement in the DD code, now it's more efficient getting units in to the DD group.
(05/04/2009):
- Fixed an unit leak (thanks a lot Litany
) and improved the CreateUnitEx function.(18/12/2017):
- Redefined some variables, fixed some bugs with KillUnitEx()
- Added demo map (an endless battle)
Please download the test map for further testing. (last updated: 05/04/2009)