diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index a2d0b7cab186578fd4a765c8201be27bfe42a2b3..34a038248d328be64ce6ca9d0a231babdd5ce2e8 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -205,6 +205,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["Cancel"] = "取消", ["End"] = "结束", -- ["Quit"] = "退出", + ["All"] = "全部", ["BanGeneral"] = "禁将", ["ResumeGeneral"] = "解禁", ["Enable"] = "启用", diff --git a/lua/core/util.lua b/lua/core/util.lua index 77ffd362623ec0f6f1f674a87e34c7b23e2e5a06..0a27025aae20f0065e4c69fd25003b5a5bd16f8a 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -98,15 +98,15 @@ end ---@param val_func? fun(e: T): integer @ 计算权值的函数,对int[]可不写 ---@param reverse? boolean @ 是否反排?反排的话优先返回权值小的元素 function fk.sorted_pairs(t, val_func, reverse) - val_func = val_func or function(e) return e end local t2 = table.simpleClone(t) -- 克隆一次表,用作迭代器上值 + local t_vals = table.map(t2, val_func or function(e) return e end) local iter = function() local max_idx, max, max_val = -1, nil, nil for i, v in ipairs(t2) do if not max then - max_idx, max, max_val = i, v, val_func(v) + max_idx, max, max_val = i, v, t_vals[i] else - local val = val_func(v) + local val = t_vals[i] local checked = val > max_val if reverse then checked = not checked end if checked then @@ -116,6 +116,7 @@ function fk.sorted_pairs(t, val_func, reverse) end if max_idx == -1 then return nil, nil end table.remove(t2, max_idx) + table.remove(t_vals, max_idx) return -1, max, max_val end return iter, nil, 1 diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 13c9f0c8aa368c961a1926ba45170ce03e032e58..c52c8fde600e3642bff0c527e95dea10820e0490 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -635,7 +635,14 @@ function fk.CreateTreasure(spec) return card end ----@param spec GameMode +---@class GameModeSpec: GameMode +---@field public winner_getter? fun(self: GameMode, victim: ServerPlayer): string +---@field public surrender_func? fun(self: GameMode, playedTime: number): string +---@field public is_counted? fun(self: GameMode, room: Room): boolean +---@field public get_adjusted? fun(self: GameMode, player: ServerPlayer): table +---@field public reward_punish? fun(self: GameMode, victim: ServerPlayer, killer: ServerPlayer) + +---@param spec GameModeSpec ---@return GameMode function fk.CreateGameMode(spec) assert(type(spec.name) == "string") diff --git a/lua/server/ai/ai.lua b/lua/server/ai/ai.lua index 36ed560fd62742053cf9554bdc149a4dc6e72e94..9d11f6dcab3c904e729ae24e3ed8e34b06d78cfe 100644 --- a/lua/server/ai/ai.lua +++ b/lua/server/ai/ai.lua @@ -79,6 +79,17 @@ function AI:getSelectedCards() return self.handler.pendings end +---@return Card? +function AI:getSelectedCard() + if not self:isInDashboard() then return Util.DummyTable end + local handler = self.handler + if handler.selected_card then return handler.selected_card end + if not handler.skill_name then return end + local skill = Fk.skills[handler.skill_name] + if not skill:isInstanceOf(ViewAsSkill) then return end + return skill:viewAs(handler.pendings) +end + ---@return ServerPlayer[] function AI:getSelectedTargets() if not self:isInDashboard() then return Util.DummyTable end @@ -108,6 +119,30 @@ function AI:selectSkill(skill_name, selected) self.handler:update("SkillButton", skill_name, "click", { selected = selected }) end +function AI:unSelectAllCards() + for _, id in ipairs(self:getSelectedCards()) do + self:selectCard(id, false) + end +end + +function AI:unSelectAllTargets() + for _, p in ipairs(self:getSelectedTargets()) do + self:selectTarget(p, false) + end +end + +function AI:unSelectSkill() + local skill = self:getSelectedSkill() + if not skill then return end + self:selectSkill(skill, false) +end + +function AI:unSelectAll() + self:unSelectSkill() + self:unSelectAllCards() + self:unSelectAllTargets() +end + function AI:okButtonEnabled() if not self:isInDashboard() then return false end return self.handler:feasible() @@ -121,6 +156,7 @@ end function AI:makeReply() Self = self.player + -- local now = os.getms() local fn = self["handle" .. self.command] local ret = "__cancel" if fn then @@ -133,6 +169,7 @@ function AI:makeReply() end if ret == "" then ret = "__cancel" end self.handler = nil + -- printf("%s 在%fms后得出结果:%s", self.command, (os.getms() - now) / 1000, json.encode(ret)) return ret end diff --git a/lua/server/ai/init.lua b/lua/server/ai/init.lua index 07e569938132743cb625d15d3942ebe000ff0231..60b1a7f9b755b25cca081cf83ca4626b6bbbac49 100644 --- a/lua/server/ai/init.lua +++ b/lua/server/ai/init.lua @@ -4,18 +4,27 @@ AI = require "server.ai.ai" TrustAI = require "server.ai.trust_ai" RandomAI = require "server.ai.random_ai" ---[[ 在release版暂时不启动。 +---[[ 在release版暂时不启动。 SmartAI = require "server.ai.smart_ai" ---[[ 调试中,暂且不加载额外的AI。 -- load ai module from packages -local directories = FileIO.ls("packages") -require "packages.standard.ai" -require "packages.standard_cards.ai" -require "packages.maneuvering.ai" +local directories +if UsingNewCore then + directories = FileIO.ls("..") + -- require "standard.ai" + require "standard_cards.ai" + -- require "maneuvering.ai" +else + directories = FileIO.ls("packages") + require "packages.standard.ai" + require "packages.standard_cards.ai" + require "packages.maneuvering.ai" +end table.removeOne(directories, "standard") table.removeOne(directories, "standard_cards") table.removeOne(directories, "maneuvering") +--[[ local _disable_packs = json.decode(fk.GetDisabledPacks()) for _, dir in ipairs(directories) do diff --git a/lua/server/ai/smart_ai.lua b/lua/server/ai/smart_ai.lua index b10200342aea6de5091a7b1187c23ed9ad5de15e..166414ebab1a75dfb53b23cb638d3dfdb3c029c1 100644 --- a/lua/server/ai/smart_ai.lua +++ b/lua/server/ai/smart_ai.lua @@ -18,24 +18,19 @@ -- TODO: 更加详细的文档 --]] ----@class SmartAI: AI +---@class SmartAI: TrustAI ---@field private _memory table @ AI底层的空间换时间机制 ---@field public friends ServerPlayer[] @ 队友 ---@field public enemies ServerPlayer[] @ 敌人 -local SmartAI = AI:subclass("SmartAI") - ----@type table -local smart_cb = {} +local SmartAI = TrustAI:subclass("SmartAI") -- 哦,我懒得写出闪之类的,不得不继承一下,饶了我吧 function SmartAI:initialize(player) - AI.initialize(self, player) - self.cb_table = smart_cb - self.player = player + TrustAI.initialize(self, player) end function SmartAI:makeReply() self._memory = {} - return AI.makeReply(self) + return TrustAI.makeReply(self) end function SmartAI:__index(k) @@ -72,183 +67,79 @@ end -- 面板相关交互:对应操控手牌区、技能面板、直接选择目标的交互 -- 对应UI中的"responding"状态和"playing"状态 -- AI代码需要像实际操作UI那样完成以下几个任务: --- * 点击技能按钮(出牌阶段或者打算使用ViewAsSkill) --- * 技能如果带有interaction,则选择interaction --- * 如果需要的话点选手牌 +-- * 点击技能按钮,完成interaction与子卡选择;或者直接点可用手牌 -- * 选择目标 -- * 点确定 --- 这些步骤归结起来,就是让AI想办法返回如下定义的UseReply --- 或者返回nil表示点取消 --=================================================== ----@class UseReply ----@field card? integer|string @ string情况下是json.encode后 ----@field targets? integer[] ----@field special_skill string @ 出牌阶段空闲点使用实体卡特有 ----@field interaction_data any @ 因技能而异,一般都是nil - ----@param card integer|table ----@param targets? integer[] ----@param special_skill? string ----@param interaction_data? any -function SmartAI:buildUseReply(card, targets, special_skill, interaction_data) - if type(card) == "table" then card = json.encode(card) end - return { - card = card, - targets = targets or {}, - special_skill = special_skill, - interaction_data = interaction_data, - } -end - --- AskForUseActiveSkill: 询问发动主动技/视为技 --- * 此处 UseReply.card 必定由 json.encode 而来 --- * 且原型为 { skill = skillName, subcards = integer[] } ----------------------------------------------------------- - ----@type table -fk.ai_active_skill = {} - -smart_cb["AskForUseActiveSkill"] = function(self, jsonData) - local data = json.decode(jsonData) - local skillName, prompt, cancelable, extra_data = table.unpack(data) - - local skill = Fk.skills[skillName] - skill._extra_data = extra_data - - local ret = self:callFromTable(fk.ai_active_skill, nil, skillName, - self, prompt, cancelable, extra_data) - - if ret then return json.encode(ret) end - if cancelable then return "" end - return RandomAI.cb_table["AskForUseActiveSkill"](self, jsonData) -end - --- AskForUseCard: 询问使用卡牌 --- 判断函数一样返回UseReply,此时卡牌可能是integer或者string --- 为string的话肯定是由ViewAsSkill转化而来 --- 真的要考虑ViewAsSkill吗,害怕 ---------------------------------------------------------- - --- 使用牌相关——同时见于主动使用和响应式使用。 ---- 键是prompt的第一项或者牌名,优先prompt,其次name,实在不行trueName。 ----@type table -fk.ai_use_card = setmetatable({}, { - __index = function(_, k) - -- FIXME: 感觉不妥 - local c = Fk.all_card_types[k] - if not c then return nil end - if c.type == Card.TypeEquip then - return function(self, pattern, prompt, cancelable, extra_data) - local slashes = self:getCards(k, "use", extra_data) - if #slashes == 0 then return nil end - - return self:buildUseReply(slashes[1].id) - end - end - end, -}) - -local defauld_use_card = function(self, pattern, _, cancelable, exdata) - if cancelable then return nil end - local cards = self:getCards(pattern, "use", exdata) - if #cards == 0 then return nil end - - -- TODO: 目标 - return self:buildUseReply(cards[1].id) -end - ---- 请求使用,先试图使用prompt,再试图使用card_name,最后交给随机AI -smart_cb["AskForUseCard"] = function(self, jsonData) - local data = json.decode(jsonData) - local card_name, pattern, prompt, cancelable, extra_data = table.unpack(data) - - local prompt_prefix = prompt:split(":")[1] - local key - if fk.ai_use_card[prompt_prefix] then - key = prompt_prefix - elseif fk.ai_use_card[card_name] then - key = card_name - else - local tmp = card_name:split("__") - key = tmp[#tmp] - end - local ret = self:callFromTable(fk.ai_use_card, defauld_use_card, key, - self, pattern, prompt, cancelable, extra_data) - - if ret then return json.encode(ret) end - if cancelable then return "" end - return RandomAI.cb_table["AskForUseCard"](self, jsonData) +--- 主动技/视为技在SmartAI中几个选牌选目标的函数 +---@class SmartAISkillSpec +---@field name string +---@field will_use? fun(skill: ActiveSkill, ai: SmartAI, card: Card): boolean? +---@field choose_interaction? fun(skill: ActiveSkill, ai: SmartAI): boolean? +---@field choose_cards? fun(skill: ActiveSkill, ai: SmartAI): boolean? +---@field choose_targets fun(skill: ActiveSkill, ai: SmartAI, card: Card?): any + +---@type table +fk.ai_skills = {} + +---@param spec SmartAISkillSpec +function SmartAI.static:registerActiveSkill(spec, key) + spec.will_use = spec.will_use or Util.FalseFunc + -- spec.choose_interaction = spec.choose_interaction or Util.FalseFunc + -- spec.choose_cards = spec.choose_cards or Util.FalseFunc + spec.choose_targets = spec.choose_targets or Util.FalseFunc + fk.ai_skills[key or spec.name] = spec end --- AskForResponseCard: 询问打出卡牌 --- 注意事项同前 -------------------------------------- - --- 一样的牌名或者prompt做键优先prompt ----@type table -fk.ai_response_card = {} - -local defauld_response_card = function(self, pattern, _, cancelable) - if cancelable then return nil end - local cards = self:getCards(pattern, "response") - if #cards == 0 then return nil end - return self:buildUseReply(cards[1].id) -end - --- 同前 -smart_cb["AskForResponseCard"] = function(self, jsonData) - local data = json.decode(jsonData) - local card_name, pattern, prompt, cancelable, extra_data = table.unpack(data) - - local prompt_prefix = prompt:split(":")[1] - local key - if fk.ai_response_card[prompt_prefix] then - key = prompt_prefix - elseif fk.ai_response_card[card_name] then - key = card_name - else - local tmp = card_name:split("__") - key = tmp[#tmp] - end - local ret = self:callFromTable(fk.ai_response_card, defauld_response_card, key, - self, pattern, prompt, cancelable, extra_data) - - if ret then return json.encode(ret) end - if cancelable then return "" end - return RandomAI.cb_table["AskForResponseCard"](self, jsonData) +---@param spec SmartAISkillSpec +---@param key string? 可以指定不同于spec.name的key 以便复用 +---@diagnostic disable-next-line +function SmartAI:registerActiveSkill(spec, key) + error("This is a static method. Please use SmartAI:registerActiveSkill(...)") end -- PlayCard: 出牌阶段空闲时间点使用牌/技能 --- 老规矩得丢一个UseReply回来,但是自由度就高得多了 --- 需要完成的任务:从众多亮着的卡、技能中选一个 --- 考虑要不要用?用的话就用,否则选下个 --- 至于如何使用,可以复用askFor中的函数 ----------------------------------------------- -smart_cb["PlayCard"] = function(self) - local extra_use_data = { playing = true } - local cards = self:getCards(".", "use", extra_use_data) - - local card_names = {} - for _, cd in ipairs(cards) do - -- TODO: 视为技 - -- 视为技对应的function一般会返回一张印出来的卡,又要纳入新的考虑范围了 - -- 不过这种根据牌名判断的逻辑而言 可能需要调用多次视为技函数了 - -- 要用好空间换时间 - table.insertIfNeed(card_names, cd.name) - end - -- TODO: 主动技 - -- 第二步:考虑使用其中之一 - local value_func = function(str) return #str end - for _, name in fk.sorted_pairs(card_names, value_func) do - if true then - local ret = self:callFromTable(fk.ai_use_card, nil, - fk.ai_use_card[name] and name or name:split("__")[2], - self, name, "", true, extra_use_data) +function SmartAI:handlePlayCard() + local to_use = self:getEnabledCards() + table.insertTable(to_use, self:getEnabledSkills()) + + -- local value_func = function(str) return 1 end + -- for _, id_or_skill in fk.sorted_pairs(to_use, value_func) do + for _, id_or_skill in ipairs(to_use) do + local skill + local card + + if type(id_or_skill) == "string" then + skill = Fk.skills[id_or_skill] --[[@as ActiveSkill]] + else + card = Fk:getCardById(id_or_skill) + skill = card.skill + end + + local spec = fk.ai_skills[skill.name] + if spec and spec.will_use(skill, self, card) then + if not card then + self:selectSkill(id_or_skill, true) + if spec.choose_interaction and not spec.choose_interaction(skill, self) then + goto continue + end + if spec.choose_cards and not spec.choose_cards(skill, self) then + goto continue + end + card = self:getSelectedCard() -- 可能nil 但总之也要进入最终选目标阶段 + else + self:selectCard(id_or_skill, true) + end - if ret then return json.encode(ret) end + local ret = spec.choose_targets(skill, self, card) + if ret and ret ~= "" then return ret end end + + ::continue:: + self:unSelectAll() end return "" @@ -265,11 +156,10 @@ end -- 函数返回true或者false即可。 ----------------------------- ----@type table +---@type table fk.ai_skill_invoke = {} -smart_cb["AskForSkillInvoke"] = function(self, jsonData) - local data = json.decode(jsonData) +function SmartAI:handleAskForSkillInvoke(data) local skillName, prompt = data[1], data[2] local ask = fk.ai_skill_invoke[skillName] @@ -280,7 +170,7 @@ smart_cb["AskForSkillInvoke"] = function(self, jsonData) elseif Fk.skills[skillName].frequency == Skill.Frequent then return "1" else - return RandomAI.cb_table["AskForSkillInvoke"](self, jsonData) + return math.random() < 0.5 and "1" or "" end end @@ -309,38 +199,4 @@ end -- sorted_pairs 见 core/util.lua --- 合法性检测相关 --- 以后估计会单开个合法性类然后改成套壳吧 ---================================================= - --- TODO: 这东西估计会变成一个单独模块 -local invalid_func_table = { - use = function(player, card, extra_data) - local playing = extra_data and extra_data.playing - return Player.prohibitUse(player, card) or (playing and not player:canUse(card)) - end, - response = Player.prohibitResponse, - discard = Player.prohibitDiscard, -} - ---- 根据pattern获得所有手中的牌。 ----@param pattern string ----@param validator? string @ 合法检测,须为use, response, discard之一或空 ----@param extra_data? UseExtraData @ 出牌阶段用 ----@return Card[] -function SmartAI:getCards(pattern, validator, extra_data) - validator = validator or "" - extra_data = extra_data or Util.DummyTable - local invalid_func = invalid_func_table[validator] or Util.FalseFunc - local exp = Exppattern:Parse(pattern) - - local cards = table.map(self.player:getHandlyIds(), Util.Id2CardMapper) - local ret = table.filter(cards, function(c) - return exp:match(c) and not invalid_func(self.player, c, extra_data) - end) - - -- TODO: 考虑视为技,这里可以再返回一些虚拟牌 - return ret -end - return SmartAI diff --git a/lua/server/events/movecard.lua b/lua/server/events/movecard.lua index 6ae5e86d332c139b21e3aca7ef9e1cba4b50f479..1d0b28055eaff1da4d957b4ad8fa945e78a2141f 100644 --- a/lua/server/events/movecard.lua +++ b/lua/server/events/movecard.lua @@ -397,16 +397,17 @@ end ---@param cards integer|integer[] @ 移动的牌 ---@param skillName? string @ 技能名 ---@param convert? boolean @ 是否可以替换装备(默认可以) ----@param proposer? ServerPlayer @ 操作者 +---@param proposer? ServerPlayer | integer @ 操作者 function MoveEventWrappers:moveCardIntoEquip(target, cards, skillName, convert, proposer) convert = (convert == nil) and true or convert skillName = skillName or "" cards = type(cards) == "table" and cards or {cards} + proposer = type(proposer) == "number" and self:getPlayerById(proposer) or proposer + local proposerId = proposer and proposer.id or nil local moves = {} for _, cardId in ipairs(cards) do local card = Fk:getCardById(cardId) local fromId = self.owner_map[cardId] - local proposerId = proposer and proposer.id or nil if target:canMoveCardIntoEquip(cardId, convert) then if target:hasEmptyEquipSlot(card.sub_type) then table.insert(moves,{ids = {cardId}, from = fromId, to = target.id, toArea = Card.PlayerEquip, moveReason = fk.ReasonPut,skillName = skillName,proposer = proposerId}) diff --git a/lua/server/network.lua b/lua/server/network.lua index 9eac1b4301e1485c7beb45b87e0fa20bfbac07ce..75098b47f3bb4391c6596b2e40126196d866f44a 100644 --- a/lua/server/network.lua +++ b/lua/server/network.lua @@ -299,8 +299,7 @@ end function Request:_finish() local room = self.room surrenderCheck(room) - -- FIXME: 这里QML中有个bug,这个命令应该是用来暗掉玩家面板的 - -- room:doBroadcastNotify("CancelRequest", "") + for _, p in ipairs(self.players) do p.serverplayer:setThinking(false) -- 这个什么timewaste_count也该扔了 @@ -327,7 +326,7 @@ function Request:_finish() for _, isHuman in pairs(self.send_success) do if not self.ai_start_time then break end if not isHuman then - local to_delay = 500 - (os.getms() - self.ai_start_time) / 1000 + local to_delay = 800 - (os.getms() - self.ai_start_time) / 1000 room:delay(to_delay) break end diff --git a/lua/server/room.lua b/lua/server/room.lua index 3a61e73b1173a6a4bc3a27a75c529a62cf61c63a..6a29da491e39049555bfaec3e740a651d2313e68 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -1521,7 +1521,7 @@ end ---@return integer[] @ 选择的id function Room:askForCardsChosen(chooser, target, min, max, flag, reason, prompt) if min == 1 and max == 1 then - return { self:askForCardChosen(chooser, target, flag, reason) } + return { self:askForCardChosen(chooser, target, flag, reason, prompt) } end local cards diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 28fef5ebf1e9c1aaa2d07083b7a602d6fc146349..67a1b94c85d5054cddab7d3f2000ad4bc0b77822 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -40,7 +40,7 @@ function ServerPlayer:initialize(_self) self._prelighted_skills = {} self._timewaste_count = 0 - self.ai = TrustAI:new(self) + self.ai = SmartAI:new(self) end ---@param command string diff --git a/maneuvering/ai/init.lua b/maneuvering/ai/init.lua index 7785abb085032818eeeb500813de222332e35567..9ebcc10ff3b83eebeb3f7b4ed026ffbaf9e295c6 100644 --- a/maneuvering/ai/init.lua +++ b/maneuvering/ai/init.lua @@ -1,180 +1,7 @@ ---[[ -fk.ai_card.thunder__slash = fk.ai_card.slash -fk.ai_use_play.thunder__slash = fk.ai_use_play.slash -fk.ai_card.fire__slash = fk.ai_card.slash -fk.ai_use_play.fire__slash = fk.ai_use_play.slash -fk.ai_card.analeptic = { - intention = 60, -- 身份值 - value = 5, -- 卡牌价值 - priority = 3 -- 使用优先值 -} +SmartAI:registerActiveSkill(fk.ai_skills["slash_skill"], "thunder__slash_skill") +SmartAI:registerActiveSkill(fk.ai_skills["slash_skill"], "fire__slash_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "analeptic_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "iron_chain_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "fire_attack_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "supply_shortage_skill") -fk.ai_use_play["analeptic"] = function(self, card) - local cards = table.map(self.player:getCardIds("&he"), function(id) - return Fk:getCardById(id) - end) - self:sortValue(cards) - for _, sth in ipairs(self:getActives("slash")) do - local slash = nil - if sth:isInstanceOf(Card) then - if sth.skill:canUse(self.player, sth) and not self.player:prohibitUse(sth) then - slash = sth - end - else - local selected = {} - for _, c in ipairs(cards) do - if sth:cardFilter(c.id, selected) then - table.insert(selected, c.id) - end - end - local tc = sth:viewAs(selected) - if tc and tc:matchPattern("slash") and tc.skill:canUse(self.player, tc) and not self.player:prohibitUse(tc) then - slash = tc - end - end - if slash then - fk.ai_use_play.slash(self, slash) - if self.use_id then - self.use_id = card.id - self.use_tos = {} - break - end - end - end -end - -fk.ai_card.iron_chain = { - intention = function(self, card, from) - if self.player.chained then - return -80 - end - return 80 - end, -- 身份值 - value = 2, -- 卡牌价值 - priority = 3 -- 使用优先值 -} - -fk.ai_use_play["iron_chain"] = function(self, card) - for _, p in ipairs(self.friends) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) and p.chained then - table.insert(self.use_tos, p.id) - end - end - self:sort(self.enemies) - for _, p in ipairs(self.enemies) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) and not p.chained then - table.insert(self.use_tos, p.id) - end - end - if #self.use_tos < 2 then - self.use_tos = {} - else - self.use_id = card.id - end -end - -fk.ai_use_play["recast"] = function(self, card) - if self.command == "PlayCard" then - self.use_id = card.id - self.special_skill = "recast" - end -end - -fk.ai_card.fire_attack = { - intention = 90, -- 身份值 - value = 3, -- 卡牌价值 - priority = 4 -- 使用优先值 -} - -fk.ai_use_play["fire_attack"] = function(self, card) - self:sort(self.enemies) - for _, p in ipairs(self.enemies) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #self.player:getCardIds("h") > 2 then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end -end - -fk.ai_discard["fire_attack_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) - local use = self:eventData("UseCard") - for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do - if self:isEnemy(p) then - local cards = table.map(self.player:getCardIds("h"), function(id) - return Fk:getCardById(id) - end) - local exp = Exppattern:Parse(pattern) - cards = table.filter(cards, function(c) - return exp:match(c) - end) - if #cards > 0 then - self:sortValue(cards) - return { cards[1].id } - end - end - end -end - -fk.ai_nullification.fire_attack = function(self, card, to, from, positive) - if positive then - if self:isFriend(to) and #to:getCardIds("h") > 0 and #from:getCardIds("h") > 0 then - if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then - self.use_id = self.avail_cards[1] - end - end - else - if self:isEnemy(to) and #to:getCardIds("h") > 0 and #from:getCardIds("h") > 1 then - if #self.avail_cards > 1 or self:isWeak(to) then - self.use_id = self.avail_cards[1] - end - end - end -end - -fk.ai_card.fire_attack = { - intention = 120, -- 身份值 - value = 2, -- 卡牌价值 - priority = 2 -- 使用优先值 -} - -fk.ai_use_play["supply_shortage"] = function(self, card) - self:sort(self.enemies) - for _, p in ipairs(self.enemies) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) and not p.chained then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end -end - -fk.ai_nullification.supply_shortage = function(self, card, to, from, positive) - if positive then - if self:isFriend(to) then - if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then - self.use_id = self.avail_cards[1] - end - end - else - if self:isEnemy(to) then - if #self.avail_cards > 1 or self:isWeak(to) then - self.use_id = self.avail_cards[1] - end - end - end -end - -fk.ai_card.supply_shortage = { - intention = 130, -- 身份值 - value = 2, -- 卡牌价值 - priority = 1 -- 使用优先值 -} - -fk.ai_skill_invoke["#fan_skill"] = function(self) - local use = self:eventData("UseCard") - for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do - if not self:isFriend(p) then - return true - end - end -end ---]] diff --git a/standard/ai/init.lua b/standard/ai/init.lua index e25c86128b8e74044e5b1f55885c98d171498cdb..8d55696c2348365e1e1f7629bb13bb6f73044af7 100644 --- a/standard/ai/init.lua +++ b/standard/ai/init.lua @@ -1,4 +1,8 @@ -require "packages.standard.ai.aux_skills" +if UsingNewCore then + require "standard.ai.aux_skills" +else + require "packages.standard.ai.aux_skills" +end -- 魏国 diff --git a/standard/game_rule.lua b/standard/game_rule.lua index fc09c62c2ad53d622785b7f0c7f1c7c65135e1e3..f795c9b7ee554a5da08aa237a9fd31930dbf8309 100644 --- a/standard/game_rule.lua +++ b/standard/game_rule.lua @@ -81,13 +81,11 @@ GameRule = fk.CreateTriggerSkill{ end, [fk.BuryVictim] = function() player:bury() - if room.tag["SkipNormalDeathProcess"] or player.rest > 0 then + if room.tag["SkipNormalDeathProcess"] or player.rest > 0 or (data.extra_data and data.extra_data.skip_reward_punish) then return false end local damage = data.damage - if damage and damage.from then - Fk.game_modes[room.settings.gameMode]:deathRewardAndPunish(player, damage.from) - end + Fk.game_modes[room.settings.gameMode]:deathRewardAndPunish(player, damage and damage.from) end, default = function() print("game_rule: Event=" .. event) diff --git a/standard_cards/ai/init.lua b/standard_cards/ai/init.lua index 9c94733a1ab573884f644a275e34a9e7d157d1be..79606655b85e416610a75abbb6e1eaf887a96c9a 100644 --- a/standard_cards/ai/init.lua +++ b/standard_cards/ai/init.lua @@ -1,87 +1,37 @@ --- TODO: 合法性的方便函数 --- TODO: 关于如何选择多个目标 --- TODO: 关于装备牌 - --- 基本牌:杀,闪,桃 - ----@param from ServerPlayer ----@param to ServerPlayer ----@param card Card -local function tgtValidator(from, to, card) - return not from:prohibitUse(card) and - not from:isProhibited(to, card) and - true -- feasible -end - -local function justUse(self, card_name, extra_data) - local slashes = self:getCards(card_name, "use", extra_data) - if #slashes == 0 then return nil end - - return self:buildUseReply(slashes[1].id) -end - ----@param self SmartAI ----@param card_name string -local function useToEnemy(self, card_name, extra_data) - local slashes = self:getCards(card_name, "use", extra_data) - if #slashes == 0 then return nil end - - -- TODO: 目标合法性 - local targets = {} - if self.enemies[1] then - table.insert(targets, self.enemies[1].id) - else - return nil - end - - return self:buildUseReply(slashes[1].id, targets) -end - -fk.ai_use_card["slash"] = function(self, pattern, prompt, cancelable, extra_data) - return useToEnemy(self, "slash", extra_data) -end - -fk.ai_use_card["jink"] = function(self, pattern, prompt, cancelable, extra_data) - return justUse(self, "jink", extra_data) -end - -fk.ai_use_card["peach"] = function(self, _, _, _, extra_data) - local cards = self:getCards("peach", "use", extra_data) - if #cards == 0 then return nil end - - return self:buildUseReply(cards[1].id) -end - --- 自救见军争卡牌AI -fk.ai_use_card["#AskForPeaches"] = function(self) - local room = self.room - local deathEvent = room.logic:getCurrentEvent() - local data = deathEvent.data[1] ---@type DyingStruct - - -- TODO: 关于救不回来、神关羽之类的更复杂逻辑 - -- TODO: 这些逻辑感觉不能写死在此函数里面,得想出更加多样的办法 - if self:isFriend(room:getPlayerById(data.who)) then - return fk.ai_use_card["peach"](self) - end - return nil -end - -fk.ai_use_card["dismantlement"] = function(self, pattern, prompt, cancelable, extra_data) - return useToEnemy(self, "dismantlement", extra_data) -end - -fk.ai_use_card["snatch"] = function(self, pattern, prompt, cancelable, extra_data) - return useToEnemy(self, "snatch", extra_data) -end - -fk.ai_use_card["duel"] = function(self, pattern, prompt, cancelable, extra_data) - return useToEnemy(self, "duel", extra_data) -end - -fk.ai_use_card["ex_nihilo"] = function(self, pattern, prompt, cancelable, extra_data) - return justUse(self, "ex_nihilo", extra_data) -end - -fk.ai_use_card["indulgence"] = function(self, pattern, prompt, cancelable, extra_data) - return useToEnemy(self, "indulgence", extra_data) -end +SmartAI:registerActiveSkill { + name = "__just_use", + will_use = Util.TrueFunc, + choose_targets = function(skill, ai, card) + return ai:doOKButton() + end, +} + +SmartAI:registerActiveSkill { + name = "__use_to_enemy", + will_use = Util.TrueFunc, + choose_targets = function(skill, ai, card) + local targets = ai:getEnabledTargets() + for _, p in ipairs(targets) do + if ai:isEnemy(p) then + ai:selectTarget(p, true) + break + end + end + return ai:doOKButton() + end, +} + +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "slash_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "dismantlement_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "snatch_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "duel_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__use_to_enemy"], "indulgence_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "jink_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "peach_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "ex_nihilo_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "savage_assault_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "archery_attack_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "god_salvation_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "amazing_grace_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "lightning_skill") +SmartAI:registerActiveSkill(fk.ai_skills["__just_use"], "default_equip_skill") diff --git a/standard_cards/init.lua b/standard_cards/init.lua index 89713f09ba226aea46c6bf21428f01363683a850..3cb94182193145ae73dd6e84cd4b98ec30c50097 100644 --- a/standard_cards/init.lua +++ b/standard_cards/init.lua @@ -713,7 +713,7 @@ local lightningSkill = fk.CreateActiveSkill{ } room:moveCards{ - ids = Card:getIdList(effect.card), + ids = room:getSubcardsByRule(effect.card, { Card.Processing }), toArea = Card.DiscardPile, moveReason = fk.ReasonUse }