From 4fa09e4294f043fe6164d3bcfbc36930ce847272 Mon Sep 17 00:00:00 2001 From: DarkGod <darkgod@net-core.org> Date: Tue, 22 Oct 2013 22:52:52 +0200 Subject: [PATCH] NPCs can use Shoot Down --- .../default/engine/interface/ActorTalents.lua | 7 ++-- game/modules/tome/ai/tactical.lua | 40 +++++++++++++------ game/modules/tome/class/Actor.lua | 4 ++ .../data/talents/techniques/excellence.lua | 12 +++++- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/game/engines/default/engine/interface/ActorTalents.lua b/game/engines/default/engine/interface/ActorTalents.lua index e4b0a7b2aa..6d029b0d34 100644 --- a/game/engines/default/engine/interface/ActorTalents.lua +++ b/game/engines/default/engine/interface/ActorTalents.lua @@ -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 diff --git a/game/modules/tome/ai/tactical.lua b/game/modules/tome/ai/tactical.lua index b5cfc0e284..3cadfb7a80 100644 --- a/game/modules/tome/ai/tactical.lua +++ b/game/modules/tome/ai/tactical.lua @@ -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 diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index dd1a6e99b9..d4124cc27d 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -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 diff --git a/game/modules/tome/data/talents/techniques/excellence.lua b/game/modules/tome/data/talents/techniques/excellence.lua index f76e5f5151..1b16e345ef 100644 --- a/game/modules/tome/data/talents/techniques/excellence.lua +++ b/game/modules/tome/data/talents/techniques/excellence.lua @@ -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}) -- GitLab