diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml
index 07356fd4..1a008c74 100644
--- a/.github/workflows/build-android.yml
+++ b/.github/workflows/build-android.yml
@@ -86,6 +86,7 @@ jobs:
cp -r /etc/ssl/certs .
cp /usr/share/ca-certificates/mozilla/* certs/
echo ${FKVER%)} > fk_ver
+ ./genfkver.sh
- name: Configure CMake Project
working-directory: ${{github.workspace}}
diff --git a/.gitignore b/.gitignore
index 73076526..449381e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@
/*.kdev4
/.cache/
/tags
+/.luarc.json
# file produced by game
/FreeKill
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c14f53b3..a55a906d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,11 +39,6 @@ include_directories(include/lua)
include_directories(include)
include_directories(include/libgit2)
include_directories(src)
-include_directories(src/client)
-include_directories(src/core)
-include_directories(src/network)
-include_directories(src/server)
-include_directories(src/ui)
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
if (DEFINED FK_SERVER_ONLY)
@@ -78,6 +73,7 @@ add_custom_command(
POST_BUILD
COMMENT "Generating version file fk_ver"
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
+ COMMAND ${PROJECT_SOURCE_DIR}/genfkver.sh
)
add_subdirectory(src)
diff --git a/Fk/Config.qml b/Fk/Config.qml
index fa47a54b..452a0e09 100644
--- a/Fk/Config.qml
+++ b/Fk/Config.qml
@@ -43,6 +43,8 @@ QtObject {
property string password: ""
property string cipherText
property string aeskey
+ // string => { roomId => config }
+ property var roomConfigCache: ({})
// Client data
property string serverMotd: ""
diff --git a/Fk/Logic.js b/Fk/Logic.js
index 97991935..b098e7ef 100644
--- a/Fk/Logic.js
+++ b/Fk/Logic.js
@@ -81,6 +81,25 @@ callbacks["ErrorMsg"] = (jsonData) => {
}
}
+callbacks["ErrorDlg"] = (jsonData) => {
+ let log;
+ try {
+ const a = JSON.parse(jsonData);
+ log = qsTr(a[0]).arg(a[1]);
+ } catch (e) {
+ log = qsTr(jsonData);
+ }
+
+ console.log("ERROR: " + log);
+ Backend.showDialog("warning", log, jsonData);
+ mainWindow.busy = false;
+ if (sheduled_download !== "") {
+ mainWindow.busy = true;
+ Pacman.loadSummary(JSON.stringify(sheduled_download), true);
+ sheduled_download = "";
+ }
+}
+
callbacks["UpdatePackage"] = (jsonData) => sheduled_download = jsonData;
callbacks["UpdateBusyText"] = (jsonData) => {
diff --git a/Fk/Pages/Init.qml b/Fk/Pages/Init.qml
index ea636a9e..44d58183 100644
--- a/Fk/Pages/Init.qml
+++ b/Fk/Pages/Init.qml
@@ -183,17 +183,6 @@ Item {
}
}
- // Temp
- Button {
- text: qsTr("Making Mod")
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- visible: Debugging
- onClicked: {
- mainStack.push(modMaker);
- }
- }
-
function downloadComplete() {
toast.show(qsTr("updated packages for md5"));
}
diff --git a/Fk/Pages/Lobby.qml b/Fk/Pages/Lobby.qml
index f376fd2d..4931978c 100644
--- a/Fk/Pages/Lobby.qml
+++ b/Fk/Pages/Lobby.qml
@@ -11,112 +11,151 @@ import "Logic.js" as Logic
Item {
id: root
property alias roomModel: roomModel
+ property var roomInfoCache: ({})
property string password
- Rectangle {
- width: parent.width / 2 - roomListLayout.width / 2 - 50
- height: parent.height * 0.7
- anchors.top: exitButton.bottom
- anchors.bottom: createRoomButton.top
- anchors.right: parent.right
- anchors.rightMargin: 20
- color: "#88EEEEEE"
- radius: 6
+ Component {
+ id: roomDelegate
- Flickable {
- id: flickableContainer
- ScrollBar.vertical: ScrollBar {}
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.topMargin: 10
- flickableDirection: Flickable.VerticalFlick
- width: parent.width - 10
- height: parent.height - 10
- contentHeight: bulletin_info.height
- clip: true
+ Rectangle {
+ radius: 8
+ height: 124 - 8
+ width: 124 - 8
+ color: outdated ? "#E2E2E2" : "lightgreen"
Text {
- id: bulletin_info
- width: parent.width
- wrapMode: TextEdit.WordWrap
- textFormat: Text.MarkdownText
- text: config.serverMotd + "\n___\n" + luatr('Bulletin Info')
- onLinkActivated: Qt.openUrlExternally(link);
+ id: roomNameText
+ horizontalAlignment: Text.AlignLeft
+ width: parent.width - 16
+ height: contentHeight
+ maximumLineCount: 2
+ wrapMode: Text.WrapAnywhere
+ textFormat: Text.PlainText
+ text: roomName
+ // color: outdated ? "gray" : "black"
+ font.pixelSize: 16
+ // elide: Label.ElideRight
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: 8
+ }
+
+ Text {
+ id: roomIdText
+ text: luatr(gameMode) + ' #' + roomId
+ anchors.top: roomNameText.bottom
+ anchors.left: roomNameText.left
+ }
+
+ Image {
+ source: AppPath + "/image/button/skill/locked.png"
+ visible: hasPassword
+ scale: 0.8
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.margins: -4
+ }
+
+ Text {
+ color: (playerNum == capacity) ? "red" : "black"
+ text: playerNum + "/" + capacity
+ font.pixelSize: 18
+ font.bold: true
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 8
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+ }
+
+ TapHandler {
+ gesturePolicy: TapHandler.WithinBounds
+ enabled: !opTimer.running && !outdated
+
+ onTapped: {
+ lobby_dialog.sourceComponent = roomDetailDialog;
+ lobby_dialog.item.roomData = {
+ roomId, roomName, gameMode, playerNum, capacity,
+ hasPassword, outdated,
+ };
+ lobby_dialog.item.roomConfig = config.roomConfigCache?.[config.serverAddr]?.[roomId]
+ lobby_drawer.open();
+ }
}
}
}
Component {
- id: roomDelegate
+ id: roomDetailDialog
+ ColumnLayout {
+ property var roomData: ({
+ roomName: "",
+ hasPassword: true,
+ })
+ property var roomConfig: undefined
+ signal finished()
+ anchors.fill: parent
+ anchors.margins: 16
- Item {
- height: 48
- width: roomList.width
+ Text {
+ text: roomData.roomName
+ font.pixelSize: 18
+ }
+
+ Text {
+ font.pixelSize: 18
+ text: {
+ let ret = luatr(roomData.gameMode);
+ ret += (' #' + roomData.roomId);
+ ret += (' ' + roomData.playerNum + '/' + roomData.capacity);
+ return ret;
+ }
+ }
+
+ Item { Layout.fillHeight: true }
+
+ // Dummy
+ Text {
+ text: "在未来的版本中这一块区域将增加更多实用的功能,
"+
+ "例如直接查看房间的各种配置信息
"+
+ "以及更多与禁将有关的实用功能!"+
+ "注:绿色按钮为试做型UI 后面优化"
+ font.pixelSize: 18
+ }
RowLayout {
- anchors.fill: parent
- spacing: 16
+ Layout.fillWidth: true
Text {
- text: roomId
- color: "grey"
+ visible: roomData.hasPassword
+ text: luatr("Please input room's password")
}
- Text {
- horizontalAlignment: Text.AlignLeft
+ TextField {
+ id: passwordEdit
+ visible: roomData.hasPassword
Layout.fillWidth: true
- text: {
- let ret = roomName;
- if (outdated) {
- ret = '' + ret + '';
- }
- return ret;
- }
- font.pixelSize: 20
- elide: Label.ElideRight
+ onTextChanged: root.password = text;
}
Item {
- Layout.preferredWidth: 16
- Image {
- source: AppPath + "/image/button/skill/locked.png"
- visible: hasPassword
- anchors.centerIn: parent
- scale: 0.8
- }
- }
-
- Text {
- text: luatr(gameMode)
- }
-
- Text {
- color: (playerNum == capacity) ? "red" : "black"
- text: playerNum + "/" + capacity
- font.pixelSize: 20
- font.bold: true
+ visible: !roomData.hasPassword
+ Layout.fillWidth: true
}
Button {
- text: (playerNum < capacity) ? luatr("Enter") :
- luatr("Observe")
-
- enabled: !opTimer.running && !outdated
-
+ // text: "OK"
+ text: (roomData.playerNum < roomData.capacity) ? luatr("Enter") : luatr("Observe")
onClicked: {
- opTimer.start();
- if (hasPassword) {
- lobby_dialog.sourceComponent = enterPassword;
- lobby_dialog.item.roomId = roomId;
- lobby_dialog.item.playerNum = playerNum;
- lobby_dialog.item.capacity = capacity;
- lobby_drawer.open();
- } else {
- enterRoom(roomId, playerNum, capacity, "");
- }
+ enterRoom(roomData.roomId, roomData.playerNum, roomData.capacity,
+ roomData.hasPassword ? root.password : "");
+ lobby_dialog.item.finished();
}
}
}
+
+ Component.onCompleted: {
+ passwordEdit.text = "";
+ }
}
}
@@ -124,8 +163,7 @@ Item {
id: roomModel
}
- PersonalSettings {
- }
+ PersonalSettings {}
Timer {
id: opTimer
@@ -134,69 +172,132 @@ Item {
ColumnLayout {
id: roomListLayout
- anchors.top: parent.top
- anchors.topMargin: 10
- anchors.horizontalCenter: parent.horizontalCenter
- width: root.width * 0.48
- height: root.height - 80
- Button {
- Layout.alignment: Qt.AlignRight
- text: luatr("Refresh Room List")
- enabled: !opTimer.running
- onClicked: {
- opTimer.start();
- ClientInstance.notifyServer("RefreshRoomList", "");
+ height: root.height - 72
+ y: 16
+ anchors.left: parent.left
+ anchors.leftMargin: root.width * 0.03 + root.width * 0.94 * 0.8 % 128 / 2
+ width: {
+ let ret = root.width * 0.94 * 0.8;
+ ret -= ret % 128;
+ return ret;
+ }
+ clip: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Item { Layout.fillWidth: true }
+ Button {
+ Layout.alignment: Qt.AlignRight
+ text: luatr("Refresh Room List").arg(roomModel.count)
+ enabled: !opTimer.running
+ onClicked: {
+ opTimer.start();
+ ClientInstance.notifyServer("RefreshRoomList", "");
+ }
+ }
+ Button {
+ text: luatr("Create Room")
+ onClicked: {
+ lobby_dialog.sourceComponent =
+ Qt.createComponent("../LobbyElement/CreateRoom.qml");
+ lobby_drawer.open();
+ config.observing = false;
+ config.replaying = false;
+ }
}
}
- Item {
- Layout.fillWidth: true
+
+ GridView {
+ id: roomList
+ cellWidth: 128
+ cellHeight: 128
Layout.fillHeight: true
- Rectangle {
- anchors.fill: parent
- anchors.centerIn: parent
- color: "#88EEEEEE"
- radius: 16
- Text {
- width: parent.width
- horizontalAlignment: Text.AlignHCenter
- text: luatr("Room List").arg(roomModel.count)
- }
- ListView {
- id: roomList
- height: parent.height * 0.9
- width: parent.width * 0.95
- contentHeight: roomDelegate.height * count
- ScrollBar.vertical: ScrollBar {}
- anchors.centerIn: parent
- delegate: roomDelegate
- clip: true
- model: roomModel
- }
- }
+ Layout.fillWidth: true
+ ScrollBar.vertical: ScrollBar {}
+ delegate: roomDelegate
+ clip: true
+ model: roomModel
}
}
- Button {
- id: createRoomButton
- anchors.bottom: buttonRow.top
+ Rectangle {
+ id: serverInfoLayout
+ height: root.height - 112
+ y: 56
+ width: root.width * 0.94 * 0.2
anchors.right: parent.right
- width: 120
- display: AbstractButton.TextUnderIcon
- icon.name: "media-playback-start"
- text: luatr("Create Room")
- onClicked: {
- lobby_dialog.sourceComponent =
- Qt.createComponent("../LobbyElement/CreateRoom.qml");
- lobby_drawer.open();
- config.observing = false;
- config.replaying = false;
+ anchors.rightMargin: root.width * 0.03
+ // anchors.horizontalCenter: parent.horizontalCenter
+ color: "#88EEEEEE"
+ property bool chatShown: true
+
+ Flickable {
+ ScrollBar.vertical: ScrollBar {}
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ flickableDirection: Flickable.VerticalFlick
+ width: parent.width - 10
+ height: parent.height - 10 - (parent.chatShown ? 200 : 0)
+ contentHeight: bulletin_info.height
+ clip: true
+
+ Text {
+ id: bulletin_info
+ width: parent.width
+ wrapMode: TextEdit.WordWrap
+ textFormat: Text.MarkdownText
+ text: config.serverMotd + "\n\n___\n\n" + luatr('Bulletin Info')
+ onLinkActivated: Qt.openUrlExternally(link);
+ }
+ }
+
+ MetroButton {
+ text: "🗨️" + (parent.chatShown ? "➖" : "➕")
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: lobbyChat.top
+ onClicked: {
+ parent.chatShown = !parent.chatShown
+ }
+ }
+
+ ChatBox {
+ id: lobbyChat
+ width: parent.width
+ height: parent.chatShown ? 200 : 0
+ Behavior on height { NumberAnimation { duration: 200 } }
+ anchors.bottom: parent.bottom
+ isLobby: true
+ color: "#88EEEEEE"
+ clip: true
}
}
RowLayout {
id: buttonRow
- anchors.right: parent.right
+ anchors.left: parent.left
anchors.bottom: parent.bottom
+ width: parent.width
+
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.preferredWidth: childrenRect.width + 48
+
+ gradient: Gradient {
+ orientation: Gradient.Horizontal
+ GradientStop { position: 0.8; color: "white" }
+ GradientStop { position: 1.0; color: "transparent" }
+ }
+ Text {
+ x: 16; y: 4
+ font.pixelSize: 16
+ text: luatr("$OnlineInfo")
+ .arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
+ + "Powered by FreeKill " + FkVersion
+ }
+ }
+
+ Item { Layout.fillWidth: true }
Button {
text: luatr("Generals Overview")
onClicked: {
@@ -276,39 +377,6 @@ Item {
}
}
- Component {
- id: enterPassword
- ColumnLayout {
- property int roomId
- property int playerNum
- property int capacity
- signal finished()
- anchors.fill: parent
- anchors.margins: 16
-
- Text {
- text: luatr("Please input room's password")
- }
-
- TextField {
- id: passwordEdit
- onTextChanged: root.password = text;
- }
-
- Button {
- text: "OK"
- onClicked: {
- enterRoom(roomId, playerNum, capacity, root.password);
- parent.finished();
- }
- }
-
- Component.onCompleted: {
- passwordEdit.text = "";
- }
- }
- }
-
function enterRoom(roomId, playerNum, capacity, pw) {
config.replaying = false;
if (playerNum < capacity) {
@@ -333,40 +401,15 @@ Item {
property int lobbyPlayerNum: 0
property int serverPlayerNum: 0
+ /*
function updateOnlineInfo() {
}
onLobbyPlayerNumChanged: updateOnlineInfo();
onServerPlayerNumChanged: updateOnlineInfo();
- Rectangle {
- id: info
- color: "#88EEEEEE"
- width: root.width * 0.23 // childrenRect.width + 8
- height: childrenRect.height + 4
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- radius: 4
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- x: 4; y: 2
- font.pixelSize: 16
- text: luatr("$OnlineInfo")
- .arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
- + "Powered by FreeKill " + FkVersion
- }
- }
-
- ChatBox {
- id: lobbyChat
- anchors.bottom: info.top
- width: info.width
- height: root.height * 0.6
- isLobby: true
- color: "#88EEEEEE"
- radius: 4
- }
+ /*
+ */
Danmaku {
id: danmaku
diff --git a/Fk/main.qml b/Fk/main.qml
index 7e454912..469fea57 100644
--- a/Fk/main.qml
+++ b/Fk/main.qml
@@ -53,7 +53,6 @@ Window {
Component { id: init; Init {} }
Component { id: packageManage; PackageManage {} }
- Component { id: modMaker; ModMaker {} }
Component { id: lobby; Lobby {} }
Component { id: generalsOverview; GeneralsOverview {} }
Component { id: cardsOverview; CardsOverview {} }
diff --git a/genfkver.sh b/genfkver.sh
new file mode 100755
index 00000000..e7809bab
--- /dev/null
+++ b/genfkver.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# 为fk_ver文件追加编译时相关文件列表
+# 类似其他项目中flist.txt的功能
+
+cd $(dirname $0)
+sed -i '2,$d' ./fk_ver
+
+fn() {
+ for f in $(ls -1 $1); do
+ if [ -d $1/$f ]; then
+ fn $1/$f
+ else
+ echo $1/$f >> ./fk_ver
+ fi
+ done
+}
+
+fn lua
+fn Fk
+cd -
diff --git a/lang/zh_CN.ts b/lang/zh_CN.ts
index f7c58d6d..d033ea87 100644
--- a/lang/zh_CN.ts
+++ b/lang/zh_CN.ts
@@ -113,6 +113,38 @@
+
+ QmlBackend
+
+
+ 新月杀
+
+
+
+ 提示:请检查密码是否泄漏
+
+
+
+ 提示:请尝试重新启动程序
+
+
+
+ 提示:此为永久封禁,请联系管理员说明
+
+
+
+ 提示:此为暂时封禁,一般在约二十分钟后自动解禁
+
+
+
+ 提示:请联系服主解决
+
+
+
+ 提示:可能该用户名已被占用,或者密码错误,如果你是初次注册的话考虑用另一个用户名密码进行登入
+
+
+
Init
@@ -292,7 +324,15 @@
- 其他人用你的用户名和密码登陆到了服务器,请检查密码是否泄漏
+ 其他人用你的用户名和密码登陆到了服务器
+
+
+
+ 服务端解密密码时出现未知错误
+
+
+
+ 你不在该服务器的白名单中!
diff --git a/sgs b/sgs
new file mode 100644
index 00000000..804335a2
--- /dev/null
+++ b/sgs
@@ -0,0 +1,10 @@
+{
+ "banwords": [ "习近", "近平", "共产党", "介石", "刘少奇", "邓小平", "江泽民", "胡锦涛", "毛泽东" ],
+ "description": "新月杀 [0.4.15] 主力联机服务器!请素质交流、理性对局!交流请去贴吧[新月杀]吧",
+ "iconUrl": "http://175.178.66.93/ba-freekill.png",
+ "capacity": 800,
+ "tempBanTime": 15,
+ "motd": "6.5更新\n\n手杀测试服:司马孚、成济、SP毌丘俭、李昭焦伯;十周年(一将24获奖版初稿):宣公主、徐琨、令狐愚、司马孚\n\n6.3~6.4更新\n\nOL:界法正、蒋琬(注:暂不实现禁用手牌排序,且点击“牌序”按钮并不影响真实顺序,如不小心点击则通过点击武将上的“自若”标记查看真实顺序);十周年:韩嵩、马铁;线下:周姬、鄂焕\n\n5.31~6.1更新\n\n十周年:乐诸葛果、小孙权、乐邹氏、乐祢衡、谋张绣;\n\n\n\n请为新月杀的Github仓库点一个star吧!感谢! https://github.com/Notify-ctrl/FreeKill\n\n## 点此查看游玩教程: https://fkbook-all-in-one.readthedocs.io",
+ "hiddenPacks": [],
+ "enableBots": false
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5f83432e..2d6473b8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,10 +8,14 @@ set(freekill_SRCS
"network/server_socket.cpp"
"network/client_socket.cpp"
"network/router.cpp"
+ "server/auth.cpp"
"server/server.cpp"
"server/serverplayer.cpp"
+ "server/roombase.cpp"
+ "server/lobby.cpp"
"server/room.cpp"
"server/roomthread.cpp"
+ "server/scheduler.cpp"
"ui/qmlbackend.cpp"
"swig/freekill-wrap.cxx"
)
@@ -21,7 +25,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
"client/client.cpp"
"client/clientplayer.cpp"
"client/replayer.cpp"
- "ui/mod.cpp"
+ # "ui/mod.cpp"
)
endif ()
diff --git a/src/client/client.cpp b/src/client/client.cpp
index a9578520..8b336abc 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -1,13 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "client.h"
-#include "client_socket.h"
-#include "clientplayer.h"
-#include "qmlbackend.h"
-#include "util.h"
-#include "server.h"
-#include
-#include
+#include "client/client.h"
+#include "client/clientplayer.h"
+#include "ui/qmlbackend.h"
+#include "core/util.h"
+#include "server/server.h"
+#include "network/client_socket.h"
Client *ClientInstance = nullptr;
ClientPlayer *Self = nullptr;
diff --git a/src/client/client.h b/src/client/client.h
index f5d0d7ab..db362d19 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -3,12 +3,11 @@
#ifndef _CLIENT_H
#define _CLIENT_H
-#include "router.h"
-#include "clientplayer.h"
-#include
+#include "network/router.h"
+#include "client/clientplayer.h"
#ifndef FK_SERVER_ONLY
-#include "qmlbackend.h"
+#include "ui/qmlbackend.h"
#endif
class Client : public QObject {
diff --git a/src/client/clientplayer.cpp b/src/client/clientplayer.cpp
index 462a1143..a1ca578f 100644
--- a/src/client/clientplayer.cpp
+++ b/src/client/clientplayer.cpp
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "clientplayer.h"
+#include "client/clientplayer.h"
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
setId(id);
diff --git a/src/client/clientplayer.h b/src/client/clientplayer.h
index 31d5bea1..47a96f97 100644
--- a/src/client/clientplayer.h
+++ b/src/client/clientplayer.h
@@ -3,7 +3,7 @@
#ifndef _CLIENTPLAYER_H
#define _CLIENTPLAYER_H
-#include "player.h"
+#include "core/player.h"
class ClientPlayer : public Player {
Q_OBJECT
diff --git a/src/client/replayer.cpp b/src/client/replayer.cpp
index d31800f3..a30b0c52 100644
--- a/src/client/replayer.cpp
+++ b/src/client/replayer.cpp
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "replayer.h"
-#include "client.h"
-#include "qmlbackend.h"
-#include "util.h"
+#include "client/replayer.h"
+#include "client/client.h"
+#include "ui/qmlbackend.h"
+#include "core/util.h"
Replayer::Replayer(QObject *parent, const QString &filename) :
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),
diff --git a/src/core/packman.cpp b/src/core/packman.cpp
index 520a0378..cf30461d 100644
--- a/src/core/packman.cpp
+++ b/src/core/packman.cpp
@@ -1,10 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "packman.h"
+#include "core/packman.h"
#include "git2.h"
-#include "util.h"
-#include "qmlbackend.h"
-#include
+#include "core/util.h"
+#include "ui/qmlbackend.h"
PackMan *Pacman;
@@ -70,13 +69,16 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
auto obj = e.toObject();
auto name = obj["name"].toString();
auto url = obj["url"].toString();
-#ifndef FK_SERVER_ONLY
- Backend->showToast(tr("[%1/%2] upgrading package '%3'").arg(i).arg(arr.count()).arg(name));
-#endif
+ bool toast_showed = false;
if (SelectFromDatabase(
db,
QString("SELECT name FROM packages WHERE name='%1';").arg(name))
.isEmpty()) {
+#ifndef FK_SERVER_ONLY
+ Backend->showToast(tr("[%1/%2] upgrading package '%3'")
+ .arg(i).arg(arr.count()).arg(name));
+ toast_showed = true;
+#endif
downloadNewPack(url);
}
ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'")
@@ -85,6 +87,11 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
enablePack(name);
if (head(name) != obj["hash"].toString()) {
+#ifndef FK_SERVER_ONLY
+ if (!toast_showed)
+ Backend->showToast(tr("[%1/%2] upgrading package '%3'")
+ .arg(i).arg(arr.count()).arg(name));
+#endif
updatePack(name);
}
}
@@ -171,7 +178,7 @@ void PackMan::updatePack(const QString &pack) {
if (error != 0) {
#ifndef FK_SERVER_ONLY
if (Backend != nullptr) {
- Backend->showToast(tr("packages/%1: some error occured.").arg(pack));
+ Backend->dialog("critical", tr("packages/%1: some error occured.").arg(pack));
}
#endif
return;
@@ -193,7 +200,7 @@ void PackMan::upgradePack(const QString &pack) {
if (error != 0) {
#ifndef FK_SERVER_ONLY
if (Backend != nullptr) {
- Backend->showToast(tr("packages/%1: some error occured.").arg(pack));
+ Backend->showDialog("critical", tr("packages/%1: some error occured.").arg(pack));
}
#endif
return;
diff --git a/src/core/packman.h b/src/core/packman.h
index b1dd4b90..3269dcb3 100644
--- a/src/core/packman.h
+++ b/src/core/packman.h
@@ -3,8 +3,6 @@
#ifndef _PACKMAN_H
#define _PACKMAN_H
-#include
-
// 管理拓展包所需的类,本质上是libgit2接口的再封装。
class PackMan : public QObject {
Q_OBJECT
diff --git a/src/core/player.cpp b/src/core/player.cpp
index 65685b4f..3f1f7cb1 100644
--- a/src/core/player.cpp
+++ b/src/core/player.cpp
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "player.h"
+#include "core/player.h"
Player::Player(QObject *parent)
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),
diff --git a/src/core/util.cpp b/src/core/util.cpp
index c14c3293..6c517955 100644
--- a/src/core/util.cpp
+++ b/src/core/util.cpp
@@ -1,10 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "util.h"
-#include "packman.h"
-#include
-#include
-#include
+#include "core/util.h"
+#include "core/packman.h"
#include
extern "C" {
@@ -172,6 +169,17 @@ static void writeDirMD5(QFile &dest, const QString &dir,
}
}
+static void writeFkVerMD5(QFile &dest) {
+ QFile flist("fk_ver");
+ if (flist.exists() && flist.open(QIODevice::ReadOnly)) {
+ while (true) {
+ QByteArray bytes = flist.readLine().simplified();
+ if (bytes.isNull()) break;
+ writeFileMD5(dest, bytes);
+ }
+ }
+}
+
QString calcFileMD5() {
// First, generate flist.txt
// flist.txt is a file contains all md5sum for code files
@@ -180,12 +188,13 @@ QString calcFileMD5() {
qFatal("Cannot open flist.txt. Quitting.");
}
+ writeFkVerMD5(flist);
writeDirMD5(flist, "packages", "*.lua");
writeDirMD5(flist, "packages", "*.qml");
writeDirMD5(flist, "packages", "*.js");
- writeDirMD5(flist, "lua", "*.lua");
- writeDirMD5(flist, "Fk", "*.qml");
- writeDirMD5(flist, "Fk", "*.js");
+ // writeDirMD5(flist, "lua", "*.lua");
+ // writeDirMD5(flist, "Fk", "*.qml");
+ // writeDirMD5(flist, "Fk", "*.js");
// then, return flist.txt's md5
flist.close();
diff --git a/src/main.cpp b/src/main.cpp
index 6ea696ce..aa8be159 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "client.h"
-#include "util.h"
+#include "client/client.h"
+#include "core/util.h"
using namespace fkShell;
-#include "packman.h"
-#include "server.h"
+#include "core/packman.h"
+#include "server/server.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
-#include "shell.h"
+#include "server/shell.h"
#endif
#if defined(Q_OS_WIN32)
@@ -22,7 +22,7 @@ using namespace fkShell;
#ifndef Q_OS_ANDROID
#include
#endif
-#include "qmlbackend.h"
+#include "ui/qmlbackend.h"
#endif
#if defined(Q_OS_ANDROID)
@@ -113,10 +113,10 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
break;
}
- fprintf(stderr, "\r%02d/%02d ", date.month(), date.day());
+ fprintf(stderr, "%02d/%02d ", date.month(), date.day());
fprintf(stderr, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
- fprintf(file, "\r%02d/%02d ", date.month(), date.day());
+ fprintf(file, "%02d/%02d ", date.month(), date.day());
fprintf(file, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
@@ -150,8 +150,7 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
"C", localMsg.constData());
#ifndef FK_SERVER_ONLY
if (Backend != nullptr) {
- Backend->notifyUI(
- "ErrorDialog",
+ Backend->notifyUI("ErrorDialog",
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
}
#endif
@@ -329,6 +328,7 @@ int main(int argc, char *argv[]) {
#if defined(Q_OS_ANDROID)
system = "Android";
#elif defined(Q_OS_WIN32)
+ qputenv("QT_MEDIA_BACKEND", "windows");
system = "Win";
::system("chcp 65001");
#elif defined(Q_OS_LINUX)
diff --git a/src/network/client_socket.cpp b/src/network/client_socket.cpp
index de033e4b..180fc31a 100644
--- a/src/network/client_socket.cpp
+++ b/src/network/client_socket.cpp
@@ -1,9 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "client_socket.h"
+#include "network/client_socket.h"
#include
-#include
-#include
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
aes_ready = false;
@@ -35,7 +33,7 @@ void ClientSocket::connectToHost(const QString &address, ushort port) {
void ClientSocket::getMessage() {
while (socket->canReadLine()) {
auto msg = socket->readLine();
- msg = aesDecrypt(msg);
+ msg = aesDec(msg);
if (msg.startsWith("Compressed")) {
msg = msg.sliced(10);
msg = qUncompress(QByteArray::fromBase64(msg));
@@ -54,9 +52,9 @@ void ClientSocket::send(const QByteArray &msg) {
if (msg.length() >= 1024) {
auto comp = qCompress(msg);
_msg = "Compressed" + comp.toBase64();
- _msg = aesEncrypt(_msg) + "\n";
+ _msg = aesEnc(_msg) + "\n";
} else {
- _msg = aesEncrypt(msg) + "\n";
+ _msg = aesEnc(msg) + "\n";
}
socket->write(_msg);
@@ -156,7 +154,7 @@ void ClientSocket::installAESKey(const QByteArray &key) {
aes_ready = true;
}
-QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
+QByteArray ClientSocket::aesEnc(const QByteArray &in) {
if (!aes_ready) {
return in;
}
@@ -182,7 +180,7 @@ QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
return iv + out.toBase64();
}
-QByteArray ClientSocket::aesDecrypt(const QByteArray &in) {
+QByteArray ClientSocket::aesDec(const QByteArray &in) {
if (!aes_ready) {
return in;
}
diff --git a/src/network/client_socket.h b/src/network/client_socket.h
index 12dfc202..26ccd6ec 100644
--- a/src/network/client_socket.h
+++ b/src/network/client_socket.h
@@ -33,8 +33,8 @@ private slots:
void raiseError(QAbstractSocket::SocketError error);
private:
- QByteArray aesEncrypt(const QByteArray &in);
- QByteArray aesDecrypt(const QByteArray &out);
+ QByteArray aesEnc(const QByteArray &in);
+ QByteArray aesDec(const QByteArray &out);
AES_KEY aes_key;
bool aes_ready;
QTcpSocket *socket;
diff --git a/src/network/router.cpp b/src/network/router.cpp
index fcbf71a5..2475ba60 100644
--- a/src/network/router.cpp
+++ b/src/network/router.cpp
@@ -1,13 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "router.h"
-#include "client.h"
-#include "client_socket.h"
-#include "roomthread.h"
-#include
-#include "server.h"
-#include "serverplayer.h"
-#include "util.h"
+#include "network/router.h"
+#include "client/client.h"
+#include "network/client_socket.h"
+#include "server/roomthread.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "core/util.h"
Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
: QObject(parent) {
@@ -160,7 +159,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
return;
}
- Room *room = player->getRoom();
+ auto room = player->getRoom();
room->handlePacket(player, command, jsonData);
}
} else if (type & TYPE_REQUEST) {
@@ -180,10 +179,13 @@ void Router::handlePacket(const QByteArray &rawPacket) {
ServerPlayer *player = qobject_cast(parent());
player->setThinking(false);
- // qDebug() << "wake up!";
- auto room = player->getRoom();
- if (room->getThread()) {
- room->getThread()->wakeUp();
+ auto _room = player->getRoom();
+ if (!_room->isLobby()) {
+ auto room = qobject_cast(_room);
+ if (room->getThread()) {
+ room->getThread()->wakeUp(room->getId());
+ // TODO: signal
+ }
}
if (requestId != this->expectedReplyId)
diff --git a/src/network/server_socket.cpp b/src/network/server_socket.cpp
index d6e7e722..989ab90e 100644
--- a/src/network/server_socket.cpp
+++ b/src/network/server_socket.cpp
@@ -1,15 +1,22 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "server_socket.h"
-#include "client_socket.h"
+#include "network/server_socket.h"
+#include "network/client_socket.h"
+#include "server/server.h"
+#include "core/util.h"
-ServerSocket::ServerSocket() {
+ServerSocket::ServerSocket(QObject *parent) : QObject(parent) {
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this,
&ServerSocket::processNewConnection);
+
+ udpSocket = new QUdpSocket(this);
+ connect(udpSocket, &QUdpSocket::readyRead,
+ this, &ServerSocket::readPendingDatagrams);
}
bool ServerSocket::listen(const QHostAddress &address, ushort port) {
+ udpSocket->bind(port);
return server->listen(address, port);
}
@@ -20,3 +27,29 @@ void ServerSocket::processNewConnection() {
[connection]() { connection->deleteLater(); });
emit new_connection(connection);
}
+
+void ServerSocket::readPendingDatagrams() {
+ while (udpSocket->hasPendingDatagrams()) {
+ QNetworkDatagram datagram = udpSocket->receiveDatagram();
+ if (datagram.isValid()) {
+ processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
+ }
+ }
+}
+
+void ServerSocket::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
+ auto server = qobject_cast(parent());
+ if (msg == "fkDetectServer") {
+ udpSocket->writeDatagram("me", addr, port);
+ } else if (msg.startsWith("fkGetDetail,")) {
+ udpSocket->writeDatagram(JsonArray2Bytes(QJsonArray({
+ FK_VERSION,
+ server->getConfig("iconUrl"),
+ server->getConfig("description"),
+ server->getConfig("capacity"),
+ server->getPlayers().count(),
+ msg.sliced(12).constData(),
+ })), addr, port);
+ }
+ udpSocket->flush();
+}
diff --git a/src/network/server_socket.h b/src/network/server_socket.h
index 7cbd2fa2..543b017b 100644
--- a/src/network/server_socket.h
+++ b/src/network/server_socket.h
@@ -10,7 +10,7 @@ class ServerSocket : public QObject {
Q_OBJECT
public:
- ServerSocket();
+ ServerSocket(QObject *parent = nullptr);
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
@@ -20,9 +20,12 @@ signals:
private slots:
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
void processNewConnection();
+ void readPendingDatagrams();
private:
QTcpServer *server;
+ QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
+ void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
};
#endif // _SERVER_SOCKET_H
diff --git a/src/server/auth.cpp b/src/server/auth.cpp
new file mode 100644
index 00000000..9fc70ff1
--- /dev/null
+++ b/src/server/auth.cpp
@@ -0,0 +1,195 @@
+#include "server/auth.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "core/util.h"
+#include "network/client_socket.h"
+
+AuthManager::AuthManager(QObject *parent) : QObject(parent) {
+ rsa = initRSA();
+
+ QFile file("server/rsa_pub");
+ file.open(QIODevice::ReadOnly);
+ QTextStream in(&file);
+ public_key = in.readAll();
+}
+
+AuthManager::~AuthManager() noexcept {
+ RSA_free(rsa);
+}
+
+RSA *AuthManager::initRSA() {
+ RSA *rsa = RSA_new();
+ if (!QFile::exists("server/rsa_pub")) {
+ BIGNUM *bne = BN_new();
+ BN_set_word(bne, RSA_F4);
+ RSA_generate_key_ex(rsa, 2048, bne, NULL);
+
+ BIO *bp_pub = BIO_new_file("server/rsa_pub", "w+");
+ PEM_write_bio_RSAPublicKey(bp_pub, rsa);
+ BIO *bp_pri = BIO_new_file("server/rsa", "w+");
+ PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL);
+
+ BIO_free_all(bp_pub);
+ BIO_free_all(bp_pri);
+ QFile("server/rsa")
+ .setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
+ BN_free(bne);
+ }
+ FILE *keyFile = fopen("server/rsa_pub", "r");
+ PEM_read_RSAPublicKey(keyFile, &rsa, NULL, NULL);
+ fclose(keyFile);
+ keyFile = fopen("server/rsa", "r");
+ PEM_read_RSAPrivateKey(keyFile, &rsa, NULL, NULL);
+ fclose(keyFile);
+ return rsa;
+}
+
+bool AuthManager::checkClientVersion(ClientSocket *client, const QString &cver) {
+ auto server = qobject_cast(parent());
+ auto client_ver = QVersionNumber::fromString(cver);
+ auto ver = QVersionNumber::fromString(FK_VERSION);
+ int cmp = QVersionNumber::compare(ver, client_ver);
+ if (cmp != 0) {
+ auto errmsg = QString();
+ if (cmp < 0) {
+ errmsg = QString("[\"server is still on version %%2\",\"%1\"]")
+ .arg(FK_VERSION, "1");
+ } else {
+ errmsg = QString("[\"server is using version %%2, please update\",\"%1\"]")
+ .arg(FK_VERSION, "1");
+ }
+
+ server->sendEarlyPacket(client, "ErrorDlg", errmsg);
+ client->disconnectFromHost();
+ return false;
+ }
+ return true;
+}
+
+QJsonObject AuthManager::queryUserInfo(ClientSocket *client, const QString &name,
+ const QByteArray &password) {
+ auto server = qobject_cast(parent());
+ auto db = server->getDatabase();
+ auto pw = password;
+
+ auto sql_find = QString("SELECT * FROM userinfo WHERE name='%1';")
+ .arg(name);
+
+ auto result = SelectFromDatabase(db, sql_find);
+ if (result.isEmpty()) {
+ auto salt_gen = QRandomGenerator::securelySeeded();
+ auto salt = QByteArray::number(salt_gen(), 16);
+ pw.append(salt);
+ auto passwordHash =
+ QCryptographicHash::hash(pw, QCryptographicHash::Sha256).toHex();
+
+ auto sql_reg = QString("INSERT INTO userinfo (name,password,salt,\
+ avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4','%5',%6);")
+ .arg(name).arg(QString(passwordHash))
+ .arg(salt).arg("liubei").arg(client->peerAddress())
+ .arg("FALSE");
+
+ ExecSQL(db, sql_reg);
+ result = SelectFromDatabase(db, sql_find); // refresh result
+ auto obj = result[0].toObject();
+
+ auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
+ ExecSQL(db, info_update);
+ }
+
+ return result[0].toObject();
+}
+
+QJsonObject AuthManager::checkPassword(ClientSocket *client, const QString &name,
+ const QString &password) {
+
+ auto server = qobject_cast(parent());
+ bool passed = false;
+ QString error_msg;
+ QJsonObject obj;
+ int id;
+ QByteArray salt;
+ QByteArray passwordHash;
+ auto players = server->getPlayers();
+
+ auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
+ unsigned char buf[4096] = {0};
+ RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(),
+ buf, rsa, RSA_PKCS1_PADDING);
+ auto decrypted_pw =
+ QByteArray::fromRawData((const char *)buf, strlen((const char *)buf));
+
+ if (decrypted_pw.length() > 32) {
+ auto aes_bytes = decrypted_pw.first(32);
+
+ // tell client to install aes key
+ server->sendEarlyPacket(client, "InstallKey", "");
+ client->installAESKey(aes_bytes);
+ decrypted_pw.remove(0, 32);
+ } else {
+ // FIXME
+ // decrypted_pw = "\xFF";
+ error_msg = "unknown password error";
+ goto FAIL;
+ }
+
+ if (!CheckSqlString(name) || !server->checkBanWord(name)) {
+ error_msg = "invalid user name";
+ goto FAIL;
+ }
+
+ if (server->getConfig("whitelist").isArray() &&
+ !server->getConfig("whitelist").toArray().toVariantList().contains(name)) {
+ error_msg = "user name not in whitelist";
+ goto FAIL;
+ }
+
+ obj = queryUserInfo(client, name, decrypted_pw);
+
+ // check ban account
+ id = obj["id"].toString().toInt();
+ passed = obj["banned"].toString().toInt() == 0;
+ if (!passed) {
+ error_msg = "you have been banned!";
+ goto FAIL;
+ }
+
+ // check if password is the same
+ salt = obj["salt"].toString().toLatin1();
+ decrypted_pw.append(salt);
+ passwordHash =
+ QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256).toHex();
+ passed = (passwordHash == obj["password"].toString());
+ if (!passed) {
+ error_msg = "username or password error";
+ goto FAIL;
+ }
+
+ if (players.value(id)) {
+ auto player = players.value(id);
+ // 顶号机制,如果在线的话就让他变成不在线
+ if (player->getState() == Player::Online) {
+ player->doNotify("ErrorDlg", "others logged in again with this name");
+ emit player->kicked();
+ }
+
+ if (player->getState() == Player::Offline) {
+ player->reconnect(client);
+ passed = true;
+ return QJsonObject();
+ } else {
+ error_msg = "others logged in with this name";
+ passed = false;
+ }
+ }
+
+FAIL:
+ if (!passed) {
+ qInfo() << client->peerAddress() << "lost connection:" << error_msg;
+ server->sendEarlyPacket(client, "ErrorDlg", error_msg);
+ client->disconnectFromHost();
+ return QJsonObject();
+ }
+
+ return obj;
+}
diff --git a/src/server/auth.h b/src/server/auth.h
new file mode 100644
index 00000000..7619cb01
--- /dev/null
+++ b/src/server/auth.h
@@ -0,0 +1,27 @@
+#ifndef _AUTH_H
+#define _AUTH_H
+
+#include
+#include
+
+class ClientSocket;
+
+class AuthManager : public QObject {
+ Q_OBJECT
+public:
+ AuthManager(QObject *parent = nullptr);
+ ~AuthManager() noexcept;
+ auto getPublicKey() const { return public_key; }
+
+ bool checkClientVersion(ClientSocket *client, const QString &ver);
+ QJsonObject checkPassword(ClientSocket *client, const QString &name, const QString &password);
+
+private:
+ RSA *rsa;
+ QString public_key;
+
+ static RSA *initRSA();
+ QJsonObject queryUserInfo(ClientSocket *client, const QString &name, const QByteArray &password);
+};
+
+#endif // _AUTH_H
diff --git a/src/server/lobby.cpp b/src/server/lobby.cpp
new file mode 100644
index 00000000..bddcb429
--- /dev/null
+++ b/src/server/lobby.cpp
@@ -0,0 +1,162 @@
+#include "server/lobby.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "core/util.h"
+
+Lobby::Lobby(Server *server) {
+ this->server = server;
+ setParent(server);
+}
+
+void Lobby::addPlayer(ServerPlayer *player) {
+ if (!player) return;
+
+ players.append(player);
+ player->setRoom(this);
+
+ if (player->getState() == Player::Robot) {
+ removePlayer(player);
+ player->deleteLater();
+ } else {
+ player->doNotify("EnterLobby", "[]");
+ }
+
+ server->updateOnlineInfo();
+}
+
+void Lobby::removePlayer(ServerPlayer *player) {
+ players.removeOne(player);
+ server->updateOnlineInfo();
+}
+
+void Lobby::updateAvatar(ServerPlayer *sender, const QString &jsonData) {
+ auto arr = String2Json(jsonData).array();
+ auto avatar = arr[0].toString();
+
+ if (CheckSqlString(avatar)) {
+ auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
+ .arg(avatar)
+ .arg(sender->getId());
+ ExecSQL(ServerInstance->getDatabase(), sql);
+ sender->setAvatar(avatar);
+ sender->doNotify("UpdateAvatar", avatar);
+ }
+}
+
+void Lobby::updatePassword(ServerPlayer *sender, const QString &jsonData) {
+ auto arr = String2Json(jsonData).array();
+ auto oldpw = arr[0].toString();
+ auto newpw = arr[1].toString();
+ auto sql_find =
+ QString("SELECT password, salt FROM userinfo WHERE id=%1;")
+ .arg(sender->getId());
+
+ auto passed = false;
+ auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
+ auto result = arr2[0].toObject();
+ passed = (result["password"].toString() ==
+ QCryptographicHash::hash(
+ oldpw.append(result["salt"].toString()).toLatin1(),
+ QCryptographicHash::Sha256)
+ .toHex());
+ if (passed) {
+ auto sql_update =
+ QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
+ .arg(QCryptographicHash::hash(
+ newpw.append(result["salt"].toString()).toLatin1(),
+ QCryptographicHash::Sha256)
+ .toHex())
+ .arg(sender->getId());
+ ExecSQL(ServerInstance->getDatabase(), sql_update);
+ }
+
+ sender->doNotify("UpdatePassword", passed ? "1" : "0");
+}
+
+void Lobby::createRoom(ServerPlayer *sender, const QString &jsonData) {
+ auto arr = String2Json(jsonData).array();
+ auto name = arr[0].toString();
+ auto capacity = arr[1].toInt();
+ auto timeout = arr[2].toInt();
+ auto settings =
+ QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
+ ServerInstance->createRoom(sender, name, capacity, timeout, settings);
+}
+
+void Lobby::getRoomConfig(ServerPlayer *sender, const QString &jsonData) {
+ auto arr = String2Json(jsonData).array();
+ auto roomId = arr[0].toInt();
+ auto room = ServerInstance->findRoom(roomId);
+ if (room) {
+ auto settings = room->getSettings();
+ // 手搓JSON数组 跳过编码解码
+ sender->doNotify("GetRoomConfig", QString("[%1,%2]").arg(roomId).arg(settings));
+ } else {
+ sender->doNotify("ErrorMsg", "no such room");
+ }
+}
+
+void Lobby::enterRoom(ServerPlayer *sender, const QString &jsonData) {
+ auto arr = String2Json(jsonData).array();
+ auto roomId = arr[0].toInt();
+ auto room = ServerInstance->findRoom(roomId);
+ if (room) {
+ auto settings = QJsonDocument::fromJson(room->getSettings());
+ auto password = settings["password"].toString();
+ if (password.isEmpty() || arr[1].toString() == password) {
+ if (room->isOutdated()) {
+ sender->doNotify("ErrorMsg", "room is outdated");
+ } else {
+ room->addPlayer(sender);
+ }
+ } else {
+ sender->doNotify("ErrorMsg", "room password error");
+ }
+ } else {
+ sender->doNotify("ErrorMsg", "no such room");
+ }
+}
+
+void Lobby::observeRoom(ServerPlayer *sender, const QString &jsonData) {
+ auto arr = String2Json(jsonData).array();
+ auto roomId = arr[0].toInt();
+ auto room = ServerInstance->findRoom(roomId);
+ if (room) {
+ auto settings = QJsonDocument::fromJson(room->getSettings());
+ auto password = settings["password"].toString();
+ if (password.isEmpty() || arr[1].toString() == password) {
+ if (room->isOutdated()) {
+ sender->doNotify("ErrorMsg", "room is outdated");
+ } else {
+ room->addObserver(sender);
+ }
+ } else {
+ sender->doNotify("ErrorMsg", "room password error");
+ }
+ } else {
+ sender->doNotify("ErrorMsg", "no such room");
+ }
+}
+
+void Lobby::refreshRoomList(ServerPlayer *sender, const QString &) {
+ ServerInstance->updateRoomList(sender);
+};
+
+typedef void (Lobby::*room_cb)(ServerPlayer *, const QString &);
+
+void Lobby::handlePacket(ServerPlayer *sender, const QString &command,
+ const QString &jsonData) {
+ static const QMap lobby_actions = {
+ {"UpdateAvatar", &Lobby::updateAvatar},
+ {"UpdatePassword", &Lobby::updatePassword},
+ {"CreateRoom", &Lobby::createRoom},
+ {"GetRoomConfig", &Lobby::getRoomConfig},
+ {"EnterRoom", &Lobby::enterRoom},
+ {"ObserveRoom", &Lobby::observeRoom},
+ {"RefreshRoomList", &Lobby::refreshRoomList},
+ {"Chat", &Lobby::chat},
+ };
+
+ auto func = lobby_actions[command];
+ if (func) (this->*func)(sender, jsonData);
+}
diff --git a/src/server/lobby.h b/src/server/lobby.h
new file mode 100644
index 00000000..dad70931
--- /dev/null
+++ b/src/server/lobby.h
@@ -0,0 +1,27 @@
+#ifndef _LOBBY_H
+#define _LOBBY_H
+
+#include "server/roombase.h"
+
+class Lobby : public RoomBase {
+ Q_OBJECT
+ public:
+ Lobby(Server *server);
+
+ void addPlayer(ServerPlayer *player);
+ void removePlayer(ServerPlayer *player);
+
+ void handlePacket(ServerPlayer *sender, const QString &command,
+ const QString &jsonData);
+ private:
+ // for handle packet
+ void updateAvatar(ServerPlayer *, const QString &);
+ void updatePassword(ServerPlayer *, const QString &);
+ void createRoom(ServerPlayer *, const QString &);
+ void getRoomConfig(ServerPlayer *, const QString &);
+ void enterRoom(ServerPlayer *, const QString &);
+ void observeRoom(ServerPlayer *, const QString &);
+ void refreshRoomList(ServerPlayer *, const QString &);
+};
+
+#endif // _LOBBY_H
diff --git a/src/server/room.cpp b/src/server/room.cpp
index 9a27d8f3..60e20aed 100644
--- a/src/server/room.cpp
+++ b/src/server/room.cpp
@@ -1,28 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "room.h"
-
-#include
-#include
+#include "server/room.h"
+#include "server/lobby.h"
#ifdef FK_SERVER_ONLY
static void *ClientInstance = nullptr;
#else
-#include "client.h"
+#include "client/client.h"
#endif
-#include "client_socket.h"
-#include "roomthread.h"
-#include "server.h"
-#include "serverplayer.h"
-#include "util.h"
+#include "network/client_socket.h"
+#include "server/roomthread.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "core/util.h"
Room::Room(RoomThread *m_thread) {
auto server = ServerInstance;
id = server->nextRoomId;
server->nextRoomId++;
this->server = server;
- setThread(m_thread);
if (m_thread) { // In case of lobby
m_thread->addRoom(this);
}
@@ -36,14 +33,8 @@ Room::Room(RoomThread *m_thread) {
m_ready = true;
- // 如果是普通房间而不是大厅,就初始化Lua,否则置Lua为nullptr
- if (!isLobby()) {
- // 如果不是大厅,那么:
- // * 只要房间添加人了,那么从大厅中移掉这个人
- // * 只要有人离开房间,那就把他加到大厅去
- connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
- connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
- }
+ connect(this, &Room::playerAdded, server->lobby(), &Lobby::removePlayer);
+ connect(this, &Room::playerRemoved, server->lobby(), &Lobby::addPlayer);
}
Room::~Room() {
@@ -56,8 +47,6 @@ Room::~Room() {
}
}
-Server *Room::getServer() const { return server; }
-
RoomThread *Room::getThread() const { return m_thread; }
void Room::setThread(RoomThread *t) {
@@ -71,8 +60,6 @@ int Room::getId() const { return id; }
void Room::setId(int id) { this->id = id; }
-bool Room::isLobby() const { return id == 0; }
-
QString Room::getName() const { return name; }
void Room::setName(const QString &name) { this->name = name; }
@@ -88,9 +75,6 @@ const QByteArray Room::getSettings() const { return settings; }
void Room::setSettings(QByteArray settings) { this->settings = settings; }
bool Room::isAbandoned() const {
- if (isLobby())
- return false;
-
if (players.isEmpty())
return true;
@@ -151,72 +135,60 @@ void Room::addPlayer(ServerPlayer *player) {
auto mode = settings["gameMode"].toString();
// 告诉房里所有玩家有新人进来了
- if (!isLobby()) {
- jsonData << player->getId();
- jsonData << player->getScreenName();
- jsonData << player->getAvatar();
- jsonData << player->isReady();
- jsonData << player->getTotalGameTime();
- doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
- }
+ jsonData << player->getId();
+ jsonData << player->getScreenName();
+ jsonData << player->getAvatar();
+ jsonData << player->isReady();
+ jsonData << player->getTotalGameTime();
+ doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
players.append(player);
player->setRoom(this);
- if (isLobby()) {
- // 有机器人进入大厅(可能因为被踢),那么改为销毁
- if (player->getState() == Player::Robot) {
- removePlayer(player);
- player->deleteLater();
- } else {
- player->doNotify("EnterLobby", "[]");
- }
- } else {
- // Second, let the player enter room and add other players
+ // Second, let the player enter room and add other players
+ jsonData = QJsonArray();
+ jsonData << this->capacity;
+ jsonData << this->timeout;
+ jsonData << QJsonDocument::fromJson(this->settings).object();
+ player->doNotify("EnterRoom", JsonArray2Bytes(jsonData));
+
+ foreach (ServerPlayer *p, getOtherPlayers(player)) {
jsonData = QJsonArray();
- jsonData << this->capacity;
- jsonData << this->timeout;
- jsonData << QJsonDocument::fromJson(this->settings).object();
- player->doNotify("EnterRoom", JsonArray2Bytes(jsonData));
+ jsonData << p->getId();
+ jsonData << p->getScreenName();
+ jsonData << p->getAvatar();
+ jsonData << p->isReady();
+ jsonData << p->getTotalGameTime();
+ player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
- foreach (ServerPlayer *p, getOtherPlayers(player)) {
- jsonData = QJsonArray();
- jsonData << p->getId();
- jsonData << p->getScreenName();
- jsonData << p->getAvatar();
- jsonData << p->isReady();
- jsonData << p->getTotalGameTime();
- player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
-
- jsonData = QJsonArray();
- jsonData << p->getId();
- foreach (int i, p->getGameData()) {
- jsonData << i;
- }
- player->doNotify("UpdateGameData", JsonArray2Bytes(jsonData));
+ jsonData = QJsonArray();
+ jsonData << p->getId();
+ foreach (int i, p->getGameData()) {
+ jsonData << i;
}
-
- if (this->owner != nullptr) {
- jsonData = QJsonArray();
- jsonData << this->owner->getId();
- player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
- }
-
- if (player->getLastGameMode() != mode) {
- player->setLastGameMode(mode);
- updatePlayerGameData(player->getId(), mode);
- } else {
- auto jsonData = QJsonArray();
- jsonData << player->getId();
- foreach (int i, player->getGameData()) {
- jsonData << i;
- }
- doBroadcastNotify(getPlayers(), "UpdateGameData", JsonArray2Bytes(jsonData));
- }
- // 玩家手动启动
- // if (isFull() && !gameStarted)
- // start();
+ player->doNotify("UpdateGameData", JsonArray2Bytes(jsonData));
}
+
+ if (this->owner != nullptr) {
+ jsonData = QJsonArray();
+ jsonData << this->owner->getId();
+ player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
+ }
+
+ if (player->getLastGameMode() != mode) {
+ player->setLastGameMode(mode);
+ updatePlayerGameData(player->getId(), mode);
+ } else {
+ auto jsonData = QJsonArray();
+ jsonData << player->getId();
+ foreach (int i, player->getGameData()) {
+ jsonData << i;
+ }
+ doBroadcastNotify(getPlayers(), "UpdateGameData", JsonArray2Bytes(jsonData));
+ }
+ // 玩家手动启动
+ // if (isFull() && !gameStarted)
+ // start();
emit playerAdded(player);
}
@@ -251,12 +223,7 @@ void Room::removePlayer(ServerPlayer *player) {
}
emit playerRemoved(player);
- if (isLobby())
- return;
-
- QJsonArray jsonData;
- jsonData << player->getId();
- doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes(jsonData));
+ doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes({ player->getId() }));
} else {
// 否则给跑路玩家召唤个AI代打
// TODO: if the player is died..
@@ -287,7 +254,7 @@ void Room::removePlayer(ServerPlayer *player) {
// 原先的跑路机器人会在游戏结束后自动销毁掉
server->addPlayer(runner);
- m_thread->wakeUp();
+ // m_thread->wakeUp();
// 发出信号,让大厅添加这个人
emit playerRemoved(runner);
@@ -312,22 +279,6 @@ void Room::removePlayer(ServerPlayer *player) {
}
}
-QList Room::getPlayers() const { return players; }
-
-QList Room::getOtherPlayers(ServerPlayer *expect) const {
- QList others = getPlayers();
- others.removeOne(expect);
- return others;
-}
-
-ServerPlayer *Room::findPlayer(int id) const {
- foreach (ServerPlayer *p, players) {
- if (p->getId() == id)
- return p;
- }
- return nullptr;
-}
-
void Room::addObserver(ServerPlayer *player) {
// 首先只能旁观在运行的房间,因为旁观是由Lua处理的
if (!gameStarted) {
@@ -371,6 +322,10 @@ int Room::getTimeout() const { return timeout; }
void Room::setTimeout(int timeout) { this->timeout = timeout; }
+void Room::delay(int ms) {
+ m_thread->delay(id, ms);
+}
+
bool Room::isOutdated() {
bool ret = md5 != server->getMd5();
if (ret) md5 = "";
@@ -379,42 +334,6 @@ bool Room::isOutdated() {
bool Room::isStarted() const { return gameStarted; }
-void Room::doBroadcastNotify(const QList targets,
- const QString &command, const QString &jsonData) {
- foreach (ServerPlayer *p, targets) {
- p->doNotify(command, jsonData);
- }
-}
-
-void Room::chat(ServerPlayer *sender, const QString &jsonData) {
- auto doc = String2Json(jsonData).object();
- auto type = doc["type"].toInt();
- doc["sender"] = sender->getId();
-
- // 屏蔽.号,防止有人在HTML文本发链接,而正常发链接看不出来有啥改动
- auto msg = doc["msg"].toString();
- msg.replace(".", "․");
- // 300字限制,与客户端相同
- msg.erase(msg.begin() + 300, msg.end());
- doc["msg"] = msg;
- if (!server->checkBanWord(msg)) {
- return;
- }
-
- if (type == 1) {
- doc["userName"] = sender->getScreenName();
- auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
- doBroadcastNotify(players, "Chat", json);
- } else {
- auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
- doBroadcastNotify(players, "Chat", json);
- doBroadcastNotify(observers, "Chat", json);
- }
-
- qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
- doc["msg"].toString().toUtf8().constData());
-}
-
static const QString findWinRate =
QString("SELECT win, lose, draw "
"FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';");
@@ -551,18 +470,18 @@ void Room::updatePlayerGameData(int id, const QString &mode) {
auto room = player->getRoom();
player->setGameData(total, win, run);
auto data_arr = QJsonArray({ player->getId(), total, win, run });
- if (!room->isLobby()) {
- room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
- }
+ room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
}
void Room::gameOver() {
if (!gameStarted) return;
+ insideGameOver = true;
gameStarted = false;
runned_players.clear();
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
auto settings = QJsonDocument::fromJson(this->settings);
auto mode = settings["gameMode"].toString();
+ server->beginTransaction();
foreach (ServerPlayer *p, players) {
auto pid = p->getId();
@@ -578,7 +497,7 @@ void Room::gameOver() {
realPlayer->doNotify("AddTotalGameTime", bytes);
}
- // 摸了,这么写总之不会有问题
+ // 将游戏时间更新到数据库中
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
ExecSQL(server->getDatabase(), info_update);
@@ -587,18 +506,13 @@ void Room::gameOver() {
if (p->getState() != Player::Online) {
if (p->getState() == Player::Offline) {
addRunRate(pid, mode);
- // addRunRate(pid, mode);
server->temporarilyBan(pid);
}
p->deleteLater();
}
}
- // 旁观者不能在这清除,因为removePlayer逻辑不一样
- // observers.clear();
- // 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
- // players.clear();
- // owner = nullptr;
- // clearRequest();
+ server->endTransaction();
+ insideGameOver = true;
}
void Room::manuallyStart() {
@@ -620,7 +534,6 @@ void Room::pushRequest(const QString &req) {
}
void Room::addRejectId(int id) {
- if (isLobby()) return;
rejected_players << id;
}
@@ -629,184 +542,84 @@ void Room::removeRejectId(int id) {
}
// ------------------------------------------------
-static void updateAvatar(ServerPlayer *sender, const QString &jsonData) {
- auto arr = String2Json(jsonData).array();
- auto avatar = arr[0].toString();
-
- if (CheckSqlString(avatar)) {
- auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
- .arg(avatar)
- .arg(sender->getId());
- ExecSQL(ServerInstance->getDatabase(), sql);
- sender->setAvatar(avatar);
- sender->doNotify("UpdateAvatar", avatar);
- }
-}
-
-static void updatePassword(ServerPlayer *sender, const QString &jsonData) {
- auto arr = String2Json(jsonData).array();
- auto oldpw = arr[0].toString();
- auto newpw = arr[1].toString();
- auto sql_find =
- QString("SELECT password, salt FROM userinfo WHERE id=%1;")
- .arg(sender->getId());
-
- auto passed = false;
- auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
- auto result = arr2[0].toObject();
- passed = (result["password"].toString() ==
- QCryptographicHash::hash(
- oldpw.append(result["salt"].toString()).toLatin1(),
- QCryptographicHash::Sha256)
- .toHex());
- if (passed) {
- auto sql_update =
- QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
- .arg(QCryptographicHash::hash(
- newpw.append(result["salt"].toString()).toLatin1(),
- QCryptographicHash::Sha256)
- .toHex())
- .arg(sender->getId());
- ExecSQL(ServerInstance->getDatabase(), sql_update);
- }
-
- sender->doNotify("UpdatePassword", passed ? "1" : "0");
-}
-
-static void createRoom(ServerPlayer *sender, const QString &jsonData) {
- auto arr = String2Json(jsonData).array();
- auto name = arr[0].toString();
- auto capacity = arr[1].toInt();
- auto timeout = arr[2].toInt();
- auto settings =
- QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
- ServerInstance->createRoom(sender, name, capacity, timeout, settings);
-}
-
-static void enterRoom(ServerPlayer *sender, const QString &jsonData) {
- auto arr = String2Json(jsonData).array();
- auto roomId = arr[0].toInt();
- auto room = ServerInstance->findRoom(roomId);
- if (room) {
- auto settings = QJsonDocument::fromJson(room->getSettings());
- auto password = settings["password"].toString();
- if (password.isEmpty() || arr[1].toString() == password) {
- if (room->isOutdated()) {
- sender->doNotify("ErrorMsg", "room is outdated");
- } else {
- room->addPlayer(sender);
- }
- } else {
- sender->doNotify("ErrorMsg", "room password error");
- }
- } else {
- sender->doNotify("ErrorMsg", "no such room");
- }
-}
-
-static void observeRoom(ServerPlayer *sender, const QString &jsonData) {
- auto arr = String2Json(jsonData).array();
- auto roomId = arr[0].toInt();
- auto room = ServerInstance->findRoom(roomId);
- if (room) {
- auto settings = QJsonDocument::fromJson(room->getSettings());
- auto password = settings["password"].toString();
- if (password.isEmpty() || arr[1].toString() == password) {
- if (room->isOutdated()) {
- sender->doNotify("ErrorMsg", "room is outdated");
- } else {
- room->addObserver(sender);
- }
- } else {
- sender->doNotify("ErrorMsg", "room password error");
- }
- } else {
- sender->doNotify("ErrorMsg", "no such room");
- }
-}
-
-static void refreshRoomList(ServerPlayer *sender, const QString &) {
- ServerInstance->updateRoomList(sender);
-};
-
-static void quitRoom(ServerPlayer *player, const QString &) {
- auto room = player->getRoom();
- room->removePlayer(player);
- if (room->isOutdated()) {
+void Room::quitRoom(ServerPlayer *player, const QString &) {
+ removePlayer(player);
+ if (isOutdated()) {
player->kicked();
}
}
-static void addRobot(ServerPlayer *player, const QString &) {
- auto room = player->getRoom();
+void Room::addRobotRequest(ServerPlayer *player, const QString &) {
if (ServerInstance->getConfig("enableBots").toBool())
- room->addRobot(player);
+ addRobot(player);
}
-static void kickPlayer(ServerPlayer *player, const QString &jsonData) {
- auto room = player->getRoom();
+void Room::kickPlayer(ServerPlayer *player, const QString &jsonData) {
int i = jsonData.toInt();
- auto p = room->findPlayer(i);
- if (p && !room->isStarted()) {
- room->removePlayer(p);
- room->addRejectId(i);
- QTimer::singleShot(30000, room, [=]() {
- room->removeRejectId(i);
+ auto p = findPlayer(i);
+ if (p && !isStarted()) {
+ removePlayer(p);
+ addRejectId(i);
+ QTimer::singleShot(30000, this, [=]() {
+ removeRejectId(i);
});
}
}
-static void ready(ServerPlayer *player, const QString &) {
- auto room = player->getRoom();
+void Room::ready(ServerPlayer *player, const QString &) {
player->setReady(!player->isReady());
- room->doBroadcastNotify(room->getPlayers(), "ReadyChanged",
+ doBroadcastNotify(getPlayers(), "ReadyChanged",
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
}
-static void startGame(ServerPlayer *player, const QString &) {
- auto room = player->getRoom();
- if (room->isOutdated()) {
- foreach (auto p, room->getPlayers()) {
+void Room::startGame(ServerPlayer *player, const QString &) {
+ if (isOutdated()) {
+ foreach (auto p, getPlayers()) {
p->doNotify("ErrorMsg", "room is outdated");
p->kicked();
}
} else {
- room->manuallyStart();
+ manuallyStart();
}
}
-typedef void (*room_cb)(ServerPlayer *, const QString &);
-static const QMap lobby_actions = {
- {"UpdateAvatar", updateAvatar},
- {"UpdatePassword", updatePassword},
- {"CreateRoom", createRoom},
- {"EnterRoom", enterRoom},
- {"ObserveRoom", observeRoom},
- {"RefreshRoomList", refreshRoomList},
-};
-
-static const QMap room_actions = {
- {"QuitRoom", quitRoom},
- {"AddRobot", addRobot},
- {"KickPlayer", kickPlayer},
- {"Ready", ready},
- {"StartGame", startGame},
-};
+typedef void (Room::*room_cb)(ServerPlayer *, const QString &);
void Room::handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData) {
- if (command == "Chat") {
- chat(sender, jsonData);
+ static const QMap room_actions = {
+ {"QuitRoom", &Room::quitRoom},
+ {"AddRobot", &Room::addRobotRequest},
+ {"KickPlayer", &Room::kickPlayer},
+ {"Ready", &Room::ready},
+ {"StartGame", &Room::startGame},
+ {"Chat", &Room::chat},
+ };
+
+ if (command == "PushRequest") {
+ pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
return;
- } else if (command == "PushRequest") {
- if (!isLobby())
- pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
}
- auto func_table = lobby_actions;
- if (!isLobby()) func_table = room_actions;
- auto func = func_table[command];
- if (func) {
- func(sender, jsonData);
- }
+ auto func = room_actions[command];
+ if (func) (this->*func)(sender, jsonData);
+}
+
+// Lua用:request之前设置计时器防止等到死。
+void Room::setRequestTimer(int ms) {
+ request_timer = new QTimer();
+ request_timer->setSingleShot(true);
+ request_timer->setInterval(ms);
+ connect(request_timer, &QTimer::timeout, this, [=](){
+ m_thread->wakeUp(id);
+ });
+ request_timer->start();
+}
+
+// Lua用:当request完成后手动销毁计时器。
+void Room::destroyRequestTimer() {
+ if (!request_timer) return;
+ request_timer->stop();
+ delete request_timer;
+ request_timer = nullptr;
}
diff --git a/src/server/room.h b/src/server/room.h
index 221be5b8..ab671686 100644
--- a/src/server/room.h
+++ b/src/server/room.h
@@ -3,11 +3,13 @@
#ifndef _ROOM_H
#define _ROOM_H
+#include "server/roombase.h"
+
class Server;
class ServerPlayer;
class RoomThread;
-class Room : public QObject {
+class Room : public RoomBase {
Q_OBJECT
public:
explicit Room(RoomThread *m_thread);
@@ -15,12 +17,11 @@ class Room : public QObject {
// Property reader & setter
// ==================================={
- Server *getServer() const;
RoomThread *getThread() const;
void setThread(RoomThread *t);
+
int getId() const;
void setId(int id);
- bool isLobby() const;
QString getName() const;
void setName(const QString &name);
int getCapacity() const;
@@ -38,9 +39,6 @@ class Room : public QObject {
void addPlayer(ServerPlayer *player);
void addRobot(ServerPlayer *player);
void removePlayer(ServerPlayer *player);
- QList getPlayers() const;
- QList getOtherPlayers(ServerPlayer *expect) const;
- ServerPlayer *findPlayer(int id) const;
void addObserver(ServerPlayer *player);
void removeObserver(ServerPlayer *player);
@@ -49,16 +47,13 @@ class Room : public QObject {
int getTimeout() const;
void setTimeout(int timeout);
+ void delay(int ms);
bool isOutdated();
bool isStarted() const;
// ====================================}
- void doBroadcastNotify(const QList targets,
- const QString &command, const QString &jsonData);
- void chat(ServerPlayer *sender, const QString &jsonData);
-
void updateWinRate(int id, const QString &general, const QString &mode,
int result, bool dead);
void gameOver();
@@ -71,6 +66,13 @@ class Room : public QObject {
// router用
void handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData);
+
+ void setRequestTimer(int ms);
+ void destroyRequestTimer();
+
+ // FIXME
+ volatile bool insideGameOver = false;
+
signals:
void abandoned();
@@ -78,8 +80,7 @@ class Room : public QObject {
void playerRemoved(ServerPlayer *player);
private:
- Server *server;
- RoomThread *m_thread;
+ RoomThread *m_thread = nullptr;
int id; // Lobby's id is 0
QString name; // “阴间大乱斗”
int capacity; // by default is 5, max is 8
@@ -87,8 +88,6 @@ class Room : public QObject {
bool m_abandoned; // If room is empty, delete it
ServerPlayer *owner; // who created this room?
- QList players;
- QList observers;
QList runned_players;
QList rejected_players;
int robot_id;
@@ -98,8 +97,17 @@ class Room : public QObject {
int timeout;
QString md5;
+ QTimer *request_timer = nullptr;
+
void addRunRate(int id, const QString &mode);
void updatePlayerGameData(int id, const QString &mode);
+
+ // handle packet
+ void quitRoom(ServerPlayer *, const QString &);
+ void addRobotRequest(ServerPlayer *, const QString &);
+ void kickPlayer(ServerPlayer *, const QString &);
+ void ready(ServerPlayer *, const QString &);
+ void startGame(ServerPlayer *, const QString &);
};
#endif // _ROOM_H
diff --git a/src/server/roombase.cpp b/src/server/roombase.cpp
new file mode 100644
index 00000000..0abb85db
--- /dev/null
+++ b/src/server/roombase.cpp
@@ -0,0 +1,62 @@
+#include "server/roombase.h"
+#include "server/serverplayer.h"
+#include "server/server.h"
+#include "core/util.h"
+
+Server *RoomBase::getServer() const { return server; }
+
+bool RoomBase::isLobby() const {
+ return inherits("Lobby");
+}
+
+QList RoomBase::getPlayers() const { return players; }
+
+QList RoomBase::getOtherPlayers(ServerPlayer *expect) const {
+ QList others = getPlayers();
+ others.removeOne(expect);
+ return others;
+}
+
+ServerPlayer *RoomBase::findPlayer(int id) const {
+ foreach (ServerPlayer *p, players) {
+ if (p->getId() == id)
+ return p;
+ }
+ return nullptr;
+}
+
+void RoomBase::doBroadcastNotify(const QList targets,
+ const QString &command, const QString &jsonData) {
+ foreach (ServerPlayer *p, targets) {
+ p->doNotify(command, jsonData);
+ }
+}
+
+void RoomBase::chat(ServerPlayer *sender, const QString &jsonData) {
+ auto doc = String2Json(jsonData).object();
+ auto type = doc["type"].toInt();
+ doc["sender"] = sender->getId();
+
+ // 屏蔽.号,防止有人在HTML文本发链接,而正常发链接看不出来有啥改动
+ auto msg = doc["msg"].toString();
+ msg.replace(".", "․");
+ // 300字限制,与客户端相同
+ msg.erase(msg.begin() + 300, msg.end());
+ doc["msg"] = msg;
+ if (!server->checkBanWord(msg)) {
+ return;
+ }
+
+ if (type == 1) {
+ doc["userName"] = sender->getScreenName();
+ auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
+ doBroadcastNotify(players, "Chat", json);
+ } else {
+ auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
+ doBroadcastNotify(players, "Chat", json);
+ doBroadcastNotify(observers, "Chat", json);
+ }
+
+ qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
+ doc["msg"].toString().toUtf8().constData());
+}
diff --git a/src/server/roombase.h b/src/server/roombase.h
new file mode 100644
index 00000000..ef749949
--- /dev/null
+++ b/src/server/roombase.h
@@ -0,0 +1,30 @@
+#ifndef _ROOMBASE_H
+#define _ROOMBASE_H
+
+class Server;
+class ServerPlayer;
+
+class RoomBase : public QObject {
+ public:
+ Server *getServer() const;
+ bool isLobby() const;
+ QList getPlayers() const;
+ QList getOtherPlayers(ServerPlayer *expect) const;
+ ServerPlayer *findPlayer(int id) const;
+
+ void doBroadcastNotify(const QList targets,
+ const QString &command, const QString &jsonData);
+
+ void chat(ServerPlayer *sender, const QString &jsonData);
+
+ virtual void addPlayer(ServerPlayer *player) = 0;
+ virtual void removePlayer(ServerPlayer *player) = 0;
+ virtual void handlePacket(ServerPlayer *sender, const QString &command,
+ const QString &jsonData) = 0;
+ protected:
+ Server *server;
+ QList players;
+ QList observers;
+};
+
+#endif // _ROOMBASE_H
diff --git a/src/server/roomthread.cpp b/src/server/roomthread.cpp
index cbbff968..10f1cd87 100644
--- a/src/server/roomthread.cpp
+++ b/src/server/roomthread.cpp
@@ -1,45 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "roomthread.h"
-#include "server.h"
-#include "util.h"
-#include
+#include "server/roomthread.h"
+#include "server/scheduler.h"
+#include "server/server.h"
#ifndef FK_SERVER_ONLY
-#include "client.h"
+#include "client/client.h"
#endif
RoomThread::RoomThread(Server *m_server) {
setObjectName("Room");
this->m_server = m_server;
m_capacity = 100; // TODO: server cfg
- terminated = false;
md5 = m_server->getMd5();
- L = CreateLuaState();
- if (QFile::exists("packages/freekill-core") &&
- !GetDisabledPacks().contains("freekill-core")) {
- // 危险的cd操作,记得在lua中切回游戏根目录
- QDir::setCurrent("packages/freekill-core");
- }
-
- DoLuaScript(L, "lua/freekill.lua");
- DoLuaScript(L, "lua/server/scheduler.lua");
start();
}
RoomThread::~RoomThread() {
- tryTerminate();
if (isRunning()) {
- wait();
+ quit();
}
- lua_close(L);
+ delete m_scheduler;
m_server->removeThread(this);
// foreach (auto room, room_list) {
// room->deleteLater();
// }
}
+void RoomThread::run() {
+ // 在run中创建,这样就能在接下来的exec中处理事件了
+ m_scheduler = new Scheduler(this);
+ connect(this, &RoomThread::pushRequest, m_scheduler, &Scheduler::handleRequest);
+ connect(this, &RoomThread::delay, m_scheduler, &Scheduler::doDelay);
+ connect(this, &RoomThread::wakeUp, m_scheduler, &Scheduler::resumeRoom);
+ exec();
+}
+
Server *RoomThread::getServer() const {
return m_server;
}
@@ -56,7 +53,7 @@ Room *RoomThread::getRoom(int id) const {
}
void RoomThread::addRoom(Room *room) {
- Q_UNUSED(room);
+ room->setThread(this);
m_capacity--;
}
@@ -69,6 +66,7 @@ void RoomThread::removeRoom(Room *room) {
}
}
+/*
QString RoomThread::fetchRequest() {
// if (!gameStarted)
// return "";
@@ -124,6 +122,7 @@ void RoomThread::tryTerminate() {
bool RoomThread::isTerminated() const {
return terminated;
}
+*/
bool RoomThread::isConsoleStart() const {
#ifndef FK_SERVER_ONLY
diff --git a/src/server/roomthread.h b/src/server/roomthread.h
index 25d0afe3..aa80e88a 100644
--- a/src/server/roomthread.h
+++ b/src/server/roomthread.h
@@ -3,9 +3,9 @@
#ifndef _ROOMTHREAD_H
#define _ROOMTHREAD_H
-#include
class Room;
class Server;
+class Scheduler;
class RoomThread : public QThread {
Q_OBJECT
@@ -21,21 +21,24 @@ class RoomThread : public QThread {
void addRoom(Room *room);
void removeRoom(Room *room);
- QString fetchRequest();
- void pushRequest(const QString &req);
- void clearRequest();
- bool hasRequest();
+ //QString fetchRequest();
+ //void clearRequest();
+ //bool hasRequest();
- void trySleep(int ms);
- void wakeUp();
+ // void trySleep(int ms);
- void tryTerminate();
- bool isTerminated() const;
+ // void tryTerminate();
+ // bool isTerminated() const;
bool isConsoleStart() const;
bool isOutdated();
+ signals:
+ void pushRequest(const QString &req);
+ void delay(int roomId, int ms);
+ void wakeUp(int roomId);
+
protected:
virtual void run();
@@ -45,11 +48,11 @@ class RoomThread : public QThread {
int m_capacity;
QString md5;
- lua_State *L;
- QMutex request_queue_mutex;
- QQueue request_queue; // json string
- QSemaphore sema_wake;
- volatile bool terminated;
+ Scheduler *m_scheduler;
+ // QMutex request_queue_mutex;
+ // QQueue request_queue; // json string
+ // QSemaphore sema_wake;
+ // volatile bool terminated;
};
#endif // _ROOMTHREAD_H
diff --git a/src/server/scheduler.cpp b/src/server/scheduler.cpp
new file mode 100644
index 00000000..f0a9d211
--- /dev/null
+++ b/src/server/scheduler.cpp
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "server/scheduler.h"
+#include "server/roomthread.h"
+#include "core/util.h"
+
+Scheduler::Scheduler(RoomThread *thread) {
+ m_thread = thread;
+ L = CreateLuaState();
+ if (QFile::exists("packages/freekill-core") &&
+ !GetDisabledPacks().contains("freekill-core")) {
+ // 危险的cd操作,记得在lua中切回游戏根目录
+ QDir::setCurrent("packages/freekill-core");
+ }
+ DoLuaScript(L, "lua/freekill.lua");
+ DoLuaScript(L, "lua/server/scheduler.lua");
+ tellThreadToLua();
+}
+
+Scheduler::~Scheduler() {
+ lua_close(L);
+}
+
+void Scheduler::handleRequest(const QString &req) {
+ lua_getglobal(L, "HandleRequest");
+ auto bytes = req.toUtf8();
+ lua_pushstring(L, bytes.data());
+
+ int err = lua_pcall(L, 1, 1, 0);
+ const char *result = lua_tostring(L, -1);
+ if (err) {
+ qCritical() << result;
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+}
+
+void Scheduler::doDelay(int roomId, int ms) {
+ QTimer::singleShot(ms, [=](){ resumeRoom(roomId); });
+}
+
+bool Scheduler::resumeRoom(int roomId) {
+ lua_getglobal(L, "ResumeRoom");
+ lua_pushnumber(L, roomId);
+
+ int err = lua_pcall(L, 1, 1, 0);
+ const char *result = lua_tostring(L, -1);
+ if (err) {
+ qCritical() << result;
+ lua_pop(L, 1);
+ return true;
+ }
+ auto ret = lua_toboolean(L, -1);
+ return ret;
+}
diff --git a/src/server/scheduler.h b/src/server/scheduler.h
new file mode 100644
index 00000000..c304e76f
--- /dev/null
+++ b/src/server/scheduler.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef _SCHEDULER_H
+#define _SCHEDULER_H
+
+class Room;
+class RoomThread;
+
+class Scheduler : public QObject {
+ Q_OBJECT
+ public:
+ explicit Scheduler(RoomThread *m_thread);
+ ~Scheduler();
+
+ void tellThreadToLua(); // 转swig
+
+ public slots:
+ // 跨线程传递引用可能出问题!
+ void handleRequest(const QString &req);
+ void doDelay(int roomId, int ms);
+ bool resumeRoom(int roomId);
+
+ private:
+ RoomThread *m_thread;
+ lua_State *L;
+ // QList room_list;
+};
+
+#endif // _ROOMTHREAD_H
diff --git a/src/server/server.cpp b/src/server/server.cpp
index 996157b1..6500f8ae 100644
--- a/src/server/server.cpp
+++ b/src/server/server.cpp
@@ -1,54 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "server.h"
+#include "server/server.h"
+#include "server/auth.h"
+#include "server/room.h"
+#include "server/lobby.h"
+#include "server/roomthread.h"
+#include "server/serverplayer.h"
+#include "network/router.h"
+#include "network/client_socket.h"
+#include "network/server_socket.h"
+#include "core/packman.h"
+#include "core/util.h"
-#include
-#include
-#include
-#include
-#include
#include
-#include
-
-#include "client_socket.h"
-#include "packman.h"
-#include "player.h"
-#include "room.h"
-#include "roomthread.h"
-#include "router.h"
-#include "server_socket.h"
-#include "serverplayer.h"
-#include "util.h"
-
Server *ServerInstance = nullptr;
Server::Server(QObject *parent) : QObject(parent) {
ServerInstance = this;
db = OpenDatabase();
- rsa = initServerRSA();
- QFile file("server/rsa_pub");
- file.open(QIODevice::ReadOnly);
- QTextStream in(&file);
- public_key = in.readAll();
md5 = calcFileMD5();
readConfig();
- server = new ServerSocket();
- server->setParent(this);
+ auth = new AuthManager(this);
+ server = new ServerSocket(this);
connect(server, &ServerSocket::new_connection, this,
&Server::processNewConnection);
- udpSocket = new QUdpSocket(this);
- connect(udpSocket, &QUdpSocket::readyRead,
- this, &Server::readPendingDatagrams);
-
- // 创建第一个房间,这个房间作为“大厅房间”
- nextRoomId = 0;
- createRoom(nullptr, "Lobby", INT32_MAX);
- // 大厅只要发生人员变动,就向所有人广播一下房间列表
- connect(lobby(), &Room::playerAdded, this, &Server::updateOnlineInfo);
- connect(lobby(), &Room::playerRemoved, this, &Server::updateOnlineInfo);
+ nextRoomId = 1;
+ m_lobby = new Lobby(this);
// 启动心跳包线程
auto heartbeatThread = QThread::create([=]() {
@@ -90,12 +70,10 @@ Server::~Server() {
thread->deleteLater();
}
sqlite3_close(db);
- RSA_free(rsa);
}
bool Server::listen(const QHostAddress &address, ushort port) {
bool ret = server->listen(address, port);
- udpSocket->bind(port);
isListening = ret;
return ret;
}
@@ -118,7 +96,7 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
}
}
- if (!thread && nextRoomId != 0) {
+ if (!thread) {
thread = createThread();
}
@@ -127,16 +105,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
room->setId(nextRoomId);
nextRoomId++;
room->setAbandoned(false);
- room->setThread(thread);
thread->addRoom(room);
rooms.insert(room->getId(), room);
} else {
room = new Room(thread);
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
- if (room->isLobby())
- m_lobby = room;
- else
- rooms.insert(room->getId(), room);
+ rooms.insert(room->getId(), room);
}
room->setName(name);
@@ -144,13 +118,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
room->setTimeout(timeout);
room->setSettings(settings);
room->addPlayer(owner);
- if (!room->isLobby())
- room->setOwner(owner);
+ room->setOwner(owner);
}
Room *Server::findRoom(int id) const { return rooms.value(id); }
-Room *Server::lobby() const { return m_lobby; }
+Lobby *Server::lobby() const { return m_lobby; }
RoomThread *Server::createThread() {
RoomThread *thread = new RoomThread(this);
@@ -234,27 +207,6 @@ void Server::sendEarlyPacket(ClientSocket *client, const QString &type, const QS
client->send(JsonArray2Bytes(body));
}
-bool Server::checkClientVersion(ClientSocket *client, const QString &cver) {
- auto client_ver = QVersionNumber::fromString(cver);
- auto ver = QVersionNumber::fromString(FK_VERSION);
- int cmp = QVersionNumber::compare(ver, client_ver);
- if (cmp != 0) {
- auto errmsg = QString();
- if (cmp < 0) {
- errmsg = QString("[\"server is still on version %%2\",\"%1\"]")
- .arg(FK_VERSION, "1");
- } else {
- errmsg = QString("[\"server is using version %%2, please update\",\"%1\"]")
- .arg(FK_VERSION, "1");
- }
-
- sendEarlyPacket(client, "ErrorMsg", errmsg);
- client->disconnectFromHost();
- return false;
- }
- return true;
-}
-
void Server::setupPlayer(ServerPlayer *player, bool all_info) {
// tell the lobby player's basic property
QJsonArray arr;
@@ -291,7 +243,7 @@ void Server::processNewConnection(ClientSocket *client) {
}
if (!errmsg.isEmpty()) {
- sendEarlyPacket(client, "ErrorMsg", errmsg);
+ sendEarlyPacket(client, "ErrorDlg", errmsg);
qInfo() << "Refused banned IP:" << addr;
client->disconnectFromHost();
return;
@@ -301,7 +253,7 @@ void Server::processNewConnection(ClientSocket *client) {
[client]() { qInfo() << client->peerAddress() << "disconnected"; });
// network delay test
- sendEarlyPacket(client, "NetworkDelayTest", public_key);
+ sendEarlyPacket(client, "NetworkDelayTest", auth->getPublicKey());
// Note: the client should send a setup string next
connect(client, &ClientSocket::message_got, this, &Server::processRequest);
client->timerSignup.start(30000);
@@ -328,56 +280,30 @@ void Server::processRequest(const QByteArray &msg) {
if (!valid) {
qWarning() << "Invalid setup string:" << msg;
- sendEarlyPacket(client, "ErrorMsg", "INVALID SETUP STRING");
+ sendEarlyPacket(client, "ErrorDlg", "INVALID SETUP STRING");
client->disconnectFromHost();
return;
}
QJsonArray arr = String2Json(doc[3].toString()).array();
- if (!checkClientVersion(client, arr[3].toString())) return;
+ if (!auth->checkClientVersion(client, arr[3].toString())) return;
- auto uuid = arr[4].toString();
+ auto uuid_str = arr[4].toString();
auto result2 = QJsonArray({1});
- if (CheckSqlString(uuid)) {
+ if (CheckSqlString(uuid_str)) {
result2 = SelectFromDatabase(
- db, QString("SELECT * FROM banuuid WHERE uuid='%1';").arg(uuid));
+ db, QString("SELECT * FROM banuuid WHERE uuid='%1';").arg(uuid_str));
}
if (!result2.isEmpty()) {
- sendEarlyPacket(client, "ErrorMsg", "you have been banned!");
- qInfo() << "Refused banned UUID:" << uuid;
+ sendEarlyPacket(client, "ErrorDlg", "you have been banned!");
+ qInfo() << "Refused banned UUID:" << uuid_str;
client->disconnectFromHost();
return;
}
- handleNameAndPassword(client, arr[0].toString(), arr[1].toString(),
- arr[2].toString(), uuid);
-}
-
-void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
- const QString &password,
- const QString &md5_str,
- const QString &uuid_str) {
- auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
- unsigned char buf[4096] = {0};
- RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(),
- buf, rsa, RSA_PKCS1_PADDING);
- auto decrypted_pw =
- QByteArray::fromRawData((const char *)buf, strlen((const char *)buf));
-
- if (decrypted_pw.length() > 32) {
- auto aes_bytes = decrypted_pw.first(32);
-
- // tell client to install aes key
- sendEarlyPacket(client, "InstallKey", "");
-
- client->installAESKey(aes_bytes);
- decrypted_pw.remove(0, 32);
- } else {
- decrypted_pw = "\xFF";
- }
-
+ auto md5_str = arr[2].toString();
if (md5 != md5_str) {
sendEarlyPacket(client, "ErrorMsg", "MD5 check failed!");
sendEarlyPacket(client, "UpdatePackage", Pacman->getPackSummary());
@@ -385,146 +311,53 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
return;
}
- bool passed = false;
- QString error_msg;
- QJsonArray result;
- QJsonObject obj;
+ auto name = arr[0].toString();
+ auto password = arr[1].toString();
+ auto obj = auth->checkPassword(client, name, password);
+ if (obj.isEmpty()) return;
- if (CheckSqlString(name) && checkBanWord(name)) {
- // Then we check the database,
- QString sql_find = QString("SELECT * FROM userinfo \
- WHERE name='%1';")
- .arg(name);
- result = SelectFromDatabase(db, sql_find);
- if (result.isEmpty()) {
- auto salt_gen = QRandomGenerator::securelySeeded();
- auto salt = QByteArray::number(salt_gen(), 16);
- decrypted_pw.append(salt);
- auto passwordHash =
- QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256)
- .toHex();
- // not present in database, register
- QString sql_reg = QString("INSERT INTO userinfo (name,password,salt,\
- avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4','%5',%6);")
- .arg(name)
- .arg(QString(passwordHash))
- .arg(salt)
- .arg("liubei")
- .arg(client->peerAddress())
- .arg("FALSE");
- ExecSQL(db, sql_reg);
- result = SelectFromDatabase(db, sql_find); // refresh result
- obj = result[0].toObject();
+ // update lastLoginIp
+ int id = obj["id"].toString().toInt();
+ beginTransaction();
+ auto sql_update =
+ QString("UPDATE userinfo SET lastLoginIp='%1' WHERE id=%2;")
+ .arg(client->peerAddress())
+ .arg(id);
+ ExecSQL(db, sql_update);
- auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
- ExecSQL(db, info_update);
+ auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');")
+ .arg(id).arg(uuid_str);
+ ExecSQL(db, uuid_update);
- passed = true;
- } else {
- obj = result[0].toObject();
+ // 来晚了,有很大可能存在已经注册但是表里面没数据的人
+ ExecSQL(db, QString("INSERT OR IGNORE INTO usergameinfo (id) VALUES (%1);").arg(id));
+ auto info_update = QString("UPDATE usergameinfo SET lastLoginTime=%2 where id=%1;").arg(id).arg(QDateTime::currentSecsSinceEpoch());
+ ExecSQL(db, info_update);
+ endTransaction();
- // check ban account
- int id = obj["id"].toString().toInt();
- passed = obj["banned"].toString().toInt() == 0;
- if (!passed) {
- error_msg = "you have been banned!";
- }
-
- // check if password is the same
- auto salt = obj["salt"].toString().toLatin1();
- decrypted_pw.append(salt);
- auto passwordHash =
- QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256)
- .toHex();
- passed = (passwordHash == obj["password"].toString());
-
- if (!passed) {
- error_msg = "username or password error";
- } else if (players.value(id)) {
- auto player = players.value(id);
- // 顶号机制,如果在线的话就让他变成不在线
- if (player->getState() == Player::Online) {
- player->doNotify("ErrorMsg", "others logged in again with this name");
- emit player->kicked();
- }
-
- if (player->getState() == Player::Offline) {
- auto room = player->getRoom();
- player->setSocket(client);
- player->alive = true;
- client->disconnect(this);
- if (players.count() <= 10) {
- broadcast("ServerMessage", tr("%1 backed").arg(player->getScreenName()));
- }
-
- if (room && !room->isLobby()) {
- setupPlayer(player, true);
- room->pushRequest(QString("%1,reconnect").arg(id));
- } else {
- // 懒得处理掉线玩家在大厅了!踢掉得了
- player->doNotify("ErrorMsg", "Unknown Error");
- emit player->kicked();
- }
-
- return;
- } else {
- error_msg = "others logged in with this name";
- passed = false;
- }
- }
- }
- } else {
- error_msg = "invalid user name";
+ // create new ServerPlayer and setup
+ ServerPlayer *player = new ServerPlayer(lobby());
+ player->setSocket(client);
+ client->disconnect(this);
+ connect(player, &ServerPlayer::disconnected, this,
+ &Server::onUserDisconnected);
+ connect(player, &Player::stateChanged, this, &Server::onUserStateChanged);
+ player->setScreenName(name);
+ player->setAvatar(obj["avatar"].toString());
+ player->setId(id);
+ if (players.count() <= 10) {
+ broadcast("ServerMessage", tr("%1 logged in").arg(player->getScreenName()));
}
+ players.insert(player->getId(), player);
- if (passed) {
- // update lastLoginIp
- int id = obj["id"].toString().toInt();
- beginTransaction();
- auto sql_update =
- QString("UPDATE userinfo SET lastLoginIp='%1' WHERE id=%2;")
- .arg(client->peerAddress())
- .arg(id);
- ExecSQL(db, sql_update);
+ setupPlayer(player);
- auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');").arg(id).arg(uuid_str);
- ExecSQL(db, uuid_update);
+ auto result = SelectFromDatabase(db, QString("SELECT totalGameTime FROM usergameinfo WHERE id=%1;").arg(id));
+ auto time = result[0].toObject()["totalGameTime"].toString().toInt();
+ player->addTotalGameTime(time);
+ player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
- // 来晚了,有很大可能存在已经注册但是表里面没数据的人
- ExecSQL(db, QString("INSERT OR IGNORE INTO usergameinfo (id) VALUES (%1);").arg(id));
- auto info_update = QString("UPDATE usergameinfo SET lastLoginTime=%2 where id=%1;").arg(id).arg(QDateTime::currentSecsSinceEpoch());
- ExecSQL(db, info_update);
- endTransaction();
-
- // create new ServerPlayer and setup
- ServerPlayer *player = new ServerPlayer(lobby());
- player->setSocket(client);
- client->disconnect(this);
- connect(player, &ServerPlayer::disconnected, this,
- &Server::onUserDisconnected);
- connect(player, &Player::stateChanged, this, &Server::onUserStateChanged);
- player->setScreenName(name);
- player->setAvatar(obj["avatar"].toString());
- player->setId(id);
- if (players.count() <= 10) {
- broadcast("ServerMessage", tr("%1 logged in").arg(player->getScreenName()));
- }
- players.insert(player->getId(), player);
-
- setupPlayer(player);
-
- auto result = SelectFromDatabase(db, QString("SELECT totalGameTime FROM usergameinfo WHERE id=%1;").arg(id));
- auto time = result[0].toObject()["totalGameTime"].toString().toInt();
- player->addTotalGameTime(time);
- player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
-
- lobby()->addPlayer(player);
- } else {
- qInfo() << client->peerAddress() << "lost connection:" << error_msg;
- sendEarlyPacket(client, "ErrorMsg", error_msg);
- client->disconnectFromHost();
- return;
- }
+ lobby()->addPlayer(player);
}
void Server::onRoomAbandoned() {
@@ -537,37 +370,49 @@ void Server::onRoomAbandoned() {
// FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。
// room->deleteLater();
idle_rooms.push(room);
+ room->getThread()->wakeUp(room->getId());
room->getThread()->removeRoom(room);
}
void Server::onUserDisconnected() {
- ServerPlayer *player = qobject_cast(sender());
+ auto player = qobject_cast(sender());
qInfo() << "Player" << player->getId() << "disconnected";
if (players.count() <= 10) {
broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName()));
}
- Room *room = player->getRoom();
- if (room->isStarted()) {
- if (room->getObservers().contains(player)) {
- room->removeObserver(player);
- player->deleteLater();
- return;
- }
- player->setState(Player::Offline);
- player->setSocket(nullptr);
- // TODO: add a robot
- } else {
+
+ auto _room = player->getRoom();
+ if (_room->isLobby()) {
player->setState(Player::Robot); // 大厅!然而又不能设Offline
player->deleteLater();
+ } else {
+ auto room = qobject_cast(_room);
+ if (room->isStarted()) {
+ if (room->getObservers().contains(player)) {
+ room->removeObserver(player);
+ player->deleteLater();
+ return;
+ }
+ player->setState(Player::Offline);
+ player->setSocket(nullptr);
+ // TODO: add a robot
+ } else {
+ player->setState(Player::Robot); // 大厅!然而又不能设Offline
+ // 这里有一个多线程问题,可能与Room::gameOver同时deleteLater导致出事
+ // FIXME: 这种解法肯定不安全
+ if (!room->insideGameOver)
+ player->deleteLater();
+ }
}
}
void Server::onUserStateChanged() {
ServerPlayer *player = qobject_cast(sender());
- auto room = player->getRoom();
- if (!room || room->isLobby() || room->isAbandoned()) {
- return;
- }
+ auto _room = player->getRoom();
+ if (!_room || _room->isLobby()) return;
+ auto room = qobject_cast(_room);
+ if (room->isAbandoned()) return;
+
auto state = player->getState();
room->doBroadcastNotify(room->getPlayers(), "NetStateChanged",
QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString()));
@@ -579,33 +424,6 @@ void Server::onUserStateChanged() {
}
}
-RSA *Server::initServerRSA() {
- RSA *rsa = RSA_new();
- if (!QFile::exists("server/rsa_pub")) {
- BIGNUM *bne = BN_new();
- BN_set_word(bne, RSA_F4);
- RSA_generate_key_ex(rsa, 2048, bne, NULL);
-
- BIO *bp_pub = BIO_new_file("server/rsa_pub", "w+");
- PEM_write_bio_RSAPublicKey(bp_pub, rsa);
- BIO *bp_pri = BIO_new_file("server/rsa", "w+");
- PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL);
-
- BIO_free_all(bp_pub);
- BIO_free_all(bp_pri);
- QFile("server/rsa")
- .setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
- BN_free(bne);
- }
- FILE *keyFile = fopen("server/rsa_pub", "r");
- PEM_read_RSAPublicKey(keyFile, &rsa, NULL, NULL);
- fclose(keyFile);
- keyFile = fopen("server/rsa", "r");
- PEM_read_RSAPrivateKey(keyFile, &rsa, NULL, NULL);
- fclose(keyFile);
- return rsa;
-}
-
#define SET_DEFAULT_CONFIG(k, v) do {\
if (config.value(k).isUndefined()) { \
config[k] = (v); \
@@ -705,28 +523,3 @@ void Server::refreshMd5() {
emit p->kicked();
}
}
-
-void Server::readPendingDatagrams() {
- while (udpSocket->hasPendingDatagrams()) {
- QNetworkDatagram datagram = udpSocket->receiveDatagram();
- if (datagram.isValid()) {
- processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
- }
- }
-}
-
-void Server::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
- if (msg == "fkDetectServer") {
- udpSocket->writeDatagram("me", addr, port);
- } else if (msg.startsWith("fkGetDetail,")) {
- udpSocket->writeDatagram(JsonArray2Bytes(QJsonArray({
- FK_VERSION,
- getConfig("iconUrl"),
- getConfig("description"),
- getConfig("capacity"),
- players.count(),
- msg.sliced(12).constData(),
- })), addr, port);
- }
- udpSocket->flush();
-}
diff --git a/src/server/server.h b/src/server/server.h
index f2baaa53..593195cb 100644
--- a/src/server/server.h
+++ b/src/server/server.h
@@ -3,17 +3,14 @@
#ifndef _SERVER_H
#define _SERVER_H
-#include
-#include
-
-#include
-#include
+class AuthManager;
class ServerSocket;
class ClientSocket;
class ServerPlayer;
class RoomThread;
+class Lobby;
-#include "room.h"
+#include "server/room.h"
class Server : public QObject {
Q_OBJECT
@@ -29,7 +26,7 @@ public:
int timeout = 15, const QByteArray &settings = "{}");
Room *findRoom(int id) const;
- Room *lobby() const;
+ Lobby *lobby() const;
RoomThread *createThread();
void removeThread(RoomThread *thread);
@@ -37,6 +34,7 @@ public:
ServerPlayer *findPlayer(int id) const;
void addPlayer(ServerPlayer *player);
void removePlayer(int id);
+ auto getPlayers() { return players; }
void updateRoomList(ServerPlayer *teller);
void updateOnlineInfo();
@@ -44,6 +42,8 @@ public:
sqlite3 *getDatabase();
void broadcast(const QString &command, const QString &jsonData);
+ void sendEarlyPacket(ClientSocket *client, const QString &type, const QString &msg);
+ void setupPlayer(ServerPlayer *player, bool all_info = true);
bool isListening;
QJsonValue getConfig(const QString &command);
@@ -64,7 +64,6 @@ signals:
public slots:
void processNewConnection(ClientSocket *client);
void processRequest(const QByteArray &msg);
- void readPendingDatagrams();
void onRoomAbandoned();
void onUserDisconnected();
@@ -73,9 +72,8 @@ public slots:
private:
friend class Shell;
ServerSocket *server;
- QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
- Room *m_lobby;
+ Lobby *m_lobby;
QMap rooms;
QStack idle_rooms;
QList threads;
@@ -84,26 +82,13 @@ private:
QHash players;
QList temp_banlist;
- RSA *rsa;
- QString public_key;
+ AuthManager *auth;
sqlite3 *db;
QMutex transaction_mutex;
QString md5;
- static RSA *initServerRSA();
-
QJsonObject config;
void readConfig();
-
- // 用于确定建立连接之前与客户端通信,连接后用doNotify
- void sendEarlyPacket(ClientSocket *client, const QString &type, const QString &msg);
- bool checkClientVersion(ClientSocket *client, const QString &ver);
-
- // 某玩家刚刚连入之后,服务器告诉他关于他的一些基本信息
- void setupPlayer(ServerPlayer *player, bool all_info = true);
- void handleNameAndPassword(ClientSocket *client, const QString &name,
- const QString &password, const QString &md5_str, const QString &uuid_str);
- void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
};
extern Server *ServerInstance;
diff --git a/src/server/serverplayer.cpp b/src/server/serverplayer.cpp
index 34089ad5..ac3825b4 100644
--- a/src/server/serverplayer.cpp
+++ b/src/server/serverplayer.cpp
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "serverplayer.h"
-#include "client_socket.h"
-#include "room.h"
-#include "roomthread.h"
-#include "router.h"
-#include "server.h"
+#include "server/serverplayer.h"
+#include "network/client_socket.h"
+#include "server/room.h"
+#include "server/roomthread.h"
+#include "network/router.h"
+#include "server/server.h"
-ServerPlayer::ServerPlayer(Room *room) {
+ServerPlayer::ServerPlayer(RoomBase *room) {
socket = nullptr;
router = new Router(this, socket, Router::TYPE_SERVER);
setState(Player::Online);
@@ -61,9 +61,9 @@ void ServerPlayer::removeSocket() {
Server *ServerPlayer::getServer() const { return server; }
-Room *ServerPlayer::getRoom() const { return room; }
+RoomBase *ServerPlayer::getRoom() const { return room; }
-void ServerPlayer::setRoom(Room *room) { this->room = room; }
+void ServerPlayer::setRoom(RoomBase *room) { this->room = room; }
void ServerPlayer::speak(const QString &message) { ; }
@@ -112,6 +112,24 @@ void ServerPlayer::kick() {
setSocket(nullptr);
}
+void ServerPlayer::reconnect(ClientSocket *client) {
+ setSocket(client);
+ alive = true;
+ client->disconnect(this);
+ if (server->getPlayers().count() <= 10) {
+ server->broadcast("ServerMessage", tr("%1 backed").arg(getScreenName()));
+ }
+
+ if (room && !room->isLobby()) {
+ server->setupPlayer(this, true);
+ qobject_cast(room)->pushRequest(QString("%1,reconnect").arg(getId()));
+ } else {
+ // 懒得处理掉线玩家在大厅了!踢掉得了
+ doNotify("ErrorMsg", "Unknown Error");
+ emit kicked();
+ }
+}
+
bool ServerPlayer::thinking() {
m_thinking_mutex.lock();
bool ret = m_thinking;
diff --git a/src/server/serverplayer.h b/src/server/serverplayer.h
index c9b0a10a..9bbf8d2d 100644
--- a/src/server/serverplayer.h
+++ b/src/server/serverplayer.h
@@ -3,17 +3,18 @@
#ifndef _SERVERPLAYER_H
#define _SERVERPLAYER_H
-#include "player.h"
+#include "core/player.h"
class ClientSocket;
class Router;
class Server;
class Room;
+class RoomBase;
class ServerPlayer : public Player {
Q_OBJECT
public:
- explicit ServerPlayer(Room *room);
+ explicit ServerPlayer(RoomBase *room);
~ServerPlayer();
void setSocket(ClientSocket *socket);
@@ -21,8 +22,8 @@ public:
ClientSocket *getSocket() const;
Server *getServer() const;
- Room *getRoom() const;
- void setRoom(Room *room);
+ RoomBase *getRoom() const;
+ void setRoom(RoomBase *room);
void speak(const QString &message);
@@ -37,6 +38,7 @@ public:
volatile bool alive; // For heartbeat
void kick();
+ void reconnect(ClientSocket *socket);
bool busy() const { return m_busy; }
void setBusy(bool busy) { m_busy = busy; }
@@ -57,7 +59,7 @@ private:
ClientSocket *socket; // socket for communicating with client
Router *router;
Server *server;
- Room *room; // Room that player is in, maybe lobby
+ RoomBase *room; // Room that player is in, maybe lobby
bool m_busy; // (Lua专用) 是否有doRequest没处理完?见于神貂蝉这种一控多的
bool m_thinking; // 是否在烧条?
QMutex m_thinking_mutex; // 注意setBusy只在Lua使用,所以不需要锁。
diff --git a/src/server/shell.cpp b/src/server/shell.cpp
index c286701d..f1168adf 100644
--- a/src/server/shell.cpp
+++ b/src/server/shell.cpp
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
-#include "shell.h"
-#include "packman.h"
-#include "server.h"
-#include "serverplayer.h"
-#include "util.h"
+#include "server/shell.h"
+#include "core/packman.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "core/util.h"
#include
#include
#include
diff --git a/src/swig/freekill-nogui.i b/src/swig/freekill-nogui.i
index 726746cc..b7ebd97a 100644
--- a/src/swig/freekill-nogui.i
+++ b/src/swig/freekill-nogui.i
@@ -3,13 +3,13 @@
%module fk
%{
-#include "server.h"
-#include "serverplayer.h"
-#include "clientplayer.h"
-#include "room.h"
-#include "roomthread.h"
-#include "util.h"
-#include "qmlbackend.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "client/clientplayer.h"
+#include "server/room.h"
+#include "server/roomthread.h"
+#include "core/util.h"
+#include "ui/qmlbackend.h"
class ClientPlayer *Self = nullptr;
%}
diff --git a/src/swig/freekill.i b/src/swig/freekill.i
index f8b95d85..962f9014 100644
--- a/src/swig/freekill.i
+++ b/src/swig/freekill.i
@@ -3,14 +3,14 @@
%module fk
%{
-#include "client.h"
-#include "server.h"
-#include "serverplayer.h"
-#include "clientplayer.h"
-#include "room.h"
-#include "roomthread.h"
-#include "qmlbackend.h"
-#include "util.h"
+#include "client/client.h"
+#include "server/server.h"
+#include "server/serverplayer.h"
+#include "client/clientplayer.h"
+#include "server/room.h"
+#include "server/roomthread.h"
+#include "ui/qmlbackend.h"
+#include "core/util.h"
const char *FK_VER = FK_VERSION;
%}
diff --git a/src/swig/naturalvar.i b/src/swig/naturalvar.i
index 9aff29f1..f83a4f84 100644
--- a/src/swig/naturalvar.i
+++ b/src/swig/naturalvar.i
@@ -5,7 +5,7 @@
// ------------------------------------------------------
%{
-#include
+#include "ui/qmlbackend.h"
%}
// Lua 5.4 特有的不能pushnumber, swig迟迟不更只好手动调教
diff --git a/src/swig/server.i b/src/swig/server.i
index ba72a769..97d546b5 100644
--- a/src/swig/server.i
+++ b/src/swig/server.i
@@ -1,5 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
+%nodefaultctor Server;
+%nodefaultdtor Server;
+class Server : public QObject {
+public:
+ void beginTransaction();
+ void endTransaction();
+};
+extern Server *ServerInstance;
+
%nodefaultctor Room;
%nodefaultdtor Room;
class Room : public QObject {
@@ -14,11 +23,14 @@ public:
QList getObservers() const;
bool hasObserver(ServerPlayer *player) const;
int getTimeout() const;
+ void delay(int ms);
void checkAbandoned();
void updateWinRate(int id, const QString &general, const QString &mode,
int result, bool dead);
void gameOver();
+ void setRequestTimer(int ms);
+ void destroyRequestTimer();
};
%extend Room {
@@ -33,25 +45,26 @@ class RoomThread : public QThread {
public:
Room *getRoom(int id);
- QString fetchRequest();
- void clearRequest();
- bool hasRequest();
+ // QString fetchRequest();
+ // void clearRequest();
+ // bool hasRequest();
- void trySleep(int ms);
- bool isTerminated() const;
+ // void trySleep(int ms);
+ // bool isTerminated() const;
bool isConsoleStart() const;
bool isOutdated();
};
%{
-void RoomThread::run()
+#include "server/scheduler.h"
+void Scheduler::tellThreadToLua()
{
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_getglobal(L, "InitScheduler");
- SWIG_NewPointerObj(L, this, SWIGTYPE_p_RoomThread, 0);
+ SWIG_NewPointerObj(L, m_thread, SWIGTYPE_p_RoomThread, 0);
int error = lua_pcall(L, 1, 0, -2);
lua_pop(L, 1);
if (error) {
diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp
index b86446a3..0f821901 100644
--- a/src/ui/qmlbackend.cpp
+++ b/src/ui/qmlbackend.cpp
@@ -1,27 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "qmlbackend.h"
-#include
-#include
-#include
+#include "ui/qmlbackend.h"
#ifndef FK_SERVER_ONLY
-#include
-#include
-#include
+#include
#include
#include
#include
#include
-#include "mod.h"
+#include
+// #include "mod.h"
#endif
#include
-#include "server.h"
-#include "client.h"
-#include "util.h"
-#include "replayer.h"
+#include "server/server.h"
+#include "client/client.h"
+#include "core/util.h"
+#include "client/replayer.h"
QmlBackend *Backend = nullptr;
@@ -35,6 +31,7 @@ QmlBackend::QmlBackend(QObject *parent) : QObject(parent) {
udpSocket->bind(0);
connect(udpSocket, &QUdpSocket::readyRead,
this, &QmlBackend::readPendingDatagrams);
+ connect(this, &QmlBackend::dialog, this, &QmlBackend::showDialog);
#endif
}
@@ -464,7 +461,7 @@ void QmlBackend::installAESKey() {
}
void QmlBackend::createModBackend() {
- engine->rootContext()->setContextProperty("ModBackend", new ModMaker);
+ //engine->rootContext()->setContextProperty("ModBackend", new ModMaker);
}
@@ -549,6 +546,30 @@ void QmlBackend::readPendingDatagrams() {
}
}
+void QmlBackend::showDialog(const QString &type, const QString &text, const QString &orig) {
+ static const QString title = tr("FreeKill") + " v" + FK_VERSION;
+ QMessageBox *box = nullptr;
+ if (type == "critical") {
+ box = new QMessageBox(QMessageBox::Critical, title, text, QMessageBox::Ok);
+ connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
+ } else if (type == "info") {
+ box = new QMessageBox(QMessageBox::Information, title, text, QMessageBox::Ok);
+ connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
+ } else if (type == "warning") {
+ box = new QMessageBox(QMessageBox::Warning, title, text, QMessageBox::Ok);
+ connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
+ }
+
+ if (box) {
+ if (!orig.isEmpty()) {
+ auto bytes = orig.toLocal8Bit().prepend("help: ");
+ if (tr(bytes) != bytes) box->setInformativeText(tr(bytes));
+ }
+ box->setWindowModality(Qt::NonModal);
+ box->show();
+ }
+}
+
void QmlBackend::removeRecord(const QString &fname) {
QFile::remove("recording/" + fname);
}
diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h
index 0961c11b..53be74c4 100644
--- a/src/ui/qmlbackend.h
+++ b/src/ui/qmlbackend.h
@@ -64,6 +64,9 @@ public:
Q_INVOKABLE void detectServer();
Q_INVOKABLE void getServerInfo(const QString &addr);
+ Q_INVOKABLE void showDialog(const QString &type, const QString &text,
+ const QString &orig = QString());
+
qreal volume() const { return m_volume; }
void setVolume(qreal v) { m_volume = v; }
@@ -77,6 +80,7 @@ public:
signals:
void notifyUI(const QString &command, const QVariant &data);
+ void dialog(const QString &type, const QString &text, const QString &orig = QString());
void volumeChanged(qreal);
void replayerToggle();
void replayerSpeedUp();