469 lines
18 KiB
Lua
469 lines
18 KiB
Lua
local ____lualib = require("lualib_bundle")
|
||
local __TS__Class = ____lualib.__TS__Class
|
||
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
|
||
local __TS__Decorate = ____lualib.__TS__Decorate
|
||
local ____exports = {}
|
||
local ____difficulty_manager = require("difficulty_manager")
|
||
local Difficulty = ____difficulty_manager.Difficulty
|
||
local ____dota_ts_adapter = require("lib.dota_ts_adapter")
|
||
local BaseAbility = ____dota_ts_adapter.BaseAbility
|
||
local BaseModifier = ____dota_ts_adapter.BaseModifier
|
||
local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth
|
||
local registerAbility = ____dota_ts_adapter.registerAbility
|
||
local registerModifier = ____dota_ts_adapter.registerModifier
|
||
--- Та же физика дуги, что у `frogmen_acid_jump` / `modifier_acid_blob_jump`.
|
||
local CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_LOWEST = 400
|
||
local CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_HIGHEST = 200
|
||
local CONTRACT_HEAD_LEAP_ACCELERATION_Z = 1500
|
||
local CONTRACT_HEAD_LEAP_MAX_HORIZONTAL_ACCELERATION = 1500
|
||
--- Пассивка контракта: прыжок дугой к точке, где стоял герой; урон только если к моменту приземления он не ушёл из зоны.
|
||
____exports.contract_head_leap = __TS__Class()
|
||
local contract_head_leap = ____exports.contract_head_leap
|
||
contract_head_leap.name = "contract_head_leap"
|
||
contract_head_leap.____file_path = "scripts/vscripts/abilities/creep/contract_head_leap.lua"
|
||
__TS__ClassExtends(contract_head_leap, BaseAbility)
|
||
function contract_head_leap.prototype.Precache(self, context)
|
||
PrecacheResource("particle", "particles/generic_gameplay/generic_hit_blood.vpcf", context)
|
||
PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context)
|
||
PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", context)
|
||
end
|
||
function contract_head_leap.prototype.GetIntrinsicModifierName(self)
|
||
return "modifier_contract_head_leap_passive"
|
||
end
|
||
function contract_head_leap.prototype.showLeapPrecastVisuals(self, caster, strikeGround, damageRadius, warningDuration)
|
||
if not IsServer() or warningDuration <= 0 then
|
||
return
|
||
end
|
||
local groundPos = GetGroundPosition(strikeGround, nil)
|
||
local r = math.max(1, damageRadius)
|
||
local particleAoe = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil)
|
||
if particleAoe ~= nil then
|
||
ParticleManager:SetParticleControl(particleAoe, 0, groundPos)
|
||
ParticleManager:SetParticleControl(
|
||
particleAoe,
|
||
1,
|
||
Vector(r, 0, 0)
|
||
)
|
||
ParticleManager:SetParticleControl(
|
||
particleAoe,
|
||
2,
|
||
Vector(6, 0, 1)
|
||
)
|
||
ParticleManager:SetParticleControl(
|
||
particleAoe,
|
||
3,
|
||
Vector(200, 0, 0)
|
||
)
|
||
ParticleManager:SetParticleControl(particleAoe, 4, groundPos)
|
||
Timers:CreateTimer(
|
||
warningDuration,
|
||
function()
|
||
ParticleManager:DestroyParticle(particleAoe, false)
|
||
ParticleManager:ReleaseParticleIndex(particleAoe)
|
||
end
|
||
)
|
||
end
|
||
local casterPos = GetGroundPosition(
|
||
caster:GetAbsOrigin(),
|
||
nil
|
||
)
|
||
local particleArrow = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil)
|
||
if particleArrow ~= nil then
|
||
ParticleManager:SetParticleControl(particleArrow, 0, casterPos)
|
||
ParticleManager:SetParticleControl(particleArrow, 1, groundPos)
|
||
ParticleManager:SetParticleControl(
|
||
particleArrow,
|
||
2,
|
||
Vector(warningDuration, 0, 0)
|
||
)
|
||
Timers:CreateTimer(
|
||
warningDuration,
|
||
function()
|
||
ParticleManager:DestroyParticle(particleArrow, false)
|
||
ParticleManager:ReleaseParticleIndex(particleArrow)
|
||
end
|
||
)
|
||
end
|
||
end
|
||
function contract_head_leap.prototype.performLeapLanding(self, heroEntIndex, strikeGround)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
local creep = self:GetCaster()
|
||
if not creep or not IsValidEntity(creep) or not creep:IsAlive() then
|
||
return
|
||
end
|
||
local hero = EntIndexToHScript(heroEntIndex)
|
||
local hitRadius = math.max(
|
||
0,
|
||
self:GetSpecialValueFor("damage_radius")
|
||
)
|
||
local hpos = hero and IsValidEntity(hero) and hero:IsAlive() and hero:GetAbsOrigin() or nil
|
||
local inZone = false
|
||
if hpos ~= nil then
|
||
local dx = hpos.x - strikeGround.x
|
||
local dy = hpos.y - strikeGround.y
|
||
inZone = math.sqrt(dx * dx + dy * dy) <= hitRadius
|
||
end
|
||
if hero and IsValidEntity(hero) and hero:IsAlive() and not hero:IsInvulnerable() and inZone then
|
||
local scale = Difficulty:getNpcStatScale()
|
||
local flatDamage = math.max(
|
||
1,
|
||
math.floor(self:GetSpecialValueFor("leap_damage") * scale + 0.5)
|
||
)
|
||
local pctFromAttack = math.max(
|
||
0,
|
||
self:GetSpecialValueFor("damage_from_attack_pct")
|
||
)
|
||
local avgAtk = creep:GetAverageTrueAttackDamage(hero)
|
||
local fromAttack = math.floor(avgAtk * (pctFromAttack / 100) * scale + 0.5)
|
||
local totalDamage = flatDamage + fromAttack
|
||
ApplyDamage({
|
||
victim = hero,
|
||
attacker = creep,
|
||
damage = totalDamage,
|
||
damage_type = DAMAGE_TYPE_PHYSICAL,
|
||
ability = self
|
||
})
|
||
local hitFx = ParticleManager:CreateParticle("particles/generic_gameplay/generic_hit_blood.vpcf", PATTACH_CUSTOMORIGIN, nil)
|
||
ParticleManager:SetParticleControlEnt(
|
||
hitFx,
|
||
0,
|
||
hero,
|
||
PATTACH_POINT_FOLLOW,
|
||
"attach_hitloc",
|
||
hero:GetAbsOrigin(),
|
||
true
|
||
)
|
||
ParticleManager:ReleaseParticleIndex(hitFx)
|
||
EmitSoundOn("Hero_MonkeyKing.Spring.Channel", creep)
|
||
end
|
||
end
|
||
contract_head_leap = __TS__Decorate(
|
||
contract_head_leap,
|
||
contract_head_leap,
|
||
{registerAbility(nil)},
|
||
{kind = "class", name = "contract_head_leap"}
|
||
)
|
||
____exports.contract_head_leap = contract_head_leap
|
||
____exports.modifier_contract_head_leap_passive = __TS__Class()
|
||
local modifier_contract_head_leap_passive = ____exports.modifier_contract_head_leap_passive
|
||
modifier_contract_head_leap_passive.name = "modifier_contract_head_leap_passive"
|
||
modifier_contract_head_leap_passive.____file_path = "scripts/vscripts/abilities/creep/contract_head_leap.lua"
|
||
__TS__ClassExtends(modifier_contract_head_leap_passive, BaseModifier)
|
||
function modifier_contract_head_leap_passive.prototype.____constructor(self, ...)
|
||
BaseModifier.prototype.____constructor(self, ...)
|
||
self.precastLockUntil = -1
|
||
end
|
||
function modifier_contract_head_leap_passive.prototype.IsHidden(self)
|
||
return true
|
||
end
|
||
function modifier_contract_head_leap_passive.prototype.IsDebuff(self)
|
||
return false
|
||
end
|
||
function modifier_contract_head_leap_passive.prototype.IsPurgable(self)
|
||
return false
|
||
end
|
||
function modifier_contract_head_leap_passive.prototype.OnCreated(self)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
local ability = self:GetAbility()
|
||
local interval = ability and math.max(
|
||
0.35,
|
||
ability:GetSpecialValueFor("leap_interval")
|
||
) or 2.4
|
||
self:StartIntervalThink(interval)
|
||
end
|
||
function modifier_contract_head_leap_passive.prototype.OnRefresh(self)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
local ability = self:GetAbility()
|
||
local interval = ability and math.max(
|
||
0.35,
|
||
ability:GetSpecialValueFor("leap_interval")
|
||
) or 2.4
|
||
self:StartIntervalThink(interval)
|
||
end
|
||
function modifier_contract_head_leap_passive.prototype.OnIntervalThink(self)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
local parent = self:GetParent()
|
||
local ability = self:GetAbility()
|
||
if not ability or not IsValidEntity(parent) or not parent:IsAlive() or parent:IsStunned() or parent:IsHexed() then
|
||
return
|
||
end
|
||
local now = GameRules:GetDOTATime(false, false)
|
||
if now < self.precastLockUntil then
|
||
return
|
||
end
|
||
local primary = parent:FindAbilityByName("contract_head_leap")
|
||
if not primary or primary ~= ability then
|
||
return
|
||
end
|
||
if parent:HasModifier("modifier_contract_head_leap_arc") then
|
||
return
|
||
end
|
||
local radius = math.max(
|
||
50,
|
||
ability:GetSpecialValueFor("search_radius")
|
||
)
|
||
local heroes = FindUnitsInRadius(
|
||
parent:GetTeamNumber(),
|
||
parent:GetAbsOrigin(),
|
||
nil,
|
||
radius,
|
||
DOTA_UNIT_TARGET_TEAM_ENEMY,
|
||
DOTA_UNIT_TARGET_HERO,
|
||
DOTA_UNIT_TARGET_FLAG_NONE,
|
||
FIND_CLOSEST,
|
||
false
|
||
)
|
||
local hero
|
||
for ____, u in ipairs(heroes) do
|
||
do
|
||
if not u or not IsValidEntity(u) or not u:IsAlive() or not u:IsHero() then
|
||
goto __continue28
|
||
end
|
||
if not u:IsRealHero() then
|
||
goto __continue28
|
||
end
|
||
if u:IsInvulnerable() then
|
||
goto __continue28
|
||
end
|
||
if u:GetUnitName() == "npc_homer" then
|
||
goto __continue28
|
||
end
|
||
hero = u
|
||
break
|
||
end
|
||
::__continue28::
|
||
end
|
||
if not hero then
|
||
return
|
||
end
|
||
local height = math.max(
|
||
40,
|
||
ability:GetSpecialValueFor("height_above_hero")
|
||
)
|
||
local strikeGround = GetGroundPosition(
|
||
hero:GetAbsOrigin(),
|
||
hero
|
||
)
|
||
local headPos = Vector(strikeGround.x, strikeGround.y, strikeGround.z + height)
|
||
local warningTime = math.max(
|
||
0,
|
||
ability:GetSpecialValueFor("precast_warning_time")
|
||
)
|
||
local dmgRadius = math.max(
|
||
1,
|
||
ability:GetSpecialValueFor("damage_radius")
|
||
)
|
||
local arcPayload = {
|
||
vLocX = headPos.x,
|
||
vLocY = headPos.y,
|
||
vLocZ = headPos.z,
|
||
strikeX = strikeGround.x,
|
||
strikeY = strikeGround.y,
|
||
strikeZ = strikeGround.z,
|
||
hero_ei = hero:entindex()
|
||
}
|
||
if warningTime > 0 then
|
||
self.precastLockUntil = now + warningTime
|
||
ability:showLeapPrecastVisuals(parent, strikeGround, dmgRadius, warningTime)
|
||
local ____self = self
|
||
Timers:CreateTimer(
|
||
warningTime,
|
||
function()
|
||
____self.precastLockUntil = -1
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
if not IsValidEntity(parent) or not parent:IsAlive() then
|
||
return
|
||
end
|
||
if parent:HasModifier("modifier_contract_head_leap_arc") then
|
||
return
|
||
end
|
||
local ab = parent:FindAbilityByName("contract_head_leap")
|
||
if not ab then
|
||
return
|
||
end
|
||
parent:AddNewModifier(parent, ab, "modifier_contract_head_leap_arc", arcPayload)
|
||
end
|
||
)
|
||
else
|
||
parent:AddNewModifier(parent, ability, "modifier_contract_head_leap_arc", arcPayload)
|
||
end
|
||
end
|
||
modifier_contract_head_leap_passive = __TS__Decorate(
|
||
modifier_contract_head_leap_passive,
|
||
modifier_contract_head_leap_passive,
|
||
{registerModifier(nil)},
|
||
{kind = "class", name = "modifier_contract_head_leap_passive"}
|
||
)
|
||
____exports.modifier_contract_head_leap_passive = modifier_contract_head_leap_passive
|
||
____exports.modifier_contract_head_leap_arc = __TS__Class()
|
||
local modifier_contract_head_leap_arc = ____exports.modifier_contract_head_leap_arc
|
||
modifier_contract_head_leap_arc.name = "modifier_contract_head_leap_arc"
|
||
modifier_contract_head_leap_arc.____file_path = "scripts/vscripts/abilities/creep/contract_head_leap.lua"
|
||
__TS__ClassExtends(modifier_contract_head_leap_arc, BaseModifierMotionBoth)
|
||
function modifier_contract_head_leap_arc.prototype.____constructor(self, ...)
|
||
BaseModifierMotionBoth.prototype.____constructor(self, ...)
|
||
self.bHorizontalMotionInterrupted = false
|
||
self.flCurrentTimeHoriz = 0
|
||
self.flCurrentTimeVert = 0
|
||
self.flInitialVelocityZ = 0
|
||
self.flPredictedTotalTime = 0
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.IsHidden(self)
|
||
return true
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.IsPurgable(self)
|
||
return false
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.RemoveOnDeath(self)
|
||
return true
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.OnCreated(self, params)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
local parent = self:GetParent()
|
||
local ability = self:GetAbility()
|
||
if not parent or not ability then
|
||
self:Destroy()
|
||
return
|
||
end
|
||
local hx = params.hero_ei
|
||
if hx == nil or hx == nil then
|
||
self:Destroy()
|
||
return
|
||
end
|
||
self.heroEntIndex = hx
|
||
local sx = params.strikeX or parent:GetOrigin().x
|
||
local sy = params.strikeY or parent:GetOrigin().y
|
||
local sz = params.strikeZ or parent:GetOrigin().z
|
||
self.strikeGround = Vector(sx, sy, sz)
|
||
self.bHorizontalMotionInterrupted = false
|
||
if not self:ApplyHorizontalMotionController() or not self:ApplyVerticalMotionController() then
|
||
self:Destroy()
|
||
return
|
||
end
|
||
self.vStartPosition = GetGroundPosition(
|
||
parent:GetOrigin(),
|
||
parent
|
||
)
|
||
self.flCurrentTimeHoriz = 0
|
||
self.flCurrentTimeVert = 0
|
||
local x = params.vLocX or parent:GetOrigin().x
|
||
local y = params.vLocY or parent:GetOrigin().y
|
||
local z = params.vLocZ or parent:GetOrigin().z
|
||
self.vLoc = Vector(x, y, z)
|
||
self.vLastKnownTargetPos = self.vLoc
|
||
local duration = math.max(
|
||
0.08,
|
||
ability:GetSpecialValueFor("jump_duration")
|
||
)
|
||
local flDesiredHeight = CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_LOWEST * duration * duration
|
||
local flLowZ = math.min(self.vLastKnownTargetPos.z, self.vStartPosition.z)
|
||
local flHighZ = math.max(self.vLastKnownTargetPos.z, self.vStartPosition.z)
|
||
local flArcTopZ = math.max(flLowZ + flDesiredHeight, flHighZ + CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_HIGHEST)
|
||
local flArcDeltaZ = flArcTopZ - self.vStartPosition.z
|
||
self.flInitialVelocityZ = math.sqrt(2 * flArcDeltaZ * CONTRACT_HEAD_LEAP_ACCELERATION_Z)
|
||
local flDeltaZ = self.vLastKnownTargetPos.z - self.vStartPosition.z
|
||
local flSqrtDet = math.sqrt(math.max(0, self.flInitialVelocityZ * self.flInitialVelocityZ - 2 * CONTRACT_HEAD_LEAP_ACCELERATION_Z * flDeltaZ))
|
||
self.flPredictedTotalTime = math.max((self.flInitialVelocityZ + flSqrtDet) / CONTRACT_HEAD_LEAP_ACCELERATION_Z, (self.flInitialVelocityZ - flSqrtDet) / CONTRACT_HEAD_LEAP_ACCELERATION_Z)
|
||
self.vHorizontalVelocity = (self.vLastKnownTargetPos - self.vStartPosition) / self.flPredictedTotalTime
|
||
self.vHorizontalVelocity.z = 0
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.OnDestroy(self)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
local parent = self:GetParent()
|
||
if parent ~= nil then
|
||
parent:RemoveHorizontalMotionController(self)
|
||
parent:RemoveVerticalMotionController(self)
|
||
end
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.CheckState(self)
|
||
return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_UNSELECTABLE] = true}
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.UpdateHorizontalMotion(self, me, dt)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
self.flCurrentTimeHoriz = math.min(self.flCurrentTimeHoriz + dt, self.flPredictedTotalTime)
|
||
local t = self.flCurrentTimeHoriz / self.flPredictedTotalTime
|
||
local vStartToTarget = self.vLastKnownTargetPos - self.vStartPosition
|
||
local vDesiredPos = self.vStartPosition + vStartToTarget * t
|
||
local vOldPos = me:GetOrigin()
|
||
local vToDesired = vDesiredPos - vOldPos
|
||
vToDesired.z = 0
|
||
local vDesiredVel = vToDesired / dt
|
||
local vVelDif = vDesiredVel - self.vHorizontalVelocity
|
||
local flVelDif = vVelDif:Length2D()
|
||
vVelDif = vVelDif:Normalized()
|
||
local flVelDelta = math.min(flVelDif, CONTRACT_HEAD_LEAP_MAX_HORIZONTAL_ACCELERATION)
|
||
self.vHorizontalVelocity = self.vHorizontalVelocity + vVelDif * flVelDelta * dt
|
||
local vNewPos = vOldPos + self.vHorizontalVelocity * dt
|
||
me:SetOrigin(vNewPos)
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.UpdateVerticalMotion(self, me, dt)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
self.flCurrentTimeVert = self.flCurrentTimeVert + dt
|
||
local bGoingDown = -CONTRACT_HEAD_LEAP_ACCELERATION_Z * self.flCurrentTimeVert + self.flInitialVelocityZ < 0
|
||
local vNewPos = me:GetOrigin()
|
||
vNewPos.z = self.vStartPosition.z + (-0.5 * CONTRACT_HEAD_LEAP_ACCELERATION_Z * self.flCurrentTimeVert * self.flCurrentTimeVert + self.flInitialVelocityZ * self.flCurrentTimeVert)
|
||
local flGroundHeight = GetGroundHeight(
|
||
vNewPos,
|
||
self:GetParent()
|
||
)
|
||
local bLanded = false
|
||
if vNewPos.z < flGroundHeight and bGoingDown then
|
||
vNewPos.z = flGroundHeight
|
||
bLanded = true
|
||
end
|
||
me:SetOrigin(vNewPos)
|
||
if bLanded then
|
||
if not self.bHorizontalMotionInterrupted then
|
||
local ability = self:GetAbility()
|
||
if ability then
|
||
ability:performLeapLanding(self.heroEntIndex, self.strikeGround)
|
||
end
|
||
end
|
||
self:Destroy()
|
||
end
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.OnHorizontalMotionInterrupted(self)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
self.bHorizontalMotionInterrupted = true
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.OnVerticalMotionInterrupted(self)
|
||
if not IsServer() then
|
||
return
|
||
end
|
||
self:Destroy()
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.DeclareFunctions(self)
|
||
return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION}
|
||
end
|
||
function modifier_contract_head_leap_arc.prototype.GetOverrideAnimation(self)
|
||
return ACT_DOTA_CAST_ABILITY_1
|
||
end
|
||
modifier_contract_head_leap_arc = __TS__Decorate(
|
||
modifier_contract_head_leap_arc,
|
||
modifier_contract_head_leap_arc,
|
||
{registerModifier(nil)},
|
||
{kind = "class", name = "modifier_contract_head_leap_arc"}
|
||
)
|
||
____exports.modifier_contract_head_leap_arc = modifier_contract_head_leap_arc
|
||
return ____exports
|