Ai小添加 (#320)

出牌策略仍然搞不定呀
- Qml: 新增leval函数可获得lua表达式的值
- 新增AbstractRoom类 去除冗余
This commit is contained in:
notify 2024-02-17 09:46:48 +08:00 committed by GitHub
parent 7661cabd58
commit c3cdb8dc50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 218 additions and 729 deletions

View File

@ -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) { function luatr(src) {
return Backend.translate(src); return Backend.translate(src);
} }

View File

@ -1,16 +1,14 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
---@class Client ---@class Client : AbstractRoom
---@field public client fk.Client ---@field public client fk.Client
---@field public players ClientPlayer[] @ 所有参战玩家的数组 ---@field public players ClientPlayer[] @ 所有参战玩家的数组
---@field public alive_players ClientPlayer[] @ 所有存活玩家的数组 ---@field public alive_players ClientPlayer[] @ 所有存活玩家的数组
---@field public observers ClientPlayer[] @ 观察者的数组 ---@field public observers ClientPlayer[] @ 观察者的数组
---@field public current ClientPlayer @ 当前回合玩家 ---@field public current ClientPlayer @ 当前回合玩家
---@field public discard_pile integer[] @ 弃牌堆 ---@field public discard_pile integer[] @ 弃牌堆
---@field public status_skills Skill[] @ 状态技总和
---@field public banners table<string, any> @ 左上角显示的东西
---@field public observing boolean ---@field public observing boolean
Client = class('Client') Client = AbstractRoom:subclass('Client')
-- load client classes -- load client classes
ClientPlayer = require "client.clientplayer" ClientPlayer = require "client.clientplayer"
@ -26,6 +24,7 @@ local pattern_refresh_commands = {
} }
function Client:initialize() function Client:initialize()
AbstractRoom.initialize(self)
self.client = fk.ClientInstance self.client = fk.ClientInstance
self.notifyUI = function(self, command, jsonData) self.notifyUI = function(self, command, jsonData)
fk.Backend:emitNotifyUI(command, jsonData) fk.Backend:emitNotifyUI(command, jsonData)
@ -49,21 +48,8 @@ function Client:initialize()
end end
end end
self.players = {} -- ClientPlayer[]
self.alive_players = {}
self.observers = {}
self.discard_pile = {} 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_packs = {}
self.disabled_generals = {} self.disabled_generals = {}
@ -71,7 +57,7 @@ function Client:initialize()
end end
---@param id integer ---@param id integer
---@return ClientPlayer ---@return ClientPlayer?
function Client:getPlayerById(id) function Client:getPlayerById(id)
if id == Self.id then return Self end if id == Self.id then return Self end
for _, p in ipairs(self.players) do for _, p in ipairs(self.players) do
@ -237,15 +223,6 @@ function Client:setCardNote(ids, msg)
end end
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) fk.client_callback["SetCardFootnote"] = function(jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
ClientInstance:setCardNote(data[1], data[2]); ClientInstance:setCardNote(data[1], data[2]);

View File

@ -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<class, Skill[]> @ 这个房间中含有的状态技列表
---@field public filtered_cards table<integer, Card> @ 见于Engine其实在这
---@field public printed_cards table<integer, Card> @ 同上
---@field public skill_costs table<string, any> @ 用来存skill.cost_data
---@field public card_marks table<integer, any> @ 用来存实体卡的card.mark
---@field public banners table<string, any> @ 全局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

View File

@ -78,20 +78,20 @@ function Engine:initialize()
end end
local _foreign_keys = { local _foreign_keys = {
"currentResponsePattern", ["currentResponsePattern"] = true,
"currentResponseReason", ["currentResponseReason"] = true,
"filtered_cards", ["filtered_cards"] = true,
"printed_cards", ["printed_cards"] = true,
} }
function Engine:__index(k) function Engine:__index(k)
if table.contains(_foreign_keys, k) then if _foreign_keys[k] then
return self:currentRoom()[k] return self:currentRoom()[k]
end end
end end
function Engine:__newindex(k, v) function Engine:__newindex(k, v)
if table.contains(_foreign_keys, k) then if _foreign_keys[k] then
self:currentRoom()[k] = v self:currentRoom()[k] = v
else else
rawset(self, k, v) rawset(self, k, v)
@ -548,7 +548,7 @@ function Engine:_addPrintedCard(card)
end end
--- 获知当前的Engine是跑在服务端还是客户端并返回相应的实例。 --- 获知当前的Engine是跑在服务端还是客户端并返回相应的实例。
---@return Room | Client ---@return AbstractRoom
function Engine:currentRoom() function Engine:currentRoom()
if RoomInstance then if RoomInstance then
return RoomInstance return RoomInstance

View File

@ -33,6 +33,7 @@ UsableSkill = require "core.skill_type.usable_skill"
StatusSkill = require "core.skill_type.status_skill" StatusSkill = require "core.skill_type.status_skill"
Player = require "core.player" Player = require "core.player"
GameMode = require "core.game_mode" GameMode = require "core.game_mode"
AbstractRoom = require "core.abstract_room"
UI = require "ui-util" UI = require "ui-util"
-- 读取配置文件。 -- 读取配置文件。

View File

@ -132,9 +132,24 @@ end
-- 真的要考虑ViewAsSkill吗害怕 -- 真的要考虑ViewAsSkill吗害怕
--------------------------------------------------------- ---------------------------------------------------------
-- 使用牌相关——同时见于主动使用和响应式使用。
--- 键是prompt的第一项或者牌名优先prompt其次name实在不行trueName。 --- 键是prompt的第一项或者牌名优先prompt其次name实在不行trueName。
---@type table<string, fun(self: SmartAI, pattern: string, prompt: string, cancelable?: boolean, extra_data?: UseExtraData): UseReply?> ---@type table<string, fun(self: SmartAI, pattern: string, prompt: string, cancelable?: boolean, extra_data?: UseExtraData): UseReply?>
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) local defauld_use_card = function(self, pattern, _, cancelable, exdata)
if cancelable then return nil end if cancelable then return nil end
@ -219,6 +234,9 @@ smart_cb["PlayCard"] = function(self)
local card_names = {} local card_names = {}
for _, cd in ipairs(cards) do for _, cd in ipairs(cards) do
-- TODO: 视为技 -- TODO: 视为技
-- 视为技对应的function一般会返回一张印出来的卡又要纳入新的考虑范围了
-- 不过这种根据牌名判断的逻辑而言 可能需要调用多次视为技函数了
-- 要用好空间换时间
table.insertIfNeed(card_names, cd.name) table.insertIfNeed(card_names, cd.name)
end end
-- TODO: 主动技 -- TODO: 主动技
@ -277,7 +295,7 @@ function SmartAI:isFriend(target)
if Self.role == target.role then return true end if Self.role == target.role then return true end
local t = { "lord", "loyalist" } local t = { "lord", "loyalist" }
if table.contains(t, Self.role) and table.contains(t, target.role) then return true end 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 return false
end end

