Skip to content
Snippets Groups Projects
Commit 4fa09e42 authored by DarkGod's avatar DarkGod
Browse files

NPCs can use Shoot Down

parent 48066ca9
No related branches found
No related tags found
No related merge requests found
......@@ -139,13 +139,14 @@ function _M:useTalent(id, who, force_level, ignore_cd, force_target, silent, no_
if not self:preUseTalent(ab, silent) then return end
local old_level
local old_target
if force_level then old_level = who.talents[id]; who.talents[id] = force_level end
if force_target then old_target = rawget(who, "getTarget"); who.getTarget = function(a) return force_target.x, force_target.y, not force_target.__no_self and force_target end end
if ab.onAIGetTarget and not self.player then old_target = rawget(who, "getTarget"); who.getTarget = function() return ab.onAIGetTarget(self, ab) end end
if force_target and not old_target then old_target = rawget(who, "getTarget"); who.getTarget = function(a) return force_target.x, force_target.y, not force_target.__no_self and force_target end end
self.__talent_running = ab
local ok, ret, special = xpcall(function() return ab.action(who, ab) end, debug.traceback)
self.__talent_running = nil
if force_target then who.getTarget = old_target end
if old_target then who.getTarget = old_target end
if force_level then who.talents[id] = old_level end
if not ok then error(ret) end
......
......@@ -54,18 +54,32 @@ newAI("use_tactical", function(self)
-- Find available talents
print("============================== TACTICAL AI", self.name)
local avail = {}
local _
local ok = false
local ax, ay = self:aiSeeTargetPos(self.ai_target.actor)
local target_dist = self.ai_target.actor and core.fov.distance(self.x, self.y, ax, ay)
local hate = self.ai_target.actor and (self:reactionToward(self.ai_target.actor) < 0)
local has_los = self.ai_target.actor and self:hasLOS(ax, ay)
local aitarget = self.ai_target.actor
local ax, ay = self:aiSeeTargetPos(aitarget)
local target_dist = aitarget and core.fov.distance(self.x, self.y, ax, ay)
local hate = aitarget and (self:reactionToward(aitarget) < 0)
local has_los = aitarget and self:hasLOS(ax, ay)
local self_compassion = (self.ai_state.self_compassion == false and 0) or self.ai_state.self_compassion or 5
local ally_compassion = (self.ai_state.ally_compassion == false and 0) or self.ai_state.ally_compassion or 1
for tid, lvl in pairs(self.talents) do
local aitarget = aitarget
local ax, ay = ax, ay
local target_dist = target_dist
local t = self:getTalentFromId(tid)
if t.onAIGetTarget then
_, _, aitarget = t.onAIGetTarget(self, t)
if aitarget then
ax, ay = self:aiSeeTargetPos(aitarget)
target_dist = aitarget and core.fov.distance(self.x, self.y, ax, ay)
end
end
local t_avail = false
print(self.name, self.uid, "tactical ai talents testing", t.name, tid)
if t.tactical then
print(self.name, self.uid, "tactical ai talents testing", t.name, tid, "on target", aitarget and aitarget.name)
if t.tactical and aitarget then
local tg = self:getTalentTarget(t)
local default_tg = {type=util.getval(t.direct_hit, self, t) and "hit" or "bolt"}
-- Only assume range... some talents may no require LOS, etc
......@@ -88,7 +102,7 @@ newAI("use_tactical", function(self)
local self_hit = {}
local typ = engine.Target:getType(tg or default_tg)
if tg or self:getTalentRequiresTarget(t) then
local target_actor = self.ai_target.actor or self
local target_actor = aitarget or self
self:project(typ, ax, ay, function(px, py)
local act = game.level.map(px, py, engine.Map.ACTOR)
if act and not act.dead then
......@@ -107,7 +121,7 @@ newAI("use_tactical", function(self)
end
-- Evaluate the tactical weights and weight functions
for tact, val in pairs(t.tactical) do
if type(val) == "function" then val = val(self, t, self.ai_target.actor) or 0 end
if type(val) == "function" then val = val(self, t, aitarget) or 0 end
-- Handle damage_types and resistances
local nb_foes_hit, nb_allies_hit, nb_self_hit = 0, 0, 0
if type(val) == "table" then
......@@ -151,7 +165,7 @@ newAI("use_tactical", function(self)
val = val * (self.ai_talents and self.ai_talents[t.id] or 1) * (1 + lvl / 5)
-- Update the weight by the dummy projection data
-- Also force scaling if the talent requires a target (stand-in for canProject)
if self:getTalentRequiresTarget(t) or nb_foes_hit > 0 or nb_allies_hit > 0 or nb_self_hit > 0 then
if tact ~= "special" and (self:getTalentRequiresTarget(t) or nb_foes_hit > 0 or nb_allies_hit > 0 or nb_self_hit > 0) then
val = val * (nb_foes_hit - ally_compassion * nb_allies_hit - self_compassion * nb_self_hit)
end
-- Only take values greater than 0... allows the ai_talents to turn talents off
......@@ -323,17 +337,17 @@ newAI("use_tactical", function(self)
end
-- Attacks
if avail.attack and self.ai_target.actor then
if avail.attack and aitarget then
-- Use the foe/ally ratio from the best attack talent
table.sort(avail.attack, function(a,b) return a.val > b.val end)
want.attack = avail.attack[1].val
end
if avail.disable and self.ai_target.actor then
if avail.disable and aitarget then
-- Use the foe/ally ratio from the best disable talent
table.sort(avail.disable, function(a,b) return a.val > b.val end)
want.disable = (want.attack or 0) + avail.disable[1].val
end
if avail.attackarea and self.ai_target.actor then
if avail.attackarea and aitarget then
-- Use the foe/ally ratio from the best attackarea talent
table.sort(avail.attackarea, function(a,b) return a.val > b.val end)
want.attackarea = avail.attackarea[1].val
......@@ -344,6 +358,8 @@ newAI("use_tactical", function(self)
want.buff = math.max(0.01, want.attack + 0.5)
end
if avail.special then want.special = avail.special[1].val end
print("Tactical ai report for", self.name)
local res = {}
for k, v in pairs(want) do
......
......@@ -3769,6 +3769,10 @@ function _M:preUseTalent(ab, silent, fake)
if ab.is_teleport and self:attr("encased_in_ice") then return false end
end
-- Special checks -- AI
if not self.player and ab.on_pre_use_ai and not (ab.mode == "sustained" and self:isTalentActive(ab.id)) and not ab.on_pre_use_ai(self, ab, silent, fake) then return false end
if not silent then
-- Allow for silent talents
if ab.message ~= nil then
......
......@@ -26,13 +26,23 @@ newTalent{
stamina = 20,
range = archery_range,
require = techs_dex_req_high1,
no_npc_use = true,
onAIGetTarget = function(self, t)
local tgts = {}
self:project({type="ball", radius=self:getTalentRange(t)}, self.x, self.y, function(px, py)
local tgt = game.level.map(px, py, Map.PROJECTILE)
if tgt and (not tgt.src or self:reactionToward(tgt.src) < 0) then tgts[#tgts+1] = {x=px, y=py, tgt=tgt, dist=core.fov.distance(self.x, self.y, px, py)} end
end)
table.sort(tgts, function(a, b) return a.dist < b.dist end)
if #tgts > 0 then return tgts[1].x, tgts[1].y, tgts[1].tgt end
end,
on_pre_use_ai = function(self, t, silent) return t.onAIGetTarget(self, t) and true or false end,
on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
requires_target = true,
getNb = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
target = function(self, t)
return {type="bolt", range=self:getTalentRange(t), scan_on=engine.Map.PROJECTILE, no_first_target_filter=true}
end,
tactical = {SPECIAL=10},
action = function(self, t)
for i = 1, t.getNb(self, t) do
local targets = self:archeryAcquireTargets(self:getTalentTarget(t), {one_shot=true, no_energy=true})
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment