//-------------------------------------------------------------------------------------------------------
//|
//| Spell: The Dark Plague
//| Author: Anachron
//|
//| Description:
//| Releases a deadly cloud of bacteria to the target area, making units infected with the "dark plague".
//|
//| Units suffering from the "dark plague" have a limited sight area, attack and movespeed.
//| Caused by their low energy every fwe seconds they have a chance for a confusion (2s).
//|
//| Confused units can't attack and/or cast spells. Every time an unit gets confused, the chance of a sudden death increases drastically.
//|
//| Units dying from a sudden death will release the sickness to nearby units.
//|
//| Credits:
//| Magtheridon96 for RegisterPlayerUnitEvent
//| Bribe for SpellEffectEvent & Table
//| Rising_Dusk for GroupUtils
//|
//-------------------------------------------------------------------------------------------------------
library TheDarkPlague initializer onInit requires RegisterPlayerUnitEvent, StructUtils, GroupUtils, Table
globals
// Main spell ability for hero, inclusive buffID and casting order
private constant integer HERO_SPELL_CODE='Adkp'
private constant integer MAIN_SPELL_CODE='Adpb'
private constant integer MAIN_BUFF_CODE='Btdp'
private constant string MAIN_SPELL_ORDER="slow"
// Secondary spell ability for confusion casting, including buffID and casting order
private constant integer CONF_SPELL_CODE='Acnf'
private constant integer CONF_BUFF_CODE='Bcnf'
private constant string CONF_SPELL_ORDER="silence"
endglobals
// Formula to calculate spell area
private function GetSpellArea takes integer lvl returns real
return 150. + lvl * 50.
endfunction
// Formula to calculate spell duration
private function GetSpellDuration takes integer lvl returns real
return 15. + lvl * 5.
endfunction
// Formula to calculate confusion chance
private function GetSpellConfusionChance takes integer lvl returns real
return (15. + lvl * 5.) / 100.
endfunction
// Formula to calculate sudden death chance
private function GetSpellSuddenDeathChance takes integer lvl, integer times returns real
return (times * (8. + lvl * 3.)) / 100.
endfunction
struct dummySpell
public static real period = 2.5
implement PeriodicStruct
endstruct
struct spell extends dummySpell
private group unitHitGroup = null
private static group tempHitGroup = null
real duration = 0.
unit caster = null
integer level = 0
private static Table shocks = 0
private static unit dummy = null
// Cast spell on unit (For normal buff)
public method cast takes integer theAbility, unit theUnit, string theOrder returns nothing
call SetUnitOwner(thistype.dummy, GetOwningPlayer(.caster), false)
call UnitAddAbility(thistype.dummy, theAbility)
call SetUnitAbilityLevel(thistype.dummy, theAbility, .level)
call SetUnitX(thistype.dummy, GetUnitX(theUnit))
call SetUnitY(thistype.dummy, GetUnitY(theUnit))
call IssueTargetOrder(thistype.dummy, theOrder, theUnit)
call UnitRemoveAbility(thistype.dummy, theAbility)
endmethod
// Cast spell on location (For confusion buff)
public method castXY takes integer theAbility, real x, real y, string theOrder returns nothing
call SetUnitOwner(thistype.dummy, GetOwningPlayer(.caster), false)
call UnitAddAbility(thistype.dummy, theAbility)
call SetUnitAbilityLevel(thistype.dummy, theAbility, .level)
call SetUnitX(thistype.dummy, x)
call SetUnitY(thistype.dummy, y)
call IssuePointOrder(thistype.dummy, theOrder, x, y)
call UnitRemoveAbility(thistype.dummy, theAbility)
endmethod
public method isUnitHit takes unit theUnit returns boolean
return IsUnitInGroup(theUnit, .unitHitGroup)
endmethod
stub method unitHitCond takes unit theUnit returns boolean
local boolean triggerHit = true
set triggerHit = triggerHit and IsPlayerEnemy(GetOwningPlayer(.caster), GetOwningPlayer(theUnit))
set triggerHit = triggerHit and GetUnitState(theUnit, UNIT_STATE_LIFE) > 0.405
set triggerHit = triggerHit and not .isUnitHit(theUnit) and GetUnitAbilityLevel(theUnit, MAIN_BUFF_CODE) == 0
return triggerHit
endmethod
private method onUnitHit takes unit theUnit returns boolean
call .cast(MAIN_SPELL_CODE, theUnit, MAIN_SPELL_ORDER)
return true
endmethod
private static method delegateUnitHitCheck takes nothing returns boolean
local thistype this = thistype.SELF
local unit theUnit = GetFilterUnit()
local boolean hasHit = .unitHitCond(theUnit)
if hasHit then
call GroupAddUnit(.unitHitGroup, theUnit)
call .onUnitHit(theUnit)
endif
set theUnit = null
return false
endmethod
private method onSuddenDeath takes unit theUnit returns nothing
call UnitDamageTarget(.caster, theUnit, GetWidgetLife(theUnit), false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
call GroupRemoveUnit(.unitHitGroup, theUnit)
endmethod
private method checkSuddenDeath takes unit theUnit returns nothing
if GetHeroLevel(theUnit) > 0 then
return
endif
if GetRandomReal(0., 1.) <= GetSpellSuddenDeathChance(.level, .shocks[GetHandleId(theUnit)])then
call .onSuddenDeath(theUnit)
endif
endmethod
private method onConfusion takes unit theUnit returns nothing
call .castXY(CONF_SPELL_CODE, GetUnitX(theUnit), GetUnitY(theUnit), CONF_SPELL_ORDER)
set .shocks[GetHandleId(theUnit)] = .shocks[GetHandleId(theUnit)] +1
call .checkSuddenDeath(theUnit)
endmethod
private static method checkConfusion takes nothing returns nothing
local thistype this = thistype.SELF
local unit u = GetEnumUnit()
if GetUnitAbilityLevel(u, MAIN_BUFF_CODE) == 0 then
call GroupRemoveUnit(.unitHitGroup, u)
set u = null
return
endif
if GetUnitAbilityLevel(u, CONF_BUFF_CODE) > 0 then
set u = null
return
endif
if GetRandomReal(0., 1.) <= GetSpellConfusionChance(.level) then
call .onConfusion(u)
endif
set u = null
endmethod
public method onPeriod takes nothing returns boolean
if .duration > 0. then
set .duration = .duration - thistype.period
call ForGroup(.unitHitGroup, function thistype.checkConfusion)
return true
endif
return false
endmethod
private method onDestroy takes nothing returns nothing
if .unitHitGroup != null then
call ReleaseGroup(.unitHitGroup)
set .unitHitGroup = null
endif
if .shocks != 0 then
call .shocks.destroy()
endif
call .unregister()
endmethod
private static method onInit takes nothing returns nothing
set thistype.tempHitGroup = NewGroup()
set thistype.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'dumy', 0, 0, 0.)
call UnitAddAbility(thistype.dummy, 'Aloc')
endmethod
public static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set .caster = GetTriggerUnit()
set .level = GetUnitAbilityLevel(.caster, GetSpellAbilityId())
set .shocks = Table.create()
set .unitHitGroup = NewGroup()
set thistype.SELF = this
call GroupEnumUnitsInRange(thistype.tempHitGroup, GetSpellTargetX(), GetSpellTargetY(), GetSpellArea(.level), function thistype.delegateUnitHitCheck)
set .duration = GetSpellDuration(.level)
set .isAlive = true
set .isActive = true
call .register()
return this
endmethod
endstruct
private function onInit takes nothing returns nothing
call RegisterSpellEffectEvent(HERO_SPELL_CODE, function spell.create)
call FogEnable(false)
call FogMaskEnable(false)
endfunction
endlibrary