/*
* System for League of Legends-style camouflage.
* https://leagueoflegends.fandom.com/wiki/Stealth#Camouflage
* Basically, you are invisible while X range away from enemies.
* UPDATES_PER_TICK constant is used to control how many units the system processes per tick, meaning some control over performance impact of system, if many units are registered.
*
* setSystemOn is used to globally turn this system on/off. Turning it off reveals any unit that was invisible (removing the "Undead - Ghost" ability, if they had it).
*/
library CamouflageSystem
globals
private constant integer CAMOUFLAGE_INVIS_ABILITY = 'Agho' //"Undead - Ghost"-based ability for being able to cast while invis
private constant real BASE_RANGE = 500.0
private constant integer UPDATES_PER_TICK = 8 //If less that this many units have this ability, it does not matter. If more than this many units have this ability, only so many will be updated per tick.
private constant real TICK_RATE = 0.03125000
endglobals
struct Camouflage
private static thistype array camouflagedUnits
private static boolean turnedOn = true
private static integer k = -1
private static integer loopIndex = 0
private static timer t = CreateTimer()
private unit source
private real range
method makeVisible takes nothing returns nothing
call UnitRemoveAbility(source, CAMOUFLAGE_INVIS_ABILITY)
endmethod
method makeInvis takes nothing returns nothing
call UnitAddAbility(source, CAMOUFLAGE_INVIS_ABILITY)
endmethod
method isInvis takes nothing returns boolean
return GetUnitAbilityLevel(source, CAMOUFLAGE_INVIS_ABILITY) > 0
endmethod
method toggleInvis takes nothing returns nothing
if isInvis() then
call makeVisible()
else
call makeInvis()
endif
endmethod
public static method setInvisibleForAll takes boolean invis returns nothing
local integer i = 0
if invis then
loop
exitwhen i > k
call camouflagedUnits[i].makeInvis()
set i = i + 1
endloop
else
loop
exitwhen i > k
call camouflagedUnits[i].makeVisible()
set i = i + 1
endloop
endif
endmethod
public method getRange takes nothing returns real
return range
endmethod
public method setRange takes real newRange returns nothing
set range = newRange
endmethod
method update takes nothing returns nothing
local group unitsInRange = CreateGroup()
local player owner = GetOwningPlayer(source)
local unit u
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local boolean hasEnemiesInRange = false
local boolean isInvis = isInvis()
call GroupEnumUnitsInRange(unitsInRange, x, y, range, null)
loop
set u = FirstOfGroup(unitsInRange)
exitwhen u == null
if UnitAlive(u) and not IsUnitAlly(u, owner) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
set hasEnemiesInRange = true
exitwhen true
endif
call GroupRemoveUnit(unitsInRange,u)
endloop
call DestroyGroup (unitsInRange)
set u = null
set owner = null
set unitsInRange = null
if hasEnemiesInRange and isInvis then
//call BJDebugMsg("invis Unit turned visible")
call makeVisible()
elseif not isInvis and not hasEnemiesInRange then
//call BJDebugMsg("Visible Unit turned invis")
call makeInvis()
endif
endmethod
static method onPeriod takes nothing returns nothing
local thistype this
local integer processed = 0
if loopIndex > k then
if k == -1 then
call PauseTimer(t) //Nothing to update!
endif
set loopIndex = 0
else
loop
exitwhen processed == UPDATES_PER_TICK or loopIndex > k
set processed = processed + 1
set this = camouflagedUnits[loopIndex]
if UnitAlive(source) then
call update()
else
set loopIndex = remove(loopIndex)
endif
set loopIndex = loopIndex + 1
endloop
endif
endmethod
public static method setSystemOn takes boolean newTurnedOn returns nothing
if not (turnedOn == newTurnedOn) then
if turnedOn and not newTurnedOn then
set turnedOn = false
call PauseTimer(t)
call setInvisibleForAll(false)
else
set turnedOn = true
if k > -1 then
call TimerStart(t, TICK_RATE, true, function thistype.onPeriod)
endif
endif
endif
endmethod
static method register takes unit u returns nothing
local thistype this = allocate()
set .source = u
set .range = BASE_RANGE
set k = k + 1
set camouflagedUnits[k] = this
if turnedOn and k == 0 then
call TimerStart(t, TICK_RATE, true, function thistype.onPeriod)
endif
endmethod
static method remove takes integer i returns integer
local thistype this = camouflagedUnits[i]
set camouflagedUnits[i] = camouflagedUnits[k]
set k = k - 1
set source = null
call .deallocate()
if k == -1 then
call PauseTimer(t)
endif
return i - 1
endmethod
endstruct
endlibrary