View File

@ -3,7 +3,7 @@
--- Room是fk游戏逻辑运行的主要场所同时也提供了许多API函数供编写技能使用。 --- Room是fk游戏逻辑运行的主要场所同时也提供了许多API函数供编写技能使用。
--- ---
--- 一个房间中只有一个Room实例保存在RoomInstance全局变量中。 --- 一个房间中只有一个Room实例保存在RoomInstance全局变量中。
---@class Room : Object ---@class Room : AbstractRoom
---@field public room fk.Room @ C++层面的Room类实例别管他就是了用不着 ---@field public room fk.Room @ C++层面的Room类实例别管他就是了用不着
---@field public id integer @ 房间的id ---@field public id integer @ 房间的id
---@field private main_co any @ 本房间的主协程 ---@field private main_co any @ 本房间的主协程
@ -15,7 +15,6 @@
---@field public game_finished boolean @ 游戏是否已经结束 ---@field public game_finished boolean @ 游戏是否已经结束
---@field public timeout integer @ 出牌时长上限 ---@field public timeout integer @ 出牌时长上限
---@field public tag table<string, any> @ Tag清单其实跟Player的标记是差不多的东西 ---@field public tag table<string, any> @ Tag清单其实跟Player的标记是差不多的东西
---@field public banners table<string, any> @ 左上角显示点啥好呢?
---@field public general_pile string[] @ 武将牌堆,这是可用武将名的数组 ---@field public general_pile string[] @ 武将牌堆,这是可用武将名的数组
---@field public draw_pile integer[] @ 摸牌堆这是卡牌id的数组 ---@field public draw_pile integer[] @ 摸牌堆这是卡牌id的数组
---@field public discard_pile integer[] @ 弃牌堆也是卡牌id的数组 ---@field public discard_pile integer[] @ 弃牌堆也是卡牌id的数组
@ -23,14 +22,13 @@
---@field public void integer[] @ 从游戏中除外区一样的是卡牌id数组 ---@field public void integer[] @ 从游戏中除外区一样的是卡牌id数组
---@field public card_place table<integer, CardArea> @ 每个卡牌的id对应的区域一张表 ---@field public card_place table<integer, CardArea> @ 每个卡牌的id对应的区域一张表
---@field public owner_map table<integer, integer> @ 每个卡牌id对应的主人表的值是那个玩家的id可能是nil ---@field public owner_map table<integer, integer> @ 每个卡牌id对应的主人表的值是那个玩家的id可能是nil
---@field public status_skills Skill[] @ 这个房间中含有的状态技列表
---@field public settings table @ 房间的额外设置差不多是json对象 ---@field public settings table @ 房间的额外设置差不多是json对象
---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动 ---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动
---@field public request_queue table<userdata, table> ---@field public request_queue table<userdata, table>
---@field public request_self table<integer, integer> ---@field public request_self table<integer, integer>
---@field public skill_costs table<string, any> @ 存放skill.cost_data用 ---@field public skill_costs table<string, any> @ 存放skill.cost_data用
---@field public card_marks table<integer, any> @ 存放card.mark之用 ---@field public card_marks table<integer, any> @ 存放card.mark之用
local Room = class("Room") local Room = AbstractRoom:subclass("Room")
-- load classes used by the game -- load classes used by the game
GameEvent = require "server.gameevent" GameEvent = require "server.gameevent"
@ -67,18 +65,14 @@ dofile "lua/server/ai/init.lua"
--- 构造函数。别去构造 --- 构造函数。别去构造
---@param _room fk.Room ---@param _room fk.Room
function Room:initialize(_room) function Room:initialize(_room)
AbstractRoom.initialize(self)
self.room = _room self.room = _room
self.id = _room:getId() self.id = _room:getId()
self.players = {}
self.alive_players = {}
self.observers = {}
self.current = nil
self.game_started = false self.game_started = false
self.game_finished = false self.game_finished = false
self.timeout = _room:getTimeout() self.timeout = _room:getTimeout()
self.tag = {} self.tag = {}
self.banners = {}
self.general_pile = {} self.general_pile = {}
self.draw_pile = {} self.draw_pile = {}
self.discard_pile = {} self.discard_pile = {}
@ -86,16 +80,8 @@ function Room:initialize(_room)
self.void = {} self.void = {}
self.card_place = {} self.card_place = {}
self.owner_map = {} 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_queue = {}
self.request_self = {} self.request_self = {}
self.skill_costs = {}
self.card_marks = {}
self.filtered_cards = {}
self.printed_cards = {}
self.settings = json.decode(self.room:settings()) self.settings = json.decode(self.room:settings())
self.disabled_packs = self.settings.disabledPack self.disabled_packs = self.settings.disabledPack
@ -568,15 +554,10 @@ function Room:removeTag(tag_name)
end end
function Room:setBanner(name, value) function Room:setBanner(name, value)
if value == 0 then value = nil end AbstractRoom.setBanner(self, name, value)
self.banners[name] = value
self:doBroadcastNotify("SetBanner", json.encode{ name, value }) self:doBroadcastNotify("SetBanner", json.encode{ name, value })
end end
function Room:getBanner(name)
return self.banners[name]
end
---@return boolean ---@return boolean
local function execGameEvent(type, ...) local function execGameEvent(type, ...)
local event = GameEvent:new(type, ...) local event = GameEvent:new(type, ...)

