// CONFIGURATION
// Constants
// Main Spell raw code
constant function MS_GetSpellID takes nothing returns integer
return 'A000'
endfunction
// Dummy raw code
constant function MS_DummyID takes nothing returns integer
return 'h001'
endfunction
// Dummy Lightning Spell raw code
constant function MS_LightningID takes nothing returns integer
return 'A001'
endfunction
// Armor (-1) ability raw code
constant function MS_Armor1ID takes nothing returns integer
return 'A002'
endfunction
// Armor (-10) ability raw code
constant function MS_Armor10ID takes nothing returns integer
return 'A003'
endfunction
// Armor (-100) ability raw code
constant function MS_Armor100ID takes nothing returns integer
return 'A009'
endfunction
// Spell Book raw code
constant function MS_SpellBookID takes nothing returns integer
return 'A007'
endfunction
// Dummy lightning base order string
constant function MS_LightningOrder takes nothing returns string
return "fingerofdeath"
endfunction
// Booleans
// Can attack structures or not?
constant function MS_AttackStrucs takes nothing returns boolean
return true
endfunction
// Can reduce structures' armor or not?
constant function MS_ReduceStrucArmor takes nothing returns boolean
return true
endfunction
// True = target the unit with lowest HP percentage, false = target unit with lowest HP amount
constant function MS_HPPercentage takes nothing returns boolean
return true
endfunction
// Enable random interval or not?
constant function MS_RandomInterval takes nothing returns boolean
return false
endfunction
// Minimum value for random interval
constant function MS_RandomIntervalMin takes nothing returns real
return 1.00
endfunction
// Maximum value for random interval
constant function MS_RandomIntervalMax takes nothing returns real
return 2.00
endfunction
// Filter valid units
function MS_FilterUnit takes unit caster, unit u returns boolean
local boolean b = IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitVisible(u, GetOwningPlayer(caster))
if MS_AttackStrucs() then
return b
endif
return b and not IsUnitType (u, UNIT_TYPE_STRUCTURE)
endfunction
// Get Abilities Level
// Get the spell level of the caster
function MS_SpellLvl takes unit caster returns integer
return GetUnitAbilityLevel (caster, MS_GetSpellID())
endfunction
// Get the armor (-1) level of the target
function MS_Armor1Lvl takes unit u returns integer
return GetUnitAbilityLevel (u, MS_Armor1ID())
endfunction
// Get the armor (-10) level of the target
function MS_Armor10Lvl takes unit u returns integer
return GetUnitAbilityLevel (u, MS_Armor10ID())
endfunction
// Get the armor (-100) level of the target
function MS_Armor100Lvl takes unit u returns integer
return GetUnitAbilityLevel (u, MS_Armor100ID())
endfunction
// Level dependable values
// Spell Duration
function MS_Duration takes integer level returns real
return 20 + level*10.
endfunction
// Damage per strike
function MS_Damage takes integer level returns real
return 50 + level*10.
endfunction
// Armor reduced per strike
function MS_ArmorReduced takes integer level returns integer
return level
endfunction
// Max attack range of the storm
function MS_Range takes integer level returns real
return 400 + level*100.
endfunction
// Damage intensification rate i.e the increment of damage percentage with each successive attack
function MS_DmgPlusRate takes integer level returns real
return 0.0
endfunction
// Cooldown between each strike
function MS_Cooldown takes integer level returns real
return 1.25 - level*0.15
endfunction
// Max amount of units attacked per strike
function MS_MaxStrike takes integer level returns integer
return level
endfunction
// Attack - Damage - Weapon type
function MS_AttackType takes nothing returns attacktype
return ATTACK_TYPE_HERO
endfunction
function MS_DamageType takes nothing returns damagetype
return DAMAGE_TYPE_NORMAL
endfunction
function MS_WeaponType takes nothing returns weapontype
return WEAPON_TYPE_WHOKNOWS
endfunction
// END CONFIGURATION
// Preload
function MS_Preload takes nothing returns nothing
local unit dummy
local integer i = 0
set dummy = CreateUnit(Player(0), MS_DummyID(), 0, 0, 0.00)
call UnitAddAbility (dummy, MS_GetSpellID())
call UnitAddAbility (dummy, MS_Armor10ID())
call UnitAddAbility (dummy, MS_Armor10ID())
call UnitAddAbility (dummy, MS_Armor100ID())
call UnitAddAbility (dummy, MS_SpellBookID())
loop
exitwhen i > 15
call SetPlayerAbilityAvailable(Player(i), MS_SpellBookID(), false) // Disable the spell book
set i = i + 1
endloop
call RemoveUnit (dummy)
set dummy = null
endfunction
// End preload
// SPELL FUNCTIONS
// Modify the armor reduction value of the affected unit
function MS_ModifyArmor takes unit target, integer armorreduction returns nothing
local integer array armor
local integer level1 = MS_Armor1Lvl(target)
local integer level2 = MS_Armor10Lvl(target)
local integer level3 = MS_Armor100Lvl(target)
local real armorreduce = level1+level2*10+level3*100
local real armorcalc
local integer tempdivider
local integer tempint
local integer i
set armor[0] = MS_Armor100ID()
set armor[1] = MS_Armor10ID()
set armor[2] = MS_Armor1ID()
if armorreduce == 0 then
call UnitAddAbility(target, MS_SpellBookID()) // Add the spell book
set armorcalc = armorreduce + armorreduction // Set the amount of armor to be modified
if armorcalc == 0 then
call UnitRemoveAbility (target, MS_SpellBookID())
endif
set i = 0
set tempint = 0
set tempdivider = 100
loop
exitwhen i == 3
set tempint = R2I(armorcalc/tempdivider)
call SetUnitAbilityLevel(target, armor[i], tempint+1)
set armorcalc = armorcalc - I2R(tempint*tempdivider)
set tempdivider = tempdivider/10
set i = i + 1
endloop
else
set armorreduce = level1+level2*10+level3*100-111 // Calculate the current armor reduction rate
set armorcalc = armorreduce + armorreduction
if armorcalc == 0 then
call UnitRemoveAbility (target, MS_SpellBookID()) // Remove the spell book if the unit's armor reduction reaches 0
endif
set i = 0
set tempint = 0
set tempdivider = 100
loop
exitwhen i == 3
set tempint = R2I(armorcalc/tempdivider)
call SetUnitAbilityLevel(target, armor[i], tempint+1)
set armorcalc = armorcalc - I2R(tempint*tempdivider)
set tempdivider = tempdivider/10
set i = i + 1
endloop
endif
endfunction
// Damage the target and initiate the armor reduction
function MS_DamageTarget takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer timerID = GetHandleId(t)
local unit caster = LoadUnitHandle(udg_MS_Hashtable, timerID, 0)
local unit target = LoadUnitHandle(udg_MS_Hashtable, timerID, 1)
local integer id = LoadInteger (udg_MS_Hashtable, timerID, 2)
local group g = LoadGroupHandle(udg_MS_Hashtable, id, 3)
local real dmg = LoadReal (udg_MS_Hashtable, timerID, 3)
local integer unitId = GetHandleId(target)
local integer count = LoadInteger (udg_MS_Hashtable, unitId, id)
local integer i = MS_SpellLvl(caster)
// Damage the target and check how many armor should be reduced
call UnitDamageTarget(caster, target, dmg, false, false, MS_AttackType(), MS_DamageType(), MS_WeaponType())
if MS_ReduceStrucArmor() and IsUnitType(target, UNIT_TYPE_STRUCTURE) then
call MS_ModifyArmor (target, MS_ArmorReduced(i))
set count = count + MS_ArmorReduced(i)
call SaveInteger (udg_MS_Hashtable, unitId, id, count)
else
if not IsUnitType (target, UNIT_TYPE_STRUCTURE) then
call MS_ModifyArmor (target, MS_ArmorReduced(i))
set count = count + MS_ArmorReduced(i)
call SaveInteger (udg_MS_Hashtable, unitId, id, count)
endif
endif
if IsUnitType (target, UNIT_TYPE_DEAD) then
call UnitRemoveAbility (target, MS_SpellBookID())
endif
if g != null then
call GroupAddUnit (g, target)
else
set g = CreateGroup()
call GroupAddUnit (g, target)
endif
// Increase the damage for the next hit
set dmg = dmg*(1.00 + MS_DmgPlusRate(i)/100)
call SaveGroupHandle(udg_MS_Hashtable, id, 3, g)
call SaveReal (udg_MS_Hashtable, id, 4, dmg)
// Clean Up
call FlushChildHashtable(udg_MS_Hashtable, timerID)
call PauseTimer(t)
call DestroyTimer(t)
set caster = null
set target = null
set g = null
set t = null
endfunction
// Function to loop through units and choose the one with the lowest HP percentage or amount
function MS_LifeCheck takes unit caster, unit stormfx, integer id, real dmg returns nothing
local real lifecheck
local group g = CreateGroup()
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local unit target
local timer t
local unit u
local integer timerID
local real r
// Group units for valid units
call GroupEnumUnitsInRange(g, x, y, MS_Range(MS_SpellLvl(caster)), null)
loop
set target = FirstOfGroup(g)
exitwhen target == null or MS_FilterUnit(caster, target) and not IsUnitInGroup (target, LoadGroupHandle(udg_MS_Hashtable, id, 5))
call GroupRemoveUnit(g, target)
endloop
// Loops through the chosen units for the target
if target != null then
set lifecheck = GetUnitState(target, UNIT_STATE_LIFE)
if MS_HPPercentage() then
set lifecheck = lifecheck/GetUnitState(target, UNIT_STATE_MAX_LIFE)
endif
loop
set u = FirstOfGroup(g)
exitwhen u == null
if MS_FilterUnit(caster, u) and not IsUnitInGroup (u, LoadGroupHandle(udg_MS_Hashtable, id, 5)) then
set r = GetUnitState(u, UNIT_STATE_LIFE)
if MS_HPPercentage() then
set r = r/GetUnitState(u, UNIT_STATE_MAX_LIFE)
endif
if r<lifecheck then
set lifecheck = r
set target = u
endif
endif
call GroupRemoveUnit(g, u)
endloop
set t = CreateTimer ()
set timerID = GetHandleId(t)
call SaveUnitHandle(udg_MS_Hashtable, timerID, 0, caster)
call SaveUnitHandle(udg_MS_Hashtable, timerID, 1, target)
call SaveInteger(udg_MS_Hashtable, timerID, 2, id)
call SaveReal(udg_MS_Hashtable, timerID, 3, dmg)
call TimerStart (t, 0.3, false, function MS_DamageTarget)
call GroupAddUnit (LoadGroupHandle(udg_MS_Hashtable, id, 5), target)
call IssueTargetOrder(stormfx, MS_LightningOrder(), target)
set target = null
set t = null
endif
call DestroyGroup (g)
set g = null
endfunction
// Periodical trigger for moving the dummy and initialize stuffs when the spell ends
function MS_Periodic takes nothing returns nothing
local trigger trig = GetTriggeringTrigger()
local integer trigID = GetHandleId (trig)
local unit caster = LoadUnitHandle(udg_MS_Hashtable, trigID, 1)
local unit stormfx = LoadUnitHandle(udg_MS_Hashtable, trigID, 2)
local integer evalcount = GetTriggerEvalCount(trig)
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local group g
local real dmg = LoadReal (udg_MS_Hashtable, trigID, 4)
local timer t
local integer count
local unit u
local integer unitid
local integer i = 0
// Check if the caster is dead or the spell duration expired to remove the dummy
if(GetTriggerEventId()==EVENT_UNIT_DEATH)then
if(GetDyingUnit() == caster)then
call KillUnit (stormfx) // We kill the dummy if the caster is dead
endif
call DestroyGroup (LoadGroupHandle(udg_MS_Hashtable, trigID, 5))
call KillUnit (stormfx)
// Remove the armor reduction induced by the spell instance
set g = LoadGroupHandle(udg_MS_Hashtable, trigID, 3)
loop
set u = FirstOfGroup (g)
exitwhen u == null
set unitid = GetHandleId (u)
set count = LoadInteger (udg_MS_Hashtable, unitid, trigID)
call MS_ModifyArmor (u, (-count))
call GroupRemoveUnit(g, u)
endloop
call DestroyGroup (g)
call FlushChildHashtable(udg_MS_Hashtable, trigID)
set g = null
call DisableTrigger (trig)
call TriggerRemoveAction (trig, LoadTriggerActionHandle(udg_MS_Hashtable, trigID, 0))
set trig = null
call DestroyTrigger (GetTriggeringTrigger())
else
call GroupClear (LoadGroupHandle(udg_MS_Hashtable, trigID, 5))
if evalcount == 1 or evalcount >= LoadInteger(udg_MS_Hashtable, trigID, 6) then
loop
exitwhen i == MS_MaxStrike(MS_SpellLvl(caster))
call MS_LifeCheck(caster, stormfx, trigID, dmg)
set i = i + 1
endloop
if MS_RandomInterval() then
call SaveInteger (udg_MS_Hashtable, trigID, 6, evalcount + GetRandomInt(R2I(MS_RandomIntervalMin()/0.03125), R2I(MS_RandomIntervalMax()/0.03125)))
else
call SaveInteger (udg_MS_Hashtable, trigID, 6, evalcount + R2I(MS_Cooldown(MS_SpellLvl(caster))/0.03125))
endif
endif
call SetUnitPosition (stormfx, x, y)
endif
set caster = null
set stormfx = null
set trig = null
endfunction
// Initiate function
function Trig_MS_Conditions takes nothing returns boolean
local trigger trig
local unit caster
local unit stormfx
local real dmg
local integer trigID
if GetSpellAbilityId() == MS_GetSpellID() then
set caster = GetTriggerUnit()
set trig = CreateTrigger()
set trigID = GetHandleId (trig)
set stormfx = CreateUnit(GetTriggerPlayer(), MS_DummyID(), GetUnitX(caster), GetUnitY(caster), bj_UNIT_FACING)
call UnitAddAbility(stormfx, MS_LightningID())
call UnitApplyTimedLife (stormfx, 'BTLF', MS_Duration(MS_SpellLvl(caster)))
call TriggerRegisterTimerEvent(trig, 0.03125, true)
call TriggerRegisterUnitEvent(trig, caster, EVENT_UNIT_DEATH)
call TriggerRegisterUnitEvent(trig, stormfx, EVENT_UNIT_DEATH)
call SaveTriggerActionHandle(udg_MS_Hashtable , trigID, 0, TriggerAddAction( trig, function MS_Periodic ))
call SaveUnitHandle(udg_MS_Hashtable, trigID, 1, caster)
call SaveUnitHandle(udg_MS_Hashtable, trigID, 2, stormfx)
call SaveReal(udg_MS_Hashtable, trigID, 4, MS_Damage(MS_SpellLvl(caster)))
call SaveGroupHandle(udg_MS_Hashtable, trigID, 5, CreateGroup())
call SaveInteger(udg_MS_Hashtable, trigID, 6, GetTriggerEvalCount(trig) + GetRandomInt(R2I(MS_RandomIntervalMin()/0.03125), R2I(MS_RandomIntervalMax()/0.03125)))
set trig = null
set stormfx = null
endif
set caster = null
return false
endfunction
//===========================================================================
function InitTrig_Mystic_Storm takes nothing returns nothing
local trigger MS = CreateTrigger()
set udg_MS_Hashtable = InitHashtable ()
call MS_Preload()
call TriggerRegisterAnyUnitEventBJ(MS, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition( MS, Condition( function Trig_MS_Conditions ) )
set MS = null
endfunction