scope HolyDash
/***********************************************************************************
* HolyDash
* ________
* v1.0.1.1
* by Thelordmarshall
*
* The Paladin move fast toward one point, causing damage to all enemy
* units in the way and healing your allies for 30% of the inflicted
* damage.
*
* Level 1 - 700 cast range, 90 damage + 9% of strength (Configurable).
* Level 2 - 900 cast range, 170 damage + 17% of strength (Configurable).
* Level 3 - 1100 cast range, 280 damage + 24% of strength (Configurable).
* Level 4 - 1300 cast range, 360 damage + 30% of strength (Configurable).
*
* Cooldown: 19/17/15/13 seconds.
*
* How to import:
* ______________
*
* - Copy the folder "Requires" and "Holy Dash Spell".
* - Copy the dummy unit and ability.
* - Copy the ability Unit Indexing.
* - Configure all raw codes in UnitIndexer and HolyDash triggers.
* - You can configure other parameters of the spell (optional).
* - Enjoy.
*
* Credits:
* ________
*
* - Vexorian: TimerUtils.
* - Nestharus: UnitIndexer.
* - PitzerMike: DestructableLib functions.
* - Rising_Dusk: TerrainPathability
*
* Copyright © 2014.
************************************************************************************/
globals
private real array dmgLvl
private real array attLvl
endglobals
//CONFIGURATION
globals
//Raw codes
private constant integer SPELL_ID = 'A001' //Raw code of your ability.
private constant integer DASH_DUMMY = 'h000' //Dummy raw code.
private constant integer MAIN_CASTER = 'H001' //Raw code of the main caster.
//Basic Configuration
private constant real DASH_SPEED_TIME = .8 //Max speed of the dash (in seconds).
private constant real DASH_AOE = 90. //Units affected in this range.
private constant real TAG_SIZE = .02 //Textag size.
private constant real DASH_ANIM_SPEED = 120. //Caster animation speed.
private constant real DUMMY_ANIM_SPEED = 220. //Dummy animation speed.
private constant integer MOTION_BLUR_LEVEL = 90 //Transparency of the dummy unit (from 0 to 255).
private constant integer MOTION_TIME = 4 //Time/speed to add transparency and remove the dummy unit.
private constant string ATTACH_FX_CASTER = "weapon" //Attach point of the fx caster.
private constant string ATTACH_FX = "origin" //Attach point of the fx enemys.
private constant string DASH_ANIMATION = "attack" //Animation for the caster.
private constant string DUMMY_ANIMATION = "attack" //Animation for dummys.
private constant damagetype DASH_DMG_TYPE = DAMAGE_TYPE_DIVINE
//Tag colors
private constant string TAG_HEAL_COLOR = "|c00FEBA0E"
private constant string TAG_DMG_COLOR = "|c00FF0000"
//Fx
private constant string DASH_FX_CASTER = "Abilities\\Spells\\Other\\HealingSpray\\HealBottleMissile.mdl"
private constant string DASH_FX_ALLY = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
private constant string DASH_FX_ENEMY = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
private constant string DASH_EXTRA_FX = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl" //fx used in not walkable terrain.
//Knockback fxs (if use the knockback feature)
private constant string KNOCKBACKED_FX1 = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl" //fx in land.
private constant string KNOCKBACKED_FX2 = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl" //fx in water.
//Advance Configuration
private constant boolean SHOW_TEXT_TAG = true
private constant boolean PAUSE_CASTER = true
private constant boolean HIT_TREES = true
private constant boolean USE_KNOCKBACK = true //Konckback for enemys.
private constant real KNOCKBACKED_DIS = 85.
private constant real KNOCKBACKED_SPEED = 1.
private constant real REFRESH_PERIOD = .03125 //Recomended: .03125 (or 0.03 in GUI)
private constant real DEBUG_PERIOD = 5. //Time to disable the ability, only in case if not turn off automatically.
endglobals
//Damage base for each level.
private function DamageConfig takes nothing returns nothing
set dmgLvl[1] = 90.
set dmgLvl[2] = 170.
set dmgLvl[3] = 280.
set dmgLvl[4] = 360.
endfunction
//Attribute % for each level.
private function AttributePercentConfig takes nothing returns nothing
set attLvl[1] = 9.
set attLvl[2] = 17.
set attLvl[3] = 24.
set attLvl[4] = 30.
endfunction
//Attribute type, used for damage calculation, you can use str, int or agi.
private function AttributeType takes unit caster returns integer
return GetHeroStr(caster,true)
endfunction
//Heal calculation.
private function GetHeal takes real dmg returns real
return dmg*30./100. // =30%
endfunction
//Filter for afected units.
private function ConfigFilter takes unit u returns boolean
return GetWidgetLife(u) > .405 and not IsUnitType(u,UNIT_TYPE_STRUCTURE) and not IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE)
endfunction
//ENDCONFIGURATION
//Core.
private struct HolyDash extends array
static if HIT_TREES then
static rect treesRect
static unit treesMurderer
endif
static if USE_KNOCKBACK then
unit knockbacked
endif
unit caster
player p
real cos
real sin
real dmg
real d1
real d2
real tTotal
group dashGroup
effect fx
method showTag takes string s, real x, real y returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag,s,TAG_SIZE)
call SetTextTagPos(tag,x,y,16.)
call SetTextTagVelocity(tag,0.,.0355)
call SetTextTagFadepoint(tag,1.5)
call SetTextTagLifespan(tag,2.)
call SetTextTagPermanent(tag,false)
set tag = null
endmethod
static method fxOnGround takes real x, real y returns nothing
local real AoE = 250.
local integer duration = R2I(.4*1000.)
local real spaceWave = 5.*duration/10.
local real timeWave = 5.*AoE/200.
local real radiusRatio = AoE/50.
call TerrainDeformStop(TerrainDeformRipple(x,y,AoE,80.,duration,2,spaceWave,timeWave,radiusRatio,true),duration)
endmethod
static if HIT_TREES then
//Credits to PitzerMike for this function.
static method isDesIsTree takes destructable d returns boolean
local boolean result = false
if d != null then
call PauseUnit(treesMurderer,false)
set result = IssueTargetOrder(treesMurderer,"harvest",d)
call PauseUnit(treesMurderer,true)
endif
return result
endmethod
static method destoyTrees takes nothing returns nothing
local destructable d = GetEnumDestructable()
if GetDestructableLife(d) > .405 and .isDesIsTree(d) then
call KillDestructable(d)
endif
set d = null
endmethod
endif
static if USE_KNOCKBACK then
static method onKnockback takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local real x = GetUnitX(.knockbacked)
local real y = GetUnitY(.knockbacked)
set .tTotal = .tTotal+REFRESH_PERIOD
if not IsUnitPaused(.knockbacked) then
call PauseUnit(.knockbacked,true)
endif
call SetUnitPosition(.knockbacked,x+.d1*.cos,y+.d1*.sin)
if IsTerrainLand(x,y) then
call DestroyEffect(AddSpecialEffect(KNOCKBACKED_FX1,x,y))
elseif IsTerrainShallowWater(x,y) then
call DestroyEffect(AddSpecialEffect(KNOCKBACKED_FX2,x,y))
endif
set .d1 = .d1-.d2
if .d1 <= 0. or .tTotal >= DEBUG_PERIOD then
call PauseUnit(.knockbacked,false)
call SetUnitPathing(.knockbacked,true)
set .knockbacked = null
call ReleaseTimer(t)
endif
set t = null
endmethod
static method startKnockback takes unit u, real a returns nothing
local thistype this = thistype[GetUnitId(u)]
local integer q = R2I(KNOCKBACKED_SPEED/REFRESH_PERIOD)
set .knockbacked = u
set .cos = Cos(a)
set .sin = Sin(a)
set .d1 = 2.*KNOCKBACKED_DIS/q+1.
set .d2 = .d1/q
set .tTotal = 0.
call SetUnitPathing(.knockbacked,false)
call TimerStart(NewTimerEx(this),REFRESH_PERIOD,true,function thistype.onKnockback)
endmethod
endif
method destroy takes nothing returns nothing
call SetUnitTimeScale(.caster,100.*.01)
call SetUnitAnimation(.caster,"attack")
call PauseUnit(.caster,false)
call SetUnitPathing(.caster,true)
call DestroyGroup(.dashGroup)
call DestroyEffect(.fx)
call MotionBlur[.caster].destroy()
set .p = null
set .caster = null
set .dashGroup = null
endmethod
static method onLoop takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local real x = GetUnitX(.caster)
local real y = GetUnitY(.caster)
local real x2
local real y2
local real maxLife
local real life
local real heal
local unit u
set .tTotal = .tTotal+REFRESH_PERIOD
static if PAUSE_CASTER then
if not IsUnitPaused(.caster) then
call PauseUnit(.caster,true)
endif
endif
call SetUnitPosition(.caster,x+.d1*.cos,y+.d1*.sin)
call SetUnitAnimation(.caster,DASH_ANIMATION)
if not IsTerrainWalkable(x,y) then
call DestroyEffect(AddSpecialEffect(DASH_EXTRA_FX,x,y))
endif
call GroupEnumUnitsInRange(bj_lastCreatedGroup,x,y,DASH_AOE,null)
loop
set u = FirstOfGroup(bj_lastCreatedGroup)
call GroupRemoveUnit(bj_lastCreatedGroup,u)
if ConfigFilter(u) and not IsUnitInGroup(u,.dashGroup) and u != .caster then
set x2 = GetUnitX(u)
set y2 = GetUnitY(u)
if IsUnitEnemy(u,.p) then
call UnitDamageTarget(.caster,u,.dmg,false,false,ATTACK_TYPE_HERO,DASH_DMG_TYPE,null)
//! textmacro OnHit takes FX,TAG_COLOR,REAL_VALUE
call DestroyEffect(AddSpecialEffectTarget($FX$,u,ATTACH_FX))
static if SHOW_TEXT_TAG then
call .showTag($TAG_COLOR$+I2S(R2I($REAL_VALUE$))+"!|r",GetUnitX(u),GetUnitY(u))
endif
call .fxOnGround(x2,y2)
//! endtextmacro
//! runtextmacro OnHit("DASH_FX_ENEMY","TAG_DMG_COLOR",".dmg")
static if USE_KNOCKBACK then
call .startKnockback(u,Atan2(y2-y,x2-x))
endif
else //If ally
set maxLife = GetUnitState(u,UNIT_STATE_MAX_LIFE)
set life = GetUnitState(u,UNIT_STATE_LIFE)
if life < maxLife then
set heal = GetHeal(.dmg)
call SetWidgetLife(u,life+heal)
//! runtextmacro OnHit("DASH_FX_ALLY","TAG_HEAL_COLOR","heal")
endif
endif
call GroupAddUnit(.dashGroup,u)
endif
exitwhen u == null
endloop
static if HIT_TREES then
call MoveRectTo(treesRect,x,y)
call EnumDestructablesInRect(treesRect,null,function thistype.destoyTrees)
endif
set .d1 = .d1-.d2
if .d1 <= 0. or tTotal >= DEBUG_PERIOD then
call .fxOnGround(x,y)
call .destroy()
call ReleaseTimer(t)
endif
set t = null
endmethod
static method onCast takes nothing returns boolean
local unit u = GetTriggerUnit()
local real x1 = GetUnitX(u)
local real y1 = GetUnitY(u)
local real x2 = GetSpellTargetX()
local real y2 = GetSpellTargetY()
local real a = Atan2(y2-y1,x2-x1)
local integer lvl = GetUnitAbilityLevel(u,SPELL_ID)
local integer id = GetUnitTypeId(u)
local integer q = R2I(DASH_SPEED_TIME/REFRESH_PERIOD)
local thistype this = thistype[GetUnitId(u)]
if GetSpellAbilityId() == SPELL_ID then
set .caster = u
set .p = GetTriggerPlayer()
set .cos = Cos(a)
set .sin = Sin(a)
set .d1 = 2.*SquareRoot(Pow(x2-x1,2)+Pow(y2-y1,2))/(q+1.)
set .d2 = .d1/q
set .dmg = dmgLvl[lvl]+I2R(AttributeType(.caster))*attLvl[lvl]/100.
set .fx = AddSpecialEffectTarget(DASH_FX_CASTER,.caster,ATTACH_FX_CASTER)
set .dashGroup = CreateGroup()
set .tTotal = 0.
//Activating the motion blur.
if id == MAIN_CASTER then
call MotionBlur[.caster].add(DASH_DUMMY,DUMMY_ANIMATION,DUMMY_ANIM_SPEED,MOTION_BLUR_LEVEL,MOTION_TIME,false)
else
call MotionBlur[.caster].add(id,DUMMY_ANIMATION,DUMMY_ANIM_SPEED,MOTION_BLUR_LEVEL,MOTION_TIME,true)
endif
call SetUnitPathing(.caster,false)
call SetUnitTimeScale(.caster,DASH_ANIM_SPEED*.01)
call TimerStart(NewTimerEx(this),REFRESH_PERIOD,true,function thistype.onLoop)
endif
set u = null
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call DamageConfig()
call AttributePercentConfig()
//Configuring the trees murderer dummy unit and rect(used for EnumDestructablesInRect).
static if HIT_TREES then
set treesMurderer = CreateUnit(Player(15),'hfoo',0.,0.,0.)
call ShowUnit(treesMurderer,false)
call UnitAddAbility(treesMurderer,'Ahrl')
call UnitAddAbility(treesMurderer,'Aloc')
call PauseUnit(treesMurderer,true)
set treesRect = Rect(-DASH_AOE,-DASH_AOE,DASH_AOE,DASH_AOE)
endif
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t,Condition(function thistype.onCast))
endmethod
endstruct
endscope