From c3cdb8dc50db700178bf60797d92fc7a2a2ef053 Mon Sep 17 00:00:00 2001 From: notify Date: Sat, 17 Feb 2024 09:46:48 +0800 Subject: [PATCH] =?UTF-8?q?Ai=E5=B0=8F=E6=B7=BB=E5=8A=A0=20(#320)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 出牌策略仍然搞不定呀 - Qml: 新增leval函数可获得lua表达式的值 - 新增AbstractRoom类 去除冗余 --- Fk/main.qml | 9 + lua/client/client.lua | 31 +- lua/core/abstract_room.lua | 52 +++ lua/core/engine.lua | 14 +- lua/freekill.lua | 1 + lua/server/ai/smart_ai.lua | 22 +- lua/server/room.lua | 27 +- packages/standard/ai/init.lua | 286 +++-------------- packages/standard_cards/ai/init.lua | 481 +++------------------------- src/ui/qmlbackend.cpp | 23 ++ src/ui/qmlbackend.h | 1 + 11 files changed, 218 insertions(+), 729 deletions(-) create mode 100644 lua/core/abstract_room.lua diff --git a/Fk/main.qml b/Fk/main.qml index 75fe3c0b..54a913a8 100644 --- a/Fk/main.qml +++ b/Fk/main.qml @@ -269,6 +269,15 @@ Window { } } + function leval(lua) { + const ret = Backend.evalLuaExp(`return json.encode(${lua})`); + try { + return JSON.parse(ret); + } catch (e) { + return ret; + } + } + function luatr(src) { return Backend.translate(src); } diff --git a/lua/client/client.lua b/lua/client/client.lua index 95978400..d5ff5d07 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -1,16 +1,14 @@ -- SPDX-License-Identifier: GPL-3.0-or-later ----@class Client +---@class Client : AbstractRoom ---@field public client fk.Client ---@field public players ClientPlayer[] @ 所有参战玩家的数组 ---@field public alive_players ClientPlayer[] @ 所有存活玩家的数组 ---@field public observers ClientPlayer[] @ 观察者的数组 ---@field public current ClientPlayer @ 当前回合玩家 ---@field public discard_pile integer[] @ 弃牌堆 ----@field public status_skills Skill[] @ 状态技总和 ----@field public banners table @ 左上角显示的东西 ---@field public observing boolean -Client = class('Client') +Client = AbstractRoom:subclass('Client') -- load client classes ClientPlayer = require "client.clientplayer" @@ -26,6 +24,7 @@ local pattern_refresh_commands = { } function Client:initialize() + AbstractRoom.initialize(self) self.client = fk.ClientInstance self.notifyUI = function(self, command, jsonData) fk.Backend:emitNotifyUI(command, jsonData) @@ -49,21 +48,8 @@ function Client:initialize() end end - self.players = {} -- ClientPlayer[] - self.alive_players = {} - self.observers = {} self.discard_pile = {} - self.status_skills = {} - for class, skills in pairs(Fk.global_status_skill) do - self.status_skills[class] = {table.unpack(skills)} - end - self.banners = {} - - self.skill_costs = {} - self.card_marks = {} - self.filtered_cards = {} - self.printed_cards = {} self.disabled_packs = {} self.disabled_generals = {} @@ -71,7 +57,7 @@ function Client:initialize() end ---@param id integer ----@return ClientPlayer +---@return ClientPlayer? function Client:getPlayerById(id) if id == Self.id then return Self end for _, p in ipairs(self.players) do @@ -237,15 +223,6 @@ function Client:setCardNote(ids, msg) end end -function Client:setBanner(name, value) - if value == 0 then value = nil end - self.banners[name] = value -end - -function Client:getBanner(name) - return self.banners[name] -end - fk.client_callback["SetCardFootnote"] = function(jsonData) local data = json.decode(jsonData) ClientInstance:setCardNote(data[1], data[2]); diff --git a/lua/core/abstract_room.lua b/lua/core/abstract_room.lua new file mode 100644 index 00000000..0a4d837b --- /dev/null +++ b/lua/core/abstract_room.lua @@ -0,0 +1,52 @@ +-- 作Room和Client的基类,这二者有不少共通之处 +---@class AbstractRoom : Object +---@fiele public players Player[] @ 房内参战角色们 +---@field public alive_players Player[] @ 所有存活玩家的数组 +---@field public observers Player[] @ 看戏的 +---@field public current Player @ 当前行动者 +---@field public status_skills table @ 这个房间中含有的状态技列表 +---@field public filtered_cards table @ 见于Engine,其实在这 +---@field public printed_cards table @ 同上 +---@field public skill_costs table @ 用来存skill.cost_data +---@field public card_marks table @ 用来存实体卡的card.mark +---@field public banners table @ 全局mark +local AbstractRoom = class("AbstractRoom") + +function AbstractRoom:initialize() + self.players = {} + self.alive_players = {} + self.observers = {} + self.current = nil + + self.status_skills = {} + for class, skills in pairs(Fk.global_status_skill) do + self.status_skills[class] = {table.unpack(skills)} + end + + self.filtered_cards = {} + self.printed_cards = {} + self.skill_costs = {} + self.card_marks = {} + self.banners = {} +end + +-- 仅供注释,其余空函数一样 +---@param id integer +---@return Player? +function AbstractRoom:getPlayerById(id) end + +--- 获取一张牌所处的区域。 +---@param cardId integer | Card @ 要获得区域的那张牌,可以是Card或者一个id +---@return CardArea @ 这张牌的区域 +function AbstractRoom:getCardArea(cardId) end + +function AbstractRoom:setBanner(name, value) + if value == 0 then value = nil end + self.banners[name] = value +end + +function AbstractRoom:getBanner(name) + return self.banners[name] +end + +return AbstractRoom diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 596af613..57c41da1 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -78,20 +78,20 @@ function Engine:initialize() end local _foreign_keys = { - "currentResponsePattern", - "currentResponseReason", - "filtered_cards", - "printed_cards", + ["currentResponsePattern"] = true, + ["currentResponseReason"] = true, + ["filtered_cards"] = true, + ["printed_cards"] = true, } function Engine:__index(k) - if table.contains(_foreign_keys, k) then + if _foreign_keys[k] then return self:currentRoom()[k] end end function Engine:__newindex(k, v) - if table.contains(_foreign_keys, k) then + if _foreign_keys[k] then self:currentRoom()[k] = v else rawset(self, k, v) @@ -548,7 +548,7 @@ function Engine:_addPrintedCard(card) end --- 获知当前的Engine是跑在服务端还是客户端,并返回相应的实例。 ----@return Room | Client +---@return AbstractRoom function Engine:currentRoom() if RoomInstance then return RoomInstance diff --git a/lua/freekill.lua b/lua/freekill.lua index 27c169d9..24a90185 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -33,6 +33,7 @@ UsableSkill = require "core.skill_type.usable_skill" StatusSkill = require "core.skill_type.status_skill" Player = require "core.player" GameMode = require "core.game_mode" +AbstractRoom = require "core.abstract_room" UI = require "ui-util" -- 读取配置文件。 diff --git a/lua/server/ai/smart_ai.lua b/lua/server/ai/smart_ai.lua index 9d834459..857f4bf1 100644 --- a/lua/server/ai/smart_ai.lua +++ b/lua/server/ai/smart_ai.lua @@ -132,9 +132,24 @@ end -- 真的要考虑ViewAsSkill吗,害怕 --------------------------------------------------------- +-- 使用牌相关——同时见于主动使用和响应式使用。 --- 键是prompt的第一项或者牌名,优先prompt,其次name,实在不行trueName。 ---@type table -fk.ai_use_card = {} +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 @@ -219,6 +234,9 @@ smart_cb["PlayCard"] = function(self) local card_names = {} for _, cd in ipairs(cards) do -- TODO: 视为技 + -- 视为技对应的function一般会返回一张印出来的卡,又要纳入新的考虑范围了 + -- 不过这种根据牌名判断的逻辑而言 可能需要调用多次视为技函数了 + -- 要用好空间换时间 table.insertIfNeed(card_names, cd.name) end -- TODO: 主动技 @@ -277,7 +295,7 @@ function SmartAI:isFriend(target) if Self.role == target.role then return true end local t = { "lord", "loyalist" } if table.contains(t, Self.role) and table.contains(t, target.role) then return true end - if Self.role == "renegade" or target.role == "renegade" then return math.random() < 0.5 end + if Self.role == "renegade" or target.role == "renegade" then return math.random() < 0.6 end return false end diff --git a/lua/server/room.lua b/lua/server/room.lua index d194dd6d..049a2e6a 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -3,7 +3,7 @@ --- Room是fk游戏逻辑运行的主要场所,同时也提供了许多API函数供编写技能使用。 --- --- 一个房间中只有一个Room实例,保存在RoomInstance全局变量中。 ----@class Room : Object +---@class Room : AbstractRoom ---@field public room fk.Room @ C++层面的Room类实例,别管他就是了,用不着 ---@field public id integer @ 房间的id ---@field private main_co any @ 本房间的主协程 @@ -15,7 +15,6 @@ ---@field public game_finished boolean @ 游戏是否已经结束 ---@field public timeout integer @ 出牌时长上限 ---@field public tag table @ Tag清单,其实跟Player的标记是差不多的东西 ----@field public banners table @ 左上角显示点啥好呢? ---@field public general_pile string[] @ 武将牌堆,这是可用武将名的数组 ---@field public draw_pile integer[] @ 摸牌堆,这是卡牌id的数组 ---@field public discard_pile integer[] @ 弃牌堆,也是卡牌id的数组 @@ -23,14 +22,13 @@ ---@field public void integer[] @ 从游戏中除外区,一样的是卡牌id数组 ---@field public card_place table @ 每个卡牌的id对应的区域,一张表 ---@field public owner_map table @ 每个卡牌id对应的主人,表的值是那个玩家的id,可能是nil ----@field public status_skills Skill[] @ 这个房间中含有的状态技列表 ---@field public settings table @ 房间的额外设置,差不多是json对象 ---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动 ---@field public request_queue table ---@field public request_self table ---@field public skill_costs table @ 存放skill.cost_data用 ---@field public card_marks table @ 存放card.mark之用 -local Room = class("Room") +local Room = AbstractRoom:subclass("Room") -- load classes used by the game GameEvent = require "server.gameevent" @@ -67,18 +65,14 @@ dofile "lua/server/ai/init.lua" --- 构造函数。别去构造 ---@param _room fk.Room function Room:initialize(_room) + AbstractRoom.initialize(self) self.room = _room self.id = _room:getId() - self.players = {} - self.alive_players = {} - self.observers = {} - self.current = nil self.game_started = false self.game_finished = false self.timeout = _room:getTimeout() self.tag = {} - self.banners = {} self.general_pile = {} self.draw_pile = {} self.discard_pile = {} @@ -86,16 +80,8 @@ function Room:initialize(_room) self.void = {} self.card_place = {} self.owner_map = {} - self.status_skills = {} - for class, skills in pairs(Fk.global_status_skill) do - self.status_skills[class] = {table.unpack(skills)} - end self.request_queue = {} self.request_self = {} - self.skill_costs = {} - self.card_marks = {} - self.filtered_cards = {} - self.printed_cards = {} self.settings = json.decode(self.room:settings()) self.disabled_packs = self.settings.disabledPack @@ -568,15 +554,10 @@ function Room:removeTag(tag_name) end function Room:setBanner(name, value) - if value == 0 then value = nil end - self.banners[name] = value + AbstractRoom.setBanner(self, name, value) self:doBroadcastNotify("SetBanner", json.encode{ name, value }) end -function Room:getBanner(name) - return self.banners[name] -end - ---@return boolean local function execGameEvent(type, ...) local event = GameEvent:new(type, ...) diff --git a/packages/standard/ai/init.lua b/packages/standard/ai/init.lua index 04a7ab47..e25c8612 100644 --- a/packages/standard/ai/init.lua +++ b/packages/standard/ai/init.lua @@ -1,266 +1,80 @@ require "packages.standard.ai.aux_skills" ---[[ -fk.ai_use_play["rende"] = function(self, skill) - for _, p in ipairs(self.friends_noself) do - if p.kingdom == "shu" and #self.player:getCardIds("h") >= self.player.hp then - self.use_id = {} - for _, cid in ipairs(self.player:getCardIds("h")) do - if #self.use_id < #self.player:getCardIds("h") / 2 then - table.insert(self.use_id, cid) - end - end - self.use_tos = { p.id } - return - end - end - for _, p in ipairs(self.friends_noself) do - if #self.player:getCardIds("h") >= self.player.hp then - self.use_id = {} - for _, cid in ipairs(self.player:getCardIds("h")) do - if #self.use_id < #self.player:getCardIds("h") / 2 then - table.insert(self.use_id, cid) - end - end - self.use_tos = { p.id } - return - end - end -end - -fk.ai_card["jijiang"] = { priority = 10 } - -fk.ai_use_play["lijian"] = function(self, skill) - local c = Fk:cloneCard("duel") - c.skillName = "lijian" - local cards = table.map( - self.player:getCardIds("he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, p in ipairs(self.enemies) do - for _, pt in ipairs(self.enemies) do - if p.gender == General.Male and pt.gender == General.Male and p.id ~= pt.id - and c.skill:targetFilter(pt.id, {}, p.id, c) then - self.use_id = { cards[1].id } - self.use_tos = { pt.id, p.id } - break - end - end - end - for _, p in ipairs(self.friends_noself) do - for _, pt in ipairs(self.enemies) do - if p.gender == General.Male and pt.gender == General.Male and p.id ~= pt.id - and c.skill:targetFilter(pt.id, {}, p.id, c) then - self.use_id = { cards[1].id } - self.use_tos = { pt.id, p.id } - break - end - end - end -end - -fk.ai_card["lijian"] = { priority = 2 } - -fk.ai_use_play["zhiheng"] = function(self, skill) - local card_ids = {} - local cards = table.map( - self.player:getCardIds("he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, h in ipairs(cards) do - if #card_ids < #cards / 2 then - table.insert(card_ids, h.id) - end - end - if #card_ids > 0 then - self.use_id = card_ids - end -end - -fk.ai_use_play["kurou"] = function(self, skill) - if #self:getActives("peach") + self.player.hp > 1 then - local slash = Fk:cloneCard("slash") - if slash.skill:canUse(self.player, slash) and not self.player:prohibitUse(slash) then - fk.ai_use_play.slash(self, slash) - if self.use_id then - self.use_id = {} - self.use_tos = {} - end - end - end -end - -fk.ai_use_play["fanjian"] = function(self, skill) - for _, p in ipairs(self.enemies) do - if #self.player:getCardIds("h") > 0 then - self.use_id = {} - table.insert(self.use_tos, p.id) - break - end - end -end - -fk.ai_use_play["jieyin"] = function(self, skill) - local cards = table.map( - self.player:getCardIds("h"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, p in ipairs(self.friends_noself) do - if #cards > 1 and p.gender == General.Male and p:isWounded() then - self.use_id = { cards[1].id, cards[2].id } - table.insert(self.use_tos, p.id) - break - end - end -end - -fk.ai_use_play["qingnang"] = function(self, skill) - local cards = table.map( - self.player:getCardIds("h"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, p in ipairs(self.friends) do - if #cards > 0 and p:isWounded() then - self.use_id = { cards[1].id } - table.insert(self.use_tos, p.id) - break - end - end -end +-- 魏国 fk.ai_skill_invoke["jianxiong"] = true +-- TODO: hujia +-- TODO: guicai 关于如何界定判定的好坏 需要向AI中单独说明 -fk.ai_card["hujia"] = { priority = 10 } +fk.ai_skill_invoke["fankui"] = function(self) + local room = self.room + local logic = room.logic -fk.ai_response_card["#hujia-ask"] = function(self, pattern, prompt, cancelable, data) - local to = self.room:getPlayerById(tonumber(prompt:split(":")[2])) - if to and self:isFriend(to) and (self:isWeak(to) or #self:getActives(pattern)>1) then - self:setUseId(pattern) - end + -- 询问反馈时,处于on_cost环节,当前事件必是damage且有from + local event = logic:getCurrentEvent() + local dmg = event.data[1] + return self:isEnemy(dmg.from) end -fk.ai_response_card["#jijiang-ask"] = fk.ai_response_card["#hujia-ask"] +fk.ai_skill_invoke["ganglie"] = fk.ai_skill_invoke["fankui"] -fk.ai_skill_invoke["fankui"] = function(self, data, prompt) - local damage = self:eventData("Damage") - return damage and damage.from and not self:isFriend(damage.from) -end +-- TODO: tuxi -fk.ai_response_card["#guicai-ask"] = function(self, pattern, prompt, cancelable, data) - local cards = table.map(self.player:getHandlyIds(true), function(id) - return Fk:getCardById(id) - end) - local id = self:getRetrialCardId(cards) - if id then - self.use_id = id - end -end - -fk.ai_skill_invoke["ganglie"] = function(self, data, prompt) - local damage = self:eventData("Damage") - return damage and damage.from and not self:isFriend(damage.from) -end - -fk.ai_judge["ganglie"] = { ".|.|heart", false } - -fk.ai_skill_invoke["luoyi"] = function(self, data, prompt) - for _, p in ipairs(self.enemies) do - if #self:getActives("slash") > 0 and not self:isWeak() then - return true - end - end +fk.ai_skill_invoke["luoyi"] = function(self) + return false end fk.ai_skill_invoke["tiandu"] = true -fk.ai_skill_invoke["yiji"] = true +-- TODO: yiji fk.ai_skill_invoke["luoshen"] = true -fk.ai_skill_invoke["guanxing"] = true +-- TODO: qingguo -fk.ai_skill_invoke["tieqi"] = function(self, data, prompt) - local use = self:eventData("UseCard") - for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do - p = self.room:getPlayerById(p) - if self:isEnemy(p) then - return true - end - end +-- 蜀国 +-- TODO: rende +-- TODO: jijiang +-- TODO: wusheng +-- TODO: guanxing +-- TODO: longdan + +fk.ai_skill_invoke["tieqi"] = function(self) + local room = self.room + local logic = room.logic + + -- 询问反馈时,处于on_cost环节,当前事件必是damage且有from + local event = logic:getCurrentEvent() + local use = event.data[1] ---@type CardUseStruct + return table.find(use.tos, function(t) + return self:isEnemy(room:getPlayerById(t[1])) + end) end fk.ai_skill_invoke["jizhi"] = true +-- 吴国 +-- TODO: zhiheng +-- TODO: qixi + fk.ai_skill_invoke["keji"] = true +-- TODO: kurou + fk.ai_skill_invoke["yingzi"] = true -fk.ai_skill_invoke["lianying"] = true +-- TODO: fanjian +-- TODO: guose +-- TODO: liuli +fk.ai_skill_invoke["lianying"] = true fk.ai_skill_invoke["xiaoji"] = true +-- TODO: jieyin + +-- 群雄 +-- TODO: qingnang +-- TODO: jijiu +-- TODO: wushuang +-- TODO: lijian fk.ai_skill_invoke["biyue"] = true - -fk.ai_choose_players["tuxi"] = function(self, targets, min_num, num, cancelable) - for _, pid in ipairs(targets) do - local p = self.room:getPlayerById(pid) - if self:isEnemy(p) and #self.use_tos < num then - table.insert(self.use_tos, pid) - end - end -end - -fk.ai_active_skill["yiji_active"] = function(self, prompt, cancelable, data) - for _, p in ipairs(self.friends_noself) do - for c, cid in ipairs(self.player.tag["yiji_ids"]) do - c = Fk:getCardById(cid) - if c:getMark("yiji") > 0 and c.skill:canUse(p, c) then - self.use_tos = { p.id } - self.use_id = json.encode { - skill = "yiji_active", - subcards = { cid } - } - return - end - end - end -end - -fk.ai_choose_players["liuli"] = function(self, targets, min_num, num, cancelable) - local cards = table.map( - self.player:getCardIds("he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, pid in ipairs(targets) do - local p = self.room:getPlayerById(pid) - if self:isEnemy(p) and #self.use_tos < num and #cards > 0 then - table.insert(self.use_tos, pid) - self.use_id = { cards[1].id } - return - end - end - for _, pid in ipairs(targets) do - local p = self.room:getPlayerById(pid) - if not self:isFriend(p) and #self.use_tos < num and #cards > 0 then - table.insert(self.use_tos, pid) - self.use_id = { cards[1].id } - return - end - end -end ---]] diff --git a/packages/standard_cards/ai/init.lua b/packages/standard_cards/ai/init.lua index 4fb90903..9c94733a 100644 --- a/packages/standard_cards/ai/init.lua +++ b/packages/standard_cards/ai/init.lua @@ -1,16 +1,50 @@ +-- TODO: 合法性的方便函数 +-- TODO: 关于如何选择多个目标 +-- TODO: 关于装备牌 + -- 基本牌:杀,闪,桃 -fk.ai_use_card["slash"] = function(self, pattern, prompt, cancelable, extra_data) - local slashes = self:getCards("slash", "use", extra_data) +---@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) end + 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 @@ -32,443 +66,22 @@ fk.ai_use_card["#AskForPeaches"] = function(self) return nil end ---[[ -fk.ai_card.slash = { - intention = 100, -- 身份值 - value = 4, -- 卡牌价值 - priority = 2.5 -- 使用优先值 -} -fk.ai_card.peach = { - intention = -150, - value = 10, - priority = 0.5 -} -fk.ai_card.dismantlement = { - intention = function(self, card, from) - if #self.player.player_cards[Player.Judge] < 1 then - return 80 - elseif self.ai_role[from.id] == "neutral" then - return 30 - end - end, - value = 3.5, - priority = 10.5 -} -fk.ai_card.snatch = { - intention = function(self, card, from) - if #self.player.player_cards[Player.Judge] < 1 then - return 80 - elseif self.ai_role[from.id] == "neutral" then - return 30 - end - end, - value = 4.5, - priority = 10.4 -} -fk.ai_card.duel = { - intention = 120, - value = 4.5, - priority = 3.5 -} -fk.ai_card.collateral = { - intention = 20, - value = 3, - priority = 4.5 -} -fk.ai_card.ex_nihilo = { - intention = -200, - value = 8, - priority = 10 -} -fk.ai_card.savage_assault = { - intention = 20, - value = 2, - priority = 4 -} -fk.ai_card.archery_attack = { - intention = 30, - value = 2, - priority = 3 -} -fk.ai_card.god_salvation = { - intention = function(self, card, from) - if self.player.hp ~= self.player.maxHp then - return -45 - end - end, - value = 1.5, - priority = 4 -} -fk.ai_card.amazing_grace = { - intention = -30, - value = 2, - priority = 2 -} -fk.ai_card.indulgence = { - intention = 150, - value = -1, - priority = 2 -} - -local function slashEeffect(slash, to) - for _, s in ipairs(to:getAllSkills()) do - if s.name == "#vine_skill" then - if slash.name == "slash" then - return - end - end - if s.name == "#nioh_shield_skill" then - if slash.color == Card.Black then - return - end - end - end - return true +fk.ai_use_card["dismantlement"] = function(self, pattern, prompt, cancelable, extra_data) + return useToEnemy(self, "dismantlement", extra_data) end -fk.ai_use_play["slash"] = 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 slashEeffect(card, p) and self:objectiveLevel(p) > 2 then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end +fk.ai_use_card["snatch"] = function(self, pattern, prompt, cancelable, extra_data) + return useToEnemy(self, "snatch", extra_data) end -fk.ai_use_card["#slash-jink"] = function(self, pattern, prompt, cancelable, extra_data) - local act = self:getActives(pattern) - if tonumber(prompt:split(":")[4]) > #act then - return - end - local cards = - table.map( - self.player:getCardIds("&he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, sth in ipairs(act) do - if sth:isInstanceOf(Card) then - self.use_id = sth.id - break - else - local selected = {} - for _, c in ipairs(cards) do - if sth.cardFilter(sth, c.id, selected) then - table.insert(selected, c.id) - end - end - local tc = sth.viewAs(sth, selected) - if tc and tc:matchPattern(pattern) then - self.use_id = - json.encode { - skill = sth.name, - subcards = selected - } - break - end - end - end +fk.ai_use_card["duel"] = function(self, pattern, prompt, cancelable, extra_data) + return useToEnemy(self, "duel", extra_data) end -fk.ai_use_card["#slash-jinks"] = fk.ai_use_card["#slash-jink"] - -fk.ai_use_play["snatch"] = function(self, card) - for _, p in ipairs(self.friends_noself) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #p:getCardIds("j") > 0 then - self.use_id = card.id - 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 #p:getCardIds("he") > 0 then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end +fk.ai_use_card["ex_nihilo"] = function(self, pattern, prompt, cancelable, extra_data) + return justUse(self, "ex_nihilo", extra_data) end -fk.ai_nullification.snatch = function(self, card, to, from, positive) - if positive then - if self:isFriend(to) and not self:isFriend(from) and self.ai_role[from.id] ~= "neutral" 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 self:isEnemy(from) then - if #self.avail_cards > 1 or self:isWeak(to) then - self.use_id = self.avail_cards[1] - end - end - end +fk.ai_use_card["indulgence"] = function(self, pattern, prompt, cancelable, extra_data) + return useToEnemy(self, "indulgence", extra_data) end - -fk.ai_use_play["dismantlement"] = function(self, card) - for _, p in ipairs(self.friends_noself) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #p:getCardIds("j") > 0 then - self.use_id = card.id - 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 #p:getCardIds("he") > 0 then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end -end - -fk.ai_nullification.dismantlement = function(self, card, to, from, positive) - if positive then - if self:isFriend(to) and not self:isFriend(from) and self.ai_role[from.id] ~= "neutral" 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 self:isEnemy(from) then - if #self.avail_cards > 1 or self:isWeak(to) then - self.use_id = self.avail_cards[1] - end - end - end -end - -fk.ai_use_play["indulgence"] = function(self, card) - self:sort(self.enemies, nil, true) - for _, p in ipairs(self.enemies) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end -end - -fk.ai_nullification.indulgence = 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_use_play["collateral"] = function(self, card) - local max = (card.skill:getMaxTargetNum(self.player, card) - 1) * 2 - self:sort(self.enemies) - for _, p in ipairs(self.enemies) do - if #self.use_tos < max and card.skill:targetFilter(p.id, {}, {}, card) then - for _, pt in ipairs(self.enemies) do - if p ~= pt and p:inMyAttackRange(pt) then - table.insert(self.use_tos, p.id) - table.insert(self.use_tos, pt.id) - self.use_id = card.id - break - end - end - end - end - for _, p in ipairs(self.friends_noself) do - if #self.use_tos < max and card.skill:targetFilter(p.id, {}, {}, card) then - for _, pt in ipairs(self.enemies) do - if p ~= pt and p:inMyAttackRange(pt) then - table.insert(self.use_tos, p.id) - table.insert(self.use_tos, pt.id) - self.use_id = card.id - break - end - end - end - end -end - -fk.ai_nullification.collateral = function(self, card, to, from, positive) - if positive then - if self:isFriend(to) and self:isEnemy(from) 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 - end -end - -fk.ai_nullification.ex_nihilo = function(self, card, to, from, positive) - if positive then - if self:isEnemy(to) then - if #self.avail_cards > 1 or self:isWeak(to) then - self.use_id = self.avail_cards[1] - end - end - else - 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 - end -end - -fk.ai_nullification.savage_assault = 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_nullification.archery_attack = 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_nullification.god_salvation = function(self, card, to, from, positive) - if positive then - if self:isEnemy(to) and to.hp ~= to.maxHp then - if #self.avail_cards > 1 or self:isWeak(to) then - self.use_id = self.avail_cards[1] - end - end - else - if self:isFriend(to) and to.hp ~= to.maxHp 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 - end -end - -fk.ai_use_play["god_salvation"] = function(self, card) - local can = 0 - for _, p in ipairs(self.enemies) do - if p:isWounded() - then - can = can - 1 - if self:isWeak(p) - then - can = can - 1 - end - end - end - for _, p in ipairs(self.friends) do - if p:isWounded() - then - can = can + 1 - if self:isWeak(p) - then - can = can + 1 - end - end - end - self.use_id = can > 0 and card.id -end - -fk.ai_use_play["amazing_grace"] = function(self, card) - self.use_id = #self.player:getCardIds("&h") <= self.player.hp and card.id -end - -fk.ai_use_play["ex_nihilo"] = function(self, card) - self.use_id = card.id -end - -fk.ai_use_play["lightning"] = function(self, card) - self.use_id = #self.enemies > #self.friends and card.id -end - -fk.ai_use_play["peach"] = function(self, card) - if self.command == "PlayCard" then - self.use_id = self.player.hp ~= self.player.maxHp and self.player.hp < #self.player:getCardIds("h") and card.id - else - for _, p in ipairs(self.friends) do - if p.dying then - self.use_id = card.id - self.use_tos = { p.id } - break - end - end - end -end - -fk.ai_use_play["duel"] = function(self, card) - self:sort(self.enemies) - for _, p in ipairs(self.enemies) do - if card.skill:targetFilter(p.id, self.use_tos, {}, card) then - self.use_id = card.id - table.insert(self.use_tos, p.id) - end - end -end - -fk.ai_skill_invoke["#ice_sword_skill"] = function(self) - local damage = self:eventData("Damage") - return self:isFriend(damage.to) or not self:isWeak(damage.to) and #damage.to:getCardIds("e") > 1 -end - -fk.ai_skill_invoke["#double_swords_skill"] = function(self) - local use = self:eventData("UseCard") - for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do - if not self:isFriend(p) and self.room:getPlayerById(p).gender ~= self.player.gender then - return true - end - end -end - -fk.ai_discard["#double_swords_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) - local use = self:eventData("UseCard") - return self:isEnemy(use.from) and { self.player:getCardIds("h")[1] } -end - -fk.ai_discard["#axe_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) - local ids = {} - local effect = self:eventData("CardEffect") - for _, cid in ipairs(self.player:getCardIds("he")) do - if Fk:getCardById(cid):matchPattern(pattern) then - table.insert(ids, cid) - end - if #ids >= min_num and self:isEnemy(effect.to) - and (self:isWeak(effect.to) or #self.player:getCardIds("he") > 3) then - return ids - end - end -end - -fk.ai_skill_invoke["#kylin_bow_skill"] = function(self) - local damage = self:eventData("Damage") - return not self:isFriend(damage.to) -end - -fk.ai_skill_invoke["#eight_diagram_skill"] = function(self) - local effect = self:eventData("CardEffect") - return effect and self:isFriend(effect.to) -end ---]] diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index b4d86d66..e9ba2361 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -225,6 +225,29 @@ QString QmlBackend::callLuaFunction(const QString &func_name, return QString(result); } +QString QmlBackend::evalLuaExp(const QString &lua) { + if (!ClientInstance) return "{}"; + + lua_State *L = ClientInstance->getLuaState(); + int err; + err = luaL_loadstring(L, lua.toUtf8().constData()); + if (err != LUA_OK) { + qCritical() << lua_tostring(L, -1); + lua_pop(L, 1); + return ""; + } + err = lua_pcall(L, 0, 1, 0); + const char *result = luaL_tolstring(L, -1, NULL); + if (err) { + qCritical() << result; + lua_pop(L, 1); + return ""; + } + lua_pop(L, 1); + + return QString(result); +} + QString QmlBackend::pubEncrypt(const QString &key, const QString &data) { // 在用公钥加密口令时,也随机生成AES密钥/IV,并随着口令一起加密 // AES密钥和IV都是固定16字节的,所以可以放在开头 diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index 9c167248..2f582910 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -40,6 +40,7 @@ public: Q_INVOKABLE QString translate(const QString &src); Q_INVOKABLE QString callLuaFunction(const QString &func_name, QVariantList params); + Q_INVOKABLE QString evalLuaExp(const QString &lua); Q_INVOKABLE QString pubEncrypt(const QString &key, const QString &data); Q_INVOKABLE QString loadConf();