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 00000000..a78ba9f6 Binary files /dev/null and b/packages/standard/image/generals/avatar/__observer.jpg differ diff --git a/packages/standard/image/generals/avatar/__server.jpg b/packages/standard/image/generals/avatar/__server.jpg new file mode 100644 index 00000000..716b73c9 Binary files /dev/null and b/packages/standard/image/generals/avatar/__server.jpg differ diff --git a/packages/standard/image/generals/avatar/anjiang.jpg b/packages/standard/image/generals/avatar/anjiang.jpg new file mode 100644 index 00000000..53f68d75 Binary files /dev/null and b/packages/standard/image/generals/avatar/anjiang.jpg differ 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