From e840a3b32292e856ba572d0c30fca70686382b4a Mon Sep 17 00:00:00 2001 From: YoumuKon <38815081+YoumuKon@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:56:04 +0800 Subject: [PATCH] bugfix (#328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为findParent添加深度限制参数(默认无限制) - 搬运了damageByCardEffect - 修复了ex__choose_skill - 修复了华佗、吕蒙和古锭刀 - 添加势力映射,可以指定一个势力必须变成其他几个势力之一(需要神话再临包/OL包自行处理神将变将范围) - askForCardsChosen界限突破,改成了基于askForPoxi的格式 - 修复了空城虚拟杀可以方天的bug - 给强制平局添加了原因提醒 - 优化了移动牌的视觉逻辑 --- Fk/Pages/RoomLogic.js | 26 +++- Fk/RoomElement/PoxiBox.qml | 12 +- lua/client/client_util.lua | 3 + lua/client/i18n/en_US.lua | 6 + lua/client/i18n/zh_CN.lua | 6 + lua/core/engine.lua | 27 +++++ lua/core/player.lua | 2 +- lua/core/skill_type/active.lua | 2 +- lua/fk_ex.lua | 1 + lua/server/events/gameflow.lua | 5 + lua/server/gameevent.lua | 17 ++- lua/server/gamelogic.lua | 19 +++ lua/server/room.lua | 198 ++++++++++++++++++++----------- packages/maneuvering/init.lua | 16 ++- packages/standard/aux_poxi.lua | 22 ++++ packages/standard/aux_skills.lua | 10 +- packages/standard/init.lua | 57 +++++---- packages/standard_cards/init.lua | 2 +- packages/test/init.lua | 1 + 19 files changed, 318 insertions(+), 114 deletions(-) create mode 100644 packages/standard/aux_poxi.lua diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index 46fb9508..39be0dbe 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -275,15 +275,30 @@ function moveCards(moves) { const move = moves[i]; const from = getAreaItem(move.fromArea, move.from); const to = getAreaItem(move.toArea, move.to); - if (!from || !to || from === to) + if (!from || !to || (from === to && move.fromArea !== Card.DiscardPile)) continue; const items = from.remove(move.ids, move.fromSpecialName); - if (items.length > 0) - to.add(items, move.specialName); - to.updateCardPosition(true); + if (to === tablePile) { + let vanished = items.filter(c => c.cid === -1); + if (vanished.length > 0) { + drawPile.add(vanished, move.specialName); + drawPile.updateCardPosition(true); + } + vanished = items.filter(c => c.cid !== -1); + if (vanished.length > 0) { + to.add(vanished, move.specialName); + to.updateCardPosition(true); + } + } else { + if (items.length > 0) + to.add(items, move.specialName); + to.updateCardPosition(true); + } } } + + function resortHandcards() { if (!dashboard.handcardArea.cards.length) { return; @@ -1174,9 +1189,9 @@ callbacks["AskForPoxi"] = (jsonData) => { roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PoxiBox.qml"); const box = roomScene.popupBox.item; + box.extra_data = JSON.stringify(extra_data); box.poxi_type = type; box.card_data = data; - box.extra_data = extra_data; box.cancelable = cancelable; for (let d of data) { const arr = []; @@ -1185,6 +1200,7 @@ callbacks["AskForPoxi"] = (jsonData) => { ids.forEach(id => arr.push(lcall("GetCardData", id))); box.addCustomCards(d[0], arr); } + box.refreshPrompt(); roomScene.popupBox.moveToCenter(); box.cardsSelected.connect((ids) => { diff --git a/Fk/RoomElement/PoxiBox.qml b/Fk/RoomElement/PoxiBox.qml index 27340916..62a98c53 100644 --- a/Fk/RoomElement/PoxiBox.qml +++ b/Fk/RoomElement/PoxiBox.qml @@ -2,12 +2,13 @@ import QtQuick import QtQuick.Layouts +import Fk import Fk.Pages GraphicsBox { id: root - title.text: lcall("PoxiPrompt", poxi_type, card_data, extra_data) + title.text: Util.processPrompt(lcall("PoxiPrompt", poxi_type, card_data, extra_data)) // TODO: Adjust the UI design in case there are more than 7 cards width: 70 + 700 @@ -71,7 +72,7 @@ GraphicsBox { number: model.number || 0 autoBack: false known: model.cid !== -1 - selectable: root.selected_ids.includes(model.cid) || + selectable: chosenInBox || lcall("PoxiFilter", root.poxi_type, model.cid, root.selected_ids, root.card_data, root.extra_data); @@ -84,6 +85,7 @@ GraphicsBox { root.selected_ids.splice(root.selected_ids.indexOf(cid), 1); } root.selected_ids = root.selected_ids; + refreshPrompt(); } } } @@ -123,7 +125,7 @@ GraphicsBox { let ret; for (let i = 0; i < cardModel.count; i++) { let item = cardModel.get(i); - if (item.areaName == name) { + if (item.areaName === name) { ret = item; break; } @@ -149,4 +151,8 @@ GraphicsBox { area.append(cards); } } + + function refreshPrompt() { + root.title.text = Util.processPrompt(lcall("PoxiPrompt", poxi_type, card_data, extra_data)) + } } diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 1bf67992..a92531eb 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -744,6 +744,7 @@ end function PoxiPrompt(poxi_type, data, extra_data) local poxi = Fk.poxi_methods[poxi_type] + extra_data = extra_data and json.decode(extra_data) if not poxi or not poxi.prompt then return "" end if type(poxi.prompt) == "string" then return Fk:translate(poxi.prompt) end return Fk:translate(poxi.prompt(data, extra_data)) @@ -751,12 +752,14 @@ end function PoxiFilter(poxi_type, to_select, selected, data, extra_data) local poxi = Fk.poxi_methods[poxi_type] + extra_data = extra_data and json.decode(extra_data) if not poxi then return "false" end return json.encode(poxi.card_filter(to_select, selected, data, extra_data)) end function PoxiFeasible(poxi_type, selected, data, extra_data) local poxi = Fk.poxi_methods[poxi_type] + extra_data = extra_data and json.decode(extra_data) if not poxi then return "false" end return json.encode(poxi.feasible(selected, data, extra_data)) end diff --git a/lua/client/i18n/en_US.lua b/lua/client/i18n/en_US.lua index ee356e21..d83fd798 100644 --- a/lua/client/i18n/en_US.lua +++ b/lua/client/i18n/en_US.lua @@ -250,6 +250,12 @@ Fk:loadTranslationTable({ -- ["Trusting ..."] = "托管中 ...", -- ["Observing ..."] = "旁观中 ...", + ["#NoCardDraw"] = "Card Pile is empty", + ["#NoGeneralDraw"] = "General Pile is empty", + ["#NoEventDraw"] = "All game events terminated", + ["#NoEnoughGeneralDraw"] = "No enough generals! (%arg/%arg2)", + ["#TimeOutDraw"] = "It's over 9999 Round!", + ["$GameOver"] = "Game Over", ["$Winner"] = "Winner is %1", ["$NoWinner"] = "Draw!", diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index b9400988..8ac6c0dc 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -307,6 +307,12 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["resting..."] = "休整中", ["rest round num"] = "轮次", + ["#NoCardDraw"] = "牌堆被摸空了", + ["#NoGeneralDraw"] = "武将牌堆被摸空了", + ["#NoEventDraw"] = "没有可执行的事件", + ["#NoEnoughGeneralDraw"] = "武将数不足!(%arg/%arg2)", + ["#TimeOutDraw"] = "轮数已经突破极限!", + ["$GameOver"] = "游戏结束", ["$Winner"] = "%1 获胜", ["$NoWinner"] = "平局!", diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 57c41da1..1585aa3d 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -27,6 +27,8 @@ ---@field public currentResponseReason string @ 要求用牌的原因(如濒死,被特定牌指定,使用特定技能···) ---@field public filtered_cards table @ 被锁视技影响的卡牌 ---@field public printed_cards table @ 被某些房间现场打印的卡牌,id都是负数且从-2开始 +---@field private kingdoms string[] @ 总势力 +---@field private kingdom_map table @ 势力映射表 ---@field private _custom_events any[] @ 自定义事件列表 ---@field public poxi_methods table @ “魄袭”框操作方法表 ---@field public qml_marks table @ 自定义Qml标记的表 @@ -67,6 +69,7 @@ function Engine:initialize() self.game_modes = {} self.game_mode_disabled = {} self.kingdoms = {} + self.kingdom_map = {} self._custom_events = {} self.poxi_methods = {} self.qml_marks = {} @@ -271,6 +274,30 @@ function Engine:addGenerals(generals) end end +--- 为一个势力添加势力映射 +--- +--- 这意味着原势力登场时必须改变为添加的几个势力之一(须存在) +---@param kingdom string @ 原势力 +---@param kingdoms string[] @ 需要映射到的势力 +function Engine:appendKingdomMap(kingdom, kingdoms) + local ret = self.kingdom_map[kingdom] or {} + table.insertTableIfNeed(ret, kingdoms) + self.kingdom_map[kingdom] = ret +end + +---获得一个势力所映射到的势力,若没有,返回空集 +---@param kingdom string @ 原势力 +---@return string[] @ 可用势力列表,可能是空的 +function Engine:getKingdomMap(kingdom) + local ret = {} + for _, k in ipairs(self.kingdom_map[kingdom] or {}) do + if table.contains(self.kingdoms, k) then + table.insertIfNeed(ret, k) + end + end + return ret +end + --- 判断一个武将是否在本房间可用。 ---@param g string @ 武将名 function Engine:canUseGeneral(g) diff --git a/lua/core/player.lua b/lua/core/player.lua index ceb536b7..db78d0e2 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -874,7 +874,7 @@ end ---@param card Card @ 特定牌 ---@param extra_data? UseExtraData @ 额外数据 function Player:canUse(card, extra_data) - return card.skill:canUse(self, card, extra_data) + return not self:prohibitUse(card) and card.skill:canUse(self, card, extra_data) end --- 确认玩家是否可以对特定玩家使用特定牌。 diff --git a/lua/core/skill_type/active.lua b/lua/core/skill_type/active.lua index 7609879c..3f13b76f 100644 --- a/lua/core/skill_type/active.lua +++ b/lua/core/skill_type/active.lua @@ -57,7 +57,7 @@ end ---@param selected? integer[] @ ids of selected targets ---@param user? integer @ id of the userdata ---@param card? Card @ helper ----@param distance_limited boolean @ is limited by distance +---@param distance_limited? boolean @ is limited by distance function ActiveSkill:modTargetFilter(to_select, selected, user, card, distance_limited) return false end diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 6ceb1866..b5ce40cc 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -235,6 +235,7 @@ end ---@field public enabled_at_play? fun(self: ViewAsSkill, player: Player): boolean? ---@field public enabled_at_response? fun(self: ViewAsSkill, player: Player, response: boolean): boolean? ---@field public before_use? fun(self: ViewAsSkill, player: ServerPlayer, use: CardUseStruct): string? +---@field public after_use? fun(self: ViewAsSkill, player: ServerPlayer, use: CardUseStruct): string? ---@field public prompt? string|fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): string ---@param spec ViewAsSkillSpec diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index 47370508..cec12d99 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -148,6 +148,10 @@ GameEvent.functions[GameEvent.Round] = function(self) room:doBroadcastNotify("UpdateRoundNum", roundCount) -- 强行平局 防止can_trigger报错导致瞬间几十万轮卡炸服务器 if roundCount >= 9999 then + room:sendLog{ + type = "#TimeOutDraw", + toast = true, + } room:gameOver("") end @@ -354,6 +358,7 @@ GameEvent.functions[GameEvent.Phase] = function(self) end) end ) - player:getMaxCards() + room:broadcastProperty(player, "MaxCards") if discardNum > 0 then room:askForDiscard(player, discardNum, discardNum, false, "game_rule", false) end diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index b5401faf..1bc91287 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -84,17 +84,32 @@ function GameEvent:prependExitFunc(f) table.insert(self.extra_exit_funcs, 1, f) end -function GameEvent:findParent(eventType, includeSelf) +-- 找第一个与当前事件有继承关系的特定事件 +---@param eventType integer @ 事件类型 +---@param includeSelf bool @ 是否包括本事件 +---@param depth? integer @ 搜索深度 +---@return GameEvent? +function GameEvent:findParent(eventType, includeSelf, depth) if includeSelf and self.event == eventType then return self end + if depth == 0 then return nil end local e = self.parent + local l = 1 while e do if e.event == eventType then return e end + if depth and l >= depth then break end e = e.parent + l = l + 1 end return nil end -- 找n个id介于from和to之间的事件。 +---@param events GameEvent[] @ 事件数组 +---@param from integer @ 起始id +---@param to integer @ 终止id +---@param n integer @ 最多找多少个 +---@param func fun(e: GameEvent): boolean? @ 过滤用的函数 +---@return GameEvent[] @ 找到的符合条件的所有事件,最多n个但不保证有n个 local function bin_search(events, from, to, n, func) local left = 1 local right = #events diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 71c556d7..d2afa29e 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -447,6 +447,10 @@ function GameLogic:start() end if not e then -- 没有事件,按理说不应该,平局处理 + self.room:sendLog{ + type = "#NoEventDraw", + toast = true, + } self.room:gameOver("") end @@ -744,6 +748,21 @@ function GameLogic:getActualDamageEvents(n, func, scope, end_id) return ret end +--检测最近的伤害事件是否由执行牌的效果触发,即通常描述的使用牌对目标角色造成伤害 +---@param is_exact? bool @ 是否进一步判定使用者和来源是否一致(默认为true) +---@return bool +function GameLogic:damageByCardEffect(is_exact) + is_exact = (is_exact == nil) and true or is_exact + local d_event = self:getCurrentEvent():findParent(GameEvent.Damage, true) + if d_event == nil then return false end + local damage = d_event.data[1] + if damage.chain or damage.card == nil then return false end + local c_event = d_event:findParent(GameEvent.CardEffect, false, 2) + if c_event == nil then return false end + return damage.card == c_event.data[1].card and + (not is_exact or d_event.data[1].from.id == c_event.data[1].from) +end + function GameLogic:dumpEventStack(detailed) local top = self:getCurrentEvent() local i = self.game_event_stack.p diff --git a/lua/server/room.lua b/lua/server/room.lua index 5f6b9688..508f5c7f 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -56,6 +56,7 @@ dofile "lua/server/ai/init.lua" gameevent.lua (游戏事件的执行逻辑,以及各种事件的执行方法) game_rule.lua (基础游戏规则,包括执行阶段、决胜负等) aux_skills.lua (某些交互方法是套壳askForUseActiveSkill,就是在这定义的) + aux_poxi.lua (有了Poxi之后,一些交互方法改成了以各种PoxiMethod为基础的交互) ]]---------------------------------------------------------------------- ------------------------------------------------------------------------ @@ -431,6 +432,10 @@ function Room:getNCards(num, from) if #self.draw_pile < num then self:shuffleDrawPile() if #self.draw_pile < num then + self:sendLog{ + type = "#NoCardDraw", + toast = true, + } self:gameOver("") end end @@ -605,14 +610,16 @@ function Room:changeHero(player, new_general, full, isDeputy, sendLog, maxHpChan kingdomChange = (kingdomChange == nil) and true or kingdomChange local kingdom = (isDeputy or not kingdomChange) and player.kingdom or new.kingdom - if not isDeputy and kingdomChange and (new.kingdom == "god" or new.subkingdom) then + if not isDeputy and kingdomChange then local allKingdoms = {} - if new.kingdom == "god" then - allKingdoms = table.filter({"wei", "shu", "wu", "qun", "jin"}, function(k) return table.contains(Fk.kingdoms, k) end) - elseif new.subkingdom then + if new.subkingdom then allKingdoms = { new.kingdom, new.subkingdom } + else + allKingdoms = Fk:getKingdomMap(new.kingdom) + end + if #allKingdoms > 0 then + kingdom = self:askForChoice(player, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom") end - kingdom = self:askForChoice(player, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom") end execGameEvent(GameEvent.ChangeProperty, @@ -1372,10 +1379,10 @@ function Room:askForChooseCardsAndPlayers(player, minCardNum, maxCardNum, target local data = { targets = targets, - max_target_num = maxTargetNum, - min_target_num = minTargetNum, - max_card_num = maxCardNum, - min_card_num = minCardNum, + max_t_num = maxTargetNum, + min_t_num = minTargetNum, + max_c_num = maxCardNum, + min_c_num = minCardNum, pattern = pattern, skillName = skillName, -- include_equip = includeEquip, -- FIXME: 预定一个破坏性更新 @@ -1501,6 +1508,10 @@ function Room:getNGenerals(n, position) end if #generals < 1 then + self:sendLog{ + type = "#NoGeneralDraw", + toast = true, + } self:gameOver("") end return generals @@ -1594,24 +1605,25 @@ end function Room:askForChooseKingdom(players) players = players or self.alive_players local specialKingdomPlayers = table.filter(players, function(p) - return p.kingdom == "god" or Fk.generals[p.general].subkingdom + return Fk.generals[p.general].subkingdom or #Fk:getKingdomMap(p.kingdom) > 0 end) if #specialKingdomPlayers > 0 then local choiceMap = {} for _, p in ipairs(specialKingdomPlayers) do local allKingdoms = {} - if p.kingdom == "god" then - allKingdoms = table.filter({"wei", "shu", "wu", "qun", "jin"}, function(k) return table.contains(Fk.kingdoms, k) end) - else - local curGeneral = Fk.generals[p.general] + local curGeneral = Fk.generals[p.general] + if curGeneral.subkingdom then allKingdoms = { curGeneral.kingdom, curGeneral.subkingdom } + else + allKingdoms = Fk:getKingdomMap(p.kingdom) end + if #allKingdoms > 0 then + choiceMap[p.id] = allKingdoms - choiceMap[p.id] = allKingdoms - - local data = json.encode({ allKingdoms, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom" }) - p.request_data = data + local data = json.encode({ allKingdoms, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom" }) + p.request_data = data + end end self:notifyMoveFocus(players, "AskForKingdom") @@ -1674,57 +1686,6 @@ function Room:askForCardChosen(chooser, target, flag, reason, prompt) return result end ---- 完全类似askForCardChosen,但是可以选择多张牌。 ---- 相应的,返回的是id的数组而不是单个id。 ----@param chooser ServerPlayer @ 要被询问的人 ----@param target ServerPlayer @ 被选牌的人 ----@param min integer @ 最小选牌数 ----@param max integer @ 最大选牌数 ----@param flag any @ 用"hej"三个字母的组合表示能选择哪些区域, h 手牌区, e - 装备区, j - 判定区 ----@param reason string @ 原因,一般是技能名 ----@param prompt? string @ 提示信息 ----@return integer[] @ 选择的id -function Room:askForCardsChosen(chooser, target, min, max, flag, reason, prompt) - if min == 1 and max == 1 then - return { self:askForCardChosen(chooser, target, flag, reason) } - end - - local command = "AskForCardsChosen" - prompt = prompt or "" - self:notifyMoveFocus(chooser, command) - local data = {target.id, min, max, flag, reason, prompt} - local result = self:doRequest(chooser, command, json.encode(data)) - - local ret - if result ~= "" then - ret = json.decode(result) - else - local areas = {} - local handcards - if type(flag) == "string" then - if string.find(flag, "h") then table.insert(areas, Player.Hand) end - if string.find(flag, "e") then table.insert(areas, Player.Equip) end - if string.find(flag, "j") then table.insert(areas, Player.Judge) end - handcards = target:getCardIds(areas) - else - handcards = {} - for _, t in ipairs(flag.card_data) do - table.insertTable(handcards, t[2]) - end - end - if #handcards == 0 then return {} end - ret = table.random(handcards, math.min(min, #handcards)) - end - - local new_ret = table.filter(ret, function(id) return id ~= -1 end) - local hand_num = #ret - #new_ret - if hand_num > 0 then - table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), hand_num)) - end - - return new_ret -end - --- 谋askForCardsChosen,需使用Fk:addPoxiMethod定义好方法 --- --- 选卡规则和返回值啥的全部自己想办法解决,data填入所有卡的列表(类似ui.card_data) @@ -1756,6 +1717,103 @@ function Room:askForPoxi(player, poxi_type, data, extra_data, cancelable) end end +--- 完全类似askForCardChosen,但是可以选择多张牌。 +--- 相应的,返回的是id的数组而不是单个id。 +---@param chooser ServerPlayer @ 要被询问的人 +---@param target ServerPlayer @ 被选牌的人 +---@param min integer @ 最小选牌数 +---@param max integer @ 最大选牌数 +---@param flag any @ 用"hej"三个字母的组合表示能选择哪些区域, h 手牌区, e - 装备区, j - 判定区 +---可以通过flag.card_data = {{牌堆1名, 牌堆1ID表},...}来定制能选择的牌 +---@param reason string @ 原因,一般是技能名 +---@param prompt? string @ 提示信息 +---@return integer[] @ 选择的id +function Room:askForCardsChosen(chooser, target, min, max, flag, reason, prompt) + if min == 1 and max == 1 then + return { self:askForCardChosen(chooser, target, flag, reason) } + end + + -- local command = "AskForCardsChosen" + -- prompt = prompt or "" + -- self:notifyMoveFocus(chooser, command) + -- local data = {target.id, min, max, flag, reason, prompt} + -- local result = self:doRequest(chooser, command, json.encode(data)) + + -- local ret + -- if result ~= "" then + -- ret = json.decode(result) + -- else + -- local areas = {} + -- local handcards + -- if type(flag) == "string" then + -- if string.find(flag, "h") then table.insert(areas, Player.Hand) end + -- if string.find(flag, "e") then table.insert(areas, Player.Equip) end + -- if string.find(flag, "j") then table.insert(areas, Player.Judge) end + -- handcards = target:getCardIds(areas) + -- else + -- handcards = {} + -- for _, t in ipairs(flag.card_data) do + -- table.insertTable(handcards, t[2]) + -- end + -- end + -- if #handcards == 0 then return {} end + -- ret = table.random(handcards, math.min(min, #handcards)) + -- end + + -- local new_ret = table.filter(ret, function(id) return id ~= -1 end) + -- local hand_num = #ret - #new_ret + -- if hand_num > 0 then + -- table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), hand_num)) + -- end + + -- return new_ret + local areas = {} + local cards + local data = { + to = target.id, + min = min, + max = max, + skillName = reason, + prompt = prompt, + } + if type(flag) == "string" then + if string.find(flag, "h") then table.insert(areas, Player.Hand) end + if string.find(flag, "e") then table.insert(areas, Player.Equip) end + if string.find(flag, "j") then table.insert(areas, Player.Judge) end + cards = target:getCardIds(areas) + else + cards = {} + for _, t in ipairs(flag.card_data) do + table.insertTable(cards, t[2]) + end + end + if #cards <= min then return table.random(cards, math.min(min, #cards)) end + local cards_data = {} + if type(flag) == "string" then + if string.find(flag, "h") and #target:getCardIds(Player.Hand) > 0 then + local handcards = {} + for _, _ in ipairs(target:getCardIds(Player.Hand)) do + table.insert(handcards, -1) + end + table.insert(cards_data, {"$Hand", handcards}) + end + if string.find(flag, "e") and #target:getCardIds(Player.Equip) > 0 then table.insert(cards_data, {"$Equip", target:getCardIds(Player.Equip)}) end + if string.find(flag, "j") and #target:getCardIds(Player.Judge) > 0 then table.insert(cards_data, {"$Judge", target:getCardIds(Player.Judge)}) end + local ret = self:askForPoxi(chooser, "AskForCardsChosen", cards_data, data, false) + local new_ret = table.filter(ret, function(id) return id ~= -1 end) + local hidden_num = #ret - #new_ret + if hidden_num > 0 then + table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), hidden_num)) + end + return new_ret + else + for _, t in ipairs(flag.card_data) do + table.insert(cards_data, t) + end + end + return self:askForPoxi(chooser, "AskForCardsChosen", cards_data, data, false) +end + --- 询问一名玩家从众多选项中选择一个。 ---@param player ServerPlayer @ 要询问的玩家 ---@param choices string[] @ 可选选项列表 @@ -3027,7 +3085,7 @@ end function Room:obtainCard(player, cid, unhide, reason, proposer) if type(cid) ~= "number" then assert(cid and type(cid) == "table") - if cid:isInstanceOf(Card) then + if cid[1] == nil then cid = cid:isVirtual() and cid.subcards or {cid.id} end else diff --git a/packages/maneuvering/init.lua b/packages/maneuvering/init.lua index 0a95e07c..fa84147d 100644 --- a/packages/maneuvering/init.lua +++ b/packages/maneuvering/init.lua @@ -114,7 +114,7 @@ local analepticSkill = fk.CreateActiveSkill{ end) end, can_use = function(self, player, card, extra_data) - return ((extra_data and (extra_data.bypass_times or extra_data.analepticRecover)) or + return not player:isProhibited(player, card) and ((extra_data and (extra_data.bypass_times or extra_data.analepticRecover)) or self:withinTimesLimit(player, Player.HistoryTurn, card, "analeptic", player)) end, on_use = function(_, _, use) @@ -333,9 +333,17 @@ local gudingSkill = fk.CreateTriggerSkill{ frequency = Skill.Compulsory, events = {fk.DamageCaused}, can_trigger = function(self, _, target, player, data) - return target == player and player:hasSkill(self) and - data.to:isKongcheng() and data.card and data.card.trueName == "slash" and - not data.chain + local logic = player.room.logic + if target == player and player:hasSkill(self) and + data.to:isKongcheng() and data.card and data.card.trueName == "slash" and not data.chain then + local event = logic:getCurrentEvent() + if event == nil then return false end + event = event.parent + if event == nil or event.event ~= GameEvent.SkillEffect then return false end + event = event.parent + if event == nil or event.event ~= GameEvent.CardEffect then return false end + return data.card == event.data[1].card and data.from.id == event.data[1].from + end end, on_use = function(_, _, _, _, data) data.damage = data.damage + 1 diff --git a/packages/standard/aux_poxi.lua b/packages/standard/aux_poxi.lua new file mode 100644 index 00000000..f1918022 --- /dev/null +++ b/packages/standard/aux_poxi.lua @@ -0,0 +1,22 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +Fk:addPoxiMethod{ + name = "AskForCardsChosen", + card_filter = function(to_select, selected, data, extra_data) + return #selected < extra_data.max + end, + feasible = function(selected, data, extra_data) + return #selected >= extra_data.min and #selected <= extra_data.max + end, + prompt = function(data, extra_data) + if extra_data.prompt then + return extra_data.prompt + else + local ret = Fk:translate("#AskForChooseCards") + ret = ret:gsub("%%1", Fk:translate(extra_data.skillName or "AskForCardsChosen")) + ret = ret:gsub("%%2", Fk:translate(extra_data.min)) + ret = ret:gsub("%%3", Fk:translate(extra_data.max)) + return ret .. ":" ..extra_data.to + end + end, +} diff --git a/packages/standard/aux_skills.lua b/packages/standard/aux_skills.lua index dcbca2a4..408c3521 100644 --- a/packages/standard/aux_skills.lua +++ b/packages/standard/aux_skills.lua @@ -95,7 +95,7 @@ local choosePlayersSkill = fk.CreateActiveSkill{ local exChooseSkill = fk.CreateActiveSkill{ name = "ex__choose_skill", card_filter = function(self, to_select, selected) - if #selected >= self.max_card_num then return false end + if #selected >= self.max_c_num then return false end if Fk:currentRoom():getCardArea(to_select) == Card.PlayerSpecial then if not string.find(self.pattern or "", self.expand_pile or "") then return false end @@ -114,11 +114,15 @@ local exChooseSkill = fk.CreateActiveSkill{ return checkpoint end, target_filter = function(self, to_select, selected, cards) - if self.pattern ~= "" and #cards < self.min_card_num then return end - if #selected < self.max_target_num then + if #cards < self.min_c_num then return end + if #selected < self.max_t_num then return table.contains(self.targets, to_select) end end, + min_target_num = function(self) return self.min_t_num end, + max_target_num = function(self) return self.max_t_num end, + min_card_num = function(self) return self.min_c_num end, + max_card_num = function(self) return self.max_c_num end, } local maxCardsSkill = fk.CreateMaxCardsSkill{ diff --git a/packages/standard/init.lua b/packages/standard/init.lua index 2b5fbaba..90d4c728 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -4,6 +4,7 @@ local extension = Package:new("standard") extension.metadata = require "packages.standard.metadata" dofile "packages/standard/game_rule.lua" dofile "packages/standard/aux_skills.lua" +dofile "packages/standard/aux_poxi.lua" local jianxiong = fk.CreateTriggerSkill{ name = "jianxiong", @@ -699,29 +700,26 @@ local keji = fk.CreateTriggerSkill{ can_trigger = function(self, event, target, player, data) if target == player and player:hasSkill(self) and data.to == Player.Discard then local room = player.room - local logic = room.logic - local e = logic:getCurrentEvent():findParent(GameEvent.Turn, true) - if e == nil then return false end - local end_id = e.id - local events = logic.event_recorder[GameEvent.UseCard] or Util.DummyTable - for i = #events, 1, -1 do - e = events[i] - if e.id <= end_id then break end - local use = e.data[1] - if use.from == player.id and use.card.trueName == "slash" then - return false + local play_ids = {} + player.room.logic:getEventsOfScope(GameEvent.Phase, 1, function (e) + if e.data[2] == Player.Play and e.end_id then + table.insert(play_ids, {e.id, e.end_id}) end - end - events = logic.event_recorder[GameEvent.RespondCard] or Util.DummyTable - for i = #events, 1, -1 do - e = events[i] - if e.id <= end_id then break end - local resp = e.data[1] - if resp.from == player.id and resp.card.trueName == "slash" then - return false + return false + end, Player.HistoryTurn) + if #play_ids == 0 then return true end + local function PlayCheck (e) + local in_play = false + for _, ids in ipairs(play_ids) do + if e.id > ids[1] and e.id < ids[2] then + in_play = true + break + end end + return in_play and e.data[1].from == player.id and e.data[1].card.trueName == "slash" end - return true + return #player.room.logic:getEventsOfScope(GameEvent.UseCard, 1, PlayCheck, Player.HistoryTurn) == 0 + and #player.room.logic:getEventsOfScope(GameEvent.RespondCard, 1, PlayCheck, Player.HistoryTurn) == 0 end end, on_use = function(self, event, target, player, data) @@ -989,9 +987,10 @@ local qingnang = fk.CreateActiveSkill{ on_use = function(self, room, effect) local from = room:getPlayerById(effect.from) room:throwCard(effect.cards, self.name, from, from) - if from:isAlive() and from:isWounded() then + local to = room:getPlayerById(effect.tos[1]) + if to:isAlive() and to:isWounded() then room:recover({ - who = room:getPlayerById(effect.tos[1]), + who = to, num = 1, recoverBy = from, skillName = self.name @@ -1117,12 +1116,20 @@ local role_getlogic = function() if lord ~= nil then room.current = lord + local a1 = #room.general_pile + local a2 = #room.players * generalNum + lord_num + if a1 < a2 then + room:sendLog{ + type = "#NoEnoughGeneralDraw", + arg = a1, + arg2 = a2, + toast = true, + } + room:gameOver("") + end local generals = table.connect(room:findGenerals(function(g) return table.find(Fk.generals[g].skills, function(s) return s.lordSkill end) end, lord_num), room:getNGenerals(generalNum)) - if #room.general_pile < (#room.players - 1) * generalNum then - room:gameOver("") - end lord_generals = room:askForGeneral(lord, generals, n) local lord_general, deputy if type(lord_generals) == "table" then diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index 9374e9a6..b43b9580 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -1086,7 +1086,7 @@ local halberdSkill = fk.CreateTargetModSkill{ if player:hasSkill(self) and skill.trueName == "slash_skill" then local cards = card:isVirtual() and card.subcards or {card.id} local handcards = player:getCardIds(Player.Hand) - if #cards == #handcards and table.every(cards, function(id) return table.contains(handcards, id) end) then + if #handcards > 0 and #cards == #handcards and table.every(cards, function(id) return table.contains(handcards, id) end) then return 2 end end diff --git a/packages/test/init.lua b/packages/test/init.lua index 802b1535..12b8385b 100644 --- a/packages/test/init.lua +++ b/packages/test/init.lua @@ -83,6 +83,7 @@ local control = fk.CreateActiveSkill{ -- p(room:askForYiji(from, from:getCardIds(Player.Hand), table.map(effect.tos, Util.Id2PlayerMapper), self.name, 2, 10, nil, false, nil, false, 3, true)) for _, pid in ipairs(effect.tos) do local to = room:getPlayerById(pid) + -- p(room:askForCardsChosen(from, to, 2, 3, "hej", self.name)) -- p(room:askForPoxi(from, "test", { -- { "你自己", from:getCardIds "h" }, -- { "对方", to:getCardIds "h" },