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