library TidalPayback initializer Init requires TimerUtils, SpellEffectEvent, TerrainPathability
globals
// CONFIGURABLE CONSTANTS
// General.
private constant integer ABILITY_ID = 'A000' // Tidal Payback Ability Raw Code.
private constant integer MISSILE_ID = 'n000' // Tidal Payback Missile Raw Code.
private constant integer FLYABIL_ID = 'Amrf' // The ability whose bug can be used to make unit flyable.
private constant integer GEYSER_ID = 'n001' // Tidal Payback Geyser Raw Code.
// Running Phase.
private constant string RP_BEFORE_EFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The effect before the run starts.
private constant string RP_EFFECT = "units\\human\\WaterElemental\\WaterElemental.mdl" // The effect during the run.
private constant string RP_DAMAGE_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl" // The effect on the units damaged during the run.
private constant integer RP_WALK_ANIM_INDEX = 0 // The index of 'walk' animation of the appropriate model.
private constant real RP_DISTANCE = 1000. // The distance which the hero must run.
private constant real RP_DISTANCE_FACTOR = 30. // Run distance increment per level.
private constant real RP_ENEMY_CHECK_AOE = 190. // The range from the caster which is scanned for enemies.
private constant real RP_ENEMY_CHECK_AOE_FACTOR = 20. // Range increment per level.
private constant real RP_TIMER_PERIOD = .03125 // The timer of caster's running timeout.
private constant real RP_DISTANCE_PER_PERIOD = 45. // The distance that caster will pass every RP_TIMER_PERIOD seconds.
private constant real RP_WALK_ANIM_SPEED = 2.2 // The speed of 'walk' animation which is needed to make the effect of running. The value '1' means 100%.
private constant real RP_DAMAGE = 120. // The damage that geyser will deal.
private constant real RP_DAMAGE_FACTOR = 42. // Damage increment per level.
private constant attacktype RP_ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype RP_DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype RP_WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
// Geyser after jump.
private constant string GZ_BEFORE_EFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The effect before the geyser explodes.
private constant string GZ_EXPLODE_EFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The effect when the geyser explodes.
private constant string GZ_EXPLODE_EFFECT_2 = "TidalErruption.mdx" // The effect when the geyser explodes (2).
private constant string GZ_DAMAGE_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl" // The effect on the units damaged by geyser.
private constant real GZ_DELAY = 1.7 // The time before geyser will explode.
private constant real GZ_DELAY_DECREASE_FACTOR = .2 // How many can delay decrease with every level of the spell.
private constant real GZ_ENEMY_CHECK_AOE = 140. // The range from geyser in which enemies are considered as caught.
private constant real GZ_ENEMY_CHECK_AOE_FACTOR = 22. // Range increment per level.
private constant real GZ_DAMAGE = 340. // The damage that geyser will deal.
private constant real GZ_DAMAGE_FACTOR = 42. // Damage increment per level.
private constant attacktype GZ_ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype GZ_DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype GZ_WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
// Jumping Phase.
private constant string JP_BEFORE_EFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The effect before the jump starts.
private constant string JP_AFTER_EFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The effect after the jump ends.
private constant string JP_AFTER_EFFECT_2 = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl" // The effect after the jump ends (2).
private constant real JP_DISTANCE = 1400. // The distance which the hero must jump.
private constant real JP_DISTANCE_FACTOR = 10. // Jump distance increment per level.
private constant real JP_TIMER_PERIOD = .03125 // The timer of caster's jumping timeout.
private constant real JP_DISTANCE_PER_PERIOD = 35. // The distance that caster will pass every JP_TIMER_PERIOD seconds.
private constant real JP_MAX_FLY_HEIGHT = 400. // The maximal flying height that will be achieved by the jumper.
private constant real JP_ENEMY_CHECK_AOE = 380. // The range from geyser in which enemies are considered as caught.
private constant real JP_ENEMY_CHECK_AOE_FACTOR = 62. // Range increment per level.
private constant real JP_DAMAGE = 340. // The damage that geyser will deal.
private constant real JP_DAMAGE_FACTOR = 42. // Damage increment per level.
private constant attacktype JP_ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype JP_DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype JP_WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
// HARDCODED STUFF
private group TempGroup = CreateGroup()
endglobals
private function EnumEnemiesConditions takes unit abilityCaster, unit enumUnit returns boolean
return IsUnitEnemy(abilityCaster,GetOwningPlayer(enumUnit)) and not IsUnitType(enumUnit,UNIT_TYPE_DEAD) and not IsUnitType(enumUnit,UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(enumUnit,UNIT_TYPE_STRUCTURE)
endfunction
private struct SpellActions
unit abilityCaster = null
integer abilityLevel = 0
real casterX = 0.
real casterY = 0.
real spellTargetX = 0.
real spellTargetY = 0.
real coordAngle = 0.
real distCos = 0.
real distSin = 0.
real distancePassed = 0.
timer loopTimer = null
timer geyserTimer = null
unit geyserDummy = null
effect geyserEffect = null
unit flyingMissile = null
boolean isFlyingUp = true
real propWindow = 0.
integer spellPhase = 1
group damagedUnits = CreateGroup()
static method geyserCallback takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real geyserX = GetWidgetX(.geyserDummy)
local real geyserY = GetWidgetY(.geyserDummy)
local unit enumUnit
call GroupEnumUnitsInRange(TempGroup,geyserX,geyserY,GZ_ENEMY_CHECK_AOE+GZ_ENEMY_CHECK_AOE_FACTOR*abilityLevel,null)
loop
set enumUnit = FirstOfGroup(TempGroup)
exitwhen enumUnit == null
if EnumEnemiesConditions(.abilityCaster,enumUnit) then
call DestroyEffect(AddSpecialEffect(GZ_DAMAGE_EFFECT,GetWidgetX(enumUnit),GetWidgetY(enumUnit)))
call UnitDamageTarget(.abilityCaster,enumUnit,GZ_DAMAGE+GZ_DAMAGE_FACTOR*abilityLevel,true,false,GZ_ATTACK_TYPE,GZ_DAMAGE_TYPE,GZ_WEAPON_TYPE)
endif
call GroupRemoveUnit(TempGroup,enumUnit)
endloop
call DestroyEffect(AddSpecialEffect(GZ_EXPLODE_EFFECT,geyserX,geyserY))
call DestroyEffect(AddSpecialEffect(GZ_EXPLODE_EFFECT_2,geyserX,geyserY))
call DestroyEffect(.geyserEffect)
call RemoveUnit(.geyserDummy)
call ReleaseTimer(.geyserTimer)
set .geyserEffect = null
set .geyserDummy = null
set .geyserTimer = null
endmethod
static method createGeyser takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
set .geyserTimer = NewTimerEx(this)
set .geyserDummy = CreateUnit(GetOwningPlayer(.abilityCaster),GEYSER_ID,.casterX,.casterY,0.)
set .geyserEffect = AddSpecialEffect(GZ_BEFORE_EFFECT,.casterX,.casterY)
call TimerStart(.geyserTimer,GZ_DELAY+GZ_DELAY_DECREASE_FACTOR*.abilityLevel,false,function thistype.geyserCallback)
endmethod
static method jumpCallback takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real nextX = GetWidgetX(.flyingMissile) + .distCos
local real nextY = GetWidgetY(.flyingMissile) + .distSin
local unit enumUnit
if .distancePassed < JP_DISTANCE+JP_DISTANCE_FACTOR*.abilityLevel then
if .distancePassed == 0. and .isFlyingUp then
if UnitAddAbility(.flyingMissile,FLYABIL_ID) then
call UnitRemoveAbility(.flyingMissile,FLYABIL_ID)
endif
call SetUnitFlyHeight(.flyingMissile,JP_MAX_FLY_HEIGHT,JP_MAX_FLY_HEIGHT / (.5 * (JP_DISTANCE+JP_DISTANCE_FACTOR*.abilityLevel) / (JP_DISTANCE_PER_PERIOD / JP_TIMER_PERIOD)))
endif
if distancePassed >= .5 * (JP_DISTANCE+JP_DISTANCE_FACTOR*.abilityLevel) and .isFlyingUp then
call SetUnitFlyHeight(.flyingMissile,0.,JP_MAX_FLY_HEIGHT / (.5 * (JP_DISTANCE+JP_DISTANCE_FACTOR*.abilityLevel) / (JP_DISTANCE_PER_PERIOD / JP_TIMER_PERIOD)))
set .isFlyingUp = false
endif
if IsTerrainWalkable(nextX,nextY) then
call SetUnitX(.flyingMissile,nextX)
call SetUnitY(.flyingMissile,nextY)
call SetUnitX(.abilityCaster,nextX)
call SetUnitY(.abilityCaster,nextY)
endif
set .distancePassed = .distancePassed + JP_DISTANCE_PER_PERIOD
else
if IsTerrainWalkable(nextX,nextY) then
call SetUnitX(.abilityCaster,nextX)
call SetUnitY(.abilityCaster,nextY)
else
call SetUnitX(.abilityCaster,GetWidgetX(.flyingMissile))
call SetUnitY(.abilityCaster,GetWidgetY(.flyingMissile))
endif
call SetUnitVertexColor(.abilityCaster,255,255,255,255)
call ShowUnit(.flyingMissile,false)
call RemoveUnit(.flyingMissile)
call DestroyEffect(AddSpecialEffect(JP_AFTER_EFFECT,nextX,nextY))
call DestroyEffect(AddSpecialEffect(JP_AFTER_EFFECT_2,nextX,nextY))
call SetUnitPropWindow(.abilityCaster,.propWindow)
call SetUnitTurnSpeed(.abilityCaster,GetUnitDefaultTurnSpeed(.abilityCaster))
call IssueImmediateOrderById(.abilityCaster,851972)
call SetUnitFacing(.abilityCaster,.coordAngle*57.295827)
call SetUnitTimeScale(.abilityCaster,1.)
call GroupEnumUnitsInRange(TempGroup,nextX,nextY,JP_ENEMY_CHECK_AOE+JP_ENEMY_CHECK_AOE_FACTOR*.abilityLevel,null)
loop
set enumUnit = FirstOfGroup(TempGroup)
exitwhen enumUnit == null
if EnumEnemiesConditions(.abilityCaster,enumUnit) then
call UnitDamageTarget(.abilityCaster,enumUnit,JP_DAMAGE+JP_DAMAGE_FACTOR*.abilityLevel,true,false,JP_ATTACK_TYPE,JP_DAMAGE_TYPE,JP_WEAPON_TYPE)
endif
call GroupRemoveUnit(TempGroup,enumUnit)
endloop
call ReleaseTimer(.loopTimer)
call GroupClear(.damagedUnits)
call DestroyGroup(.damagedUnits)
set .damagedUnits = null
set .flyingMissile = null
set .abilityCaster = null
set .loopTimer = null
call .destroy()
endif
endmethod
static method walkCallback takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local unit enumUnit
set .casterX = GetWidgetX(abilityCaster)
set .casterY = GetWidgetY(abilityCaster)
if .distancePassed < RP_DISTANCE+RP_DISTANCE_FACTOR*abilityLevel then
call GroupEnumUnitsInRange(TempGroup,.casterX+.distCos,.casterY+.distSin,RP_ENEMY_CHECK_AOE+RP_ENEMY_CHECK_AOE_FACTOR*.abilityLevel,null)
loop
set enumUnit = FirstOfGroup(TempGroup)
exitwhen enumUnit == null
if EnumEnemiesConditions(.abilityCaster,enumUnit) and not IsUnitInGroup(enumUnit,.damagedUnits) then
call GroupAddUnit(.damagedUnits,enumUnit)
call DestroyEffect(AddSpecialEffect(RP_DAMAGE_EFFECT,GetWidgetX(enumUnit),GetWidgetY(enumUnit)))
call UnitDamageTarget(.abilityCaster,enumUnit,RP_DAMAGE+RP_DAMAGE_FACTOR*.abilityLevel,true,false,RP_ATTACK_TYPE,RP_DAMAGE_TYPE,RP_WEAPON_TYPE)
endif
call GroupRemoveUnit(TempGroup,enumUnit)
endloop
call SetUnitFacing(.abilityCaster,.coordAngle*57.295827)
if IsTerrainWalkable(.casterX+.distCos,.casterY+.distSin) then
call SetUnitX(.abilityCaster,.casterX+.distCos)
call SetUnitY(.abilityCaster,.casterY+.distSin)
endif
call DestroyEffect(AddSpecialEffect(RP_EFFECT,.casterX,.casterY))
set .distancePassed = .distancePassed + RP_DISTANCE_PER_PERIOD
else
set .spellPhase = 2
call SetUnitVertexColor(.abilityCaster,255,255,255,0)
call .createGeyser()
set .distancePassed = 0.
set .flyingMissile = CreateUnit(GetOwningPlayer(.abilityCaster),MISSILE_ID,.casterX,.casterY,.coordAngle*57.295827)
call PauseTimer(.loopTimer)
call TimerStart(.loopTimer,JP_TIMER_PERIOD,true,function thistype.callback)
endif
endmethod
static method callback takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if .spellPhase == 1 then
call .walkCallback()
elseif .spellPhase == 2 then
call .jumpCallback()
endif
endmethod
static method create takes unit abilityCaster, real spellTargetX, real spellTargetY returns thistype
local thistype this = thistype.allocate()
set .abilityCaster = abilityCaster
set .abilityLevel = GetUnitAbilityLevel(abilityCaster,ABILITY_ID)
set .casterX = GetWidgetX(abilityCaster)
set .casterY = GetWidgetY(abilityCaster)
set .spellTargetX = spellTargetX
set .spellTargetY = spellTargetY
set .coordAngle = Atan2(.spellTargetY-.casterY,.spellTargetX-.casterX)
set .distCos = RP_DISTANCE_PER_PERIOD * Cos(.coordAngle)
set .distSin = RP_DISTANCE_PER_PERIOD * Sin(.coordAngle)
set .propWindow = GetUnitPropWindow(abilityCaster)
set .loopTimer = NewTimerEx(this)
call SetUnitPropWindow(abilityCaster,0.)
call SetUnitTurnSpeed(abilityCaster,0.)
call SetUnitAnimationByIndex(abilityCaster,RP_WALK_ANIM_INDEX)
call TimerStart(.loopTimer,RP_TIMER_PERIOD,true,function thistype.callback)
return this
endmethod
endstruct
private function Actions takes nothing returns boolean
local unit abilityCaster = GetTriggerUnit()
local real casterX = GetWidgetX(abilityCaster)
local real casterY = GetWidgetY(abilityCaster)
local real spellTargetX = GetSpellTargetX()
local real spellTargetY = GetSpellTargetY()
call SetUnitFacingTimed(abilityCaster,Atan2(spellTargetY-casterY,spellTargetX-casterX)*57.295827,0.)
call DestroyEffect(AddSpecialEffect(RP_BEFORE_EFFECT,casterX,casterY))
call SetUnitTimeScale(abilityCaster,RP_WALK_ANIM_SPEED)
call SpellActions.create(abilityCaster,spellTargetX,spellTargetY)
set abilityCaster = null
return false
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(ABILITY_ID,function Actions)
endfunction
endlibrary