diff --git a/Fk/Pages/Lobby.qml b/Fk/Pages/Lobby.qml index 7d4fd364..317acfdd 100644 --- a/Fk/Pages/Lobby.qml +++ b/Fk/Pages/Lobby.qml @@ -139,7 +139,7 @@ Item { Text { width: parent.width horizontalAlignment: Text.AlignHCenter - text: Backend.translate("Room List") + text: Backend.translate("Room List").arg(roomModel.count) } ListView { id: roomList diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index a8622381..0194157f 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -59,7 +59,7 @@ Item { id: bgm source: config.bgmFile - // loops: MediaPlayer.Infinite + loops: MediaPlayer.Infinite onPlaybackStateChanged: { if (playbackState == MediaPlayer.StoppedState && roomScene.isStarted) play(); diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index 35bb4c5c..03c32b55 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -1070,6 +1070,31 @@ callbacks["AskForCardsChosen"] = (jsonData) => { }); } +callbacks["AskForPoxi"] = (jsonData) => { + const { type, data } = JSON.parse(jsonData); + + roomScene.state = "replying"; + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PoxiBox.qml"); + const box = roomScene.popupBox.item; + box.poxi_type = type; + box.card_data = data; + for (let d of data) { + const arr = []; + const ids = d[1]; + + ids.forEach(id => { + const card_data = JSON.parse(Backend.callLuaFunction("GetCardData", [id])); + arr.push(card_data); + }); + box.addCustomCards(d[0], arr); + } + + roomScene.popupBox.moveToCenter(); + box.cardsSelected.connect((ids) => { + replyToServer(JSON.stringify(ids)); + }); +} + callbacks["AskForMoveCardInBoard"] = (jsonData) => { const data = JSON.parse(jsonData); const { cards, cardsPosition, generalNames, playerIds } = data; diff --git a/Fk/PhotoElement/MarkArea.qml b/Fk/PhotoElement/MarkArea.qml index ebf98e67..adda007c 100644 --- a/Fk/PhotoElement/MarkArea.qml +++ b/Fk/PhotoElement/MarkArea.qml @@ -62,7 +62,12 @@ Item { } if (mark_name.startsWith('@$')) { - params.cardNames = mark_extra.split(','); + let data = mark_extra.split(','); + if (!Object.is(parseInt(data[0]), NaN)) { + params.ids = data.map(s => parseInt(s)); + } else { + params.cardNames = data; + } } else { let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name])); data = data.filter((e) => e !== -1); diff --git a/Fk/RoomElement/CardItem.qml b/Fk/RoomElement/CardItem.qml index c41d789a..d74ea8d3 100644 --- a/Fk/RoomElement/CardItem.qml +++ b/Fk/RoomElement/CardItem.qml @@ -31,11 +31,13 @@ Item { property string color: "" // only use when suit is empty property string footnote: "" // footnote, e.g. "A use card to B" property bool footnoteVisible: false + property string prohibitReason: "" property bool known: true // if false it only show a card back property bool enabled: true // if false the card will be grey property alias card: cardItem property alias glow: glowItem property var mark: ({}) + property alias chosenInBox: chosen.visible function getColor() { if (suit != "") @@ -88,6 +90,7 @@ Item { visible: false } + Image { id: cardItem source: known ? SkinBank.getCardPicture(cid || name) @@ -217,6 +220,15 @@ Item { } } + Image { + id: chosen + visible: false + source: SkinBank.CARD_DIR + "chosen" + anchors.horizontalCenter: parent.horizontalCenter + y: 90 + scale: 1.25 + } + Rectangle { visible: !root.selectable anchors.fill: parent @@ -224,6 +236,24 @@ Item { opacity: 0.7 } + Text { + id: prohibitText + visible: !root.selectable + anchors.centerIn: parent + font.family: fontLibian.name + font.pixelSize: 18 + opacity: 0.9 + horizontalAlignment: Text.AlignHCenter + lineHeight: 18 + lineHeightMode: Text.FixedHeight + color: "snow" + width: 20 + wrapMode: Text.WrapAnywhere + style: Text.Outline + styleColor: "red" + text: prohibitReason + } + TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton gesturePolicy: TapHandler.WithinBounds diff --git a/Fk/RoomElement/Dashboard.qml b/Fk/RoomElement/Dashboard.qml index 2158a9eb..09faeb03 100644 --- a/Fk/RoomElement/Dashboard.qml +++ b/Fk/RoomElement/Dashboard.qml @@ -166,17 +166,35 @@ RowLayout { const ids = []; let cards = handcardAreaItem.cards; for (let i = 0; i < cards.length; i++) { + cards[i].prohibitReason = ""; if (cardValid(cards[i].cid, cname)) { ids.push(cards[i].cid); + } else { + const prohibitReason = Backend.callLuaFunction( + "GetCardProhibitReason", + [cards[i].cid, roomScene.respond_play ? "response" : "use", cname] + ); + if (prohibitReason) { + cards[i].prohibitReason = prohibitReason; + } } } cards = self.equipArea.getAllCards(); cards.forEach(c => { + c.prohibitReason = ""; if (cardValid(c.cid, cname)) { ids.push(c.cid); if (!expanded_piles["_equip"]) { expandPile("_equip"); } + } else { + const prohibitReason = Backend.callLuaFunction( + "GetCardProhibitReason", + [c.cid, roomScene.respond_play ? "response" : "use", cname] + ); + if (prohibitReason) { + c.prohibitReason = prohibitReason; + } } }); @@ -202,6 +220,7 @@ RowLayout { const ids = [], cards = handcardAreaItem.cards; for (let i = 0; i < cards.length; i++) { + cards[i].prohibitReason = ""; if (JSON.parse(Backend.callLuaFunction("CanUseCard", [cards[i].cid, Self.id]))) { ids.push(cards[i].cid); } else { @@ -214,6 +233,14 @@ RowLayout { break; } } + + // still cannot use? show message on card + if (!ids.includes(cards[i].cid)) { + const prohibitReason = Backend.callLuaFunction("GetCardProhibitReason", [cards[i].cid, "play"]); + if (prohibitReason) { + cards[i].prohibitReason = prohibitReason; + } + } } } handcardAreaItem.enableCards(ids) @@ -372,6 +399,11 @@ RowLayout { item.enabled = item.pressed; } + const cards = handcardAreaItem.cards; + for (let i = 0; i < cards.length; i++) { + cards[i].prohibitReason = ""; + } + updatePending(); } diff --git a/Fk/RoomElement/HandcardArea.qml b/Fk/RoomElement/HandcardArea.qml index 3b282ff1..2d96bea1 100644 --- a/Fk/RoomElement/HandcardArea.qml +++ b/Fk/RoomElement/HandcardArea.qml @@ -48,6 +48,7 @@ Item { card.selectable = false; card.showDetail = false; card.selectedChanged.disconnect(adjustCards); + card.prohibitReason = ""; } return result; } diff --git a/Fk/RoomElement/PlayerCardBox.qml b/Fk/RoomElement/PlayerCardBox.qml index d6554702..e9d4a5bf 100644 --- a/Fk/RoomElement/PlayerCardBox.qml +++ b/Fk/RoomElement/PlayerCardBox.qml @@ -78,10 +78,10 @@ GraphicsBox { } onSelectedChanged: { if (selected) { - virt_name = "$Selected"; + chosenInBox = true; root.selected_ids.push(cid); } else { - virt_name = ""; + chosenInBox = false; root.selected_ids.splice(root.selected_ids.indexOf(cid), 1); } root.selected_ids = root.selected_ids; @@ -122,38 +122,6 @@ GraphicsBox { return ret; } - function addHandcards(cards) { - let handcards = findAreaModel('$Hand').areaCards; - if (cards instanceof Array) { - for (let i = 0; i < cards.length; i++) - handcards.append(cards[i]); - } else { - handcards.append(cards); - } - } - - function addEquips(cards) - { - let equips = findAreaModel('$Equip').areaCards; - if (cards instanceof Array) { - for (let i = 0; i < cards.length; i++) - equips.append(cards[i]); - } else { - equips.append(cards); - } - } - - function addDelayedTricks(cards) - { - let delayedTricks = findAreaModel('$Judge').areaCards; - if (cards instanceof Array) { - for (let i = 0; i < cards.length; i++) - delayedTricks.append(cards[i]); - } else { - delayedTricks.append(cards); - } - } - function addCustomCards(name, cards) { let area = findAreaModel(name).areaCards; if (cards instanceof Array) { diff --git a/Fk/RoomElement/PoxiBox.qml b/Fk/RoomElement/PoxiBox.qml new file mode 100644 index 00000000..1f30c8c1 --- /dev/null +++ b/Fk/RoomElement/PoxiBox.qml @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Layouts +import Fk.Pages + +GraphicsBox { + id: root + + title.text: Backend.callLuaFunction("PoxiPrompt", [poxi_type, card_data]) + + // TODO: Adjust the UI design in case there are more than 7 cards + width: 70 + 700 + height: 64 + Math.min(cardView.contentHeight, 400) + 20 + + signal cardSelected(int cid) + signal cardsSelected(var ids) + property var selected_ids: [] + property string poxi_type + property var card_data + + ListModel { + id: cardModel + } + + ListView { + id: cardView + anchors.fill: parent + anchors.topMargin: 40 + anchors.leftMargin: 20 + anchors.rightMargin: 20 + anchors.bottomMargin: 20 + spacing: 20 + model: cardModel + clip: true + + delegate: RowLayout { + spacing: 15 + visible: areaCards.count > 0 + + Rectangle { + border.color: "#A6967A" + radius: 5 + color: "transparent" + width: 18 + height: 130 + Layout.alignment: Qt.AlignTop + + Text { + color: "#E4D5A0" + text: Backend.translate(areaName) + anchors.fill: parent + wrapMode: Text.WrapAnywhere + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 15 + } + } + + GridLayout { + columns: 7 + Repeater { + model: areaCards + + CardItem { + cid: model.cid + name: model.name || "" + suit: model.suit || "" + number: model.number || 0 + autoBack: false + known: model.cid !== -1 + selectable: { + return root.selected_ids.includes(model.cid) || JSON.parse(Backend.callLuaFunction( + "PoxiFilter", + [root.poxi_type, model.cid, root.selected_ids, root.card_data] + )); + } + onSelectedChanged: { + if (selected) { + chosenInBox = true; + root.selected_ids.push(cid); + } else { + chosenInBox = false; + root.selected_ids.splice(root.selected_ids.indexOf(cid), 1); + } + root.selected_ids = root.selected_ids; + } + } + } + } + } + } + + MetroButton { + anchors.bottom: parent.bottom + text: Backend.translate("OK") + enabled: { + return JSON.parse(Backend.callLuaFunction( + "PoxiFeasible", + [root.poxi_type, root.selected_ids, root.card_data] + )); + } + onClicked: root.cardsSelected(root.selected_ids) + } + + onCardSelected: finished(); + + function findAreaModel(name) { + let ret; + for (let i = 0; i < cardModel.count; i++) { + let item = cardModel.get(i); + if (item.areaName == name) { + ret = item; + break; + } + } + if (!ret) { + ret = { + areaName: name, + areaCards: [], + } + cardModel.append(ret); + ret = findAreaModel(name); + } + return ret; + } + + + function addCustomCards(name, cards) { + let area = findAreaModel(name).areaCards; + if (cards instanceof Array) { + for (let i = 0; i < cards.length; i++) + area.append(cards[i]); + } else { + area.append(cards); + } + } +} diff --git a/Fk/RoomElement/SkillButton.qml b/Fk/RoomElement/SkillButton.qml index 09459869..d7c9c7f7 100644 --- a/Fk/RoomElement/SkillButton.qml +++ b/Fk/RoomElement/SkillButton.qml @@ -23,9 +23,17 @@ Item { x: -13 - 120 * 0.166 y: -6 - 55 * 0.166 scale: 0.66 - source: type === "notactive" ? "" - : AppPath + "/image/button/skill/" + type + "/" - + (enabled ? (pressed ? "pressed" : "normal") : "disabled") + source: { + if (type === "notactive") { + return ""; + } + let ret = AppPath + "/image/button/skill/" + type + "/"; + let suffix = enabled ? (pressed ? "pressed" : "normal") : "disabled"; + if (enabled && type === "active" && orig.endsWith("&")) { + suffix += "-attach"; + } + return ret + suffix; + } } Image { diff --git a/image/button/skill/active/normal-attach.png b/image/button/skill/active/normal-attach.png new file mode 100644 index 00000000..792d7143 Binary files /dev/null and b/image/button/skill/active/normal-attach.png differ diff --git a/image/button/skill/active/pressed-attach.png b/image/button/skill/active/pressed-attach.png new file mode 100644 index 00000000..28ffc10d Binary files /dev/null and b/image/button/skill/active/pressed-attach.png differ diff --git a/image/card/chosen.png b/image/card/chosen.png new file mode 100644 index 00000000..6c9049e6 Binary files /dev/null and b/image/card/chosen.png differ diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 2ecf8572..e2529057 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -684,4 +684,52 @@ function SaveRecord() c.client:saveRecord(json.encode(c.record), c.record[2]) end +function GetCardProhibitReason(cid, method, pattern) + local card = Fk:getCardById(cid) + if not card then return "" end + if method == "play" and not card.skill:canUse(Self, card) then return "" end + if method ~= "play" and not card:matchPattern(pattern) then return "" end + if method == "play" then method = "use" end + + local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or Util.DummyTable + local s + for _, skill in ipairs(status_skills) do + local fn = method == "use" and skill.prohibitUse or skill.prohibitResponse + if fn(skill, Self, card) then + s = skill + break + end + end + if not s then return "" end + + -- try to return a translated string + local skillName = s.name + local ret = Fk:translate(skillName) + if ret ~= skillName then + -- TODO: translate + return ret .. "禁" .. (method == "use" and "使用" or "打出") + elseif skillName:endsWith("_prohibit") and skillName:startsWith("#") then + return Fk:translate(skillName:sub(2, -10)) .. "禁" .. (method == "use" and "使用" or "打出") + end +end + +function PoxiPrompt(poxi_type, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi or not poxi.prompt then return "" end + if type(poxi.prompt) == "string" then return Fk:translate(poxi.prompt) end + return poxi.prompt(data) +end + +function PoxiFilter(poxi_type, to_select, selected, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi then return "false" end + return json.encode(poxi.card_filter(to_select, selected, data)) +end + +function PoxiFeasible(poxi_type, selected, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi then return "false" end + return json.encode(poxi.feasible(selected, data)) +end + dofile "lua/client/i18n/init.lua" diff --git a/lua/client/i18n/en_US.lua b/lua/client/i18n/en_US.lua index 983c4489..13c6ad0e 100644 --- a/lua/client/i18n/en_US.lua +++ b/lua/client/i18n/en_US.lua @@ -2,7 +2,7 @@ Fk:loadTranslationTable({ -- Lobby - -- ["Room List"] = "房间列表", + ["Room List"] = "Room List (currently have %1 rooms)", -- ["Enter"] = "进入", -- ["Observe"] = "旁观", diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 14137fb5..5071ee73 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -2,7 +2,7 @@ Fk:loadTranslationTable{ -- Lobby - ["Room List"] = "房间列表", + ["Room List"] = "房间列表 (共%1个房间)", ["Enter"] = "进入", ["Observe"] = "旁观", diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 3cd53dce..ebbb63e6 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -25,6 +25,7 @@ ---@field public filtered_cards table @ 被锁视技影响的卡牌 ---@field public printed_cards table @ 被某些房间现场打印的卡牌,id都是负数且从-2开始 ---@field private _custom_events any[] @ 自定义事件列表 +---@field public poxi_methods table @ “魄袭”框操作方法表 local Engine = class("Engine") --- Engine的构造函数。 @@ -55,6 +56,7 @@ function Engine:initialize() self.game_mode_disabled = {} self.kingdoms = {} self._custom_events = {} + self.poxi_methods = {} self:loadPackages() self:loadDisabled() @@ -334,6 +336,16 @@ function Engine:addGameEvent(name, pfunc, mfunc, cfunc, efunc) table.insert(self._custom_events, { name = name, p = pfunc, m = mfunc, c = cfunc, e = efunc }) end +---@param spec PoxiSpec +function Engine:addPoxiMethod(spec) + assert(type(spec.name) == "string") + assert(type(spec.card_filter) == "function") + assert(type(spec.feasible) == "function") + self.poxi_methods[spec.name] = spec + spec.default_choice = spec.default_choice or function() return {} end + spec.post_select = spec.post_select or function(s) return s end +end + --- 从已经开启的拓展包中,随机选出若干名武将。 --- --- 对于同名武将不会重复选取。 diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 5d1882b5..3a1bcf85 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -588,3 +588,13 @@ function fk.CreateGameMode(spec) end return ret end + +-- other + +---@class PoxiSpec +---@field name string +---@field card_filter fun(to_select: int, selected: int[], data: any): bool +---@field feasible fun(selected: int[], data: any): bool +---@field post_select nil | fun(selected: int[], data: any): int[] +---@field default_choice nil | fun(data: any): int[] +---@field prompt nil | string | fun(data: any): string diff --git a/lua/lsp/freekill.lua b/lua/lsp/freekill.lua index f5f4ef65..994ce5dc 100644 --- a/lua/lsp/freekill.lua +++ b/lua/lsp/freekill.lua @@ -7,6 +7,7 @@ ---@alias null nil ---@alias bool boolean | nil +---@alias int integer ---@class fk ---FreeKill's lua API diff --git a/lua/server/ai/init.lua b/lua/server/ai/init.lua index 8426698b..2b150ff8 100644 --- a/lua/server/ai/init.lua +++ b/lua/server/ai/init.lua @@ -3,3 +3,25 @@ AI = require "server.ai.ai" TrustAI = require "server.ai.trust_ai" RandomAI = require "server.ai.random_ai" +SmartAI = require "server.ai.smart_ai" + +-- load ai module from packages +local directories = FileIO.ls("packages") +require "packages.standard.ai" +require "packages.standard_cards.ai" +require "packages.maneuvering.ai" +table.removeOne(directories, "standard") +table.removeOne(directories, "standard_cards") +table.removeOne(directories, "maneuvering") + +local _disable_packs = json.decode(fk.GetDisabledPacks()) + +for _, dir in ipairs(directories) do + if (not string.find(dir, ".disabled")) and not table.contains(_disable_packs, dir) + and FileIO.isDir("packages/" .. dir) + and FileIO.exists("packages/" .. dir .. "/ai/init.lua") then + + require(string.format("packages.%s.ai", dir)) + + end +end diff --git a/lua/server/ai/smart_ai.lua b/lua/server/ai/smart_ai.lua new file mode 100644 index 00000000..184d439b --- /dev/null +++ b/lua/server/ai/smart_ai.lua @@ -0,0 +1,10 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +---@class SmartAI: AI +local SmartAI = AI:subclass("RandomAI") + +function SmartAI:initialize(player) + AI.initialize(self, player) +end + +return SmartAI diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index 61e4cfbe..4c884f96 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -73,6 +73,13 @@ function GameEvent:addExitFunc(f) table.insert(self.extra_exit_funcs, f) end +function GameEvent:prependExitFunc(f) + if self.extra_exit_funcs == Util.DummyTable then + self.extra_exit_funcs = {} + end + table.insert(self.extra_exit_funcs, 1, f) +end + function GameEvent:findParent(eventType, includeSelf) if includeSelf and self.event == eventType then return self end local e = self.parent diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 31d0e44a..b0d28f53 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -376,14 +376,14 @@ function GameLogic:trigger(event, target, data, refresh_only) end repeat do - local triggerables = table.filter(skills, function(skill) + local invoked_skills = {} + local filter_func = function(skill) return skill.priority_table[event] == prio and + not table.contains(invoked_skills, skill) and skill:triggerable(event, target, player, data) - end) + end - local skill_names = table.map(triggerables, function(skill) - return skill.name - end) + local skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper) while #skill_names > 0 do local skill_name = prio <= 0 and table.random(skill_names) or @@ -392,23 +392,14 @@ function GameLogic:trigger(event, target, data, refresh_only) local skill = skill_name == "game_rule" and GameRule or Fk.skills[skill_name] - local len = #skills + table.insert(invoked_skills, skill) broken = skill:trigger(event, target, player, data) - - table.insertTable( - skill_names, - table.map(table.filter(table.slice(skills, len - #skills), function(s) - return - s.priority_table[event] == prio and - s:triggerable(event, target, player, data) - end), function(s) return s.name end) - ) + skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper) broken = broken or (event == fk.AskForPeaches and room:getPlayerById(data.who).hp > 0) if broken then break end - table.removeOne(skill_names, skill_name) end if broken then break end @@ -428,6 +419,18 @@ function GameLogic:getCurrentEvent() return self.game_event_stack.t[self.game_event_stack.p] end +--- 如果当前事件刚好是技能生效事件,就返回那个技能名,否则返回空串。 +function GameLogic:getCurrentSkillName() + local skillEvent = self:getCurrentEvent() + local ret = "" + if skillEvent.event == GameEvent.SkillEffect then + local _, _, _skill = table.unpack(skillEvent.data) + local skill = _skill.main_skill and _skill.main_skill or _skill + ret = skill.name + end + return ret +end + -- 在指定历史范围中找至多n个符合条件的事件 ---@param eventType integer @ 要查找的事件类型 ---@param n integer @ 最多找多少个 diff --git a/lua/server/room.lua b/lua/server/room.lua index 65cd0791..721cc1ef 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -1457,6 +1457,33 @@ function Room:askForCardsChosen(chooser, target, min, max, flag, reason) return new_ret end +--- 谋askForCardsChosen,需使用Fk:addPoxiMethod定义好方法 +--- +--- 选卡规则和返回值啥的全部自己想办法解决,data填入所有卡的列表(类似ui.card_data) +--- +--- 注意一定要返回一个表,毕竟本质上是选卡函数 +---@param player ServerPlayer +---@param poxi_type string +---@param data any +---@return integer[] +function Room:askForPoxi(player, poxi_type, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi then return {} end + + local command = "AskForPoxi" + self:notifyMoveFocus(player, poxi_type) + local result = self:doRequest(player, command, json.encode { + type = poxi_type, + data = data, + }) + + if result == "" then + return poxi.default_choice(data) + else + return poxi.post_select(json.decode(result), data) + end +end + --- 询问一名玩家从众多选项中选择一个。 ---@param player ServerPlayer @ 要询问的玩家 ---@param choices string[] @ 可选选项列表 diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index e07d2b90..4f253350 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -469,7 +469,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay) local logic = room.logic local turn = logic:getCurrentEvent():findParent(GameEvent.Phase, true) if turn then - turn:addExitFunc(function() self:gainAnExtraPhase(phase, false) end) + turn:prependExitFunc(function() self:gainAnExtraPhase(phase, false) end) return end end @@ -484,7 +484,6 @@ function ServerPlayer:gainAnExtraPhase(phase, delay) arg = phase_name_table[phase], } - GameEvent(GameEvent.Phase, self, self.phase):exec() self.phase = current @@ -580,6 +579,7 @@ function ServerPlayer:skip(phase) end end +--- 当进行到出牌阶段空闲点时,结束出牌阶段。 function ServerPlayer:endPlayPhase() self._play_phase_end = true -- TODO: send log @@ -592,7 +592,7 @@ function ServerPlayer:gainAnExtraTurn(delay) local logic = room.logic local turn = logic:getCurrentEvent():findParent(GameEvent.Turn, true) if turn then - turn:addExitFunc(function() self:gainAnExtraTurn(false) end) + turn:prependExitFunc(function() self:gainAnExtraTurn(false) end) return end end @@ -604,10 +604,32 @@ function ServerPlayer:gainAnExtraTurn(delay) local current = room.current room.current = self + + self.tag["_extra_turn_count"] = self.tag["_extra_turn_count"] or {} + local ex_tag = self.tag["_extra_turn_count"] + local skillName = room.logic:getCurrentSkillName() + table.insert(ex_tag, skillName) + GameEvent(GameEvent.Turn, self):exec() + + table.remove(ex_tag) + room.current = current end +function ServerPlayer:insideExtraTurn() + return self.tag["_extra_turn_count"] and #self.tag["_extra_turn_count"] > 0 +end + +---@return string +function ServerPlayer:getCurrentExtraTurnReason() + local ex_tag = self.tag["_extra_turn_count"] + if (not ex_tag) or #ex_tag == 0 then + return "game_rule" + end + return ex_tag[#ex_tag] +end + function ServerPlayer:drawCards(num, skillName, fromPlace) return self.room:drawCards(self, num, skillName, fromPlace) end diff --git a/packages/maneuvering/ai/init.lua b/packages/maneuvering/ai/init.lua new file mode 100644 index 00000000..e69de29b diff --git a/packages/standard/ai/init.lua b/packages/standard/ai/init.lua new file mode 100644 index 00000000..e69de29b diff --git a/packages/standard_cards/ai/init.lua b/packages/standard_cards/ai/init.lua new file mode 100644 index 00000000..e69de29b diff --git a/packages/test/init.lua b/packages/test/init.lua index 17ab4006..95ce37d8 100644 --- a/packages/test/init.lua +++ b/packages/test/init.lua @@ -90,6 +90,12 @@ local control = fk.CreateActiveSkill{ -- room:swapSeat(from, to) for _, pid in ipairs(effect.tos) do local to = room:getPlayerById(pid) + -- p(room:askForPoxi(from, "test", { + -- { "你自己", from:getCardIds "h" }, + -- { "对方", to:getCardIds "h" }, + -- })) + -- room:setPlayerMark(from, "@$a", {1,2,3}) + -- room:setPlayerMark(from, "@$b", {'slash','duel','axe'}) if to:getMark("mouxushengcontrolled") == 0 then room:addPlayerMark(to, "mouxushengcontrolled") from:control(to) @@ -124,6 +130,22 @@ local control = fk.CreateActiveSkill{ -- room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true) end, } +--[[ +Fk:addPoxiMethod{ + name = "test", + card_filter = function(to_select, selected, data) + local s = Fk:getCardById(to_select).suit + for _, id in ipairs(selected) do + if Fk:getCardById(id).suit == s then return false end + end + return true + end, + feasible = function(selected, data) + return #selected == 0 or #selected == 4 + end, + prompt = "魄袭:选你们俩手牌总共四个花色,或者不选直接按确定按钮" +} +--]] local test_vs = fk.CreateViewAsSkill{ name = "test_vs", pattern = "nullification",