library Camouflage /*
*/uses /*
*/SpellFramework /* https://www.hiveworkshop.com/threads/325448/
*/UnitDex /* https://www.hiveworkshop.com/threads/248209/
*/RegisterPlayerUnitEvent /* https://www.hiveworkshop.com/threads/250266/
*/
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
private module StealthConfiguration
static constant integer SPELL_ABILITY_ID = 'A00U' //the rawcode of the spell
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
static constant real SPELL_PERIOD = 1./32.
static constant integer BUFF_ID = 'B00D' //the rawcode of the buff
static constant integer INVIS_ID = 'A00S' //the rawcode of the invis
static constant integer DUMMY_ID = 'h001' //the rawcode of the dummy
static constant integer DUMMY_SPELL_ID = 'A00T' //the rawcode of ability added to dummy to apply buff
static constant player DUMMY_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE) //owner of dummy caster
static constant string SHIMMER_SFX_MODEL = ""
static constant integer TRANSPARENCY = 110
static constant method Duration takes integer level returns real
return 0. + 10.*level
endmethod
static constant method HeroDetectionRange takes integer level returns real
return 500. + 0.*level // returns the range the spell will affect
endmethod
static constant method BackInvis takes integer level returns real
return 1. + 0.*level
endmethod
static constant method ShimmerCooldown takes integer level returns real
return 1. + 0.*level
endmethod
static method Targets takes unit target, unit caster returns boolean
return UnitAlive(target) and/*
*/ IsUnitEnemy(target, GetOwningPlayer(caster)) and/*
*/ IsUnitType(target, UNIT_TYPE_HERO) and/*
*/ IsUnitVisible(target, GetOwningPlayer(caster)) // is caster in enemy line of sight
endmethod
static method OnCasterShimmer takes unit caster returns nothing
/*
* <What shimmer does>
*/
call DestroyEffect(AddSpecialEffect(SHIMMER_SFX_MODEL, GetUnitX(caster), GetUnitY(caster)))
endmethod
static method OnCasterRevealStart takes unit caster returns nothing
/*
* What happens when the caster is revealed
* For example, you could create additional special effects for the caster here
*/
endmethod
static method OnCasterRevealEnd takes unit caster returns nothing
/*
* What happens when the caster becomes invisible again after being revealed
*/
endmethod
endmodule
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
native UnitAlive takes unit u returns boolean
globals
private group enumGroup = CreateGroup()
endglobals
//===========================================================================
private struct Camouflage extends array
implement StealthConfiguration
private static unit dummy
private static unit enumUnit
private static unit caster
private player p
private real duration
private real range
private real shimmerCooldown
private real backInvis
private integer level
private boolean seen
private method onSpellStart takes nothing returns thistype
set this = GetUnitId(Spell.triggerUnit)
set this.level = Spell.level
set this.p = Spell.triggerPlayer
set this.range = HeroDetectionRange(Spell.level)
set this.duration = Duration(Spell.level)
set this.seen = false
set this.shimmerCooldown = 0.
set this.backInvis = 0.
if IssueTargetOrder(dummy, "acidbomb", Spell.triggerUnit) then // add buff
call UnitAddAbility(Spell.triggerUnit, INVIS_ID) // add invis
return this // add 'this' instance to periodic timer
endif
return 0 // [ERROR] issued dummy order failed, don't run periodic operations
endmethod
private method onSpellPeriodic takes nothing returns boolean
local real i = 0
local real cX = GetUnitX(caster)
local real cY = GetUnitY(caster)
local real dx
local real dy
local boolean seen = false
set caster = GetUnitById(this)
/*
* If there is at least 1 hero within detection range, mark the caster as 'seen'
*/
call GroupEnumUnitsInRange(enumGroup, cX, cY, this.range, null)
loop
set enumUnit = FirstOfGroup(enumGroup)
exitwhen enumUnit == null
if Targets(enumUnit, caster) then
set seen = true
endif
call GroupRemoveUnit(enumGroup, enumUnit)
endloop
/*
* Keep track of the moment when the 'seen' status of the caster changes,
* i.e., the moment when the caster becomes seen, or unseen
*/
if seen != this.seen then
set this.seen = seen
if seen then
/*
* caster has just been seen by at least 1 enemy hero
*/
call UnitRemoveAbility(caster, INVIS_ID)
call SetUnitVertexColor(caster, 255, 255, 255, TRANSPARENCY)
else
/*
* caster has just been unseen by ALL enemy heroes
*/
call SetUnitVertexColor(caster, 255, 255, 255, 255)
call UnitAddAbility(caster, INVIS_ID)
set this.backInvis = BackInvis(this.level)
static if thistype.OnCasterRevealStart.exists then
call OnCasterRevealStart(caster)
endif
endif
endif
/*
* update spell duration
*/
set this.duration = this.duration - SPELL_PERIOD
if this.duration < 0.00 then
call UnitRemoveAbility(caster, BUFF_ID)
return true // duration ended: end periodic operations
endif
/*
* update shimmer cooldown
*/
if this.shimmerCooldown > 0. then
set this.shimmerCooldown = this.shimmerCooldown - SPELL_PERIOD
endif
if this.backInvis > 0. then
/*
* caster is revealed
*/
set this.backInvis = this.backInvis - SPELL_PERIOD
if this.backInvis <= 0. then
/*
* reveal ended: add buff again
*/
call IssueTargetOrder(dummy, "acidbomb", caster)
static if thistype.OnCasterRevealEnd.exists then
call OnCasterRevealEnd(caster)
endif
return false // do not end periodic operations
endif
endif
/*
* if the caster no longer has the buff even when not revealed, end periodic operations
*/
return GetUnitAbilityLevel(GetUnitById(this), BUFF_ID) == 0 and this.backInvis <= 0.
endmethod
private method onSpellEnd takes nothing returns nothing
local unit caster = GetUnitById(this)
call SetUnitVertexColor(caster, 255, 255, 255, 255)
if GetUnitAbilityLevel(caster , INVIS_ID) > 0 then
call UnitRemoveAbility(caster, INVIS_ID)
endif
// Wash leaks
set caster = null
endmethod
implement SpellEvent // IMPORTANT so that the above methods automatically run when the correct spell is cast
//===========================================================================
private static method OnOtherSpellCast takes nothing returns boolean
return GetSpellAbilityId() != SPELL_ABILITY_ID and UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
endmethod
private static method OnCasterItemUse takes nothing returns boolean
return UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
endmethod
private static method OnCasterAttacks takes nothing returns boolean
return UnitRemoveAbility(GetAttacker(), BUFF_ID)
endmethod
private static method OnCasterDamaged takes nothing returns boolean
local thistype this
if GetUnitAbilityLevel(udg_DamageEventTarget, BUFF_ID) > 0 then
call UnitRemoveAbility(udg_DamageEventTarget, BUFF_ID)
set this = GetUnitId(udg_DamageEventTarget)
if this.shimmerCooldown <= 0. then
set this.shimmerCooldown = ShimmerCooldown(this.level)
call OnCasterShimmer(udg_DamageEventTarget)
endif
endif
return false
endmethod
//===========================================================================
private static method onInit takes nothing returns nothing
// other events
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1.00)
call TriggerAddCondition(t, Condition(function thistype.OnCasterDamaged))
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.OnOtherSpellCast)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function thistype.OnCasterItemUse)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.OnCasterAttacks)
// setting globals
set dummy = CreateUnit(DUMMY_PLAYER, DUMMY_ID, 0., 0., 270.) // Only need to create dummy caster once
call UnitAddAbility(dummy, DUMMY_SPELL_ID)
set t = null
// preloading effects
endmethod
endstruct
endlibrary