diff --git a/lua/server/ai/skill.lua b/lua/server/ai/skill.lua index 6047c6a7e9b2140848dd22075133771631f29f9f..24edf16cf24ac502cc7c2736d9fc03ea0d98db54 100644 --- a/lua/server/ai/skill.lua +++ b/lua/server/ai/skill.lua @@ -59,6 +59,16 @@ end function SkillAI:thinkForSkillInvoke(ai, skill_name, prompt) end +---@param ai SmartAI +---@param choices string[] @ 可选选项列表 +---@param skill_name? string @ 技能名 +---@param prompt? string @ 提示信息 +---@param detailed? boolean @ 选项详细描述 +---@param all_choices? string[] @ 所有选项(不可选变灰) +---@return string, integer? +function SkillAI:thinkForChoice(ai, choices, skill_name, prompt, detailed, all_choices) +end + -- 搜索类方法:怎么走下一步? -- choose系列的函数都是用作迭代算子的,因此它们需要能计算出所有的可选情况 -- (至少是需要所有的以及觉得可行的可选情况,如果另外写AI的话) @@ -194,6 +204,7 @@ function SkillAI:onEffect(logic, cardEffectEvent) end ---@field think? fun(self: SkillAI, ai: SmartAI): any?, integer? ---@field think_card_chosen? fun(self: SkillAI, ai: SmartAI, target: ServerPlayer, flag: string, prompt: string?): integer, integer? ---@field think_skill_invoke? fun(self: SkillAI, ai: SmartAI, skill_name: string, prompt: string?): boolean, integer? +---@field think_choice? fun(self: SkillAI, ai: SmartAI, choices:string[], skill_name: string, prompt: string, detailed:string, all_choices: string[]): string, integer ---@field choose_interaction? fun(self: SkillAI, ai: SmartAI): boolean? ---@field choose_cards? fun(self: SkillAI, ai: SmartAI): boolean? ---@field choose_targets? fun(self: SkillAI, ai: SmartAI): any, integer? diff --git a/lua/server/ai/smart_ai.lua b/lua/server/ai/smart_ai.lua index 743bb1d5ba0ee3d1c0e008aab84d63e18d0c3b4a..f5373251fd0efdaa3dd56b22a9595156ad8ce929 100644 --- a/lua/server/ai/smart_ai.lua +++ b/lua/server/ai/smart_ai.lua @@ -6,6 +6,8 @@ --]] +fk.ai_card_keep_value = {} + ---@class SmartAI: TrustAI, AIUtil ---@field private _memory table @ AI底层的空间换时间机制 ---@field public friends ServerPlayer[] @ 队友 @@ -74,6 +76,7 @@ function SmartAI.static:setSkillAI(key, spec, inherit) think = "think", think_card_chosen = "thinkForCardChosen", think_skill_invoke = "thinkForSkillInvoke", + think_choice = "thinkForChoice", choose_interaction = "chooseInteraction", choose_cards = "chooseCards", choose_targets = "chooseTargets", @@ -330,6 +333,17 @@ function SmartAI:handleAskForSkillInvoke(data) end end +function SmartAI:handleAskForChoice(data) + local choices, skillName, prompt, detailed, allChoices = table.unpack(data) + local ai = fk.ai_skills[skillName] + if ai then + local ret = ai:thinkForChoice(self, choices, prompt, detailed, allChoices) + return ret + else + return choices[1] + end +end + -- 敌友判断相关。 -- 目前才开始,做个明身份打牌的就行了。 --======================================== diff --git a/lua/server/ai/util.lua b/lua/server/ai/util.lua index 2f3c8326e61f9e155541a6bf91c346d5faed1137..2cc979949dda4019844fa8a59d69e1b0a1647798 100644 --- a/lua/server/ai/util.lua +++ b/lua/server/ai/util.lua @@ -2,4 +2,39 @@ ---@class AIUtil local AIUtil = {} -- mixin +--- 根据传入的牌ids 判断权重 +---@param cards integer[] @ 牌ids +---@param number integer @ 需要弃置几张 +---@param filter? fun(integer) @ 条件判断 判断权重大于 或 小于 n +---@return integer[] @ 牌ids +function AIUtil:getChoiceCardsByKeepValue(cards, number, filter) + number = math.max(1, math.min(number, #cards)) + + --- 根据每张牌的id 查找权重 + local list = {} + for i, id in ipairs(cards) do + local card = Fk:getCardById(id) + local value = fk.ai_card_keep_value[card.name] or -50 + + if (not filter) or filter(value) then + table.insert(list, {id, value}) + end + end + + --- 根据权重从低往高排序 + table.sort(list, function(a, b) + return a[2] < b[2] + end) + + --- 将排序后的card id返回 + local ret = {} + for _, entry in ipairs(list) do + table.insert(ret, entry[1]) + if #ret == number then break end --- 一旦收集到足够的牌,就停止循环 + end + + return ret +end + + return AIUtil diff --git a/maneuvering/ai/init.lua b/maneuvering/ai/init.lua index fa8ac2f3db1ee32631180825342e88077b0fd5e1..80fec18d6edf4faec9f9ae7b02720c5b284035b1 100644 --- a/maneuvering/ai/init.lua +++ b/maneuvering/ai/init.lua @@ -1,4 +1,7 @@ +fk.ai_card_keep_value["thunder__slash"] = 20 SmartAI:setCardSkillAI("thunder__slash_skill", nil, "slash_skill") + +fk.ai_card_keep_value["fire__slash"] = 35 SmartAI:setCardSkillAI("fire__slash_skill", nil, "slash_skill") SmartAI:setCardSkillAI("iron_chain_skill", { @@ -31,7 +34,15 @@ SmartAI:setCardSkillAI("supply_shortage_skill") --[[ SmartAI:setSkillAI("analeptic_skill", just_use) --]] +fk.ai_card_keep_value["analeptic"] = 30 + +fk.ai_card_keep_value["iron_chain"] = 25 + +fk.ai_card_keep_value["fire_attack"] = 30 + +fk.ai_card_keep_value["supply_shortage"] = 45 +fk.ai_card_keep_value["guding_blade"] = 20 SmartAI:setTriggerSkillAI("#guding_blade_skill", { correct_func = function(self, logic, event, target, player, data) if self.skill:triggerable(event, target, player, data) then @@ -40,6 +51,7 @@ SmartAI:setTriggerSkillAI("#guding_blade_skill", { end, }) +fk.ai_card_keep_value["vine"] = 30 SmartAI:setTriggerSkillAI("#vine_skill", { correct_func = function(self, logic, event, target, player, data) local skill = self.skill @@ -52,3 +64,9 @@ SmartAI:setTriggerSkillAI("#vine_skill", { end end, }) + +fk.ai_card_keep_value["hualiu"] = 20 + +fk.ai_card_keep_value["silver_lion"] = 20 + +fk.ai_card_keep_value["fan"] = 20 diff --git a/maneuvering/init.lua b/maneuvering/init.lua index b083089a67cfdf0ddee69e4d696dcdd49b4d2039..e05e48e56ff631947b1314fdd613eb7641d358fd 100644 --- a/maneuvering/init.lua +++ b/maneuvering/init.lua @@ -144,7 +144,6 @@ local analepticSkill = fk.CreateActiveSkill{ end end } - local analepticEffect = fk.CreateTriggerSkill{ name = "analeptic_effect", global = true, @@ -179,8 +178,8 @@ local analepticEffect = fk.CreateTriggerSkill{ end end, } -Fk:addSkill(analepticEffect) +Fk:addSkill(analepticEffect) local analeptic = fk.CreateBasicCard{ name = "analeptic", suit = Card.Spade, diff --git a/standard/ai/init.lua b/standard/ai/init.lua index 0c6c1f1c2fba94a7d7b98f59d834cc89b51e549b..0e4605a3c9215aceb7c3349332fc834e308f86b0 100644 --- a/standard/ai/init.lua +++ b/standard/ai/init.lua @@ -25,7 +25,8 @@ SmartAI:setSkillAI("ganglie", { think = function(self, ai) local cards = ai:getEnabledCards() if #cards < 2 then return "" end - local to_discard = table.random(cards, 2) -- TODO: 用于选择最适合弃牌的ai函数 + + local to_discard = ai:getChoiceCardsByKeepValue(cards, 2) local cancel_val = ai:getBenefitOfEvents(function(logic) logic:damage{ from = ai.room.logic:getCurrentEvent().data[2], @@ -96,6 +97,54 @@ SmartAI:setSkillAI("fankui", { end, }) +SmartAI:setSkillAI("guicai", { + think = function(self, ai) + ---@type JudgeStruct + local dmg = ai.room.logic:getCurrentEvent().data[1] + local target = dmg.who + local isFriend = ai:isFriend(target) + + local function handleCardSelection(ai, cardPattern) + local cards = ai:getEnabledCards(cardPattern) + if #cards == 0 then + return {}, -1000 + elseif #cards == 1 then + return { cards = cards }, 100 + else + cards = ai:getChoiceCardsByKeepValue(cards, 1) + return { cards = cards }, 100 + end + end + + local function getResponseForReason(ai, reason, dmgCard, isFriend) + local patterns = { + indulgence = { matchPattern = ".|.|heart", friendPattern = ".|.|heart", enemyPattern = ".|.|^heart" }, + supply_shortage = { matchPattern = ".|.|club", friendPattern = ".|.|club", enemyPattern = ".|.|^club" }, + lightning = { matchPattern = ".|2~9|spade", friendPattern = ".|^2~9|^spade", enemyPattern = ".|2~9|spade" } + } + + local patternInfo = patterns[reason] + if not patternInfo then return {}, -1000 end + + local matchFunction = isFriend and patternInfo.friendPattern or patternInfo.enemyPattern + local matchResult = Exppattern:Parse(matchFunction):match(dmgCard) + + if (isFriend and not matchResult) or (not isFriend and matchResult) then + --- 如果目标是友方且不匹配友方结果,或者目标是敌方且匹配敌方结果(需要改判) + return handleCardSelection(ai, matchFunction) + else + --- 其他情况(目标是友方且匹配友方结果,或者目标是敌方且不匹配敌方结果) + return {}, -1000 + end + end + + local dmgCard = dmg.card + local response, value = getResponseForReason(ai, dmg.reason, dmgCard, isFriend) + + return response, value + end, +}) + SmartAI:setSkillAI("tuxi", { think = function(self, ai) local player = ai.player @@ -145,7 +194,10 @@ SmartAI:setSkillAI("jizhi", { SmartAI:setSkillAI("zhiheng", { think = function(self, ai) local player = ai.player - local cards = ai:getEnabledCards() + local cards = ai:getEnabledCards(".|.|.|hand|.|.|.") + + cards = ai:getChoiceCardsByKeepValue(cards, #cards, function(value) return value < 45 end) + return { cards = cards }, ai:getBenefitOfEvents(function(logic) logic:throwCard(cards, self.skill.name, player, player) logic:drawCards(player, #cards, self.skill.name) @@ -178,6 +230,51 @@ SmartAI:setSkillAI("yingzi", { think_skill_invoke = Util.TrueFunc, }) +SmartAI:setSkillAI("fanjian", { + think = function(self, ai) + local cards = ai:getEnabledCards() + local players = ai:getEnabledTargets() + + --- 获取手牌中权重偏大的牌 + local good_cards = ai:getChoiceCardsByKeepValue(cards, #cards, function(value) return value >= 45 end) + if (#good_cards / #cards) <= 0.8 then return {}, -1000 end + + local benefits = {} + + --- 遍历所有玩家,计算收益 + for _, target in ipairs(players) do + --- 计算获得手牌的收益 + local card_benefit = ai:getBenefitOfEvents(function(logic) + local c = ai.player:getCardIds("h")[1] --- 假设总是取第一张手牌,这里可能需要更复杂的逻辑 + logic:obtainCard(target, c, true, fk.ReasonGive) + end) + + --- 计算造成伤害的收益 + local damage_benefit = ai:getBenefitOfEvents(function(logic) + logic:damage{ + from = ai.player, + to = target, + damage = 1, + skillName = self.skill.name, + } + end) + + benefits[#benefits + 1] = { target, card_benefit + damage_benefit } + end + + table.sort(benefits, function(a, b) return a[2] < b[2] end) + + if #benefits == 0 then return {}, -1000 end + + return { targets = { benefits[1][1] } }, benefits[1][2] + end, + + --- 似乎反间不需要这个 + --- think_card_chosen = function (self, ai, target, flag, prompt) + + --- end, +}) + SmartAI:setSkillAI("xiaoji", { think_skill_invoke = function(self, ai, skill_name, prompt) return ai:getBenefitOfEvents(function(logic) @@ -186,6 +283,67 @@ SmartAI:setSkillAI("xiaoji", { end, }) +SmartAI:setSkillAI("tieqi", { + think_skill_invoke = function(self, ai, skill_name, prompt) + ---@type CardUseStruct + local dmg = ai.room.logic:getCurrentEvent().data[1] + local targets = dmg.tos + if not targets then return false end + + --- TODO 能跑,但是返回是0 + --- TODO 需要注意targets的问题 例如:方天多个目标 + -- local use_val = ai:getBenefitOfEvents(function(logic) + -- logic:useCard{ + -- from = ai.player.id, + -- to = targets[1], + -- card = dmg.card + -- } + -- end) + + -- if use_val >= 0 then + -- return true + -- end + + -- return false + + return ai:isEnemy(targets[1]) + end, +}) + +SmartAI:setSkillAI("qingnang", { + think = function(self, ai) + local player = ai.player + local cards = ai:getEnabledCards(".|.|.|hand|.|.|.") + local players = ai:getEnabledTargets() + + --- 对所有目标计算回血的收益 + local benefits = table.map(players, function(p) + return { p, ai:getBenefitOfEvents(function(logic) + --- @type RecoverStruct + logic:recover{ + who = p, + num = 1, + recoverBy = player + } + end)} + end) + + table.sort(benefits, function(a, b) return a[2] > b[2] end) + + if #benefits == 0 then return {}, -1000 end + + --- 尽量选择权重占比小的牌 + cards = ai:getChoiceCardsByKeepValue(cards, 1) + + --- 计算弃牌收益 + local throw = ai:getBenefitOfEvents(function(logic) + logic:throwCard(cards, self.skill.name, player, player) + end) + + return { targets = { benefits[1][1] }, cards = cards }, benefits[1][2] + throw + end, +}) + SmartAI:setSkillAI("biyue", nil, "jizhi") SmartAI:setSkillAI("wusheng", nil, "spear_skill") @@ -196,4 +354,4 @@ SmartAI:setSkillAI("guose", nil, "spear_skill") SmartAI:setSkillAI("jijiu", nil, "spear_skill") -SmartAI:setSkillAI("qixi", nil, "spear_skill") \ No newline at end of file +SmartAI:setSkillAI("qixi", nil, "spear_skill") diff --git a/standard_cards/ai/init.lua b/standard_cards/ai/init.lua index 67acbe448b1a32eebeeb234456f8e3a07cddc2b4..b5124f867ea2ff80798efadbff9881b83651d606 100644 --- a/standard_cards/ai/init.lua +++ b/standard_cards/ai/init.lua @@ -7,14 +7,18 @@ SmartAI:setCardSkillAI("default_card_skill", { end, }) +fk.ai_card_keep_value["slash"] = 10 SmartAI:setCardSkillAI("slash_skill", { estimated_benefit = 100, }, "default_card_skill") -- jink +fk.ai_card_keep_value["jink"] = 40 +fk.ai_card_keep_value["peach"] = 60 SmartAI:setCardSkillAI("peach_skill", nil, "default_card_skill") +fk.ai_card_keep_value["dismantlement"] = 45 SmartAI:setCardSkillAI("dismantlement_skill", { on_effect = function(self, logic, effect) local from = logic:getPlayerById(effect.from) @@ -39,6 +43,7 @@ SmartAI:setCardSkillAI("dismantlement_skill", { end, }) +fk.ai_card_keep_value["snatch"] = 45 SmartAI:setCardSkillAI("snatch_skill", { think_card_chosen = function(self, ai, target, _, __) local cards = target:getCardIds("hej") @@ -56,8 +61,12 @@ SmartAI:setCardSkillAI("snatch_skill", { }, "dismantlement_skill") -- duel +fk.ai_card_keep_value["duel"] = 30 + -- collateral_skill +fk.ai_card_keep_value["collateral"] = 10 +fk.ai_card_keep_value["ex_nihilo"] = 50 SmartAI:setCardSkillAI("ex_nihilo_skill", { on_use = function(self, logic, effect) self.skill:onUse(logic, effect) @@ -69,7 +78,9 @@ SmartAI:setCardSkillAI("ex_nihilo_skill", { }) -- nullification +fk.ai_card_keep_value["nullification"] = 55 +fk.ai_card_keep_value["savage_assault"] = 20 SmartAI:setCardSkillAI("savage_assault_skill", { on_use = function(self, logic, effect) self.skill:onUse(logic, effect) @@ -79,6 +90,7 @@ SmartAI:setCardSkillAI("savage_assault_skill", { end, }) +fk.ai_card_keep_value["archery_attack"] = 20 SmartAI:setCardSkillAI("archery_attack_skill", { on_use = function(self, logic, effect) self.skill:onUse(logic, effect) @@ -88,11 +100,16 @@ SmartAI:setCardSkillAI("archery_attack_skill", { end, }) +fk.ai_card_keep_value["god_salvation"] = 20 SmartAI:setCardSkillAI("god_salvation_skill", nil, "default_card_skill") -- amazing_grace_skill +fk.ai_card_keep_value["amazing_grace"] = 20 + -- lightning_skill +fk.ai_card_keep_value["lightning"] = -10 +fk.ai_card_keep_value["indulgence"] = 50 SmartAI:setCardSkillAI("indulgence_skill") SmartAI:setCardSkillAI("default_equip_skill", { @@ -131,6 +148,40 @@ SmartAI:setCardSkillAI("default_equip_skill", { end, }) +fk.ai_card_keep_value["crossbow"] = 30 + +fk.ai_card_keep_value["qinggang_sword"] = 20 + +fk.ai_card_keep_value["ice_sword"] = 20 + +fk.ai_card_keep_value["double_swords"] = 20 + +fk.ai_card_keep_value["blade"] = 25 + +fk.ai_card_keep_value["spear"] = 25 + +fk.ai_card_keep_value["axe"] = 45 + +fk.ai_card_keep_value["halberd"] = 10 + +fk.ai_card_keep_value["kylin_bow"] = 5 + +fk.ai_card_keep_value["eight_diagram"] = 25 + +fk.ai_card_keep_value["nioh_shield"] = 20 + +fk.ai_card_keep_value["dilu"] = 20 + +fk.ai_card_keep_value["jueying"] = 20 + +fk.ai_card_keep_value["zhuahuangfeidian"] = 20 + +fk.ai_card_keep_value["chitu"] = 20 + +fk.ai_card_keep_value["dayuan"] = 20 + +fk.ai_card_keep_value["zixing"] = 20 + SmartAI:setTriggerSkillAI("#nioh_shield_skill", { correct_func = function(self, logic, event, target, player, data) return self.skill:triggerable(event, target, player, data) @@ -187,4 +238,4 @@ SmartAI:setSkillAI("spear_skill", { return best_ret, best_val end, -}, "__card_skill") \ No newline at end of file +}, "__card_skill") diff --git a/standard_cards/init.lua b/standard_cards/init.lua index b0ea7fec953009797acf0acc4bb1b047986e3829..77d59256fa35d05186e7ee7ccd3b75813a5c26eb 100644 --- a/standard_cards/init.lua +++ b/standard_cards/init.lua @@ -668,7 +668,6 @@ local amazingGraceSkill = fk.CreateActiveSkill{ table.removeOne(effect.extra_data.AGFilled, chosen) end } - local amazingGrace = fk.CreateTrickCard{ name = "amazing_grace", suit = Card.Heart, @@ -846,9 +845,9 @@ local crossbowSkill = fk.CreateTargetModSkill{ end end, } + crossbowSkill:addRelatedSkill(crossbowAudio) Fk:addSkill(crossbowSkill) - local crossbow = fk.CreateWeapon{ name = "crossbow", suit = Card.Club, @@ -899,8 +898,8 @@ local qingGangSkill = fk.CreateTriggerSkill{ end) end, } -Fk:addSkill(qingGangSkill) +Fk:addSkill(qingGangSkill) local qingGang = fk.CreateWeapon{ name = "qinggang_sword", suit = Card.Spade, @@ -932,8 +931,8 @@ local iceSwordSkill = fk.CreateTriggerSkill{ return true end } -Fk:addSkill(iceSwordSkill) +Fk:addSkill(iceSwordSkill) local iceSword = fk.CreateWeapon{ name = "ice_sword", suit = Card.Spade,