From cc0228dc0365198b6fc6ca14c912544f8bfaf954 Mon Sep 17 00:00:00 2001 From: notify Date: Wed, 2 Aug 2023 21:40:00 +0800 Subject: [PATCH] Dev (#233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 以不存在的游戏模式开房时,自动替换成身份局 - 烧条满3管直接踢 - 游戏结束时掉线玩家(可能故意杀后台逃跑的)会受到逃跑惩罚 - 修git闪退的bug - 关于页面补全作者信息 - 增加重载配置文件的shell命令 - 禁将方案切换 --- Fk/Config.qml | 10 ++ Fk/LobbyElement/BanGeneralSetting.qml | 126 ++++++++++++++++++++++++++ Fk/LobbyElement/EditProfile.qml | 4 + Fk/Pages/PackageManage.qml | 17 +++- Fk/Pages/Replay.qml | 3 +- lua/client/i18n/zh_CN.lua | 14 ++- lua/server/room.lua | 4 + lua/server/serverplayer.lua | 9 ++ src/core/packman.cpp | 30 +++++- src/server/room.cpp | 3 + src/server/server.cpp | 14 ++- src/server/shell.cpp | 13 ++- src/server/shell.h | 1 + src/swig/server.i | 6 ++ src/ui/qmlbackend.cpp | 4 + src/ui/qmlbackend.h | 1 + 16 files changed, 246 insertions(+), 13 deletions(-) create mode 100644 Fk/LobbyElement/BanGeneralSetting.qml diff --git a/Fk/Config.qml b/Fk/Config.qml index 8609754d..933891c8 100644 --- a/Fk/Config.qml +++ b/Fk/Config.qml @@ -24,6 +24,8 @@ QtObject { property bool disableMsgAudio property bool hideUseless property var disabledGenerals: [] + property var disableGeneralSchemes: [] + property int disableSchemeIdx: 0 property int preferredTimeout property int preferredLuckTime @@ -43,6 +45,10 @@ QtObject { property bool replaying: false property var blockedUsers: [] + onDisabledGeneralsChanged: { + disableGeneralSchemes[disableSchemeIdx] = disabledGenerals; + } + function loadConf() { conf = JSON.parse(Backend.loadConf()); winX = conf.winX ?? 100; @@ -67,6 +73,8 @@ QtObject { preferredTimeout = conf.preferredTimeout ?? 15; preferredLuckTime = conf.preferredLuckTime ?? 0; disabledGenerals = conf.disabledGenerals ?? []; + disableGeneralSchemes = conf.disableGeneralSchemes ?? [ disabledGenerals ]; + disableSchemeIdx = conf.disableSchemeIdx ?? 0; } function saveConf() { @@ -92,6 +100,8 @@ QtObject { conf.preferredTimeout = preferredTimeout; conf.preferredLuckTime = preferredLuckTime; conf.disabledGenerals = disabledGenerals; + conf.disableGeneralSchemes = disableGeneralSchemes; + conf.disableSchemeIdx = disableSchemeIdx; Backend.saveConf(JSON.stringify(conf, undefined, 2)); } diff --git a/Fk/LobbyElement/BanGeneralSetting.qml b/Fk/LobbyElement/BanGeneralSetting.qml new file mode 100644 index 00000000..6a617cd3 --- /dev/null +++ b/Fk/LobbyElement/BanGeneralSetting.qml @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + clip: true + + ColumnLayout { + anchors.fill: parent + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: "禁将方案" + } + ComboBox { + id: banCombo + textRole: "name" + model: ListModel { + id: banComboList + } + + onCurrentIndexChanged: { + config.disableSchemeIdx = currentIndex; + config.disabledGenerals = config.disableGeneralSchemes[currentIndex]; + } + } + + Button { + text: "新建" + onClicked: { + const i = config.disableGeneralSchemes.length; + banComboList.append({ + name: "方案" + (i + 1), + }); + config.disableGeneralSchemes.push([]); + } + } + + Button { + text: "清空" + onClicked: { + config.disabledGenerals = []; + } + } + } + + Text { + Layout.fillWidth: true + Layout.margins: 8 + wrapMode: Text.WrapAnywhere + text: "导出键会将这个方案的内容复制到剪贴板中;" + + "导入键会自动读取剪贴板,若可以导入则导入,不能导入则报错。" + } + + RowLayout { + Button { + text: "导出" + onClicked: { + Backend.copyToClipboard(JSON.stringify(config.disabledGenerals)); + toast.show("该禁将方案已经复制到剪贴板。"); + } + } + + Button { + text: "导入" + onClicked: { + const str = Backend.readClipboard(); + let data; + try { + data = JSON.parse(str); + } catch (e) { + toast.show("导入失败:不是合法的JSON字符串。"); + return; + } + if (!data instanceof Array) { + toast.show("导入失败:数据格式不对。"); + return; + } + for (let e of data) { + if (!(typeof e === "string" && Backend.translate(e) !== e)) { + toast.show("导入失败:含有未知的武将。"); + return; + } + } + config.disabledGenerals = data; + toast.show("导入禁将方案成功。"); + } + } + } + + GridView { + id: listView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + cellWidth: width / 2 + cellHeight: 24 + model: config.disabledGenerals + delegate: Text { + width: listView.width + text: { + const prefix = modelData.split("__")[0]; + let name = Backend.translate(modelData); + if (prefix !== modelData) { + name += (" (" + Backend.translate(prefix) + ")"); + } + return name; + } + font.pixelSize: 16 + } + } + } + + Component.onCompleted: { + for (let i = 0; i < config.disableGeneralSchemes.length; i++) { + banComboList.append({ + name: "方案" + (i + 1), + }); + } + banCombo.currentIndex = config.disableSchemeIdx; + } +} diff --git a/Fk/LobbyElement/EditProfile.qml b/Fk/LobbyElement/EditProfile.qml index 2fb36e18..0acbeab0 100644 --- a/Fk/LobbyElement/EditProfile.qml +++ b/Fk/LobbyElement/EditProfile.qml @@ -24,6 +24,9 @@ Item { TabButton { text: Backend.translate("Audio Settings") } + TabButton { + text: Backend.translate("Ban General Settings") + } } SwipeView { @@ -36,5 +39,6 @@ Item { UserInfo {} BGSetting {} AudioSetting {} + BanGeneralSetting {} } } diff --git a/Fk/Pages/PackageManage.qml b/Fk/Pages/PackageManage.qml index 39b53abc..319b1a24 100644 --- a/Fk/Pages/PackageManage.qml +++ b/Fk/Pages/PackageManage.qml @@ -106,10 +106,12 @@ Item { onClicked: { if (pkgEnabled === "0") { Pacman.enablePack(pkgName); + pkgEnabled = "1"; } else { Pacman.disablePack(pkgName); + pkgEnabled = "0"; } - updatePackageList(); + // updatePackageList(); } } @@ -120,7 +122,15 @@ Item { anchors.rightMargin: 8 onClicked: { Pacman.upgradePack(pkgName); - updatePackageList(); + // updatePackageList(); + const data = JSON.parse(Pacman.listPackages()); + const e = data[index]; + packageModel.set(index, { + pkgName: e.name, + pkgURL: e.url, + pkgVersion: e.hash.substring(0, 8), + pkgEnabled: e.enabled + }); } } @@ -131,7 +141,8 @@ Item { anchors.rightMargin: 8 onClicked: { Pacman.removePack(pkgName); - updatePackageList(); + // updatePackageList(); + packageModel.remove(index); } } diff --git a/Fk/Pages/Replay.qml b/Fk/Pages/Replay.qml index 81b461b0..6f2742bf 100644 --- a/Fk/Pages/Replay.qml +++ b/Fk/Pages/Replay.qml @@ -38,8 +38,7 @@ Item { width: parent.width height: parent.height - bar.height anchors.top: bar.bottom - color: "snow" - opacity: 0.75 + color: "#A0EFEFEF" clip: true ListView { diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 1e07e362..69d5be51 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -24,6 +24,7 @@ Fk:loadTranslationTable{ ["Audio Settings"] = "音频", ["Disable message audio"] = "禁用聊天语音", ["Hide unselectable cards"] = "下移不可选卡牌", + ["Ban General Settings"] = "禁将", ["Back"] = "返回", ["Refresh Room List"] = "刷新房间列表", @@ -84,13 +85,24 @@ Fk:loadTranslationTable{ 以便于DIY为首要目的的开源三国杀游戏。 项目链接: https://github.com/Notify-ctrl/FreeKill + +--- + +作者: Notify Ho-spair + +开发者: RalphR Nyutanislavsky xxyheaven 妖梦厨 + +贡献者: 假象 deepskybird 板蓝根 + +鸣谢: Mogara + ]], ["about_qt_description"] = [[ # 关于Qt Qt是一个C++图形界面应用程序开发框架,拥有强大的跨平台能力以及易于使用的API。 -本程序使用Qt 6.2+,主要利用QtQuick开发UI,同时也使用Qt的网络库开发服务端程序。 +本程序使用Qt 6.4,主要利用QtQuick开发UI,同时也使用Qt的网络库开发服务端程序。 官网: https://www.qt.io ]], diff --git a/lua/server/room.lua b/lua/server/room.lua index 7dc58ac2..0ff2c524 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -94,6 +94,10 @@ function Room:initialize(_room) self.settings = json.decode(self.room:settings()) self.disabled_packs = self.settings.disabledPack + if not Fk.game_modes[self.settings.gameMode] then + self.settings.gameMode = "aaa_role_mode" + end + table.insertTable(self.disabled_packs, Fk.game_mode_disabled[self.settings.gameMode]) self.disabled_generals = self.settings.disabledGenerals end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 50284ed2..dd5b61e8 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -14,6 +14,7 @@ ---@field public phase_state table[] ---@field public phase_index integer ---@field public role_shown boolean +---@field private _timewaste_count integer ---@field public ai AI ---@field public ai_data any local ServerPlayer = Player:subclass("ServerPlayer") @@ -34,6 +35,7 @@ function ServerPlayer:initialize(_self) self.reply_cancel = false self.phases = {} self.skipped_phases = {} + self._timewaste_count = 0 self.ai = RandomAI:new(self) end @@ -113,12 +115,19 @@ local function _waitForReply(player, timeout) player.serverplayer:setThinking(true) result = player.serverplayer:waitForReply(0) if result ~= "__notready" then + player._timewaste_count = 0 player.serverplayer:setThinking(false) return result end local rest = timeout * 1000 - (os.getms() - start) / 1000 if timeout and rest <= 0 then + player._timewaste_count = player._timewaste_count + 1 player.serverplayer:setThinking(false) + + if player._timewaste_count >= 3 then + player.serverplayer:emitKick() + end + return "" end diff --git a/src/core/packman.cpp b/src/core/packman.cpp index ece2490e..89c69474 100644 --- a/src/core/packman.cpp +++ b/src/core/packman.cpp @@ -16,6 +16,20 @@ PackMan *Pacman; PackMan::PackMan(QObject *parent) : QObject(parent) { git_libgit2_init(); db = OpenDatabase("./packages/packages.db", "./packages/init.sql"); + + QDir d("packages"); + foreach (auto e, SelectFromDatabase(db, "SELECT name, enabled FROM packages;")) { + auto obj = e.toObject(); + auto pack = obj["name"].toString(); + auto enabled = obj["enabled"].toString().toInt() == 1; + + if (enabled) { + d.rename(pack + ".disabled", pack); + } else { + d.rename(pack, pack + ".disabled"); + } + } + #ifdef Q_OS_ANDROID git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, "./certs"); #endif @@ -128,16 +142,24 @@ void PackMan::enablePack(const QString &pack) { ExecSQL( db, QString("UPDATE packages SET enabled = 1 WHERE name = '%1';").arg(pack)); - QDir d(QString("packages")); - d.rename(pack + ".disabled", pack); + QDir d("packages"); + int i = 0; + while (!d.rename(pack + ".disabled", pack) && i < 3) { + QThread::currentThread()->msleep(1); + i++; + } } void PackMan::disablePack(const QString &pack) { ExecSQL( db, QString("UPDATE packages SET enabled = 0 WHERE name = '%1';").arg(pack)); - QDir d(QString("packages")); - d.rename(pack, pack + ".disabled"); + QDir d("packages"); + int i = 0; + while (!d.rename(pack, pack + ".disabled") && i < 3) { + QThread::currentThread()->msleep(1); + i++; + } } void PackMan::updatePack(const QString &pack) { diff --git a/src/server/room.cpp b/src/server/room.cpp index b859b93d..9482b421 100644 --- a/src/server/room.cpp +++ b/src/server/room.cpp @@ -535,6 +535,9 @@ void Room::gameOver() { // 清理所有状态不是“在线”的玩家 foreach (ServerPlayer *p, players) { if (p->getState() != Player::Online) { + if (p->getState() == Player::Offline) { + server->temporarilyBan(p->getId()); + } p->deleteLater(); } } diff --git a/src/server/server.cpp b/src/server/server.cpp index 748f8836..28fefab1 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -636,9 +636,19 @@ void Server::temporarilyBan(int playerId) { if (!player) return; auto socket = player->getSocket(); - if (!socket) return; + QString addr; + if (!socket) { + QString sql_find = QString("SELECT * FROM userinfo \ + WHERE id=%1;").arg(playerId); + auto result = SelectFromDatabase(db, sql_find); + if (result.isEmpty()) + return; - auto addr = socket->peerAddress(); + auto obj = result[0].toObject(); + addr = obj["lastLoginIp"].toString(); + } else { + addr = socket->peerAddress(); + } temp_banlist.append(addr); auto time = getConfig("tempBanTime").toInt(); diff --git a/src/server/shell.cpp b/src/server/shell.cpp index addd182e..72d372f4 100644 --- a/src/server/shell.cpp +++ b/src/server/shell.cpp @@ -25,8 +25,10 @@ void Shell::helpCommand(QStringList &) { HELP_MSG("%s: Display this help message.", "help"); HELP_MSG("%s: Shut down the server.", "quit"); + HELP_MSG("%s: Crash the server. Useful when encounter dead loop.", "crash"); HELP_MSG("%s: List all online players.", "lsplayer"); HELP_MSG("%s: List all running rooms.", "lsroom"); + HELP_MSG("%s: Reload server config file.", "reloadconf"); HELP_MSG("%s: Kick a player by his .", "kick"); HELP_MSG("%s: Broadcast message.", "msg"); HELP_MSG("%s: Ban 1 or more accounts, IP, UUID by their .", "ban"); @@ -349,6 +351,11 @@ void Shell::unbanUuidCommand(QStringList &list) { } } +void Shell::reloadConfCommand(QStringList &) { + ServerInstance->readConfig(); + qInfo("Reloaded server config file."); +} + Shell::Shell() { setObjectName("Shell"); signal(SIGINT, sigintHandler); @@ -373,6 +380,7 @@ Shell::Shell() { handlers["unbanip"] = &Shell::unbanipCommand; handlers["banuuid"] = &Shell::banUuidCommand; handlers["unbanuuid"] = &Shell::unbanUuidCommand; + handlers["reloadconf"] = &Shell::reloadConfCommand; } handler_map = handlers; } @@ -384,7 +392,8 @@ void Shell::run() { "This is free software, and you are welcome to redistribute it under\n"); printf("certain conditions; For more information visit " "http://www.gnu.org/licenses.\n\n"); - printf("This is server cli. Enter \"help\" for usage hints.\n"); + + printf("[v%s] This is server cli. Enter \"help\" for usage hints.\n", FK_VERSION); while (true) { char *bytes = readline("fk> "); @@ -394,6 +403,8 @@ void Shell::run() { return; } + qInfo("Running command: \"%s\"", bytes); + if (!strcmp(bytes, "crash")) { qFatal("Crashing."); // should dump core return; diff --git a/src/server/shell.h b/src/server/shell.h index a1c0b8dc..7d044417 100644 --- a/src/server/shell.h +++ b/src/server/shell.h @@ -31,6 +31,7 @@ private: void unbanCommand(QStringList &); void unbanipCommand(QStringList &); void unbanUuidCommand(QStringList &); + void reloadConfCommand(QStringList &); }; #endif diff --git a/src/swig/server.i b/src/swig/server.i index f352d95d..3b4b1597 100644 --- a/src/swig/server.i +++ b/src/swig/server.i @@ -72,3 +72,9 @@ public: bool thinking(); void setThinking(bool t); }; + +%extend ServerPlayer { + void emitKick() { + emit $self->kicked(); + } +} diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 580c8982..525d02b7 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -321,6 +321,10 @@ void QmlBackend::copyToClipboard(const QString &s) { QGuiApplication::clipboard()->setText(s); } +QString QmlBackend::readClipboard() { + return QGuiApplication::clipboard()->text(); +} + void QmlBackend::setAESKey(const QString &key) { aes_key = key; } QString QmlBackend::getAESKey() const { return aes_key; } diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index c957994e..9c167248 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -50,6 +50,7 @@ public: Q_INVOKABLE void playSound(const QString &name, int index = 0); Q_INVOKABLE void copyToClipboard(const QString &s); + Q_INVOKABLE QString readClipboard(); Q_INVOKABLE void setAESKey(const QString &key); Q_INVOKABLE QString getAESKey() const;