Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Zeus ultimate #105

Merged
merged 2 commits into from
Jul 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 115 additions & 64 deletions ability_item_usage_zuus.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ end
local Talents = {}
local Abilities = {}
local AbilitiesReal = {}
npcBot.ult = {} -- Track possible ult targets across frames (see GetDelayedUltDesire())
local delayedUltDesire = 0

ability_item_usage_generic.InitAbility(Abilities, AbilitiesReal, Talents)

local damageBuffer = 0.95 -- buffer ult damage for regen during cast time, etc.
local patience = 1.6 -- number of seconds to try killing without ult

local AbilityToLevelUp =
{
Expand Down Expand Up @@ -105,6 +109,7 @@ function GetComboMana()
return ability_item_usage_generic.GetComboMana(AbilitiesReal)
end

-- zuus_arc_lightning
Consider[1] = function()

local ability = AbilitiesReal[1];
Expand Down Expand Up @@ -251,6 +256,7 @@ Consider[1] = function()

end

-- zuus_lightning_bolt
Consider[2] = function()

local ability = AbilitiesReal[2];
Expand Down Expand Up @@ -366,7 +372,7 @@ Consider[2] = function()
return BOT_ACTION_DESIRE_NONE, 0, "nil";
end

-- 7.31 upgrade
-- zuus_heavenly_jump
Consider[3] = function()
local abilityNumber = 3
--------------------------------------
Expand Down Expand Up @@ -451,90 +457,132 @@ Consider[3] = function()
return BOT_ACTION_DESIRE_NONE
end

local function DontSteal(enemy)
return AbilityExtensions:GetUnitList(UNIT_LIST_ALLIED_HEROES):Remove(npcBot):Any(function(t) return enemy:
WasRecentlyDamagedByHero(t, 1.5)
end) and not enemy:WasRecentlyDamagedByHero(npcBot, 2.5)
end
function DebugDrawUltTable()
if debugmode then

local function ShouldUseUltimate(enemy)
return CanCast[6](enemy) and DontSteal(enemy)
end
local drawY = 720
local drawStep = 18

Consider[6] = function()
for enemyName, enemyStats in pairs(npcBot.ult) do
local timeElapsed = DotaTime() - enemyStats[1]
local enemyBot = enemyStats[2]
local enemyHero = enemyStats[3]
local wasKillable = enemyStats[4]
local isNull = ""

local ability = AbilitiesReal[6]
if not ability:IsFullyCastable()
then
return BOT_ACTION_DESIRE_NONE
end
if enemyBot:IsNull() then
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a function IsNull() in Lua language? The general usage seems to be enemyBot == nil

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think IsNull() comes from the C interface. Wherever it's from, it detects when a bot object exists but is no longer valid. From what I can see, when a bot goes out of vision, its object loses all of its API functions, but is not nil. E.g. calling enemyBot:GetUnitName will return an error. This is only really an issue when keeping track of bots across frames.

I assume the reason it works this way is to prevent you from keeping track of the real hero by just tracking the object id. Interestingly, it seems dying does not cause the object to be cleared.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get it, this is really interesting.

isNull = "!null"
end

local Damage = 125 + 100 * ability:GetLevel()
local WeakestEnemy = nil
local LowestHP = 10000.0
local sInfo = enemyName .. isNull .. " Hero " .. tostring(enemyHero)
sInfo = sInfo .. " Killable=" .. tostring(wasKillable) .. " " .. timeElapsed

for _, Enemy in pairs(GetUnitList(UNIT_LIST_ENEMY_HEROES)) do
if Enemy ~= nil
then
if (GetUnitToUnitDistance(npcBot, Enemy) > AbilitiesReal[2]:GetCastRange() or not AbilitiesReal[2]:IsFullyCastable())
then
if Enemy:IsAlive() and Enemy:CanBeSeen() and ShouldUseUltimate(Enemy)
then
if (LowestHP > Enemy:GetHealth())
then
WeakestEnemy = Enemy;
LowestHP = Enemy:GetHealth();
end
end
end
DebugDrawText(100, drawY, sInfo, 255, 255, 255)
drawY = drawY + drawStep
end
end
end

-- Don't kill enemies that allies have just damaged but we haven't
local function DontSteal(enemy)
return not enemy:WasRecentlyDamagedByAnyHero(1.5) or
enemy:WasRecentlyDamagedByHero(npcBot, 2.5)
end

-- Tracking enemies with low health that we are giving teammates a chance to kill
local function GetDelayedUltDesire()
local ability = AbilitiesReal[6];

if not ability:IsTrained() or ability:GetCooldownTimeRemaining() > 30 then
-- Skill is on cooldown, no point in tracking
if next(npcBot.ult) ~= nil then
npcBot.ult = {}
end

if WeakestEnemy == nil or LowestHP < 1 then
return BOT_ACTION_DESIRE_NONE
end

if LowestHP <= WeakestEnemy:GetActualIncomingDamage(Damage, DAMAGE_TYPE_MAGICAL)
then
--return BOT_ACTION_DESIRE_VERYHIGH
local time_byname = WeakestEnemy:GetUnitName()
local allys = WeakestEnemy:GetNearbyHeroes(600, true, BOT_MODE_NONE)
if #allys == 0 or #allys == 1 and allys[1] == npcBot then
-- Don't rush to get the kill
return BOT_ACTION_DESIRE_MODERATE
else
if (npcBot.ult == nil)
then
npcBot.ult = {}
end
if (npcBot.ult.time_byname == nil)
then
npcBot.ult.time_byname = { DotaTime(), WeakestEnemy, WeakestEnemy:GetPlayerID() }
local Damage = ability:GetSpecialValueInt("damage") * damageBuffer

-- Find good enemies to kill, put them in a list and wait a
-- couple seconds to see if our team can kill them without the ult
for _, Enemy in ipairs(GetUnitList(UNIT_LIST_ENEMY_HEROES)) do
-- Include enemies if:
-- * They are alive and visible
-- * We won't kill steal
-- * We can't just hit them with lightning bolt
-- * Our ult would kill them
-- * TODO: They are the real hero
-- * TODO: When shield support added, add that to health (or do hard way with modifiers)
local isKillable = Enemy:GetHealth() <= Enemy:GetActualIncomingDamage(Damage, DAMAGE_TYPE_MAGICAL)
if Enemy:IsAlive() and Enemy:CanBeSeen() and DontSteal(Enemy) and
(GetUnitToUnitDistance(npcBot, Enemy) > AbilitiesReal[2]:GetCastRange() or
not AbilitiesReal[2]:IsFullyCastable()) and
isKillable
then
local enemyName = Enemy:GetUnitName()
if (npcBot.ult[enemyName] == nil) then
npcBot.ult[enemyName] = { DotaTime(), Enemy, Enemy:GetPlayerID(), isKillable }
end
end
end

if (npcBot.ult ~= nil)
then
for _, i in pairs(npcBot.ult) do
if (DotaTime() - i[1] >= 1.6)
then
if (
i[2] == nil or i[2]:IsNull() or
i[2]:GetHealth() <= i[2]:GetActualIncomingDamage(Damage, DAMAGE_TYPE_MAGICAL) and IsHeroAlive(i[3])) and
CanCast[6](i[2])
then
npcBot.ult = nil
return BOT_ACTION_DESIRE_VERYHIGH
end
end
DebugDrawUltTable()

for enemyName, enemyStats in pairs(npcBot.ult) do
local timeElapsed = DotaTime() - enemyStats[1]
local enemyBot = enemyStats[2]
local enemyHero = enemyStats[3]
local wasKillable = enemyStats[4]

local isEnemyNull = (enemyBot == nil or enemyBot:IsNull())
local isKillable = not isEnemyNull and (IsHeroAlive(enemyHero) and
CanCast[6](enemyBot) and
enemyBot:GetHealth() <= enemyBot:GetActualIncomingDamage(Damage, DAMAGE_TYPE_MAGICAL))

-- update killable status in case they go out of vision
enemyStats[4] = isKillable

if timeElapsed >= 30 or not IsHeroAlive(enemyHero) or
(isEnemyNull and not wasKillable)
then
-- Enemy either got away or got killed
-- Forget about them; we can always add them again if they get low
npcBot.ult[enemyName] = nil

elseif isEnemyNull and IsHeroAlive(enemyHero) and wasKillable then
-- enemyBot should never be nil, but will become null when
-- we lose sight of them. Ult now!
-- Note: this may cast because they went invis
-- they won't take damage, but we will get true sight
return BOT_ACTION_DESIRE_VERYHIGH

elseif (timeElapsed >= patience and isKillable) then
-- Okay, we've been patient enough. Time for some wrath.
return BOT_ACTION_DESIRE_VERYHIGH

end
end


return BOT_ACTION_DESIRE_NONE
end

-- zuus_thundergods_wrath
Consider[6] = function()
local ability = AbilitiesReal[6];

local Damage = ability:GetSpecialValueInt("damage") * damageBuffer

if not ability:IsFullyCastable() then
return BOT_ACTION_DESIRE_NONE
end

-- Results from GetDelayedUltDesire
return delayedUltDesire
end


-- zuus_cloud (Nimbus)
Consider[4] = function()

local ability = AbilitiesReal[4]
Expand Down Expand Up @@ -581,6 +629,9 @@ end
AbilityExtensions:AutoModifyConsiderFunction(npcBot, Consider, AbilitiesReal)
function AbilityUsageThink()

-- Run even if we can't cast, to keep track of targets
delayedUltDesire = GetDelayedUltDesire()

-- Check if we're already using an ability
if (npcBot:IsUsingAbility() or npcBot:IsChanneling() or npcBot:IsSilenced())
then
Expand Down