From 9519d1b9a733eadc42cffde5d07241a6fb6c14da Mon Sep 17 00:00:00 2001 From: notify Date: Sun, 4 Jun 2023 19:31:44 +0800 Subject: [PATCH] Misc (#169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 扣上限心碎 - 进服维护的各种跟后端稳定性有关的代码 - 断线重连/旁观时候计入技能次数 - ban人和banip,相应的也有解禁 - 开房设置现在可以滑动 - 完善网络错误报错 - 现在开始游戏之前需要等待和所有人准备 - 指示掉线之人和走小道之人 - 掉线和走小道的人不再被AI接管 - 延时锦囊牌素材从拓展包找 - 拓展包管理界面UI优化,下载失败的包可以在管理拓展包中删除 --- .gitignore | 2 + Fk/Cheat/PlayerDetail.qml | 12 +- Fk/LobbyElement/RoomGeneralSettings.qml | 353 +++++++++--------- Fk/Pages/PackageManage.qml | 289 +++++++------- Fk/Pages/Room.qml | 66 +++- Fk/Pages/RoomLogic.js | 71 +++- Fk/PhotoElement/DelayedTrickArea.qml | 2 +- Fk/RoomElement/ChooseGeneralBox.qml | 3 +- Fk/RoomElement/GameOverBox.qml | 10 + Fk/RoomElement/Photo.qml | 10 +- Fk/skin-bank.js | 16 + audio/system/losemaxhp.mp3 | Bin 0 -> 18224 bytes .../{lightning.png => unknown.png} | Bin image/photo/notready.png | Bin 0 -> 112 bytes image/photo/state/run.png | Bin 0 -> 4413 bytes lang/zh_CN.ts | 90 +++-- lua/client/i18n/zh_CN.lua | 6 + lua/server/events/gameflow.lua | 15 +- lua/server/events/hp.lua | 4 + lua/server/request.lua | 4 +- lua/server/serverplayer.lua | 11 + .../card/delayedTrick/supply_shortage.png | Bin .../image}/card/delayedTrick/indulgence.png | Bin .../image/card/delayedTrick/lightning.png | Bin 0 -> 2998 bytes src/client/client.cpp | 4 +- src/core/packman.cpp | 20 +- src/main.cpp | 52 +++ src/network/client_socket.cpp | 40 +- src/network/router.cpp | 10 + src/server/room.cpp | 40 +- src/server/room.h | 1 + src/server/server.cpp | 102 ++++- src/server/shell.cpp | 132 ++++++- src/server/shell.h | 4 + 34 files changed, 961 insertions(+), 408 deletions(-) create mode 100644 audio/system/losemaxhp.mp3 rename image/card/delayedTrick/{lightning.png => unknown.png} (100%) create mode 100644 image/photo/notready.png create mode 100644 image/photo/state/run.png rename {image => packages/maneuvering/image}/card/delayedTrick/supply_shortage.png (100%) rename {image => packages/standard_cards/image}/card/delayedTrick/indulgence.png (100%) create mode 100644 packages/standard_cards/image/card/delayedTrick/lightning.png diff --git a/.gitignore b/.gitignore index 9f7c107f..4e7279dd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ freekill-wrap.cxx /server/rsa_pub /freekill.client.config.json /freekill.server.config.json +/freekill.server.error.log +/freekill.server.info.log /flist.txt # windeployqt diff --git a/Fk/Cheat/PlayerDetail.qml b/Fk/Cheat/PlayerDetail.qml index 55681380..e281fb50 100644 --- a/Fk/Cheat/PlayerDetail.qml +++ b/Fk/Cheat/PlayerDetail.qml @@ -43,12 +43,22 @@ Flickable { Button { text: Backend.translate("Give Shoe") - enabled: Math.random() < 0.5 + enabled: Math.random() < 0.3 onClicked: { root.givePresent("Shoe"); root.finish(); } } + + Button { + text: Backend.translate("Kick From Room") + visible: !roomScene.isStarted && roomScene.isOwner + enabled: pid !== Self.id + onClicked: { + ClientInstance.notifyServer("KickPlayer", pid.toString()); + root.finish(); + } + } } // TODO: player details diff --git a/Fk/LobbyElement/RoomGeneralSettings.qml b/Fk/LobbyElement/RoomGeneralSettings.qml index f0b676a4..b49a5297 100644 --- a/Fk/LobbyElement/RoomGeneralSettings.qml +++ b/Fk/LobbyElement/RoomGeneralSettings.qml @@ -4,194 +4,201 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -ColumnLayout { - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Room Name") - } - TextField { - id: roomName - maximumLength: 64 - font.pixelSize: 18 - text: Backend.translate("$RoomName").arg(Self.screenName) - } - } +Flickable { + flickableDirection: Flickable.AutoFlickIfNeeded + clip: true + contentHeight: layout.height - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Player num") - } - SpinBox { - id: playerNum - from: 2 - to: 8 - value: config.preferedPlayerNum - - onValueChanged: { - config.preferedPlayerNum = value; + ColumnLayout { + id: layout + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Room Name") + } + TextField { + id: roomName + maximumLength: 64 + font.pixelSize: 18 + text: Backend.translate("$RoomName").arg(Self.screenName) } } - } - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Game Mode") - } - ComboBox { - id: gameModeCombo - textRole: "name" - model: ListModel { - id: gameModeList + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Player num") } + SpinBox { + id: playerNum + from: 2 + to: 8 + value: config.preferedPlayerNum - onCurrentIndexChanged: { - let data = gameModeList.get(currentIndex); - playerNum.from = data.minPlayer; - playerNum.to = data.maxPlayer; - - config.preferedMode = data.orig_name; + onValueChanged: { + config.preferedPlayerNum = value; + } } } - } - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Select general num") - } - SpinBox { - id: generalNum - from: 3 - to: 18 - value: config.preferredGeneralNum - - onValueChanged: { - config.preferredGeneralNum = value; + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Game Mode") } - } - } - - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Operation timeout") - } - SpinBox { - from: 10 - to: 60 - editable: true - value: config.preferredTimeout - - onValueChanged: { - config.preferredTimeout = value; - } - } - } - - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Luck Card Times") - } - SpinBox { - from: 0 - to: 8 - value: config.preferredLuckTime - - onValueChanged: { - config.preferredLuckTime = value; - } - } - } - - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: Backend.translate("Room Password") - } - TextField { - id: roomPassword - maximumLength: 16 - font.pixelSize: 18 - } - } - - Switch { - id: freeAssignCheck - checked: Debugging ? true : false - text: Backend.translate("Enable free assign") - } - - Switch { - id: deputyCheck - checked: Debugging ? true : false - text: Backend.translate("Enable deputy general") - } - - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Button { - text: Backend.translate("OK") - onClicked: { - root.finished(); - mainWindow.busy = true; - - let disabledGenerals = config.disabledGenerals.slice(); - if (disabledGenerals.length) { - const availablePack = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", [])). - filter((pack) => !config.disabledPack.includes(pack)); - disabledGenerals = disabledGenerals.filter((general) => { - return availablePack.find((pack) => JSON.parse(Backend.callLuaFunction("GetGenerals", [pack])).includes(general)); - }); - - disabledGenerals = Array.from(new Set(disabledGenerals)); + ComboBox { + id: gameModeCombo + textRole: "name" + model: ListModel { + id: gameModeList } - ClientInstance.notifyServer( - "CreateRoom", - JSON.stringify([roomName.text, playerNum.value, config.preferredTimeout, { - enableFreeAssign: freeAssignCheck.checked, - enableDeputy: deputyCheck.checked, - gameMode: config.preferedMode, - disabledPack: config.disabledPack, - generalNum: config.preferredGeneralNum, - luckTime: config.preferredLuckTime, - password: roomPassword.text, - disabledGenerals, - }]) - ); - } - } - Button { - text: Backend.translate("Cancel") - onClicked: { - root.finished(); - } - } - } + onCurrentIndexChanged: { + let data = gameModeList.get(currentIndex); + playerNum.from = data.minPlayer; + playerNum.to = data.maxPlayer; - Component.onCompleted: { - let mode_data = JSON.parse(Backend.callLuaFunction("GetGameModes", [])); - let i = 0; - for (let d of mode_data) { - gameModeList.append(d); - if (d.orig_name == config.preferedMode) { - gameModeCombo.currentIndex = i; + config.preferedMode = data.orig_name; + } } - i += 1; } - playerNum.value = config.preferedPlayerNum; + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Select general num") + } + SpinBox { + id: generalNum + from: 3 + to: 18 + value: config.preferredGeneralNum + + onValueChanged: { + config.preferredGeneralNum = value; + } + } + } + + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Operation timeout") + } + SpinBox { + from: 10 + to: 60 + editable: true + value: config.preferredTimeout + + onValueChanged: { + config.preferredTimeout = value; + } + } + } + + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Luck Card Times") + } + SpinBox { + from: 0 + to: 8 + value: config.preferredLuckTime + + onValueChanged: { + config.preferredLuckTime = value; + } + } + } + + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Room Password") + } + TextField { + id: roomPassword + maximumLength: 16 + font.pixelSize: 18 + } + } + + Switch { + id: freeAssignCheck + checked: Debugging ? true : false + text: Backend.translate("Enable free assign") + } + + Switch { + id: deputyCheck + checked: Debugging ? true : false + text: Backend.translate("Enable deputy general") + } + + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Button { + text: Backend.translate("OK") + onClicked: { + root.finished(); + mainWindow.busy = true; + + let disabledGenerals = config.disabledGenerals.slice(); + if (disabledGenerals.length) { + const availablePack = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", [])). + filter((pack) => !config.disabledPack.includes(pack)); + disabledGenerals = disabledGenerals.filter((general) => { + return availablePack.find((pack) => JSON.parse(Backend.callLuaFunction("GetGenerals", [pack])).includes(general)); + }); + + disabledGenerals = Array.from(new Set(disabledGenerals)); + } + + ClientInstance.notifyServer( + "CreateRoom", + JSON.stringify([roomName.text, playerNum.value, config.preferredTimeout, { + enableFreeAssign: freeAssignCheck.checked, + enableDeputy: deputyCheck.checked, + gameMode: config.preferedMode, + disabledPack: config.disabledPack, + generalNum: config.preferredGeneralNum, + luckTime: config.preferredLuckTime, + password: roomPassword.text, + disabledGenerals, + }]) + ); + } + } + Button { + text: Backend.translate("Cancel") + onClicked: { + root.finished(); + } + } + } + + Component.onCompleted: { + let mode_data = JSON.parse(Backend.callLuaFunction("GetGameModes", [])); + let i = 0; + for (let d of mode_data) { + gameModeList.append(d); + if (d.orig_name == config.preferedMode) { + gameModeCombo.currentIndex = i; + } + i += 1; + } + + playerNum.value = config.preferedPlayerNum; + } } } diff --git a/Fk/Pages/PackageManage.qml b/Fk/Pages/PackageManage.qml index 6c15ab13..d008250d 100644 --- a/Fk/Pages/PackageManage.qml +++ b/Fk/Pages/PackageManage.qml @@ -6,195 +6,170 @@ import QtQuick.Layouts Item { id: root - Button { - text: qsTr("Quit") - anchors.right: parent.right - onClicked: { - mainStack.pop(); - } - } - Component { - id: packageDelegate - - Item { - height: 22 - width: packageList.width - - RowLayout { - anchors.fill: parent - spacing: 16 - Text { - font.pixelSize: 20 - text: pkgName - } - - Text { - font.pixelSize: 20 - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: pkgURL - } - - Text { - font.pixelSize: 20 - text: pkgVersion - } - - Text { - font.pixelSize: 20 - color: pkgEnabled === "1" ? "green" : "red" - text: pkgEnabled === "1" ? qsTr("Enabled") : qsTr("Disabled") - } + ToolBar { + id: bar + width: parent.width + RowLayout { + anchors.fill: parent + ToolButton { + icon.source: AppPath + "/image/modmaker/back" + onClicked: mainStack.pop(); } + Label { + text: qsTr("Package Manager") + horizontalAlignment: Qt.AlignHCenter + Layout.fillWidth: true + } + ToolButton { + icon.source: AppPath + "/image/modmaker/menu" + onClicked: menu.open() - TapHandler { - onTapped: { - if (packageList.currentIndex === index) { - packageList.currentIndex = -1; - } else { - packageList.currentIndex = index; + Menu { + id: menu + y: bar.height + + MenuItem { + text: qsTr("Enable All") + onTriggered: { + for (let i = 0; i < packageModel.count; i++) { + let name = packageModel.get(i).pkgName; + Pacman.enablePack(name); + } + updatePackageList(); + } + } + MenuItem { + text: qsTr("Disable All") + onTriggered: { + for (let i = 0; i < packageModel.count; i++) { + let name = packageModel.get(i).pkgName; + Pacman.disablePack(name); + } + updatePackageList(); + } + } + MenuItem { + text: qsTr("Upgrade All") + onTriggered: { + for (let i = 0; i < packageModel.count; i++) { + let name = packageModel.get(i).pkgName; + Pacman.upgradePack(name); + } + updatePackageList(); + } } } } } } - ListModel { - id: packageModel - } + Rectangle { + width: parent.width + height: parent.height - bar.height - urlInstaller.height + anchors.top: bar.bottom + color: "snow" + opacity: 0.75 + clip: true - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillHeight: true - Layout.alignment: Qt.AlignHCenter - Item { - Layout.preferredWidth: root.width * 0.9 - Layout.fillHeight: true - Rectangle { - anchors.fill: parent - color: "#88EEEEEE" + ListView { + id: packageList + anchors.fill: parent + model: ListModel { + id: packageModel } - ListView { - id: packageList - anchors.fill: parent + delegate: ItemDelegate { + width: root.width + height: 64 - contentHeight: packageDelegate.height * count - ScrollBar.vertical: ScrollBar {} - header: RowLayout { - height: 22 - width: packageList.width - spacing: 16 + ColumnLayout { + anchors.fill: parent + anchors.margins: 8 Text { - font.pixelSize: 20 - text: qsTr("Name") + text: "" + pkgName + " (" + pkgVersion + ")" + font.pixelSize: 18 + textFormat: Text.RichText + color: pkgEnabled === "1" ? "black" : "grey" } - Text { - font.pixelSize: 20 - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: "URL" - } - - Text { - font.pixelSize: 20 - text: qsTr("Version") - } - - Text { - font.pixelSize: 20 - text: qsTr("Enable") + text: pkgURL + color: pkgEnabled === "1" ? "black" : "grey" } } - delegate: packageDelegate - model: packageModel - highlight: Rectangle { color: "lightsteelblue"; radius: 5 } - Component.onCompleted: { currentIndex = -1; } - } - } - ColumnLayout { - Button { - enabled: packageList.currentItem - text: qsTr("Enable") - onClicked: { - let idx = packageList.currentIndex; - let name = packageModel.get(idx).pkgName; - Pacman.enablePack(name); - updatePackageList(); - packageList.currentIndex = idx; + Button { + id: enableBtn + text: pkgEnabled === "0" ? qsTr("Enable") : qsTr("Disable") + anchors.right: upgradeBtn.left + anchors.rightMargin: 8 + onClicked: { + if (pkgEnabled === "0") { + Pacman.enablePack(pkgName); + } else { + Pacman.disablePack(pkgName); + } + updatePackageList(); + } } - } - Button { - enabled: packageList.currentItem - text: qsTr("Disable") - onClicked: { - let idx = packageList.currentIndex; - let name = packageModel.get(idx).pkgName; - Pacman.disablePack(name); - updatePackageList(); - packageList.currentIndex = idx; + + Button { + id: upgradeBtn + text: qsTr("Upgrade") + anchors.right: delBtn.left + anchors.rightMargin: 8 + onClicked: { + Pacman.upgradePack(pkgName); + updatePackageList(); + } } - } - Button { - enabled: packageList.currentItem - text: qsTr("Upgrade") - onClicked: { - let idx = packageList.currentIndex; - let name = packageModel.get(idx).pkgName; - Pacman.upgradePack(name); - updatePackageList(); - packageList.currentIndex = idx; + + Button { + id: delBtn + text: qsTr("Remove") + anchors.right: parent.right + anchors.rightMargin: 8 + onClicked: { + Pacman.removePack(pkgName); + updatePackageList(); + } } - } - Button { - enabled: packageList.currentItem - text: qsTr("Remove") + onClicked: { - let idx = packageList.currentIndex; - let name = packageModel.get(idx).pkgName; - Pacman.removePack(name); - updatePackageList(); - packageList.currentIndex = idx; - } - } - Button { - enabled: packageList.currentItem - text: qsTr("Copy URL") - onClicked: { - let idx = packageList.currentIndex; - let name = packageModel.get(idx).pkgURL; - Backend.copyToClipboard(name); - toast.show(qsTr("Copied.")); + Backend.copyToClipboard(pkgURL); + toast.show(qsTr("Copied %1.").arg(pkgURL)); } } } } - RowLayout { - Layout.fillWidth: true - TextField { - id: urlEdit - Layout.fillWidth: true - clip: true - } + Rectangle { + id: urlInstaller + width: parent.width + height: childrenRect.height + color: "snow" + opacity: 0.75 + anchors.bottom: parent.bottom - Button { - text: qsTr("Install From URL") - enabled: urlEdit.text !== "" - onClicked: { - let url = urlEdit.text; - mainWindow.busy = true; - Pacman.downloadNewPack(url, true); + RowLayout { + width: parent.width + TextField { + id: urlEdit + Layout.fillWidth: true + clip: true + } + + Button { + text: qsTr("Install From URL") + enabled: urlEdit.text !== "" + onClicked: { + let url = urlEdit.text; + mainWindow.busy = true; + Pacman.downloadNewPack(url, true); + } } } } - } - function updatePackageList() { packageModel.clear(); let data = JSON.parse(Pacman.listPackages()); diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index 921022ab..1c0e4d9a 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -17,6 +17,9 @@ Item { property bool isOwner: false property bool isStarted: false + property bool isFull: false + property bool isAllReady: false + property bool isReady: false property alias popupBox: popupBox property alias manualBox: manualBox @@ -79,13 +82,36 @@ Item { } } Button { - text: "add robot" - visible: isOwner && !isStarted + text: Backend.translate("Add Robot") + visible: isOwner && !isStarted && !isFull anchors.centerIn: parent onClicked: { ClientInstance.notifyServer("AddRobot", "[]"); } } + Button { + text: Backend.translate("Start Game") + visible: isOwner && !isStarted && isFull + enabled: isAllReady + anchors.centerIn: parent + onClicked: { + ClientInstance.notifyServer("StartGame", "[]"); + } + } + Timer { + id: opTimer + interval: 1000 + } + Button { + text: isReady ? Backend.translate("Cancel Ready") : Backend.translate("Ready") + visible: !isOwner && !isStarted + enabled: !opTimer.running + anchors.centerIn: parent + onClicked: { + opTimer.start(); + ClientInstance.notifyServer("Ready", ""); + } + } states: [ State { name: "notactive" }, // Normal status @@ -195,6 +221,7 @@ Item { Photo { playerid: model.id general: model.general + avatar: model.avatar deputyGeneral: model.deputyGeneral screenName: model.screenName role: model.role @@ -210,6 +237,7 @@ Item { chained: model.chained drank: model.drank isOwner: model.isOwner + ready: model.ready onSelectedChanged: { Logic.updateSelectedTargets(playerid, selected); @@ -830,6 +858,36 @@ Item { cheatDrawer.open(); } + function resetToInit() { + let datalist = []; + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id > 0) { + datalist.push({ + id: item.id, + avatar: item.avatar, + name: item.screenName, + isOwner: item.isOwner, + ready: item.ready, + }); + } + } + mainStack.pop(); + mainStack.push(room); + mainStack.currentItem.loadPlayerData(datalist); + } + + function loadPlayerData(datalist) { + datalist.forEach(d => { + if (d.id == Self.id) { + roomScene.isOwner = d.isOwner; + } else { + callbacks["AddPlayer"](JSON.stringify([d.id, d.name, d.avatar, d.ready])); + } + Logic.getPhotoModel(d.id).isOwner = d.isOwner; + }); + } + Component.onCompleted: { toast.show(Backend.translate("$EnterRoom")); playerNum = config.roomCapacity; @@ -839,6 +897,7 @@ Item { id: i ? -1 : Self.id, index: i, // For animating seat swap general: i ? "" : Self.avatar, + avatar: i ? "" : Self.avatar, deputyGeneral: "", screenName: i ? "" : Self.screenName, role: "unknown", @@ -853,7 +912,8 @@ Item { faceup: true, chained: false, drank: 0, - isOwner: false + isOwner: false, + ready: false, }); } diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index e3334230..61befd6e 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -334,7 +334,7 @@ function changeSelf(id) { } callbacks["AddPlayer"] = function(jsonData) { - // jsonData: int id, string screenName, string avatar + // jsonData: int id, string screenName, string avatar, bool ready for (let i = 0; i < photoModel.count; i++) { let item = photoModel.get(i); if (item.id === -1) { @@ -342,9 +342,22 @@ callbacks["AddPlayer"] = function(jsonData) { let uid = data[0]; let name = data[1]; let avatar = data[2]; + let ready = data[3]; + item.id = uid; item.screenName = name; item.general = avatar; + item.avatar = avatar; + item.ready = ready; + + checkAllReady(); + + if (getPhoto(-1)) { + roomScene.isFull = false; + } else { + roomScene.isFull = true; + } + return; } } @@ -485,6 +498,8 @@ callbacks["RemovePlayer"] = function(jsonData) { model.id = -1; model.screenName = ""; model.general = ""; + model.isOwner = false; + roomScene.isFull = false; } } @@ -492,9 +507,7 @@ callbacks["RoomOwner"] = function(jsonData) { // jsonData: int uid of the owner let uid = JSON.parse(jsonData)[0]; - if (Self.id === uid) { - roomScene.isOwner = true; - } + roomScene.isOwner = (Self.id === uid); let model = getPhotoModel(uid); if (typeof(model) !== "undefined") { @@ -502,6 +515,46 @@ callbacks["RoomOwner"] = function(jsonData) { } } +function checkAllReady() { + let allReady = true; + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (!item.isOwner && !item.ready) { + allReady = false; + break; + } + } + roomScene.isAllReady = allReady; +} + +callbacks["ReadyChanged"] = (j) => { + const data = JSON.parse(j); + const id = data[0]; + const ready = data[1]; + + if (id === Self.id) { + roomScene.isReady = ready === 1; + } + + let model = getPhotoModel(id); + if (typeof(model) !== "undefined") { + model.ready = ready ? true : false; + checkAllReady(); + } +} + +callbacks["NetStateChanged"] = (j) => { + const data = JSON.parse(j); + const id = data[0]; + let state = data[1]; + + let model = getPhotoModel(id); + if (state == "run" && model.dead) { + state = "leave"; + } + model.netstate = state; +} + callbacks["PropertyUpdate"] = function(jsonData) { // jsonData: int id, string property_name, value let data = JSON.parse(jsonData); @@ -510,6 +563,7 @@ callbacks["PropertyUpdate"] = function(jsonData) { let value = data[2]; let model = getPhotoModel(uid); + if (typeof(model) !== "undefined") { model[property_name] = value; } @@ -520,6 +574,7 @@ callbacks["StartGame"] = function(jsonData) { for (let i = 0; i < photoModel.count; i++) { let item = photoModel.get(i); + item.ready = false; item.general = ""; } } @@ -1013,6 +1068,12 @@ callbacks["LogEvent"] = function(jsonData) { Backend.playSound("./audio/system/losehp"); break; } + case "ChangeMaxHp": { + if (data.num < 0) { + Backend.playSound("./audio/system/losemaxhp"); + } + break; + } case "PlaySkillSound": { let skill = data.name; let extension = data.extension; @@ -1042,7 +1103,7 @@ callbacks["GameOver"] = function(jsonData) { roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GameOverBox.qml"); let box = roomScene.popupBox.item; box.winner = jsonData; - roomScene.isStarted = false; + // roomScene.isStarted = false; } callbacks["FillAG"] = (j) => { diff --git a/Fk/PhotoElement/DelayedTrickArea.qml b/Fk/PhotoElement/DelayedTrickArea.qml index b1db9b20..37a630f5 100644 --- a/Fk/PhotoElement/DelayedTrickArea.qml +++ b/Fk/PhotoElement/DelayedTrickArea.qml @@ -25,7 +25,7 @@ Item { Image { height: 55 * 0.8 width: 47 * 0.8 - source: SkinBank.DELAYED_TRICK_DIR + name + source: SkinBank.getDelayedTrickPicture(name) // SkinBank.DELAYED_TRICK_DIR + name } } } diff --git a/Fk/RoomElement/ChooseGeneralBox.qml b/Fk/RoomElement/ChooseGeneralBox.qml index bbf4c924..71cf7f9c 100644 --- a/Fk/RoomElement/ChooseGeneralBox.qml +++ b/Fk/RoomElement/ChooseGeneralBox.qml @@ -18,7 +18,8 @@ GraphicsBox { } id: root - title.text: Backend.translate("$ChooseGeneral").arg(choiceNum) + title.text: Backend.translate("$ChooseGeneral").arg(choiceNum) + + (config.enableFreeAssign ? "(" + Backend.translate("Enable free assign") + ")" : "") width: generalArea.width + body.anchors.leftMargin + body.anchors.rightMargin height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin diff --git a/Fk/RoomElement/GameOverBox.qml b/Fk/RoomElement/GameOverBox.qml index 79af281e..d7b74e34 100644 --- a/Fk/RoomElement/GameOverBox.qml +++ b/Fk/RoomElement/GameOverBox.qml @@ -22,6 +22,16 @@ GraphicsBox { color: "#E4D5A0" } + MetroButton { + text: Backend.translate("Back To Room") + anchors.horizontalCenter: parent.horizontalCenter + + onClicked: { + roomScene.resetToInit(); + finished(); + } + } + MetroButton { text: Backend.translate("Back To Lobby") anchors.horizontalCenter: parent.horizontalCenter diff --git a/Fk/RoomElement/Photo.qml b/Fk/RoomElement/Photo.qml index c8792fab..30831920 100644 --- a/Fk/RoomElement/Photo.qml +++ b/Fk/RoomElement/Photo.qml @@ -13,6 +13,7 @@ Item { scale: 0.75 property int playerid: 0 property string general: "" + property string avatar: "" property string deputyGeneral: "" property string screenName: "" property string role: "unknown" @@ -29,6 +30,7 @@ Item { property bool chained: false property int drank: 0 property bool isOwner: false + property bool ready: false property int distance: 0 property string status: "normal" property int maxCard: 0 @@ -249,7 +251,7 @@ Item { anchors.right: parent.right anchors.bottomMargin: 8 anchors.rightMargin: 4 - source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : "ready") + source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : (ready ? "ready" : "notready")) visible: screenName != "" && !roomScene.isStarted } @@ -342,6 +344,8 @@ Item { source: SkinBank.STATE_DIR + root.netstate x: photoMask.x y: photoMask.y + scale: 0.9 + transformOrigin: Item.TopLeft } Image { @@ -596,6 +600,10 @@ Item { } function showDetail() { + if (playerid === 0 || playerid === -1) { + return; + } + roomScene.startCheat("PlayerDetail", { photo: this }); } } diff --git a/Fk/skin-bank.js b/Fk/skin-bank.js index b7db7a3b..f1c7c875 100644 --- a/Fk/skin-bank.js +++ b/Fk/skin-bank.js @@ -53,6 +53,22 @@ function getCardPicture(cidOrName) { return CARD_DIR + "unknown.png"; } +function getDelayedTrickPicture(name) { + let extension = Backend.callLuaFunction("GetCardExtensionByName", [name]); + + let path = AppPath + "/packages/" + extension + "/image/card/delayedTrick/" + name + ".png"; + if (Backend.exists(path)) { + return path; + } else { + for (let dir of Backend.ls(AppPath + "/packages/")) { + path = AppPath + "/packages/" + dir + "/image/card/delayedTrick/" + name + ".png"; + if (Backend.exists(path)) return path; + } + } + return DELAYED_TRICK_DIR + "unknown.png"; +} + + function getEquipIcon(cid, icon) { let data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid])); let extension = data.extension; diff --git a/audio/system/losemaxhp.mp3 b/audio/system/losemaxhp.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e28f1296d6c1d5bd9fd0ec06c9e333f7433841cd GIT binary patch literal 18224 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 literal 0 HcmV?d00001 diff --git a/image/photo/state/run.png b/image/photo/state/run.png new file mode 100644 index 0000000000000000000000000000000000000000..d04b605a8066654b608adb249d858f725b7a8053 GIT binary patch literal 4413 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 D$cmZD literal 0 HcmV?d00001 diff --git a/lang/zh_CN.ts b/lang/zh_CN.ts index b6d68107..fb4ccdf5 100644 --- a/lang/zh_CN.ts +++ b/lang/zh_CN.ts @@ -15,7 +15,59 @@ Socket access error - 套接字访问错误 + 无权访问套接字文件 + + + Socket resource error + 系统资源不足 + + + Socket timeout error + 连接超时 + + + Datagram too large error + 报文过长 + + + Network error + 网络错误 + + + Unsupprted socket operation + 不支持的套接字操作 + + + Unfinished socket operation + 未完成的套接字操作 + + + Proxy auth error + 代理服务器认证失败 + + + Proxy refused + 代理服务器拒绝连接 + + + Proxy closed + 代理服务器已关闭连接 + + + Proxy timeout + 代理服务器连接超时 + + + Proxy protocol error + 代理服务器协议错误 + + + Operation error + 不允许的操作 + + + Temporary error + 网络暂时出现故障,请稍后重试 Unknown error @@ -187,14 +239,14 @@ no such room 房间不存在 + + you have been banned! + 你已经被该服务器封禁! + PackageManage - - Quit - 退出 - Install From URL 从URL安装 @@ -207,14 +259,6 @@ Disable 禁用 - - Enabled - 已启用 - - - Disabled - 已禁用 - Remove 删除 @@ -224,20 +268,24 @@ 更新 - Name - 名称 + Copied %1. + 已复制到剪贴板。(%1) - Version - 版本 + Package Manager + 新月杀拓展包管理器 - Copy URL - 复制URL + Enable All + 全部启用 - Copied. - 已复制。 + Disable All + 全部禁用 + + + Upgrade All + 全部更新 diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 864153a8..219d6d21 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -34,6 +34,10 @@ Fk:loadTranslationTable{ ["Luck Card Times"] = "手气卡次数", ["Room Password"] = "房间密码", ["Please input room's password"] = "请输入房间的密码", + ["Add Robot"] = "添加机器人", + ["Start Game"] = "开始游戏", + ["Ready"] = "准备", + ["Cancel Ready"] = "取消准备", ["Game Mode"] = "游戏模式", ["Enable free assign"] = "自由选将", ["Enable deputy general"] = "启用副将机制", @@ -45,6 +49,7 @@ Fk:loadTranslationTable{ ["Give Flower"] = "送花", ["Give Egg"] = "砸蛋", ["Give Shoe"] = "拖鞋", + ["Kick From Room"] = "踢出房间", ["$OnlineInfo"] = "大厅人数:%1,总在线人数:%2", @@ -206,6 +211,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["$GameOver"] = "游戏结束", ["$Winner"] = "%1 获胜", ["$NoWinner"] = "平局!", + ["Back To Room"] = "回到房间", ["Back To Lobby"] = "返回大厅", ["Bulletin Info"] = [==[

v0.2.0 更新说明

diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index 95c4b6c5..9be010a2 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -102,12 +102,23 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self) break end - if table.every(room:getTag("LuckCardData").playerList, function(id) - return room:getTag("LuckCardData")[id].luckTime == 0 + -- local ldata = room:getTag("LuckCardData") + local ldata = luck_data + + if table.every(ldata.playerList, function(id) + return ldata[id].luckTime == 0 end) then break end + for _, id in ipairs(ldata.playerList) do + if room:getPlayerById(id)._splayer:getStateString() ~= "online" then + ldata[id].luckTime = 0 + end + end + + -- room:setTag("LuckCardData", ldata) + checkNoHuman(room) coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000) diff --git a/lua/server/events/hp.lua b/lua/server/events/hp.lua index c01bdc71..5bb04226 100644 --- a/lua/server/events/hp.lua +++ b/lua/server/events/hp.lua @@ -242,6 +242,10 @@ GameEvent.functions[GameEvent.ChangeMaxHp] = function(self) player.maxHp = math.max(player.maxHp + num, 0) self:broadcastProperty(player, "maxHp") + self:sendLogEvent("ChangeMaxHp", { + player = player.id, + num = num, + }) if player.maxHp == 0 then self:killPlayer({ who = player.id }) end diff --git a/lua/server/request.lua b/lua/server/request.lua index c5c6cca8..051caa62 100644 --- a/lua/server/request.lua +++ b/lua/server/request.lua @@ -70,7 +70,9 @@ end local request_handlers = {} request_handlers["reconnect"] = function(room, id, reqlist) local p = room:getPlayerById(id) - p:reconnect() + if p then + p:reconnect() + end end request_handlers["observe"] = function(room, id, reqlist) diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 7930407a..b78f3be3 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -102,6 +102,11 @@ local function _waitForReply(player, timeout) local start = os.getms() local state = player.serverplayer:getStateString() if state ~= "online" then + if state ~= "robot" then + checkNoHuman(player.room) + player.room:delay(500) + return "__cancel" + end -- Let AI make reply. First handle request local ret_msg = true while ret_msg do @@ -259,6 +264,12 @@ function ServerPlayer:marshal(player) end end + for k, v in pairs(self.skillUsedHistory) do + if v[1] > 0 then + player:doNotify("AddSkillUseHistory", json.encode{self.id, k, v[1]}) + end + end + if self.role_shown then room:notifyProperty(player, self, "role") end diff --git a/image/card/delayedTrick/supply_shortage.png b/packages/maneuvering/image/card/delayedTrick/supply_shortage.png similarity index 100% rename from image/card/delayedTrick/supply_shortage.png rename to packages/maneuvering/image/card/delayedTrick/supply_shortage.png diff --git a/image/card/delayedTrick/indulgence.png b/packages/standard_cards/image/card/delayedTrick/indulgence.png similarity index 100% rename from image/card/delayedTrick/indulgence.png rename to packages/standard_cards/image/card/delayedTrick/indulgence.png diff --git a/packages/standard_cards/image/card/delayedTrick/lightning.png b/packages/standard_cards/image/card/delayedTrick/lightning.png new file mode 100644 index 0000000000000000000000000000000000000000..0c0f0c3704ed05b834a4f061a1287b0bc6661a0d GIT binary patch literal 2998 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 s)~pUUip?YscSrs{?&Ci0objectName().toLatin1(); @@ -111,19 +131,27 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, case QtDebugMsg: fprintf(stderr, "%s[D] %s\n", threadName.constData(), localMsg.constData()); + fprintf(file, "%s[D] %s\n", threadName.constData(), + localMsg.constData()); break; case QtInfoMsg: fprintf(stderr, "%s[%s] %s\n", threadName.constData(), Color("I", Green).toUtf8().constData(), localMsg.constData()); + fprintf(file, "%s[%s] %s\n", threadName.constData(), + "I", localMsg.constData()); break; case QtWarningMsg: fprintf(stderr, "%s[%s] %s\n", threadName.constData(), Color("W", Yellow, Bold).toUtf8().constData(), localMsg.constData()); + fprintf(file, "%s[%s] %s\n", threadName.constData(), + "W", localMsg.constData()); break; case QtCriticalMsg: fprintf(stderr, "%s[%s] %s\n", threadName.constData(), Color("C", Red, Bold).toUtf8().constData(), localMsg.constData()); + fprintf(file, "%s[%s] %s\n", threadName.constData(), + "C", localMsg.constData()); #ifndef FK_SERVER_ONLY if (Backend != nullptr) { Backend->notifyUI( @@ -135,6 +163,8 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, case QtFatalMsg: fprintf(stderr, "%s[%s] %s\n", threadName.constData(), Color("E", Red, Bold).toUtf8().constData(), localMsg.constData()); + fprintf(file, "%s[%s] %s\n", threadName.constData(), + "E", localMsg.constData()); break; } } @@ -143,6 +173,19 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, int main(int argc, char *argv[]) { // 初始化一下各种杂项信息 QThread::currentThread()->setObjectName("Main"); + if (!info_log) { + info_log = fopen("freekill.server.info.log", "a+"); + if (!info_log) { + qFatal("Cannot open info.log"); + } + } + if (!err_log) { + err_log = fopen("freekill.server.error.log", "a+"); + if (!err_log) { + qFatal("Cannot open error.log"); + } + } + qInstallMessageHandler(fkMsgHandler); QCoreApplication *app; QCoreApplication::setApplicationName("FreeKill"); @@ -313,6 +356,15 @@ int main(int argc, char *argv[]) { ); #endif + if (info_log) { + fclose(info_log); + info_log = nullptr; + } + if (err_log) { + fclose(err_log); + info_log = nullptr; + } + return ret; #endif } diff --git a/src/network/client_socket.cpp b/src/network/client_socket.cpp index b5bfcd7b..de033e4b 100644 --- a/src/network/client_socket.cpp +++ b/src/network/client_socket.cpp @@ -2,6 +2,7 @@ #include "client_socket.h" #include +#include #include ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { @@ -96,8 +97,45 @@ void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) { case QAbstractSocket::SocketAccessError: reason = tr("Socket access error"); break; + case QAbstractSocket::SocketResourceError: + reason = tr("Socket resource error"); + break; + case QAbstractSocket::SocketTimeoutError: + reason = tr("Socket timeout error"); + break; + case QAbstractSocket::DatagramTooLargeError: + reason = tr("Datagram too large error"); + break; case QAbstractSocket::NetworkError: - return; // this error is ignored ... + reason = tr("Network error"); + break; + case QAbstractSocket::UnsupportedSocketOperationError: + reason = tr("Unsupprted socket operation"); + break; + case QAbstractSocket::UnfinishedSocketOperationError: + reason = tr("Unfinished socket operation"); + break; + case QAbstractSocket::ProxyAuthenticationRequiredError: + reason = tr("Proxy auth error"); + break; + case QAbstractSocket::ProxyConnectionRefusedError: + reason = tr("Proxy refused"); + break; + case QAbstractSocket::ProxyConnectionClosedError: + reason = tr("Proxy closed"); + break; + case QAbstractSocket::ProxyConnectionTimeoutError: + reason = tr("Proxy timeout"); + break; + case QAbstractSocket::ProxyProtocolError: + reason = tr("Proxy protocol error"); + break; + case QAbstractSocket::OperationError: + reason = tr("Operation error"); + break; + case QAbstractSocket::TemporaryError: + reason = tr("Temporary error"); + break; default: reason = tr("Unknown error"); break; diff --git a/src/network/router.cpp b/src/network/router.cpp index d18e1e39..91f73109 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -276,6 +276,16 @@ void Router::handlePacket(const QByteArray &rawPacket) { room->removePlayer(player); } else if (command == "AddRobot") { room->addRobot(player); + } else if (command == "KickPlayer") { + int i = jsonData.toInt(); + auto p = room->findPlayer(i); + if (p) room->removePlayer(p); + } else if (command == "Ready") { + player->setReady(!player->isReady()); + room->doBroadcastNotify(room->getPlayers(), "ReadyChanged", + QString("[%1,%2]").arg(player->getId()).arg(player->isReady())); + } else if (command == "StartGame") { + room->manuallyStart(); } else if (command == "Chat") { room->chat(player, jsonData); } else if (command == "PushRequest") { diff --git a/src/server/room.cpp b/src/server/room.cpp index 6b7281d8..4ef74600 100644 --- a/src/server/room.cpp +++ b/src/server/room.cpp @@ -114,6 +114,7 @@ void Room::addPlayer(ServerPlayer *player) { jsonData << player->getId(); jsonData << player->getScreenName(); jsonData << player->getAvatar(); + jsonData << player->isReady(); doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData)); } @@ -121,7 +122,13 @@ void Room::addPlayer(ServerPlayer *player) { player->setRoom(this); if (isLobby()) { - player->doNotify("EnterLobby", "[]"); + // 有机器人进入大厅(可能因为被踢),那么改为销毁 + if (player->getState() == Player::Robot) { + removePlayer(player); + player->deleteLater(); + } else { + player->doNotify("EnterLobby", "[]"); + } } else { // Second, let the player enter room and add other players jsonData = QJsonArray(); @@ -135,6 +142,7 @@ void Room::addPlayer(ServerPlayer *player) { jsonData << p->getId(); jsonData << p->getScreenName(); jsonData << p->getAvatar(); + jsonData << p->isReady(); player->doNotify("AddPlayer", JsonArray2Bytes(jsonData)); } @@ -144,8 +152,9 @@ void Room::addPlayer(ServerPlayer *player) { player->doNotify("RoomOwner", JsonArray2Bytes(jsonData)); } - if (isFull() && !gameStarted) - start(); + // 玩家手动启动 + // if (isFull() && !gameStarted) + // start(); } emit playerAdded(player); } @@ -159,6 +168,7 @@ void Room::addRobot(ServerPlayer *player) { robot->setId(robot_id); robot->setAvatar("guanyu"); robot->setScreenName(QString("COMP-%1").arg(robot_id)); + robot->setReady(true); robot_id--; // FIXME: 会触发Lobby:removePlayer @@ -174,7 +184,7 @@ void Room::removePlayer(ServerPlayer *player) { if (!gameStarted) { // 游戏还没开始的话,直接删除这名玩家 - if (players.contains(player)) { + if (players.contains(player) && !players.isEmpty()) { players.removeOne(player); } emit playerRemoved(player); @@ -213,9 +223,13 @@ void Room::removePlayer(ServerPlayer *player) { } // 如果房间空了,就把房间标为废弃,Server有信号处理函数的 - if (isAbandoned() && !m_abandoned) { + if (isAbandoned()) { + bool tmp = m_abandoned; m_abandoned = true; - emit abandoned(); + // 只释放一次信号就行了,他销毁机器人的时候会多次调用removePlayer + if (!tmp) { + emit abandoned(); + } } else if (player == owner) { setOwner(players.first()); } @@ -377,9 +391,19 @@ void Room::gameOver() { } // 旁观者不能在这清除,因为removePlayer逻辑不一样 // observers.clear(); - players.clear(); + // 玩家也不能在这里清除,因为要能返回原来房间继续玩呢 + // players.clear(); + // owner = nullptr; clearRequest(); - owner = nullptr; +} + +void Room::manuallyStart() { + if (isFull() && !gameStarted) { + foreach (auto p, players) { + p->setReady(false); + } + start(); + } } QString Room::fetchRequest() { diff --git a/src/server/room.h b/src/server/room.h index 3a28269e..6f33135f 100644 --- a/src/server/room.h +++ b/src/server/room.h @@ -59,6 +59,7 @@ class Room : public QThread { void initLua(); void roomStart(); + void manuallyStart(); LuaFunction startGame; QString fetchRequest(); diff --git a/src/server/server.cpp b/src/server/server.cpp index b5942485..cad23589 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -19,7 +19,7 @@ #include "serverplayer.h" #include "util.h" -Server *ServerInstance; +Server *ServerInstance = nullptr; Server::Server(QObject *parent) : QObject(parent) { ServerInstance = this; @@ -134,22 +134,36 @@ void Server::addPlayer(ServerPlayer *player) { players.insert(id, player); } -void Server::removePlayer(int id) { players.remove(id); } +void Server::removePlayer(int id) { + if (players[id]) { + players.remove(id); + } +} void Server::updateRoomList() { QJsonArray arr; + QJsonArray avail_arr; foreach (Room *room, rooms) { QJsonArray obj; auto settings = QJsonDocument::fromJson(room->getSettings()); auto password = settings["password"].toString(); + auto count = room->getPlayers().count(); // playerNum + auto cap = room->getCapacity(); // capacity - obj << room->getId(); // roomId - obj << room->getName(); // roomName - obj << settings["gameMode"]; // gameMode - obj << room->getPlayers().count(); // playerNum - obj << room->getCapacity(); // capacity + obj << room->getId(); // roomId + obj << room->getName(); // roomName + obj << settings["gameMode"]; // gameMode + obj << count; + obj << cap; obj << !password.isEmpty(); - arr << obj; + + if (count == cap) + arr << obj; + else + avail_arr << obj; + } + foreach (auto v, avail_arr) { + arr.prepend(v); } auto jsonData = JsonArray2Bytes(arr); lobby()->doBroadcastNotify(lobby()->getPlayers(), "UpdateRoomList", @@ -171,8 +185,22 @@ void Server::broadcast(const QString &command, const QString &jsonData) { } void Server::processNewConnection(ClientSocket *client) { - qInfo() << client->peerAddress() << "connected"; - // version check, file check, ban IP, reconnect, etc + auto addr = client->peerAddress(); + qInfo() << addr << "connected"; + auto result = SelectFromDatabase( + db, QString("SELECT * FROM banip WHERE ip='%1';").arg(addr)); + if (!result.isEmpty()) { + QJsonArray body; + body << -2; + body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | + Router::DEST_CLIENT); + body << "ErrorMsg"; + body << "you have been banned!"; + client->send(JsonArray2Bytes(body)); + qInfo() << "Refused banned IP:" << addr; + client->disconnectFromHost(); + return; + } connect(client, &ClientSocket::disconnected, this, [client]() { qInfo() << client->peerAddress() << "disconnected"; }); @@ -334,7 +362,10 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, obj = result[0].toObject(); // check if this username already login int id = obj["id"].toString().toInt(); - if (!players.value(id)) { + passed = obj["banned"].toString().toInt() == 0; + if (!passed) { + error_msg = "you have been banned!"; + } else if (!players.value(id)) { // check if password is the same auto salt = obj["salt"].toString().toLatin1(); decrypted_pw.append(salt); @@ -351,12 +382,21 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, player->setSocket(client); player->alive = true; client->disconnect(this); - broadcast("ServerMessage", - tr("%1 backed").arg(player->getScreenName())); - room->pushRequest(QString("%1,reconnect").arg(id)); + // broadcast("ServerMessage", + // tr("%1 backed").arg(player->getScreenName())); + + if (room && !room->isLobby()) { + room->pushRequest(QString("%1,reconnect").arg(id)); + } else { + // 懒得处理掉线玩家在大厅了!踢掉得了 + player->doNotify("ErrorMsg", "Unknown Error"); + player->kicked(); + } + return; } else { error_msg = "others logged in with this name"; + passed = false; } } } @@ -365,6 +405,13 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, } if (passed) { + // update lastLoginIp + auto sql_update = + QString("UPDATE userinfo SET lastLoginIp='%1' WHERE id=%2;") + .arg(client->peerAddress()) + .arg(obj["id"].toString().toInt()); + ExecSQL(db, sql_update); + // create new ServerPlayer and setup ServerPlayer *player = new ServerPlayer(lobby()); player->setSocket(client); @@ -375,7 +422,8 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, player->setScreenName(name); player->setAvatar(obj["avatar"].toString()); player->setId(obj["id"].toString().toInt()); - broadcast("ServerMessage", tr("%1 logged in").arg(player->getScreenName())); + // broadcast("ServerMessage", tr("%1 logged + // in").arg(player->getScreenName())); players.insert(player->getId(), player); // tell the lobby player's basic property @@ -410,6 +458,14 @@ void Server::onRoomAbandoned() { updateRoomList(); // room->deleteLater(); idle_rooms.push(room); + // 懒得改了! + // 这里出bug的原因还是在于room的销毁工作没做好 + // room销毁这块bug很多 + // if (idle_rooms.length() > 10) { + // auto junk = idle_rooms[0]; + // idle_rooms.removeFirst(); + // junk->deleteLater(); + // } #ifdef QT_DEBUG qDebug() << rooms.size() << "running room(s)," << idle_rooms.size() << "idle room(s)."; @@ -419,7 +475,8 @@ void Server::onRoomAbandoned() { void Server::onUserDisconnected() { ServerPlayer *player = qobject_cast(sender()); qInfo() << "Player" << player->getId() << "disconnected"; - broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName())); + // broadcast("ServerMessage", tr("%1 logged + // out").arg(player->getScreenName())); Room *room = player->getRoom(); if (room->isStarted()) { if (room->getObservers().contains(player)) { @@ -435,7 +492,15 @@ void Server::onUserDisconnected() { } } -void Server::onUserStateChanged() {} +void Server::onUserStateChanged() { + ServerPlayer *player = qobject_cast(sender()); + auto room = player->getRoom(); + if (!room || room->isLobby() || room->isAbandoned()) { + return; + } + room->doBroadcastNotify(room->getPlayers(), "NetStateChanged", + QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString())); +} RSA *Server::initServerRSA() { RSA *rsa = RSA_new(); @@ -451,7 +516,8 @@ RSA *Server::initServerRSA() { BIO_free_all(bp_pub); BIO_free_all(bp_pri); - QFile("server/rsa").setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); + QFile("server/rsa") + .setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); BN_free(bne); } FILE *keyFile = fopen("server/rsa_pub", "r"); diff --git a/src/server/shell.cpp b/src/server/shell.cpp index 94ef3cf5..7fc9a7b5 100644 --- a/src/server/shell.cpp +++ b/src/server/shell.cpp @@ -20,15 +20,25 @@ static void sigintHandler(int) { void Shell::helpCommand(QStringList &) { qInfo("Frequently used commands:"); -#define HELP_MSG(a, b) \ +#define HELP_MSG(a, b) \ qInfo((a), Color((b), fkShell::Cyan).toUtf8().constData()); HELP_MSG("%s: Display this help message.", "help"); HELP_MSG("%s: Shut down the server.", "quit"); HELP_MSG("%s: List all online players.", "lsplayer"); HELP_MSG("%s: List all running rooms.", "lsroom"); - HELP_MSG("%s: Kick a player by his id.", "kick"); + HELP_MSG("%s: Kick a player by his .", "kick"); HELP_MSG("%s: Broadcast message.", "msg"); + HELP_MSG("%s: Ban 1 or more accounts by their .", "ban"); + HELP_MSG("%s: Unban 1 or more accounts by their .", "unban"); + HELP_MSG( + "%s: Ban 1 or more IP address according to somebody's 'lastLoginIp'. " + "At least 1 required.", + "banip"); + HELP_MSG( + "%s: Unban 1 or more IP address according to somebody's 'lastLoginIp'. " + "At least 1 required.", + "unbanip"); qInfo(); qInfo("===== Package commands ====="); HELP_MSG("%s: Install a new package from .", "install"); @@ -36,7 +46,7 @@ void Shell::helpCommand(QStringList &) { HELP_MSG("%s: List all packages.", "lspkg"); HELP_MSG("%s: Enable a package.", "enable"); HELP_MSG("%s: Disable a package.", "disable"); - HELP_MSG("%s: Upgrade a package.", "upgrade"); + HELP_MSG("%s: Upgrade a package. Leave empty to upgrade all.", "upgrade"); qInfo("For more commands, check the documentation."); #undef HELP_MSG @@ -86,7 +96,12 @@ void Shell::removeCommand(QStringList &list) { void Shell::upgradeCommand(QStringList &list) { if (list.isEmpty()) { - qWarning("The 'upgrade' command need a package name to upgrade."); + // qWarning("The 'upgrade' command need a package name to upgrade."); + auto arr = QJsonDocument::fromJson(Pacman->listPackages().toUtf8()).array(); + foreach (auto a, arr) { + auto obj = a.toObject(); + Pacman->upgradePack(obj["name"].toString()); + } return; } @@ -155,6 +170,111 @@ void Shell::msgCommand(QStringList &list) { ServerInstance->broadcast("ServerMessage", msg); } +static void banAccount(sqlite3 *db, const QString &name, bool banned) { + if (!CheckSqlString(name)) + return; + QString sql_find = QString("SELECT * FROM userinfo \ + WHERE name='%1';") + .arg(name); + auto result = SelectFromDatabase(db, sql_find); + if (result.isEmpty()) + return; + auto obj = result[0].toObject(); + int id = obj["id"].toString().toInt(); + ExecSQL(db, QString("UPDATE userinfo SET banned=%2 WHERE id=%1;") + .arg(id) + .arg(banned ? 1 : 0)); + + if (banned) { + auto p = ServerInstance->findPlayer(id); + if (p) { + p->kicked(); + } + qInfo("Banned %s.", name.toUtf8().constData()); + } else { + qInfo("Unbanned %s.", name.toUtf8().constData()); + } +} + +void Shell::banCommand(QStringList &list) { + if (list.isEmpty()) { + qWarning("The 'ban' command needs at least 1 ."); + return; + } + + auto db = ServerInstance->getDatabase(); + + foreach (auto name, list) { + banAccount(db, name, true); + } +} + +void Shell::unbanCommand(QStringList &list) { + if (list.isEmpty()) { + qWarning("The 'unban' command needs at least 1 ."); + return; + } + + auto db = ServerInstance->getDatabase(); + + foreach (auto name, list) { + banAccount(db, name, false); + } +} +static void banIPByName(sqlite3 *db, const QString &name, bool banned) { + if (!CheckSqlString(name)) + return; + + QString sql_find = QString("SELECT * FROM userinfo \ + WHERE name='%1';") + .arg(name); + auto result = SelectFromDatabase(db, sql_find); + if (result.isEmpty()) + return; + auto obj = result[0].toObject(); + int id = obj["id"].toString().toInt(); + auto addr = obj["lastLoginIp"].toString(); + + if (banned) { + ExecSQL(db, QString("INSERT INTO banip VALUES('%1');").arg(addr)); + + auto p = ServerInstance->findPlayer(id); + if (p) { + p->kicked(); + } + qInfo("Banned IP %s.", addr.toUtf8().constData()); + } else { + ExecSQL(db, QString("DELETE FROM banip WHERE ip='%1';").arg(addr)); + qInfo("Unbanned IP %s.", addr.toUtf8().constData()); + } +} + +void Shell::banipCommand(QStringList &list) { + if (list.isEmpty()) { + qWarning("The 'banip' command needs at least 1 ."); + return; + } + + auto db = ServerInstance->getDatabase(); + + foreach (auto name, list) { + banIPByName(db, name, true); + } +} + +void Shell::unbanipCommand(QStringList &list) { + if (list.isEmpty()) { + qWarning("The 'unbanip' command needs at least 1 ."); + return; + } + + auto db = ServerInstance->getDatabase(); + + foreach (auto name, list) { + banIPByName(db, name, false); + } +} + Shell::Shell() { setObjectName("Shell"); signal(SIGINT, sigintHandler); @@ -173,6 +293,10 @@ Shell::Shell() { handlers["disable"] = &Shell::disableCommand; handlers["kick"] = &Shell::kickCommand; handlers["msg"] = &Shell::msgCommand; + handlers["ban"] = &Shell::banCommand; + handlers["unban"] = &Shell::unbanCommand; + handlers["banip"] = &Shell::banipCommand; + handlers["unbanip"] = &Shell::unbanipCommand; } handler_map = handlers; } diff --git a/src/server/shell.h b/src/server/shell.h index 149d6d36..0e4b1564 100644 --- a/src/server/shell.h +++ b/src/server/shell.h @@ -25,6 +25,10 @@ private: void disableCommand(QStringList &); void kickCommand(QStringList &); void msgCommand(QStringList &); + void banCommand(QStringList &); + void banipCommand(QStringList &); + void unbanCommand(QStringList &); + void unbanipCommand(QStringList &); }; #endif