mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-16 03:32:34 +08:00
Cppdev (#356)
- 修改cpp代码 - 与之配合修改了相关lua代码 !注意从此开始,lua代码的更新转移到freekill-core仓库 Qsgs-Fans/freekill-core#1
This commit is contained in:
parent
766e93378e
commit
ebc5675ead
1
.github/workflows/build-android.yml
vendored
1
.github/workflows/build-android.yml
vendored
|
@ -86,6 +86,7 @@ jobs:
|
||||||
cp -r /etc/ssl/certs .
|
cp -r /etc/ssl/certs .
|
||||||
cp /usr/share/ca-certificates/mozilla/* certs/
|
cp /usr/share/ca-certificates/mozilla/* certs/
|
||||||
echo ${FKVER%)} > fk_ver
|
echo ${FKVER%)} > fk_ver
|
||||||
|
./genfkver.sh
|
||||||
|
|
||||||
- name: Configure CMake Project
|
- name: Configure CMake Project
|
||||||
working-directory: ${{github.workspace}}
|
working-directory: ${{github.workspace}}
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,6 +15,7 @@
|
||||||
/*.kdev4
|
/*.kdev4
|
||||||
/.cache/
|
/.cache/
|
||||||
/tags
|
/tags
|
||||||
|
/.luarc.json
|
||||||
|
|
||||||
# file produced by game
|
# file produced by game
|
||||||
/FreeKill
|
/FreeKill
|
||||||
|
|
|
@ -39,11 +39,6 @@ include_directories(include/lua)
|
||||||
include_directories(include)
|
include_directories(include)
|
||||||
include_directories(include/libgit2)
|
include_directories(include/libgit2)
|
||||||
include_directories(src)
|
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")
|
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
|
||||||
if (DEFINED FK_SERVER_ONLY)
|
if (DEFINED FK_SERVER_ONLY)
|
||||||
|
@ -78,6 +73,7 @@ add_custom_command(
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMENT "Generating version file fk_ver"
|
COMMENT "Generating version file fk_ver"
|
||||||
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
|
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
|
||||||
|
COMMAND ${PROJECT_SOURCE_DIR}/genfkver.sh
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
|
@ -43,6 +43,8 @@ QtObject {
|
||||||
property string password: ""
|
property string password: ""
|
||||||
property string cipherText
|
property string cipherText
|
||||||
property string aeskey
|
property string aeskey
|
||||||
|
// string => { roomId => config }
|
||||||
|
property var roomConfigCache: ({})
|
||||||
|
|
||||||
// Client data
|
// Client data
|
||||||
property string serverMotd: ""
|
property string serverMotd: ""
|
||||||
|
|
19
Fk/Logic.js
19
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["UpdatePackage"] = (jsonData) => sheduled_download = jsonData;
|
||||||
|
|
||||||
callbacks["UpdateBusyText"] = (jsonData) => {
|
callbacks["UpdateBusyText"] = (jsonData) => {
|
||||||
|
|
|
@ -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() {
|
function downloadComplete() {
|
||||||
toast.show(qsTr("updated packages for md5"));
|
toast.show(qsTr("updated packages for md5"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,111 +11,150 @@ import "Logic.js" as Logic
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property alias roomModel: roomModel
|
property alias roomModel: roomModel
|
||||||
|
property var roomInfoCache: ({})
|
||||||
|
|
||||||
property string password
|
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: roomDelegate
|
id: roomDelegate
|
||||||
|
|
||||||
Item {
|
Rectangle {
|
||||||
height: 48
|
radius: 8
|
||||||
width: roomList.width
|
height: 124 - 8
|
||||||
|
width: 124 - 8
|
||||||
RowLayout {
|
color: outdated ? "#E2E2E2" : "lightgreen"
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 16
|
|
||||||
Text {
|
|
||||||
text: roomId
|
|
||||||
color: "grey"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
id: roomNameText
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
Layout.fillWidth: true
|
width: parent.width - 16
|
||||||
text: {
|
height: contentHeight
|
||||||
let ret = roomName;
|
maximumLineCount: 2
|
||||||
if (outdated) {
|
wrapMode: Text.WrapAnywhere
|
||||||
ret = '<font color="grey"><del>' + ret + '</del></font>';
|
textFormat: Text.PlainText
|
||||||
}
|
text: roomName
|
||||||
return ret;
|
// color: outdated ? "gray" : "black"
|
||||||
}
|
font.pixelSize: 16
|
||||||
font.pixelSize: 20
|
// elide: Label.ElideRight
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 16
|
|
||||||
Image {
|
Image {
|
||||||
source: AppPath + "/image/button/skill/locked.png"
|
source: AppPath + "/image/button/skill/locked.png"
|
||||||
visible: hasPassword
|
visible: hasPassword
|
||||||
anchors.centerIn: parent
|
|
||||||
scale: 0.8
|
scale: 0.8
|
||||||
}
|
anchors.bottom: parent.bottom
|
||||||
}
|
anchors.left: parent.left
|
||||||
|
anchors.margins: -4
|
||||||
Text {
|
|
||||||
text: luatr(gameMode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
color: (playerNum == capacity) ? "red" : "black"
|
color: (playerNum == capacity) ? "red" : "black"
|
||||||
text: playerNum + "/" + capacity
|
text: playerNum + "/" + capacity
|
||||||
font.pixelSize: 20
|
font.pixelSize: 18
|
||||||
font.bold: true
|
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: roomDetailDialog
|
||||||
|
ColumnLayout {
|
||||||
|
property var roomData: ({
|
||||||
|
roomName: "",
|
||||||
|
hasPassword: true,
|
||||||
|
})
|
||||||
|
property var roomConfig: undefined
|
||||||
|
signal finished()
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
|
||||||
|
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: "在未来的版本中这一块区域将增加更多实用的功能,<br>"+
|
||||||
|
"例如直接查看房间的各种配置信息<br>"+
|
||||||
|
"以及更多与禁将有关的实用功能!"+
|
||||||
|
"<font color='gray'>注:绿色按钮为试做型UI 后面优化</font>"
|
||||||
|
font.pixelSize: 18
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Text {
|
||||||
|
visible: roomData.hasPassword
|
||||||
|
text: luatr("Please input room's password")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: passwordEdit
|
||||||
|
visible: roomData.hasPassword
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onTextChanged: root.password = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: !roomData.hasPassword
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: (playerNum < capacity) ? luatr("Enter") :
|
// text: "OK"
|
||||||
luatr("Observe")
|
text: (roomData.playerNum < roomData.capacity) ? luatr("Enter") : luatr("Observe")
|
||||||
|
|
||||||
enabled: !opTimer.running && !outdated
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
opTimer.start();
|
enterRoom(roomData.roomId, roomData.playerNum, roomData.capacity,
|
||||||
if (hasPassword) {
|
roomData.hasPassword ? root.password : "");
|
||||||
lobby_dialog.sourceComponent = enterPassword;
|
lobby_dialog.item.finished();
|
||||||
lobby_dialog.item.roomId = roomId;
|
|
||||||
lobby_dialog.item.playerNum = playerNum;
|
|
||||||
lobby_dialog.item.capacity = capacity;
|
|
||||||
lobby_drawer.open();
|
|
||||||
} else {
|
|
||||||
enterRoom(roomId, playerNum, capacity, "");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
passwordEdit.text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,8 +163,7 @@ Item {
|
||||||
id: roomModel
|
id: roomModel
|
||||||
}
|
}
|
||||||
|
|
||||||
PersonalSettings {
|
PersonalSettings {}
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: opTimer
|
id: opTimer
|
||||||
|
@ -134,55 +172,30 @@ Item {
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: roomListLayout
|
id: roomListLayout
|
||||||
anchors.top: parent.top
|
height: root.height - 72
|
||||||
anchors.topMargin: 10
|
y: 16
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.left: parent.left
|
||||||
width: root.width * 0.48
|
anchors.leftMargin: root.width * 0.03 + root.width * 0.94 * 0.8 % 128 / 2
|
||||||
height: root.height - 80
|
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 {
|
Button {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
text: luatr("Refresh Room List")
|
text: luatr("Refresh Room List").arg(roomModel.count)
|
||||||
enabled: !opTimer.running
|
enabled: !opTimer.running
|
||||||
onClicked: {
|
onClicked: {
|
||||||
opTimer.start();
|
opTimer.start();
|
||||||
ClientInstance.notifyServer("RefreshRoomList", "");
|
ClientInstance.notifyServer("RefreshRoomList", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: createRoomButton
|
|
||||||
anchors.bottom: buttonRow.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
width: 120
|
|
||||||
display: AbstractButton.TextUnderIcon
|
|
||||||
icon.name: "media-playback-start"
|
|
||||||
text: luatr("Create Room")
|
text: luatr("Create Room")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
lobby_dialog.sourceComponent =
|
lobby_dialog.sourceComponent =
|
||||||
|
@ -192,11 +205,99 @@ Item {
|
||||||
config.replaying = false;
|
config.replaying = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
id: roomList
|
||||||
|
cellWidth: 128
|
||||||
|
cellHeight: 128
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
delegate: roomDelegate
|
||||||
|
clip: true
|
||||||
|
model: roomModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: serverInfoLayout
|
||||||
|
height: root.height - 112
|
||||||
|
y: 56
|
||||||
|
width: root.width * 0.94 * 0.2
|
||||||
|
anchors.right: parent.right
|
||||||
|
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 {
|
RowLayout {
|
||||||
id: buttonRow
|
id: buttonRow
|
||||||
anchors.right: parent.right
|
anchors.left: parent.left
|
||||||
anchors.bottom: parent.bottom
|
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 {
|
Button {
|
||||||
text: luatr("Generals Overview")
|
text: luatr("Generals Overview")
|
||||||
onClicked: {
|
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) {
|
function enterRoom(roomId, playerNum, capacity, pw) {
|
||||||
config.replaying = false;
|
config.replaying = false;
|
||||||
if (playerNum < capacity) {
|
if (playerNum < capacity) {
|
||||||
|
@ -333,40 +401,15 @@ Item {
|
||||||
property int lobbyPlayerNum: 0
|
property int lobbyPlayerNum: 0
|
||||||
property int serverPlayerNum: 0
|
property int serverPlayerNum: 0
|
||||||
|
|
||||||
|
/*
|
||||||
function updateOnlineInfo() {
|
function updateOnlineInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLobbyPlayerNumChanged: updateOnlineInfo();
|
onLobbyPlayerNumChanged: updateOnlineInfo();
|
||||||
onServerPlayerNumChanged: 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 {
|
Danmaku {
|
||||||
id: danmaku
|
id: danmaku
|
||||||
|
|
|
@ -53,7 +53,6 @@ Window {
|
||||||
|
|
||||||
Component { id: init; Init {} }
|
Component { id: init; Init {} }
|
||||||
Component { id: packageManage; PackageManage {} }
|
Component { id: packageManage; PackageManage {} }
|
||||||
Component { id: modMaker; ModMaker {} }
|
|
||||||
Component { id: lobby; Lobby {} }
|
Component { id: lobby; Lobby {} }
|
||||||
Component { id: generalsOverview; GeneralsOverview {} }
|
Component { id: generalsOverview; GeneralsOverview {} }
|
||||||
Component { id: cardsOverview; CardsOverview {} }
|
Component { id: cardsOverview; CardsOverview {} }
|
||||||
|
|
21
genfkver.sh
Executable file
21
genfkver.sh
Executable file
|
@ -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 -
|
|
@ -113,6 +113,38 @@
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
<name>QmlBackend</name>
|
||||||
|
<message>
|
||||||
|
<source>FreeKill</source>
|
||||||
|
<translation>新月杀</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>help: others logged in again with this name</source>
|
||||||
|
<translation>提示:请检查密码是否泄漏</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>help: unknown password error</source>
|
||||||
|
<translation>提示:请尝试重新启动程序</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>help: you have been banned!</source>
|
||||||
|
<translation>提示:此为永久封禁,请联系管理员说明</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>help: you have been temporarily banned!</source>
|
||||||
|
<translation>提示:此为暂时封禁,一般在约二十分钟后自动解禁</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>help: user name not in whitelist</source>
|
||||||
|
<translation>提示:请联系服主解决</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>help: username or password error</source>
|
||||||
|
<translation>提示:可能该用户名已被占用,或者密码错误,如果你是初次注册的话考虑用另一个用户名密码进行登入</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
|
||||||
<context>
|
<context>
|
||||||
<name>Init</name>
|
<name>Init</name>
|
||||||
<message>
|
<message>
|
||||||
|
@ -292,7 +324,15 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>others logged in again with this name</source>
|
<source>others logged in again with this name</source>
|
||||||
<translation>其他人用你的用户名和密码登陆到了服务器,请检查密码是否泄漏</translation>
|
<translation>其他人用你的用户名和密码登陆到了服务器</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>unknown password error</source>
|
||||||
|
<translation>服务端解密密码时出现未知错误</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>user name not in whitelist</source>
|
||||||
|
<translation>你不在该服务器的白名单中!</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>invalid user name</source>
|
<source>invalid user name</source>
|
||||||
|
|
10
sgs
Normal file
10
sgs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"banwords": [ "习近", "近平", "共产党", "介石", "刘少奇", "邓小平", "江泽民", "胡锦涛", "毛泽东" ],
|
||||||
|
"description": "新月杀 [0.4.15] 主力联机服务器!请素质交流、理性对局!<b>交流请去贴吧[新月杀]吧</b>",
|
||||||
|
"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
|
||||||
|
}
|
|
@ -8,10 +8,14 @@ set(freekill_SRCS
|
||||||
"network/server_socket.cpp"
|
"network/server_socket.cpp"
|
||||||
"network/client_socket.cpp"
|
"network/client_socket.cpp"
|
||||||
"network/router.cpp"
|
"network/router.cpp"
|
||||||
|
"server/auth.cpp"
|
||||||
"server/server.cpp"
|
"server/server.cpp"
|
||||||
"server/serverplayer.cpp"
|
"server/serverplayer.cpp"
|
||||||
|
"server/roombase.cpp"
|
||||||
|
"server/lobby.cpp"
|
||||||
"server/room.cpp"
|
"server/room.cpp"
|
||||||
"server/roomthread.cpp"
|
"server/roomthread.cpp"
|
||||||
|
"server/scheduler.cpp"
|
||||||
"ui/qmlbackend.cpp"
|
"ui/qmlbackend.cpp"
|
||||||
"swig/freekill-wrap.cxx"
|
"swig/freekill-wrap.cxx"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +25,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
|
||||||
"client/client.cpp"
|
"client/client.cpp"
|
||||||
"client/clientplayer.cpp"
|
"client/clientplayer.cpp"
|
||||||
"client/replayer.cpp"
|
"client/replayer.cpp"
|
||||||
"ui/mod.cpp"
|
# "ui/mod.cpp"
|
||||||
)
|
)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#include "client_socket.h"
|
#include "client/clientplayer.h"
|
||||||
#include "clientplayer.h"
|
#include "ui/qmlbackend.h"
|
||||||
#include "qmlbackend.h"
|
#include "core/util.h"
|
||||||
#include "util.h"
|
#include "server/server.h"
|
||||||
#include "server.h"
|
#include "network/client_socket.h"
|
||||||
#include <qforeach.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
|
|
||||||
Client *ClientInstance = nullptr;
|
Client *ClientInstance = nullptr;
|
||||||
ClientPlayer *Self = nullptr;
|
ClientPlayer *Self = nullptr;
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
#ifndef _CLIENT_H
|
#ifndef _CLIENT_H
|
||||||
#define _CLIENT_H
|
#define _CLIENT_H
|
||||||
|
|
||||||
#include "router.h"
|
#include "network/router.h"
|
||||||
#include "clientplayer.h"
|
#include "client/clientplayer.h"
|
||||||
#include <qfilesystemwatcher.h>
|
|
||||||
|
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Client : public QObject {
|
class Client : public QObject {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "clientplayer.h"
|
#include "client/clientplayer.h"
|
||||||
|
|
||||||
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
|
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
|
||||||
setId(id);
|
setId(id);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#ifndef _CLIENTPLAYER_H
|
#ifndef _CLIENTPLAYER_H
|
||||||
#define _CLIENTPLAYER_H
|
#define _CLIENTPLAYER_H
|
||||||
|
|
||||||
#include "player.h"
|
#include "core/player.h"
|
||||||
|
|
||||||
class ClientPlayer : public Player {
|
class ClientPlayer : public Player {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "replayer.h"
|
#include "client/replayer.h"
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
|
|
||||||
Replayer::Replayer(QObject *parent, const QString &filename) :
|
Replayer::Replayer(QObject *parent, const QString &filename) :
|
||||||
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),
|
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "packman.h"
|
#include "core/packman.h"
|
||||||
#include "git2.h"
|
#include "git2.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
#include <qjsondocument.h>
|
|
||||||
|
|
||||||
PackMan *Pacman;
|
PackMan *Pacman;
|
||||||
|
|
||||||
|
@ -70,13 +69,16 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
|
||||||
auto obj = e.toObject();
|
auto obj = e.toObject();
|
||||||
auto name = obj["name"].toString();
|
auto name = obj["name"].toString();
|
||||||
auto url = obj["url"].toString();
|
auto url = obj["url"].toString();
|
||||||
#ifndef FK_SERVER_ONLY
|
bool toast_showed = false;
|
||||||
Backend->showToast(tr("[%1/%2] upgrading package '%3'").arg(i).arg(arr.count()).arg(name));
|
|
||||||
#endif
|
|
||||||
if (SelectFromDatabase(
|
if (SelectFromDatabase(
|
||||||
db,
|
db,
|
||||||
QString("SELECT name FROM packages WHERE name='%1';").arg(name))
|
QString("SELECT name FROM packages WHERE name='%1';").arg(name))
|
||||||
.isEmpty()) {
|
.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);
|
downloadNewPack(url);
|
||||||
}
|
}
|
||||||
ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'")
|
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);
|
enablePack(name);
|
||||||
|
|
||||||
if (head(name) != obj["hash"].toString()) {
|
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);
|
updatePack(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +178,7 @@ void PackMan::updatePack(const QString &pack) {
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
if (Backend != nullptr) {
|
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
|
#endif
|
||||||
return;
|
return;
|
||||||
|
@ -193,7 +200,7 @@ void PackMan::upgradePack(const QString &pack) {
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
if (Backend != nullptr) {
|
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
|
#endif
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
#ifndef _PACKMAN_H
|
#ifndef _PACKMAN_H
|
||||||
#define _PACKMAN_H
|
#define _PACKMAN_H
|
||||||
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
// 管理拓展包所需的类,本质上是libgit2接口的再封装。
|
// 管理拓展包所需的类,本质上是libgit2接口的再封装。
|
||||||
class PackMan : public QObject {
|
class PackMan : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "player.h"
|
#include "core/player.h"
|
||||||
|
|
||||||
Player::Player(QObject *parent)
|
Player::Player(QObject *parent)
|
||||||
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),
|
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
#include "packman.h"
|
#include "core/packman.h"
|
||||||
#include <qcryptographichash.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qregularexpression.h>
|
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
|
|
||||||
extern "C" {
|
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() {
|
QString calcFileMD5() {
|
||||||
// First, generate flist.txt
|
// First, generate flist.txt
|
||||||
// flist.txt is a file contains all md5sum for code files
|
// flist.txt is a file contains all md5sum for code files
|
||||||
|
@ -180,12 +188,13 @@ QString calcFileMD5() {
|
||||||
qFatal("Cannot open flist.txt. Quitting.");
|
qFatal("Cannot open flist.txt. Quitting.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeFkVerMD5(flist);
|
||||||
writeDirMD5(flist, "packages", "*.lua");
|
writeDirMD5(flist, "packages", "*.lua");
|
||||||
writeDirMD5(flist, "packages", "*.qml");
|
writeDirMD5(flist, "packages", "*.qml");
|
||||||
writeDirMD5(flist, "packages", "*.js");
|
writeDirMD5(flist, "packages", "*.js");
|
||||||
writeDirMD5(flist, "lua", "*.lua");
|
// writeDirMD5(flist, "lua", "*.lua");
|
||||||
writeDirMD5(flist, "Fk", "*.qml");
|
// writeDirMD5(flist, "Fk", "*.qml");
|
||||||
writeDirMD5(flist, "Fk", "*.js");
|
// writeDirMD5(flist, "Fk", "*.js");
|
||||||
|
|
||||||
// then, return flist.txt's md5
|
// then, return flist.txt's md5
|
||||||
flist.close();
|
flist.close();
|
||||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -1,14 +1,14 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
using namespace fkShell;
|
using namespace fkShell;
|
||||||
|
|
||||||
#include "packman.h"
|
#include "core/packman.h"
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||||
#include "shell.h"
|
#include "server/shell.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_WIN32)
|
#if defined(Q_OS_WIN32)
|
||||||
|
@ -22,7 +22,7 @@ using namespace fkShell;
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#endif
|
#endif
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
@ -113,10 +113,10 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "\r%02d/%02d ", date.month(), date.day());
|
fprintf(stderr, "%02d/%02d ", date.month(), date.day());
|
||||||
fprintf(stderr, "%s ",
|
fprintf(stderr, "%s ",
|
||||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
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 ",
|
fprintf(file, "%s ",
|
||||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||||
|
|
||||||
|
@ -150,8 +150,7 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
||||||
"C", localMsg.constData());
|
"C", localMsg.constData());
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
if (Backend != nullptr) {
|
if (Backend != nullptr) {
|
||||||
Backend->notifyUI(
|
Backend->notifyUI("ErrorDialog",
|
||||||
"ErrorDialog",
|
|
||||||
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -329,6 +328,7 @@ int main(int argc, char *argv[]) {
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
system = "Android";
|
system = "Android";
|
||||||
#elif defined(Q_OS_WIN32)
|
#elif defined(Q_OS_WIN32)
|
||||||
|
qputenv("QT_MEDIA_BACKEND", "windows");
|
||||||
system = "Win";
|
system = "Win";
|
||||||
::system("chcp 65001");
|
::system("chcp 65001");
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client_socket.h"
|
#include "network/client_socket.h"
|
||||||
#include <openssl/aes.h>
|
#include <openssl/aes.h>
|
||||||
#include <qabstractsocket.h>
|
|
||||||
#include <qrandom.h>
|
|
||||||
|
|
||||||
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
|
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
|
||||||
aes_ready = false;
|
aes_ready = false;
|
||||||
|
@ -35,7 +33,7 @@ void ClientSocket::connectToHost(const QString &address, ushort port) {
|
||||||
void ClientSocket::getMessage() {
|
void ClientSocket::getMessage() {
|
||||||
while (socket->canReadLine()) {
|
while (socket->canReadLine()) {
|
||||||
auto msg = socket->readLine();
|
auto msg = socket->readLine();
|
||||||
msg = aesDecrypt(msg);
|
msg = aesDec(msg);
|
||||||
if (msg.startsWith("Compressed")) {
|
if (msg.startsWith("Compressed")) {
|
||||||
msg = msg.sliced(10);
|
msg = msg.sliced(10);
|
||||||
msg = qUncompress(QByteArray::fromBase64(msg));
|
msg = qUncompress(QByteArray::fromBase64(msg));
|
||||||
|
@ -54,9 +52,9 @@ void ClientSocket::send(const QByteArray &msg) {
|
||||||
if (msg.length() >= 1024) {
|
if (msg.length() >= 1024) {
|
||||||
auto comp = qCompress(msg);
|
auto comp = qCompress(msg);
|
||||||
_msg = "Compressed" + comp.toBase64();
|
_msg = "Compressed" + comp.toBase64();
|
||||||
_msg = aesEncrypt(_msg) + "\n";
|
_msg = aesEnc(_msg) + "\n";
|
||||||
} else {
|
} else {
|
||||||
_msg = aesEncrypt(msg) + "\n";
|
_msg = aesEnc(msg) + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
socket->write(_msg);
|
socket->write(_msg);
|
||||||
|
@ -156,7 +154,7 @@ void ClientSocket::installAESKey(const QByteArray &key) {
|
||||||
aes_ready = true;
|
aes_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
|
QByteArray ClientSocket::aesEnc(const QByteArray &in) {
|
||||||
if (!aes_ready) {
|
if (!aes_ready) {
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
@ -182,7 +180,7 @@ QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
|
||||||
|
|
||||||
return iv + out.toBase64();
|
return iv + out.toBase64();
|
||||||
}
|
}
|
||||||
QByteArray ClientSocket::aesDecrypt(const QByteArray &in) {
|
QByteArray ClientSocket::aesDec(const QByteArray &in) {
|
||||||
if (!aes_ready) {
|
if (!aes_ready) {
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ private slots:
|
||||||
void raiseError(QAbstractSocket::SocketError error);
|
void raiseError(QAbstractSocket::SocketError error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray aesEncrypt(const QByteArray &in);
|
QByteArray aesEnc(const QByteArray &in);
|
||||||
QByteArray aesDecrypt(const QByteArray &out);
|
QByteArray aesDec(const QByteArray &out);
|
||||||
AES_KEY aes_key;
|
AES_KEY aes_key;
|
||||||
bool aes_ready;
|
bool aes_ready;
|
||||||
QTcpSocket *socket;
|
QTcpSocket *socket;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "router.h"
|
#include "network/router.h"
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#include "client_socket.h"
|
#include "network/client_socket.h"
|
||||||
#include "roomthread.h"
|
#include "server/roomthread.h"
|
||||||
#include <qjsondocument.h>
|
#include "server/server.h"
|
||||||
#include "server.h"
|
#include "server/serverplayer.h"
|
||||||
#include "serverplayer.h"
|
#include "core/util.h"
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
|
Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
|
||||||
: QObject(parent) {
|
: QObject(parent) {
|
||||||
|
@ -160,7 +159,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Room *room = player->getRoom();
|
auto room = player->getRoom();
|
||||||
room->handlePacket(player, command, jsonData);
|
room->handlePacket(player, command, jsonData);
|
||||||
}
|
}
|
||||||
} else if (type & TYPE_REQUEST) {
|
} else if (type & TYPE_REQUEST) {
|
||||||
|
@ -180,10 +179,13 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
|
|
||||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
|
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
|
||||||
player->setThinking(false);
|
player->setThinking(false);
|
||||||
// qDebug() << "wake up!";
|
auto _room = player->getRoom();
|
||||||
auto room = player->getRoom();
|
if (!_room->isLobby()) {
|
||||||
|
auto room = qobject_cast<Room *>(_room);
|
||||||
if (room->getThread()) {
|
if (room->getThread()) {
|
||||||
room->getThread()->wakeUp();
|
room->getThread()->wakeUp(room->getId());
|
||||||
|
// TODO: signal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestId != this->expectedReplyId)
|
if (requestId != this->expectedReplyId)
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "server_socket.h"
|
#include "network/server_socket.h"
|
||||||
#include "client_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);
|
server = new QTcpServer(this);
|
||||||
connect(server, &QTcpServer::newConnection, this,
|
connect(server, &QTcpServer::newConnection, this,
|
||||||
&ServerSocket::processNewConnection);
|
&ServerSocket::processNewConnection);
|
||||||
|
|
||||||
|
udpSocket = new QUdpSocket(this);
|
||||||
|
connect(udpSocket, &QUdpSocket::readyRead,
|
||||||
|
this, &ServerSocket::readPendingDatagrams);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerSocket::listen(const QHostAddress &address, ushort port) {
|
bool ServerSocket::listen(const QHostAddress &address, ushort port) {
|
||||||
|
udpSocket->bind(port);
|
||||||
return server->listen(address, port);
|
return server->listen(address, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,3 +27,29 @@ void ServerSocket::processNewConnection() {
|
||||||
[connection]() { connection->deleteLater(); });
|
[connection]() { connection->deleteLater(); });
|
||||||
emit new_connection(connection);
|
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<Server *>(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();
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ class ServerSocket : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerSocket();
|
ServerSocket(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
|
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
|
||||||
|
|
||||||
|
@ -20,9 +20,12 @@ signals:
|
||||||
private slots:
|
private slots:
|
||||||
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
||||||
void processNewConnection();
|
void processNewConnection();
|
||||||
|
void readPendingDatagrams();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTcpServer *server;
|
QTcpServer *server;
|
||||||
|
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
|
||||||
|
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _SERVER_SOCKET_H
|
#endif // _SERVER_SOCKET_H
|
||||||
|
|
195
src/server/auth.cpp
Normal file
195
src/server/auth.cpp
Normal file
|
@ -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<Server *>(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<Server *>(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<Server *>(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;
|
||||||
|
}
|
27
src/server/auth.h
Normal file
27
src/server/auth.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef _AUTH_H
|
||||||
|
#define _AUTH_H
|
||||||
|
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
|
||||||
|
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
|
162
src/server/lobby.cpp
Normal file
162
src/server/lobby.cpp
Normal file
|
@ -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<QString, room_cb> 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);
|
||||||
|
}
|
27
src/server/lobby.h
Normal file
27
src/server/lobby.h
Normal file
|
@ -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
|
|
@ -1,28 +1,25 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "room.h"
|
#include "server/room.h"
|
||||||
|
#include "server/lobby.h"
|
||||||
#include <qjsonarray.h>
|
|
||||||
#include <qjsondocument.h>
|
|
||||||
|
|
||||||
#ifdef FK_SERVER_ONLY
|
#ifdef FK_SERVER_ONLY
|
||||||
static void *ClientInstance = nullptr;
|
static void *ClientInstance = nullptr;
|
||||||
#else
|
#else
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "client_socket.h"
|
#include "network/client_socket.h"
|
||||||
#include "roomthread.h"
|
#include "server/roomthread.h"
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
#include "serverplayer.h"
|
#include "server/serverplayer.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
|
|
||||||
Room::Room(RoomThread *m_thread) {
|
Room::Room(RoomThread *m_thread) {
|
||||||
auto server = ServerInstance;
|
auto server = ServerInstance;
|
||||||
id = server->nextRoomId;
|
id = server->nextRoomId;
|
||||||
server->nextRoomId++;
|
server->nextRoomId++;
|
||||||
this->server = server;
|
this->server = server;
|
||||||
setThread(m_thread);
|
|
||||||
if (m_thread) { // In case of lobby
|
if (m_thread) { // In case of lobby
|
||||||
m_thread->addRoom(this);
|
m_thread->addRoom(this);
|
||||||
}
|
}
|
||||||
|
@ -36,14 +33,8 @@ Room::Room(RoomThread *m_thread) {
|
||||||
|
|
||||||
m_ready = true;
|
m_ready = true;
|
||||||
|
|
||||||
// 如果是普通房间而不是大厅,就初始化Lua,否则置Lua为nullptr
|
connect(this, &Room::playerAdded, server->lobby(), &Lobby::removePlayer);
|
||||||
if (!isLobby()) {
|
connect(this, &Room::playerRemoved, server->lobby(), &Lobby::addPlayer);
|
||||||
// 如果不是大厅,那么:
|
|
||||||
// * 只要房间添加人了,那么从大厅中移掉这个人
|
|
||||||
// * 只要有人离开房间,那就把他加到大厅去
|
|
||||||
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
|
|
||||||
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Room::~Room() {
|
Room::~Room() {
|
||||||
|
@ -56,8 +47,6 @@ Room::~Room() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Server *Room::getServer() const { return server; }
|
|
||||||
|
|
||||||
RoomThread *Room::getThread() const { return m_thread; }
|
RoomThread *Room::getThread() const { return m_thread; }
|
||||||
|
|
||||||
void Room::setThread(RoomThread *t) {
|
void Room::setThread(RoomThread *t) {
|
||||||
|
@ -71,8 +60,6 @@ int Room::getId() const { return id; }
|
||||||
|
|
||||||
void Room::setId(int id) { this->id = id; }
|
void Room::setId(int id) { this->id = id; }
|
||||||
|
|
||||||
bool Room::isLobby() const { return id == 0; }
|
|
||||||
|
|
||||||
QString Room::getName() const { return name; }
|
QString Room::getName() const { return name; }
|
||||||
|
|
||||||
void Room::setName(const QString &name) { this->name = 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; }
|
void Room::setSettings(QByteArray settings) { this->settings = settings; }
|
||||||
|
|
||||||
bool Room::isAbandoned() const {
|
bool Room::isAbandoned() const {
|
||||||
if (isLobby())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (players.isEmpty())
|
if (players.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -151,27 +135,16 @@ void Room::addPlayer(ServerPlayer *player) {
|
||||||
auto mode = settings["gameMode"].toString();
|
auto mode = settings["gameMode"].toString();
|
||||||
|
|
||||||
// 告诉房里所有玩家有新人进来了
|
// 告诉房里所有玩家有新人进来了
|
||||||
if (!isLobby()) {
|
|
||||||
jsonData << player->getId();
|
jsonData << player->getId();
|
||||||
jsonData << player->getScreenName();
|
jsonData << player->getScreenName();
|
||||||
jsonData << player->getAvatar();
|
jsonData << player->getAvatar();
|
||||||
jsonData << player->isReady();
|
jsonData << player->isReady();
|
||||||
jsonData << player->getTotalGameTime();
|
jsonData << player->getTotalGameTime();
|
||||||
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
|
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
|
||||||
}
|
|
||||||
|
|
||||||
players.append(player);
|
players.append(player);
|
||||||
player->setRoom(this);
|
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 = QJsonArray();
|
||||||
jsonData << this->capacity;
|
jsonData << this->capacity;
|
||||||
|
@ -216,7 +189,6 @@ void Room::addPlayer(ServerPlayer *player) {
|
||||||
// 玩家手动启动
|
// 玩家手动启动
|
||||||
// if (isFull() && !gameStarted)
|
// if (isFull() && !gameStarted)
|
||||||
// start();
|
// start();
|
||||||
}
|
|
||||||
emit playerAdded(player);
|
emit playerAdded(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,12 +223,7 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
}
|
}
|
||||||
emit playerRemoved(player);
|
emit playerRemoved(player);
|
||||||
|
|
||||||
if (isLobby())
|
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes({ player->getId() }));
|
||||||
return;
|
|
||||||
|
|
||||||
QJsonArray jsonData;
|
|
||||||
jsonData << player->getId();
|
|
||||||
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes(jsonData));
|
|
||||||
} else {
|
} else {
|
||||||
// 否则给跑路玩家召唤个AI代打
|
// 否则给跑路玩家召唤个AI代打
|
||||||
// TODO: if the player is died..
|
// TODO: if the player is died..
|
||||||
|
@ -287,7 +254,7 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
||||||
server->addPlayer(runner);
|
server->addPlayer(runner);
|
||||||
|
|
||||||
m_thread->wakeUp();
|
// m_thread->wakeUp();
|
||||||
|
|
||||||
// 发出信号,让大厅添加这个人
|
// 发出信号,让大厅添加这个人
|
||||||
emit playerRemoved(runner);
|
emit playerRemoved(runner);
|
||||||
|
@ -312,22 +279,6 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ServerPlayer *> Room::getPlayers() const { return players; }
|
|
||||||
|
|
||||||
QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer *expect) const {
|
|
||||||
QList<ServerPlayer *> 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) {
|
void Room::addObserver(ServerPlayer *player) {
|
||||||
// 首先只能旁观在运行的房间,因为旁观是由Lua处理的
|
// 首先只能旁观在运行的房间,因为旁观是由Lua处理的
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
|
@ -371,6 +322,10 @@ int Room::getTimeout() const { return timeout; }
|
||||||
|
|
||||||
void Room::setTimeout(int timeout) { this->timeout = timeout; }
|
void Room::setTimeout(int timeout) { this->timeout = timeout; }
|
||||||
|
|
||||||
|
void Room::delay(int ms) {
|
||||||
|
m_thread->delay(id, ms);
|
||||||
|
}
|
||||||
|
|
||||||
bool Room::isOutdated() {
|
bool Room::isOutdated() {
|
||||||
bool ret = md5 != server->getMd5();
|
bool ret = md5 != server->getMd5();
|
||||||
if (ret) md5 = "";
|
if (ret) md5 = "";
|
||||||
|
@ -379,42 +334,6 @@ bool Room::isOutdated() {
|
||||||
|
|
||||||
bool Room::isStarted() const { return gameStarted; }
|
bool Room::isStarted() const { return gameStarted; }
|
||||||
|
|
||||||
void Room::doBroadcastNotify(const QList<ServerPlayer *> 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 =
|
static const QString findWinRate =
|
||||||
QString("SELECT win, lose, draw "
|
QString("SELECT win, lose, draw "
|
||||||
"FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';");
|
"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();
|
auto room = player->getRoom();
|
||||||
player->setGameData(total, win, run);
|
player->setGameData(total, win, run);
|
||||||
auto data_arr = QJsonArray({ player->getId(), 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() {
|
void Room::gameOver() {
|
||||||
if (!gameStarted) return;
|
if (!gameStarted) return;
|
||||||
|
insideGameOver = true;
|
||||||
gameStarted = false;
|
gameStarted = false;
|
||||||
runned_players.clear();
|
runned_players.clear();
|
||||||
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
|
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
|
||||||
auto settings = QJsonDocument::fromJson(this->settings);
|
auto settings = QJsonDocument::fromJson(this->settings);
|
||||||
auto mode = settings["gameMode"].toString();
|
auto mode = settings["gameMode"].toString();
|
||||||
|
server->beginTransaction();
|
||||||
foreach (ServerPlayer *p, players) {
|
foreach (ServerPlayer *p, players) {
|
||||||
auto pid = p->getId();
|
auto pid = p->getId();
|
||||||
|
|
||||||
|
@ -578,7 +497,7 @@ void Room::gameOver() {
|
||||||
realPlayer->doNotify("AddTotalGameTime", bytes);
|
realPlayer->doNotify("AddTotalGameTime", bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 摸了,这么写总之不会有问题
|
// 将游戏时间更新到数据库中
|
||||||
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
|
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
|
||||||
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
|
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
|
||||||
ExecSQL(server->getDatabase(), info_update);
|
ExecSQL(server->getDatabase(), info_update);
|
||||||
|
@ -587,18 +506,13 @@ void Room::gameOver() {
|
||||||
if (p->getState() != Player::Online) {
|
if (p->getState() != Player::Online) {
|
||||||
if (p->getState() == Player::Offline) {
|
if (p->getState() == Player::Offline) {
|
||||||
addRunRate(pid, mode);
|
addRunRate(pid, mode);
|
||||||
// addRunRate(pid, mode);
|
|
||||||
server->temporarilyBan(pid);
|
server->temporarilyBan(pid);
|
||||||
}
|
}
|
||||||
p->deleteLater();
|
p->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 旁观者不能在这清除,因为removePlayer逻辑不一样
|
server->endTransaction();
|
||||||
// observers.clear();
|
insideGameOver = true;
|
||||||
// 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
|
|
||||||
// players.clear();
|
|
||||||
// owner = nullptr;
|
|
||||||
// clearRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::manuallyStart() {
|
void Room::manuallyStart() {
|
||||||
|
@ -620,7 +534,6 @@ void Room::pushRequest(const QString &req) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::addRejectId(int id) {
|
void Room::addRejectId(int id) {
|
||||||
if (isLobby()) return;
|
|
||||||
rejected_players << id;
|
rejected_players << id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,184 +542,84 @@ void Room::removeRejectId(int id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
static void updateAvatar(ServerPlayer *sender, const QString &jsonData) {
|
void Room::quitRoom(ServerPlayer *player, const QString &) {
|
||||||
auto arr = String2Json(jsonData).array();
|
removePlayer(player);
|
||||||
auto avatar = arr[0].toString();
|
if (isOutdated()) {
|
||||||
|
|
||||||
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()) {
|
|
||||||
player->kicked();
|
player->kicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addRobot(ServerPlayer *player, const QString &) {
|
void Room::addRobotRequest(ServerPlayer *player, const QString &) {
|
||||||
auto room = player->getRoom();
|
|
||||||
if (ServerInstance->getConfig("enableBots").toBool())
|
if (ServerInstance->getConfig("enableBots").toBool())
|
||||||
room->addRobot(player);
|
addRobot(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void kickPlayer(ServerPlayer *player, const QString &jsonData) {
|
void Room::kickPlayer(ServerPlayer *player, const QString &jsonData) {
|
||||||
auto room = player->getRoom();
|
|
||||||
int i = jsonData.toInt();
|
int i = jsonData.toInt();
|
||||||
auto p = room->findPlayer(i);
|
auto p = findPlayer(i);
|
||||||
if (p && !room->isStarted()) {
|
if (p && !isStarted()) {
|
||||||
room->removePlayer(p);
|
removePlayer(p);
|
||||||
room->addRejectId(i);
|
addRejectId(i);
|
||||||
QTimer::singleShot(30000, room, [=]() {
|
QTimer::singleShot(30000, this, [=]() {
|
||||||
room->removeRejectId(i);
|
removeRejectId(i);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ready(ServerPlayer *player, const QString &) {
|
void Room::ready(ServerPlayer *player, const QString &) {
|
||||||
auto room = player->getRoom();
|
|
||||||
player->setReady(!player->isReady());
|
player->setReady(!player->isReady());
|
||||||
room->doBroadcastNotify(room->getPlayers(), "ReadyChanged",
|
doBroadcastNotify(getPlayers(), "ReadyChanged",
|
||||||
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
|
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void startGame(ServerPlayer *player, const QString &) {
|
void Room::startGame(ServerPlayer *player, const QString &) {
|
||||||
auto room = player->getRoom();
|
if (isOutdated()) {
|
||||||
if (room->isOutdated()) {
|
foreach (auto p, getPlayers()) {
|
||||||
foreach (auto p, room->getPlayers()) {
|
|
||||||
p->doNotify("ErrorMsg", "room is outdated");
|
p->doNotify("ErrorMsg", "room is outdated");
|
||||||
p->kicked();
|
p->kicked();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
room->manuallyStart();
|
manuallyStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*room_cb)(ServerPlayer *, const QString &);
|
typedef void (Room::*room_cb)(ServerPlayer *, const QString &);
|
||||||
static const QMap<QString, room_cb> lobby_actions = {
|
|
||||||
{"UpdateAvatar", updateAvatar},
|
|
||||||
{"UpdatePassword", updatePassword},
|
|
||||||
{"CreateRoom", createRoom},
|
|
||||||
{"EnterRoom", enterRoom},
|
|
||||||
{"ObserveRoom", observeRoom},
|
|
||||||
{"RefreshRoomList", refreshRoomList},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const QMap<QString, room_cb> room_actions = {
|
|
||||||
{"QuitRoom", quitRoom},
|
|
||||||
{"AddRobot", addRobot},
|
|
||||||
{"KickPlayer", kickPlayer},
|
|
||||||
{"Ready", ready},
|
|
||||||
{"StartGame", startGame},
|
|
||||||
};
|
|
||||||
|
|
||||||
void Room::handlePacket(ServerPlayer *sender, const QString &command,
|
void Room::handlePacket(ServerPlayer *sender, const QString &command,
|
||||||
const QString &jsonData) {
|
const QString &jsonData) {
|
||||||
if (command == "Chat") {
|
static const QMap<QString, room_cb> room_actions = {
|
||||||
chat(sender, jsonData);
|
{"QuitRoom", &Room::quitRoom},
|
||||||
return;
|
{"AddRobot", &Room::addRobotRequest},
|
||||||
} else if (command == "PushRequest") {
|
{"KickPlayer", &Room::kickPlayer},
|
||||||
if (!isLobby())
|
{"Ready", &Room::ready},
|
||||||
|
{"StartGame", &Room::startGame},
|
||||||
|
{"Chat", &Room::chat},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (command == "PushRequest") {
|
||||||
pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
|
pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto func_table = lobby_actions;
|
auto func = room_actions[command];
|
||||||
if (!isLobby()) func_table = room_actions;
|
if (func) (this->*func)(sender, jsonData);
|
||||||
auto func = func_table[command];
|
|
||||||
if (func) {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
#ifndef _ROOM_H
|
#ifndef _ROOM_H
|
||||||
#define _ROOM_H
|
#define _ROOM_H
|
||||||
|
|
||||||
|
#include "server/roombase.h"
|
||||||
|
|
||||||
class Server;
|
class Server;
|
||||||
class ServerPlayer;
|
class ServerPlayer;
|
||||||
class RoomThread;
|
class RoomThread;
|
||||||
|
|
||||||
class Room : public QObject {
|
class Room : public RoomBase {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit Room(RoomThread *m_thread);
|
explicit Room(RoomThread *m_thread);
|
||||||
|
@ -15,12 +17,11 @@ class Room : public QObject {
|
||||||
|
|
||||||
// Property reader & setter
|
// Property reader & setter
|
||||||
// ==================================={
|
// ==================================={
|
||||||
Server *getServer() const;
|
|
||||||
RoomThread *getThread() const;
|
RoomThread *getThread() const;
|
||||||
void setThread(RoomThread *t);
|
void setThread(RoomThread *t);
|
||||||
|
|
||||||
int getId() const;
|
int getId() const;
|
||||||
void setId(int id);
|
void setId(int id);
|
||||||
bool isLobby() const;
|
|
||||||
QString getName() const;
|
QString getName() const;
|
||||||
void setName(const QString &name);
|
void setName(const QString &name);
|
||||||
int getCapacity() const;
|
int getCapacity() const;
|
||||||
|
@ -38,9 +39,6 @@ class Room : public QObject {
|
||||||
void addPlayer(ServerPlayer *player);
|
void addPlayer(ServerPlayer *player);
|
||||||
void addRobot(ServerPlayer *player);
|
void addRobot(ServerPlayer *player);
|
||||||
void removePlayer(ServerPlayer *player);
|
void removePlayer(ServerPlayer *player);
|
||||||
QList<ServerPlayer *> getPlayers() const;
|
|
||||||
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
|
|
||||||
ServerPlayer *findPlayer(int id) const;
|
|
||||||
|
|
||||||
void addObserver(ServerPlayer *player);
|
void addObserver(ServerPlayer *player);
|
||||||
void removeObserver(ServerPlayer *player);
|
void removeObserver(ServerPlayer *player);
|
||||||
|
@ -49,16 +47,13 @@ class Room : public QObject {
|
||||||
|
|
||||||
int getTimeout() const;
|
int getTimeout() const;
|
||||||
void setTimeout(int timeout);
|
void setTimeout(int timeout);
|
||||||
|
void delay(int ms);
|
||||||
|
|
||||||
bool isOutdated();
|
bool isOutdated();
|
||||||
|
|
||||||
bool isStarted() const;
|
bool isStarted() const;
|
||||||
// ====================================}
|
// ====================================}
|
||||||
|
|
||||||
void doBroadcastNotify(const QList<ServerPlayer *> targets,
|
|
||||||
const QString &command, const QString &jsonData);
|
|
||||||
void chat(ServerPlayer *sender, const QString &jsonData);
|
|
||||||
|
|
||||||
void updateWinRate(int id, const QString &general, const QString &mode,
|
void updateWinRate(int id, const QString &general, const QString &mode,
|
||||||
int result, bool dead);
|
int result, bool dead);
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
@ -71,6 +66,13 @@ class Room : public QObject {
|
||||||
// router用
|
// router用
|
||||||
void handlePacket(ServerPlayer *sender, const QString &command,
|
void handlePacket(ServerPlayer *sender, const QString &command,
|
||||||
const QString &jsonData);
|
const QString &jsonData);
|
||||||
|
|
||||||
|
void setRequestTimer(int ms);
|
||||||
|
void destroyRequestTimer();
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
volatile bool insideGameOver = false;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void abandoned();
|
void abandoned();
|
||||||
|
|
||||||
|
@ -78,8 +80,7 @@ class Room : public QObject {
|
||||||
void playerRemoved(ServerPlayer *player);
|
void playerRemoved(ServerPlayer *player);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Server *server;
|
RoomThread *m_thread = nullptr;
|
||||||
RoomThread *m_thread;
|
|
||||||
int id; // Lobby's id is 0
|
int id; // Lobby's id is 0
|
||||||
QString name; // “阴间大乱斗”
|
QString name; // “阴间大乱斗”
|
||||||
int capacity; // by default is 5, max is 8
|
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
|
bool m_abandoned; // If room is empty, delete it
|
||||||
|
|
||||||
ServerPlayer *owner; // who created this room?
|
ServerPlayer *owner; // who created this room?
|
||||||
QList<ServerPlayer *> players;
|
|
||||||
QList<ServerPlayer *> observers;
|
|
||||||
QList<int> runned_players;
|
QList<int> runned_players;
|
||||||
QList<int> rejected_players;
|
QList<int> rejected_players;
|
||||||
int robot_id;
|
int robot_id;
|
||||||
|
@ -98,8 +97,17 @@ class Room : public QObject {
|
||||||
int timeout;
|
int timeout;
|
||||||
QString md5;
|
QString md5;
|
||||||
|
|
||||||
|
QTimer *request_timer = nullptr;
|
||||||
|
|
||||||
void addRunRate(int id, const QString &mode);
|
void addRunRate(int id, const QString &mode);
|
||||||
void updatePlayerGameData(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
|
#endif // _ROOM_H
|
||||||
|
|
62
src/server/roombase.cpp
Normal file
62
src/server/roombase.cpp
Normal file
|
@ -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<ServerPlayer *> RoomBase::getPlayers() const { return players; }
|
||||||
|
|
||||||
|
QList<ServerPlayer *> RoomBase::getOtherPlayers(ServerPlayer *expect) const {
|
||||||
|
QList<ServerPlayer *> 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<ServerPlayer *> 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());
|
||||||
|
}
|
30
src/server/roombase.h
Normal file
30
src/server/roombase.h
Normal file
|
@ -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<ServerPlayer *> getPlayers() const;
|
||||||
|
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
|
||||||
|
ServerPlayer *findPlayer(int id) const;
|
||||||
|
|
||||||
|
void doBroadcastNotify(const QList<ServerPlayer *> 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<ServerPlayer *> players;
|
||||||
|
QList<ServerPlayer *> observers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _ROOMBASE_H
|
|
@ -1,45 +1,42 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "roomthread.h"
|
#include "server/roomthread.h"
|
||||||
#include "server.h"
|
#include "server/scheduler.h"
|
||||||
#include "util.h"
|
#include "server/server.h"
|
||||||
#include <lua.h>
|
|
||||||
|
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RoomThread::RoomThread(Server *m_server) {
|
RoomThread::RoomThread(Server *m_server) {
|
||||||
setObjectName("Room");
|
setObjectName("Room");
|
||||||
this->m_server = m_server;
|
this->m_server = m_server;
|
||||||
m_capacity = 100; // TODO: server cfg
|
m_capacity = 100; // TODO: server cfg
|
||||||
terminated = false;
|
|
||||||
md5 = m_server->getMd5();
|
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();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomThread::~RoomThread() {
|
RoomThread::~RoomThread() {
|
||||||
tryTerminate();
|
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
wait();
|
quit();
|
||||||
}
|
}
|
||||||
lua_close(L);
|
delete m_scheduler;
|
||||||
m_server->removeThread(this);
|
m_server->removeThread(this);
|
||||||
// foreach (auto room, room_list) {
|
// foreach (auto room, room_list) {
|
||||||
// room->deleteLater();
|
// 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 {
|
Server *RoomThread::getServer() const {
|
||||||
return m_server;
|
return m_server;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +53,7 @@ Room *RoomThread::getRoom(int id) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomThread::addRoom(Room *room) {
|
void RoomThread::addRoom(Room *room) {
|
||||||
Q_UNUSED(room);
|
room->setThread(this);
|
||||||
m_capacity--;
|
m_capacity--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +66,7 @@ void RoomThread::removeRoom(Room *room) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
QString RoomThread::fetchRequest() {
|
QString RoomThread::fetchRequest() {
|
||||||
// if (!gameStarted)
|
// if (!gameStarted)
|
||||||
// return "";
|
// return "";
|
||||||
|
@ -124,6 +122,7 @@ void RoomThread::tryTerminate() {
|
||||||
bool RoomThread::isTerminated() const {
|
bool RoomThread::isTerminated() const {
|
||||||
return terminated;
|
return terminated;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
bool RoomThread::isConsoleStart() const {
|
bool RoomThread::isConsoleStart() const {
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
#ifndef _ROOMTHREAD_H
|
#ifndef _ROOMTHREAD_H
|
||||||
#define _ROOMTHREAD_H
|
#define _ROOMTHREAD_H
|
||||||
|
|
||||||
#include <qsemaphore.h>
|
|
||||||
class Room;
|
class Room;
|
||||||
class Server;
|
class Server;
|
||||||
|
class Scheduler;
|
||||||
|
|
||||||
class RoomThread : public QThread {
|
class RoomThread : public QThread {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -21,21 +21,24 @@ class RoomThread : public QThread {
|
||||||
void addRoom(Room *room);
|
void addRoom(Room *room);
|
||||||
void removeRoom(Room *room);
|
void removeRoom(Room *room);
|
||||||
|
|
||||||
QString fetchRequest();
|
//QString fetchRequest();
|
||||||
void pushRequest(const QString &req);
|
//void clearRequest();
|
||||||
void clearRequest();
|
//bool hasRequest();
|
||||||
bool hasRequest();
|
|
||||||
|
|
||||||
void trySleep(int ms);
|
// void trySleep(int ms);
|
||||||
void wakeUp();
|
|
||||||
|
|
||||||
void tryTerminate();
|
// void tryTerminate();
|
||||||
bool isTerminated() const;
|
// bool isTerminated() const;
|
||||||
|
|
||||||
bool isConsoleStart() const;
|
bool isConsoleStart() const;
|
||||||
|
|
||||||
bool isOutdated();
|
bool isOutdated();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void pushRequest(const QString &req);
|
||||||
|
void delay(int roomId, int ms);
|
||||||
|
void wakeUp(int roomId);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void run();
|
virtual void run();
|
||||||
|
|
||||||
|
@ -45,11 +48,11 @@ class RoomThread : public QThread {
|
||||||
int m_capacity;
|
int m_capacity;
|
||||||
QString md5;
|
QString md5;
|
||||||
|
|
||||||
lua_State *L;
|
Scheduler *m_scheduler;
|
||||||
QMutex request_queue_mutex;
|
// QMutex request_queue_mutex;
|
||||||
QQueue<QString> request_queue; // json string
|
// QQueue<QString> request_queue; // json string
|
||||||
QSemaphore sema_wake;
|
// QSemaphore sema_wake;
|
||||||
volatile bool terminated;
|
// volatile bool terminated;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _ROOMTHREAD_H
|
#endif // _ROOMTHREAD_H
|
||||||
|
|
55
src/server/scheduler.cpp
Normal file
55
src/server/scheduler.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
29
src/server/scheduler.h
Normal file
29
src/server/scheduler.h
Normal file
|
@ -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 *> room_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _ROOMTHREAD_H
|
|
@ -1,54 +1,34 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// 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 <qjsonarray.h>
|
|
||||||
#include <qjsondocument.h>
|
|
||||||
#include <qjsonvalue.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qversionnumber.h>
|
|
||||||
#include <QNetworkDatagram>
|
#include <QNetworkDatagram>
|
||||||
|
|
||||||
#include <openssl/bn.h>
|
|
||||||
|
|
||||||
#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 *ServerInstance = nullptr;
|
||||||
|
|
||||||
Server::Server(QObject *parent) : QObject(parent) {
|
Server::Server(QObject *parent) : QObject(parent) {
|
||||||
ServerInstance = this;
|
ServerInstance = this;
|
||||||
db = OpenDatabase();
|
db = OpenDatabase();
|
||||||
rsa = initServerRSA();
|
|
||||||
QFile file("server/rsa_pub");
|
|
||||||
file.open(QIODevice::ReadOnly);
|
|
||||||
QTextStream in(&file);
|
|
||||||
public_key = in.readAll();
|
|
||||||
md5 = calcFileMD5();
|
md5 = calcFileMD5();
|
||||||
readConfig();
|
readConfig();
|
||||||
|
|
||||||
server = new ServerSocket();
|
auth = new AuthManager(this);
|
||||||
server->setParent(this);
|
server = new ServerSocket(this);
|
||||||
connect(server, &ServerSocket::new_connection, this,
|
connect(server, &ServerSocket::new_connection, this,
|
||||||
&Server::processNewConnection);
|
&Server::processNewConnection);
|
||||||
|
|
||||||
udpSocket = new QUdpSocket(this);
|
nextRoomId = 1;
|
||||||
connect(udpSocket, &QUdpSocket::readyRead,
|
m_lobby = new Lobby(this);
|
||||||
this, &Server::readPendingDatagrams);
|
|
||||||
|
|
||||||
// 创建第一个房间,这个房间作为“大厅房间”
|
|
||||||
nextRoomId = 0;
|
|
||||||
createRoom(nullptr, "Lobby", INT32_MAX);
|
|
||||||
// 大厅只要发生人员变动,就向所有人广播一下房间列表
|
|
||||||
connect(lobby(), &Room::playerAdded, this, &Server::updateOnlineInfo);
|
|
||||||
connect(lobby(), &Room::playerRemoved, this, &Server::updateOnlineInfo);
|
|
||||||
|
|
||||||
// 启动心跳包线程
|
// 启动心跳包线程
|
||||||
auto heartbeatThread = QThread::create([=]() {
|
auto heartbeatThread = QThread::create([=]() {
|
||||||
|
@ -90,12 +70,10 @@ Server::~Server() {
|
||||||
thread->deleteLater();
|
thread->deleteLater();
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
RSA_free(rsa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Server::listen(const QHostAddress &address, ushort port) {
|
bool Server::listen(const QHostAddress &address, ushort port) {
|
||||||
bool ret = server->listen(address, port);
|
bool ret = server->listen(address, port);
|
||||||
udpSocket->bind(port);
|
|
||||||
isListening = ret;
|
isListening = ret;
|
||||||
return 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();
|
thread = createThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,15 +105,11 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
||||||
room->setId(nextRoomId);
|
room->setId(nextRoomId);
|
||||||
nextRoomId++;
|
nextRoomId++;
|
||||||
room->setAbandoned(false);
|
room->setAbandoned(false);
|
||||||
room->setThread(thread);
|
|
||||||
thread->addRoom(room);
|
thread->addRoom(room);
|
||||||
rooms.insert(room->getId(), room);
|
rooms.insert(room->getId(), room);
|
||||||
} else {
|
} else {
|
||||||
room = new Room(thread);
|
room = new Room(thread);
|
||||||
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
|
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
|
||||||
if (room->isLobby())
|
|
||||||
m_lobby = room;
|
|
||||||
else
|
|
||||||
rooms.insert(room->getId(), room);
|
rooms.insert(room->getId(), room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,13 +118,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
||||||
room->setTimeout(timeout);
|
room->setTimeout(timeout);
|
||||||
room->setSettings(settings);
|
room->setSettings(settings);
|
||||||
room->addPlayer(owner);
|
room->addPlayer(owner);
|
||||||
if (!room->isLobby())
|
|
||||||
room->setOwner(owner);
|
room->setOwner(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
Room *Server::findRoom(int id) const { return rooms.value(id); }
|
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 *Server::createThread() {
|
||||||
RoomThread *thread = new RoomThread(this);
|
RoomThread *thread = new RoomThread(this);
|
||||||
|
@ -234,27 +207,6 @@ void Server::sendEarlyPacket(ClientSocket *client, const QString &type, const QS
|
||||||
client->send(JsonArray2Bytes(body));
|
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) {
|
void Server::setupPlayer(ServerPlayer *player, bool all_info) {
|
||||||
// tell the lobby player's basic property
|
// tell the lobby player's basic property
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
|
@ -291,7 +243,7 @@ void Server::processNewConnection(ClientSocket *client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errmsg.isEmpty()) {
|
if (!errmsg.isEmpty()) {
|
||||||
sendEarlyPacket(client, "ErrorMsg", errmsg);
|
sendEarlyPacket(client, "ErrorDlg", errmsg);
|
||||||
qInfo() << "Refused banned IP:" << addr;
|
qInfo() << "Refused banned IP:" << addr;
|
||||||
client->disconnectFromHost();
|
client->disconnectFromHost();
|
||||||
return;
|
return;
|
||||||
|
@ -301,7 +253,7 @@ void Server::processNewConnection(ClientSocket *client) {
|
||||||
[client]() { qInfo() << client->peerAddress() << "disconnected"; });
|
[client]() { qInfo() << client->peerAddress() << "disconnected"; });
|
||||||
|
|
||||||
// network delay test
|
// network delay test
|
||||||
sendEarlyPacket(client, "NetworkDelayTest", public_key);
|
sendEarlyPacket(client, "NetworkDelayTest", auth->getPublicKey());
|
||||||
// Note: the client should send a setup string next
|
// Note: the client should send a setup string next
|
||||||
connect(client, &ClientSocket::message_got, this, &Server::processRequest);
|
connect(client, &ClientSocket::message_got, this, &Server::processRequest);
|
||||||
client->timerSignup.start(30000);
|
client->timerSignup.start(30000);
|
||||||
|
@ -328,56 +280,30 @@ void Server::processRequest(const QByteArray &msg) {
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
qWarning() << "Invalid setup string:" << msg;
|
qWarning() << "Invalid setup string:" << msg;
|
||||||
sendEarlyPacket(client, "ErrorMsg", "INVALID SETUP STRING");
|
sendEarlyPacket(client, "ErrorDlg", "INVALID SETUP STRING");
|
||||||
client->disconnectFromHost();
|
client->disconnectFromHost();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray arr = String2Json(doc[3].toString()).array();
|
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});
|
auto result2 = QJsonArray({1});
|
||||||
if (CheckSqlString(uuid)) {
|
if (CheckSqlString(uuid_str)) {
|
||||||
result2 = SelectFromDatabase(
|
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()) {
|
if (!result2.isEmpty()) {
|
||||||
sendEarlyPacket(client, "ErrorMsg", "you have been banned!");
|
sendEarlyPacket(client, "ErrorDlg", "you have been banned!");
|
||||||
qInfo() << "Refused banned UUID:" << uuid;
|
qInfo() << "Refused banned UUID:" << uuid_str;
|
||||||
client->disconnectFromHost();
|
client->disconnectFromHost();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameAndPassword(client, arr[0].toString(), arr[1].toString(),
|
auto md5_str = arr[2].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";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (md5 != md5_str) {
|
if (md5 != md5_str) {
|
||||||
sendEarlyPacket(client, "ErrorMsg", "MD5 check failed!");
|
sendEarlyPacket(client, "ErrorMsg", "MD5 check failed!");
|
||||||
sendEarlyPacket(client, "UpdatePackage", Pacman->getPackSummary());
|
sendEarlyPacket(client, "UpdatePackage", Pacman->getPackSummary());
|
||||||
|
@ -385,99 +311,11 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool passed = false;
|
auto name = arr[0].toString();
|
||||||
QString error_msg;
|
auto password = arr[1].toString();
|
||||||
QJsonArray result;
|
auto obj = auth->checkPassword(client, name, password);
|
||||||
QJsonObject obj;
|
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();
|
|
||||||
|
|
||||||
auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
|
|
||||||
ExecSQL(db, info_update);
|
|
||||||
|
|
||||||
passed = true;
|
|
||||||
} else {
|
|
||||||
obj = result[0].toObject();
|
|
||||||
|
|
||||||
// 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";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passed) {
|
|
||||||
// update lastLoginIp
|
// update lastLoginIp
|
||||||
int id = obj["id"].toString().toInt();
|
int id = obj["id"].toString().toInt();
|
||||||
beginTransaction();
|
beginTransaction();
|
||||||
|
@ -487,7 +325,8 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
|
||||||
.arg(id);
|
.arg(id);
|
||||||
ExecSQL(db, sql_update);
|
ExecSQL(db, sql_update);
|
||||||
|
|
||||||
auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');").arg(id).arg(uuid_str);
|
auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');")
|
||||||
|
.arg(id).arg(uuid_str);
|
||||||
ExecSQL(db, uuid_update);
|
ExecSQL(db, uuid_update);
|
||||||
|
|
||||||
// 来晚了,有很大可能存在已经注册但是表里面没数据的人
|
// 来晚了,有很大可能存在已经注册但是表里面没数据的人
|
||||||
|
@ -519,12 +358,6 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
|
||||||
player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
|
player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
|
||||||
|
|
||||||
lobby()->addPlayer(player);
|
lobby()->addPlayer(player);
|
||||||
} else {
|
|
||||||
qInfo() << client->peerAddress() << "lost connection:" << error_msg;
|
|
||||||
sendEarlyPacket(client, "ErrorMsg", error_msg);
|
|
||||||
client->disconnectFromHost();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::onRoomAbandoned() {
|
void Server::onRoomAbandoned() {
|
||||||
|
@ -537,16 +370,23 @@ void Server::onRoomAbandoned() {
|
||||||
// FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。
|
// FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。
|
||||||
// room->deleteLater();
|
// room->deleteLater();
|
||||||
idle_rooms.push(room);
|
idle_rooms.push(room);
|
||||||
|
room->getThread()->wakeUp(room->getId());
|
||||||
room->getThread()->removeRoom(room);
|
room->getThread()->removeRoom(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::onUserDisconnected() {
|
void Server::onUserDisconnected() {
|
||||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
|
auto player = qobject_cast<ServerPlayer *>(sender());
|
||||||
qInfo() << "Player" << player->getId() << "disconnected";
|
qInfo() << "Player" << player->getId() << "disconnected";
|
||||||
if (players.count() <= 10) {
|
if (players.count() <= 10) {
|
||||||
broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName()));
|
broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName()));
|
||||||
}
|
}
|
||||||
Room *room = player->getRoom();
|
|
||||||
|
auto _room = player->getRoom();
|
||||||
|
if (_room->isLobby()) {
|
||||||
|
player->setState(Player::Robot); // 大厅!然而又不能设Offline
|
||||||
|
player->deleteLater();
|
||||||
|
} else {
|
||||||
|
auto room = qobject_cast<Room *>(_room);
|
||||||
if (room->isStarted()) {
|
if (room->isStarted()) {
|
||||||
if (room->getObservers().contains(player)) {
|
if (room->getObservers().contains(player)) {
|
||||||
room->removeObserver(player);
|
room->removeObserver(player);
|
||||||
|
@ -558,16 +398,21 @@ void Server::onUserDisconnected() {
|
||||||
// TODO: add a robot
|
// TODO: add a robot
|
||||||
} else {
|
} else {
|
||||||
player->setState(Player::Robot); // 大厅!然而又不能设Offline
|
player->setState(Player::Robot); // 大厅!然而又不能设Offline
|
||||||
|
// 这里有一个多线程问题,可能与Room::gameOver同时deleteLater导致出事
|
||||||
|
// FIXME: 这种解法肯定不安全
|
||||||
|
if (!room->insideGameOver)
|
||||||
player->deleteLater();
|
player->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Server::onUserStateChanged() {
|
void Server::onUserStateChanged() {
|
||||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
|
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
|
||||||
auto room = player->getRoom();
|
auto _room = player->getRoom();
|
||||||
if (!room || room->isLobby() || room->isAbandoned()) {
|
if (!_room || _room->isLobby()) return;
|
||||||
return;
|
auto room = qobject_cast<Room *>(_room);
|
||||||
}
|
if (room->isAbandoned()) return;
|
||||||
|
|
||||||
auto state = player->getState();
|
auto state = player->getState();
|
||||||
room->doBroadcastNotify(room->getPlayers(), "NetStateChanged",
|
room->doBroadcastNotify(room->getPlayers(), "NetStateChanged",
|
||||||
QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString()));
|
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 {\
|
#define SET_DEFAULT_CONFIG(k, v) do {\
|
||||||
if (config.value(k).isUndefined()) { \
|
if (config.value(k).isUndefined()) { \
|
||||||
config[k] = (v); \
|
config[k] = (v); \
|
||||||
|
@ -705,28 +523,3 @@ void Server::refreshMd5() {
|
||||||
emit p->kicked();
|
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();
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,17 +3,14 @@
|
||||||
#ifndef _SERVER_H
|
#ifndef _SERVER_H
|
||||||
#define _SERVER_H
|
#define _SERVER_H
|
||||||
|
|
||||||
#include <openssl/rsa.h>
|
class AuthManager;
|
||||||
#include <openssl/pem.h>
|
|
||||||
|
|
||||||
#include <qjsonobject.h>
|
|
||||||
#include <qjsonvalue.h>
|
|
||||||
class ServerSocket;
|
class ServerSocket;
|
||||||
class ClientSocket;
|
class ClientSocket;
|
||||||
class ServerPlayer;
|
class ServerPlayer;
|
||||||
class RoomThread;
|
class RoomThread;
|
||||||
|
class Lobby;
|
||||||
|
|
||||||
#include "room.h"
|
#include "server/room.h"
|
||||||
|
|
||||||
class Server : public QObject {
|
class Server : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -29,7 +26,7 @@ public:
|
||||||
int timeout = 15, const QByteArray &settings = "{}");
|
int timeout = 15, const QByteArray &settings = "{}");
|
||||||
|
|
||||||
Room *findRoom(int id) const;
|
Room *findRoom(int id) const;
|
||||||
Room *lobby() const;
|
Lobby *lobby() const;
|
||||||
|
|
||||||
RoomThread *createThread();
|
RoomThread *createThread();
|
||||||
void removeThread(RoomThread *thread);
|
void removeThread(RoomThread *thread);
|
||||||
|
@ -37,6 +34,7 @@ public:
|
||||||
ServerPlayer *findPlayer(int id) const;
|
ServerPlayer *findPlayer(int id) const;
|
||||||
void addPlayer(ServerPlayer *player);
|
void addPlayer(ServerPlayer *player);
|
||||||
void removePlayer(int id);
|
void removePlayer(int id);
|
||||||
|
auto getPlayers() { return players; }
|
||||||
|
|
||||||
void updateRoomList(ServerPlayer *teller);
|
void updateRoomList(ServerPlayer *teller);
|
||||||
void updateOnlineInfo();
|
void updateOnlineInfo();
|
||||||
|
@ -44,6 +42,8 @@ public:
|
||||||
sqlite3 *getDatabase();
|
sqlite3 *getDatabase();
|
||||||
|
|
||||||
void broadcast(const QString &command, const QString &jsonData);
|
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;
|
bool isListening;
|
||||||
|
|
||||||
QJsonValue getConfig(const QString &command);
|
QJsonValue getConfig(const QString &command);
|
||||||
|
@ -64,7 +64,6 @@ signals:
|
||||||
public slots:
|
public slots:
|
||||||
void processNewConnection(ClientSocket *client);
|
void processNewConnection(ClientSocket *client);
|
||||||
void processRequest(const QByteArray &msg);
|
void processRequest(const QByteArray &msg);
|
||||||
void readPendingDatagrams();
|
|
||||||
|
|
||||||
void onRoomAbandoned();
|
void onRoomAbandoned();
|
||||||
void onUserDisconnected();
|
void onUserDisconnected();
|
||||||
|
@ -73,9 +72,8 @@ public slots:
|
||||||
private:
|
private:
|
||||||
friend class Shell;
|
friend class Shell;
|
||||||
ServerSocket *server;
|
ServerSocket *server;
|
||||||
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
|
|
||||||
|
|
||||||
Room *m_lobby;
|
Lobby *m_lobby;
|
||||||
QMap<int, Room *> rooms;
|
QMap<int, Room *> rooms;
|
||||||
QStack<Room *> idle_rooms;
|
QStack<Room *> idle_rooms;
|
||||||
QList<RoomThread *> threads;
|
QList<RoomThread *> threads;
|
||||||
|
@ -84,26 +82,13 @@ private:
|
||||||
QHash<int, ServerPlayer *> players;
|
QHash<int, ServerPlayer *> players;
|
||||||
QList<QString> temp_banlist;
|
QList<QString> temp_banlist;
|
||||||
|
|
||||||
RSA *rsa;
|
AuthManager *auth;
|
||||||
QString public_key;
|
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
QMutex transaction_mutex;
|
QMutex transaction_mutex;
|
||||||
QString md5;
|
QString md5;
|
||||||
|
|
||||||
static RSA *initServerRSA();
|
|
||||||
|
|
||||||
QJsonObject config;
|
QJsonObject config;
|
||||||
void readConfig();
|
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;
|
extern Server *ServerInstance;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "serverplayer.h"
|
#include "server/serverplayer.h"
|
||||||
#include "client_socket.h"
|
#include "network/client_socket.h"
|
||||||
#include "room.h"
|
#include "server/room.h"
|
||||||
#include "roomthread.h"
|
#include "server/roomthread.h"
|
||||||
#include "router.h"
|
#include "network/router.h"
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
|
|
||||||
ServerPlayer::ServerPlayer(Room *room) {
|
ServerPlayer::ServerPlayer(RoomBase *room) {
|
||||||
socket = nullptr;
|
socket = nullptr;
|
||||||
router = new Router(this, socket, Router::TYPE_SERVER);
|
router = new Router(this, socket, Router::TYPE_SERVER);
|
||||||
setState(Player::Online);
|
setState(Player::Online);
|
||||||
|
@ -61,9 +61,9 @@ void ServerPlayer::removeSocket() {
|
||||||
|
|
||||||
Server *ServerPlayer::getServer() const { return server; }
|
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) { ; }
|
void ServerPlayer::speak(const QString &message) { ; }
|
||||||
|
|
||||||
|
@ -112,6 +112,24 @@ void ServerPlayer::kick() {
|
||||||
setSocket(nullptr);
|
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 *>(room)->pushRequest(QString("%1,reconnect").arg(getId()));
|
||||||
|
} else {
|
||||||
|
// 懒得处理掉线玩家在大厅了!踢掉得了
|
||||||
|
doNotify("ErrorMsg", "Unknown Error");
|
||||||
|
emit kicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ServerPlayer::thinking() {
|
bool ServerPlayer::thinking() {
|
||||||
m_thinking_mutex.lock();
|
m_thinking_mutex.lock();
|
||||||
bool ret = m_thinking;
|
bool ret = m_thinking;
|
||||||
|
|
|
@ -3,17 +3,18 @@
|
||||||
#ifndef _SERVERPLAYER_H
|
#ifndef _SERVERPLAYER_H
|
||||||
#define _SERVERPLAYER_H
|
#define _SERVERPLAYER_H
|
||||||
|
|
||||||
#include "player.h"
|
#include "core/player.h"
|
||||||
|
|
||||||
class ClientSocket;
|
class ClientSocket;
|
||||||
class Router;
|
class Router;
|
||||||
class Server;
|
class Server;
|
||||||
class Room;
|
class Room;
|
||||||
|
class RoomBase;
|
||||||
|
|
||||||
class ServerPlayer : public Player {
|
class ServerPlayer : public Player {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ServerPlayer(Room *room);
|
explicit ServerPlayer(RoomBase *room);
|
||||||
~ServerPlayer();
|
~ServerPlayer();
|
||||||
|
|
||||||
void setSocket(ClientSocket *socket);
|
void setSocket(ClientSocket *socket);
|
||||||
|
@ -21,8 +22,8 @@ public:
|
||||||
ClientSocket *getSocket() const;
|
ClientSocket *getSocket() const;
|
||||||
|
|
||||||
Server *getServer() const;
|
Server *getServer() const;
|
||||||
Room *getRoom() const;
|
RoomBase *getRoom() const;
|
||||||
void setRoom(Room *room);
|
void setRoom(RoomBase *room);
|
||||||
|
|
||||||
void speak(const QString &message);
|
void speak(const QString &message);
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ public:
|
||||||
|
|
||||||
volatile bool alive; // For heartbeat
|
volatile bool alive; // For heartbeat
|
||||||
void kick();
|
void kick();
|
||||||
|
void reconnect(ClientSocket *socket);
|
||||||
|
|
||||||
bool busy() const { return m_busy; }
|
bool busy() const { return m_busy; }
|
||||||
void setBusy(bool busy) { m_busy = busy; }
|
void setBusy(bool busy) { m_busy = busy; }
|
||||||
|
@ -57,7 +59,7 @@ private:
|
||||||
ClientSocket *socket; // socket for communicating with client
|
ClientSocket *socket; // socket for communicating with client
|
||||||
Router *router;
|
Router *router;
|
||||||
Server *server;
|
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_busy; // (Lua专用) 是否有doRequest没处理完?见于神貂蝉这种一控多的
|
||||||
bool m_thinking; // 是否在烧条?
|
bool m_thinking; // 是否在烧条?
|
||||||
QMutex m_thinking_mutex; // 注意setBusy只在Lua使用,所以不需要锁。
|
QMutex m_thinking_mutex; // 注意setBusy只在Lua使用,所以不需要锁。
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||||
#include "shell.h"
|
#include "server/shell.h"
|
||||||
#include "packman.h"
|
#include "core/packman.h"
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
#include "serverplayer.h"
|
#include "server/serverplayer.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
#include <readline/history.h>
|
#include <readline/history.h>
|
||||||
#include <readline/readline.h>
|
#include <readline/readline.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
%module fk
|
%module fk
|
||||||
|
|
||||||
%{
|
%{
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
#include "serverplayer.h"
|
#include "server/serverplayer.h"
|
||||||
#include "clientplayer.h"
|
#include "client/clientplayer.h"
|
||||||
#include "room.h"
|
#include "server/room.h"
|
||||||
#include "roomthread.h"
|
#include "server/roomthread.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
class ClientPlayer *Self = nullptr;
|
class ClientPlayer *Self = nullptr;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
%module fk
|
%module fk
|
||||||
|
|
||||||
%{
|
%{
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
#include "serverplayer.h"
|
#include "server/serverplayer.h"
|
||||||
#include "clientplayer.h"
|
#include "client/clientplayer.h"
|
||||||
#include "room.h"
|
#include "server/room.h"
|
||||||
#include "roomthread.h"
|
#include "server/roomthread.h"
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
|
|
||||||
const char *FK_VER = FK_VERSION;
|
const char *FK_VER = FK_VERSION;
|
||||||
%}
|
%}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
||||||
%{
|
%{
|
||||||
#include <qmlbackend.h>
|
#include "ui/qmlbackend.h"
|
||||||
%}
|
%}
|
||||||
|
|
||||||
// Lua 5.4 特有的不能pushnumber, swig迟迟不更只好手动调教
|
// Lua 5.4 特有的不能pushnumber, swig迟迟不更只好手动调教
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// 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;
|
%nodefaultctor Room;
|
||||||
%nodefaultdtor Room;
|
%nodefaultdtor Room;
|
||||||
class Room : public QObject {
|
class Room : public QObject {
|
||||||
|
@ -14,11 +23,14 @@ public:
|
||||||
QList<ServerPlayer *> getObservers() const;
|
QList<ServerPlayer *> getObservers() const;
|
||||||
bool hasObserver(ServerPlayer *player) const;
|
bool hasObserver(ServerPlayer *player) const;
|
||||||
int getTimeout() const;
|
int getTimeout() const;
|
||||||
|
void delay(int ms);
|
||||||
void checkAbandoned();
|
void checkAbandoned();
|
||||||
|
|
||||||
void updateWinRate(int id, const QString &general, const QString &mode,
|
void updateWinRate(int id, const QString &general, const QString &mode,
|
||||||
int result, bool dead);
|
int result, bool dead);
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
void setRequestTimer(int ms);
|
||||||
|
void destroyRequestTimer();
|
||||||
};
|
};
|
||||||
|
|
||||||
%extend Room {
|
%extend Room {
|
||||||
|
@ -33,25 +45,26 @@ class RoomThread : public QThread {
|
||||||
public:
|
public:
|
||||||
Room *getRoom(int id);
|
Room *getRoom(int id);
|
||||||
|
|
||||||
QString fetchRequest();
|
// QString fetchRequest();
|
||||||
void clearRequest();
|
// void clearRequest();
|
||||||
bool hasRequest();
|
// bool hasRequest();
|
||||||
|
|
||||||
void trySleep(int ms);
|
// void trySleep(int ms);
|
||||||
bool isTerminated() const;
|
// bool isTerminated() const;
|
||||||
|
|
||||||
bool isConsoleStart() const;
|
bool isConsoleStart() const;
|
||||||
bool isOutdated();
|
bool isOutdated();
|
||||||
};
|
};
|
||||||
|
|
||||||
%{
|
%{
|
||||||
void RoomThread::run()
|
#include "server/scheduler.h"
|
||||||
|
void Scheduler::tellThreadToLua()
|
||||||
{
|
{
|
||||||
lua_getglobal(L, "debug");
|
lua_getglobal(L, "debug");
|
||||||
lua_getfield(L, -1, "traceback");
|
lua_getfield(L, -1, "traceback");
|
||||||
lua_replace(L, -2);
|
lua_replace(L, -2);
|
||||||
lua_getglobal(L, "InitScheduler");
|
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);
|
int error = lua_pcall(L, 1, 0, -2);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "qmlbackend.h"
|
#include "ui/qmlbackend.h"
|
||||||
#include <lua.h>
|
|
||||||
#include <qjsondocument.h>
|
|
||||||
#include <qvariant.h>
|
|
||||||
|
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
#include <qaudiooutput.h>
|
#include <QAudioOutput>
|
||||||
#include <qmediaplayer.h>
|
|
||||||
#include <qrandom.h>
|
|
||||||
#include <QNetworkDatagram>
|
#include <QNetworkDatagram>
|
||||||
#include <QDnsLookup>
|
#include <QDnsLookup>
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
#include "mod.h"
|
#include <QMessageBox>
|
||||||
|
// #include "mod.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include "server.h"
|
#include "server/server.h"
|
||||||
#include "client.h"
|
#include "client/client.h"
|
||||||
#include "util.h"
|
#include "core/util.h"
|
||||||
#include "replayer.h"
|
#include "client/replayer.h"
|
||||||
|
|
||||||
QmlBackend *Backend = nullptr;
|
QmlBackend *Backend = nullptr;
|
||||||
|
|
||||||
|
@ -35,6 +31,7 @@ QmlBackend::QmlBackend(QObject *parent) : QObject(parent) {
|
||||||
udpSocket->bind(0);
|
udpSocket->bind(0);
|
||||||
connect(udpSocket, &QUdpSocket::readyRead,
|
connect(udpSocket, &QUdpSocket::readyRead,
|
||||||
this, &QmlBackend::readPendingDatagrams);
|
this, &QmlBackend::readPendingDatagrams);
|
||||||
|
connect(this, &QmlBackend::dialog, this, &QmlBackend::showDialog);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +461,7 @@ void QmlBackend::installAESKey() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlBackend::createModBackend() {
|
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) {
|
void QmlBackend::removeRecord(const QString &fname) {
|
||||||
QFile::remove("recording/" + fname);
|
QFile::remove("recording/" + fname);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,9 @@ public:
|
||||||
Q_INVOKABLE void detectServer();
|
Q_INVOKABLE void detectServer();
|
||||||
Q_INVOKABLE void getServerInfo(const QString &addr);
|
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; }
|
qreal volume() const { return m_volume; }
|
||||||
void setVolume(qreal v) { m_volume = v; }
|
void setVolume(qreal v) { m_volume = v; }
|
||||||
|
|
||||||
|
@ -77,6 +80,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void notifyUI(const QString &command, const QVariant &data);
|
void notifyUI(const QString &command, const QVariant &data);
|
||||||
|
void dialog(const QString &type, const QString &text, const QString &orig = QString());
|
||||||
void volumeChanged(qreal);
|
void volumeChanged(qreal);
|
||||||
void replayerToggle();
|
void replayerToggle();
|
||||||
void replayerSpeedUp();
|
void replayerSpeedUp();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user