View File

@ -1,266 +1,80 @@
require "packages.standard.ai.aux_skills" 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 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) -- 询问反馈时处于on_cost环节当前事件必是damage且有from
local to = self.room:getPlayerById(tonumber(prompt:split(":")[2])) local event = logic:getCurrentEvent()
if to and self:isFriend(to) and (self:isWeak(to) or #self:getActives(pattern)>1) then local dmg = event.data[1]
self:setUseId(pattern) return self:isEnemy(dmg.from)
end
end 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) -- TODO: tuxi
local damage = self:eventData("Damage")
return damage and damage.from and not self:isFriend(damage.from)
end
fk.ai_response_card["#guicai-ask"] = function(self, pattern, prompt, cancelable, data) fk.ai_skill_invoke["luoyi"] = function(self)
local cards = table.map(self.player:getHandlyIds(true), function(id) return false
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
end end
fk.ai_skill_invoke["tiandu"] = true fk.ai_skill_invoke["tiandu"] = true
fk.ai_skill_invoke["yiji"] = true -- TODO: yiji
fk.ai_skill_invoke["luoshen"] = true 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") -- TODO: rende
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do -- TODO: jijiang
p = self.room:getPlayerById(p) -- TODO: wusheng
if self:isEnemy(p) then -- TODO: guanxing
return true -- TODO: longdan
end
end 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 end
fk.ai_skill_invoke["jizhi"] = true fk.ai_skill_invoke["jizhi"] = true
-- 吴国
-- TODO: zhiheng
-- TODO: qixi
fk.ai_skill_invoke["keji"] = true fk.ai_skill_invoke["keji"] = true
-- TODO: kurou
fk.ai_skill_invoke["yingzi"] = true 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 fk.ai_skill_invoke["xiaoji"] = true
-- TODO: jieyin
-- 群雄
-- TODO: qingnang
-- TODO: jijiu
-- TODO: wushuang
-- TODO: lijian
fk.ai_skill_invoke["biyue"] = true 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
--]]

