From 1c6304f0f73c192519593e3197feb0b1d7aff8bc Mon Sep 17 00:00:00 2001 From: Ho-spair <62695577+Ho-spair@users.noreply.github.com> Date: Sat, 20 May 2023 16:00:03 +0800 Subject: [PATCH] UI modify (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现移动场上一张牌; - 实现用作记录牌名并可查看的mark; - 将askForChoice和interaction的文本解析方式改为prompt; - 新增属性将牌移至牌堆指定索引位置; - 修改时机“游戏开始时”至正确位置; - 优化衍生牌逻辑; - 新增“卡牌展示后”时机。 --- Fk/Common/ChatBox.qml | 2 +- Fk/LobbyElement/PersonalSettings.qml | 2 +- Fk/Pages/Lobby.qml | 2 +- Fk/Pages/Room.qml | 6 +- Fk/Pages/RoomLogic.js | 47 ++++++-- Fk/PhotoElement/MarkArea.qml | 33 ++++-- Fk/RoomElement/CardItem.qml | 2 +- Fk/RoomElement/ChoiceBox.qml | 14 ++- Fk/RoomElement/Dashboard.qml | 2 +- Fk/RoomElement/InvisibleCardArea.qml | 2 +- Fk/RoomElement/MoveCardInBoardBox.qml | 148 +++++++++++++++++++++++++ Fk/RoomElement/SkillButton.qml | 24 +++- Fk/RoomElement/ViewPile.qml | 13 ++- Fk/SkillInteraction/SkillCombo.qml | 17 ++- Fk/skin-bank.js | 16 ++- lua/client/client_util.lua | 8 ++ lua/client/i18n/zh_CN.lua | 5 +- lua/core/card.lua | 7 ++ lua/core/player.lua | 13 +++ lua/core/util.lua | 14 +++ lua/server/event.lua | 7 +- lua/server/events/gameflow.lua | 9 +- lua/server/events/movecard.lua | 14 ++- lua/server/events/usecard.lua | 2 +- lua/server/gamelogic.lua | 12 +- lua/server/room.lua | 77 +++++++++++++ lua/server/serverplayer.lua | 2 + lua/server/system_enum.lua | 1 + packages/standard/aux_skills.lua | 22 ---- packages/standard/game_rule.lua | 4 +- packages/standard_cards/i18n/zh_CN.lua | 11 ++ 31 files changed, 460 insertions(+), 78 deletions(-) create mode 100644 Fk/RoomElement/MoveCardInBoardBox.qml diff --git a/Fk/Common/ChatBox.qml b/Fk/Common/ChatBox.qml index 41c937c8..2ddce5d7 100644 --- a/Fk/Common/ChatBox.qml +++ b/Fk/Common/ChatBox.qml @@ -34,7 +34,7 @@ Rectangle { Layout.preferredHeight: 120 cellHeight: 48 cellWidth: 48 - model: 49 + model: 50 visible: false delegate: ItemDelegate { Image { diff --git a/Fk/LobbyElement/PersonalSettings.qml b/Fk/LobbyElement/PersonalSettings.qml index a278972d..ea2b8fe6 100644 --- a/Fk/LobbyElement/PersonalSettings.qml +++ b/Fk/LobbyElement/PersonalSettings.qml @@ -49,7 +49,7 @@ Item { TapHandler { onTapped: { - lobby_dialog.sourceComponent = Qt.createComponent("Fk.LobbyElement", "EditProfile"); + lobby_dialog.sourceComponent = Qt.createComponent("EditProfile.qml"); lobby_drawer.open(); } } diff --git a/Fk/Pages/Lobby.qml b/Fk/Pages/Lobby.qml index 56e2d988..095ba1c8 100644 --- a/Fk/Pages/Lobby.qml +++ b/Fk/Pages/Lobby.qml @@ -154,7 +154,7 @@ Item { icon.name: "media-playback-start" text: Backend.translate("Create Room") onClicked: { - lobby_dialog.sourceComponent = Qt.createComponent("Fk.LobbyElement", "CreateRoom"); + lobby_dialog.sourceComponent = Qt.createComponent("../LobbyElement/CreateRoom.qml"); lobby_drawer.open(); config.observing = false; } diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index b0ffe5e3..5bf7ae0a 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -487,14 +487,14 @@ Item { Backend.callLuaFunction("SetInteractionDataOfSkill", [skill_name, "null"]); switch (data.type) { case "combo": - skillInteraction.sourceComponent = Qt.createComponent("Fk.SkillInteraction", "SkillCombo"); + skillInteraction.sourceComponent = Qt.createComponent("../SkillInteraction/SkillCombo.qml"); skillInteraction.item.skill = skill_name; skillInteraction.item.default_choice = data["default"]; skillInteraction.item.choices = data.choices; // skillInteraction.item.clicked(); break; case "spin": - skillInteraction.sourceComponent = Qt.createComponent("Fk.SkillInteraction", "SkillSpin"); + skillInteraction.sourceComponent = Qt.createComponent("../SkillInteraction/SkillSpin.qml"); skillInteraction.item.skill = skill_name; skillInteraction.item.from = data.from; skillInteraction.item.to = data.to; @@ -816,7 +816,7 @@ Item { } function startCheat(type, data) { - cheatLoader.sourceComponent = Qt.createComponent("Fk.Cheat", type); + cheatLoader.sourceComponent = Qt.createComponent(`../Cheat/${type}.qml`); cheatLoader.item.extra_data = data; cheatDrawer.open(); } diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index c124570e..171bb5a7 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -228,7 +228,7 @@ function setEmotion(id, emotion, isCardId) { // TODO: set picture emotion return; } - let component = Qt.createComponent("Fk.RoomElement", "PixmapAnimation"); + let component = Qt.createComponent("../RoomElement/PixmapAnimation.qml"); if (component.status !== Component.Ready) return; @@ -277,7 +277,7 @@ function changeHp(id, delta, losthp) { } function doIndicate(from, tos) { - let component = Qt.createComponent("Fk.RoomElement", "IndicatorLine"); + let component = Qt.createComponent("../RoomElement/IndicatorLine.qml"); if (component.status !== Component.Ready) return; @@ -594,7 +594,7 @@ callbacks["AskForGeneral"] = function(jsonData) { let heg = data[2]; roomScene.promptText = Backend.translate("#AskForGeneral"); roomScene.state = "replying"; - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "ChooseGeneralBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChooseGeneralBox.qml"); let box = roomScene.popupBox.item; box.accepted.connect(() => { replyToServer(JSON.stringify(box.choices)); @@ -627,7 +627,7 @@ callbacks["AskForGuanxing"] = function(jsonData) { let min_bottom_cards = data.min_bottom_cards; let max_bottom_cards = data.max_bottom_cards; roomScene.state = "replying"; - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "GuanxingBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GuanxingBox.qml"); data.cards.forEach(id => { let d = Backend.callLuaFunction("GetCardData", [id]); cards.push(JSON.parse(d)); @@ -663,7 +663,7 @@ callbacks["AskForChoice"] = function(jsonData) { roomScene.promptText = processPrompt(prompt); } roomScene.state = "replying"; - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "ChoiceBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChoiceBox.qml"); let box = roomScene.popupBox.item; box.options = choices; box.skill_name = skill_name; @@ -702,7 +702,7 @@ callbacks["AskForCardChosen"] = function(jsonData) { roomScene.promptText = Backend.translate("#AskForChooseCard") .arg(Backend.translate(reason)); roomScene.state = "replying"; - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "PlayerCardBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PlayerCardBox.qml"); let box = roomScene.popupBox.item; box.addHandcards(handcards); box.addEquips(equips); @@ -745,7 +745,7 @@ callbacks["AskForCardsChosen"] = function(jsonData) { roomScene.promptText = Backend.translate("#AskForChooseCard") .arg(Backend.translate(reason)); roomScene.state = "replying"; - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "PlayerCardBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PlayerCardBox.qml"); let box = roomScene.popupBox.item; box.multiChoose = true; box.min = min; @@ -759,6 +759,33 @@ callbacks["AskForCardsChosen"] = function(jsonData) { }); } +callbacks["AskForMoveCardInBoard"] = function(jsonData) { + const data = JSON.parse(jsonData); + const { cards, cardsPosition, generalNames } = data; + + roomScene.state = "replying"; + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/MoveCardInBoardBox.qml"); + + const boxCards = []; + cards.forEach(id => { + let d = Backend.callLuaFunction("GetCardData", [id]); + boxCards.push(JSON.parse(d)); + }); + + const box = roomScene.popupBox.item; + box.cards = boxCards; + box.cardsPosition = cardsPosition; + box.generalNames = generalNames.map(name => { + const namesSplited = name.split('/'); + return namesSplited.length > 1 ? namesSplited.map(nameSplited => Backend.translate(nameSplited)).join('/') : Backend.translate(name) + }); + + box.arrangeCards(); + box.accepted.connect(() => { + replyToServer(JSON.stringify(box.getResult())); + }); +} + callbacks["MoveCards"] = function(jsonData) { // jsonData: merged moves let moves = JSON.parse(jsonData); @@ -938,7 +965,7 @@ callbacks["Animate"] = function(jsonData) { } case "InvokeSkill": { let id = data.player; - let component = Qt.createComponent("Fk.RoomElement", "SkillInvokeAnimation"); + let component = Qt.createComponent("../RoomElement/SkillInvokeAnimation.qml"); if (component.status !== Component.Ready) return; @@ -1002,7 +1029,7 @@ callbacks["LogEvent"] = function(jsonData) { callbacks["GameOver"] = function(jsonData) { roomScene.state = "notactive"; - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "GameOverBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GameOverBox.qml"); let box = roomScene.popupBox.item; box.winner = jsonData; roomScene.isStarted = false; @@ -1011,7 +1038,7 @@ callbacks["GameOver"] = function(jsonData) { callbacks["FillAG"] = (j) => { let data = JSON.parse(j); let ids = data[0]; - roomScene.manualBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "AG"); + roomScene.manualBox.sourceComponent = Qt.createComponent("../RoomElement/AG.qml"); roomScene.manualBox.item.addIds(ids); } diff --git a/Fk/PhotoElement/MarkArea.qml b/Fk/PhotoElement/MarkArea.qml index 9550b329..650d1d13 100644 --- a/Fk/PhotoElement/MarkArea.qml +++ b/Fk/PhotoElement/MarkArea.qml @@ -33,7 +33,7 @@ Item { width: childrenRect.width height: 22 Text { - text: Backend.translate(mark_name) + ' ' + mark_extra + text: Backend.translate(mark_name) + ' ' + (special_value !== '' ? special_value : mark_extra) font.family: fontLibian.name font.pixelSize: 22 font.letterSpacing: -0.6 @@ -53,16 +53,20 @@ Item { TapHandler { enabled: root.parent.state != "candidate" || !root.parent.selectable onTapped: { - let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name])); - data = data.filter((e) => e !== -1); - if (data.length === 0) - return; + const params = { name: mark_name }; + if (mark_name.startsWith('@$')) { + params.cardNames = mark_extra.split(','); + } else { + let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name])); + data = data.filter((e) => e !== -1); + if (data.length === 0) + return; + + params.ids = data; + } // Just for using room's right drawer - roomScene.startCheat("ViewPile", { - name: mark_name, - ids: data - }); + roomScene.startCheat("../RoomElement/ViewPile", params); } } } @@ -83,11 +87,18 @@ Item { } } - data = (data instanceof Array ? data.map((markText) => Backend.translate(markText)).join(' ') : Backend.translate(data)); + let special_value = ''; + if (mark.startsWith('@$')) { + special_value = data.length; + data = data.join(','); + } else { + data = data instanceof Array ? data.map((markText) => Backend.translate(markText)).join(' ') : Backend.translate(data); + } + if (modelItem) modelItem.mark_extra = data; else - markList.append({ mark_name: mark, mark_extra: data }); + markList.append({ mark_name: mark, mark_extra: data, special_value }); arrangeMarks(); } diff --git a/Fk/RoomElement/CardItem.qml b/Fk/RoomElement/CardItem.qml index 4e78bf23..2cea771e 100644 --- a/Fk/RoomElement/CardItem.qml +++ b/Fk/RoomElement/CardItem.qml @@ -85,7 +85,7 @@ Item { Image { id: cardItem - source: known ? SkinBank.getCardPicture(cid) + source: known ? SkinBank.getCardPicture(cid || name) : (SkinBank.CARD_DIR + "card-back") anchors.fill: parent fillMode: Image.PreserveAspectCrop diff --git a/Fk/RoomElement/ChoiceBox.qml b/Fk/RoomElement/ChoiceBox.qml index d9ae9ca8..967eac5d 100644 --- a/Fk/RoomElement/ChoiceBox.qml +++ b/Fk/RoomElement/ChoiceBox.qml @@ -14,6 +14,18 @@ GraphicsBox { width: Math.max(140, body.width + 20) height: body.height + title.height + 20 + function processPrompt(prompt) { + const data = prompt.split(":"); + let raw = Backend.translate(data[0]); + const src = parseInt(data[1]); + const dest = parseInt(data[2]); + if (raw.match("%src")) raw = raw.replace("%src", Backend.translate(getPhoto(src).general)); + if (raw.match("%dest")) raw = raw.replace("%dest", Backend.translate(getPhoto(dest).general)); + if (raw.match("%arg")) raw = raw.replace("%arg", Backend.translate(data[3])); + if (raw.match("%arg2")) raw = raw.replace("%arg2", Backend.translate(data[4])); + return raw; + } + GridLayout { id: body x: 10 @@ -27,7 +39,7 @@ GraphicsBox { MetroButton { Layout.fillWidth: true - text: Backend.translate(modelData) + text: processPrompt(modelData) onClicked: { result = index; diff --git a/Fk/RoomElement/Dashboard.qml b/Fk/RoomElement/Dashboard.qml index 33907fae..1d8c68fb 100644 --- a/Fk/RoomElement/Dashboard.qml +++ b/Fk/RoomElement/Dashboard.qml @@ -73,7 +73,7 @@ RowLayout { if (expanded_pile_names.indexOf(pile) !== -1) return; - let component = Qt.createComponent("Fk.RoomElement", "CardItem"); + let component = Qt.createComponent("../RoomElement/CardItem.qml"); let parentPos = roomScene.mapFromItem(self, 0, 0); expanded_piles[pile] = []; diff --git a/Fk/RoomElement/InvisibleCardArea.qml b/Fk/RoomElement/InvisibleCardArea.qml index f0474b2d..2a01aa28 100644 --- a/Fk/RoomElement/InvisibleCardArea.qml +++ b/Fk/RoomElement/InvisibleCardArea.qml @@ -50,7 +50,7 @@ Item { function remove(outputs) { - let component = Qt.createComponent("Fk.RoomElement", "CardItem"); + let component = Qt.createComponent("CardItem.qml"); if (component.status !== Component.Ready) return []; diff --git a/Fk/RoomElement/MoveCardInBoardBox.qml b/Fk/RoomElement/MoveCardInBoardBox.qml new file mode 100644 index 00000000..7b82f423 --- /dev/null +++ b/Fk/RoomElement/MoveCardInBoardBox.qml @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Layouts +import Fk.Pages + +GraphicsBox { + id: root + property var cards: [] + property var cardsPosition: [] + property var generalNames: [] + property var result + property int padding: 25 + + title.text: Backend.translate("Please click to move card") + width: body.width + padding * 2 + height: title.height + body.height + padding * 2 + + ColumnLayout { + id: body + x: padding + y: parent.height - padding - height + spacing: 20 + + Repeater { + id: areaRepeater + model: generalNames + + Row { + spacing: 5 + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + color: "#6B5D42" + width: 20 + height: 100 + radius: 5 + + Text { + anchors.fill: parent + width: 20 + height: 100 + text: modelData + color: "white" + font.family: fontLibian.name + font.pixelSize: 18 + style: Text.Outline + wrapMode: Text.WrapAnywhere + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + } + + Repeater { + id: cardRepeater + model: cards + + Rectangle { + color: "#4A4139" + width: 93 + height: 130 + opacity: 0.5 + + Text { + horizontalAlignment: Text.AlignHCenter + anchors.centerIn: parent + text: Backend.translate(JSON.parse(Backend.callLuaFunction("GetCardData", [modelData.cid])).subtype) + color: "#90765F" + font.family: fontLibian.name + font.pixelSize: 16 + width: parent.width * 0.8 + wrapMode: Text.WordWrap + } + } + } + property alias cardRepeater: cardRepeater + } + } + + MetroButton { + Layout.alignment: Qt.AlignHCenter + id: buttonConfirm + text: Backend.translate("OK") + width: 120 + height: 35 + enabled: false + + onClicked: close(); + } + } + + Repeater { + id: cardItem + model: cards + + CardItem { + x: index + y: -1 + cid: modelData.cid + name: modelData.name + suit: modelData.suit + number: modelData.number + + selectable: !result || result.item === this + onClicked: { + if (!selectable) return; + if ((result || {}).item === this) { + result = undefined; + } else { + result = { item: this }; + } + + updatePosition(this); + } + } + } + + function arrangeCards() { + for (let i = 0; i < cards.length; i++) { + const curCard = cardItem.itemAt(i); + curCard.origX = i * 98 + 50; + curCard.origY = cardsPosition[i] * 150 + body.y; + curCard.goBack(); + } + } + + function updatePosition(item) { + for (let i = 0; i < 2; i++) { + const index = cards.findIndex(data => item.cid === data.cid); + result && (result.pos = cardsPosition[index]); + + const cardPos = cardsPosition[index] === 0 ? (result ? 1 : 0) : (result ? 0 : 1); + const curArea = areaRepeater.itemAt(cardPos); + const curBox = curArea.cardRepeater.itemAt(index); + const curPos = mapFromItem(curArea, curBox.x, curBox.y); + + item.origX = curPos.x; + item.origY = curPos.y; + item.goBack(true); + + buttonConfirm.enabled = !!result; + } + } + + function getResult() { + return result ? { cardId: result.item.cid, pos: result.pos } : ''; + } +} diff --git a/Fk/RoomElement/SkillButton.qml b/Fk/RoomElement/SkillButton.qml index 7087e28d..09459869 100644 --- a/Fk/RoomElement/SkillButton.qml +++ b/Fk/RoomElement/SkillButton.qml @@ -39,27 +39,41 @@ Item { Text { anchors.centerIn: parent + topPadding: 5 id: skill font.family: fontLi2.name font.pixelSize: Math.max(26 - text.length, 18) visible: false + font.bold: true } Glow { id: glowItem source: skill anchors.fill: skill - radius: 6 - //samples: 8 - color: "grey" + color: "black" + spread: 0.3 + radius: 5 } LinearGradient { anchors.fill: skill source: skill gradient: Gradient { - GradientStop { position: 0; color: "#FFE07C" } - GradientStop { position: 1; color: "#B79A5F" } + GradientStop { + position: 0 + color: "#FEF7C2" + } + + GradientStop { + position: 0.5 + color: "#D2AD4A" + } + + GradientStop { + position: 1 + color: "#BE9878" + } } } diff --git a/Fk/RoomElement/ViewPile.qml b/Fk/RoomElement/ViewPile.qml index 8263c968..860f4e6a 100644 --- a/Fk/RoomElement/ViewPile.qml +++ b/Fk/RoomElement/ViewPile.qml @@ -71,7 +71,7 @@ Item { columns: 4 Repeater { - model: extra_data.ids + model: extra_data.ids || extra_data.cardNames CardItem { id: cardItem @@ -79,7 +79,16 @@ Item { height: cardItem.width * 1.4 autoBack: false Component.onCompleted: { - let data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData])); + let data = {} + if (extra_data.ids) { + data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData])); + } else { + data.cid = 0; + data.name = modelData; + data.suit = ''; + data.number = 0; + data.color = ''; + } setData(data); } } diff --git a/Fk/SkillInteraction/SkillCombo.qml b/Fk/SkillInteraction/SkillCombo.qml index 2e7f4ddb..178595c6 100644 --- a/Fk/SkillInteraction/SkillCombo.qml +++ b/Fk/SkillInteraction/SkillCombo.qml @@ -9,7 +9,20 @@ MetroButton { property var choices: [] property string default_choice property string answer: default_choice - text: Backend.translate(answer) + + function processPrompt(prompt) { + const data = prompt.split(":"); + let raw = Backend.translate(data[0]); + const src = parseInt(data[1]); + const dest = parseInt(data[2]); + if (raw.match("%src")) raw = raw.replace("%src", Backend.translate(getPhoto(src).general)); + if (raw.match("%dest")) raw = raw.replace("%dest", Backend.translate(getPhoto(dest).general)); + if (raw.match("%arg")) raw = raw.replace("%arg", Backend.translate(data[3])); + if (raw.match("%arg2")) raw = raw.replace("%arg2", Backend.translate(data[4])); + return raw; + } + + text: processPrompt(answer) onAnswerChanged: { if (!answer) return; @@ -21,7 +34,7 @@ MetroButton { } onClicked: { - roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "ChoiceBox"); + roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChoiceBox.qml"); let box = roomScene.popupBox.item; box.options = choices; box.accepted.connect(() => { diff --git a/Fk/skin-bank.js b/Fk/skin-bank.js index 039bed1c..b7db7a3b 100644 --- a/Fk/skin-bank.js +++ b/Fk/skin-bank.js @@ -29,10 +29,18 @@ function getGeneralPicture(name) { return GENERAL_DIR + "0.jpg"; } -function getCardPicture(cid) { - let data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid])); - let extension = data.extension; - let name = data.name; +function getCardPicture(cidOrName) { + let extension = ""; + let name = "unknown"; + if (typeof cidOrName === 'string') { + name = cidOrName; + extension = Backend.callLuaFunction("GetCardExtensionByName", [cidOrName]); + } else { + const data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid])); + extension = data.extension; + name = data.name; + } + let path = AppPath + "/packages/" + extension + "/image/card/" + name + ".png"; if (Backend.exists(path)) { return path; diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index b65a18cd..8a2a36c2 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -98,6 +98,14 @@ function GetCardData(id) return json.encode(ret) end +function GetCardExtensionByName(cardName) + local card = table.find(Fk.cards, function(card) + return card.name == cardName + end) + + return card and card.package.extensionName or "" +end + function GetAllGeneralPack() local ret = {} for _, name in ipairs(Fk.package_names) do diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index f7083a30..d30d9b24 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -143,6 +143,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["#choose-trigger"] = "请选择一项技能发动", ["trigger"] = "选择技能", ["Please arrange cards"] = "请拖拽移动卡牌", + ["Please click to move card"] = "请点击移动卡牌", [" thinking..."] = " 思考中...", ["AskForGeneral"] = "选择武将", @@ -203,10 +204,6 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["$NoWinner"] = "平局!", ["Back To Lobby"] = "返回大厅", - ["basic_char"] = "基", - ["trick_char"] = "锦", - ["equip_char"] = "装", - ["Bulletin Info"] = "

更新说明

\ 1. 实现禁将功能;(在武将一览中,点击查看武将并点击退出按钮下方的“禁将/解禁”进行操作);
\ 2. 实现转换技,新增发动转换技动画;
\ diff --git a/lua/core/card.lua b/lua/core/card.lua index 7ed31ae4..52e1e504 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -22,6 +22,7 @@ ---@field public skill Skill ---@field public special_skills string[] | nil ---@field public is_damage_card boolean +---@field public is_derived boolean|null local Card = class("Card") ---@alias Suit integer @@ -96,6 +97,11 @@ function Card:initialize(name, suit, number, color) self.skillNames = {} self.mark = {} + if string.sub(name, 1, 1) == "&" then + self.name = string.sub(name, 2, #name) + self.is_derived = true + end + local mt = table.simpleClone(getmetatable(self)) local newidx = mt.__newindex or rawset mt.__newindex = function(t, k, v) @@ -139,6 +145,7 @@ function Card:clone(suit, number) newCard.equip_skill = self.equip_skill newCard.attack_range = self.attack_range newCard.is_damage_card = self.is_damage_card + newCard.is_derived = self.is_derived return newCard end diff --git a/lua/core/player.lua b/lua/core/player.lua index fd932f65..427d3c3d 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -742,4 +742,17 @@ function Player:getSwitchSkillState(skillName, afterUse) end end +function Player:canMoveCardInBoardTo(to, id) + local card = Fk:getCardById(id) + assert(card.type == Card.TypeEquip or card.sub_type == Card.SubtypeDelayedTrick) + + if card.type == Card.TypeEquip then + return not to:getEquipment(card.sub_type) + else + return not table.find(to:getCardIds(Player.Judge), function(cardId) + return Fk:getCardById(cardId).name == card.name + end) + end +end + return Player diff --git a/lua/core/util.lua b/lua/core/util.lua index 9597a3d5..dc26ebcb 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -208,6 +208,20 @@ function table:slice(begin, _end) return ret end +function table:assign(targetTbl) + for key, value in pairs(targetTbl) do + if self[key] then + if type(value) == "table" then + table.insertTable(self[key], value) + else + table.insert(self[key], value) + end + else + self[key] = value + end + end +end + -- allow a = "Hello"; a[1] == "H" local str_mt = getmetatable("") str_mt.__index = function(str, k) diff --git a/lua/server/event.lua b/lua/server/event.lua index b7a9642f..f1849411 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -6,6 +6,7 @@ ---@alias Event integer fk.NonTrigger = 1 +fk.GamePrepared = 78 fk.GameStart = 2 fk.TurnStart = 3 fk.TurnEnd = 73 @@ -101,4 +102,8 @@ fk.BeforeTriggerSkillUse = 75 fk.BeforeDrawCard = 76 -fk.NumOfEvents = 77 +fk.CardShown = 77 + +-- 78 = GamePrepared + +fk.NumOfEvents = 79 diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index fe0e1218..95c4b6c5 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -92,7 +92,7 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self) room:notifyMoveFocus(room.alive_players, "AskForLuckCard") room:doBroadcastNotify("AskForLuckCard", room.settings.luckTime or 4) - local remainTime = room.timeout + local remainTime = room.timeout + 1 local currentTime = os.time() local elapsed = 0 @@ -127,12 +127,17 @@ GameEvent.functions[GameEvent.Round] = function(self) local logic = room.logic local p - if room:getTag("FirstRound") then + local isFirstRound = room:getTag("FirstRound") + if isFirstRound then room:setTag("FirstRound", false) end room:setTag("RoundCount", room:getTag("RoundCount") + 1) room:doBroadcastNotify("UpdateRoundNum", room:getTag("RoundCount")) + if isFirstRound then + logic:trigger(fk.GameStart, room.current) + end + logic:trigger(fk.RoundStart, room.current) repeat diff --git a/lua/server/events/movecard.lua b/lua/server/events/movecard.lua index 4c638b8a..2af690c3 100644 --- a/lua/server/events/movecard.lua +++ b/lua/server/events/movecard.lua @@ -37,6 +37,7 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) moveVisible = cardsMoveInfo.moveVisible, specialName = cardsMoveInfo.specialName, specialVisible = cardsMoveInfo.specialVisible, + drawPilePosition = cardsMoveInfo.drawPilePosition, } table.insert(cardsMoveStructs, cardsMoveStruct) @@ -100,7 +101,18 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) toAreaIds = self.void end - table.insert(toAreaIds, data.toArea == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId) + if data.toArea == Card.DrawPile then + local putIndex = data.drawPilePosition or 1 + if putIndex == -1 then + putIndex = #self.draw_pile + 1 + elseif putIndex < 1 or putIndex > #self.draw_pile + 1 then + putIndex = 1 + end + + table.insert(toAreaIds, putIndex, info.cardId) + else + table.insert(toAreaIds, info.cardId) + end end self:setCardArea(info.cardId, data.toArea, data.to) if data.toArea == Card.DrawPile or realFromArea == Card.DrawPile then diff --git a/lua/server/events/usecard.lua b/lua/server/events/usecard.lua index b308b25c..68af1924 100644 --- a/lua/server/events/usecard.lua +++ b/lua/server/events/usecard.lua @@ -187,7 +187,7 @@ GameEvent.functions[GameEvent.UseCard] = function(self) table.insert(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card) end - for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do + for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.CardUsing }) do if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then break end diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index b317ad7b..3d0e4893 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -233,6 +233,16 @@ end function GameLogic:prepareDrawPile() local room = self.room local allCardIds = Fk:getAllCardIds() + + for i = #allCardIds, 1, -1 do + if Fk:getCardById(allCardIds[i]).is_derived then + local id = allCardIds[i] + table.removeOne(allCardIds, id) + table.insert(room.void, id) + room:setCardArea(id, Card.Void, nil) + end + end + table.shuffle(allCardIds) room.draw_pile = allCardIds for _, id in ipairs(room.draw_pile) do @@ -291,7 +301,7 @@ function GameLogic:prepareForStart() end function GameLogic:action() - self:trigger(fk.GameStart) + self:trigger(fk.GamePrepared) local room = self.room execGameEvent(GameEvent.DrawInitial) diff --git a/lua/server/room.lua b/lua/server/room.lua index 28f81639..d3941e78 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -1572,6 +1572,82 @@ function Room:askForCustomDialog(player, focustxt, qmlPath, extra_data) }) end +---@field player ServerPlayer +---@field targetOne ServerPlayer +---@field targetTwo ServerPlayer +---@field skillName string +---@return cardId +function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName) + local cards = {} + local cardsPosition = {} + for _, equipId in ipairs(targetOne:getCardIds(Player.Equip)) do + if targetOne:canMoveCardInBoardTo(targetTwo, equipId) then + table.insert(cards, equipId) + end + end + for _, equipId in ipairs(targetTwo:getCardIds(Player.Equip)) do + if targetTwo:canMoveCardInBoardTo(targetOne, equipId) then + table.insert(cards, equipId) + end + end + + if #cards > 0 then + table.sort(cards, function(prev, next) + local prevSubType = Fk:getCardById(prev).sub_type + local nextSubType = Fk:getCardById(next).sub_type + + return prevSubType < nextSubType + end) + + for _, id in ipairs(cards) do + table.insert(cardsPosition, self:getCardOwner(id) == targetOne and 0 or 1) + end + end + + for _, trickId in ipairs(targetOne:getCardIds(Player.Judge)) do + if targetOne:canMoveCardInBoardTo(targetTwo, trickId) then + table.insert(cards, trickId) + table.insert(cardsPosition, 0) + end + end + for _, trickId in ipairs(targetTwo:getCardIds(Player.Judge)) do + if targetTwo:canMoveCardInBoardTo(targetOne, trickId) then + table.insert(cards, trickId) + table.insert(cardsPosition, 1) + end + end + + if #cards == 0 then + return + end + + local firstGeneralName = targetOne.general + (targetOne.deputyGeneral ~= "" and ("/" .. targetOne.deputyGeneral) or "") + local secGeneralName = targetTwo.general + (targetTwo.deputyGeneral ~= "" and ("/" .. targetTwo.deputyGeneral) or "") + + local data = { cards = cards, cardsPosition = cardsPosition, generalNames = { firstGeneralName, secGeneralName } } + local command = "AskForMoveCardInBoard" + self:notifyMoveFocus(player, command) + local result = self:doRequest(player, command, json.encode(data)) + + if result == "" then + local randomIndex = math.random(1, #cards) + result = { cardId = cards[randomIndex], pos = cardsPosition[randomIndex] } + else + result = json.decode(result) + end + + local cardToMove = Fk:getCardById(result.cardId) + self:moveCardTo( + cardToMove, + cardToMove.type == Card.TypeEquip and Player.Equip or Player.Judge, + result.pos == 0 and targetTwo or targetOne, + fk.ReasonPut, + skillName, + nil, + true + ) +end + ------------------------------------------------------------------------ -- 使用牌 ------------------------------------------------------------------------ @@ -1712,6 +1788,7 @@ function Room:doCardUseEffect(cardUseEvent) local realCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing }) + self.logic:trigger(fk.BeforeCardUseEffect, cardUseEvent.from, cardUseEvent) -- If using Equip or Delayed trick, move them to the area and return if cardUseEvent.card.type == Card.TypeEquip then if #realCardIds == 0 then diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 7eca40af..36bf646e 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -343,6 +343,8 @@ function ServerPlayer:showCards(cards) from = self.id, cards = cards, }) + + room.logic:trigger(fk.CardShown, self, { cardIds = cards }) end ---@param from_phase Phase diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index 4573b885..cdfc7a16 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -28,6 +28,7 @@ ---@field public moveVisible boolean|null ---@field public specialName string|null ---@field public specialVisible boolean|null +---@field public drawPilePosition number|null @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底 ---@class PindianResult ---@field public toCard Card diff --git a/packages/standard/aux_skills.lua b/packages/standard/aux_skills.lua index 7b86f45e..e5387e9d 100644 --- a/packages/standard/aux_skills.lua +++ b/packages/standard/aux_skills.lua @@ -78,31 +78,9 @@ local maxCardsSkill = fk.CreateMaxCardsSkill{ end, } -local moveTokenSkill = fk.CreateTriggerSkill{ - name = "move_token_skill", - global = true, - - refresh_events = {fk.GameStart}, --refresh优先于on_use,不要在正常的游戏开始发牌技能refresh中拿牌 - can_refresh = function(self, event, target, player, data) - return player.seat == 1 - end, - on_refresh = function(self, event, target, player, data) - local room = player.room - for i = #room.draw_pile, 1, -1 do - if Fk:getCardById(room.draw_pile[i]).name[1] == "&" then - local id = room.draw_pile[i] - table.removeOne(room.draw_pile, id) - table.insert(room.void, id) - room:setCardArea(id, Card.Void, nil) - end - end - end, -} - AuxSkills = { discardSkill, chooseCardsSkill, choosePlayersSkill, maxCardsSkill, - moveTokenSkill, } diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index e6ce5978..7a87a429 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -41,7 +41,7 @@ end GameRule = fk.CreateTriggerSkill{ name = "game_rule", events = { - fk.GameStart, + fk.GamePrepared, fk.AskForPeaches, fk.AskForPeachesDone, fk.GameOverJudge, fk.BuryVictim, }, @@ -58,7 +58,7 @@ GameRule = fk.CreateTriggerSkill{ return false end - if event == fk.GameStart then + if event == fk.GamePrepared then room:setTag("FirstRound", true) room:setTag("RoundCount", 0) return false diff --git a/packages/standard_cards/i18n/zh_CN.lua b/packages/standard_cards/i18n/zh_CN.lua index 868acace..93f58b21 100644 --- a/packages/standard_cards/i18n/zh_CN.lua +++ b/packages/standard_cards/i18n/zh_CN.lua @@ -15,6 +15,17 @@ Fk:loadTranslationTable{ ["club"] = "梅花", ["diamond"] = "方块", + ["basic_char"] = "基", + ["trick_char"] = "锦", + ["equip_char"] = "装", + + ["weapon"] = "武器牌", + ["armor"] = "防具牌", + ["defensive_horse"] = "防御坐骑牌", + ["offensive_horse"] = "进攻坐骑牌", + ["treasure"] = "宝物牌", + ["delayed_trick"] = "延时类锦囊牌", + ["slash"] = "杀", [":slash"] = "基本牌
时机:出牌阶段
目标:攻击范围内的一名其他角色
效果:对目标角色造成1点伤害。", ["#slash-jink"] = "%src 对你使用了杀,请使用 %arg 张闪",