Ai(鸽之) (#289)

鸽了一点新AI
This commit is contained in:
notify 2023-12-03 18:45:25 +08:00 committed by GitHub
parent 041d5835ff
commit eba115a4fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 456 additions and 1338 deletions

20
docs/dev/smart-ai.rst Normal file
View File

@ -0,0 +1,20 @@
关于类似神杀的Smart-AI的实现思路
==================================
AI的目的就是为了响应各种askFor而Smart-ai则是给了玩家自定义askFor策略的接口。
大体框架还是一样的根据command type去选择执行某个通用函数再根据各种参数不断
细化函数执行最后执行Mod开发者的自定义逻辑。
而如何设计这种接口就是要面对的问题了。
神杀智慧1堆积如山的hasSkill
------------------------------
神杀一个突出的问题就是各种hasSkill写死比如判断要不要黑杀某人直接写死hasSkill
仁王盾啥的
神杀智慧2一次性sort所有卡牌/主动技/视为技
--------------------------------------------
如题,这导致每次都要花秒级甚至分钟级别的时间来出一张牌。

View File

@ -739,7 +739,7 @@ fk.client_callback["AskForUseActiveSkill"] = function(jsonData)
-- jsonData: [ string skill_name, string prompt, bool cancelable. json extra_data ] -- jsonData: [ string skill_name, string prompt, bool cancelable. json extra_data ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local skill = Fk.skills[data[1]] local skill = Fk.skills[data[1]]
local extra_data = json.decode(data[4]) local extra_data = data[4]
for k, v in pairs(extra_data) do for k, v in pairs(extra_data) do
skill[k] = v skill[k] = v
end end

View File

@ -382,6 +382,7 @@ end
---@param include_hand bool @ 是否包含真正的手牌 ---@param include_hand bool @ 是否包含真正的手牌
---@return integer[] ---@return integer[]
function Player:getHandlyIds(include_hand) function Player:getHandlyIds(include_hand)
include_hand = include_hand or include_hand == nil
local ret = include_hand and self:getCardIds("h") or {} local ret = include_hand and self:getCardIds("h") or {}
for k, v in pairs(self.special_cards) do for k, v in pairs(self.special_cards) do
if k:endsWith("&") then table.insertTable(ret, v) end if k:endsWith("&") then table.insertTable(ret, v) end

View File

@ -85,6 +85,35 @@ function fk.qlist(list)
return qlist_iterator, list, -1 return qlist_iterator, list, -1
end end
--- 用于for循环的迭代函数。可以将表按照某种权值的顺序进行遍历这样不用进行完整排序。
---@generic T
---@param t T[]
---@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 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)
else
local val = val_func(v)
local checked = val > max_val
if reverse then checked = not checked end
if checked then
max_idx, max, max_val = i, v, val
end
end
end
if max_idx == -1 then return nil, nil end
table.remove(t2, max_idx)
return -1, max, max_val
end
return iter, nil, 1
end
---@param func fun(element, index, array) ---@param func fun(element, index, array)
function table:forEach(func) function table:forEach(func)
for i, v in ipairs(self) do for i, v in ipairs(self) do

View File

@ -6,7 +6,7 @@ RandomAI = require "server.ai.random_ai"
--[[ 在release版暂时不启动。 --[[ 在release版暂时不启动。
SmartAI = require "server.ai.smart_ai" SmartAI = require "server.ai.smart_ai"
---[[ 调试中暂且不加载额外的AI。
-- load ai module from packages -- load ai module from packages
local directories = FileIO.ls("packages") local directories = FileIO.ls("packages")
require "packages.standard.ai" require "packages.standard.ai"

View File

@ -6,7 +6,7 @@ local RandomAI = AI:subclass("RandomAI")
---@param self RandomAI ---@param self RandomAI
---@param skill ActiveSkill ---@param skill ActiveSkill
---@param card Card | nil ---@param card Card | nil
local function useActiveSkill(self, skill, card) function RandomAI:useActiveSkill(skill, card)
local room = self.room local room = self.room
local player = self.player local player = self.player
@ -62,7 +62,7 @@ end
---@param self RandomAI ---@param self RandomAI
---@param skill ViewAsSkill ---@param skill ViewAsSkill
local function useVSSkill(self, skill, pattern, cancelable, extra_data) function RandomAI:useVSSkill(skill, pattern, cancelable, extra_data)
local player = self.player local player = self.player
local room = self.room local room = self.room
local precondition local precondition
@ -116,11 +116,11 @@ random_cb["AskForUseActiveSkill"] = function(self, jsonData)
local skill = Fk.skills[data[1]] local skill = Fk.skills[data[1]]
local cancelable = data[3] local cancelable = data[3]
if cancelable and math.random() < 0.25 then return "" end if cancelable and math.random() < 0.25 then return "" end
local extra_data = json.decode(data[4]) local extra_data = data[4]
for k, v in pairs(extra_data) do for k, v in pairs(extra_data) do
skill[k] = v skill[k] = v
end end
return useActiveSkill(self, skill) return RandomAI.useActiveSkill(self, skill)
end end
random_cb["AskForSkillInvoke"] = function(self, jsonData) random_cb["AskForSkillInvoke"] = function(self, jsonData)
@ -202,7 +202,7 @@ random_cb["PlayCard"] = function(self, jsonData)
local card = sth local card = sth
local skill = card.skill ---@type ActiveSkill local skill = card.skill ---@type ActiveSkill
if math.random() > 0.15 then if math.random() > 0.15 then
local ret = useActiveSkill(self, skill, card) local ret = RandomAI.useActiveSkill(self, skill, card)
if ret ~= "" then return ret end if ret ~= "" then return ret end
table.removeOne(cards, card) table.removeOne(cards, card)
else else
@ -211,14 +211,14 @@ random_cb["PlayCard"] = function(self, jsonData)
elseif sth:isInstanceOf(ActiveSkill) then elseif sth:isInstanceOf(ActiveSkill) then
local active = sth local active = sth
if math.random() > 0.30 then if math.random() > 0.30 then
local ret = useActiveSkill(self, active, nil) local ret = RandomAI.useActiveSkill(self, active, nil)
if ret ~= "" then return ret end if ret ~= "" then return ret end
end end
table.removeOne(cards, active) table.removeOne(cards, active)
else else
local vs = sth local vs = sth
if math.random() > 0.20 then if math.random() > 0.20 then
local ret = useVSSkill(self, vs) local ret = self:useVSSkill(vs)
-- TODO: handle vs result -- TODO: handle vs result
end end
table.removeOne(cards, vs) table.removeOne(cards, vs)
@ -228,6 +228,9 @@ random_cb["PlayCard"] = function(self, jsonData)
return "" return ""
end end
-- FIXME: for smart ai
RandomAI.cb_table = random_cb
function RandomAI:initialize(player) function RandomAI:initialize(player)
AI.initialize(self, player) AI.initialize(self, player)
self.cb_table = random_cb self.cb_table = random_cb

File diff suppressed because it is too large Load Diff

View File

@ -1067,7 +1067,7 @@ function Room:askForUseActiveSkill(player, skill_name, prompt, cancelable, extra
local command = "AskForUseActiveSkill" local command = "AskForUseActiveSkill"
self:notifyMoveFocus(player, extra_data.skillName or skill_name) -- for display skill name instead of command name self:notifyMoveFocus(player, extra_data.skillName or skill_name) -- for display skill name instead of command name
local data = {skill_name, prompt, cancelable, json.encode(extra_data)} local data = {skill_name, prompt, cancelable, extra_data}
Fk.currentResponseReason = extra_data.skillName Fk.currentResponseReason = extra_data.skillName
local result = self:doRequest(player, command, json.encode(data)) local result = self:doRequest(player, command, json.encode(data))
@ -1951,7 +1951,7 @@ end
---@param pattern string|nil @ 使用牌的规则默认就是card_name的值 ---@param pattern string|nil @ 使用牌的规则默认就是card_name的值
---@param prompt string|nil @ 提示信息 ---@param prompt string|nil @ 提示信息
---@param cancelable bool @ 能否点取消 ---@param cancelable bool @ 能否点取消
---@param extra_data integer|nil @ 额外信息 ---@param extra_data? UseExtraData @ 额外信息
---@param event_data CardEffectEvent|nil @ 事件信息 ---@param event_data CardEffectEvent|nil @ 事件信息
---@return CardUseStruct | nil @ 返回关于本次使用牌的数据,以便后续处理 ---@return CardUseStruct | nil @ 返回关于本次使用牌的数据,以便后续处理
function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extra_data, event_data) function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extra_data, event_data)
@ -2683,7 +2683,7 @@ function Room:handleCardEffect(event, cardEffectEvent)
local players = {} local players = {}
Fk.currentResponsePattern = "nullification" Fk.currentResponsePattern = "nullification"
for _, p in ipairs(self.alive_players) do for _, p in ipairs(self.alive_players) do
local cards = p:getHandlyIds(true) local cards = p:getHandlyIds()
for _, cid in ipairs(cards) do for _, cid in ipairs(cards) do
if if
Fk:getCardById(cid).trueName == "nullification" and Fk:getCardById(cid).trueName == "nullification" and

View File

@ -84,6 +84,14 @@ fk.IceDamage = 4
---@field public who integer ---@field public who integer
---@field public damage DamageStruct ---@field public damage DamageStruct
--- askForUseCard中的extra_data
---@class UseExtraData
---@field public must_targets? integer[] @ 必须选的目标(?)
---@field public exclusive_targets? integer[] @ ??
---@field public bypass_distances? boolean @ 无距离限制?
---@field public bypass_times? boolean @ 无次数限制?
---@field public playing? boolean @ (AI专用) 出牌阶段?
---@class CardUseStruct ---@class CardUseStruct
---@field public from integer ---@field public from integer
---@field public tos TargetGroup ---@field public tos TargetGroup

View File

@ -1,3 +1,4 @@
--[[
fk.ai_card.thunder__slash = fk.ai_card.slash fk.ai_card.thunder__slash = fk.ai_card.slash
fk.ai_use_play.thunder__slash = fk.ai_use_play.slash fk.ai_use_play.thunder__slash = fk.ai_use_play.slash
fk.ai_card.fire__slash = fk.ai_card.slash fk.ai_card.fire__slash = fk.ai_card.slash
@ -98,7 +99,7 @@ end
fk.ai_discard["fire_attack_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) fk.ai_discard["fire_attack_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
local use = self:eventData("UseCard") local use = self:eventData("UseCard")
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do
if self:isEnemie(p) then if self:isEnemy(p) then
local cards = table.map(self.player:getCardIds("h"), function(id) local cards = table.map(self.player:getCardIds("h"), function(id)
return Fk:getCardById(id) return Fk:getCardById(id)
end) end)
@ -122,7 +123,7 @@ fk.ai_nullification.fire_attack = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) and #to:getCardIds("h") > 0 and #from:getCardIds("h") > 1 then 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 if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -154,7 +155,7 @@ fk.ai_nullification.supply_shortage = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) then if self:isEnemy(to) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -176,3 +177,4 @@ fk.ai_skill_invoke["#fan_skill"] = function(self)
end end
end end
end end
--]]

View File

@ -5,70 +5,51 @@
----------------------------- -----------------------------
--- 弃牌相关判定函数的表。键为技能名,值为原型如下的函数。 --- 弃牌相关判定函数的表。键为技能名,值为原型如下的函数。
---@type table<string, fun(self: SmartAI, min_num: number, num: number, include_equip: bool, cancelable: bool, pattern: string, prompt: string): any> ---@type table<string, fun(self: SmartAI, min_num: number, num: number, include_equip: bool, cancelable: bool, pattern: string, prompt: string): integer[]|nil>
fk.ai_discard = {} fk.ai_discard = {}
--- 请求弃置 local default_discard = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
--- if cancelable then return nil end
---由skillName进行下一级的决策只需要在下一级里返回需要弃置的卡牌id表就行
fk.ai_use_skill["discard_skill"] = function(self, prompt, cancelable, data)
local ask = fk.ai_discard[data.skillName]
self:assignValue()
if type(ask) == "function" then
ask = ask(self, data.min_num, data.num, data.include_equip, cancelable, data.pattern, prompt)
end
if type(ask) ~= "table" and not cancelable then
local flag = "h" local flag = "h"
if data.include_equip then if include_equip then
flag = "he" flag = "he"
end end
ask = {} local ret = {}
local cards = table.map(self.player:getCardIds(flag), function(id) local cards = self.player:getCardIds(flag)
return Fk:getCardById(id) for _, cid in ipairs(cards) do
end table.insert(ret, cid)
) if #ret >= min_num then
self:sortValue(cards)
for _, c in ipairs(cards) do
table.insert(ask, c.id)
if #ask >= data.min_num then
break break
end end
end end
end return ret
if type(ask) == "table" and #ask >= data.min_num then end
self.use_id = json.encode {
skill = data.skillName, fk.ai_active_skill["discard_skill"] = function(self, prompt, cancelable, data)
subcards = ask local ret = self:callFromTable(fk.ai_discard, not cancelable and default_discard, data.skillName,
} self, data.min_num, data.num, data.include_equip, cancelable, data.pattern, prompt)
end
if ret == nil or #ret < data.min_num then return nil end
return self:buildUseReply { skill = "discard_skill", subcards = ret }
end end
-- choose_players_skill: 选人相关AI -- choose_players_skill: 选人相关AI
------------------------------------- -------------------------------------
---@class ChoosePlayersReply
---@field cardId integer|nil
---@field targets integer[]
--- 选人相关判定函数的表。键为技能名,值为原型如下的函数。 --- 选人相关判定函数的表。键为技能名,值为原型如下的函数。
---@type table<string, fun(self: SmartAI, targets: integer[], min_num: number, num: number, cancelable: bool)> ---@type table<string, fun(self: SmartAI, targets: integer[], min_num: number, num: number, cancelable: bool): ChoosePlayersReply|nil>
fk.ai_choose_players = {} fk.ai_choose_players = {}
--- 请求选择目标 fk.ai_active_skill["choose_players_skill"] = function(self, prompt, cancelable, data)
--- local ret = self:callFromTable(fk.ai_choose_players, nil, data.skillName,
---由skillName进行下一级的决策只需要在下一级里给self.use_tos添加角色id为目标就行 self, data.targets, data.min_num, data.num, cancelable)
fk.ai_use_skill["choose_players_skill"] = function(self, prompt, cancelable, data)
local ask = fk.ai_choose_players[data.skillName] if ret then
if type(ask) == "function" then return self:buildUseReply({ skill = "choose_players_skill", subcards = { ret.cardId } }, ret.targets)
ask(self, data.targets, data.min_num, data.num, cancelable)
end
if #self.use_tos > 0 then
if self.use_id then
self.use_id = json.encode {
skill = data.skillName,
subcards = self.use_id
}
else
self.use_id = json.encode {
skill = data.skillName,
subcards = {}
}
end
end end
end end

View File

@ -1,5 +1,6 @@
require "packages.standard.ai.aux_skills" require "packages.standard.ai.aux_skills"
--[[
fk.ai_use_play["rende"] = function(self, skill) fk.ai_use_play["rende"] = function(self, skill)
for _, p in ipairs(self.friends_noself) do for _, p in ipairs(self.friends_noself) do
if p.kingdom == "shu" and #self.player:getCardIds("h") >= self.player.hp then if p.kingdom == "shu" and #self.player:getCardIds("h") >= self.player.hp then
@ -194,7 +195,7 @@ fk.ai_skill_invoke["tieqi"] = function(self, data, prompt)
local use = self:eventData("UseCard") local use = self:eventData("UseCard")
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do
p = self.room:getPlayerById(p) p = self.room:getPlayerById(p)
if self:isEnemie(p) then if self:isEnemy(p) then
return true return true
end end
end end
@ -215,13 +216,13 @@ fk.ai_skill_invoke["biyue"] = true
fk.ai_choose_players["tuxi"] = function(self, targets, min_num, num, cancelable) fk.ai_choose_players["tuxi"] = function(self, targets, min_num, num, cancelable)
for _, pid in ipairs(targets) do for _, pid in ipairs(targets) do
local p = self.room:getPlayerById(pid) local p = self.room:getPlayerById(pid)
if self:isEnemie(p) and #self.use_tos < num then if self:isEnemy(p) and #self.use_tos < num then
table.insert(self.use_tos, pid) table.insert(self.use_tos, pid)
end end
end end
end end
fk.ai_use_skill["yiji_active"] = function(self, prompt, cancelable, data) fk.ai_active_skill["yiji_active"] = function(self, prompt, cancelable, data)
for _, p in ipairs(self.friends_noself) do for _, p in ipairs(self.friends_noself) do
for c, cid in ipairs(self.player.tag["yiji_ids"]) do for c, cid in ipairs(self.player.tag["yiji_ids"]) do
c = Fk:getCardById(cid) c = Fk:getCardById(cid)
@ -247,7 +248,7 @@ fk.ai_choose_players["liuli"] = function(self, targets, min_num, num, cancelable
self:sortValue(cards) self:sortValue(cards)
for _, pid in ipairs(targets) do for _, pid in ipairs(targets) do
local p = self.room:getPlayerById(pid) local p = self.room:getPlayerById(pid)
if self:isEnemie(p) and #self.use_tos < num and #cards > 0 then if self:isEnemy(p) and #self.use_tos < num and #cards > 0 then
table.insert(self.use_tos, pid) table.insert(self.use_tos, pid)
self.use_id = { cards[1].id } self.use_id = { cards[1].id }
return return
@ -262,3 +263,4 @@ fk.ai_choose_players["liuli"] = function(self, targets, min_num, num, cancelable
end end
end end
end end
--]]

View File

@ -1,3 +1,38 @@
-- 基本牌:杀,闪,桃
fk.ai_use_card["slash"] = function(self, pattern, prompt, cancelable, extra_data)
local slashes = self:getCards("slash", "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
return self:buildUseReply(slashes[1].id, targets)
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_card.slash = { fk.ai_card.slash = {
intention = 100, -- 身份值 intention = 100, -- 身份值
value = 4, -- 卡牌价值 value = 4, -- 卡牌价值
@ -101,7 +136,7 @@ fk.ai_use_play["slash"] = function(self, card)
end end
end end
fk.ai_ask_usecard["#slash-jink"] = function(self, pattern, prompt, cancelable, extra_data) fk.ai_use_card["#slash-jink"] = function(self, pattern, prompt, cancelable, extra_data)
local act = self:getActives(pattern) local act = self:getActives(pattern)
if tonumber(prompt:split(":")[4]) > #act then if tonumber(prompt:split(":")[4]) > #act then
return return
@ -138,7 +173,7 @@ fk.ai_ask_usecard["#slash-jink"] = function(self, pattern, prompt, cancelable, e
end end
end end
fk.ai_ask_usecard["#slash-jinks"] = fk.ai_ask_usecard["#slash-jink"] fk.ai_use_card["#slash-jinks"] = fk.ai_use_card["#slash-jink"]
fk.ai_use_play["snatch"] = function(self, card) fk.ai_use_play["snatch"] = function(self, card)
for _, p in ipairs(self.friends_noself) do for _, p in ipairs(self.friends_noself) do
@ -164,7 +199,7 @@ fk.ai_nullification.snatch = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) and self:isEnemie(from) then if self:isEnemy(to) and self:isEnemy(from) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -196,7 +231,7 @@ fk.ai_nullification.dismantlement = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) and self:isEnemie(from) then if self:isEnemy(to) and self:isEnemy(from) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -222,7 +257,7 @@ fk.ai_nullification.indulgence = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) then if self:isEnemy(to) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -261,7 +296,7 @@ end
fk.ai_nullification.collateral = function(self, card, to, from, positive) fk.ai_nullification.collateral = function(self, card, to, from, positive)
if positive then if positive then
if self:isFriend(to) and self:isEnemie(from) 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 if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -271,7 +306,7 @@ end
fk.ai_nullification.ex_nihilo = function(self, card, to, from, positive) fk.ai_nullification.ex_nihilo = function(self, card, to, from, positive)
if positive then if positive then
if self:isEnemie(to) then if self:isEnemy(to) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -293,7 +328,7 @@ fk.ai_nullification.savage_assault = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) then if self:isEnemy(to) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -309,7 +344,7 @@ fk.ai_nullification.archery_attack = function(self, card, to, from, positive)
end end
end end
else else
if self:isEnemie(to) then if self:isEnemy(to) then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -319,7 +354,7 @@ end
fk.ai_nullification.god_salvation = function(self, card, to, from, positive) fk.ai_nullification.god_salvation = function(self, card, to, from, positive)
if positive then if positive then
if self:isEnemie(to) and to.hp ~= to.maxHp then if self:isEnemy(to) and to.hp ~= to.maxHp then
if #self.avail_cards > 1 or self:isWeak(to) then if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1] self.use_id = self.avail_cards[1]
end end
@ -410,7 +445,7 @@ end
fk.ai_discard["#double_swords_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) fk.ai_discard["#double_swords_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
local use = self:eventData("UseCard") local use = self:eventData("UseCard")
return self:isEnemie(use.from) and { self.player:getCardIds("h")[1] } return self:isEnemy(use.from) and { self.player:getCardIds("h")[1] }
end end
fk.ai_discard["#axe_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) fk.ai_discard["#axe_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
@ -420,7 +455,7 @@ fk.ai_discard["#axe_skill"] = function(self, min_num, num, include_equip, cancel
if Fk:getCardById(cid):matchPattern(pattern) then if Fk:getCardById(cid):matchPattern(pattern) then
table.insert(ids, cid) table.insert(ids, cid)
end end
if #ids >= min_num and self:isEnemie(effect.to) if #ids >= min_num and self:isEnemy(effect.to)
and (self:isWeak(effect.to) or #self.player:getCardIds("he") > 3) then and (self:isWeak(effect.to) or #self.player:getCardIds("he") > 3) then
return ids return ids
end end
@ -436,3 +471,4 @@ fk.ai_skill_invoke["#eight_diagram_skill"] = function(self)
local effect = self:eventData("CardEffect") local effect = self:eventData("CardEffect")
return effect and self:isFriend(effect.to) return effect and self:isFriend(effect.to)
end end
--]]