library MySpell requires AutoIndex, TimerUtils, GetDistance
//-------------------------------------------------------------------------------------------
// Hunter's Instinct
//
// by Dr.Killer
//
// The Warden uses her unatural instincts to locate wounded enemies.
// Then, she gains bonus movement speed and the ability to become invisible.
// She inflicts bonus damage upon her victim based on her distance from her
// farthest target at the time of casting.
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
// Configuration
//-------------------------------------------------------------------------------------------
globals
private constant integer MAIN_SPELL_ID = 'A001' //Raw code of spell
private constant integer INVIS_ID_1 = 'A003' //Raw codes for wind walk abilities (3 levels)
private constant integer INVIS_ID_2 = 'A004'
private constant integer INVIS_ID_3 = 'A005'
private constant integer INVIS_BUFF_ID = 'BOwk' //Wind walk buff raw code
private constant real RANGE = 5100. //The radius in which targets are picked
private constant real DURATION = 15. //Spell duration
private constant integer ARRAY_SIZE = 90 //Determines the number of spells which can be run simultaneously by different units
private constant real UNIT_MAX_SPEED = 522. //Unit's maximum speed in your map (neccessary)
private group tempGroup //A group used for enumerationa and stuff.
endglobals
private function HpThreshold takes integer lvl returns real //Enemies whose hit points are below this percent, will be added to targets group
return (10.+10.*I2R(lvl))/100.
endfunction
private function SpeedBonus takes integer lvl returns real //Amount of bonus speed given to Warden upon casting (in percent)
return (20.+10.*I2R(lvl))/100.
endfunction
//-------------------------------------------------------------------------------------------
// End of Configuration
//-------------------------------------------------------------------------------------------
globals
private boolean array flag [ARRAY_SIZE][ARRAY_SIZE] //Each target unit has a flag to see if the corresponding spell' duration cast on him has ended. Makes it possible for this spell to be MUI.
private boolean array invis_flag //Checks whether the wind walk ability has been added to warden or not. Useful for preventing the Warden from getting more than 1 wind walk ability
endglobals
//-------------------------------------------------------------------------------------------
// Spell's main struct, holds all of spell's properties for later use
//-------------------------------------------------------------------------------------------
private struct SpellData //Struct variable names should be pretty self-explanatory
private unit caster
private integer casterId
private player owner
private integer lvl
private group targets //A group which holds all the targets of the spell.
private timer main_timer
private real casterX
private real casterY
private real maxDist //To determine the wind walk ability level, I store Warden's distance of the farthest target in this
private real speedBonus //Amount of speed bonus given to Warden (the real value which is actually added to her speed)
//-------------------------------------------------------------------------------------------
// This method runs when spell duration ends; does cleanups, effect removal, etc.
//-------------------------------------------------------------------------------------------
private static method onFinish takes nothing returns nothing
local unit u = null //A temp unit used in some loops.
local SpellData object = GetTimerData(GetExpiredTimer()) //An instance of SpellData struct which holds all of spell's variables, such as caster, targets, etc.
local SpellData temp //Used in the Vision Management section. More info below.
local integer i = 0 //I & J are simple counters used in loops.
local integer j = 0
local integer target_id = 0 //UnitId of the currently picked target (More info below)
local boolean grant_vision = false //Determines whether shared vision of the currently picked should be denied or granted. Used in making this MUI.
local integer owner_id = GetPlayerId(object.owner)
//----------------------------------------------------------------------------------
// Falsing flag related to this spell. Pretty obvious. Prevents this spell
// from granting vision of targets any longer.
//----------------------------------------------------------------------------------
loop
set i=i+1
exitwhen i>ARRAY_SIZE
set flag[object][i] = false
endloop
set i=0
//----------------------------------------------------------------------------------
// Vision Management section. Here, I pick the targets one by one, check if
// they are under the effect of a same spell (of another source). If that's
// true, I see if the owning player of that spell is the same as owner of this
// one. If that's also true, vision of that target is granted to the corresponding
// player and the grant_vision boolean is set to true.
// If not, vision is denied because with no Hunter's Instinct active,
// there is no reason for them to have vision of enemy heroes.
// The 'temp' instance, is used to convey info of the other active spell to this
// method, so I can have access to its caster, owning player, etc.
//----------------------------------------------------------------------------------
loop
set u = FirstOfGroup(object.targets)
exitwhen u == null
set target_id = GetUnitId(u)
loop
set j=j+1
exitwhen (j>ARRAY_SIZE or grant_vision == true)
set temp = SpellData.create()
if (flag[j][target_id] == true) then
set temp = j
if (GetOwningPlayer(temp.caster) == object.owner) then
set grant_vision = true
call UnitShareVision(u, object.owner, true)
endif
endif
call temp.destroy()
endloop
if (not grant_vision) then
call UnitShareVision(u, object.owner, false)
endif
call GroupRemoveUnit(object.targets, u)
set j=0
endloop
//----------------------------------------------------------------------------------
// Cleanup section. Removes leaks, nullifies handles, etc.
// I also set the invis_flag of the caster to false, cause she no longer
// possesses the wind walk ability and even if she is in the middle of one,
// the buff is removed, so she becomes visible.
// I also remove the speed bonus here.
//----------------------------------------------------------------------------------
set invis_flag[object.casterId] = false
call UnitRemoveAbility(object.caster, INVIS_ID_1)
call UnitRemoveAbility(object.caster, INVIS_ID_2)
call UnitRemoveAbility(object.caster, INVIS_ID_3)
call UnitRemoveAbility(object.caster, INVIS_BUFF_ID)
call SetUnitMoveSpeed(object.caster, GetUnitMoveSpeed(object.caster)-object.speedBonus)
call DestroyGroup(object.targets)
call PauseTimer(GetExpiredTimer())
call ReleaseTimer(GetExpiredTimer())
set object.caster = null
endmethod
//-------------------------------------------------------------------------------------------
// Core method; sets needed variables, runs the spell, etc.
//-------------------------------------------------------------------------------------------
private static method onCast takes nothing returns nothing
local SpellData object = SpellData.create() //Same as above, holds all needed variables for a spell.
local unit u = null //A temp unit used in loops.
local real dist = 0. //Warden's distance form the picked target. Mre info below.
local real finalSpeed = 0. //Warden's final speed, after applying the bonuses.
local real originalSpeed = 0. //Warden's original speed, before applying the bonuses.
set object.targets = CreateGroup()
set object.caster = GetTriggerUnit()
set object.casterId = GetUnitId(object.caster)
set object.owner = GetOwningPlayer(object.caster)
set object.casterX = GetUnitX(object.caster)
set object.casterY = GetUnitY(object.caster)
set object.lvl = GetUnitAbilityLevel(object.caster, MAIN_SPELL_ID)
call GroupEnumUnitsInRange(tempGroup, object.casterX, object.casterY, RANGE, null) //Aquires all potential targets. Invalid ones are filtered out below.
//----------------------------------------------------------------------------------
// Target Validation section. Throws non-hero and non-enemy units. Also
// dissmissed units whose HP is more than the required threshold which differs
// for each level. Also set required 'flag' slots to true. If a unit's flag
// is true, it means than they are under the effect of Hunter's Instinct.
// Besides, I store the farthest target's distance in maxDist variable, using
// some kind of Bubble Sort method to find it in the first place.
//----------------------------------------------------------------------------------
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
if (IsUnitType(u, UNIT_TYPE_HERO) and (GetUnitState(u, UNIT_STATE_LIFE) <= (GetUnitState(u, UNIT_STATE_MAX_LIFE)*HpThreshold(object.lvl))) and IsUnitEnemy(u, GetOwningPlayer(object.caster))) then
call GroupAddUnit(object.targets, u)
set dist = GetDistUnits(u, object.caster)
if dist>object.maxDist then
set object.maxDist = dist
endif
set dist = 0.
set flag[object][GetUnitId(u)] = true
else
set flag[object][GetUnitId(u)] = false
endif
call GroupRemoveUnit(tempGroup, u)
endloop
//----------------------------------------------------------------------------------
// This part adds the speed bonus. Also if the bonus causes Warden's speed
// to go beyond the maximum allowed speed, this corrects the bonus amount
// and removes the excess value.
//----------------------------------------------------------------------------------
if not IsUnitGroupEmptyBJ(object.targets) then
set originalSpeed = GetUnitMoveSpeed(object.caster)
set object.speedBonus = originalSpeed*SpeedBonus(object.lvl)
set finalSpeed = object.speedBonus+originalSpeed
if (finalSpeed > UNIT_MAX_SPEED) then
set object.speedBonus = object.speedBonus - finalSpeed + UNIT_MAX_SPEED
set finalSpeed = UNIT_MAX_SPEED
call SetUnitMoveSpeed(object.caster, UNIT_MAX_SPEED)
endif
call SetUnitMoveSpeed(object.caster, finalSpeed)
endif
//----------------------------------------------------------------------------------
// Vision Management section, again. Picks each individual unit present
// in the 'targets' group and grants shared vision of that unit to
// the owning player of caster.
//----------------------------------------------------------------------------------
call GroupClear(tempGroup)
call GroupAddGroup(object.targets, tempGroup)
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
call UnitShareVision(u, GetOwningPlayer(object.caster), true)
call GroupRemoveUnit(tempGroup, u)
endloop
//----------------------------------------------------------------------------------
// Here, I add the windwalk ability based on Warden's distance from her
// farthest target, which is stored in 'maxDist' variable.
//----------------------------------------------------------------------------------
if not invis_flag[object.casterId] then
if (object.maxDist <= 1700.) then
call UnitAddAbility(object.caster, INVIS_ID_1)
elseif (object.maxDist <= 2400.) then
call UnitAddAbility(object.caster, INVIS_ID_2)
else
call UnitAddAbility(object.caster, INVIS_ID_3)
endif
endif
set invis_flag[object.casterId] = true
//----------------------------------------------------------------------------------
// Fires a timer to call the onFinish method at the of spell duration.
//----------------------------------------------------------------------------------
set object.main_timer = NewTimer()
call SetTimerData(object.main_timer, object)
call TimerStart(object.main_timer, DURATION, true, function thistype.onFinish)
set u = null
endmethod
//-------------------------------------------------------------------------------------------
// Self-explanatory
//-------------------------------------------------------------------------------------------
private static method castCondition takes nothing returns boolean
return GetSpellAbilityId() == MAIN_SPELL_ID
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.castCondition))
call TriggerAddAction(t, function thistype.onCast)
set tempGroup = CreateGroup()
endmethod
endstruct
endlibrary