diff --git a/lua/server/ai/init.lua b/lua/server/ai/init.lua index 8426698b..753fc2d2 100644 --- a/lua/server/ai/init.lua +++ b/lua/server/ai/init.lua @@ -3,3 +3,4 @@ AI = require "server.ai.ai" TrustAI = require "server.ai.trust_ai" RandomAI = require "server.ai.random_ai" +SmartAI = require "server.ai.smart_ai" diff --git a/lua/server/ai/smart_ai.lua b/lua/server/ai/smart_ai.lua index 184d439b..09b1f29b 100644 --- a/lua/server/ai/smart_ai.lua +++ b/lua/server/ai/smart_ai.lua @@ -1,10 +1,1091 @@ -- SPDX-License-Identifier: GPL-3.0-or-later ---@class SmartAI: AI -local SmartAI = AI:subclass("RandomAI") +SmartAI = AI:subclass("SmartAI") + +---@param self SmartAI +---@param skill ActiveSkill|ViewAsSkill|Card +local function usePlaySkill(self, skill) + self.use_id = nil + self.use_tos = {} + Self = self.player + self.special_skill = nil + if skill:isInstanceOf(Card) then + local uc = fk.ai_use_play[skill.name] + if type(uc) == "function" then + uc(self, skill) + end + if self.use_id == nil then + if type(skill.special_skills) == "table" then + for _, sn in ipairs(skill.special_skills) do + uc = fk.ai_use_play[sn] + if type(uc) == "function" then + uc(self, skill) + if self.use_id then + break + end + end + end + end + if skill.type == 3 then + if self.player:getEquipment(skill.sub_type) or #self.player:getCardIds("h") <= self.player.hp then + return "" + end + self.use_id = skill.id + elseif skill.is_damage_card and skill.multiple_targets then + if #self.enemies < #self.friends_noself then + return "" + end + self.use_id = skill.id + end + end + elseif skill:isInstanceOf(ViewAsSkill) then + local selected = {} + local cards = + table.map( + self.player:getCardIds("&he"), + function(id) + return Fk:getCardById(id) + end + ) + self:sortValue(cards) + for _, c in ipairs(cards) do + if skill:cardFilter(c.id, selected) then + table.insert(selected, c.id) + end + end + local tc = skill:viewAs(selected) + if tc then + local uc = fk.ai_use_play[tc.name] + if type(uc) == "function" then + uc(self, tc) + if self.use_id then + self.use_id = selected + end + end + end + else + local uc = fk.ai_use_play[skill.name] + if type(uc) == "function" then + uc(self, skill) + end + end + if self.use_id then + if not skill:isInstanceOf(Card) then + self.use_id = + json.encode { + skill = skill.name, + subcards = self.use_id + } + end + return json.encode { + card = self.use_id, + targets = self.use_tos, + special_skill = self.special_skill + } + end + return "" +end + +fk.ai_use_play = {} + +local trust_cb = {} + +trust_cb.AskForUseActiveSkill = function(self, jsonData) + local data = json.decode(jsonData) + local skill = Fk.skills[data[1]] + local prompt = data[2] + local cancelable = data[3] + self:updatePlayers() + local extra_data = json.decode(data[4]) + for k, v in pairs(extra_data) do + skill[k] = v + end + self.use_id = nil + self.use_tos = {} + local ask = fk.ai_use_skill[data[1]] + if type(ask) == "function" then + ask(self, prompt, cancelable, extra_data) + end + if self.use_id then + return json.encode { + card = self.use_id, + targets = self.use_tos + } + end + return "" +end + +fk.ai_use_skill = {} + +fk.ai_use_skill.choose_players_skill = function(self, prompt, cancelable, data) + local ask = fk.ai_choose_players[data.skillName] + if type(ask) == "function" then + 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 + +fk.ai_choose_players = {} + +fk.ai_use_skill.discard_skill = function(self, prompt, cancelable, data) + local ask = fk.ai_dis_card[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" + if data.include_equip then + flag = "he" + end + ask = {} + local cards = + table.map( + self.player:getCardIds(flag), + function(id) + return Fk:getCardById(id) + end + ) + self:sortValue(cards) + for _, c in ipairs(cards) do + table.insert(ask, c.id) + if #ask >= data.min_num then + break + end + end + end + if type(ask) == "table" and #ask >= data.min_num then + self.use_id = + json.encode { + skill = data.skillName, + subcards = ask + } + end +end + +fk.ai_dis_card = {} + +trust_cb.AskForSkillInvoke = function(self, jsonData) + local data = json.decode(jsonData) + local prompt = data[2] + local extra_data = data[3] + local ask = fk.ai_skill_invoke[data[1]] + self:updatePlayers() + if type(ask) == "function" then + return ask(self, extra_data, prompt) and "1" or "" + elseif type(ask) == "boolean" then + return ask and "1" or "" + elseif Fk.skills[data[1]].frequency == 1 then + return "1" + else + return table.random { "1", "" } + end +end + +fk.ai_skill_invoke = {} + +trust_cb.AskForUseCard = function(self, jsonData) + local data = json.decode(jsonData) + local pattern = data[2] + local prompt = data[3] + local cancelable = data[4] + local extra_data = data[5] + self:updatePlayers() + self.use_id = nil + self.use_tos = {} + local exp = Exppattern:Parse(data[2] or data[1]) + self.avail_cards = + table.filter( + self.player:getCardIds("&he"), + function(id) + return exp:match(Fk:getCardById(id)) and not self.player:prohibitUse(Fk:getCardById(id)) + end + ) + Self = self.player + local ask = fk.ai_askuse_card[prompt:split(":")[1]] + if type(ask) == "function" then + ask(self, pattern, prompt, cancelable, extra_data) + else + local cards = + table.map( + self.player:getCardIds("&he"), + function(id) + return Fk:getCardById(id) + end + ) + self:sortValue(cards) + for _, sth in ipairs(self:getActives(pattern)) do + if sth:isInstanceOf(Card) then + if sth.skill:canUse(self.player, sth) and not self.player:prohibitUse(sth) then + local ret = usePlaySkill(self, sth) + if ret ~= "" then + return ret + end + end + else + local selected = {} + for _, c in ipairs(cards) do + if sth:cardFilter(c.id, selected) then + table.insert(selected, c.id) + end + end + local tc = sth:viewAs(selected) + if tc and tc:matchPattern(pattern) then + local uc = fk.ai_use_play[tc.name] + if type(uc) == "function" then + uc(self, tc) + if self.use_id then + self.use_id = + json.encode { + skill = sth.name, + subcards = selected + } + break + end + end + end + end + end + end + ask = fk.ai_askuse_card[data[1]] + if self.use_id == nil and type(ask) == "function" then + ask(self, pattern, prompt, cancelable, extra_data) + end + if self.use_id == true then + self.use_id = self.avail_cards[1] + end + if self.use_id then + return json.encode { + card = self.use_id, + targets = self.use_tos + } + end + return "" +end + +fk.ai_askuse_card = {} +fk.ai_nullification = {} + +fk.ai_askuse_card.nullification = function(self, pattern, prompt, cancelable, extra_data) + local effect = self:eventData("CardEffect") + if effect.to then + fk.askNullificationData = effect + fk.askNullification = 1 + elseif effect.from then + fk.askNullification = fk.askNullification + 1 + end + effect = fk.askNullificationData + local positive = fk.askNullification % 2 == 1 + local ask = fk.ai_nullification[effect.card.name] + if type(ask) == "function" then + ask(self, effect.card, self.room:getPlayerById(effect.to), self.room:getPlayerById(effect.from), positive) + end +end + +fk.ai_askuse_card["#AskForPeaches"] = function(self, pattern, prompt, cancelable, extra_data) + local dying = self:eventData("Dying") + local who = self.room:getPlayerById(dying.who) + if who and self:isFriend(who) then + local cards = + table.map( + self.player:getCardIds("&he"), + function(id) + return Fk:getCardById(id) + end + ) + self:sortValue(cards) + for _, sth in ipairs(self:getActives(pattern)) 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_askuse_card["#AskForPeachesSelf"] = fk.ai_askuse_card["#AskForPeaches"] + +fk.ai_card = {} +fk.cardValue = {} + +function SmartAI:assignValue(assign) + assign = assign or { "slash", "peach", "jink", "nullification" } + for v, p in ipairs(assign) do + local kept = {} + v = fk.ai_card[p] + v = v and v.value or 3 + for _, sth in ipairs(self:getActives(p)) do + if sth:isInstanceOf(Card) then + fk.cardValue[sth.id] = self:getValue(sth, kept) + else + fk.cardValue[sth.name] = self:getValue(sth, kept) + v + end + table.insert(kept, sth) + end + self.keptCv = nil + end +end + +function SmartAI:getValue(card, kept) + local v = fk.ai_card[card.name] + v = v and v.value or 0 + if kept then + if card:isInstanceOf(Card) then + if self.keptCv == nil then + self.keptCv = v + end + return v - #kept * 0.25 + else + return (self.keptCv or v) - #kept * 0.25 + end + elseif card:isInstanceOf(Card) then + return fk.cardValue[card.id] or v + else + return fk.cardValue[card.name] or v + end + return v +end + +function SmartAI:getPriority(card) + local v = card and fk.ai_card[card.name] + v = v and v.priority or 0 + if card:isInstanceOf(Card) then + if card:isInstanceOf(Armor) then + v = v + 7 + elseif card:isInstanceOf(Weapon) then + v = v + 3 + elseif card:isInstanceOf(OffensiveRide) then + v = v + 6 + elseif card:isInstanceOf(DefensiveRide) then + v = v + 4 + end + v = v + (13 - card.number) / 100 + v = v + card.suit / 100 + end + return v +end + +fk.compareFunc = { + hp = function(p) + return p.hp + end, + maxHp = function(p) + return p.maxHp + end, + hand = function(p) + return #p:getHandlyIds(true) + end, + equip = function(p) + return #p:getCardIds("e") + end, + maxcards = function(p) + return p.hp + end, + skill = function(p) + return #p:getAllSkills() + end, + defense = function(p) + return p.hp + #p:getHandlyIds(true) + end +} + +function SmartAI:sort(players, key, inverse) + key = key or "defense" + local func = fk.compareFunc[key] + if func == nil then + func = fk.compareFunc.defense + end + local function compare_func(a, b) + return func(a) < func(b) + end + table.sort(players, compare_func) + if inverse then + players = table.reverse(players) + end +end + +function SmartAI:sortValue(cards, inverse) + local function compare_func(a, b) + return self:getValue(a) < self:getValue(b) + end + table.sort(cards, compare_func) + if inverse then + cards = table.reverse(cards) + end +end + +function SmartAI:sortPriority(cards, inverse) + local function compare_func(a, b) + local va = a and self:getPriority(a) or 0 + local vb = b and self:getPriority(b) or 0 + if va == vb then + va = a and self:getValue(a) or 0 + vb = b and self:getValue(b) or 0 + end + return va > vb + end + table.sort(cards, compare_func) + if inverse then + cards = table.reverse(cards) + end +end + +---@param self SmartAI +trust_cb.AskForResponseCard = function(self, jsonData) + local data = json.decode(jsonData) + local pattern = data[2] + local prompt = data[3] + local cancelable = data[4] + local extra_data = data[5] + self:updatePlayers() + self.use_id = nil + local ask = fk.ai_response_card[prompt:split(":")[1]] + if type(ask) == "function" then + ask(self, pattern, prompt, cancelable, extra_data) + else + ask = fk.ai_response_card[data[1]] + if type(ask) == "function" then + ask(self, pattern, prompt, cancelable, extra_data) + end + local effect = self:eventData("CardEffect") + if effect and effect.card then + self:setUseId(pattern) + end + end + if self.use_id then + return json.encode { + card = self.use_id, + targets = {} + } + end + return "" +end + +fk.ai_response_card = {} + +function SmartAI:getRetrialCardId(cards, exchange) + local judge = self:eventData("Judge") + local isgood = judge.card:matchPattern(judge.pattern) + local canRetrial = {} + self:sortValue(cards) + if exchange then + for _, c in ipairs(cards) do + if c:matchPattern(judge.pattern) == isgood then + table.insert(canRetrial, c) + end + end + else + if isgood then + if self:isFriend(judge.who) then + return + end + elseif self:isEnemie(judge.who) then + return + end + end + for _, c in ipairs(cards) do + if + self:isFriend(judge.who) and c:matchPattern(judge.pattern) or + self:isEnemie(judge.who) and not c:matchPattern(judge.pattern) + then + table.insert(canRetrial, c) + end + end + if #canRetrial > 0 then + return canRetrial[1].id + end +end + +function SmartAI:getActives(pattern) + local cards = + table.map( + self.player:getCardIds("&he"), + function(id) + return Fk:getCardById(id) + end + ) + local exp = Exppattern:Parse(pattern) + cards = + table.filter( + cards, + function(c) + return exp:match(c) + end + ) + table.insertTable( + cards, + table.filter( + self.player:getAllSkills(), + function(s) + return s:isInstanceOf(ViewAsSkill) and s:enabledAtResponse(self.player, pattern) + end + ) + ) + self:sortPriority(cards) + return cards +end + +function SmartAI:setUseId(pattern) + local cards = + table.map( + self.player:getCardIds("&he"), + function(id) + return Fk:getCardById(id) + end + ) + self:sortValue(cards) + for _, sth in ipairs(self:getActives(pattern)) do + if sth:isInstanceOf(Card) then + self.use_id = sth.id + break + else + local selected = {} + for _, c in ipairs(cards) do + if sth:cardFilter(c.id, selected) then + table.insert(selected, c.id) + end + end + local tc = sth:viewAs(selected) + if tc and tc:matchPattern(pattern) then + self.use_id = + json.encode { + skill = sth.name, + subcards = selected + } + break + end + end + end +end + +function SmartAI:cardsView(pattern) + local actives = + table.filter( + self.player:getAllSkills(), + function(s) + return s:isInstanceOf(ViewAsSkill) and s:enabledAtResponse(self.player, pattern) + end + ) + return actives +end + +---@param self SmartAI +trust_cb.PlayCard = function(self, jsonData) + local cards = + table.map( + self.player:getHandlyIds(true), + function(id) + return Fk:getCardById(id) + end + ) + cards = + table.filter( + cards, + function(c) + return c.skill:canUse(self.player, c) and not self.player:prohibitUse(c) + end + ) + table.insertTable( + cards, + table.filter( + self.player:getAllSkills(), + function(s) + return s:isInstanceOf(ActiveSkill) and s:canUse(self.player) or + s:isInstanceOf(ViewAsSkill) and s:enabledAtPlay(self.player) + end + ) + ) + if #cards < 1 then + return + end + self:updatePlayers() + self:sortPriority(cards) + for _, sth in ipairs(cards) do + local ret = usePlaySkill(self, sth) + if ret ~= "" then + return ret + end + end + return "" +end + +fk.ai_card_chosen = {} + +trust_cb.AskForCardChosen = function(self, jsonData) + local data = json.decode(jsonData) + local to = self.room:getPlayerById(data[1]) + local chosen = fk.ai_card_chosen[data[3]] + if type(chosen) == "function" then + return chosen(self, to, data[2]) + elseif table.contains(self.friends, to) then + if string.find(data[2], "j") then + local jc = to:getCardIds("j") + if #jc > 0 then + return table.random(jc) + end + end + else + if string.find(data[2], "h") then + local hc = to:getCardIds("h") + if #hc == 1 then + return hc[1] + end + end + if string.find(data[2], "e") then + local ec = to:getCardIds("e") + if #ec > 0 then + return table.random(ec) + end + for c, id in ipairs(to:getCardIds("e")) do + --c = Fk:getCardById(id) + return id + end + end + if string.find(data[2], "h") then + local hc = to:getCardIds("h") + if #hc > 0 then + return table.random(hc) + end + end + end + return "" +end + +fk.ai_role = {} +fk.roleValue = {} + +fk.trick_judge = {} + +fk.trick_judge.indulgence = ".|.|heart" +fk.trick_judge.lightning = ".|.|^spade" +fk.trick_judge.supply_shortage = ".|.|club" + +local function table_clone(self) + local t = {} + for _, r in ipairs(self) do + table.insert(t, r) + end + return t +end + +trust_cb.AskForGuanxing = function(self, jsonData) + local data = json.decode(jsonData) + local cards = + table.map( + data.cards, + function(id) + return Fk:getCardById(id) + end + ) + self:sortValue(cards) + local top = {} + if self.room.current.phase < Player.Play then + local jt = + table.map( + self.room.current:getCardIds("j"), + function(id) + return Fk:getCardById(id) + end + ) + if #jt > 0 then + for i, j in ipairs(table.reverse(jt)) do + local tj = fk.trick_judge[j.name] + if tj then + for _, c in ipairs(table_clone(cards)) do + if c:matchPattern(tj) and #top < data.max_top_cards then + table.insert(top, c.id) + table.removeOne(cards, c) + tj = 1 + break + end + end + end + if tj ~= 1 and #cards > 0 and #top < data.max_top_cards then + table.insert(top, cards[1].id) + table.remove(cards, 1) + end + end + end + self:sortValue(cards, true) + for _, c in ipairs(table_clone(cards)) do + if #top < data.max_top_cards and c.skill:canUse(self.player, c) and usePlaySkill(self, c) ~= "" then + table.insert(top, c.id) + table.removeOne(cards, c) + break + end + end + end + for _, c in ipairs(table_clone(cards)) do + if #top < data.min_top_cards then + table.insert(top, c.id) + table.removeOne(cards, c) + break + end + end + return json.encode { + top, + table.map( + cards, + function(c) + return c.id + end + ) + } +end function SmartAI:initialize(player) AI.initialize(self, player) + self.cb_table = trust_cb + self.player = player + self.room = RoomInstance or ClientInstance + + fk.ai_role[player.id] = "neutral" + fk.roleValue[player.id] = { + lord = 0, + loyalist = 0, + rebel = 0, + renegade = 0 + } + self:updatePlayers() +end + +function SmartAI:isRolePredictable() + return self.room.settings.gameMode ~= "aaa_role_mode" +end + +local function aliveRoles(room) + fk.alive_roles = { + lord = 0, + loyalist = 0, + rebel = 0, + renegade = 0 + } + for _, ap in ipairs(room:getAllPlayers(false)) do + fk.alive_roles[ap.role] = 0 + end + for _, ap in ipairs(room:getAlivePlayers(false)) do + fk.alive_roles[ap.role] = fk.alive_roles[ap.role] + 1 + end + return fk.alive_roles +end + +function SmartAI:objectiveLevel(to) + if self.player.id == to.id then + return -2 + elseif #self.room:getAlivePlayers(false) < 3 then + return 5 + end + local ars = aliveRoles(self.room) + if self:isRolePredictable() then + fk.ai_role[self.player.id] = self.role + fk.roleValue[self.player.id][self.role] = 666 + if self.role == "renegade" then + fk.explicit_renegade = true + end + for _, p in ipairs(self.room:getAlivePlayers()) do + if + p.role == self.role or p.role == "lord" and self.role == "loyalist" or + p.role == "loyalist" and self.role == "lord" + then + table.insert(self.friends, p) + if p.id ~= self.player.id then + table.insert(self.friends_noself, p) + end + else + table.insert(self.enemies, p) + end + end + elseif self.role == "renegade" then + if to.role == "lord" then + return -1 + elseif ars.rebel < 1 then + return 4 + elseif fk.ai_role[to.id] == "loyalist" then + return ars.lord + ars.loyalist - ars.rebel + elseif fk.ai_role[to.id] == "rebel" then + local r = ars.rebel - ars.lord + ars.loyalist + if r >= 0 then + return 3 + else + return r + end + end + elseif self.role == "lord" or self.role == "loyalist" then + if fk.ai_role[to.id] == "rebel" then + return 5 + elseif to.role == "lord" then + return -2 + elseif ars.rebel < 1 then + if self.role == "lord" then + return fk.explicit_renegade and fk.ai_role[to.id] == "renegade" and 4 or to.hp > 1 and 2 or 0 + elseif fk.explicit_renegade then + return fk.ai_role[to.id] == "renegade" and 4 or -1 + else + return 3 + end + elseif fk.ai_role[to.id] == "loyalist" then + return -2 + elseif fk.ai_role[to.id] == "renegade" then + local r = ars.lord + ars.loyalist - ars.rebel + if r <= 0 then + return r + else + return 3 + end + end + elseif self.role == "rebel" then + if to.role == "lord" then + return 5 + elseif fk.ai_role[to.id] == "loyalist" then + return 4 + elseif fk.ai_role[to.id] == "rebel" then + return -2 + elseif fk.ai_role[to.id] == "renegade" then + local r = ars.rebel - ars.lord + ars.loyalist + if r > 0 then + return 1 + else + return r + end + end + end + return 0 +end + +function SmartAI:updatePlayers(update) + self.role = self.player.role + local neutrality = {} + self.enemies = {} + self.friends = {} + self.friends_noself = {} + + local aps = self.room:getAlivePlayers() + local function compare_func(a, b) + local v1 = fk.roleValue[a.id].rebel + local v2 = fk.roleValue[b.id].rebel + if v1 == v2 then + v1 = fk.roleValue[a.id].renegade + v2 = fk.roleValue[b.id].renegade + end + return v1 > v2 + end + table.sort(aps, compare_func) + fk.explicit_renegade = false + local ars = aliveRoles(self.room) + local rebel, renegade, loyalist = 0, 0, 0 + for _, ap in ipairs(aps) do + if ap.role == "lord" then + fk.ai_role[ap.id] = "loyalist" + elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then + rebel = rebel + 1 + fk.ai_role[ap.id] = "rebel" + elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then + renegade = renegade + 1 + fk.ai_role[ap.id] = "renegade" + fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100 + elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then + loyalist = loyalist + 1 + fk.ai_role[ap.id] = "loyalist" + else + fk.ai_role[ap.id] = "neutral" + end + end + + for n, p in ipairs(self.room:getAlivePlayers(false)) do + n = self:objectiveLevel(p) + if n < 0 then + table.insert(self.friends, p) + if p.id ~= self.player.id then + table.insert(self.friends_noself, p) + end + elseif n > 0 then + table.insert(self.enemies, p) + else + table.insert(neutrality, p) + end + end + self:assignValue() + --[[ + if self.enemies<1 and #neutrality>0 + and#self.toUse<3 and self:getOverflow()>0 + then + function compare_func(a,b) + return sgs.getDefense(a) 0 or fk.roleValue[player.id].rebel > 0 and intention < 0 then + fk.roleValue[player.id].renegade = fk.roleValue[player.id].renegade + + intention * (100 - fk.roleValue[player.id].renegade) / 200 + end + local aps = player.room:getAlivePlayers() + local function compare_func(a, b) + local v1 = fk.roleValue[a.id].rebel + local v2 = fk.roleValue[b.id].rebel + if v1 == v2 then + v1 = fk.roleValue[a.id].renegade + v2 = fk.roleValue[b.id].renegade + end + return v1 > v2 + end + table.sort(aps, compare_func) + fk.explicit_renegade = false + local ars = aliveRoles(player.room) + local rebel, renegade, loyalist = 0, 0, 0 + for _, ap in ipairs(aps) do + if ap.role == "lord" then + fk.ai_role[ap.id] = "loyalist" + elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then + rebel = rebel + 1 + fk.ai_role[ap.id] = "rebel" + elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then + renegade = renegade + 1 + fk.ai_role[ap.id] = "renegade" + fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100 + elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then + loyalist = loyalist + 1 + fk.ai_role[ap.id] = "loyalist" + else + fk.ai_role[ap.id] = "neutral" + end + end + fk.qWarning( + player.general .. + " " .. + intention .. + " " .. + fk.ai_role[player.id] .. + " rebelValue:" .. fk.roleValue[player.id].rebel .. " renegadeValue:" .. fk.roleValue[player.id].renegade + ) --]] + end +end + +function SmartAI:filterEvent(event, player, data) + if event == fk.TargetSpecified then + local callback = fk.ai_card[data.card.name] + callback = callback and callback.intention + if type(callback) == "function" then + for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do + p = self.room:getPlayerById(p) + local intention = callback(p.ai, data.card, self.room:getPlayerById(data.from)) + if type(intention) == "number" then + updateIntention(self.room:getPlayerById(data.from), p, intention) + end + end + elseif type(callback) == "number" then + for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do + p = self.room:getPlayerById(p) + updateIntention(self.room:getPlayerById(data.from), p, callback) + end + end + elseif event == fk.StartJudge then + fk.trick_judge[data.reason] = data.pattern + elseif event == fk.AfterCardsMove then + end +end + +function SmartAI:isWeak(player, getAP) + player = player or self.player + if type(player) == "number" then + player = self.room:getPlayerById(player) + end + return player.hp < 2 or player.hp <= 2 and #player:getCardIds("&h") <= 2 +end + +function SmartAI:isFriend(pid, tid) + if tid then + local bt = self:isFriend(pid) + return bt ~= nil and bt == self:isFriend(tid) + end + if type(pid) == "number" then + pid = self.room:getPlayerById(pid) + end + local ve = self:objectiveLevel(pid) + if ve < 0 then + return true + elseif ve > 0 then + return false + end +end + +function SmartAI:isEnemie(pid, tid) + if tid then + local bt = self:isFriend(pid) + return bt ~= nil and bt ~= self:isFriend(tid) + end + if type(pid) == "number" then + pid = self.room:getPlayerById(pid) + end + local ve = self:objectiveLevel(pid) + if ve > 0 then + return true + elseif ve < 0 then + return false + end +end + +function SmartAI:eventData(game_event) + local event = self.room.logic:getCurrentEvent():findParent(GameEvent[game_event], true) + return event and event.data[1] +end + +for _, n in ipairs(FileIO.ls("packages")) do + if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then + dofile("packages/" .. n .. "/" .. n .. "_ai.lua") + end +end +-- 加载两次拓展是为了能够引用,例如属性杀的使用直接套入普通杀的使用 +for _, n in ipairs(FileIO.ls("packages")) do + if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then + dofile("packages/" .. n .. "/" .. n .. "_ai.lua") + end end return SmartAI diff --git a/lua/server/ai/trust_ai.lua b/lua/server/ai/trust_ai.lua index 72ffbb00..153eca26 100644 --- a/lua/server/ai/trust_ai.lua +++ b/lua/server/ai/trust_ai.lua @@ -1,1091 +1,13 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -- Trust AI ---@class TrustAI: AI -TrustAI = AI:subclass("TrustAI") - ----@param self TrustAI ----@param skill ActiveSkill|ViewAsSkill|Card -local function usePlaySkill(self, skill) - self.use_id = nil - self.use_tos = {} - Self = self.player - self.special_skill = nil - if skill:isInstanceOf(Card) then - local uc = fk.ai_use_play[skill.name] - if type(uc) == "function" then - uc(self, skill) - end - if self.use_id == nil then - if type(skill.special_skills) == "table" then - for _, sn in ipairs(skill.special_skills) do - uc = fk.ai_use_play[sn] - if type(uc) == "function" then - uc(self, skill) - if self.use_id then - break - end - end - end - end - if skill.type == 3 then - if self.player:getEquipment(skill.sub_type) or #self.player:getCardIds("h") <= self.player.hp then - return "" - end - self.use_id = skill.id - elseif skill.is_damage_card and skill.multiple_targets then - if #self.enemies < #self.friends_noself then - return "" - end - self.use_id = skill.id - end - end - elseif skill:isInstanceOf(ViewAsSkill) then - local selected = {} - local cards = - table.map( - self.player:getCardIds("&he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, c in ipairs(cards) do - if skill:cardFilter(c.id, selected) then - table.insert(selected, c.id) - end - end - local tc = skill:viewAs(selected) - if tc then - local uc = fk.ai_use_play[tc.name] - if type(uc) == "function" then - uc(self, tc) - if self.use_id then - self.use_id = selected - end - end - end - else - local uc = fk.ai_use_play[skill.name] - if type(uc) == "function" then - uc(self, skill) - end - end - if self.use_id then - if not skill:isInstanceOf(Card) then - self.use_id = - json.encode { - skill = skill.name, - subcards = self.use_id - } - end - return json.encode { - card = self.use_id, - targets = self.use_tos, - special_skill = self.special_skill - } - end - return "" -end - -fk.ai_use_play = {} +local TrustAI = AI:subclass("TrustAI") local trust_cb = {} -trust_cb.AskForUseActiveSkill = function(self, jsonData) - local data = json.decode(jsonData) - local skill = Fk.skills[data[1]] - local prompt = data[2] - local cancelable = data[3] - self:updatePlayers() - local extra_data = json.decode(data[4]) - for k, v in pairs(extra_data) do - skill[k] = v - end - self.use_id = nil - self.use_tos = {} - local ask = fk.ai_use_skill[data[1]] - if type(ask) == "function" then - ask(self, prompt, cancelable, extra_data) - end - if self.use_id then - return json.encode { - card = self.use_id, - targets = self.use_tos - } - end - return "" -end - -fk.ai_use_skill = {} - -fk.ai_use_skill.choose_players_skill = function(self, prompt, cancelable, data) - local ask = fk.ai_choose_players[data.skillName] - if type(ask) == "function" then - 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 - -fk.ai_choose_players = {} - -fk.ai_use_skill.discard_skill = function(self, prompt, cancelable, data) - local ask = fk.ai_dis_card[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" - if data.include_equip then - flag = "he" - end - ask = {} - local cards = - table.map( - self.player:getCardIds(flag), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, c in ipairs(cards) do - table.insert(ask, c.id) - if #ask >= data.min_num then - break - end - end - end - if type(ask) == "table" and #ask >= data.min_num then - self.use_id = - json.encode { - skill = data.skillName, - subcards = ask - } - end -end - -fk.ai_dis_card = {} - -trust_cb.AskForSkillInvoke = function(self, jsonData) - local data = json.decode(jsonData) - local prompt = data[2] - local extra_data = data[3] - local ask = fk.ai_skill_invoke[data[1]] - self:updatePlayers() - if type(ask) == "function" then - return ask(self, extra_data, prompt) and "1" or "" - elseif type(ask) == "boolean" then - return ask and "1" or "" - elseif Fk.skills[data[1]].frequency == 1 then - return "1" - else - return table.random { "1", "" } - end -end - -fk.ai_skill_invoke = {} - -trust_cb.AskForUseCard = function(self, jsonData) - local data = json.decode(jsonData) - local pattern = data[2] - local prompt = data[3] - local cancelable = data[4] - local extra_data = data[5] - self:updatePlayers() - self.use_id = nil - self.use_tos = {} - local exp = Exppattern:Parse(data[2] or data[1]) - self.avail_cards = - table.filter( - self.player:getCardIds("&he"), - function(id) - return exp:match(Fk:getCardById(id)) and not self.player:prohibitUse(Fk:getCardById(id)) - end - ) - Self = self.player - local ask = fk.ai_askuse_card[prompt:split(":")[1]] - if type(ask) == "function" then - ask(self, pattern, prompt, cancelable, extra_data) - else - local cards = - table.map( - self.player:getCardIds("&he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, sth in ipairs(self:getActives(pattern)) do - if sth:isInstanceOf(Card) then - if sth.skill:canUse(self.player, sth) and not self.player:prohibitUse(sth) then - local ret = usePlaySkill(self, sth) - if ret ~= "" then - return ret - end - end - else - local selected = {} - for _, c in ipairs(cards) do - if sth:cardFilter(c.id, selected) then - table.insert(selected, c.id) - end - end - local tc = sth:viewAs(selected) - if tc and tc:matchPattern(pattern) then - local uc = fk.ai_use_play[tc.name] - if type(uc) == "function" then - uc(self, tc) - if self.use_id then - self.use_id = - json.encode { - skill = sth.name, - subcards = selected - } - break - end - end - end - end - end - end - ask = fk.ai_askuse_card[data[1]] - if self.use_id == nil and type(ask) == "function" then - ask(self, pattern, prompt, cancelable, extra_data) - end - if self.use_id == true then - self.use_id = self.avail_cards[1] - end - if self.use_id then - return json.encode { - card = self.use_id, - targets = self.use_tos - } - end - return "" -end - -fk.ai_askuse_card = {} -fk.ai_nullification = {} - -fk.ai_askuse_card.nullification = function(self, pattern, prompt, cancelable, extra_data) - local effect = self:eventData("CardEffect") - if effect.to then - fk.askNullificationData = effect - fk.askNullification = 1 - elseif effect.from then - fk.askNullification = fk.askNullification + 1 - end - effect = fk.askNullificationData - local positive = fk.askNullification % 2 == 1 - local ask = fk.ai_nullification[effect.card.name] - if type(ask) == "function" then - ask(self, effect.card, self.room:getPlayerById(effect.to), self.room:getPlayerById(effect.from), positive) - end -end - -fk.ai_askuse_card["#AskForPeaches"] = function(self, pattern, prompt, cancelable, extra_data) - local dying = self:eventData("Dying") - local who = self.room:getPlayerById(dying.who) - if who and self:isFriend(who) then - local cards = - table.map( - self.player:getCardIds("&he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, sth in ipairs(self:getActives(pattern)) 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_askuse_card["#AskForPeachesSelf"] = fk.ai_askuse_card["#AskForPeaches"] - -fk.ai_card = {} -fk.cardValue = {} - -function TrustAI:assignValue(assign) - assign = assign or { "slash", "peach", "jink", "nullification" } - for v, p in ipairs(assign) do - local kept = {} - v = fk.ai_card[p] - v = v and v.value or 3 - for _, sth in ipairs(self:getActives(p)) do - if sth:isInstanceOf(Card) then - fk.cardValue[sth.id] = self:getValue(sth, kept) - else - fk.cardValue[sth.name] = self:getValue(sth, kept) + v - end - table.insert(kept, sth) - end - self.keptCv = nil - end -end - -function TrustAI:getValue(card, kept) - local v = fk.ai_card[card.name] - v = v and v.value or 0 - if kept then - if card:isInstanceOf(Card) then - if self.keptCv == nil then - self.keptCv = v - end - return v - #kept * 0.25 - else - return (self.keptCv or v) - #kept * 0.25 - end - elseif card:isInstanceOf(Card) then - return fk.cardValue[card.id] or v - else - return fk.cardValue[card.name] or v - end - return v -end - -function TrustAI:getPriority(card) - local v = card and fk.ai_card[card.name] - v = v and v.priority or 0 - if card:isInstanceOf(Card) then - if card:isInstanceOf(Armor) then - v = v + 7 - elseif card:isInstanceOf(Weapon) then - v = v + 3 - elseif card:isInstanceOf(OffensiveRide) then - v = v + 6 - elseif card:isInstanceOf(DefensiveRide) then - v = v + 4 - end - v = v + (13 - card.number) / 100 - v = v + card.suit / 100 - end - return v -end - -fk.compareFunc = { - hp = function(p) - return p.hp - end, - maxHp = function(p) - return p.maxHp - end, - hand = function(p) - return #p:getHandlyIds(true) - end, - equip = function(p) - return #p:getCardIds("e") - end, - maxcards = function(p) - return p.hp - end, - skill = function(p) - return #p:getAllSkills() - end, - defense = function(p) - return p.hp + #p:getHandlyIds(true) - end -} - -function TrustAI:sort(players, key, inverse) - key = key or "defense" - local func = fk.compareFunc[key] - if func == nil then - func = fk.compareFunc.defense - end - local function compare_func(a, b) - return func(a) < func(b) - end - table.sort(players, compare_func) - if inverse then - players = table.reverse(players) - end -end - -function TrustAI:sortValue(cards, inverse) - local function compare_func(a, b) - return self:getValue(a) < self:getValue(b) - end - table.sort(cards, compare_func) - if inverse then - cards = table.reverse(cards) - end -end - -function TrustAI:sortPriority(cards, inverse) - local function compare_func(a, b) - local va = a and self:getPriority(a) or 0 - local vb = b and self:getPriority(b) or 0 - if va == vb then - va = a and self:getValue(a) or 0 - vb = b and self:getValue(b) or 0 - end - return va > vb - end - table.sort(cards, compare_func) - if inverse then - cards = table.reverse(cards) - end -end - ----@param self TrustAI -trust_cb.AskForResponseCard = function(self, jsonData) - local data = json.decode(jsonData) - local pattern = data[2] - local prompt = data[3] - local cancelable = data[4] - local extra_data = data[5] - self:updatePlayers() - self.use_id = nil - local ask = fk.ai_response_card[prompt:split(":")[1]] - if type(ask) == "function" then - ask(self, pattern, prompt, cancelable, extra_data) - else - ask = fk.ai_response_card[data[1]] - if type(ask) == "function" then - ask(self, pattern, prompt, cancelable, extra_data) - end - local effect = self:eventData("CardEffect") - if effect and effect.card then - self:setUseId(pattern) - end - end - if self.use_id then - return json.encode { - card = self.use_id, - targets = {} - } - end - return "" -end - -fk.ai_response_card = {} - -function TrustAI:getRetrialCardId(cards, exchange) - local judge = self:eventData("Judge") - local isgood = judge.card:matchPattern(judge.pattern) - local canRetrial = {} - self:sortValue(cards) - if exchange then - for _, c in ipairs(cards) do - if c:matchPattern(judge.pattern) == isgood then - table.insert(canRetrial, c) - end - end - else - if isgood then - if self:isFriend(judge.who) then - return - end - elseif self:isEnemie(judge.who) then - return - end - end - for _, c in ipairs(cards) do - if - self:isFriend(judge.who) and c:matchPattern(judge.pattern) or - self:isEnemie(judge.who) and not c:matchPattern(judge.pattern) - then - table.insert(canRetrial, c) - end - end - if #canRetrial > 0 then - return canRetrial[1].id - end -end - -function TrustAI:getActives(pattern) - local cards = - table.map( - self.player:getCardIds("&he"), - function(id) - return Fk:getCardById(id) - end - ) - local exp = Exppattern:Parse(pattern) - cards = - table.filter( - cards, - function(c) - return exp:match(c) - end - ) - table.insertTable( - cards, - table.filter( - self.player:getAllSkills(), - function(s) - return s:isInstanceOf(ViewAsSkill) and s:enabledAtResponse(self.player, pattern) - end - ) - ) - self:sortPriority(cards) - return cards -end - -function TrustAI:setUseId(pattern) - local cards = - table.map( - self.player:getCardIds("&he"), - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - for _, sth in ipairs(self:getActives(pattern)) do - if sth:isInstanceOf(Card) then - self.use_id = sth.id - break - else - local selected = {} - for _, c in ipairs(cards) do - if sth:cardFilter(c.id, selected) then - table.insert(selected, c.id) - end - end - local tc = sth:viewAs(selected) - if tc and tc:matchPattern(pattern) then - self.use_id = - json.encode { - skill = sth.name, - subcards = selected - } - break - end - end - end -end - -function TrustAI:cardsView(pattern) - local actives = - table.filter( - self.player:getAllSkills(), - function(s) - return s:isInstanceOf(ViewAsSkill) and s:enabledAtResponse(self.player, pattern) - end - ) - return actives -end - ----@param self TrustAI -trust_cb.PlayCard = function(self, jsonData) - local cards = - table.map( - self.player:getHandlyIds(true), - function(id) - return Fk:getCardById(id) - end - ) - cards = - table.filter( - cards, - function(c) - return c.skill:canUse(self.player, c) and not self.player:prohibitUse(c) - end - ) - table.insertTable( - cards, - table.filter( - self.player:getAllSkills(), - function(s) - return s:isInstanceOf(ActiveSkill) and s:canUse(self.player) or - s:isInstanceOf(ViewAsSkill) and s:enabledAtPlay(self.player) - end - ) - ) - if #cards < 1 then - return - end - self:updatePlayers() - self:sortPriority(cards) - for _, sth in ipairs(cards) do - local ret = usePlaySkill(self, sth) - if ret ~= "" then - return ret - end - end - return "" -end - -fk.ai_card_chosen = {} - -trust_cb.AskForCardChosen = function(self, jsonData) - local data = json.decode(jsonData) - local to = self.room:getPlayerById(data[1]) - local chosen = fk.ai_card_chosen[data[3]] - if type(chosen) == "function" then - return chosen(self, to, data[2]) - elseif table.contains(self.friends, to) then - if string.find(data[2], "j") then - local jc = to:getCardIds("j") - if #jc > 0 then - return table.random(jc) - end - end - else - if string.find(data[2], "h") then - local hc = to:getCardIds("h") - if #hc == 1 then - return hc[1] - end - end - if string.find(data[2], "e") then - local ec = to:getCardIds("e") - if #ec > 0 then - return table.random(ec) - end - for c, id in ipairs(to:getCardIds("e")) do - --c = Fk:getCardById(id) - return id - end - end - if string.find(data[2], "h") then - local hc = to:getCardIds("h") - if #hc > 0 then - return table.random(hc) - end - end - end - return "" -end - -fk.ai_role = {} -fk.roleValue = {} - -fk.trick_judge = {} - -fk.trick_judge.indulgence = ".|.|heart" -fk.trick_judge.lightning = ".|.|^spade" -fk.trick_judge.supply_shortage = ".|.|club" - -local function table_clone(self) - local t = {} - for _, r in ipairs(self) do - table.insert(t, r) - end - return t -end - -trust_cb.AskForGuanxing = function(self, jsonData) - local data = json.decode(jsonData) - local cards = - table.map( - data.cards, - function(id) - return Fk:getCardById(id) - end - ) - self:sortValue(cards) - local top = {} - if self.room.current.phase < Player.Play then - local jt = - table.map( - self.room.current:getCardIds("j"), - function(id) - return Fk:getCardById(id) - end - ) - if #jt > 0 then - for i, j in ipairs(table.reverse(jt)) do - local tj = fk.trick_judge[j.name] - if tj then - for _, c in ipairs(table_clone(cards)) do - if c:matchPattern(tj) and #top < data.max_top_cards then - table.insert(top, c.id) - table.removeOne(cards, c) - tj = 1 - break - end - end - end - if tj ~= 1 and #cards > 0 and #top < data.max_top_cards then - table.insert(top, cards[1].id) - table.remove(cards, 1) - end - end - end - self:sortValue(cards, true) - for _, c in ipairs(table_clone(cards)) do - if #top < data.max_top_cards and c.skill:canUse(self.player, c) and usePlaySkill(self, c) ~= "" then - table.insert(top, c.id) - table.removeOne(cards, c) - break - end - end - end - for _, c in ipairs(table_clone(cards)) do - if #top < data.min_top_cards then - table.insert(top, c.id) - table.removeOne(cards, c) - break - end - end - return json.encode { - top, - table.map( - cards, - function(c) - return c.id - end - ) - } -end - function TrustAI:initialize(player) AI.initialize(self, player) self.cb_table = trust_cb - self.player = player - self.room = RoomInstance or ClientInstance - - fk.ai_role[player.id] = "neutral" - fk.roleValue[player.id] = { - lord = 0, - loyalist = 0, - rebel = 0, - renegade = 0 - } - self:updatePlayers() -end - -function TrustAI:isRolePredictable() - return self.room.settings.gameMode ~= "aaa_role_mode" -end - -local function aliveRoles(room) - fk.alive_roles = { - lord = 0, - loyalist = 0, - rebel = 0, - renegade = 0 - } - for _, ap in ipairs(room:getAllPlayers(false)) do - fk.alive_roles[ap.role] = 0 - end - for _, ap in ipairs(room:getAlivePlayers(false)) do - fk.alive_roles[ap.role] = fk.alive_roles[ap.role] + 1 - end - return fk.alive_roles -end - -function TrustAI:objectiveLevel(to) - if self.player.id == to.id then - return -2 - elseif #self.room:getAlivePlayers(false) < 3 then - return 5 - end - local ars = aliveRoles(self.room) - if self:isRolePredictable() then - fk.ai_role[self.player.id] = self.role - fk.roleValue[self.player.id][self.role] = 666 - if self.role == "renegade" then - fk.explicit_renegade = true - end - for _, p in ipairs(self.room:getAlivePlayers()) do - if - p.role == self.role or p.role == "lord" and self.role == "loyalist" or - p.role == "loyalist" and self.role == "lord" - then - table.insert(self.friends, p) - if p.id ~= self.player.id then - table.insert(self.friends_noself, p) - end - else - table.insert(self.enemies, p) - end - end - elseif self.role == "renegade" then - if to.role == "lord" then - return -1 - elseif ars.rebel < 1 then - return 4 - elseif fk.ai_role[to.id] == "loyalist" then - return ars.lord + ars.loyalist - ars.rebel - elseif fk.ai_role[to.id] == "rebel" then - local r = ars.rebel - ars.lord + ars.loyalist - if r >= 0 then - return 3 - else - return r - end - end - elseif self.role == "lord" or self.role == "loyalist" then - if fk.ai_role[to.id] == "rebel" then - return 5 - elseif to.role == "lord" then - return -2 - elseif ars.rebel < 1 then - if self.role == "lord" then - return fk.explicit_renegade and fk.ai_role[to.id] == "renegade" and 4 or to.hp > 1 and 2 or 0 - elseif fk.explicit_renegade then - return fk.ai_role[to.id] == "renegade" and 4 or -1 - else - return 3 - end - elseif fk.ai_role[to.id] == "loyalist" then - return -2 - elseif fk.ai_role[to.id] == "renegade" then - local r = ars.lord + ars.loyalist - ars.rebel - if r <= 0 then - return r - else - return 3 - end - end - elseif self.role == "rebel" then - if to.role == "lord" then - return 5 - elseif fk.ai_role[to.id] == "loyalist" then - return 4 - elseif fk.ai_role[to.id] == "rebel" then - return -2 - elseif fk.ai_role[to.id] == "renegade" then - local r = ars.rebel - ars.lord + ars.loyalist - if r > 0 then - return 1 - else - return r - end - end - end - return 0 -end - -function TrustAI:updatePlayers(update) - self.role = self.player.role - local neutrality = {} - self.enemies = {} - self.friends = {} - self.friends_noself = {} - - local aps = self.room:getAlivePlayers() - local function compare_func(a, b) - local v1 = fk.roleValue[a.id].rebel - local v2 = fk.roleValue[b.id].rebel - if v1 == v2 then - v1 = fk.roleValue[a.id].renegade - v2 = fk.roleValue[b.id].renegade - end - return v1 > v2 - end - table.sort(aps, compare_func) - fk.explicit_renegade = false - local ars = aliveRoles(self.room) - local rebel, renegade, loyalist = 0, 0, 0 - for _, ap in ipairs(aps) do - if ap.role == "lord" then - fk.ai_role[ap.id] = "loyalist" - elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then - rebel = rebel + 1 - fk.ai_role[ap.id] = "rebel" - elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then - renegade = renegade + 1 - fk.ai_role[ap.id] = "renegade" - fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100 - elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then - loyalist = loyalist + 1 - fk.ai_role[ap.id] = "loyalist" - else - fk.ai_role[ap.id] = "neutral" - end - end - - for n, p in ipairs(self.room:getAlivePlayers(false)) do - n = self:objectiveLevel(p) - if n < 0 then - table.insert(self.friends, p) - if p.id ~= self.player.id then - table.insert(self.friends_noself, p) - end - elseif n > 0 then - table.insert(self.enemies, p) - else - table.insert(neutrality, p) - end - end - self:assignValue() - --[[ - if self.enemies<1 and #neutrality>0 - and#self.toUse<3 and self:getOverflow()>0 - then - function compare_func(a,b) - return sgs.getDefense(a) 0 or fk.roleValue[player.id].rebel > 0 and intention < 0 then - fk.roleValue[player.id].renegade = fk.roleValue[player.id].renegade + - intention * (100 - fk.roleValue[player.id].renegade) / 200 - end - local aps = player.room:getAlivePlayers() - local function compare_func(a, b) - local v1 = fk.roleValue[a.id].rebel - local v2 = fk.roleValue[b.id].rebel - if v1 == v2 then - v1 = fk.roleValue[a.id].renegade - v2 = fk.roleValue[b.id].renegade - end - return v1 > v2 - end - table.sort(aps, compare_func) - fk.explicit_renegade = false - local ars = aliveRoles(player.room) - local rebel, renegade, loyalist = 0, 0, 0 - for _, ap in ipairs(aps) do - if ap.role == "lord" then - fk.ai_role[ap.id] = "loyalist" - elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then - rebel = rebel + 1 - fk.ai_role[ap.id] = "rebel" - elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then - renegade = renegade + 1 - fk.ai_role[ap.id] = "renegade" - fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100 - elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then - loyalist = loyalist + 1 - fk.ai_role[ap.id] = "loyalist" - else - fk.ai_role[ap.id] = "neutral" - end - end - fk.qWarning( - player.general .. - " " .. - intention .. - " " .. - fk.ai_role[player.id] .. - " rebelValue:" .. fk.roleValue[player.id].rebel .. " renegadeValue:" .. fk.roleValue[player.id].renegade - ) --]] - end -end - -function TrustAI:filterEvent(event, player, data) - if event == fk.TargetSpecified then - local callback = fk.ai_card[data.card.name] - callback = callback and callback.intention - if type(callback) == "function" then - for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do - p = self.room:getPlayerById(p) - local intention = callback(p.ai, data.card, self.room:getPlayerById(data.from)) - if type(intention) == "number" then - updateIntention(self.room:getPlayerById(data.from), p, intention) - end - end - elseif type(callback) == "number" then - for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do - p = self.room:getPlayerById(p) - updateIntention(self.room:getPlayerById(data.from), p, callback) - end - end - elseif event == fk.StartJudge then - fk.trick_judge[data.reason] = data.pattern - elseif event == fk.AfterCardsMove then - end -end - -function TrustAI:isWeak(player, getAP) - player = player or self.player - if type(player) == "number" then - player = self.room:getPlayerById(player) - end - return player.hp < 2 or player.hp <= 2 and #player:getCardIds("&h") <= 2 -end - -function TrustAI:isFriend(pid, tid) - if tid then - local bt = self:isFriend(pid) - return bt ~= nil and bt == self:isFriend(tid) - end - if type(pid) == "number" then - pid = self.room:getPlayerById(pid) - end - local ve = self:objectiveLevel(pid) - if ve < 0 then - return true - elseif ve > 0 then - return false - end -end - -function TrustAI:isEnemie(pid, tid) - if tid then - local bt = self:isFriend(pid) - return bt ~= nil and bt ~= self:isFriend(tid) - end - if type(pid) == "number" then - pid = self.room:getPlayerById(pid) - end - local ve = self:objectiveLevel(pid) - if ve > 0 then - return true - elseif ve < 0 then - return false - end -end - -function TrustAI:eventData(game_event) - local event = self.room.logic:getCurrentEvent():findParent(GameEvent[game_event], true) - return event and event.data[1] -end - -for _, n in ipairs(FileIO.ls("packages")) do - if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then - dofile("packages/" .. n .. "/" .. n .. "_ai.lua") - end -end --- 加载两次拓展是为了能够引用,例如属性杀的使用直接套入普通杀的使用 -for _, n in ipairs(FileIO.ls("packages")) do - if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then - dofile("packages/" .. n .. "/" .. n .. "_ai.lua") - end end return TrustAI diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 26dab31b..efb847ef 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -24,8 +24,8 @@ local ServerPlayer = Player:subclass("ServerPlayer") function ServerPlayer:initialize(_self) Player.initialize(self) - self.serverplayer = _self -- 控制者 - self._splayer = _self -- 真正在玩的玩家 + self.serverplayer = _self -- 控制者 + self._splayer = _self -- 真正在玩的玩家 self._observers = { _self } -- "旁观"中的玩家,然而不包括真正的旁观者 self.id = _self:getId() self.room = nil @@ -45,7 +45,7 @@ function ServerPlayer:initialize(_self) self._prelighted_skills = {} self._timewaste_count = 0 - self.ai = TrustAI:new(self) + self.ai = SmartAI:new(self) end ---@param command string @@ -284,25 +284,25 @@ function ServerPlayer:marshal(player, observe) end for k, v in pairs(self.mark) do - player:doNotify("SetPlayerMark", json.encode{self.id, k, v}) + player:doNotify("SetPlayerMark", json.encode { self.id, k, v }) end for _, s in ipairs(self.player_skills) do - player:doNotify("AddSkill", json.encode{self.id, s.name}) + player:doNotify("AddSkill", json.encode { self.id, s.name }) end for k, v in pairs(self.cardUsedHistory) do if v[1] > 0 then - player:doNotify("AddCardUseHistory", json.encode{k, v[1]}) + player:doNotify("AddCardUseHistory", json.encode { k, v[1] }) end end for k, v in pairs(self.skillUsedHistory) do if v[4] > 0 then - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[1], 1}) - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[2], 2}) - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[3], 3}) - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[4], 4}) + player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[1], 1 }) + player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[2], 2 }) + player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[3], 3 }) + player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[4], 4 }) end end @@ -319,13 +319,13 @@ function ServerPlayer:reconnect() local room = self.room self.serverplayer:setState(fk.Player_Online) - self:doNotify("Setup", json.encode{ + self:doNotify("Setup", json.encode { self.id, self._splayer:getScreenName(), self._splayer:getAvatar(), }) self:doNotify("EnterLobby", "") - self:doNotify("EnterRoom", json.encode{ + self:doNotify("EnterRoom", json.encode { #room.players, room.timeout, room.settings, }) self:doNotify("StartGame", "") @@ -333,7 +333,7 @@ function ServerPlayer:reconnect() -- send player data for _, p in ipairs(room:getOtherPlayers(self, false, true)) do - self:doNotify("AddPlayer", json.encode{ + self:doNotify("AddPlayer", json.encode { p.id, p._splayer:getScreenName(), p._splayer:getAvatar(), @@ -350,13 +350,13 @@ function ServerPlayer:reconnect() for i = -2, -math.huge, -1 do local c = Fk.printed_cards[i] if not c then break end - self:doNotify("PrintCard", json.encode{ c.name, c.suit, c.number }) + self:doNotify("PrintCard", json.encode { c.name, c.suit, c.number }) end -- send card marks for id, marks in pairs(room.card_marks) do for k, v in pairs(marks) do - self:doNotify("SetCardMark", json.encode{ id, k, v }) + self:doNotify("SetCardMark", json.encode { id, k, v }) end end @@ -371,9 +371,9 @@ function ServerPlayer:reconnect() -- send fake skills for _, s in ipairs(self._manually_fake_skills) do - self:doNotify("AddSkill", json.encode{ self.id, s.name, true }) + self:doNotify("AddSkill", json.encode { self.id, s.name, true }) if table.contains(self.prelighted_skills, s) then - self:doNotify("PrelightSkill", json.encode{ s.name, true }) + self:doNotify("PrelightSkill", json.encode { s.name, true }) end end @@ -392,7 +392,7 @@ function ServerPlayer:turnOver() self.faceup = not self.faceup self.room:broadcastProperty(self, "faceup") - self.room:sendLog{ + self.room:sendLog { type = "#TurnOver", from = self.id, arg = self.faceup and "face_up" or "face_down", @@ -408,12 +408,12 @@ function ServerPlayer:showCards(cards) end local room = self.room - room:sendLog{ + room:sendLog { type = "#ShowCard", from = self.id, card = cards, } - room:doBroadcastNotify("ShowCard", json.encode{ + room:doBroadcastNotify("ShowCard", json.encode { from = self.id, cards = cards, }) @@ -478,7 +478,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay) self.phase = phase room:broadcastProperty(self, "phase") - room:sendLog{ + room:sendLog { type = "#GainAnExtraPhase", from = self.id, arg = phase_name_table[phase], @@ -553,7 +553,7 @@ function ServerPlayer:play(phase_table) if (not skip) or (cancel_skip) then GameEvent(GameEvent.Phase, self, self.phase):exec() else - room:sendLog{ + room:sendLog { type = "#PhaseSkipped", from = self.id, arg = phase_name_table[self.phase], @@ -565,11 +565,11 @@ end ---@param phase Phase function ServerPlayer:skip(phase) if not table.contains({ - Player.Judge, - Player.Draw, - Player.Play, - Player.Discard - }, phase) then + Player.Judge, + Player.Draw, + Player.Play, + Player.Discard + }, phase) then return end self.skipped_phases[phase] = true @@ -597,7 +597,7 @@ function ServerPlayer:gainAnExtraTurn(delay) end end - room:sendLog{ + room:sendLog { type = "#GainAnExtraTurn", from = self.id } @@ -664,7 +664,7 @@ end function ServerPlayer:addVirtualEquip(card) Player.addVirtualEquip(self, card) - self.room:doBroadcastNotify("AddVirtualEquip", json.encode{ + self.room:doBroadcastNotify("AddVirtualEquip", json.encode { player = self.id, name = card.name, subcards = card.subcards, @@ -673,7 +673,7 @@ end function ServerPlayer:removeVirtualEquip(cid) local ret = Player.removeVirtualEquip(self, cid) - self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{ + self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode { player = self.id, id = cid, }) @@ -682,22 +682,22 @@ end function ServerPlayer:addCardUseHistory(cardName, num) Player.addCardUseHistory(self, cardName, num) - self:doNotify("AddCardUseHistory", json.encode{cardName, num}) + self:doNotify("AddCardUseHistory", json.encode { cardName, num }) end function ServerPlayer:setCardUseHistory(cardName, num, scope) Player.setCardUseHistory(self, cardName, num, scope) - self:doNotify("SetCardUseHistory", json.encode{cardName, num, scope}) + self:doNotify("SetCardUseHistory", json.encode { cardName, num, scope }) end function ServerPlayer:addSkillUseHistory(cardName, num) Player.addSkillUseHistory(self, cardName, num) - self.room:doBroadcastNotify("AddSkillUseHistory", json.encode{self.id, cardName, num}) + self.room:doBroadcastNotify("AddSkillUseHistory", json.encode { self.id, cardName, num }) end function ServerPlayer:setSkillUseHistory(cardName, num, scope) Player.setSkillUseHistory(self, cardName, num, scope) - self.room:doBroadcastNotify("SetSkillUseHistory", json.encode{self.id, cardName, num, scope}) + self.room:doBroadcastNotify("SetSkillUseHistory", json.encode { self.id, cardName, num, scope }) end ---@param chained boolean @@ -710,7 +710,7 @@ function ServerPlayer:setChainState(chained) self.chained = chained room:broadcastProperty(self, "chained") - room:sendLog{ + room:sendLog { type = "#ChainStateChange", from = self.id, arg = self.chained and "chained" or "un-chained" @@ -721,7 +721,7 @@ function ServerPlayer:setChainState(chained) end function ServerPlayer:reset() - self.room:sendLog{ + self.room:sendLog { type = "#ChainStateChange", from = self.id, arg = "reset-general" @@ -770,12 +770,12 @@ function ServerPlayer:addFakeSkill(skill) table.insert(self._fake_skills, skill) for _, s in ipairs(skill.related_skills) do -- if s.main_skill == skill then -- TODO: need more detailed - table.insert(self._fake_skills, s) + table.insert(self._fake_skills, s) -- end end -- TODO - self:doNotify("AddSkill", json.encode{ self.id, skill.name, true }) + self:doNotify("AddSkill", json.encode { self.id, skill.name, true }) end ---@param skill Skill @@ -794,7 +794,7 @@ function ServerPlayer:loseFakeSkill(skill) end -- TODO - self:doNotify("LoseSkill", json.encode{ self.id, skill.name, true }) + self:doNotify("LoseSkill", json.encode { self.id, skill.name, true }) end function ServerPlayer:isFakeSkill(skill) @@ -830,7 +830,7 @@ function ServerPlayer:prelightSkill(skill, isPrelight) end end - self:doNotify("PrelightSkill", json.encode{ skill.name, isPrelight }) + self:doNotify("PrelightSkill", json.encode { skill.name, isPrelight }) end ---@param isDeputy bool @@ -854,7 +854,8 @@ function ServerPlayer:revealGeneral(isDeputy, no_trigger) local ret = true if not ((isDeputy and self.general ~= "anjiang") or (not isDeputy and self.deputyGeneral ~= "anjiang")) then - local other = Fk.generals[self:getMark(isDeputy and "__heg_general" or "__heg_deputy")] or Fk.generals["blank_shibing"] + local other = Fk.generals[self:getMark(isDeputy and "__heg_general" or "__heg_deputy")] or + Fk.generals["blank_shibing"] for _, sname in ipairs(other:getSkillNameList()) do local s = Fk.skills[sname] if s.frequency == Skill.Compulsory and s.relate_to_place ~= (isDeputy and "m" or "d") then @@ -873,9 +874,9 @@ function ServerPlayer:revealGeneral(isDeputy, no_trigger) local kingdom = general.kingdom self.kingdom = kingdom if oldKingdom == "unknown" and #table.filter(room:getOtherPlayers(self, false, true), - function(p) - return p.kingdom == kingdom - end) >= #room.players // 2 then + function(p) + return p.kingdom == kingdom + end) >= #room.players // 2 then self.kingdom = "wild" end room:broadcastProperty(self, "kingdom") @@ -887,7 +888,7 @@ function ServerPlayer:revealGeneral(isDeputy, no_trigger) self.gender = general.gender end - room:sendLog{ + room:sendLog { type = "#RevealGeneral", from = self.id, arg = isDeputy and "deputyGeneral" or "mainGeneral", @@ -905,7 +906,7 @@ function ServerPlayer:revealBySkillName(skill_name) if main then if table.contains(Fk.generals[self:getMark("__heg_general")] - :getSkillNameList(), skill_name) then + :getSkillNameList(), skill_name) then self:revealGeneral(false) return end @@ -913,7 +914,7 @@ function ServerPlayer:revealBySkillName(skill_name) if deputy then if table.contains(Fk.generals[self:getMark("__heg_deputy")] - :getSkillNameList(), skill_name) then + :getSkillNameList(), skill_name) then self:revealGeneral(true) return end @@ -926,7 +927,7 @@ function ServerPlayer:hideGeneral(isDeputy) local mark = isDeputy and "__heg_deputy" or "__heg_general" self:setMark(mark, generalName) - self:doNotify("SetPlayerMark", json.encode{ self.id, mark, generalName}) + self:doNotify("SetPlayerMark", json.encode { self.id, mark, generalName }) if isDeputy then room:setDeputyGeneral(self, "anjiang") @@ -961,6 +962,7 @@ function ServerPlayer:hideGeneral(isDeputy) room.logic:trigger(fk.GeneralHidden, room, generalName) end + -- 神貂蝉 ---@param p ServerPlayer @@ -980,7 +982,7 @@ function ServerPlayer:addBuddy(other) other = self.room:getPlayerById(other) end Player.addBuddy(self, other) - self:doNotify("AddBuddy", json.encode{ other.id, other.player_cards[Player.Hand] }) + self:doNotify("AddBuddy", json.encode { other.id, other.player_cards[Player.Hand] }) end function ServerPlayer:removeBuddy(other) @@ -992,7 +994,7 @@ function ServerPlayer:removeBuddy(other) end function ServerPlayer:getAI() - return self.ai + return self.ai end return ServerPlayer