From cec18e06140118f24a4bcaa825a9ce215da5d6ac Mon Sep 17 00:00:00 2001 From: Ho-spair <62695577+Ho-spair@users.noreply.github.com> Date: Sun, 10 Dec 2023 18:55:16 +0800 Subject: [PATCH] Modify game core (#294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增船新“休整”机制; - 修改作废逻辑,并可在当前响应读条禁用该技能(出牌阶段空闲时间点尚未完成限制); - 修复锁视技的相关bug,其cardFilter新增标识是否为判定的参数; - 将护甲扣减融合进体力扣减流程,为伤害流程增加“虚拟伤害”概念,为伤害流程增加“造成过伤害”标识id以供记录搜索使用; - 为变将新增可删除副将。 --------- Co-authored-by: notify --- Fk/Common/Avatar.qml | 20 ++ Fk/Common/AvatarChatBox.qml | 238 ++++++++++++++++++ Fk/Common/ChatBox.qml | 2 +- Fk/Common/LogEdit.qml | 6 +- Fk/Common/qmldir | 2 + Fk/LobbyElement/PersonalSettings.qml | 14 +- Fk/Pages/Replay.qml | 15 +- Fk/Pages/Room.qml | 25 +- Fk/Pages/RoomLogic.js | 4 + Fk/RoomElement/Dashboard.qml | 13 + Fk/RoomElement/Photo.qml | 43 +++- lua/core/engine.lua | 6 +- lua/core/game_mode.lua | 8 +- lua/core/player.lua | 7 +- lua/core/skill_type/filter.lua | 2 +- lua/server/event.lua | 2 + lua/server/events/death.lua | 36 ++- lua/server/events/gameflow.lua | 19 +- lua/server/events/hp.lua | 101 ++++---- lua/server/events/init.lua | 2 + lua/server/events/judge.lua | 2 + lua/server/events/misc.lua | 35 +-- lua/server/events/movecard.lua | 21 +- lua/server/events/usecard.lua | 34 +-- lua/server/gamelogic.lua | 3 + lua/server/room.lua | 123 +++++---- lua/server/system_enum.lua | 1 + packages/standard/game_rule.lua | 2 +- .../image/generals/avatar/__observer.jpg | Bin 0 -> 3162 bytes .../image/generals/avatar/__server.jpg | Bin 0 -> 2563 bytes .../image/generals/avatar/anjiang.jpg | Bin 0 -> 8766 bytes packages/standard/init.lua | 70 ++---- packages/test/init.lua | 1 + 33 files changed, 629 insertions(+), 228 deletions(-) create mode 100644 Fk/Common/Avatar.qml create mode 100644 Fk/Common/AvatarChatBox.qml create mode 100644 packages/standard/image/generals/avatar/__observer.jpg create mode 100644 packages/standard/image/generals/avatar/__server.jpg create mode 100644 packages/standard/image/generals/avatar/anjiang.jpg diff --git a/Fk/Common/Avatar.qml b/Fk/Common/Avatar.qml new file mode 100644 index 00000000..87eebd52 --- /dev/null +++ b/Fk/Common/Avatar.qml @@ -0,0 +1,20 @@ +import QtQuick +import Fk + +Image { + property string general + + width: 64 + height: 64 + source: SkinBank.getGeneralExtraPic(general, "avatar/") ?? SkinBank.getGeneralPicture(general) + // sourceSize.width: 250 + // sourceSize.height: 292 + property bool useSmallPic: !!SkinBank.getGeneralExtraPic(general, "avatar/") + sourceClipRect: useSmallPic ? undefined : Qt.rect(61, 0, 128, 128) + + Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 1 + } +} diff --git a/Fk/Common/AvatarChatBox.qml b/Fk/Common/AvatarChatBox.qml new file mode 100644 index 00000000..b79c4d6a --- /dev/null +++ b/Fk/Common/AvatarChatBox.qml @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Fk.Pages + +Rectangle { + property bool isLobby: false + + function append(chatter, data) { + let general = data.general; + let avatar; + if (general == "__server") { + general = ""; + avatar = "__server" + } else if (!roomScene.getPhoto(data.sender)) { + avatar = "__observer"; + } + chatLogBox.append({ + avatar: data.general || roomScene.getPhoto(data.sender)?.general || avatar || "unknown", + general: general, + msg: data.msg, + userName: data.userName, + time: data.time, + isSelf: data.sender === Self.id, + }) + } + + function loadSkills() { + for (let i = 1; i <= 16; i++) { + skills.append({ name: "fastchat_m", idx: i }); + } + } + + Timer { + id: opTimer + interval: 1500 + } + + Component { + id: avatarDelegate + Item { + width: chatLogBox.width + height: childrenRect.height + Avatar { + id: avatarPic + width: 36 + height: 36 + general: avatar + anchors.top: parent.top + anchors.topMargin: 8 + anchors.left: isSelf ? undefined : parent.left + anchors.right: !isSelf ? undefined : parent.right + } + + Text { + id: unameLbl + anchors.left: isSelf ? undefined : avatarPic.right + anchors.right: !isSelf ? undefined : avatarPic.left + anchors.margins: 6 + font.pixelSize: 14 + text: userName + (general ? (" (" + Backend.translate(general) + ")") : "") + + ' [' + time + "]" + } + + Rectangle { + anchors.left: isSelf ? undefined : avatarPic.right + anchors.right: !isSelf ? undefined : avatarPic.left + anchors.margins: 4 + anchors.top: unameLbl.bottom + width: Math.min(parent.width - 80, childrenRect.width + 12) + height: childrenRect.height + 12 + radius: 8 + color: isSelf ? "lightgreen" : "lightsteelblue" + Text { + width: Math.min(contentWidth, parent.parent.width - 80 - 12) + x: 6; y: 6 + text: msg + wrapMode: Text.WrapAnywhere + font.family: fontLibian.name + font.pixelSize: 16 + } + } + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton + gesturePolicy: TapHandler.WithinBounds + onTapped: chatLogBox.currentIndex = index; + } + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + LogEdit { + id: chatLogBox + anchors.fill: parent + anchors.margins: 10 + delegate: avatarDelegate + //font.pixelSize: 14 + } + } + + GridView { + id: emojiSelector + Layout.fillWidth: true + Layout.preferredHeight: 120 + cellHeight: 48 + cellWidth: 48 + model: 59 + visible: false + clip: true + delegate: ItemDelegate { + Image { + height: 32; width: 32 + anchors.centerIn: parent + source: "../../image/emoji/" + index + } + onClicked: chatEdit.insert(chatEdit.cursorPosition, "{emoji" + index + "}"); + } + } + + ListView { + id: soundSelector + Layout.fillWidth: true + Layout.preferredHeight: 180 + visible: false + clip: true + ScrollBar.vertical: ScrollBar {} + model: ListModel { + id: skills + } + // onVisibleChanged: {skills.clear(); loadSkills();} + + delegate: ItemDelegate { + width: soundSelector.width + height: 30 + text: Backend.translate("$" + name + (idx ? idx.toString() : "")) + + onClicked: { + opTimer.start(); + const general = roomScene.getPhoto(Self.id).general; + let skill = "fastchat_m"; + if (general !== "") { + const data = JSON.parse(Backend.callLuaFunction("GetGeneralDetail", [general])); + const gender = data.gender; + if (gender !== 1) { + skill = "fastchat_f"; + } + } + ClientInstance.notifyServer( + "Chat", + JSON.stringify({ + type: isLobby ? 1 : 2, + msg: "$" + skill + ":" + idx + }) + ); + soundSelector.visible = false; + } + } + } + + RowLayout { + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 28 + color: "#040403" + radius: 3 + border.width: 1 + border.color: "#A6967A" + + TextInput { + id: chatEdit + anchors.fill: parent + anchors.margins: 6 + color: "white" + clip: true + font.pixelSize: 14 + maximumLength: 300 + + onAccepted: { + if (text != "") { + ClientInstance.notifyServer( + "Chat", + JSON.stringify({ + type: isLobby ? 1 : 2, + msg: text + }) + ); + text = ""; + } + } + } + } + + MetroButton { + id: soundBtn + text: "🗨️" + visible: !isLobby + enabled: !opTimer.running; + onClicked: { + emojiSelector.visible = false; + soundSelector.visible = !soundSelector.visible; + } + } + + MetroButton { + id: emojiBtn + text: "😃" + onClicked: { + soundSelector.visible = false; + emojiSelector.visible = !emojiSelector.visible; + } + } + + MetroButton { + text: "✔️" + enabled: !opTimer.running; + onClicked: { + opTimer.start(); + chatEdit.accepted(); + } + } + } + } + + Component.onCompleted: { + loadSkills(); + } +} + diff --git a/Fk/Common/ChatBox.qml b/Fk/Common/ChatBox.qml index 43cdb081..058dae1c 100644 --- a/Fk/Common/ChatBox.qml +++ b/Fk/Common/ChatBox.qml @@ -9,7 +9,7 @@ Rectangle { property bool isLobby: false function append(chatter) { - chatLogBox.append(chatter) + chatLogBox.append({ logText: chatter }) } function loadSkills() { diff --git a/Fk/Common/LogEdit.qml b/Fk/Common/LogEdit.qml index 44491f80..765fa6dd 100644 --- a/Fk/Common/LogEdit.qml +++ b/Fk/Common/LogEdit.qml @@ -33,6 +33,8 @@ ListView { font.pixelSize: 16 TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton + gesturePolicy: TapHandler.WithinBounds onTapped: root.currentIndex = index; } } @@ -43,9 +45,9 @@ ListView { onClicked: root.currentIndex = logModel.count - 1; } - function append(text) { + function append(data) { const autoScroll = root.currentIndex === logModel.count - 1; - logModel.append({ logText: text }); + logModel.append(data); if (autoScroll) { root.currentIndex = logModel.count - 1; } diff --git a/Fk/Common/qmldir b/Fk/Common/qmldir index 410e8358..2f0912a6 100644 --- a/Fk/Common/qmldir +++ b/Fk/Common/qmldir @@ -1,3 +1,5 @@ module Fk.Common ChatBox 1.0 ChatBox.qml LogEdit 1.0 LogEdit.qml +Avatar 1.0 Avatar.qml +AvatarChatBox 1.0 AvatarChatBox.qml diff --git a/Fk/LobbyElement/PersonalSettings.qml b/Fk/LobbyElement/PersonalSettings.qml index ea3515d7..0543eff7 100644 --- a/Fk/LobbyElement/PersonalSettings.qml +++ b/Fk/LobbyElement/PersonalSettings.qml @@ -3,6 +3,7 @@ import QtQuick import QtQuick.Layouts import Fk +import Fk.Common Item { id: root @@ -20,19 +21,10 @@ Item { RowLayout { Item { Layout.preferredWidth: 16 } - Image { + Avatar { Layout.preferredWidth: 64 Layout.preferredHeight: 64 - source: SkinBank.getGeneralExtraPic(Self.avatar, "avatar/") ?? SkinBank.getGeneralPicture(Self.avatar) - // sourceSize.width: 250 - // sourceSize.height: 292 - sourceClipRect: sourceSize.width > 200 ? Qt.rect(61, 0, 128, 128) : undefined - - Rectangle { - anchors.fill: parent - color: "transparent" - border.width: 1 - } + general: Self.avatar } Item { Layout.preferredWidth: 8 } diff --git a/Fk/Pages/Replay.qml b/Fk/Pages/Replay.qml index c9e86ce5..30e70231 100644 --- a/Fk/Pages/Replay.qml +++ b/Fk/Pages/Replay.qml @@ -5,6 +5,7 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs import Fk +import Fk.Common Item { id: root @@ -59,21 +60,13 @@ Item { width: root.width height: 64 - Image { + Avatar { id: generalPic + width: 48; height: 48 anchors.top: parent.top anchors.left: parent.left anchors.margins: 8 - width: 48 - height: 48 - source: SkinBank.getGeneralExtraPic(general, "avatar/") ?? SkinBank.getGeneralPicture(general) - sourceClipRect: sourceSize.width > 200 ? Qt.rect(61, 0, 128, 128) : undefined - - Rectangle { - anchors.fill: parent - color: "transparent" - border.width: 1 - } + general: general } ColumnLayout { diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index b2d9798c..b9d19dac 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -392,6 +392,7 @@ Item { faceup: model.faceup chained: model.chained drank: model.drank + rest: model.rest isOwner: model.isOwner ready: model.ready surrendered: model.surrendered @@ -855,7 +856,7 @@ Item { } Item { visible: !config.replaying - ChatBox { + AvatarChatBox { id: chat anchors.fill: parent } @@ -1121,7 +1122,7 @@ Item { if (raw.msg.startsWith("$")) { if (specialChat(pid, raw, raw.msg.slice(1))) return; } - chat.append(msg); + chat.append(msg, raw); const photo = Logic.getPhoto(pid); if (photo === undefined) { const user = raw.userName; @@ -1176,10 +1177,11 @@ Item { Backend.playSound("./packages/" + extension + "/audio/death/" + g); const m = Backend.translate("~" + g); + data.msg = m; if (general === "") - chat.append(`[${time}] ${userName}: ${m}`); + chat.append(`[${time}] ${userName}: ${m}`, data); else - chat.append(`[${time}] ${userName}(${general}): ${m}`); + chat.append(`[${time}] ${userName}(${general}): ${m}`, data); const photo = Logic.getPhoto(pid); if (photo === undefined) { @@ -1205,10 +1207,11 @@ Item { })); } catch (e) {} const m = Backend.translate("$" + skill + (gene ? "_" + gene : "") + (idx ? idx.toString() : "")); + data.msg = m; if (general === "") - chat.append(`[${time}] ${userName}: ${m}`); + chat.append(`[${time}] ${userName}: ${m}`, data); else - chat.append(`[${time}] ${userName}(${general}): ${m}`); + chat.append(`[${time}] ${userName}(${general}): ${m}`, data) const photo = Logic.getPhoto(pid); if (photo === undefined) { @@ -1224,12 +1227,17 @@ Item { } function addToLog(msg) { - log.append(msg); + log.append({ logText: msg }); } function sendDanmaku(msg) { danmaku.sendLog(msg); - chat.append(msg); + chat.append(null, { + msg: msg, + general: "__server", // FIXME: 基于默认读取貂蝉的数据 + userName: "", + time: "Server", + }); } function showDistance(show) { @@ -1327,6 +1335,7 @@ Item { faceup: true, chained: false, drank: 0, + rest: 0, isOwner: false, ready: false, surrendered: false, diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index f1c3dd45..aaebc51b 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -1319,6 +1319,7 @@ callbacks["AskForUseCard"] = (jsonData) => { const pattern = data[1]; const prompt = data[2]; const extra_data = data[4]; + const disabledSkillNames = data[5]; if (extra_data != null) { if (extra_data.effectTo !== Self.id && roomScene.skippedUseEventId.find(id => id === extra_data.useEventId)) { doCancelButton(); @@ -1336,6 +1337,7 @@ callbacks["AskForUseCard"] = (jsonData) => { } roomScene.responding_card = pattern; roomScene.respond_play = false; + disabledSkillNames && (dashboard.disabledSkillNames = disabledSkillNames); roomScene.state = "responding"; okButton.enabled = false; cancelButton.enabled = true; @@ -1347,6 +1349,7 @@ callbacks["AskForResponseCard"] = (jsonData) => { const cardname = data[0]; const pattern = data[1]; const prompt = data[2]; + const disabledSkillNames = data[5]; if (prompt === "") { roomScene.promptText = Backend.translate("#AskForResponseCard") @@ -1356,6 +1359,7 @@ callbacks["AskForResponseCard"] = (jsonData) => { } roomScene.responding_card = pattern; roomScene.respond_play = true; + disabledSkillNames && (dashboard.disabledSkillNames = disabledSkillNames); roomScene.state = "responding"; okButton.enabled = false; cancelButton.enabled = true; diff --git a/Fk/RoomElement/Dashboard.qml b/Fk/RoomElement/Dashboard.qml index 14b27b3e..9e87ab6c 100644 --- a/Fk/RoomElement/Dashboard.qml +++ b/Fk/RoomElement/Dashboard.qml @@ -19,6 +19,8 @@ RowLayout { property var expanded_piles: ({}) // name -> int[] + property var disabledSkillNames: [] + signal cardSelected(var card) Item { width: 5 } @@ -454,6 +456,11 @@ RowLayout { // if cname is presented, we are responding use or play. for (let i = 0; i < skillButtons.count; i++) { const item = skillButtons.itemAt(i); + if (disabledSkillNames.includes(item.orig)) { + item.enabled = false; + continue; + } + const fitpattern = JSON.parse(Backend.callLuaFunction("SkillFitPattern", [item.orig, cname])); const canresp = JSON.parse(Backend.callLuaFunction("SkillCanResponse", [item.orig, cardResponsing])); item.enabled = fitpattern && canresp; @@ -462,11 +469,17 @@ RowLayout { } for (let i = 0; i < skillButtons.count; i++) { const item = skillButtons.itemAt(i); + if (disabledSkillNames.includes(item.orig)) { + item.enabled = false; + continue; + } + item.enabled = JSON.parse(Backend.callLuaFunction("ActiveCanUse", [item.orig])); } } function disableSkills() { + disabledSkillNames = []; for (let i = 0; i < skillButtons.count; i++) skillButtons.itemAt(i).enabled = false; } diff --git a/Fk/RoomElement/Photo.qml b/Fk/RoomElement/Photo.qml index eb1bb15d..e1ca95f9 100644 --- a/Fk/RoomElement/Photo.qml +++ b/Fk/RoomElement/Photo.qml @@ -3,6 +3,7 @@ import QtQuick import Qt5Compat.GraphicalEffects import QtQuick.Controls +import QtQuick.Layouts import Fk import Fk.PhotoElement @@ -29,6 +30,7 @@ Item { property bool faceup: true property bool chained: false property int drank: 0 + property int rest: 0 property bool isOwner: false property bool ready: false property int winGame: 0 @@ -269,6 +271,45 @@ Item { opacity: 0.4 + Math.log(root.drank) * 0.12 } + ColumnLayout { + id: restRect + anchors.centerIn: photoMask + anchors.leftMargin: 20 + visible: root.rest > 0 + + Text { + Layout.alignment: Qt.AlignCenter + text: "休整中" + font.family: fontLibian.name + font.pixelSize: 40 + color: "white" + style: Text.Outline + textFormat: Text.RichText + } + + Text { + Layout.alignment: Qt.AlignCenter + visible: root.rest > 0 && root.rest < 999 + text: root.rest + font.family: fontLibian.name + font.pixelSize: 30 + color: "white" + style: Text.Outline + textFormat: Text.RichText + } + + Text { + Layout.alignment: Qt.AlignCenter + visible: root.rest > 0 && root.rest < 999 + text: "轮次" + font.family: fontLibian.name + font.pixelSize: 28 + color: "white" + style: Text.Outline + textFormat: Text.RichText + } + } + Rectangle { id: winRateRect width: 138; x: 31 @@ -389,7 +430,7 @@ Item { Image { // id: saveme - visible: root.dead || root.dying || root.surrendered + visible: (root.dead && !root.rest) || root.dying || root.surrendered source: { if (root.dead) { return SkinBank.getRoleDeathPic(root.role); diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 9294b22b..00d3bf93 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -455,12 +455,12 @@ end ---@param player Player @ 和这张牌扯上关系的那名玩家 ---@param data any @ 随意,目前只用到JudgeStruct,为了影响判定牌 function Engine:filterCard(id, player, data) - local card = self:getCardById(id, true) if player == nil then self.filtered_cards[id] = nil return end - local skills = player:getAllSkills() + + local card = self:getCardById(id, true) local filters = self:currentRoom().status_skills[FilterSkill] or Util.DummyTable if #filters == 0 then @@ -475,7 +475,7 @@ function Engine:filterCard(id, player, data) end for _, f in ipairs(filters) do - if f:cardFilter(card, player) then + if f:cardFilter(card, player, type(data) == "table" and data.isJudgeEvent) then local _card = f:viewAs(card, player) _card.id = id _card.skillName = f.name diff --git a/lua/core/game_mode.lua b/lua/core/game_mode.lua index c44f88b2..d8480b98 100644 --- a/lua/core/game_mode.lua +++ b/lua/core/game_mode.lua @@ -27,10 +27,14 @@ end ---@param victim ServerPlayer @ 死者 ---@return string @ 胜者阵营 function GameMode:getWinner(victim) + if victim.rest > 0 then + return "" + end + local room = victim.room local winner = "" - local alive = table.filter(room.alive_players, function(p) - return not p.surrendered + local alive = table.filter(room.players, function(p) + return not p.surrendered and not (p.dead and p.rest == 0) end) if victim.role == "lord" then diff --git a/lua/core/player.lua b/lua/core/player.lua index ef47e8a4..d4b2d369 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -86,6 +86,7 @@ function Player:initialize() self.dying = false self.dead = false self.drank = 0 + self.rest = 0 self.player_skills = {} self.derivative_skills = {} @@ -570,9 +571,9 @@ end ---@param ignoreRemoved? boolean @ 忽略被移除 ---@param num? integer @ 第几个,默认1 ---@return ServerPlayer -function Player:getNextAlive(ignoreRemoved, num) +function Player:getNextAlive(ignoreRemoved, num, ignoreRest) if #Fk:currentRoom().alive_players == 0 then - return self + return self.rest > 0 and self.next.rest > 0 and self.next or self end local doNotIgnore = not ignoreRemoved if doNotIgnore and table.every(Fk:currentRoom().alive_players, function(p) return p:isRemoved() end) then @@ -583,7 +584,7 @@ function Player:getNextAlive(ignoreRemoved, num) num = num or 1 for _ = 1, num do ret = ret.next - while ret.dead or (doNotIgnore and ret:isRemoved()) do + while (ret.dead and not ignoreRest) or (doNotIgnore and ret:isRemoved()) do ret = ret.next end end diff --git a/lua/core/skill_type/filter.lua b/lua/core/skill_type/filter.lua index 593e57e2..aafe5def 100644 --- a/lua/core/skill_type/filter.lua +++ b/lua/core/skill_type/filter.lua @@ -4,7 +4,7 @@ local FilterSkill = StatusSkill:subclass("FilterSkill") ---@param card Card -function FilterSkill:cardFilter(card, player) +function FilterSkill:cardFilter(card, player, isJudgeEvent) return false end diff --git a/lua/server/event.lua b/lua/server/event.lua index 7be5d5ab..1936bfdf 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -135,4 +135,6 @@ fk.BeforePropertyChange = 92 fk.PropertyChange = 93 fk.AfterPropertyChange = 94 +fk.AfterPlayerRevived = 95 + fk.NumOfEvents = 96 diff --git a/lua/server/events/death.lua b/lua/server/events/death.lua index e0fce3cf..67652760 100644 --- a/lua/server/events/death.lua +++ b/lua/server/events/death.lua @@ -53,7 +53,11 @@ GameEvent.functions[GameEvent.Death] = function(self) local room = self.room local victim = room:getPlayerById(deathStruct.who) victim.dead = true - victim._splayer:setDied(true) + + if victim.rest <= 0 then + victim._splayer:setDied(true) + end + table.removeOne(room.alive_players, victim) local logic = room.logic @@ -65,22 +69,26 @@ GameEvent.functions[GameEvent.Death] = function(self) type = "#KillPlayer", to = {killer.id}, from = victim.id, - arg = victim.role, + arg = (victim.rest > 0 and 'unknown' or victim.role), } else room:sendLog{ type = "#KillPlayerWithNoKiller", from = victim.id, - arg = victim.role, + arg = (victim.rest > 0 and 'unknown' or victim.role), } end room:sendLogEvent("Death", {to = victim.id}) - room:broadcastProperty(victim, "role") + if victim.rest == 0 then + room:broadcastProperty(victim, "role") + end room:broadcastProperty(victim, "dead") victim.drank = 0 room:broadcastProperty(victim, "drank") + victim.shield = 0 + room:broadcastProperty(victim, "shield") logic:trigger(fk.GameOverJudge, victim, deathStruct) logic:trigger(fk.Death, victim, deathStruct) @@ -88,3 +96,23 @@ GameEvent.functions[GameEvent.Death] = function(self) logic:trigger(fk.Deathed, victim, deathStruct) end + +GameEvent.functions[GameEvent.Revive] = function(self) + local room = self.room + local player, sendLog, reason = table.unpack(self.data) + + if not player.dead then return end + room:setPlayerProperty(player, "dead", false) + player._splayer:setDied(false) + room:setPlayerProperty(player, "dying", false) + room:setPlayerProperty(player, "hp", player.maxHp) + table.insertIfNeed(room.alive_players, player) + + sendLog = (sendLog == nil) and true or sendLog + if sendLog then + room:sendLog { type = "#Revive", from = player.id } + end + + reason = reason or "" + room.logic:trigger(fk.AfterPlayerRevived, player, { reason = reason }) +end diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index 22559c7a..fe66b5b8 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -161,8 +161,8 @@ GameEvent.functions[GameEvent.Round] = function(self) p = room.current GameEvent(GameEvent.Turn, p):exec() if room.game_finished then break end - room.current = room.current:getNextAlive(true) - until p.seat >= p:getNextAlive(true).seat + room.current = room.current:getNextAlive(true, nil, true) + until p.seat >= p:getNextAlive(true, nil, true).seat logic:trigger(fk.RoundEnd, p) end @@ -194,6 +194,15 @@ GameEvent.prepare_funcs[GameEvent.Turn] = function(self) local logic = room.logic local player = room.current + if player.rest > 0 and player.rest < 999 then + room:setPlayerRest(player, player.rest - 1) + if player.rest == 0 and player.dead then + room:revivePlayer(player, true, "rest") + else + room:delay(50) + end + end + if player.dead then return true end room:sendLog{ type = "$AppendSeparator" } @@ -312,9 +321,9 @@ GameEvent.functions[GameEvent.Phase] = function(self) local result = room:doRequest(player, "PlayCard", player.id) if result == "" then break end - local use = room:handleUseCardReply(player, result) - if use then - room:useCard(use) + local useResult = room:handleUseCardReply(player, result) + if type(useResult) == "table" then + room:useCard(useResult) end if player._play_phase_end then diff --git a/lua/server/events/hp.lua b/lua/server/events/hp.lua index 31520d74..2bd26f38 100644 --- a/lua/server/events/hp.lua +++ b/lua/server/events/hp.lua @@ -48,37 +48,50 @@ GameEvent.functions[GameEvent.ChangeHp] = function(self) damageEvent = damageStruct, } + if reason == "damage" then + data.shield_lost = math.min(-num, player.shield) + data.num = num + data.shield_lost + end + if logic:trigger(fk.BeforeHpChanged, player, data) then logic:breakEvent(false) end - assert(not (data.reason == "recover" and data.num < 0)) - player.hp = math.min(player.hp + data.num, player.maxHp) - room:broadcastProperty(player, "hp") + if reason == "damage" and data.shield_lost > 0 and not damageStruct.isVirtualDMG then + room:changeShield(player, -data.shield_lost) + end if reason == "damage" then sendDamageLog(room, damageStruct) - elseif reason == "loseHp" then - room:sendLog{ - type = "#LoseHP", - from = player.id, - arg = 0 - num, - } - room:sendLogEvent("LoseHP", {}) - elseif reason == "recover" then - room:sendLog{ - type = "#HealHP", - from = player.id, - arg = num, - } end - room:sendLog{ - type = "#ShowHPAndMaxHP", - from = player.id, - arg = player.hp, - arg2 = player.maxHp, - } + if not (reason == "damage" and (data.num == 0 or damageStruct.isVirtualDMG)) then + assert(not (data.reason == "recover" and data.num < 0)) + player.hp = math.min(player.hp + data.num, player.maxHp) + room:broadcastProperty(player, "hp") + + if reason == "loseHp" then + room:sendLog{ + type = "#LoseHP", + from = player.id, + arg = 0 - num, + } + room:sendLogEvent("LoseHP", {}) + elseif reason == "recover" then + room:sendLog{ + type = "#HealHP", + from = player.id, + arg = num, + } + end + + room:sendLog{ + type = "#ShowHPAndMaxHP", + from = player.id, + arg = player.hp, + arg2 = player.maxHp, + } + end logic:trigger(fk.HpChanged, player, data) @@ -124,10 +137,12 @@ GameEvent.functions[GameEvent.Damage] = function(self) local stages = { {fk.PreDamage, damageStruct.from}, - {fk.DamageCaused, damageStruct.from}, - {fk.DamageInflicted, damageStruct.to}, } + if not damageStruct.isVirtualDMG then + table.insertTable(stages, { { fk.DamageCaused, damageStruct.from }, { fk.DamageInflicted, damageStruct.to } }) + end + for _, struct in ipairs(stages) do local event, player = table.unpack(struct) if logic:trigger(event, player, damageStruct) or damageStruct.damage < 1 then @@ -141,6 +156,9 @@ GameEvent.functions[GameEvent.Damage] = function(self) return false end + damageStruct.dealtRecorderId = room.logic.specific_events_id[GameEvent.Damage] + room.logic.specific_events_id[GameEvent.Damage] = room.logic.specific_events_id[GameEvent.Damage] + 1 + if damageStruct.card and damageStruct.damage > 0 then local parentUseData = logic:getCurrentEvent():findParent(GameEvent.UseCard) if parentUseData then @@ -155,23 +173,16 @@ GameEvent.functions[GameEvent.Damage] = function(self) damageStruct.to:setChainState(false) end - -- 先扣减护甲,再扣体力值 - local shield_to_lose = math.min(damageStruct.damage, damageStruct.to.shield) - room:changeShield(damageStruct.to, -shield_to_lose) - - if shield_to_lose < damageStruct.damage then - if not room:changeHp( - damageStruct.to, - shield_to_lose - damageStruct.damage, - "damage", - damageStruct.skillName, - damageStruct) then - logic:breakEvent(false) - end - else - sendDamageLog(room, damageStruct) + if not room:changeHp( + damageStruct.to, + -damageStruct.damage, + "damage", + damageStruct.skillName, + damageStruct) then + logic:breakEvent(false) end + stages = { {fk.Damage, damageStruct.from}, {fk.Damaged, damageStruct.to}, @@ -201,9 +212,15 @@ GameEvent.exit_funcs[GameEvent.Damage] = function(self) type = "#ChainDamage", from = p.id } - local dmg = table.simpleClone(damageStruct) - dmg.to = p - dmg.chain = true + local dmg = { + from = damageStruct.from, + to = p, + damage = damageStruct.damage, + card = damageStruct.card, + skillName = damageStruct.skillName, + chain = true, + } + room:damage(dmg) end end diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index c5559f88..a5e308f5 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -14,6 +14,7 @@ dofile "lua/server/events/hp.lua" GameEvent.Dying = 6 GameEvent.Death = 7 +GameEvent.Revive = 22 dofile "lua/server/events/death.lua" GameEvent.MoveCards = 8 @@ -64,6 +65,7 @@ local eventTranslations = { [GameEvent.ChangeMaxHp] = "GameEvent.ChangeMaxHp", [GameEvent.Dying] = "GameEvent.Dying", [GameEvent.Death] = "GameEvent.Death", + [GameEvent.Revive] = "GameEvent.Revive", [GameEvent.MoveCards] = "GameEvent.MoveCards", [GameEvent.UseCard] = "GameEvent.UseCard", [GameEvent.RespondCard] = "GameEvent.RespondCard", diff --git a/lua/server/events/judge.lua b/lua/server/events/judge.lua index a234dcf4..3f41eed5 100644 --- a/lua/server/events/judge.lua +++ b/lua/server/events/judge.lua @@ -5,6 +5,8 @@ GameEvent.functions[GameEvent.Judge] = function(self) local room = self.room local logic = room.logic local who = data.who + + data.isJudgeEvent = true logic:trigger(fk.StartJudge, who, data) data.card = data.card or Fk:getCardById(room:getNCards(1)[1]) diff --git a/lua/server/events/misc.lua b/lua/server/events/misc.lua index 5be32ae8..ff30ffbc 100644 --- a/lua/server/events/misc.lua +++ b/lua/server/events/misc.lua @@ -39,28 +39,33 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self) room:setPlayerProperty(player, "general", data.general) end - if data.deputyGeneral and data.deputyGeneral ~= "" and data.deputyGeneral ~= player.deputyGeneral then + if data.deputyGeneral and data.deputyGeneral ~= player.deputyGeneral then local originalDeputy = Fk.generals[player.deputyGeneral] or Fk.generals["blank_shibing"] local originalSkills = originalDeputy and originalDeputy:getSkillNameList() or Util.DummyTable table.insertTableIfNeed(skills, table.map(originalSkills, function(e) return "-" .. e end)) - local newDeputy = Fk.generals[data.deputyGeneral] or Fk.generals["blank_shibing"] - for _, name in ipairs(newDeputy:getSkillNameList()) do - local s = Fk.skills[name] - if not s.relate_to_place or s.relate_to_place == "d" then - table.insertIfNeed(skills, name) + + if data.deputyGeneral ~= "" then + local newDeputy = Fk.generals[data.deputyGeneral] or Fk.generals["blank_shibing"] + for _, name in ipairs(newDeputy:getSkillNameList()) do + local s = Fk.skills[name] + if not s.relate_to_place or s.relate_to_place == "d" then + table.insertIfNeed(skills, name) + end + end + + if data.sendLog then + room:sendLog{ + type = "#ChangeHero", + from = player.id, + arg = player.deputyGeneral, + arg2 = data.deputyGeneral, + arg3 = "deputyGeneral", + } end end - if data.sendLog then - room:sendLog{ - type = "#ChangeHero", - from = player.id, - arg = player.deputyGeneral, - arg2 = data.deputyGeneral, - arg3 = "deputyGeneral", - } - end + data.results["deputyChange"] = {player.deputyGeneral, data.deputyGeneral} room:setPlayerProperty(player, "deputyGeneral", data.deputyGeneral) end diff --git a/lua/server/events/movecard.lua b/lua/server/events/movecard.lua index dcfb7246..764a388d 100644 --- a/lua/server/events/movecard.lua +++ b/lua/server/events/movecard.lua @@ -152,10 +152,18 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) room:doBroadcastNotify("UpdateDrawPile", #room.draw_pile) end - if not (data.to and data.toArea ~= Card.PlayerHand) then - Fk:filterCard(info.cardId, room:getPlayerById(data.to)) + local beforeCard = Fk:getCardById(info.cardId) + if + realFromArea == Player.Equip and + beforeCard.type == Card.TypeEquip and + data.from ~= nil and + beforeCard.equip_skill + then + beforeCard:onUninstall(room, room:getPlayerById(data.from)) end + Fk:filterCard(info.cardId, room:getPlayerById(data.to)) + local currentCard = Fk:getCardById(info.cardId) for name, _ in pairs(currentCard.mark) do if name:endsWith("-inhand") and @@ -174,15 +182,6 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) then currentCard:onInstall(room, room:getPlayerById(data.to)) end - - if - realFromArea == Player.Equip and - currentCard.type == Card.TypeEquip and - data.from ~= nil and - currentCard.equip_skill - then - currentCard:onUninstall(room, room:getPlayerById(data.from)) - end end end end diff --git a/lua/server/events/usecard.lua b/lua/server/events/usecard.lua index 7d5a4077..fa1a997f 100644 --- a/lua/server/events/usecard.lua +++ b/lua/server/events/usecard.lua @@ -155,13 +155,29 @@ local sendCardEmotionAndLog = function(room, cardUseEvent) } end end +end +GameEvent.functions[GameEvent.UseCard] = function(self) + local cardUseEvent = table.unpack(self.data) + local room = self.room + local logic = room.logic + + if cardUseEvent.card.skill then + cardUseEvent.card.skill:onUse(room, cardUseEvent) + end + + sendCardEmotionAndLog(room, cardUseEvent) + + room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse) + + local card = cardUseEvent.card + local useCardIds = card:isVirtual() and card.subcards or { card.id } if #useCardIds == 0 then return end if cardUseEvent.tos and #cardUseEvent.tos > 0 and #cardUseEvent.tos <= 2 then local tos = table.map(cardUseEvent.tos, function(e) return e[1] end) room:sendFootnote(useCardIds, { type = "##UseCardTo", - from = from, + from = cardUseEvent.from, to = tos, }) if card:isVirtual() then @@ -170,26 +186,12 @@ local sendCardEmotionAndLog = function(room, cardUseEvent) else room:sendFootnote(useCardIds, { type = "##UseCard", - from = from, + from = cardUseEvent.from, }) if card:isVirtual() then room:sendCardVirtName(useCardIds, card.name) end end -end - -GameEvent.functions[GameEvent.UseCard] = function(self) - local cardUseEvent = table.unpack(self.data) - local room = self.room - local logic = room.logic - - room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse) - - if cardUseEvent.card.skill then - cardUseEvent.card.skill:onUse(room, cardUseEvent) - end - - sendCardEmotionAndLog(room, cardUseEvent) if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then logic:breakEvent() diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index a5abc2b5..6818aedf 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -23,6 +23,9 @@ function GameLogic:initialize(room) self.all_game_events = {} self.event_recorder = {} self.current_event_id = 0 + self.specific_events_id = { + [GameEvent.Damage] = 0, + } self.role_table = { { "lord" }, diff --git a/lua/server/room.lua b/lua/server/room.lua index e9cd8bc6..9ac5e40a 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -1913,10 +1913,13 @@ function Room:handleUseCardReply(player, data) end use.card = c - skill:beforeUse(player, use) - self:useSkill(player, skill, Util.DummyFunc) + local rejectSkillName = skill:beforeUse(player, use) + if type(rejectSkillName) == "string" then + return rejectSkillName + end + return use end end @@ -2007,17 +2010,29 @@ function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extr player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0) return askForUseCardData.result else - local data = {card_name, pattern, prompt, cancelable, extra_data} + local useResult + local disabledSkillNames = {} - Fk.currentResponsePattern = pattern - local result = self:doRequest(player, command, json.encode(data)) - Fk.currentResponsePattern = nil + repeat + useResult = nil + local data = {card_name, pattern, prompt, cancelable, extra_data, disabledSkillNames} - if result ~= "" then - player.room:setPlayerMark(player, MarkEnum.BypassDistancesLimit .. "-tmp", 0) - player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0) - return self:handleUseCardReply(player, result) - end + Fk.currentResponsePattern = pattern + local result = self:doRequest(player, command, json.encode(data)) + Fk.currentResponsePattern = nil + + if result ~= "" then + player.room:setPlayerMark(player, MarkEnum.BypassDistancesLimit .. "-tmp", 0) + player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0) + useResult = self:handleUseCardReply(player, result) + + if type(useResult) == "string" and useResult ~= "" then + table.insertIfNeed(disabledSkillNames, useResult) + end + end + until type(useResult) ~= "string" + + return useResult end player.room:setPlayerMark(player, MarkEnum.BypassDistancesLimit .. "-tmp", 0) player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0) @@ -2056,17 +2071,28 @@ function Room:askForResponse(player, card_name, pattern, prompt, cancelable, ext if eventData.result then return eventData.result else - local data = {card_name, pattern, prompt, cancelable, extra_data} + local useResult + local disabledSkillNames = {} - Fk.currentResponsePattern = pattern - local result = self:doRequest(player, command, json.encode(data)) - Fk.currentResponsePattern = nil + repeat + useResult = nil + local data = {card_name, pattern, prompt, cancelable, extra_data, disabledSkillNames} - if result ~= "" then - local use = self:handleUseCardReply(player, result) - if use then - return use.card + Fk.currentResponsePattern = pattern + local result = self:doRequest(player, command, json.encode(data)) + Fk.currentResponsePattern = nil + + if result ~= "" then + useResult = self:handleUseCardReply(player, result) + + if type(useResult) == "string" and useResult ~= "" then + table.insertIfNeed(disabledSkillNames, useResult) + end end + until type(useResult) ~= "string" + + if useResult then + return useResult.card end end return nil @@ -2094,20 +2120,31 @@ function Room:askForNullification(players, card_name, pattern, prompt, cancelabl prompt = prompt or "" pattern = pattern or card_name - self:notifyMoveFocus(self.alive_players, card_name) - self:doBroadcastNotify("WaitForNullification", "") + local useResult + local disabledSkillNames = {} - local data = {card_name, pattern, prompt, cancelable, extra_data} + repeat + useResult = nil + self:notifyMoveFocus(self.alive_players, card_name) + self:doBroadcastNotify("WaitForNullification", "") - Fk.currentResponsePattern = pattern - local winner = self:doRaceRequest(command, players, json.encode(data)) + local data = {card_name, pattern, prompt, cancelable, extra_data, disabledSkillNames} - if winner then - local result = winner.client_reply - return self:handleUseCardReply(winner, result) - end - Fk.currentResponsePattern = nil - return nil + Fk.currentResponsePattern = pattern + local winner = self:doRaceRequest(command, players, json.encode(data)) + + if winner then + local result = winner.client_reply + useResult = self:handleUseCardReply(winner, result) + + if type(useResult) == "string" and useResult ~= "" then + table.insertIfNeed(disabledSkillNames, useResult) + end + end + Fk.currentResponsePattern = nil + until type(useResult) ~= "string" + + return useResult end -- AG(a.k.a. Amazing Grace) functions @@ -2409,9 +2446,8 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators) firstTarget = false - if room.logic:trigger(stage, (stage == fk.TargetSpecifying or stage == fk.TargetSpecified) and room:getPlayerById(aimStruct.from) or room:getPlayerById(aimStruct.to), aimStruct) then - return false - end + room.logic:trigger(stage, (stage == fk.TargetSpecifying or stage == fk.TargetSpecified) and room:getPlayerById(aimStruct.from) or room:getPlayerById(aimStruct.to), aimStruct) + AimGroup:removeDeadTargets(room, aimStruct) local aimEventTargetGroup = aimStruct.targetGroup @@ -3292,19 +3328,9 @@ function Room:useSkill(player, skill, effect_cb) end ---@param player ServerPlayer ----@param sendLog? boolean -function Room:revivePlayer(player, sendLog) - if not player.dead then return end - self:setPlayerProperty(player, "dead", false) - player._splayer:setDied(false) - self:setPlayerProperty(player, "dying", false) - self:setPlayerProperty(player, "hp", player.maxHp) - table.insertIfNeed(self.alive_players, player) - - sendLog = (sendLog == nil) and true or sendLog - if sendLog then - self:sendLog { type = "#Revive", from = player.id } - end +---@param sendLog? bool +function Room:revivePlayer(player, sendLog, reason) + return execGameEvent(GameEvent.Revive, player, sendLog, reason) end ---@param room Room @@ -3573,4 +3599,9 @@ function Room:resumePlayerArea(player, playerSlots) end end +function Room:setPlayerRest(player, roundNum) + player.rest = roundNum + self:broadcastProperty(player, "rest") +end + return Room diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index e4f404ea..4120b836 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -39,6 +39,7 @@ --- 描述和一次体力变化有关的数据 ---@class HpChangedData ---@field public num integer @ 体力变化量,可能是正数或者负数 +---@field public shield_lost integer|nil ---@field public reason string @ 体力变化原因 ---@field public skillName string @ 引起体力变化的技能名 ---@field public damageEvent? DamageStruct @ 引起这次体力变化的伤害数据 diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index ca4a826a..72bb0ec8 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -82,7 +82,7 @@ GameRule = fk.CreateTriggerSkill{ end, [fk.BuryVictim] = function() player:bury() - if room.tag["SkipNormalDeathProcess"] then + if room.tag["SkipNormalDeathProcess"] or player.rest > 0 then return false end local damage = data.damage diff --git a/packages/standard/image/generals/avatar/__observer.jpg b/packages/standard/image/generals/avatar/__observer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a78ba9f6542af81a39ede534bd9cbfe2bcc05ffb GIT binary patch literal 3162 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 WE50n(S|yjah_Pjt_RY-s-v0s=Oa3ST literal 0 HcmV?d00001 diff --git a/packages/standard/init.lua b/packages/standard/init.lua index b1cec657..a1fbb3fb 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -423,6 +423,31 @@ local jijiang = fk.CreateViewAsSkill{ c.skillName = self.name return c end, + before_use = function(self, player, use) + local room = player.room + if use.tos then + room:doIndicate(player.id, TargetGroup:getRealTargets(use.tos)) + end + + for _, p in ipairs(room:getOtherPlayers(player)) do + if p.kingdom == "shu" then + local cardResponded = room:askForResponse(p, "slash", "slash", "#jijiang-ask:" .. player.id, true) + if cardResponded then + room:responseCard({ + from = p.id, + card = cardResponded, + skipDrop = true, + }) + + use.card = cardResponded + return + end + end + end + + room:setPlayerMark(player, "jijiang-failed-phase", 1) + return self.name + end, enabled_at_play = function(self, player) return player:getMark("jijiang-failed-phase") == 0 and not table.every(Fk:currentRoom().alive_players, function(p) return p == player or p.kingdom ~= "shu" @@ -434,51 +459,6 @@ local jijiang = fk.CreateViewAsSkill{ end) end, } -local jijiangResponse = fk.CreateTriggerSkill{ - name = "#jijiangResponse", - events = {fk.PreCardUse, fk.PreCardRespond}, - mute = true, - priority = 10, - can_trigger = function(self, event, target, player, data) - return target == player and player:hasSkill(self.name, true) and table.contains(data.card.skillNames, "jijiang") - end, - on_cost = function(self, event, target, player, data) - local room = player.room - room:doIndicate(player.id, TargetGroup:getRealTargets(data.tos)) - return true - end, - on_use = function(self, event, target, player, data) - local room = player.room - for _, p in ipairs(room:getOtherPlayers(player)) do - if p.kingdom == "shu" then - local cardResponded = room:askForResponse(p, "slash", "slash", "#jijiang-ask:" .. player.id, true) - if cardResponded then - room:responseCard({ - from = p.id, - card = cardResponded, - skipDrop = true, - }) - - data.card = cardResponded - return false - end - end - end - - if event == fk.PreCardUse and player.phase == Player.Play then - room:setPlayerMark(player, "jijiang-failed-phase", 1) - end - return true - end, - refresh_events = {fk.CardUsing}, - can_refresh = function(self, event, target, player, data) - return target == player and player:hasSkill(self.name, true) and player:getMark("jijiang-failed-phase") > 0 - end, - on_refresh = function(self, event, target, player, data) - player.room:setPlayerMark(player, "jijiang-failed-phase", 0) - end, -} -jijiang:addRelatedSkill(jijiangResponse) local liubei = General:new(extension, "liubei", "shu", 4) liubei:addSkill(rende) diff --git a/packages/test/init.lua b/packages/test/init.lua index 01a19ed8..f9151005 100644 --- a/packages/test/init.lua +++ b/packages/test/init.lua @@ -338,6 +338,7 @@ local test_feichu = fk.CreateActiveSkill{ room:abortPlayerArea(from, eqipSlots) end, } + local test2 = General(extension, "mouxusheng", "wu", 4, 4, General.Female) test2.shield = 3 test2.hidden = true