View File

@ -1,16 +1,50 @@
-- TODO: 合法性的方便函数
-- TODO: 关于如何选择多个目标
-- TODO: 关于装备牌
-- 基本牌:杀,闪,桃 -- 基本牌:杀,闪,桃
fk.ai_use_card["slash"] = function(self, pattern, prompt, cancelable, extra_data) ---@param from ServerPlayer
local slashes = self:getCards("slash", "use", extra_data) ---@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 if #slashes == 0 then return nil end
-- TODO: 目标合法性 -- TODO: 目标合法性
local targets = {} 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) return self:buildUseReply(slashes[1].id, targets)
end 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) fk.ai_use_card["peach"] = function(self, _, _, _, extra_data)
local cards = self:getCards("peach", "use", extra_data) local cards = self:getCards("peach", "use", extra_data)
if #cards == 0 then return nil end if #cards == 0 then return nil end
@ -32,443 +66,22 @@ fk.ai_use_card["#AskForPeaches"] = function(self)
return nil return nil
end end
--[[ fk.ai_use_card["dismantlement"] = function(self, pattern, prompt, cancelable, extra_data)
fk.ai_card.slash = { return useToEnemy(self, "dismantlement", extra_data)
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
end end
fk.ai_use_play["slash"] = function(self, card) fk.ai_use_card["snatch"] = function(self, pattern, prompt, cancelable, extra_data)
self:sort(self.enemies) return useToEnemy(self, "snatch", extra_data)
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
end end
fk.ai_use_card["#slash-jink"] = function(self, pattern, prompt, cancelable, extra_data) fk.ai_use_card["duel"] = function(self, pattern, prompt, cancelable, extra_data)
local act = self:getActives(pattern) return useToEnemy(self, "duel", extra_data)
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
end end
fk.ai_use_card["#slash-jinks"] = fk.ai_use_card["#slash-jink"] fk.ai_use_card["ex_nihilo"] = function(self, pattern, prompt, cancelable, extra_data)
return justUse(self, "ex_nihilo", extra_data)
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
end end
fk.ai_nullification.snatch = function(self, card, to, from, positive) fk.ai_use_card["indulgence"] = function(self, pattern, prompt, cancelable, extra_data)
if positive then return useToEnemy(self, "indulgence", extra_data)
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
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["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
--]]

View File

@ -225,6 +225,29 @@ QString QmlBackend::callLuaFunction(const QString &func_name,
return QString(result); 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) { QString QmlBackend::pubEncrypt(const QString &key, const QString &data) {
// 在用公钥加密口令时也随机生成AES密钥/IV并随着口令一起加密 // 在用公钥加密口令时也随机生成AES密钥/IV并随着口令一起加密
// AES密钥和IV都是固定16字节的所以可以放在开头 // AES密钥和IV都是固定16字节的所以可以放在开头

View File

@ -40,6 +40,7 @@ public:
Q_INVOKABLE QString translate(const QString &src); Q_INVOKABLE QString translate(const QString &src);
Q_INVOKABLE QString callLuaFunction(const QString &func_name, Q_INVOKABLE QString callLuaFunction(const QString &func_name,
QVariantList params); QVariantList params);
Q_INVOKABLE QString evalLuaExp(const QString &lua);
Q_INVOKABLE QString pubEncrypt(const QString &key, const QString &data); Q_INVOKABLE QString pubEncrypt(const QString &key, const QString &data);
Q_INVOKABLE QString loadConf(); Q_INVOKABLE QString loadConf();