- Joined
- Feb 12, 2018
- Messages
- 12
Why a unit recycler is important?
A unit recycler is important because CreateUnit is a computationally heavy operation. If you could, instead of creating an unit, reuse an already created unit, it would greatly improve your map performance, reducing drastically the lag. Also, every time you call CreateUnit, the engine leaks 0.04KB, which can be a significant amount if you are creating a lot of units during the game duration.
What's wrong with already existing unit recyclers?
I had an already very complex map, which I needed to optimize very badly.
I decided then to use AGD's Unit Recycler ([Snippet] Unit Recycler). However, I found many limitations, such as:
- It uses bj_lastCreatedUnit at some parts of the code, which can break my code (e.g., if I call GetRecycledUnit while bj_lastCreatedUnit is pointing to another unit, it will now point to the recycled unit);
- It creates a new timer every time I call RecycleUnitDelayed. It will be very inefficient if I am recycling units a lot;
- It is not very safe: Auto-recycle dead units is a desired feature, but it will recycle any 'valid unit', whereas I want to auto-recycle only units which were created with GetRecycledUnit (it is safer and can coexist with existing maps as mine). Likewise, it'd be safer if I could only recycle a unit if the unit was created with GetRecycledUnit, otherwise, it should only remove the unit.
- It does not support caster dummies naturally. It would be desirable if the recycler could remove a unit's abilities before recycling, otherwise the following case scenario could happen: Create a caster dummy with thunderclap ability, recycle it, get previously recycled caster dummy, add another thunderclap ability, order it to cast thunderclap (an order id conflict will happen!).
- It uses PurgeandFire's Revive Unit ([Snippet] ReviveUnit), which can be bugged if, for example, a unit was killed by an event which is triggered by entering a region (when the revive dummy moves to the unit location, it will also be dead and thus won't revive the unit. I solved it by adding instant Reincarnation to the revive dummy).
- Units with Locust cannot be revived by PurgeandFire's Revive Unit, thus making the recycle system useless for dummies units (I solved it by removing locust before reviving and then adding it again after).
What I propose
In face of those limitations, I decided to create my own Unit Recycler, which I present below, intended to be used for modders that want to optimize a map in an already advanced stage but don't want to change much in their codes.
The three mains philosophies behind this unit recycler:
- Safety
It must coexist with already existing maps harmlessly until you start calling the library API;
- Efficiency
It must be fast, otherwise, a recycler system would be pointless;
- Flexibility
One shouldn't modify their code entirely to be able to benefit from the library.
Plus (full support for dummies):
- UnitAddAbilityEx which enables a unit's added abilities to be removed before recycling.
- Dummy units are also recycled, even if they are killed somehow. Also, if they are hidden and then killed, the system detects it and recycle them anyway, displaying and reviving them in the safe area (special care was taken for locust units, so the locust will be re-added after the unit is shown).
- A method to create dummies unit which has a special effect attached which will be destroyed when the unit is recycled.
Requirements
The same notes from AGD's Unit Recycle apply here:
How to use?
- Import all the library requirements to your map
- Make an empty script in your map
- Paste the library script below
- Set the desired configurations at the beginning of the script, as indicated in the code
- Import the attached dummy model
- Replace all occurrences of UnitAddAbility to UnitAddAbilityEx in your code (the system will still work if you don't, but the feature of removing abilities before recycling a unit will be disabled.)
- That's it!
Script
Sample usage 1 (Spawn units each 30s)
Sample Usage 2 (spell with dummies for special effects and caster dummy)
Changelog:
1.1:
- Removed hooks
- Struct initialization changed to module initialization
- Changed global variables naming convenction
A unit recycler is important because CreateUnit is a computationally heavy operation. If you could, instead of creating an unit, reuse an already created unit, it would greatly improve your map performance, reducing drastically the lag. Also, every time you call CreateUnit, the engine leaks 0.04KB, which can be a significant amount if you are creating a lot of units during the game duration.
What's wrong with already existing unit recyclers?
I had an already very complex map, which I needed to optimize very badly.
I decided then to use AGD's Unit Recycler ([Snippet] Unit Recycler). However, I found many limitations, such as:
- It uses bj_lastCreatedUnit at some parts of the code, which can break my code (e.g., if I call GetRecycledUnit while bj_lastCreatedUnit is pointing to another unit, it will now point to the recycled unit);
- It creates a new timer every time I call RecycleUnitDelayed. It will be very inefficient if I am recycling units a lot;
- It is not very safe: Auto-recycle dead units is a desired feature, but it will recycle any 'valid unit', whereas I want to auto-recycle only units which were created with GetRecycledUnit (it is safer and can coexist with existing maps as mine). Likewise, it'd be safer if I could only recycle a unit if the unit was created with GetRecycledUnit, otherwise, it should only remove the unit.
- It does not support caster dummies naturally. It would be desirable if the recycler could remove a unit's abilities before recycling, otherwise the following case scenario could happen: Create a caster dummy with thunderclap ability, recycle it, get previously recycled caster dummy, add another thunderclap ability, order it to cast thunderclap (an order id conflict will happen!).
- It uses PurgeandFire's Revive Unit ([Snippet] ReviveUnit), which can be bugged if, for example, a unit was killed by an event which is triggered by entering a region (when the revive dummy moves to the unit location, it will also be dead and thus won't revive the unit. I solved it by adding instant Reincarnation to the revive dummy).
- Units with Locust cannot be revived by PurgeandFire's Revive Unit, thus making the recycle system useless for dummies units (I solved it by removing locust before reviving and then adding it again after).
What I propose
In face of those limitations, I decided to create my own Unit Recycler, which I present below, intended to be used for modders that want to optimize a map in an already advanced stage but don't want to change much in their codes.
The three mains philosophies behind this unit recycler:
- Safety
It must coexist with already existing maps harmlessly until you start calling the library API;
- Efficiency
It must be fast, otherwise, a recycler system would be pointless;
- Flexibility
One shouldn't modify their code entirely to be able to benefit from the library.
Plus (full support for dummies):
- UnitAddAbilityEx which enables a unit's added abilities to be removed before recycling.
- Dummy units are also recycled, even if they are killed somehow. Also, if they are hidden and then killed, the system detects it and recycle them anyway, displaying and reviving them in the safe area (special care was taken for locust units, so the locust will be re-added after the unit is shown).
- A method to create dummies unit which has a special effect attached which will be destroyed when the unit is recycled.
Requirements
The same notes from AGD's Unit Recycle apply here:
- This only supports non-hero units and units which leave a corpse (those units whose death type is "Can raise, does decay"). So if you want to make a unit recyclable, set its death type in the Object Editor to "Can raise, does decay".
- When retrieving/recycling a unit, this does not fire a unit index/deindex event
How to use?
- Import all the library requirements to your map
- Make an empty script in your map
- Paste the library script below
- Set the desired configurations at the beginning of the script, as indicated in the code
- Import the attached dummy model
- Replace all occurrences of UnitAddAbility to UnitAddAbilityEx in your code (the system will still work if you don't, but the feature of removing abilities before recycling a unit will be disabled.)
- That's it!
Script
JASS:
library SafeUnitRecycler requires Alloc, UnitDex, Table
/**
Safe Unit Recycler - v1.1
Created by PicoleDeLimao 10/02/18
Last update 16/02/18
[email protected]
Requirements:
- Alloc => https://www.hiveworkshop.com/threads/snippet-alloc.192348/
- UnitDex => https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
- Table => https://www.hiveworkshop.com/threads/snippet-new-table.188084/
Installation:
1) Import Veroxian's dummy model (https://www.hiveworkshop.com/attachments/dummy-rar.68985/)
2) Replace all occurrencies of UnitAddAbility to UnitAddAbilityEx.
(The system will still work if you don't, but the feature of removing added abilities before recycling will be disabled.)
3) Copy the library dependencies, then remove the ! from //! below after saving and restarting world editor.
*/
//! external ObjectMerger w3a AHre URiz anam "Dummy Resurrection" aher 0 acat "" atat "" Hre1 1 1 aare 1 0 aran 1 0 acdn 1 0 amcs 1 0 atar 1 "Air,Dead,Enemy,Friend,Neutral,Ground"
//! external ObjectMerger w3a AOre URiv anam "Dummy Revive" aeat "" acas 1 0 acdn 1 0 aher 0 Ore1 1 0
//! external ObjectMerger w3u nech eRiz unam "Dummy" uabi "Aloc,Avul" ucbs 0 ucpt 0 umdl "war3mapImported\dummy.mdx" usca "1.0" ushu "None" umvh 0 umvs 0 ufoo 0 uhpr 1000 umpi 100000 umpm 100000 umpr 1000 umvt "Fly" ucol 0 umxp 0 umxr 0 umvr 3 uble 0
/*
Description:
This is my unit recycler.
It is efficient because it only uses a single timer for delayed recycles.
Also it is made to be very safe. It will be totally harmless to already existing maps,
until you start calling the library API (i.e., creating units with GetUnit).
What does it support?
- Recycle considering facing angle (but you can turn it off for some units - e.g., caster dummies -);
- Safe recycles (only recycle units created with GetUnit);
- Delayed recycles;
- Auto recycle dead units (but only the ones that were created with GetUnit);
- Added abilities will be removed when the unit is recycled (useful for caster dummies);
API:
GetUnit takes player p, integer raw, real x, real y, real facing returns unit
It is meant to be a substitute to 'CreateUnit´ for units that you want to recycle later.
GetDummy takes player p, string sfx, real x, real y, real facing returns unit
Create a recyclable dummy which has sfx attached
GetInvisibleDummy takes player p, real x, real y returns unit
Create a recyclable invisible dummy
RecycleUnit takes unit u returns boolean
Recycle a unit (it will only work if the unit was created with GetUnit, otherwise it will only remove it).
It will return true case the recycle was successful.
RecycleUnitDelayed takes unit u, real delay returns nothing
Recycle a unit after some duration.
AddUnitsToStock takes integer raw, real angle, integer number returns nothing
Stock units to recycle later.
UnitAddAbilityEx takes unit u, integer raw returns boolean
It is meant to be a substitute to 'UnitAddAbility' so units can have their added abilities removed before recycling.
**/
//=============================================================================================
// INIT CONFIGURATIONS
//=============================================================================================
globals
private real SAFE_AREA_X = -1 // X position of the location where recycled units will be sent to
private real SAFE_AREA_Y = -1 // Y position of the location where recycled units will be sent to
// If you set SAFE_AREA_X or SAFE_AREA_Y to -1, it will be set to a location besides the camera bounds
private constant real FACING_THRESHOLD_DEGREES = 20 // Max angle difference which will be tolerated between the requested facing angle and a recycled unit's facing angle
private constant boolean AUTO_RECYCLE_DEAD_UNITS = true // it will only recycle units which were created with GetUnit
private constant real DELAY_AFTER_DEATH = 3.0 // time after death for the unit to be recycled (don't set it higher to the game decay time - Check game constants -)
private constant integer DUMMY_ID = 'eRiz'
private constant integer REVIVE_DUMMY_RES_ABILITY = 'URiz'
private constant integer REVIVE_DUMMY_SELF_RES_ABILITY = 'URiv'
endglobals
// Units which can be recycled
private function ValidUnitFilter takes unit u returns boolean
return u != null and not IsUnitIllusion(u) and IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false
endfunction
// Units which does not need to be facing any particular angle when recycled
private function IgnoreFacingFilter takes unit u returns boolean
return false
endfunction
// Actions which will be called before recycling a unit
private function BeforeRecycling takes unit u returns nothing
call PauseUnit(u, true)
call UnitRemoveBuffs(u, true, true)
call SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_MAX_LIFE))
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
call SetUnitInvulnerable(u, true)
call SetUnitTimeScale(u, 0)
call UnitResetCooldown(u)
endfunction
// Actions which will be called after a unit is recycled
private function AfterRecycled takes unit u returns nothing
call PauseUnit(u, false)
call SetUnitTimeScale(u, 1.0)
call SetUnitAnimation(u, "stand")
call SetUnitInvulnerable(u, false)
endfunction
//=============================================================================================
// END CONFIGURATIONS
//=============================================================================================
private struct LinkedList extends array
implement Alloc
thistype next
thistype previous
thistype tail
integer data
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.next = this
set this.previous = this
set this.tail = this
return this
endmethod
method add takes integer value returns thistype
local thistype temp = thistype.create()
set this.tail.data = value
set this.tail.next = temp
set temp.previous = this.tail
set this.tail = temp
return this
endmethod
method remove takes nothing returns thistype
local thistype temp
if this != this.next then
if this.previous == this then
set temp = this.next
if temp == this.tail then
set temp.data = 0
call temp.deallocate()
set this.tail = this
set this.next = this
else
set this.data = temp.data
set this.next = temp.next
set this.next.previous = this
set temp.data = 0
call temp.deallocate()
endif
return this
else
set temp = this.next
set this.previous.next = temp
set temp.previous = this.previous
set this.data = 0
call this.deallocate()
return temp
endif
endif
return this
endmethod
method destroy takes nothing returns nothing
local thistype node = this.tail
local thistype temp
loop
exitwhen node == this
set temp = node.previous
call node.deallocate()
set node = temp
endloop
call this.deallocate()
endmethod
endstruct
private struct SpecialEffect extends array
implement Alloc
private effect e
static method create takes unit u, string sfx returns thistype
local thistype this = thistype.allocate()
set this.e = AddSpecialEffectTarget(sfx, u, "origin")
return this
endmethod
method destroy takes nothing returns nothing
call DestroyEffect(this.e)
set this.e = null
call this.deallocate()
endmethod
endstruct
private struct TimedUnit extends array
implement Alloc
unit u
real duration
static method create takes unit u, real duration returns thistype
local thistype this = thistype.allocate()
set this.u = u
set this.duration = duration
return this
endmethod
method destroy takes nothing returns nothing
set this.u = null
call this.deallocate()
endmethod
endstruct
globals
private Table AvailableUnits // table which has as key the unit type id and as value a linked list for units of this unit type id facing different angles
private Table RecyclableUnits // table which has as key the unit id and as value a linked list for the added abilities
private Table LocustUnits // units which have locust (it is necessary as a workaround for revive ability)
private Table DummyUnits // units which are dummies
private LinkedList UnitsToRecycle // units which will be recycle delayed
private unit ReviveDummy // unit which is used to revive dead units
endglobals
private module InitGlobalVariablesM
private static method onInit takes nothing returns nothing
local rect bounds = GetWorldBounds()
set AvailableUnits = Table.create()
set RecyclableUnits = Table.create()
set LocustUnits = Table.create()
set DummyUnits = Table.create()
set UnitsToRecycle = LinkedList.create()
if SAFE_AREA_X == -1 or SAFE_AREA_Y == -1 then
set SAFE_AREA_X = 0
set SAFE_AREA_Y = GetRectMaxY(bounds) + 1000
endif
set ReviveDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, SAFE_AREA_X, SAFE_AREA_Y, 0)
call UnitAddAbility(ReviveDummy, REVIVE_DUMMY_RES_ABILITY)
call UnitAddAbility(ReviveDummy, REVIVE_DUMMY_SELF_RES_ABILITY)
call SetUnitPathing(ReviveDummy, false)
call RemoveRect(bounds)
set bounds = null
endmethod
endmodule
private struct InitGlobalVariables extends array
implement InitGlobalVariablesM
endstruct
private function ReviveUnitEx takes unit u returns boolean
local boolean success
call UnitResetCooldown(ReviveDummy)
call SetUnitX(ReviveDummy, GetUnitX(u))
call SetUnitY(ReviveDummy, GetUnitY(u))
set success = IssueImmediateOrderById(ReviveDummy, 852094)
call SetUnitX(ReviveDummy, SAFE_AREA_X)
call SetUnitY(ReviveDummy, SAFE_AREA_Y)
return success
endfunction
private function RemoveRecyclableUnit takes unit u returns nothing
local integer id = GetUnitId(u)
local LinkedList node
if RecyclableUnits.has(id) then
set node = RecyclableUnits[id]
call node.destroy()
call RecyclableUnits.remove(id)
endif
endfunction
private function MinDistAngles takes real a1, real a2 returns real
local real diff = a1 - a2
return RAbsBJ(ModuloReal(diff + 180, 360) - 180)
endfunction
function GetUnit takes player p, integer raw, real x, real y, real angle returns unit
local LinkedList node
local unit u
if AvailableUnits.has(raw) then
set node = AvailableUnits[raw]
loop
exitwhen node == node.next
set u = GetUnitById(node.data)
if GetWidgetLife(u) > 0.405 and (angle < 0 or IgnoreFacingFilter(u) or MinDistAngles(GetUnitFacing(u), angle) <= FACING_THRESHOLD_DEGREES) then
if angle >= 0 then
call SetUnitFacing(u, angle)
endif
call SetUnitOwner(u, p, true)
call AfterRecycled(u)
if GetUnitAbilityLevel(u, 'Aloc') == 0 then
call SetUnitPosition(u, x, y)
else
call SetUnitX(u, x)
call SetUnitY(u, y)
endif
if LocustUnits.has(node.data) then
call UnitAddAbility(u, 'Aloc')
call LocustUnits.remove(node.data)
endif
//debug call BJDebugMsg("Found recycled unit " + GetUnitName(u) + "(" + I2S(node.data) + ") with facing = " + I2S(R2I(GetUnitFacing(u))) + ".")
call node.remove()
return u
endif
set node = node.next
endloop
endif
set u = CreateUnit(p, raw, x, y, angle)
if ValidUnitFilter(u) then
set RecyclableUnits[GetUnitId(u)] = LinkedList.create()
endif
debug call BJDebugMsg("Creating new unit " + GetUnitName(u) + "(" + I2S(GetUnitId(u)) + ") with facing = " + I2S(R2I(angle)) + ".")
return u
endfunction
function GetDummy takes player p, string sfx, real x, real y, real facing returns unit
local unit u = GetUnit(p, DUMMY_ID, x, y, facing)
local SpecialEffect e = SpecialEffect.create(u, sfx)
set DummyUnits[GetUnitId(u)] = e
call SetUnitScale(u, 1, 1, 1)
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitFlyHeight(u, 0, 0)
return u
endfunction
function GetInvisibleDummy takes player p, real x, real y returns unit
local unit u = GetUnit(p, DUMMY_ID, x, y, -1)
call SetUnitScale(u, 1, 1, 1)
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitFlyHeight(u, 0, 0)
return u
endfunction
function UnitAddAbilityEx takes unit u, integer abilityId returns boolean
local integer id = GetUnitId(u)
local LinkedList node
if abilityId != 'Aloc' and RecyclableUnits.has(id) then
set node = RecyclableUnits[id]
call node.add(abilityId)
endif
return UnitAddAbility(u, abilityId)
endfunction
private function RemoveUnitAbilities takes unit u, integer id returns nothing
local LinkedList node
if RecyclableUnits.has(id) then
set node = RecyclableUnits[id]
loop
exitwhen node == node.next
call UnitRemoveAbility(u, node.data)
set node = node.remove()
endloop
endif
endfunction
function RecycleUnit takes unit u returns boolean
local integer id = GetUnitId(u)
local SpecialEffect e
local LinkedList node
if DummyUnits.has(id) then
set e = DummyUnits[id]
call e.destroy()
call DummyUnits.remove(id)
endif
if not ValidUnitFilter(u) then
call RemoveUnit(u)
debug call BJDebugMsg("Tring to recycle an invalid unit " + GetUnitName(u) + "(" + I2S(id) + ").")
elseif not RecyclableUnits.has(id) then
call RemoveUnit(u)
debug call BJDebugMsg("Trying to recycle a non-recyclable unit " + GetUnitName(u) + "(" + I2S(id) + ").")
elseif AvailableUnits.has(id) then
debug call BJDebugMsg("Attempting to recycle an already recycled unit " + GetUnitName(u) + "(" + I2S(id) + ").")
else
call SetUnitOwner(u, Player(PLAYER_NEUTRAL_PASSIVE), false)
call SetUnitPosition(u, SAFE_AREA_X, SAFE_AREA_Y)
call ShowUnit(u, false)
if GetUnitAbilityLevel(u, 'Aloc') > 0 then
call UnitRemoveAbility(u, 'Aloc')
set LocustUnits[id] = 1
endif
call ShowUnit(u, true)
call SetUnitInvulnerable(u, false)
if GetWidgetLife(u) < 0.405 and not ReviveUnitEx(u) then
debug call BJDebugMsg("Could not revive dead unit to recycle " + GetUnitName(u) + "(" + I2S(id) + ").")
call RemoveRecyclableUnit(u)
call RemoveUnit(u)
return false
endif
call BeforeRecycling(u)
call RemoveUnitAbilities(u, id)
call SetUnitX(u, SAFE_AREA_X)
call SetUnitY(u, SAFE_AREA_Y)
if AvailableUnits.has(GetUnitTypeId(u)) then
set node = AvailableUnits[GetUnitTypeId(u)]
else
set node = LinkedList.create()
set AvailableUnits[GetUnitTypeId(u)] = node
endif
call node.add(id)
//debug call BJDebugMsg("Recycled " + GetUnitName(u) + "(" + I2S(id) + ").")
return true
endif
return false
endfunction
function RecycleUnitDelayed takes unit u, real delay returns nothing
local TimedUnit t = TimedUnit.create(u, delay)
call UnitsToRecycle.add(t)
endfunction
private struct RecycleDelayedUnits extends array
private static method checkUnits takes nothing returns nothing
local TimedUnit t
local LinkedList node = UnitsToRecycle
loop
exitwhen node == node.next
set t = node.data
set t.duration = t.duration - 0.05
if t.duration <= 0 then
if t.u != null then
call RecycleUnit(t.u)
endif
call t.destroy()
set node = node.remove()
else
set node = node.next
endif
endloop
endmethod
private static method onInit takes nothing returns nothing
call TimerStart(CreateTimer(), 0.05, true, function thistype.checkUnits)
endmethod
endstruct
function AddUnitsToStock takes integer raw, real angle, integer number returns nothing
local integer i = 0
loop
set i = i + 1
exitwhen i > number
call RecycleUnit(GetUnit(Player(PLAYER_NEUTRAL_PASSIVE), raw, SAFE_AREA_X, SAFE_AREA_Y, angle))
endloop
endfunction
static if AUTO_RECYCLE_DEAD_UNITS then
private struct RecycleDeadUnits extends array
private static LinkedList DeadUnitsToRecycle
private static method checkUnits takes nothing returns nothing
local TimedUnit t
local LinkedList node = DeadUnitsToRecycle
loop
exitwhen node == node.next
set t = node.data
set t.duration = t.duration - 0.05
if t.duration <= 0 then
if t.u != null then
call RecycleUnit(t.u)
endif
call t.destroy()
set node = node.remove()
else
set node = node.next
endif
endloop
endmethod
private static method addDeadUnitCondition takes nothing returns boolean
local integer id
local SpecialEffect e
if ValidUnitFilter(GetTriggerUnit()) then
set id = GetUnitId(GetTriggerUnit())
if DummyUnits.has(id) then
set e = DummyUnits[id]
call e.destroy()
call DummyUnits.remove(id)
endif
if RecyclableUnits.has(id) then
return true
endif
endif
return false
endmethod
private static method addDeadUnitAction takes nothing returns nothing
local TimedUnit t = TimedUnit.create(GetTriggerUnit(), DELAY_AFTER_DEATH)
call DeadUnitsToRecycle.add(t)
endmethod
private static method onInit takes nothing returns nothing
local trigger triggerAddDeadUnit = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(triggerAddDeadUnit, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(triggerAddDeadUnit, function thistype.addDeadUnitCondition)
call TriggerAddAction(triggerAddDeadUnit, function thistype.addDeadUnitAction)
set DeadUnitsToRecycle = LinkedList.create()
call TimerStart(CreateTimer(), 0.05, true, function thistype.checkUnits)
endmethod
endstruct
endif
endlibrary
Sample usage 1 (Spawn units each 30s)
JASS:
struct SpawnUnits extends array
private static constant integer UNIT_TO_SPAWN = 'hsor'
private static constant integer NUM_UNITS_TO_SPAWN = 5
private static constant integer SPAWN_LOCATION_X = 0
private static constant integer SPAWN_LOCATION_Y = 0
private static method spawnUnits takes nothing returns nothing
local unit u
local integer i = 0
loop
set i = i + 1
exitwhen i > NUM_UNITS_TO_SPAWN
set u = GetUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), UNIT_TO_SPAWN, SPAWN_LOCATION_X, SPAWN_LOCATION_Y, 270.0)
call IssuePointOrder(u, "attack", SPAWN_LOCATION_X, SPAWN_LOCATION_Y)
endloop
set u = null
endmethod
private static method onInit takes nothing returns nothing
call AddUnitsToStock(UNIT_TO_SPAWN, 270.0, NUM_UNITS_TO_SPAWN)
call TimerStart(CreateTimer(), 30.0, true, function thistype.spawnUnits)
endmethod
endstruct
Sample Usage 2 (spell with dummies for special effects and caster dummy)
JASS:
struct DeepImpact extends array
implement Alloc
private static method condition takes nothing returns boolean
return GetSpellAbilityId() == 'AHfs'
endmethod
private static method action takes nothing returns nothing
local integer i = 0
local real x
local real y
loop
exitwhen i >= 10
set x = GetSpellTargetX() + Cos(Deg2Rad(36 * i)) * 250
set y = GetSpellTargetY() + Sin(Deg2Rad(36 * i)) * 250
set bj_lastCreatedUnit = GetDummy(GetTriggerPlayer(), "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl", x, y, 36 * i)
call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', 1.0 + GetRandomReal(0, 1))
set i = i + 1
endloop
set bj_lastCreatedGroup = CreateGroup()
call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetSpellTargetX(), GetSpellTargetY(), 300, null)
loop
set bj_lastCreatedUnit = FirstOfGroup(bj_lastCreatedGroup)
exitwhen bj_lastCreatedUnit == null
if GetWidgetLife(bj_lastCreatedUnit) > 0.405 and IsUnitEnemy(bj_lastCreatedUnit, GetTriggerPlayer()) then
call UnitDamageTarget(GetTriggerUnit(), bj_lastCreatedUnit, 100 * GetUnitAbilityLevel(GetTriggerUnit(), GetSpellAbilityId()), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
endif
call GroupRemoveUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
endloop
call DestroyGroup(bj_lastCreatedGroup)
set bj_lastCreatedUnit = GetInvisibleDummy(GetTriggerPlayer(), GetSpellTargetX(), GetSpellTargetY())
call UnitAddAbilityEx(bj_lastCreatedUnit, 'A000')
call IssueImmediateOrder(bj_lastCreatedUnit, "thunderclap")
call RecycleUnitDelayed(bj_lastCreatedUnit, 1.0)
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function thistype.condition)
call TriggerAddAction(t, function thistype.action)
endmethod
endstruct
Changelog:
1.1:
- Removed hooks
- Struct initialization changed to module initialization
- Changed global variables naming convenction
Attachments
Last edited: