diff --git a/Fk/Cheat/GeneralDetail.qml b/Fk/Cheat/GeneralDetail.qml index 5514a161..a6df28ba 100644 --- a/Fk/Cheat/GeneralDetail.qml +++ b/Fk/Cheat/GeneralDetail.qml @@ -56,11 +56,8 @@ Flickable { skillDesc.append(ret) } data.skill.forEach(t => { - skillDesc.append("" + luatr(t.name) + ": " + t.description) - }); - data.related_skill.forEach(t => { - skillDesc.append("" + luatr(t.name) + - ": " + t.description + "") + skillDesc.append((t.is_related_skill ? "" : "") + luatr(t.name) + + ": " + t.description + (t.is_related_skill ? "" : "")); }); skillDesc.append("\n"); }); diff --git a/Fk/Cheat/PlayerDetail.qml b/Fk/Cheat/PlayerDetail.qml index 3a3a442f..41184d69 100644 --- a/Fk/Cheat/PlayerDetail.qml +++ b/Fk/Cheat/PlayerDetail.qml @@ -212,5 +212,17 @@ Flickable { skillDesc.append("--------------------"); skillDesc.append("" + luatr(t.name) + ": " + luatr(":" + t.name)); }); + + const judge = leval( + `(function() + local p = ClientInstance:getPlayerById(${id}) + return p.player_cards[Player.Judge] + end)()` + ); + judge.forEach(cid => { + const t = lcall("GetCardData", cid); + skillDesc.append("--------------------"); + skillDesc.append("" + luatr(t.name) + ": " + luatr(":" + t.name)); + }); } } diff --git a/Fk/Common/LogEdit.qml b/Fk/Common/LogEdit.qml index 1e0e2dfc..9fb02d74 100644 --- a/Fk/Common/LogEdit.qml +++ b/Fk/Common/LogEdit.qml @@ -40,7 +40,7 @@ ListView { } Button { - text: "Return to Bottom" + text: luatr("Return to Bottom") visible: root.currentIndex !== logModel.count - 1 onClicked: root.currentIndex = logModel.count - 1; } diff --git a/Fk/Config.qml b/Fk/Config.qml index cc8babcc..3542d309 100644 --- a/Fk/Config.qml +++ b/Fk/Config.qml @@ -20,12 +20,14 @@ QtObject { property string preferedMode property int preferedPlayerNum property int preferredGeneralNum + property var preferredFilter property string ladyImg property real bgmVolume property bool disableMsgAudio property bool hideUseless property bool hideObserverChatter property bool rotateTableCard + property bool hidePresents // property list disabledGenerals: [] // property list disableGeneralSchemes: [] // property int disableSchemeIdx: 0 @@ -126,6 +128,13 @@ QtObject { preferedMode = conf.preferedMode ?? "aaa_role_mode"; preferedPlayerNum = conf.preferedPlayerNum ?? 2; preferredGeneralNum = conf.preferredGeneralNum ?? 3; + preferredFilter = conf.preferredFilter ?? { + name: "", // 房间名 + id: "", // 房间ID + modes : [], // 游戏模式 + full : 2, // 满员,0满,1未满,2不限 + hasPassword : 2, // 密码,0有,1无,2不限 + }; ladyImg = conf.ladyImg ?? AppPath + "/image/lady"; Backend.volume = conf.effectVolume ?? 50.; bgmVolume = conf.bgmVolume ?? 50.; @@ -133,6 +142,7 @@ QtObject { hideUseless = conf.hideUseless ?? false; hideObserverChatter = conf.hideObserverChatter ?? false; rotateTableCard = conf.rotateTableCard ?? false; + hidePresents = conf.hidePresents ?? false; preferredTimeout = conf.preferredTimeout ?? 15; preferredLuckTime = conf.preferredLuckTime ?? 0; firstRun = conf.firstRun ?? true; @@ -165,6 +175,7 @@ QtObject { // conf.disabledPack = disabledPack; conf.preferedMode = preferedMode; conf.preferedPlayerNum = preferedPlayerNum; + conf.preferredFilter = preferredFilter; conf.ladyImg = ladyImg; conf.preferredGeneralNum = preferredGeneralNum; conf.effectVolume = Backend.volume; @@ -173,6 +184,7 @@ QtObject { conf.hideUseless = hideUseless; conf.hideObserverChatter = hideObserverChatter; conf.rotateTableCard = rotateTableCard; + conf.hidePresents = hidePresents; conf.preferredTimeout = preferredTimeout; conf.preferredLuckTime = preferredLuckTime; conf.firstRun = firstRun; diff --git a/Fk/LobbyElement/AudioSetting.qml b/Fk/LobbyElement/AudioSetting.qml index cd75a61b..d6f52249 100644 --- a/Fk/LobbyElement/AudioSetting.qml +++ b/Fk/LobbyElement/AudioSetting.qml @@ -70,5 +70,13 @@ ColumnLayout { } } + Switch { + text: luatr("Hide presents") + checked: config.hidePresents + onCheckedChanged: { + config.hidePresents = checked; + } + } + } } diff --git a/Fk/LobbyElement/FilterRoom.qml b/Fk/LobbyElement/FilterRoom.qml new file mode 100644 index 00000000..f310375f --- /dev/null +++ b/Fk/LobbyElement/FilterRoom.qml @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Flickable { + id: root + height: parent.height + width: layout.width + anchors.fill: parent + anchors.margins: 16 + clip: true + contentWidth: layout.width + contentHeight: layout.height + ScrollBar.vertical: ScrollBar {} + ScrollBar.horizontal: ScrollBar {} // considering long game mode name + + signal finished() + + ColumnLayout { + id: layout + anchors.top: parent.top + + Item { Layout.fillHeight: true } + + // roomId, roomName, gameMode, playerNum, capacity, hasPassword, outdated + + GridLayout { + columns: 2 + + // roomName + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: luatr("Room Name") + font.bold: true + font.pixelSize: 14 + } + TextField { + id: name + maximumLength: 64 + font.pixelSize: 18 + Layout.rightMargin: 16 + Layout.fillWidth: true + text: config.preferredFilter.name + } + } + + // roomId + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: luatr("Room ID") + font.bold: true + font.pixelSize: 14 + } + TextField { + id: id + maximumLength: 64 + font.pixelSize: 18 + Layout.rightMargin: 16 + Layout.fillWidth: true + text: config.preferredFilter.id + } + } + } + + // gameMode + ButtonGroup { + id: childModes + exclusive: false + checkState: parentModeBox.checkState + } + + CheckBox { + id: parentModeBox + text: luatr("Game Mode") + font.bold: true + checkState: childModes.checkState + } + GridLayout { + columns: 6 + + Repeater { + id: modes + model: ListModel { + id: gameModeList + } + + CheckBox { + text: name + checked: config.preferredFilter.modes.includes(name) + leftPadding: indicator.width + ButtonGroup.group: childModes + } + } + } + + RowLayout { + anchors.rightMargin: 8 + // spacing: 64 + // Layout.fillWidth: true + + // full + Column { + ButtonGroup { + id: childFull + exclusive: false + checkState: parentFullBox.checkState + } + + CheckBox { + id: parentFullBox + text: luatr("Room Fullness") + font.bold: true + checkState: childFull.checkState + } + + GridLayout { + columns: 6 + + Repeater { + id: fullStates + model: ["Full", "Not Full"] + + CheckBox { + text: luatr(modelData) + checked: config.preferredFilter.full === index + leftPadding: indicator.width + ButtonGroup.group: childFull + } + } + } + } + + // hasPassword + Column { + ButtonGroup { + id: childPw + exclusive: false + checkState: parentPwBox.checkState + } + + CheckBox { + id: parentPwBox + text: luatr("Room Password") + font.bold: true + checkState: childPw.checkState + } + + GridLayout { + columns: 6 + + Repeater { + id: pwStates + model: ["Has Password", "No Password"] + + CheckBox { + text: luatr(modelData) + checked: config.preferredFilter.hasPassword === index + leftPadding: indicator.width + ButtonGroup.group: childPw + } + } + } + } + + Button { + text: luatr("Clear") + onClicked: { + opTimer.start(); + config.preferredFilter = { + name: "", + id: "", + modes : [], + full : 2, + hasPassword : 2, + } + config.preferredFilterChanged(); + ClientInstance.notifyServer("RefreshRoomList", ""); + lobby_dialog.item.finished(); + } + } + + Button { + text: luatr("Filter") + // width: 200 + // enabled: !opTimer.running + onClicked: { + // opTimer.start(); + filterRoom(); + root.finished(); + } + } + } + + // capacity + /* + Column { + ButtonGroup { + id: childCapacity + exclusive: false + checkState: parentCapacityBox.checkState + } + + CheckBox { + id: parentCapacityBox + text: luatr("Room Capacity") + font.bold: true + checkState: childCapacity.checkState + } + + GridLayout { + columns: 6 + + Repeater { + id: capacityStates + model: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + + CheckBox { + text: modelData + checked: false + leftPadding: indicator.width + ButtonGroup.group: childCapacity + } + } + } + } + */ + + Component.onCompleted: { + const mode_data = lcall("GetGameModes"); + mode_data.forEach(d => { + gameModeList.append(d); + }); + } + } + function filterRoom() { + let f = config.preferredFilter; + + f.name = name.text; + f.id = id.text; + + // mode + let modeList = []; + if (parentModeBox.checkState === Qt.PartiallyChecked) { + for (let index = 0; index < modes.count; index++) { + var tCheckBox = modes.itemAt(index) + if (tCheckBox.checked) {modeList.push(tCheckBox.text)} + } + } + f.modes = modeList; + + f.full = parentFullBox.checkState === Qt.PartiallyChecked ? (fullStates.itemAt(0).checked ? 0 : 1) : 2; + f.hasPassword = parentPwBox.checkState === Qt.PartiallyChecked ? (pwStates.itemAt(0).checked ? 0 : 1) : 2; + + // capacity + /* + let capacityList = []; + if (parentCapacityBox.checkState === Qt.PartiallyChecked) { + for (let index = 0; index < capacityStates.count; index++) { + var nCheckBox = capacityStates.itemAt(index) + if (nCheckBox.checked) {capacityList.push(parseInt(nCheckBox.text))} + } + } + */ + + config.preferredFilterChanged(); + + for (let i = roomModel.count - 1; i >= 0; i--) { + const r = roomModel.get(i); + if ((name.text !== '' && !r.roomName.includes(name.text)) + || (id.text !== '' && !r.roomId.toString().includes(id.text)) + || (modeList.length > 0 && !modeList.includes(luatr(r.gameMode))) + || (f.full !== 2 && + (f.full === 0 ? r.playerNum < r.capacity : r.playerNum >= r.capacity)) + || (f.hasPassword !== 2 && + (f.hasPassword === 0 ? !r.hasPassword : r.hasPassword)) + // || (capacityList.length > 0 && !capacityList.includes(r.capacity)) + ) { + roomModel.remove(i); + } + } + } +} diff --git a/Fk/LobbyElement/RoomPackageSettings.qml b/Fk/LobbyElement/RoomPackageSettings.qml index 3b03385b..33256d96 100644 --- a/Fk/LobbyElement/RoomPackageSettings.qml +++ b/Fk/LobbyElement/RoomPackageSettings.qml @@ -22,9 +22,11 @@ Flickable { anchors.top: parent.top anchors.topMargin: 8 + /* Switch { text: luatr("Disable Extension") } + */ RowLayout { Text { diff --git a/Fk/LobbyElement/qmldir b/Fk/LobbyElement/qmldir index 8e3b854b..5e7c6c35 100644 --- a/Fk/LobbyElement/qmldir +++ b/Fk/LobbyElement/qmldir @@ -1,8 +1,10 @@ module Fk.LobbyElement AudioSetting 1.0 AudioSetting.qml +BanGeneralSetting 1.0 BanGeneralSetting.qml BGSetting 1.0 BGSetting.qml CreateRoom 1.0 CreateRoom.qml EditProfile 1.0 EditProfile.qml +FilterRoom 1.0 FilterRoom.qml PersonalSettings 1.0 PersonalSettings.qml RoomGeneralSettings 1.0 RoomGeneralSettings.qml RoomPackageSettings 1.0 RoomPackageSettings.qml diff --git a/Fk/Pages/CardsOverview.qml b/Fk/Pages/CardsOverview.qml index 8b8de045..a63a81c5 100644 --- a/Fk/Pages/CardsOverview.qml +++ b/Fk/Pages/CardsOverview.qml @@ -8,6 +8,7 @@ import Fk.RoomElement Item { id: root + objectName: "CardsOverview" property bool loaded: false @@ -196,6 +197,7 @@ Item { property var cards function updateCard() { const data = lcall("GetCardData", cid); + detailFlickable.contentY = 0; // 重置滚动条 const suitTable = { spade: "♠", heart: '', club: "♣", diamond: '', @@ -234,6 +236,7 @@ Item { } Flickable { + id: detailFlickable flickableDirection: Flickable.VerticalFlick contentHeight: detailLayout.height width: parent.width - 40 @@ -323,6 +326,7 @@ Item { Button { text: luatr("Quit") anchors.right: parent.right + visible: mainStack.currentItem.objectName === "CardsOverview" onClicked: { mainStack.pop(); } diff --git a/Fk/Pages/GeneralsOverview.qml b/Fk/Pages/GeneralsOverview.qml index 88a190a4..08cc9cfa 100644 --- a/Fk/Pages/GeneralsOverview.qml +++ b/Fk/Pages/GeneralsOverview.qml @@ -9,6 +9,7 @@ import "RoomLogic.js" as RoomLogic Item { id: root + objectName: "GeneralsOverview" property bool loaded: false property int stat: 0 // 0=normal 1=banPkg 2=banChara @@ -165,6 +166,7 @@ Item { return luatr("BanGeneral"); } enabled: stat !== 1 + visible: mainStack.currentItem.objectName === "GeneralsOverview" onClicked: { if (stat === 0) { stat = 2; @@ -182,6 +184,7 @@ Item { return luatr("BanPackage"); } enabled: stat !== 2 + visible: mainStack.currentItem.objectName === "GeneralsOverview" onClicked: { if (stat === 0) { stat = 1; @@ -194,6 +197,7 @@ Item { ToolButton { text: luatr("Quit") font.pixelSize: 20 + visible: mainStack.currentItem.objectName === "GeneralsOverview" onClicked: { mainStack.pop(); config.saveConf(); @@ -283,9 +287,9 @@ Item { const gdata = lcall("GetGeneralData", modelData); const pack = gdata.package; if (s.banPkg[pack]) { - if (s.banPkg[pack].includes(modelData)) return '启用'; + if (s.banPkg[pack].includes(modelData)) return luatr('Enable'); } else { - if (!!s.normalPkg[pack]?.includes(modelData)) return '禁'; + if (!!s.normalPkg[pack]?.includes(modelData)) return luatr('Prohibit'); } } anchors.centerIn: parent @@ -370,17 +374,9 @@ Item { Text { Layout.fillWidth: true text: { - const orig = '$' + name + (idx ? idx.toString() : ""); - const orig_trans = luatr(orig); - - // try general specific - const orig_g = '$' + name + '_' + detailGeneralCard.name + const orig = '$' + name + (specific ? '_' + detailGeneralCard.name : "") + (idx ? idx.toString() : ""); - const orig_g_trans = luatr(orig_g); - - if (orig_g_trans !== orig_g) { - return orig_g_trans; - } + const orig_trans = luatr(orig); if (orig_trans !== orig) { return orig_trans; @@ -396,10 +392,51 @@ Item { callbacks["LogEvent"]({ type: "PlaySkillSound", name: name, - general: detailGeneralCard.name, + general: specific ? detailGeneralCard.name : null, // 分化特别和一般 i: idx, }); } + + onPressAndHold: { + Backend.copyToClipboard('$' + name + (specific ? '_' + detailGeneralCard.name : "") + + (idx ? idx.toString() : "") + ':'); + toast.show(luatr("Audio Code Copy Success")); + } + + ToolButton { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + visible: parent.hovered + text: "⋮" + onClicked: { + if (skillAudioMenu.visible){ + skillAudioMenu.close(); + } else { + skillAudioMenu.open(); + } + } + Menu { + id: skillAudioMenu + MenuItem { + text: luatr("Copy Audio Code") + onTriggered: { + Backend.copyToClipboard('$' + name + (specific ? '_' + detailGeneralCard.name : "") + + (idx ? idx.toString() : "") + ':'); + toast.show(luatr("Audio Code Copy Success")); + } + } + MenuItem { + text: luatr("Copy Audio Text") + onTriggered: { + Backend.copyToClipboard(luatr('$' + name + (specific ? '_' + detailGeneralCard.name : "") + + (idx ? idx.toString() : ""))); + toast.show(luatr("Audio Text Copy Success")); + } + } + } + } } } @@ -427,7 +464,7 @@ Item { if (Backend.exists(fname)) { ret = true; - audioModel.append({ name: skill, idx: i }); + audioModel.append({ name: skill, idx: i, specific: true }); } else { if (i > 0) break; } @@ -445,7 +482,7 @@ Item { skill + (i !== 0 ? i.toString() : "") + ".mp3"; if (Backend.exists(fname)) { - audioModel.append({ name: skill, idx: i }); + audioModel.append({ name: skill, idx: i, specific: false}); } else { if (i > 0) break; } @@ -465,6 +502,7 @@ Item { function updateGeneral() { detailGeneralCard.name = general; + detailFlickable.contentY = 0; // 重置滚动条 const data = lcall("GetGeneralDetail", general); generalText.clear(); audioModel.clear(); @@ -479,14 +517,8 @@ Item { } data.skill.forEach(t => { - generalText.append("" + luatr(t.name) + - ": " + t.description); - - addSkillAudio(t.name); - }); - data.related_skill.forEach(t => { - generalText.append("" + luatr(t.name) + - ": " + t.description + ""); + generalText.append((t.is_related_skill ? "" : "") + luatr(t.name) + + ": " + t.description + (t.is_related_skill ? "" : "")); addSkillAudio(t.name); }); @@ -521,6 +553,8 @@ Item { wrapMode: Text.WordWrap textFormat: TextEdit.RichText font.pixelSize: 16 + lineHeight: 21 + lineHeightMode: Text.FixedHeight function trans(str) { const ret = luatr(str); if (ret === str) { @@ -530,13 +564,18 @@ Item { } text: { const general = generalDetail.general; - return [ - luatr(lcall("GetGeneralData", general).package), + const gdata = lcall("GetGeneralData", general); + let ret = [ + luatr(gdata.package), luatr("Title") + trans("#" + general), luatr("Designer") + trans("designer:" + general), luatr("Voice Actor") + trans("cv:" + general), luatr("Illustrator") + trans("illustrator:" + general), ].join("
"); + if (gdata.hidden) { + ret += "
" + luatr("Hidden General") + ""; + } + return ret; } } @@ -547,6 +586,7 @@ Item { Button { text: luatr("Set as Avatar") + visible: mainStack.currentItem.objectName === "GeneralsOverview" enabled: detailGeneralCard.name !== "" && !opTimer.running && Self.avatar !== detailGeneralCard.name onClicked: { @@ -562,6 +602,7 @@ Item { } Flickable { + id: detailFlickable flickableDirection: Flickable.VerticalFlick contentHeight: detailLayout.height width: parent.width - 40 - generalInfo.width @@ -628,6 +669,44 @@ Item { Backend.playSound("./packages/" + extension + "/audio/death/" + general); } + + onPressAndHold: { + Backend.copyToClipboard("$~" + generalDetail.general); + toast.show(luatr("Audio Code Copy Success")); + } + + ToolButton { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + visible: parent.hovered + text: "⋮" + onClicked: { + if (deathAudioMenu.visible){ + deathAudioMenu.close(); + } else { + deathAudioMenu.open(); + } + } + Menu { + id: deathAudioMenu + MenuItem { + text: luatr("Copy Audio Code") + onTriggered: { + Backend.copyToClipboard("$~" + generalDetail.general); + toast.show(luatr("Audio Code Copy Success")); + } + } + MenuItem { + text: luatr("Copy Audio Text") + onTriggered: { + Backend.copyToClipboard(luatr("~" + generalDetail.general)); + toast.show(luatr("Audio Text Copy Success")); + } + } + } + } } } } diff --git a/Fk/Pages/JoinServer.qml b/Fk/Pages/JoinServer.qml index 35582bb5..e950c958 100644 --- a/Fk/Pages/JoinServer.qml +++ b/Fk/Pages/JoinServer.qml @@ -102,6 +102,32 @@ Item { } } + ToolButton { + x: parent.width - 32 + y: parent.height / 2 - 8 + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + visible: !!favorite + text: "⋮" + onClicked: { + if (menu.visible){ + menu.close(); + } else { + menu.open(); + } + } + + Menu { + id: menu + MenuItem { + text: qsTr("Remove from Favorites") + onTriggered: { + removeFavorite(addr, port); + } + } + } + } + ColumnLayout { x: 6 height: parent.height @@ -125,7 +151,7 @@ Item { height: childrenRect.height width: serverList.width Text { - text: "已收藏服务器与公共服务器列表" + text: qsTr("List of Favorites and Public Servers") font.pixelSize: 18 x: 32; y: 8 } @@ -170,14 +196,14 @@ Item { id: addressEdit maximumLength: 64 Layout.fillWidth: true - placeholderText: "服务器地址" + placeholderText: qsTr("Server Address") text: selectedServer?.addr ?? "" } TextField { id: portEdit maximumLength: 6 Layout.fillWidth: true - placeholderText: "端口" + placeholderText: qsTr("Port") text: selectedServer?.port ?? "" } Flickable { @@ -199,21 +225,21 @@ Item { id: usernameEdit maximumLength: 32 Layout.fillWidth: true - placeholderText: "用户名" + placeholderText: qsTr("Username") text: selectedServer?.username ?? "" } TextField { id: passwordEdit maximumLength: 32 Layout.fillWidth: true - placeholderText: "密码" + placeholderText: qsTr("Password") passwordCharacter: "*" echoMode: TextInput.Password text: selectedServer?.password ?? "" } } Button { - text: "登录(首次登录自动注册)" + text: qsTr("LOGIN (Auto-registration)") Layout.fillWidth: true enabled: !!(addressEdit.text && portEdit.text && usernameEdit.text && passwordEdit.text) @@ -237,9 +263,9 @@ Item { } } Button { - text: "从收藏夹删除" + text: qsTr("Remove from Favorites") Layout.fillWidth: true - visible: !!(selectedServer?.favorite) + visible: false // !!(selectedServer?.favorite) // 暂时禁用 onClicked: { removeFavorite(selectedServer.addr, selectedServer.port); } diff --git a/Fk/Pages/Lobby.qml b/Fk/Pages/Lobby.qml index 63de27e9..0c3ae593 100644 --- a/Fk/Pages/Lobby.qml +++ b/Fk/Pages/Lobby.qml @@ -35,6 +35,7 @@ Item { text: roomName // color: outdated ? "gray" : "black" font.pixelSize: 16 + font.strikeout: outdated // elide: Label.ElideRight anchors.top: parent.top anchors.left: parent.left @@ -44,6 +45,7 @@ Item { Text { id: roomIdText text: luatr(gameMode) + ' #' + roomId + font.strikeout: outdated anchors.top: roomNameText.bottom anchors.left: roomNameText.left } @@ -119,7 +121,7 @@ Item { text: "在未来的版本中这一块区域将增加更多实用的功能,
"+ "例如直接查看房间的各种配置信息
"+ "以及更多与禁将有关的实用功能!"+ - "注:绿色按钮为试做型UI 后面优化" + "注:灰色按钮为试做型UI 后面优化" font.pixelSize: 18 } @@ -195,6 +197,13 @@ Item { ClientInstance.notifyServer("RefreshRoomList", ""); } } + Button { + text: luatr("Filter") + onClicked: { + lobby_dialog.sourceComponent = Qt.createComponent("../LobbyElement/FilterRoom.qml"); //roomFilterDialog; + lobby_drawer.open(); + } + } Button { text: luatr("Create Room") onClicked: { @@ -313,7 +322,7 @@ Item { } } Button { - text: luatr("Scenarios Overview") + text: luatr("Modes Overview") onClicked: { mainStack.push(mainWindow.modesOverviewPage); } diff --git a/Fk/Pages/MetroButton.qml b/Fk/Pages/MetroButton.qml index b602ce26..00412e15 100644 --- a/Fk/Pages/MetroButton.qml +++ b/Fk/Pages/MetroButton.qml @@ -13,6 +13,7 @@ Item { property int padding: 5 signal clicked + signal rightClicked id: button width: icon.width + title.implicitWidth + padding * 2 @@ -44,7 +45,19 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton gesturePolicy: TapHandler.WithinBounds - onTapped: if (parent.enabled) parent.clicked() + onTapped: (p, btn) => { + if (parent.enabled) { + if (btn === Qt.LeftButton || btn === Qt.NoButton) { + parent.clicked(); + } else if (btn === Qt.RightButton) { + parent.rightClicked(); + } + } + } + + onLongPressed: { + parent.rightClicked(); + } } HoverHandler { diff --git a/Fk/Pages/ModesOverview.qml b/Fk/Pages/ModesOverview.qml index d97c84a5..e6cf0034 100644 --- a/Fk/Pages/ModesOverview.qml +++ b/Fk/Pages/ModesOverview.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import QtQuick.Controls Item { + objectName: "ModesOverview" RowLayout { anchors.fill: parent spacing: 10 @@ -30,6 +31,7 @@ Item { TapHandler { onTapped: { listView.currentIndex = index; + detailFlickable.contentY = 0; // 重置滚动条 } } } @@ -40,6 +42,7 @@ Item { Layout.fillHeight: true color: "#88EEEEEE" Flickable { + id: detailFlickable width: parent.width - 16 height: parent.height - 16 anchors.centerIn: parent @@ -62,6 +65,7 @@ Item { Button { text: luatr("Quit") anchors.bottom: parent.bottom + visible: mainStack.currentItem.objectName === "ModesOverview" onClicked: { mainStack.pop(); } @@ -72,5 +76,6 @@ Item { for (let d of mode_data) { modeList.append(d); } + listView.currentIndex = 0; } } diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index 9928829a..dc12f962 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -100,11 +100,12 @@ Item { Menu { id: menuContainer y: menuButton.height - 12 - width: 100 + width: parent.width * 1.8 MenuItem { id: quitButton text: luatr("Quit") + icon.source: AppPath + "/image/modmaker/back" onClicked: { if (config.replaying) { Backend.controlReplayer("shutdown"); @@ -117,10 +118,57 @@ Item { } } + MenuItem { + id: volumeButton + text: luatr("Audio Settings") + icon.source: AppPath + "/image/button/tileicon/configure" + onClicked: { + volumeDialog.open(); + } + } + + Menu { + title: luatr("Overview") + icon.source: AppPath + "/image/button/tileicon/rule_summary" + icon.width: 24 + icon.height: 24 + icon.color: palette.windowText + MenuItem { + id: generalButton + text: luatr("Generals Overview") + icon.source: AppPath + "/image/button/tileicon/general_overview" + onClicked: { + overviewLoader.overviewType = "Generals"; + overviewDialog.open(); + overviewLoader.item.loadPackages(); + } + } + MenuItem { + id: cardslButton + text: luatr("Cards Overview") + icon.source: AppPath + "/image/button/tileicon/card_overview" + onClicked: { + overviewLoader.overviewType = "Cards"; + overviewDialog.open(); + overviewLoader.item.loadPackages(); + } + } + MenuItem { + id: modesButton + text: luatr("Modes Overview") + icon.source: AppPath + "/image/misc/paper" + onClicked: { + overviewLoader.overviewType = "Modes"; + overviewDialog.open(); + } + } + } + MenuItem { id: surrenderButton - enabled: !config.observing && !config.replaying + enabled: !config.observing && !config.replaying && isStarted text: luatr("Surrender") + icon.source: AppPath + "/image/misc/surrender" onClicked: { const photo = getPhoto(Self.id); if (isStarted && !(photo.dead && photo.rest <= 0)) { @@ -130,21 +178,13 @@ Item { luatr('Surrender is disabled in this mode'); } else { surrenderDialog.informativeText = surrenderCheck - .map(str => `${luatr(str.text)}(${str.passed ? '√' : '×'})`) + .map(str => `${luatr(str.text)}(${str.passed ? '✓' : '✗'})`) .join('
'); } surrenderDialog.open(); } } } - - MenuItem { - id: volumeButton - text: luatr("Audio Settings") - onClicked: { - volumeDialog.open(); - } - } } } @@ -468,7 +508,68 @@ Item { MetroButton { text: luatr("Sort Cards") textFont.pixelSize: 28 - onClicked: Logic.resortHandcards(); + onClicked: { + if (lcall("CanSortHandcards", Self.id)) { + let sortMethods = []; + for (let index = 0; index < sortMenuRepeater.count; index++) { + var tCheckBox = sortMenuRepeater.itemAt(index) + sortMethods.push(tCheckBox.checked) + } + Logic.sortHandcards(sortMethods); + } + } + + onRightClicked: { + if (sortMenu.visible) { + sortMenu.close(); + } else { + sortMenu.open(); + } + } + + Menu { + id: sortMenu + x: parent.width + y: -25 + width: parent.width * 2 + background: Rectangle { + color: "black" + border.width: 3 + border.color: "white" + opacity: 0.8 + } + + Repeater { + id: sortMenuRepeater + model: ["Sort by Type", "Sort by Number", "Sort by Suit"] + + CheckBox { + id: control + text: "" + luatr(modelData) + "" + checked: modelData === "Sort by Type" + font.pixelSize: 20 + + indicator: Rectangle { + implicitWidth: 26 + implicitHeight: 26 + x: control.leftPadding + y: control.height / 2 - height / 2 + radius: 3 + border.color: "white" + + Rectangle { + width: 14 + height: 14 + x: 6 + y: 6 + radius: 2 + color: control.down ? "#17a81a" : "#21be2b" + visible: control.checked + } + } + } + } + } } MetroButton { text: luatr("Chat") @@ -994,6 +1095,28 @@ Item { } } + Popup { + id: overviewDialog + width: realMainWin.width * 0.75 + height: realMainWin.height * 0.75 + anchors.centerIn: parent + background: Rectangle { + color: "#EEEEEEEE" + radius: 5 + border.color: "#A6967A" + border.width: 1 + } + Loader { + id: overviewLoader + property string overviewType: "Generals" + anchors.centerIn: parent + width: parent.width / mainWindow.scale + height: parent.height / mainWindow.scale + scale: mainWindow.scale + source: AppPath + "/Fk/Pages/" + overviewType + "Overview.qml" + } + } + GlowText { anchors.centerIn: dashboard visible: Logic.getPhoto(Self.id).rest > 0 && !config.observing @@ -1127,6 +1250,8 @@ Item { const general = luatr(data.general); if (msg.startsWith("!")) { + if (config.hidePresents) + return true; const splited = msg.split(":"); const type = splited[0].slice(1); switch (type) { diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index a49e60aa..605f628e 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -297,13 +297,27 @@ function moveCards(moves) { } } +const suitInteger = { + spade: 1, heart: 3, + club: 2, diamond: 4, +} - -function resortHandcards() { +function sortHandcards(sortMethods) { if (!dashboard.handcardArea.cards.length) { return; } + const cardType = sortMethods[0]; + const cardNum = sortMethods[1]; + const cardSuit = sortMethods[2]; + + if (!cardType && !cardNum && !cardSuit) { + return; + } + + let sortOutputs = []; + let sortedStatus = []; + const subtypeString2Number = { ["none"]: Card.SubtypeNone, ["delayed_trick"]: Card.SubtypeDelayedTrick, @@ -318,51 +332,61 @@ function resortHandcards() { return c.cid; }) - dashboard.handcardArea.cards.sort((prev, next) => { - if (prev.footnote === next.footnote) { - if (prev.type === next.type) { - const prevSubtypeNumber = subtypeString2Number[prev.subtype]; - const nextSubtypeNumber = subtypeString2Number[next.subtype]; - if (prevSubtypeNumber === nextSubtypeNumber) { - const splitedPrevName = prev.name.split('__'); - const prevTrueName = splitedPrevName[splitedPrevName.length - 1]; + let sortedByType = true; + let handcards + if (cardType) { + handcards = dashboard.handcardArea.cards.slice(0); + handcards.sort((prev, next) => { + if (prev.footnote === next.footnote) { + if (prev.type === next.type) { + const prevSubtypeNumber = subtypeString2Number[prev.subtype]; + const nextSubtypeNumber = subtypeString2Number[next.subtype]; + if (prevSubtypeNumber === nextSubtypeNumber) { + const splitedPrevName = prev.name.split('__'); + const prevTrueName = splitedPrevName[splitedPrevName.length - 1]; - const splitedNextName = next.name.split('__'); - const nextTrueName = splitedNextName[splitedNextName.length - 1]; - if (prevTrueName === nextTrueName) { - return prev.cid - next.cid; + const splitedNextName = next.name.split('__'); + const nextTrueName = splitedNextName[splitedNextName.length - 1]; + if (prevTrueName === nextTrueName) { + return prev.cid - next.cid; + } else { + return prevTrueName > nextTrueName ? -1 : 1; + } } else { - return prevTrueName > nextTrueName ? -1 : 1; + return prevSubtypeNumber - nextSubtypeNumber; } } else { - return prevSubtypeNumber - nextSubtypeNumber; + return prev.type - next.type; } } else { - return prev.type - next.type; + return prev.footnote > next.footnote ? 1 : -1; } - } else { - return prev.footnote > next.footnote ? 1 : -1; - } - }); + }); - let i = 0; - let resort = true; - dashboard.handcardArea.cards.forEach(c => { - if (hand[i] !== c.cid) { - resort = false; - return; - } - i++; - }) + // Check if the cards are sorted by type + let i = 0; + handcards.every(c => { + if (hand[i] !== c.cid) { + sortedByType = false; + return false; + } + i++; + return true; + }) + sortOutputs.push(handcards); + sortedStatus.push(sortedByType); + } - if (resort) { - dashboard.handcardArea.cards.sort((prev, next) => { + let sortedByNum = true; + if (cardNum) { + handcards = dashboard.handcardArea.cards.slice(0); + handcards.sort((prev, next) => { if (prev.footnote === next.footnote) { - if (prev.number === next.number) { // 按点数排 - if (prev.suit === next.suit) { + if (prev.number === next.number) { + if (suitInteger[prev.suit] === suitInteger[next.suit]) { return prev.cid - next.cid; } else { - return prev.suit - next.suit; + return suitInteger[prev.suit] - suitInteger[next.suit]; } } else { return prev.number - next.number; @@ -371,8 +395,61 @@ function resortHandcards() { return prev.footnote > next.footnote ? 1 : -1; } }); + + let i = 0; + handcards.every(c => { + if (hand[i] !== c.cid) { + sortedByNum = false; + return false; + } + i++; + return true; + }) + sortOutputs.push(handcards); + sortedStatus.push(sortedByNum); } + let sortedBySuit = true; + if (cardSuit) { + handcards = dashboard.handcardArea.cards.slice(0); + handcards.sort((prev, next) => { + if (prev.footnote === next.footnote) { + if (suitInteger[prev.suit] === suitInteger[next.suit]) { + if (prev.number === next.number) { + return prev.cid - next.cid; + } else { + return prev.number - next.number; + } + } else { + return suitInteger[prev.suit] - suitInteger[next.suit]; + } + } else { + return prev.footnote > next.footnote ? 1 : -1; + } + }); + + let i = 0; + handcards.every(c => { + if (hand[i] !== c.cid) { + sortedBySuit = false; + return false; + } + i++; + return true; + }) + sortOutputs.push(handcards); + sortedStatus.push(sortedBySuit); + } + let output + for (let i = 0; i < sortedStatus.length; i++) { + if (sortedStatus[i]) { + let j = i < sortedStatus.length - 1 ? i + 1 : 0; + output = sortOutputs[j]; + break; + } + } + if (!output) output = sortOutputs[0]; + dashboard.handcardArea.cards = output; dashboard.handcardArea.updateCardPosition(true); } @@ -933,6 +1010,18 @@ callbacks["UpdateCard"] = (j) => { card.setData(lcall("GetCardData", id)); } +callbacks["UpdateSkill"] = (j) => { + const all_skills = [roomScene.dashboard.skillButtons, roomScene.dashboard.notActiveButtons]; + for (const skills of all_skills) { + for (let i = 0; i < skills.count; i++) { + const item = skills.itemAt(i); + const dat = lcall("GetSkillStatus", item.orig); + item.locked = dat.locked; + item.times = dat.times; + } + } +} + callbacks["StartGame"] = (jsonData) => { roomScene.isStarted = true; diff --git a/Fk/RoomElement/ArrangeCardsBox.qml b/Fk/RoomElement/ArrangeCardsBox.qml index 66b937d9..5b31ed62 100644 --- a/Fk/RoomElement/ArrangeCardsBox.qml +++ b/Fk/RoomElement/ArrangeCardsBox.qml @@ -82,8 +82,7 @@ GraphicsBox { } Row { - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter + Layout.alignment: Qt.AlignHCenter spacing: 32 MetroButton { diff --git a/Fk/RoomElement/CardItem.qml b/Fk/RoomElement/CardItem.qml index d00a51a6..e1c6ccd2 100644 --- a/Fk/RoomElement/CardItem.qml +++ b/Fk/RoomElement/CardItem.qml @@ -69,7 +69,7 @@ Item { signal rightClicked() signal doubleClicked() signal thrown() - signal released() + signal released(var card) signal entered() signal exited() signal moveFinished() @@ -287,7 +287,7 @@ Item { onGrabChanged: (transtition, point) => { if (transtition !== PointerDevice.UngrabExclusive) return; - parent.released(); + parent.released(root); if (autoBack) goBackAnimation.start(); } diff --git a/Fk/RoomElement/Dashboard.qml b/Fk/RoomElement/Dashboard.qml index 6ab8b42c..e5199b19 100644 --- a/Fk/RoomElement/Dashboard.qml +++ b/Fk/RoomElement/Dashboard.qml @@ -17,6 +17,7 @@ RowLayout { property int selected_card: -1 property alias skillButtons: skillPanel.skill_buttons + property alias notActiveButtons: skillPanel.not_active_buttons property var expanded_piles: ({}) // name -> int[] property var extra_cards: [] diff --git a/Fk/RoomElement/GlowText.qml b/Fk/RoomElement/GlowText.qml index 16e4c3c8..59cda0ee 100644 --- a/Fk/RoomElement/GlowText.qml +++ b/Fk/RoomElement/GlowText.qml @@ -13,6 +13,7 @@ Item { property alias style: textItem.style property alias styleColor: textItem.styleColor property alias wrapMode: textItem.wrapMode + property alias elide: textItem.elide property alias lineHeight: textItem.lineHeight property alias glow: glowItem diff --git a/Fk/RoomElement/HandcardArea.qml b/Fk/RoomElement/HandcardArea.qml index 3cd0b53f..648445cc 100644 --- a/Fk/RoomElement/HandcardArea.qml +++ b/Fk/RoomElement/HandcardArea.qml @@ -7,6 +7,7 @@ Item { property alias cards: cardArea.cards property alias length: cardArea.length property var selectedCards: [] + property var movepos signal cardSelected(int cardId, bool selected) @@ -32,9 +33,11 @@ Item { function filterInputCard(card) { card.autoBack = true; - card.draggable = true; + card.draggable = lcall("CanSortHandcards", Self.id); card.selectable = false; card.clicked.connect(adjustCards); + card.released.connect(updateCardReleased); + card.xChanged.connect(updateCardDragging); } function remove(outputs) @@ -46,6 +49,8 @@ Item { card.draggable = false; card.selectable = false; card.selectedChanged.disconnect(adjustCards); + card.released.disconnect(updateCardReleased); + card.xChanged.disconnect(updateCardDragging); card.prohibitReason = ""; } return result; @@ -84,6 +89,49 @@ Item { } } + function updateCardDragging() + { + let _card, c; + let index; + for (index = 0; index < cards.length; index++) { + c = cards[index]; + if (c.dragging) { + _card = c; + break; + } + } + if (!_card) return; + _card.goBackAnim.stop(); + _card.opacity = 0.8 + + let card; + movepos = null; + for (let i = 0; i < cards.length; i++) { + card = cards[i]; + if (card.dragging) continue; + + if (card.x > _card.x) { + movepos = i - (index < i ? 1 : 0); + break; + } + } + if (movepos == null) { // 最右 + movepos = cards.length; + } + } + + function updateCardReleased(_card) + { + let i; + if (movepos != null) { + i = cards.indexOf(_card); + cards.splice(i, 1); + cards.splice(movepos, 0, _card); + movepos = null; + } + updateCardPosition(true); + } + function adjustCards() { area.updateCardPosition(true); diff --git a/Fk/RoomElement/Photo.qml b/Fk/RoomElement/Photo.qml index 9d7ebee4..cc918540 100644 --- a/Fk/RoomElement/Photo.qml +++ b/Fk/RoomElement/Photo.qml @@ -566,6 +566,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 2 + width: parent.width - role.width - hp.width - 20 font.pixelSize: 16 text: { @@ -574,7 +575,8 @@ Item { ret = luatr(" ") + ret; return ret; } - + elide: root.playerid === Self.id ? Text.ElideNone : Text.ElideMiddle + horizontalAlignment: Qt.AlignHCenter glow.radius: 8 } diff --git a/Fk/RoomElement/SkillArea.qml b/Fk/RoomElement/SkillArea.qml index 42c08542..1028d00f 100644 --- a/Fk/RoomElement/SkillArea.qml +++ b/Fk/RoomElement/SkillArea.qml @@ -7,6 +7,7 @@ Flickable { id: root property alias skill_buttons: skill_buttons property alias prelight_buttons: prelight_buttons + property alias not_active_buttons: not_active_buttons clip: true contentWidth: panel.width @@ -91,6 +92,7 @@ Flickable { columnSpacing: 2 rowSpacing: 2 Repeater { + id: not_active_buttons model: not_active_skills onItemAdded: parent.forceLayout() SkillButton { diff --git a/Fk/RoomElement/SkillButton.qml b/Fk/RoomElement/SkillButton.qml index 09316bfe..ba4138bd 100644 --- a/Fk/RoomElement/SkillButton.qml +++ b/Fk/RoomElement/SkillButton.qml @@ -11,6 +11,8 @@ Item { property string orig: "" property bool pressed: false property bool prelighted: false + property bool locked: false + property int times: -1 onEnabledChanged: { if (!enabled) @@ -86,6 +88,76 @@ Item { } } + Image { + source: AppPath + "/image/button/skill/locked.png" + scale: 0.8 + z: 2 + visible: root.locked + opacity: 0.8 + anchors.centerIn: parent + } + + Item { + width: 12 + height: 12 + visible: root.times > -1 + anchors.right: parent.right + anchors.rightMargin: type === "notactive" ? -13 : 5 + anchors.top: parent.top + anchors.topMargin: 5 + + Rectangle { + width: Math.max(15, 1.4 * count.contentWidth) + height: 15 + radius: width * 0.5 + x: (parent.width - width) / 2 + y: -1.5 + color: "transparent" + border.color: "#D2AD4A" + border.width: 1.1 + } + + Text { + id: count + anchors.centerIn: parent + font.pixelSize: 16 + font.family: fontLibian.name + font.bold: true + text: root.times + z: 1.5 + } + + Glow { + source: count + anchors.fill: count + color: "black" + spread: 0.3 + radius: 5 + } + + LinearGradient { + anchors.fill: count + z: 3 + source: count + gradient: Gradient { + GradientStop { + position: 0 + color: "#FEF7C2" + } + + GradientStop { + position: 0.8 + color: "#D2AD4A" + } + + GradientStop { + position: 1 + color: "#BE9878" + } + } + } + } + TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton onTapped: (p, btn) => { @@ -95,6 +167,10 @@ Item { skillDetail.open(); } } + + onLongPressed: { + skillDetail.open(); + } } Popup { diff --git a/Fk/RoomElement/TablePile.qml b/Fk/RoomElement/TablePile.qml index c6855d20..7de8d1ac 100644 --- a/Fk/RoomElement/TablePile.qml +++ b/Fk/RoomElement/TablePile.qml @@ -105,10 +105,10 @@ Item { for (i = 0; i < outputs.length; i++) { let exists = false; for (j = 0; j < result.length; j++) { - if (result[j].cid === outputs[i]) { - exists = true; - break; - } + if (result[j].cid === outputs[i]) { + exists = true; + break; + } } if (!exists) vanished.push(outputs[i]); diff --git a/image/misc/paper.png b/image/misc/paper.png new file mode 100644 index 00000000..6b636efc Binary files /dev/null and b/image/misc/paper.png differ diff --git a/image/misc/surrender.png b/image/misc/surrender.png new file mode 100644 index 00000000..852d9c50 Binary files /dev/null and b/image/misc/surrender.png differ diff --git a/include b/include deleted file mode 160000 index 4fd2070d..00000000 --- a/include +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4fd2070d099d1f967d1070d72beb0fae2cb6e4be diff --git a/lang/zh_CN.ts b/lang/zh_CN.ts index 07173139..9133b542 100644 --- a/lang/zh_CN.ts +++ b/lang/zh_CN.ts @@ -227,6 +227,10 @@ Edit Server 编辑服务器 + + List of Favorites and Public Servers + 已收藏服务器与公共服务器列表 + Refresh List 刷新列表 @@ -239,6 +243,14 @@ Go Back 返回 + + Server Address + 服务器地址 + + + Port + 端口 + @VersionMismatch @@ -286,6 +298,14 @@ Delete Server 删除服务器 + + LOGIN (Auto-registration) + 登录(首次登录自动注册) + + + Remove from Favorites + 从收藏夹删除 + @@ -549,8 +569,7 @@ tutor_msg_3 新月杀本身默认只含标准包!<br> 想要体验更多武将,就要通过联机获取!<br> - 在主界面点击“加入服务器” -> 添加服务器。<br> - (目前推荐的服务器IP是175.178.66.93) + 在主界面点击“加入服务器” -> 进入公共服务器或添加新服务器。 tutor_msg_4 @@ -561,8 +580,8 @@ tutor_msg_5 - 更多指引可以去查阅下载链接附送的pdf。 - 这些pdf都是由开发者们编写的,不仅能让你快速掌握游戏的深入玩法, + 更多指引可以去查阅新月之书:<a href="https://fkbook-all-in-one.readthedocs.io">https://fkbook-all-in-one.readthedocs.io</a><br>。 + 这些文档都是由开发者们编写的,不仅能让你快速掌握游戏的深入玩法, 还可以告诉你关于开设私服、制作拓展之类的知识。