mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-16 11:42:45 +08:00
Merge branch 'master' into dev-bugfix
This commit is contained in:
commit
1bfcd0a0cb
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 /usr/share/ca-certificates/mozilla/* certs/
|
||||
echo ${FKVER%)} > fk_ver
|
||||
./genfkver.sh
|
||||
|
||||
- name: Configure CMake Project
|
||||
working-directory: ${{github.workspace}}
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,6 +15,7 @@
|
|||
/*.kdev4
|
||||
/.cache/
|
||||
/tags
|
||||
/.luarc.json
|
||||
|
||||
# file produced by game
|
||||
/FreeKill
|
||||
|
|
|
@ -39,11 +39,6 @@ include_directories(include/lua)
|
|||
include_directories(include)
|
||||
include_directories(include/libgit2)
|
||||
include_directories(src)
|
||||
include_directories(src/client)
|
||||
include_directories(src/core)
|
||||
include_directories(src/network)
|
||||
include_directories(src/server)
|
||||
include_directories(src/ui)
|
||||
|
||||
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
|
||||
if (DEFINED FK_SERVER_ONLY)
|
||||
|
@ -78,6 +73,7 @@ add_custom_command(
|
|||
POST_BUILD
|
||||
COMMENT "Generating version file fk_ver"
|
||||
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/genfkver.sh
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
|
|
@ -40,8 +40,13 @@ Flickable {
|
|||
|
||||
extra_data.generals.forEach((g) => {
|
||||
const data = lcall("GetGeneralDetail", g);
|
||||
skillDesc.append(luatr(data.kingdom) + " " + luatr(g) + " " + data.hp +
|
||||
"/" + data.maxHp);
|
||||
skillDesc.append(luatr(data.kingdom) + " " + luatr(g) + " " + (data.hp === data.maxHp
|
||||
? ((g.startsWith('hs__') || g.startsWith('ld__') || g.includes('heg__'))
|
||||
? ((data.mainMaxHp != 0 || data.deputyMaxHp != 0)
|
||||
? ((data.hp + data.mainMaxHp) / 2 + '/' + (data.hp + data.deputyMaxHp) / 2)
|
||||
: data.hp / 2)
|
||||
: data.hp)
|
||||
: data.hp + "/" + data.maxHp));
|
||||
if (data.companions.length > 0){
|
||||
let ret = '';
|
||||
ret +="<font color=\"slategrey\"><b>" + luatr("Companions") + "</b>: ";
|
||||
|
|
|
@ -50,6 +50,16 @@ Item {
|
|||
if (idx < extra_data.cards.count) {
|
||||
extra_data.cards.set(idx, { name: modelData });
|
||||
}
|
||||
|
||||
idx = 0;
|
||||
extra_data.choices.forEach( s => {
|
||||
if (s === gname) {
|
||||
extra_data.choices[idx] = modelData;
|
||||
return;
|
||||
}
|
||||
idx++;
|
||||
});
|
||||
|
||||
root.finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ QtObject {
|
|||
property string password: ""
|
||||
property string cipherText
|
||||
property string aeskey
|
||||
// string => { roomId => config }
|
||||
property var roomConfigCache: ({})
|
||||
|
||||
// Client data
|
||||
property string serverMotd: ""
|
||||
|
|
|
@ -184,6 +184,12 @@ Item {
|
|||
return ret;
|
||||
}
|
||||
delegate: Text {
|
||||
width: parent.width / 2
|
||||
wrapMode: Text.WordWrap
|
||||
fontSizeMode: Text.HorizontalFit
|
||||
minimumPixelSize: 14
|
||||
elide: Text.ElideRight
|
||||
height: 24
|
||||
text: luatr(modelData)
|
||||
font.pixelSize: 16
|
||||
}
|
||||
|
|
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["UpdateBusyText"] = (jsonData) => {
|
||||
|
|
|
@ -289,18 +289,29 @@ Item {
|
|||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: {
|
||||
if (gender === "male") {
|
||||
if (audioType === "male") {
|
||||
return luatr("Male Audio");
|
||||
} else {
|
||||
} else if (audioType === "female") {
|
||||
return luatr("Female Audio");
|
||||
} else if (audioType === "equip_effect") {
|
||||
return luatr("Equip Effect Audio");
|
||||
} {
|
||||
return luatr("Equip Use Audio");
|
||||
}
|
||||
}
|
||||
font.pixelSize: 14
|
||||
}
|
||||
onClicked: {
|
||||
const data = lcall("GetCardData", cardDetail.cid);
|
||||
Backend.playSound("./packages/" + extension + "/audio/card/"
|
||||
+ gender + "/" + data.name);
|
||||
if (audioType === "male" || audioType === "female") {
|
||||
Backend.playSound("./packages/" + extension + "/audio/card/"
|
||||
+ audioType + "/" + data.name);
|
||||
} else if (audioType === "equip_effect") {
|
||||
Backend.playSound("./packages/" + extension + "/audio/card/"
|
||||
+ "/" + data.name);
|
||||
} else {
|
||||
Backend.playSound("./audio/card/common/" + extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -317,27 +328,41 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
function loadAudio(cardName, type, extension, orig_extension) {
|
||||
const prefix = AppPath + "/packages/";
|
||||
const suffix = cardName + ".mp3";
|
||||
let midfix = type + "/";
|
||||
if (type === "equip_effect") {
|
||||
midfix = "";
|
||||
}
|
||||
let fname = prefix + extension + "/audio/card/" + midfix + suffix;
|
||||
if (Backend.exists(fname)) {
|
||||
audioRow.append( { audioType: type, extension: extension } );
|
||||
} else {
|
||||
fname = prefix + orig_extension + "/audio/card/" + midfix + suffix;
|
||||
if (Backend.exists(fname)) {
|
||||
audioRow.append( { audioType: type, extension: orig_extension} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addCardAudio(card) {
|
||||
const extension = card.extension;
|
||||
const orig_extension = lcall("GetCardExtensionByName", card.name);
|
||||
const prefix = AppPath + "/packages/";
|
||||
const suffix = card.name + ".mp3";
|
||||
let fname = prefix + extension + "/audio/card/male/" + suffix;
|
||||
if (Backend.exists(fname)) {
|
||||
audioRow.append( { gender: "male", extension: extension } );
|
||||
} else {
|
||||
fname = prefix + orig_extension + "/audio/card/male/" + suffix;
|
||||
if (Backend.exists(fname)) {
|
||||
audioRow.append( {gender: "male", extension: orig_extension} );
|
||||
}
|
||||
}
|
||||
fname = prefix + extension + "/audio/card/female/" + suffix;
|
||||
if (Backend.exists(fname)) {
|
||||
audioRow.append( { gender: "female", extension: extension } );
|
||||
}else {
|
||||
fname = prefix + orig_extension + "/audio/card/female/" + suffix;
|
||||
if (Backend.exists(fname)) {
|
||||
audioRow.append( { gender: "female", extension: orig_extension } );
|
||||
loadAudio(card.name, "male", extension, orig_extension);
|
||||
loadAudio(card.name, "female", extension, orig_extension);
|
||||
if (audioRow.count === 0 && card.type === 3) {
|
||||
loadAudio(card.name, "equip_effect", extension, orig_extension);
|
||||
if (audioRow.count === 0) {
|
||||
let subType = "";
|
||||
if (card.subtype === "defensive_horse" || card.subtype === "offensive_horse") {
|
||||
subType = "horse";
|
||||
} else if (card.subtype === "weapon") {
|
||||
subType = "weapon";
|
||||
} else {
|
||||
subType = "armor";
|
||||
}
|
||||
audioRow.append( { audioType: "equip_use", extension: subType } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -503,6 +503,8 @@ Item {
|
|||
|
||||
Item {
|
||||
id: generalInfo
|
||||
x: 5
|
||||
y: 10
|
||||
width: 150
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
@ -516,6 +518,7 @@ Item {
|
|||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: TextEdit.RichText
|
||||
font.pixelSize: 16
|
||||
function trans(str) {
|
||||
|
|
|
@ -183,17 +183,6 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Temp
|
||||
Button {
|
||||
text: qsTr("Making Mod")
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
visible: Debugging
|
||||
onClicked: {
|
||||
mainStack.push(modMaker);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadComplete() {
|
||||
toast.show(qsTr("updated packages for md5"));
|
||||
}
|
||||
|
|
|
@ -11,112 +11,151 @@ import "Logic.js" as Logic
|
|||
Item {
|
||||
id: root
|
||||
property alias roomModel: roomModel
|
||||
property var roomInfoCache: ({})
|
||||
|
||||
property string password
|
||||
|
||||
Rectangle {
|
||||
width: parent.width / 2 - roomListLayout.width / 2 - 50
|
||||
height: parent.height * 0.7
|
||||
anchors.top: exitButton.bottom
|
||||
anchors.bottom: createRoomButton.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
color: "#88EEEEEE"
|
||||
radius: 6
|
||||
Component {
|
||||
id: roomDelegate
|
||||
|
||||
Flickable {
|
||||
id: flickableContainer
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
width: parent.width - 10
|
||||
height: parent.height - 10
|
||||
contentHeight: bulletin_info.height
|
||||
clip: true
|
||||
Rectangle {
|
||||
radius: 8
|
||||
height: 124 - 8
|
||||
width: 124 - 8
|
||||
color: outdated ? "#E2E2E2" : "lightgreen"
|
||||
|
||||
Text {
|
||||
id: bulletin_info
|
||||
width: parent.width
|
||||
wrapMode: TextEdit.WordWrap
|
||||
textFormat: Text.MarkdownText
|
||||
text: config.serverMotd + "\n___\n" + luatr('Bulletin Info')
|
||||
onLinkActivated: Qt.openUrlExternally(link);
|
||||
id: roomNameText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
width: parent.width - 16
|
||||
height: contentHeight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WrapAnywhere
|
||||
textFormat: Text.PlainText
|
||||
text: roomName
|
||||
// color: outdated ? "gray" : "black"
|
||||
font.pixelSize: 16
|
||||
// elide: Label.ElideRight
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 8
|
||||
}
|
||||
|
||||
Text {
|
||||
id: roomIdText
|
||||
text: luatr(gameMode) + ' #' + roomId
|
||||
anchors.top: roomNameText.bottom
|
||||
anchors.left: roomNameText.left
|
||||
}
|
||||
|
||||
Image {
|
||||
source: AppPath + "/image/button/skill/locked.png"
|
||||
visible: hasPassword
|
||||
scale: 0.8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: -4
|
||||
}
|
||||
|
||||
Text {
|
||||
color: (playerNum == capacity) ? "red" : "black"
|
||||
text: playerNum + "/" + capacity
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
gesturePolicy: TapHandler.WithinBounds
|
||||
enabled: !opTimer.running && !outdated
|
||||
|
||||
onTapped: {
|
||||
lobby_dialog.sourceComponent = roomDetailDialog;
|
||||
lobby_dialog.item.roomData = {
|
||||
roomId, roomName, gameMode, playerNum, capacity,
|
||||
hasPassword, outdated,
|
||||
};
|
||||
lobby_dialog.item.roomConfig = config.roomConfigCache?.[config.serverAddr]?.[roomId]
|
||||
lobby_drawer.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: roomDelegate
|
||||
id: roomDetailDialog
|
||||
ColumnLayout {
|
||||
property var roomData: ({
|
||||
roomName: "",
|
||||
hasPassword: true,
|
||||
})
|
||||
property var roomConfig: undefined
|
||||
signal finished()
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
|
||||
Item {
|
||||
height: 48
|
||||
width: roomList.width
|
||||
Text {
|
||||
text: roomData.roomName
|
||||
font.pixelSize: 18
|
||||
}
|
||||
|
||||
Text {
|
||||
font.pixelSize: 18
|
||||
text: {
|
||||
let ret = luatr(roomData.gameMode);
|
||||
ret += (' #' + roomData.roomId);
|
||||
ret += (' ' + roomData.playerNum + '/' + roomData.capacity);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
// Dummy
|
||||
Text {
|
||||
text: "在未来的版本中这一块区域将增加更多实用的功能,<br>"+
|
||||
"例如直接查看房间的各种配置信息<br>"+
|
||||
"以及更多与禁将有关的实用功能!"+
|
||||
"<font color='gray'>注:绿色按钮为试做型UI 后面优化</font>"
|
||||
font.pixelSize: 18
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 16
|
||||
Layout.fillWidth: true
|
||||
Text {
|
||||
text: roomId
|
||||
color: "grey"
|
||||
visible: roomData.hasPassword
|
||||
text: luatr("Please input room's password")
|
||||
}
|
||||
|
||||
Text {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
TextField {
|
||||
id: passwordEdit
|
||||
visible: roomData.hasPassword
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
let ret = roomName;
|
||||
if (outdated) {
|
||||
ret = '<font color="grey"><del>' + ret + '</del></font>';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
font.pixelSize: 20
|
||||
elide: Label.ElideRight
|
||||
onTextChanged: root.password = text;
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 16
|
||||
Image {
|
||||
source: AppPath + "/image/button/skill/locked.png"
|
||||
visible: hasPassword
|
||||
anchors.centerIn: parent
|
||||
scale: 0.8
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: luatr(gameMode)
|
||||
}
|
||||
|
||||
Text {
|
||||
color: (playerNum == capacity) ? "red" : "black"
|
||||
text: playerNum + "/" + capacity
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
visible: !roomData.hasPassword
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
text: (playerNum < capacity) ? luatr("Enter") :
|
||||
luatr("Observe")
|
||||
|
||||
enabled: !opTimer.running && !outdated
|
||||
|
||||
// text: "OK"
|
||||
text: (roomData.playerNum < roomData.capacity) ? luatr("Enter") : luatr("Observe")
|
||||
onClicked: {
|
||||
opTimer.start();
|
||||
if (hasPassword) {
|
||||
lobby_dialog.sourceComponent = enterPassword;
|
||||
lobby_dialog.item.roomId = roomId;
|
||||
lobby_dialog.item.playerNum = playerNum;
|
||||
lobby_dialog.item.capacity = capacity;
|
||||
lobby_drawer.open();
|
||||
} else {
|
||||
enterRoom(roomId, playerNum, capacity, "");
|
||||
}
|
||||
enterRoom(roomData.roomId, roomData.playerNum, roomData.capacity,
|
||||
roomData.hasPassword ? root.password : "");
|
||||
lobby_dialog.item.finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
passwordEdit.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,8 +163,7 @@ Item {
|
|||
id: roomModel
|
||||
}
|
||||
|
||||
PersonalSettings {
|
||||
}
|
||||
PersonalSettings {}
|
||||
|
||||
Timer {
|
||||
id: opTimer
|
||||
|
@ -134,69 +172,132 @@ Item {
|
|||
|
||||
ColumnLayout {
|
||||
id: roomListLayout
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: root.width * 0.48
|
||||
height: root.height - 80
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: luatr("Refresh Room List")
|
||||
enabled: !opTimer.running
|
||||
onClicked: {
|
||||
opTimer.start();
|
||||
ClientInstance.notifyServer("RefreshRoomList", "");
|
||||
height: root.height - 72
|
||||
y: 16
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.width * 0.03 + root.width * 0.94 * 0.8 % 128 / 2
|
||||
width: {
|
||||
let ret = root.width * 0.94 * 0.8;
|
||||
ret -= ret % 128;
|
||||
return ret;
|
||||
}
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Item { Layout.fillWidth: true }
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: luatr("Refresh Room List").arg(roomModel.count)
|
||||
enabled: !opTimer.running
|
||||
onClicked: {
|
||||
opTimer.start();
|
||||
ClientInstance.notifyServer("RefreshRoomList", "");
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: luatr("Create Room")
|
||||
onClicked: {
|
||||
lobby_dialog.sourceComponent =
|
||||
Qt.createComponent("../LobbyElement/CreateRoom.qml");
|
||||
lobby_drawer.open();
|
||||
config.observing = false;
|
||||
config.replaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
GridView {
|
||||
id: roomList
|
||||
cellWidth: 128
|
||||
cellHeight: 128
|
||||
Layout.fillHeight: true
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
color: "#88EEEEEE"
|
||||
radius: 16
|
||||
Text {
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: luatr("Room List").arg(roomModel.count)
|
||||
}
|
||||
ListView {
|
||||
id: roomList
|
||||
height: parent.height * 0.9
|
||||
width: parent.width * 0.95
|
||||
contentHeight: roomDelegate.height * count
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
anchors.centerIn: parent
|
||||
delegate: roomDelegate
|
||||
clip: true
|
||||
model: roomModel
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
delegate: roomDelegate
|
||||
clip: true
|
||||
model: roomModel
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: createRoomButton
|
||||
anchors.bottom: buttonRow.top
|
||||
Rectangle {
|
||||
id: serverInfoLayout
|
||||
height: root.height - 112
|
||||
y: 56
|
||||
width: root.width * 0.94 * 0.2
|
||||
anchors.right: parent.right
|
||||
width: 120
|
||||
display: AbstractButton.TextUnderIcon
|
||||
icon.name: "media-playback-start"
|
||||
text: luatr("Create Room")
|
||||
onClicked: {
|
||||
lobby_dialog.sourceComponent =
|
||||
Qt.createComponent("../LobbyElement/CreateRoom.qml");
|
||||
lobby_drawer.open();
|
||||
config.observing = false;
|
||||
config.replaying = false;
|
||||
anchors.rightMargin: root.width * 0.03
|
||||
// anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "#88EEEEEE"
|
||||
property bool chatShown: true
|
||||
|
||||
Flickable {
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
width: parent.width - 10
|
||||
height: parent.height - 10 - (parent.chatShown ? 200 : 0)
|
||||
contentHeight: bulletin_info.height
|
||||
clip: true
|
||||
|
||||
Text {
|
||||
id: bulletin_info
|
||||
width: parent.width
|
||||
wrapMode: TextEdit.WordWrap
|
||||
textFormat: Text.MarkdownText
|
||||
text: config.serverMotd + "\n\n___\n\n" + luatr('Bulletin Info')
|
||||
onLinkActivated: Qt.openUrlExternally(link);
|
||||
}
|
||||
}
|
||||
|
||||
MetroButton {
|
||||
text: "🗨️" + (parent.chatShown ? "➖" : "➕")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: lobbyChat.top
|
||||
onClicked: {
|
||||
parent.chatShown = !parent.chatShown
|
||||
}
|
||||
}
|
||||
|
||||
ChatBox {
|
||||
id: lobbyChat
|
||||
width: parent.width
|
||||
height: parent.chatShown ? 200 : 0
|
||||
Behavior on height { NumberAnimation { duration: 200 } }
|
||||
anchors.bottom: parent.bottom
|
||||
isLobby: true
|
||||
color: "#88EEEEEE"
|
||||
clip: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: buttonRow
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: childrenRect.width + 48
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.8; color: "white" }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
Text {
|
||||
x: 16; y: 4
|
||||
font.pixelSize: 16
|
||||
text: luatr("$OnlineInfo")
|
||||
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
|
||||
+ "Powered by FreeKill " + FkVersion
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
Button {
|
||||
text: luatr("Generals Overview")
|
||||
onClicked: {
|
||||
|
@ -276,39 +377,6 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: enterPassword
|
||||
ColumnLayout {
|
||||
property int roomId
|
||||
property int playerNum
|
||||
property int capacity
|
||||
signal finished()
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
|
||||
Text {
|
||||
text: luatr("Please input room's password")
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: passwordEdit
|
||||
onTextChanged: root.password = text;
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "OK"
|
||||
onClicked: {
|
||||
enterRoom(roomId, playerNum, capacity, root.password);
|
||||
parent.finished();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
passwordEdit.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enterRoom(roomId, playerNum, capacity, pw) {
|
||||
config.replaying = false;
|
||||
if (playerNum < capacity) {
|
||||
|
@ -333,40 +401,15 @@ Item {
|
|||
property int lobbyPlayerNum: 0
|
||||
property int serverPlayerNum: 0
|
||||
|
||||
/*
|
||||
function updateOnlineInfo() {
|
||||
}
|
||||
|
||||
onLobbyPlayerNumChanged: updateOnlineInfo();
|
||||
onServerPlayerNumChanged: updateOnlineInfo();
|
||||
|
||||
Rectangle {
|
||||
id: info
|
||||
color: "#88EEEEEE"
|
||||
width: root.width * 0.23 // childrenRect.width + 8
|
||||
height: childrenRect.height + 4
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
radius: 4
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
x: 4; y: 2
|
||||
font.pixelSize: 16
|
||||
text: luatr("$OnlineInfo")
|
||||
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
|
||||
+ "Powered by FreeKill " + FkVersion
|
||||
}
|
||||
}
|
||||
|
||||
ChatBox {
|
||||
id: lobbyChat
|
||||
anchors.bottom: info.top
|
||||
width: info.width
|
||||
height: root.height * 0.6
|
||||
isLobby: true
|
||||
color: "#88EEEEEE"
|
||||
radius: 4
|
||||
}
|
||||
/*
|
||||
*/
|
||||
|
||||
Danmaku {
|
||||
id: danmaku
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
callbacks["UpdateAvatar"] = (jsonData) => {
|
||||
mainWindow.busy = false;
|
||||
Self.avatar = jsonData;
|
||||
toast.show("Update avatar done.");
|
||||
toast.show(luatr("Update avatar done."));
|
||||
}
|
||||
|
||||
callbacks["UpdatePassword"] = (jsonData) => {
|
||||
mainWindow.busy = false;
|
||||
if (jsonData === "1")
|
||||
toast.show("Update password done.");
|
||||
toast.show(luatr("Update password done."));
|
||||
else
|
||||
toast.show("Old password wrong!", 5000);
|
||||
toast.show(luatr("Old password wrong!"), 5000);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ Item {
|
|||
id: menu
|
||||
y: bar.height
|
||||
MenuItem {
|
||||
text: qsTr("Replay from file")
|
||||
text: luatr("Replay from File")
|
||||
onTriggered: {
|
||||
fdialog.open();
|
||||
}
|
||||
|
|
|
@ -810,7 +810,7 @@ Item {
|
|||
skillInteraction.item.choices = data.choices;
|
||||
skillInteraction.item.detailed = data.detailed;
|
||||
skillInteraction.item.all_choices = data.all_choices;
|
||||
// skillInteraction.item.clicked();
|
||||
skillInteraction.item.clicked();
|
||||
break;
|
||||
case "spin":
|
||||
skillInteraction.sourceComponent =
|
||||
|
@ -818,12 +818,14 @@ Item {
|
|||
skillInteraction.item.skill = skill_name;
|
||||
skillInteraction.item.from = data.from;
|
||||
skillInteraction.item.to = data.to;
|
||||
skillInteraction.item.clicked();
|
||||
break;
|
||||
case "custom":
|
||||
skillInteraction.sourceComponent =
|
||||
Qt.createComponent(AppPath + "/" + data.qml_path + ".qml");
|
||||
skillInteraction.item.skill = skill_name;
|
||||
skillInteraction.item.extra_data = data;
|
||||
skillInteraction.item.clicked();
|
||||
break;
|
||||
default:
|
||||
skillInteraction.sourceComponent = undefined;
|
||||
|
@ -837,6 +839,8 @@ Item {
|
|||
cancelButton.enabled = true;
|
||||
} else {
|
||||
skillInteraction.sourceComponent = undefined;
|
||||
if (roomScene.popupBox.item)
|
||||
roomScene.popupBox.item.close();
|
||||
Logic.doCancelButton();
|
||||
}
|
||||
}
|
||||
|
@ -1060,8 +1064,17 @@ Item {
|
|||
|
||||
Shortcut {
|
||||
sequence: "Space"
|
||||
enabled: cancelButton.enabled
|
||||
onActivated: Logic.doCancelButton();
|
||||
enabled: cancelButton.enabled || endPhaseButton.visible;
|
||||
onActivated: if (cancelButton.enabled) {
|
||||
Logic.doCancelButton();
|
||||
} else {
|
||||
Logic.replyToServer("");
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Escape"
|
||||
onActivated: menuContainer.open();
|
||||
}
|
||||
|
||||
function getCurrentCardUseMethod() {
|
||||
|
|
|
@ -314,6 +314,10 @@ function resortHandcards() {
|
|||
["treasure"]: Card.SubtypeTreasure,
|
||||
}
|
||||
|
||||
const hand = dashboard.handcardArea.cards.map(c => {
|
||||
return c.cid;
|
||||
})
|
||||
|
||||
dashboard.handcardArea.cards.sort((prev, next) => {
|
||||
if (prev.footnote === next.footnote) {
|
||||
if (prev.type === next.type) {
|
||||
|
@ -341,6 +345,34 @@ function resortHandcards() {
|
|||
}
|
||||
});
|
||||
|
||||
let i = 0;
|
||||
let resort = true;
|
||||
dashboard.handcardArea.cards.forEach(c => {
|
||||
if (hand[i] !== c.cid) {
|
||||
resort = false;
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
})
|
||||
|
||||
if (resort) {
|
||||
dashboard.handcardArea.cards.sort((prev, next) => {
|
||||
if (prev.footnote === next.footnote) {
|
||||
if (prev.number === next.number) { // 按点数排
|
||||
if (prev.suit === next.suit) {
|
||||
return prev.cid - next.cid;
|
||||
} else {
|
||||
return prev.suit - next.suit;
|
||||
}
|
||||
} else {
|
||||
return prev.number - next.number;
|
||||
}
|
||||
} else {
|
||||
return prev.footnote > next.footnote ? 1 : -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dashboard.handcardArea.updateCardPosition(true);
|
||||
}
|
||||
|
||||
|
@ -488,15 +520,29 @@ function doIndicate(from, tos) {
|
|||
line.running = true;
|
||||
}
|
||||
|
||||
function getPlayerStr(playerid) {
|
||||
const photo = getPhoto(playerid);
|
||||
if (photo.general === "anjiang" && (photo.deputyGeneral === "anjiang" || !p.deputyGeneral)) {
|
||||
return luatr("seat#" + photo.seatNumber);
|
||||
}
|
||||
|
||||
let ret = photo.general;
|
||||
ret = luatr(ret);
|
||||
if (photo.deputyGeneral && photo.deputyGeneral !== "") {
|
||||
ret = ret + "/" + luatr(photo.deputyGeneral);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function processPrompt(prompt) {
|
||||
const data = prompt.split(":");
|
||||
let raw = luatr(data[0]);
|
||||
const src = parseInt(data[1]);
|
||||
const dest = parseInt(data[2]);
|
||||
if (raw.match("%src"))
|
||||
raw = raw.replace(/%src/g, luatr(getPhoto(src).general));
|
||||
raw = raw.replace(/%src/g, getPlayerStr(src));
|
||||
if (raw.match("%dest"))
|
||||
raw = raw.replace(/%dest/g, luatr(getPhoto(dest).general));
|
||||
raw = raw.replace(/%dest/g, getPlayerStr(dest));
|
||||
if (raw.match("%arg2"))
|
||||
raw = raw.replace(/%arg2/g, luatr(data[4]));
|
||||
if (raw.match("%arg"))
|
||||
|
@ -705,6 +751,16 @@ function updateSelectedTargets(playerid, selected) {
|
|||
})
|
||||
selected_targets = remain_targets;
|
||||
}
|
||||
|
||||
roomScene.resetPrompt(); // update prompt due to selected_targets
|
||||
const prompt = lcall("ActiveSkillPrompt",
|
||||
dashboard.pending_skill !== "" ? dashboard.pending_skill: lcall("GetCardSkill", card),
|
||||
dashboard.pending_skill !== "" ? dashboard.pendings : [card],
|
||||
selected_targets);
|
||||
if (prompt !== "") {
|
||||
roomScene.setPrompt(Util.processPrompt(prompt));
|
||||
}
|
||||
|
||||
all_photos.forEach(photo => {
|
||||
if (photo.selected) return;
|
||||
const id = photo.playerid;
|
||||
|
|
|
@ -112,7 +112,7 @@ GraphicsBox {
|
|||
visible: !convertDisabled
|
||||
text: luatr("Same General Convert")
|
||||
onClicked: {
|
||||
roomScene.startCheat("SameConvert", { cards: generalList });
|
||||
roomScene.startCheat("SameConvert", { cards: generalList, choices: choices });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,6 +263,7 @@ GraphicsBox {
|
|||
item.selectable = hegemony ? isHegPair(selectedItem[0], item)
|
||||
: true;
|
||||
if (hegemony) {
|
||||
item.inPosition = 0;
|
||||
if (selectedItem[0]) {
|
||||
if (selectedItem[1]) {
|
||||
if (selectedItem[0] === item) {
|
||||
|
@ -299,6 +300,23 @@ GraphicsBox {
|
|||
}
|
||||
}
|
||||
|
||||
if (hegemony) {
|
||||
if (selectedItem[0]) {
|
||||
if (selectedItem[0].mainMaxHp < 0) {
|
||||
selectedItem[0].inPosition = 1;
|
||||
} else if (selectedItem[0].deputyMaxHp < 0) {
|
||||
selectedItem[0].inPosition = -1;
|
||||
}
|
||||
if (selectedItem[1]) {
|
||||
if (selectedItem[1].mainMaxHp < 0) {
|
||||
selectedItem[1].inPosition = -1;
|
||||
} else if (selectedItem[1].deputyMaxHp < 0) {
|
||||
selectedItem[1].inPosition = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < generalList.count; i++) {
|
||||
if (lcall("GetSameGenerals", generalList.get(i).name).length > 0) {
|
||||
convertBtn.enabled = true;
|
||||
|
|
|
@ -22,6 +22,9 @@ CardItem {
|
|||
property int hp
|
||||
property int maxHp
|
||||
property int shieldNum
|
||||
property int mainMaxHp
|
||||
property int deputyMaxHp
|
||||
property int inPosition: 0
|
||||
property string pkgName: ""
|
||||
property bool detailed: true
|
||||
property alias hasCompanions: companions.visible
|
||||
|
@ -119,12 +122,15 @@ CardItem {
|
|||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
Image {
|
||||
opacity: ((mainMaxHp < 0 || deputyMaxHp < 0) && (index * 2 + 1 === hp) && inPosition !== -1)
|
||||
? (inPosition === 0 ? 0.5 : 0) :1
|
||||
height: 12; fillMode: Image.PreserveAspectFit
|
||||
source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama-l"
|
||||
}
|
||||
Image {
|
||||
x: 4.4
|
||||
opacity: (index + 1) * 2 <= hp ? 1 : 0
|
||||
opacity: (index + 1) * 2 <= hp ? (((mainMaxHp < 0 || deputyMaxHp < 0) && inPosition !== -1 && ((index + 1) * 2 === hp))
|
||||
? (inPosition === 0 ? 0.5 : 0) : 1) : 0
|
||||
height: 12; fillMode: Image.PreserveAspectFit
|
||||
source: {
|
||||
const k = subkingdom ? subkingdom : kingdom;
|
||||
|
@ -227,6 +233,8 @@ CardItem {
|
|||
hp = data.hp;
|
||||
maxHp = data.maxHp;
|
||||
shieldNum = data.shield;
|
||||
mainMaxHp = data.mainMaxHpAdjustedValue;
|
||||
deputyMaxHp = data.deputyMaxHpAdjustedValue;
|
||||
|
||||
const splited = name.split("__");
|
||||
if (splited.length > 1) {
|
||||
|
|
|
@ -234,7 +234,26 @@ Item {
|
|||
color: "white"
|
||||
width: 24
|
||||
wrapMode: Text.WrapAnywhere
|
||||
text: luatr(deputyGeneral)
|
||||
text: ""
|
||||
style: Text.Outline
|
||||
}
|
||||
|
||||
Text {
|
||||
id: longDeputyGeneralName
|
||||
anchors.left: generalImage.right
|
||||
anchors.leftMargin: -14
|
||||
y: 23
|
||||
font.family: fontLibian.name
|
||||
font.pixelSize: 22
|
||||
rotation: 90
|
||||
transformOrigin: Item.BottomLeft
|
||||
opacity: 0.9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
lineHeight: 18
|
||||
lineHeightMode: Text.FixedHeight
|
||||
color: "white"
|
||||
width: 24
|
||||
text: ""
|
||||
style: Text.Outline
|
||||
}
|
||||
}
|
||||
|
@ -740,6 +759,18 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
onDeputyGeneralChanged: {
|
||||
if (!roomScene.isStarted) return;
|
||||
const text = luatr(deputyGeneral);
|
||||
if (text.length > 6) {
|
||||
deputyGeneralName.text = "";
|
||||
longDeputyGeneralName.text = text;
|
||||
} else {
|
||||
deputyGeneralName.text = text;
|
||||
longDeputyGeneralName.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
function chat(msg) {
|
||||
chat.text = msg;
|
||||
chat.visible = true;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Controls
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -86,7 +87,40 @@ Item {
|
|||
}
|
||||
|
||||
TapHandler {
|
||||
enabled: root.type !== "notactive" && root.enabled
|
||||
onTapped: parent.pressed = !parent.pressed;
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
|
||||
onTapped: (p, btn) => {
|
||||
if ((btn === Qt.LeftButton || btn === Qt.NoButton) && root.type !== "notactive" && root.enabled) {
|
||||
parent.pressed = !parent.pressed;
|
||||
} else if (btn === Qt.RightButton) {
|
||||
skillDetail.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: skillDetail
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
property string text: ""
|
||||
width: Math.min(contentWidth, realMainWin.width * 0.4)
|
||||
height: Math.min(contentHeight + 24, realMainWin.height * 0.9)
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
padding: 12
|
||||
background: Rectangle {
|
||||
color: "#EEEEEEEE"
|
||||
radius: 5
|
||||
border.color: "#A6967A"
|
||||
border.width: 1
|
||||
}
|
||||
contentItem: Text {
|
||||
text: "<b>" + luatr(orig) + "</b>: " + luatr(":" + orig)
|
||||
font.pixelSize: 20
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: TextEdit.RichText
|
||||
|
||||
TapHandler {
|
||||
onTapped: skillDetail.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ MetroButton {
|
|||
const box = roomScene.popupBox.item;
|
||||
box.options = choices;
|
||||
box.all_options = all_choices;
|
||||
box.skill_name = skill;
|
||||
box.accepted.connect(() => {
|
||||
answer = all_choices[box.result];
|
||||
});
|
||||
|
|
|
@ -64,9 +64,15 @@ Rectangle {
|
|||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Next")
|
||||
enabled: view.currentIndex + 1 < total
|
||||
onClicked: view.currentIndex++
|
||||
text: view.currentIndex + 1 == total ? qsTr("OK!") : qsTr("Next")
|
||||
enabled: view.currentIndex + 1 <= total
|
||||
onClicked: {
|
||||
if (view.currentIndex + 1 == total) {
|
||||
mainStack.pop();
|
||||
} else {
|
||||
view.currentIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,6 @@ Window {
|
|||
|
||||
Component { id: init; Init {} }
|
||||
Component { id: packageManage; PackageManage {} }
|
||||
Component { id: modMaker; ModMaker {} }
|
||||
Component { id: lobby; Lobby {} }
|
||||
Component { id: generalsOverview; GeneralsOverview {} }
|
||||
Component { id: cardsOverview; CardsOverview {} }
|
||||
|
|
18
Fk/util.js
18
Fk/util.js
|
@ -12,15 +12,29 @@ function convertNumber(number) {
|
|||
return "";
|
||||
}
|
||||
|
||||
function getPlayerStr(playerid) {
|
||||
const photo = getPhoto(playerid);
|
||||
if (photo.general === "anjiang" && (photo.deputyGeneral === "anjiang" || !p.deputyGeneral)) {
|
||||
return luatr("seat#" + photo.seatNumber);
|
||||
}
|
||||
|
||||
let ret = photo.general;
|
||||
ret = luatr(ret);
|
||||
if (photo.deputyGeneral && photo.deputyGeneral !== "") {
|
||||
ret = ret + "/" + luatr(photo.deputyGeneral);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function processPrompt(prompt) {
|
||||
const data = prompt.split(":");
|
||||
let raw = luatr(data[0]);
|
||||
const src = parseInt(data[1]);
|
||||
const dest = parseInt(data[2]);
|
||||
if (raw.match("%src"))
|
||||
raw = raw.replace(/%src/g, luatr(getPhoto(src).general));
|
||||
raw = raw.replace(/%src/g, getPlayerStr(src));
|
||||
if (raw.match("%dest"))
|
||||
raw = raw.replace(/%dest/g, luatr(getPhoto(dest).general));
|
||||
raw = raw.replace(/%dest/g, getPlayerStr(dest));
|
||||
if (raw.match("%arg2"))
|
||||
raw = raw.replace(/%arg2/g, luatr(data[4]));
|
||||
if (raw.match("%arg"))
|
||||
|
|
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>
|
||||
</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>
|
||||
<name>Init</name>
|
||||
<message>
|
||||
|
@ -292,7 +324,15 @@
|
|||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<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/client_socket.cpp"
|
||||
"network/router.cpp"
|
||||
"server/auth.cpp"
|
||||
"server/server.cpp"
|
||||
"server/serverplayer.cpp"
|
||||
"server/roombase.cpp"
|
||||
"server/lobby.cpp"
|
||||
"server/room.cpp"
|
||||
"server/roomthread.cpp"
|
||||
"server/scheduler.cpp"
|
||||
"ui/qmlbackend.cpp"
|
||||
"swig/freekill-wrap.cxx"
|
||||
)
|
||||
|
@ -21,7 +25,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
|
|||
"client/client.cpp"
|
||||
"client/clientplayer.cpp"
|
||||
"client/replayer.cpp"
|
||||
"ui/mod.cpp"
|
||||
# "ui/mod.cpp"
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "client.h"
|
||||
#include "client_socket.h"
|
||||
#include "clientplayer.h"
|
||||
#include "qmlbackend.h"
|
||||
#include "util.h"
|
||||
#include "server.h"
|
||||
#include <qforeach.h>
|
||||
#include <qlogging.h>
|
||||
#include "client/client.h"
|
||||
#include "client/clientplayer.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
#include "core/util.h"
|
||||
#include "server/server.h"
|
||||
#include "network/client_socket.h"
|
||||
|
||||
Client *ClientInstance = nullptr;
|
||||
ClientPlayer *Self = nullptr;
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
#ifndef _CLIENT_H
|
||||
#define _CLIENT_H
|
||||
|
||||
#include "router.h"
|
||||
#include "clientplayer.h"
|
||||
#include <qfilesystemwatcher.h>
|
||||
#include "network/router.h"
|
||||
#include "client/clientplayer.h"
|
||||
|
||||
#ifndef FK_SERVER_ONLY
|
||||
#include "qmlbackend.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
#endif
|
||||
|
||||
class Client : public QObject {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "clientplayer.h"
|
||||
#include "client/clientplayer.h"
|
||||
|
||||
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
|
||||
setId(id);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef _CLIENTPLAYER_H
|
||||
#define _CLIENTPLAYER_H
|
||||
|
||||
#include "player.h"
|
||||
#include "core/player.h"
|
||||
|
||||
class ClientPlayer : public Player {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "replayer.h"
|
||||
#include "client.h"
|
||||
#include "qmlbackend.h"
|
||||
#include "util.h"
|
||||
#include "client/replayer.h"
|
||||
#include "client/client.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
#include "core/util.h"
|
||||
|
||||
Replayer::Replayer(QObject *parent, const QString &filename) :
|
||||
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "packman.h"
|
||||
#include "core/packman.h"
|
||||
#include "git2.h"
|
||||
#include "util.h"
|
||||
#include "qmlbackend.h"
|
||||
#include <qjsondocument.h>
|
||||
#include "core/util.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
|
||||
PackMan *Pacman;
|
||||
|
||||
|
@ -70,13 +69,16 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
|
|||
auto obj = e.toObject();
|
||||
auto name = obj["name"].toString();
|
||||
auto url = obj["url"].toString();
|
||||
#ifndef FK_SERVER_ONLY
|
||||
Backend->showToast(tr("[%1/%2] upgrading package '%3'").arg(i).arg(arr.count()).arg(name));
|
||||
#endif
|
||||
bool toast_showed = false;
|
||||
if (SelectFromDatabase(
|
||||
db,
|
||||
QString("SELECT name FROM packages WHERE name='%1';").arg(name))
|
||||
.isEmpty()) {
|
||||
#ifndef FK_SERVER_ONLY
|
||||
Backend->showToast(tr("[%1/%2] upgrading package '%3'")
|
||||
.arg(i).arg(arr.count()).arg(name));
|
||||
toast_showed = true;
|
||||
#endif
|
||||
downloadNewPack(url);
|
||||
}
|
||||
ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'")
|
||||
|
@ -85,6 +87,11 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
|
|||
enablePack(name);
|
||||
|
||||
if (head(name) != obj["hash"].toString()) {
|
||||
#ifndef FK_SERVER_ONLY
|
||||
if (!toast_showed)
|
||||
Backend->showToast(tr("[%1/%2] upgrading package '%3'")
|
||||
.arg(i).arg(arr.count()).arg(name));
|
||||
#endif
|
||||
updatePack(name);
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +178,7 @@ void PackMan::updatePack(const QString &pack) {
|
|||
if (error != 0) {
|
||||
#ifndef FK_SERVER_ONLY
|
||||
if (Backend != nullptr) {
|
||||
Backend->showToast(tr("packages/%1: some error occured.").arg(pack));
|
||||
Backend->dialog("critical", tr("packages/%1: some error occured.").arg(pack));
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
|
@ -193,7 +200,7 @@ void PackMan::upgradePack(const QString &pack) {
|
|||
if (error != 0) {
|
||||
#ifndef FK_SERVER_ONLY
|
||||
if (Backend != nullptr) {
|
||||
Backend->showToast(tr("packages/%1: some error occured.").arg(pack));
|
||||
Backend->showDialog("critical", tr("packages/%1: some error occured.").arg(pack));
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
#ifndef _PACKMAN_H
|
||||
#define _PACKMAN_H
|
||||
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
// 管理拓展包所需的类,本质上是libgit2接口的再封装。
|
||||
class PackMan : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "player.h"
|
||||
#include "core/player.h"
|
||||
|
||||
Player::Player(QObject *parent)
|
||||
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "util.h"
|
||||
#include "packman.h"
|
||||
#include <qcryptographichash.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qregularexpression.h>
|
||||
#include "core/util.h"
|
||||
#include "core/packman.h"
|
||||
#include <QSysInfo>
|
||||
|
||||
extern "C" {
|
||||
|
@ -172,6 +169,17 @@ static void writeDirMD5(QFile &dest, const QString &dir,
|
|||
}
|
||||
}
|
||||
|
||||
static void writeFkVerMD5(QFile &dest) {
|
||||
QFile flist("fk_ver");
|
||||
if (flist.exists() && flist.open(QIODevice::ReadOnly)) {
|
||||
while (true) {
|
||||
QByteArray bytes = flist.readLine().simplified();
|
||||
if (bytes.isNull()) break;
|
||||
writeFileMD5(dest, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString calcFileMD5() {
|
||||
// First, generate flist.txt
|
||||
// flist.txt is a file contains all md5sum for code files
|
||||
|
@ -180,12 +188,13 @@ QString calcFileMD5() {
|
|||
qFatal("Cannot open flist.txt. Quitting.");
|
||||
}
|
||||
|
||||
writeFkVerMD5(flist);
|
||||
writeDirMD5(flist, "packages", "*.lua");
|
||||
writeDirMD5(flist, "packages", "*.qml");
|
||||
writeDirMD5(flist, "packages", "*.js");
|
||||
writeDirMD5(flist, "lua", "*.lua");
|
||||
writeDirMD5(flist, "Fk", "*.qml");
|
||||
writeDirMD5(flist, "Fk", "*.js");
|
||||
// writeDirMD5(flist, "lua", "*.lua");
|
||||
// writeDirMD5(flist, "Fk", "*.qml");
|
||||
// writeDirMD5(flist, "Fk", "*.js");
|
||||
|
||||
// then, return flist.txt's md5
|
||||
flist.close();
|
||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -1,14 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "client.h"
|
||||
#include "util.h"
|
||||
#include "client/client.h"
|
||||
#include "core/util.h"
|
||||
using namespace fkShell;
|
||||
|
||||
#include "packman.h"
|
||||
#include "server.h"
|
||||
#include "core/packman.h"
|
||||
#include "server/server.h"
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include "shell.h"
|
||||
#include "server/shell.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
|
@ -22,7 +22,7 @@ using namespace fkShell;
|
|||
#ifndef Q_OS_ANDROID
|
||||
#include <QQuickStyle>
|
||||
#endif
|
||||
#include "qmlbackend.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
|
@ -113,10 +113,10 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
|||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "\r%02d/%02d ", date.month(), date.day());
|
||||
fprintf(stderr, "%02d/%02d ", date.month(), date.day());
|
||||
fprintf(stderr, "%s ",
|
||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||
fprintf(file, "\r%02d/%02d ", date.month(), date.day());
|
||||
fprintf(file, "%02d/%02d ", date.month(), date.day());
|
||||
fprintf(file, "%s ",
|
||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||
|
||||
|
@ -150,8 +150,7 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
|||
"C", localMsg.constData());
|
||||
#ifndef FK_SERVER_ONLY
|
||||
if (Backend != nullptr) {
|
||||
Backend->notifyUI(
|
||||
"ErrorDialog",
|
||||
Backend->notifyUI("ErrorDialog",
|
||||
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
||||
}
|
||||
#endif
|
||||
|
@ -329,6 +328,7 @@ int main(int argc, char *argv[]) {
|
|||
#if defined(Q_OS_ANDROID)
|
||||
system = "Android";
|
||||
#elif defined(Q_OS_WIN32)
|
||||
qputenv("QT_MEDIA_BACKEND", "windows");
|
||||
system = "Win";
|
||||
::system("chcp 65001");
|
||||
#elif defined(Q_OS_LINUX)
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "client_socket.h"
|
||||
#include "network/client_socket.h"
|
||||
#include <openssl/aes.h>
|
||||
#include <qabstractsocket.h>
|
||||
#include <qrandom.h>
|
||||
|
||||
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
|
||||
aes_ready = false;
|
||||
|
@ -35,7 +33,7 @@ void ClientSocket::connectToHost(const QString &address, ushort port) {
|
|||
void ClientSocket::getMessage() {
|
||||
while (socket->canReadLine()) {
|
||||
auto msg = socket->readLine();
|
||||
msg = aesDecrypt(msg);
|
||||
msg = aesDec(msg);
|
||||
if (msg.startsWith("Compressed")) {
|
||||
msg = msg.sliced(10);
|
||||
msg = qUncompress(QByteArray::fromBase64(msg));
|
||||
|
@ -54,9 +52,9 @@ void ClientSocket::send(const QByteArray &msg) {
|
|||
if (msg.length() >= 1024) {
|
||||
auto comp = qCompress(msg);
|
||||
_msg = "Compressed" + comp.toBase64();
|
||||
_msg = aesEncrypt(_msg) + "\n";
|
||||
_msg = aesEnc(_msg) + "\n";
|
||||
} else {
|
||||
_msg = aesEncrypt(msg) + "\n";
|
||||
_msg = aesEnc(msg) + "\n";
|
||||
}
|
||||
|
||||
socket->write(_msg);
|
||||
|
@ -156,7 +154,7 @@ void ClientSocket::installAESKey(const QByteArray &key) {
|
|||
aes_ready = true;
|
||||
}
|
||||
|
||||
QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
|
||||
QByteArray ClientSocket::aesEnc(const QByteArray &in) {
|
||||
if (!aes_ready) {
|
||||
return in;
|
||||
}
|
||||
|
@ -182,7 +180,7 @@ QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
|
|||
|
||||
return iv + out.toBase64();
|
||||
}
|
||||
QByteArray ClientSocket::aesDecrypt(const QByteArray &in) {
|
||||
QByteArray ClientSocket::aesDec(const QByteArray &in) {
|
||||
if (!aes_ready) {
|
||||
return in;
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ private slots:
|
|||
void raiseError(QAbstractSocket::SocketError error);
|
||||
|
||||
private:
|
||||
QByteArray aesEncrypt(const QByteArray &in);
|
||||
QByteArray aesDecrypt(const QByteArray &out);
|
||||
QByteArray aesEnc(const QByteArray &in);
|
||||
QByteArray aesDec(const QByteArray &out);
|
||||
AES_KEY aes_key;
|
||||
bool aes_ready;
|
||||
QTcpSocket *socket;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "router.h"
|
||||
#include "client.h"
|
||||
#include "client_socket.h"
|
||||
#include "roomthread.h"
|
||||
#include <qjsondocument.h>
|
||||
#include "server.h"
|
||||
#include "serverplayer.h"
|
||||
#include "util.h"
|
||||
#include "network/router.h"
|
||||
#include "client/client.h"
|
||||
#include "network/client_socket.h"
|
||||
#include "server/roomthread.h"
|
||||
#include "server/server.h"
|
||||
#include "server/serverplayer.h"
|
||||
#include "core/util.h"
|
||||
|
||||
Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
|
||||
: QObject(parent) {
|
||||
|
@ -160,7 +159,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
|||
return;
|
||||
}
|
||||
|
||||
Room *room = player->getRoom();
|
||||
auto room = player->getRoom();
|
||||
room->handlePacket(player, command, jsonData);
|
||||
}
|
||||
} else if (type & TYPE_REQUEST) {
|
||||
|
@ -180,10 +179,13 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
|||
|
||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
|
||||
player->setThinking(false);
|
||||
// qDebug() << "wake up!";
|
||||
auto room = player->getRoom();
|
||||
if (room->getThread()) {
|
||||
room->getThread()->wakeUp();
|
||||
auto _room = player->getRoom();
|
||||
if (!_room->isLobby()) {
|
||||
auto room = qobject_cast<Room *>(_room);
|
||||
if (room->getThread()) {
|
||||
room->getThread()->wakeUp(room->getId());
|
||||
// TODO: signal
|
||||
}
|
||||
}
|
||||
|
||||
if (requestId != this->expectedReplyId)
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "server_socket.h"
|
||||
#include "client_socket.h"
|
||||
#include "network/server_socket.h"
|
||||
#include "network/client_socket.h"
|
||||
#include "server/server.h"
|
||||
#include "core/util.h"
|
||||
|
||||
ServerSocket::ServerSocket() {
|
||||
ServerSocket::ServerSocket(QObject *parent) : QObject(parent) {
|
||||
server = new QTcpServer(this);
|
||||
connect(server, &QTcpServer::newConnection, this,
|
||||
&ServerSocket::processNewConnection);
|
||||
|
||||
udpSocket = new QUdpSocket(this);
|
||||
connect(udpSocket, &QUdpSocket::readyRead,
|
||||
this, &ServerSocket::readPendingDatagrams);
|
||||
}
|
||||
|
||||
bool ServerSocket::listen(const QHostAddress &address, ushort port) {
|
||||
udpSocket->bind(port);
|
||||
return server->listen(address, port);
|
||||
}
|
||||
|
||||
|
@ -20,3 +27,29 @@ void ServerSocket::processNewConnection() {
|
|||
[connection]() { connection->deleteLater(); });
|
||||
emit new_connection(connection);
|
||||
}
|
||||
|
||||
void ServerSocket::readPendingDatagrams() {
|
||||
while (udpSocket->hasPendingDatagrams()) {
|
||||
QNetworkDatagram datagram = udpSocket->receiveDatagram();
|
||||
if (datagram.isValid()) {
|
||||
processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerSocket::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
|
||||
auto server = qobject_cast<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
|
||||
|
||||
public:
|
||||
ServerSocket();
|
||||
ServerSocket(QObject *parent = nullptr);
|
||||
|
||||
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
|
||||
|
||||
|
@ -20,9 +20,12 @@ signals:
|
|||
private slots:
|
||||
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
||||
void processNewConnection();
|
||||
void readPendingDatagrams();
|
||||
|
||||
private:
|
||||
QTcpServer *server;
|
||||
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
|
||||
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
|
||||
};
|
||||
|
||||
#endif // _SERVER_SOCKET_H
|
||||
|
|
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
|
||||
|
||||
#include "room.h"
|
||||
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include "server/room.h"
|
||||
#include "server/lobby.h"
|
||||
|
||||
#ifdef FK_SERVER_ONLY
|
||||
static void *ClientInstance = nullptr;
|
||||
#else
|
||||
#include "client.h"
|
||||
#include "client/client.h"
|
||||
#endif
|
||||
|
||||
#include "client_socket.h"
|
||||
#include "roomthread.h"
|
||||
#include "server.h"
|
||||
#include "serverplayer.h"
|
||||
#include "util.h"
|
||||
#include "network/client_socket.h"
|
||||
#include "server/roomthread.h"
|
||||
#include "server/server.h"
|
||||
#include "server/serverplayer.h"
|
||||
#include "core/util.h"
|
||||
|
||||
Room::Room(RoomThread *m_thread) {
|
||||
auto server = ServerInstance;
|
||||
id = server->nextRoomId;
|
||||
server->nextRoomId++;
|
||||
this->server = server;
|
||||
setThread(m_thread);
|
||||
if (m_thread) { // In case of lobby
|
||||
m_thread->addRoom(this);
|
||||
}
|
||||
|
@ -36,14 +33,8 @@ Room::Room(RoomThread *m_thread) {
|
|||
|
||||
m_ready = true;
|
||||
|
||||
// 如果是普通房间而不是大厅,就初始化Lua,否则置Lua为nullptr
|
||||
if (!isLobby()) {
|
||||
// 如果不是大厅,那么:
|
||||
// * 只要房间添加人了,那么从大厅中移掉这个人
|
||||
// * 只要有人离开房间,那就把他加到大厅去
|
||||
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
|
||||
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
|
||||
}
|
||||
connect(this, &Room::playerAdded, server->lobby(), &Lobby::removePlayer);
|
||||
connect(this, &Room::playerRemoved, server->lobby(), &Lobby::addPlayer);
|
||||
}
|
||||
|
||||
Room::~Room() {
|
||||
|
@ -56,8 +47,6 @@ Room::~Room() {
|
|||
}
|
||||
}
|
||||
|
||||
Server *Room::getServer() const { return server; }
|
||||
|
||||
RoomThread *Room::getThread() const { return m_thread; }
|
||||
|
||||
void Room::setThread(RoomThread *t) {
|
||||
|
@ -71,8 +60,6 @@ int Room::getId() const { return id; }
|
|||
|
||||
void Room::setId(int id) { this->id = id; }
|
||||
|
||||
bool Room::isLobby() const { return id == 0; }
|
||||
|
||||
QString Room::getName() const { return name; }
|
||||
|
||||
void Room::setName(const QString &name) { this->name = name; }
|
||||
|
@ -88,9 +75,6 @@ const QByteArray Room::getSettings() const { return settings; }
|
|||
void Room::setSettings(QByteArray settings) { this->settings = settings; }
|
||||
|
||||
bool Room::isAbandoned() const {
|
||||
if (isLobby())
|
||||
return false;
|
||||
|
||||
if (players.isEmpty())
|
||||
return true;
|
||||
|
||||
|
@ -151,72 +135,60 @@ void Room::addPlayer(ServerPlayer *player) {
|
|||
auto mode = settings["gameMode"].toString();
|
||||
|
||||
// 告诉房里所有玩家有新人进来了
|
||||
if (!isLobby()) {
|
||||
jsonData << player->getId();
|
||||
jsonData << player->getScreenName();
|
||||
jsonData << player->getAvatar();
|
||||
jsonData << player->isReady();
|
||||
jsonData << player->getTotalGameTime();
|
||||
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
|
||||
}
|
||||
jsonData << player->getId();
|
||||
jsonData << player->getScreenName();
|
||||
jsonData << player->getAvatar();
|
||||
jsonData << player->isReady();
|
||||
jsonData << player->getTotalGameTime();
|
||||
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
|
||||
|
||||
players.append(player);
|
||||
player->setRoom(this);
|
||||
|
||||
if (isLobby()) {
|
||||
// 有机器人进入大厅(可能因为被踢),那么改为销毁
|
||||
if (player->getState() == Player::Robot) {
|
||||
removePlayer(player);
|
||||
player->deleteLater();
|
||||
} else {
|
||||
player->doNotify("EnterLobby", "[]");
|
||||
}
|
||||
} else {
|
||||
// Second, let the player enter room and add other players
|
||||
// Second, let the player enter room and add other players
|
||||
jsonData = QJsonArray();
|
||||
jsonData << this->capacity;
|
||||
jsonData << this->timeout;
|
||||
jsonData << QJsonDocument::fromJson(this->settings).object();
|
||||
player->doNotify("EnterRoom", JsonArray2Bytes(jsonData));
|
||||
|
||||
foreach (ServerPlayer *p, getOtherPlayers(player)) {
|
||||
jsonData = QJsonArray();
|
||||
jsonData << this->capacity;
|
||||
jsonData << this->timeout;
|
||||
jsonData << QJsonDocument::fromJson(this->settings).object();
|
||||
player->doNotify("EnterRoom", JsonArray2Bytes(jsonData));
|
||||
jsonData << p->getId();
|
||||
jsonData << p->getScreenName();
|
||||
jsonData << p->getAvatar();
|
||||
jsonData << p->isReady();
|
||||
jsonData << p->getTotalGameTime();
|
||||
player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
|
||||
|
||||
foreach (ServerPlayer *p, getOtherPlayers(player)) {
|
||||
jsonData = QJsonArray();
|
||||
jsonData << p->getId();
|
||||
jsonData << p->getScreenName();
|
||||
jsonData << p->getAvatar();
|
||||
jsonData << p->isReady();
|
||||
jsonData << p->getTotalGameTime();
|
||||
player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
|
||||
|
||||
jsonData = QJsonArray();
|
||||
jsonData << p->getId();
|
||||
foreach (int i, p->getGameData()) {
|
||||
jsonData << i;
|
||||
}
|
||||
player->doNotify("UpdateGameData", JsonArray2Bytes(jsonData));
|
||||
jsonData = QJsonArray();
|
||||
jsonData << p->getId();
|
||||
foreach (int i, p->getGameData()) {
|
||||
jsonData << i;
|
||||
}
|
||||
|
||||
if (this->owner != nullptr) {
|
||||
jsonData = QJsonArray();
|
||||
jsonData << this->owner->getId();
|
||||
player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
|
||||
}
|
||||
|
||||
if (player->getLastGameMode() != mode) {
|
||||
player->setLastGameMode(mode);
|
||||
updatePlayerGameData(player->getId(), mode);
|
||||
} else {
|
||||
auto jsonData = QJsonArray();
|
||||
jsonData << player->getId();
|
||||
foreach (int i, player->getGameData()) {
|
||||
jsonData << i;
|
||||
}
|
||||
doBroadcastNotify(getPlayers(), "UpdateGameData", JsonArray2Bytes(jsonData));
|
||||
}
|
||||
// 玩家手动启动
|
||||
// if (isFull() && !gameStarted)
|
||||
// start();
|
||||
player->doNotify("UpdateGameData", JsonArray2Bytes(jsonData));
|
||||
}
|
||||
|
||||
if (this->owner != nullptr) {
|
||||
jsonData = QJsonArray();
|
||||
jsonData << this->owner->getId();
|
||||
player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
|
||||
}
|
||||
|
||||
if (player->getLastGameMode() != mode) {
|
||||
player->setLastGameMode(mode);
|
||||
updatePlayerGameData(player->getId(), mode);
|
||||
} else {
|
||||
auto jsonData = QJsonArray();
|
||||
jsonData << player->getId();
|
||||
foreach (int i, player->getGameData()) {
|
||||
jsonData << i;
|
||||
}
|
||||
doBroadcastNotify(getPlayers(), "UpdateGameData", JsonArray2Bytes(jsonData));
|
||||
}
|
||||
// 玩家手动启动
|
||||
// if (isFull() && !gameStarted)
|
||||
// start();
|
||||
emit playerAdded(player);
|
||||
}
|
||||
|
||||
|
@ -251,12 +223,7 @@ void Room::removePlayer(ServerPlayer *player) {
|
|||
}
|
||||
emit playerRemoved(player);
|
||||
|
||||
if (isLobby())
|
||||
return;
|
||||
|
||||
QJsonArray jsonData;
|
||||
jsonData << player->getId();
|
||||
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes(jsonData));
|
||||
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes({ player->getId() }));
|
||||
} else {
|
||||
// 否则给跑路玩家召唤个AI代打
|
||||
// TODO: if the player is died..
|
||||
|
@ -287,7 +254,7 @@ void Room::removePlayer(ServerPlayer *player) {
|
|||
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
||||
server->addPlayer(runner);
|
||||
|
||||
m_thread->wakeUp();
|
||||
// m_thread->wakeUp();
|
||||
|
||||
// 发出信号,让大厅添加这个人
|
||||
emit playerRemoved(runner);
|
||||
|
@ -312,22 +279,6 @@ void Room::removePlayer(ServerPlayer *player) {
|
|||
}
|
||||
}
|
||||
|
||||
QList<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) {
|
||||
// 首先只能旁观在运行的房间,因为旁观是由Lua处理的
|
||||
if (!gameStarted) {
|
||||
|
@ -371,6 +322,10 @@ int Room::getTimeout() const { return timeout; }
|
|||
|
||||
void Room::setTimeout(int timeout) { this->timeout = timeout; }
|
||||
|
||||
void Room::delay(int ms) {
|
||||
m_thread->delay(id, ms);
|
||||
}
|
||||
|
||||
bool Room::isOutdated() {
|
||||
bool ret = md5 != server->getMd5();
|
||||
if (ret) md5 = "";
|
||||
|
@ -379,42 +334,6 @@ bool Room::isOutdated() {
|
|||
|
||||
bool Room::isStarted() const { return gameStarted; }
|
||||
|
||||
void Room::doBroadcastNotify(const QList<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 =
|
||||
QString("SELECT win, lose, draw "
|
||||
"FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';");
|
||||
|
@ -551,18 +470,18 @@ void Room::updatePlayerGameData(int id, const QString &mode) {
|
|||
auto room = player->getRoom();
|
||||
player->setGameData(total, win, run);
|
||||
auto data_arr = QJsonArray({ player->getId(), total, win, run });
|
||||
if (!room->isLobby()) {
|
||||
room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
|
||||
}
|
||||
room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
|
||||
}
|
||||
|
||||
void Room::gameOver() {
|
||||
if (!gameStarted) return;
|
||||
insideGameOver = true;
|
||||
gameStarted = false;
|
||||
runned_players.clear();
|
||||
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
|
||||
auto settings = QJsonDocument::fromJson(this->settings);
|
||||
auto mode = settings["gameMode"].toString();
|
||||
server->beginTransaction();
|
||||
foreach (ServerPlayer *p, players) {
|
||||
auto pid = p->getId();
|
||||
|
||||
|
@ -578,7 +497,7 @@ void Room::gameOver() {
|
|||
realPlayer->doNotify("AddTotalGameTime", bytes);
|
||||
}
|
||||
|
||||
// 摸了,这么写总之不会有问题
|
||||
// 将游戏时间更新到数据库中
|
||||
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
|
||||
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
|
||||
ExecSQL(server->getDatabase(), info_update);
|
||||
|
@ -587,18 +506,13 @@ void Room::gameOver() {
|
|||
if (p->getState() != Player::Online) {
|
||||
if (p->getState() == Player::Offline) {
|
||||
addRunRate(pid, mode);
|
||||
// addRunRate(pid, mode);
|
||||
server->temporarilyBan(pid);
|
||||
}
|
||||
p->deleteLater();
|
||||
}
|
||||
}
|
||||
// 旁观者不能在这清除,因为removePlayer逻辑不一样
|
||||
// observers.clear();
|
||||
// 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
|
||||
// players.clear();
|
||||
// owner = nullptr;
|
||||
// clearRequest();
|
||||
server->endTransaction();
|
||||
insideGameOver = true;
|
||||
}
|
||||
|
||||
void Room::manuallyStart() {
|
||||
|
@ -620,7 +534,6 @@ void Room::pushRequest(const QString &req) {
|
|||
}
|
||||
|
||||
void Room::addRejectId(int id) {
|
||||
if (isLobby()) return;
|
||||
rejected_players << id;
|
||||
}
|
||||
|
||||
|
@ -629,184 +542,84 @@ void Room::removeRejectId(int id) {
|
|||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
static void updateAvatar(ServerPlayer *sender, const QString &jsonData) {
|
||||
auto arr = String2Json(jsonData).array();
|
||||
auto avatar = arr[0].toString();
|
||||
|
||||
if (CheckSqlString(avatar)) {
|
||||
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
|
||||
.arg(avatar)
|
||||
.arg(sender->getId());
|
||||
ExecSQL(ServerInstance->getDatabase(), sql);
|
||||
sender->setAvatar(avatar);
|
||||
sender->doNotify("UpdateAvatar", avatar);
|
||||
}
|
||||
}
|
||||
|
||||
static void updatePassword(ServerPlayer *sender, const QString &jsonData) {
|
||||
auto arr = String2Json(jsonData).array();
|
||||
auto oldpw = arr[0].toString();
|
||||
auto newpw = arr[1].toString();
|
||||
auto sql_find =
|
||||
QString("SELECT password, salt FROM userinfo WHERE id=%1;")
|
||||
.arg(sender->getId());
|
||||
|
||||
auto passed = false;
|
||||
auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
|
||||
auto result = arr2[0].toObject();
|
||||
passed = (result["password"].toString() ==
|
||||
QCryptographicHash::hash(
|
||||
oldpw.append(result["salt"].toString()).toLatin1(),
|
||||
QCryptographicHash::Sha256)
|
||||
.toHex());
|
||||
if (passed) {
|
||||
auto sql_update =
|
||||
QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
|
||||
.arg(QCryptographicHash::hash(
|
||||
newpw.append(result["salt"].toString()).toLatin1(),
|
||||
QCryptographicHash::Sha256)
|
||||
.toHex())
|
||||
.arg(sender->getId());
|
||||
ExecSQL(ServerInstance->getDatabase(), sql_update);
|
||||
}
|
||||
|
||||
sender->doNotify("UpdatePassword", passed ? "1" : "0");
|
||||
}
|
||||
|
||||
static void createRoom(ServerPlayer *sender, const QString &jsonData) {
|
||||
auto arr = String2Json(jsonData).array();
|
||||
auto name = arr[0].toString();
|
||||
auto capacity = arr[1].toInt();
|
||||
auto timeout = arr[2].toInt();
|
||||
auto settings =
|
||||
QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
|
||||
ServerInstance->createRoom(sender, name, capacity, timeout, settings);
|
||||
}
|
||||
|
||||
static void enterRoom(ServerPlayer *sender, const QString &jsonData) {
|
||||
auto arr = String2Json(jsonData).array();
|
||||
auto roomId = arr[0].toInt();
|
||||
auto room = ServerInstance->findRoom(roomId);
|
||||
if (room) {
|
||||
auto settings = QJsonDocument::fromJson(room->getSettings());
|
||||
auto password = settings["password"].toString();
|
||||
if (password.isEmpty() || arr[1].toString() == password) {
|
||||
if (room->isOutdated()) {
|
||||
sender->doNotify("ErrorMsg", "room is outdated");
|
||||
} else {
|
||||
room->addPlayer(sender);
|
||||
}
|
||||
} else {
|
||||
sender->doNotify("ErrorMsg", "room password error");
|
||||
}
|
||||
} else {
|
||||
sender->doNotify("ErrorMsg", "no such room");
|
||||
}
|
||||
}
|
||||
|
||||
static void observeRoom(ServerPlayer *sender, const QString &jsonData) {
|
||||
auto arr = String2Json(jsonData).array();
|
||||
auto roomId = arr[0].toInt();
|
||||
auto room = ServerInstance->findRoom(roomId);
|
||||
if (room) {
|
||||
auto settings = QJsonDocument::fromJson(room->getSettings());
|
||||
auto password = settings["password"].toString();
|
||||
if (password.isEmpty() || arr[1].toString() == password) {
|
||||
if (room->isOutdated()) {
|
||||
sender->doNotify("ErrorMsg", "room is outdated");
|
||||
} else {
|
||||
room->addObserver(sender);
|
||||
}
|
||||
} else {
|
||||
sender->doNotify("ErrorMsg", "room password error");
|
||||
}
|
||||
} else {
|
||||
sender->doNotify("ErrorMsg", "no such room");
|
||||
}
|
||||
}
|
||||
|
||||
static void refreshRoomList(ServerPlayer *sender, const QString &) {
|
||||
ServerInstance->updateRoomList(sender);
|
||||
};
|
||||
|
||||
static void quitRoom(ServerPlayer *player, const QString &) {
|
||||
auto room = player->getRoom();
|
||||
room->removePlayer(player);
|
||||
if (room->isOutdated()) {
|
||||
void Room::quitRoom(ServerPlayer *player, const QString &) {
|
||||
removePlayer(player);
|
||||
if (isOutdated()) {
|
||||
player->kicked();
|
||||
}
|
||||
}
|
||||
|
||||
static void addRobot(ServerPlayer *player, const QString &) {
|
||||
auto room = player->getRoom();
|
||||
void Room::addRobotRequest(ServerPlayer *player, const QString &) {
|
||||
if (ServerInstance->getConfig("enableBots").toBool())
|
||||
room->addRobot(player);
|
||||
addRobot(player);
|
||||
}
|
||||
|
||||
static void kickPlayer(ServerPlayer *player, const QString &jsonData) {
|
||||
auto room = player->getRoom();
|
||||
void Room::kickPlayer(ServerPlayer *player, const QString &jsonData) {
|
||||
int i = jsonData.toInt();
|
||||
auto p = room->findPlayer(i);
|
||||
if (p && !room->isStarted()) {
|
||||
room->removePlayer(p);
|
||||
room->addRejectId(i);
|
||||
QTimer::singleShot(30000, room, [=]() {
|
||||
room->removeRejectId(i);
|
||||
auto p = findPlayer(i);
|
||||
if (p && !isStarted()) {
|
||||
removePlayer(p);
|
||||
addRejectId(i);
|
||||
QTimer::singleShot(30000, this, [=]() {
|
||||
removeRejectId(i);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void ready(ServerPlayer *player, const QString &) {
|
||||
auto room = player->getRoom();
|
||||
void Room::ready(ServerPlayer *player, const QString &) {
|
||||
player->setReady(!player->isReady());
|
||||
room->doBroadcastNotify(room->getPlayers(), "ReadyChanged",
|
||||
doBroadcastNotify(getPlayers(), "ReadyChanged",
|
||||
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
|
||||
}
|
||||
|
||||
static void startGame(ServerPlayer *player, const QString &) {
|
||||
auto room = player->getRoom();
|
||||
if (room->isOutdated()) {
|
||||
foreach (auto p, room->getPlayers()) {
|
||||
void Room::startGame(ServerPlayer *player, const QString &) {
|
||||
if (isOutdated()) {
|
||||
foreach (auto p, getPlayers()) {
|
||||
p->doNotify("ErrorMsg", "room is outdated");
|
||||
p->kicked();
|
||||
}
|
||||
} else {
|
||||
room->manuallyStart();
|
||||
manuallyStart();
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*room_cb)(ServerPlayer *, const QString &);
|
||||
static const QMap<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},
|
||||
};
|
||||
typedef void (Room::*room_cb)(ServerPlayer *, const QString &);
|
||||
|
||||
void Room::handlePacket(ServerPlayer *sender, const QString &command,
|
||||
const QString &jsonData) {
|
||||
if (command == "Chat") {
|
||||
chat(sender, jsonData);
|
||||
static const QMap<QString, room_cb> room_actions = {
|
||||
{"QuitRoom", &Room::quitRoom},
|
||||
{"AddRobot", &Room::addRobotRequest},
|
||||
{"KickPlayer", &Room::kickPlayer},
|
||||
{"Ready", &Room::ready},
|
||||
{"StartGame", &Room::startGame},
|
||||
{"Chat", &Room::chat},
|
||||
};
|
||||
|
||||
if (command == "PushRequest") {
|
||||
pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
|
||||
return;
|
||||
} else if (command == "PushRequest") {
|
||||
if (!isLobby())
|
||||
pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
|
||||
}
|
||||
|
||||
auto func_table = lobby_actions;
|
||||
if (!isLobby()) func_table = room_actions;
|
||||
auto func = func_table[command];
|
||||
if (func) {
|
||||
func(sender, jsonData);
|
||||
}
|
||||
auto func = room_actions[command];
|
||||
if (func) (this->*func)(sender, jsonData);
|
||||
}
|
||||
|
||||
// Lua用:request之前设置计时器防止等到死。
|
||||
void Room::setRequestTimer(int ms) {
|
||||
request_timer = new QTimer();
|
||||
request_timer->setSingleShot(true);
|
||||
request_timer->setInterval(ms);
|
||||
connect(request_timer, &QTimer::timeout, this, [=](){
|
||||
m_thread->wakeUp(id);
|
||||
});
|
||||
request_timer->start();
|
||||
}
|
||||
|
||||
// Lua用:当request完成后手动销毁计时器。
|
||||
void Room::destroyRequestTimer() {
|
||||
if (!request_timer) return;
|
||||
request_timer->stop();
|
||||
delete request_timer;
|
||||
request_timer = nullptr;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
#ifndef _ROOM_H
|
||||
#define _ROOM_H
|
||||
|
||||
#include "server/roombase.h"
|
||||
|
||||
class Server;
|
||||
class ServerPlayer;
|
||||
class RoomThread;
|
||||
|
||||
class Room : public QObject {
|
||||
class Room : public RoomBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Room(RoomThread *m_thread);
|
||||
|
@ -15,12 +17,11 @@ class Room : public QObject {
|
|||
|
||||
// Property reader & setter
|
||||
// ==================================={
|
||||
Server *getServer() const;
|
||||
RoomThread *getThread() const;
|
||||
void setThread(RoomThread *t);
|
||||
|
||||
int getId() const;
|
||||
void setId(int id);
|
||||
bool isLobby() const;
|
||||
QString getName() const;
|
||||
void setName(const QString &name);
|
||||
int getCapacity() const;
|
||||
|
@ -38,9 +39,6 @@ class Room : public QObject {
|
|||
void addPlayer(ServerPlayer *player);
|
||||
void addRobot(ServerPlayer *player);
|
||||
void removePlayer(ServerPlayer *player);
|
||||
QList<ServerPlayer *> getPlayers() const;
|
||||
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
|
||||
ServerPlayer *findPlayer(int id) const;
|
||||
|
||||
void addObserver(ServerPlayer *player);
|
||||
void removeObserver(ServerPlayer *player);
|
||||
|
@ -49,16 +47,13 @@ class Room : public QObject {
|
|||
|
||||
int getTimeout() const;
|
||||
void setTimeout(int timeout);
|
||||
void delay(int ms);
|
||||
|
||||
bool isOutdated();
|
||||
|
||||
bool isStarted() const;
|
||||
// ====================================}
|
||||
|
||||
void doBroadcastNotify(const QList<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,
|
||||
int result, bool dead);
|
||||
void gameOver();
|
||||
|
@ -71,6 +66,13 @@ class Room : public QObject {
|
|||
// router用
|
||||
void handlePacket(ServerPlayer *sender, const QString &command,
|
||||
const QString &jsonData);
|
||||
|
||||
void setRequestTimer(int ms);
|
||||
void destroyRequestTimer();
|
||||
|
||||
// FIXME
|
||||
volatile bool insideGameOver = false;
|
||||
|
||||
signals:
|
||||
void abandoned();
|
||||
|
||||
|
@ -78,8 +80,7 @@ class Room : public QObject {
|
|||
void playerRemoved(ServerPlayer *player);
|
||||
|
||||
private:
|
||||
Server *server;
|
||||
RoomThread *m_thread;
|
||||
RoomThread *m_thread = nullptr;
|
||||
int id; // Lobby's id is 0
|
||||
QString name; // “阴间大乱斗”
|
||||
int capacity; // by default is 5, max is 8
|
||||
|
@ -87,8 +88,6 @@ class Room : public QObject {
|
|||
bool m_abandoned; // If room is empty, delete it
|
||||
|
||||
ServerPlayer *owner; // who created this room?
|
||||
QList<ServerPlayer *> players;
|
||||
QList<ServerPlayer *> observers;
|
||||
QList<int> runned_players;
|
||||
QList<int> rejected_players;
|
||||
int robot_id;
|
||||
|
@ -98,8 +97,17 @@ class Room : public QObject {
|
|||
int timeout;
|
||||
QString md5;
|
||||
|
||||
QTimer *request_timer = nullptr;
|
||||
|
||||
void addRunRate(int id, const QString &mode);
|
||||
void updatePlayerGameData(int id, const QString &mode);
|
||||
|
||||
// handle packet
|
||||
void quitRoom(ServerPlayer *, const QString &);
|
||||
void addRobotRequest(ServerPlayer *, const QString &);
|
||||
void kickPlayer(ServerPlayer *, const QString &);
|
||||
void ready(ServerPlayer *, const QString &);
|
||||
void startGame(ServerPlayer *, const QString &);
|
||||
};
|
||||
|
||||
#endif // _ROOM_H
|
||||
|
|
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
|
||||
|
||||
#include "roomthread.h"
|
||||
#include "server.h"
|
||||
#include "util.h"
|
||||
#include <lua.h>
|
||||
#include "server/roomthread.h"
|
||||
#include "server/scheduler.h"
|
||||
#include "server/server.h"
|
||||
|
||||
#ifndef FK_SERVER_ONLY
|
||||
#include "client.h"
|
||||
#include "client/client.h"
|
||||
#endif
|
||||
|
||||
RoomThread::RoomThread(Server *m_server) {
|
||||
setObjectName("Room");
|
||||
this->m_server = m_server;
|
||||
m_capacity = 100; // TODO: server cfg
|
||||
terminated = false;
|
||||
md5 = m_server->getMd5();
|
||||
|
||||
L = CreateLuaState();
|
||||
if (QFile::exists("packages/freekill-core") &&
|
||||
!GetDisabledPacks().contains("freekill-core")) {
|
||||
// 危险的cd操作,记得在lua中切回游戏根目录
|
||||
QDir::setCurrent("packages/freekill-core");
|
||||
}
|
||||
|
||||
DoLuaScript(L, "lua/freekill.lua");
|
||||
DoLuaScript(L, "lua/server/scheduler.lua");
|
||||
start();
|
||||
}
|
||||
|
||||
RoomThread::~RoomThread() {
|
||||
tryTerminate();
|
||||
if (isRunning()) {
|
||||
wait();
|
||||
quit();
|
||||
}
|
||||
lua_close(L);
|
||||
delete m_scheduler;
|
||||
m_server->removeThread(this);
|
||||
// foreach (auto room, room_list) {
|
||||
// room->deleteLater();
|
||||
// }
|
||||
}
|
||||
|
||||
void RoomThread::run() {
|
||||
// 在run中创建,这样就能在接下来的exec中处理事件了
|
||||
m_scheduler = new Scheduler(this);
|
||||
connect(this, &RoomThread::pushRequest, m_scheduler, &Scheduler::handleRequest);
|
||||
connect(this, &RoomThread::delay, m_scheduler, &Scheduler::doDelay);
|
||||
connect(this, &RoomThread::wakeUp, m_scheduler, &Scheduler::resumeRoom);
|
||||
exec();
|
||||
}
|
||||
|
||||
Server *RoomThread::getServer() const {
|
||||
return m_server;
|
||||
}
|
||||
|
@ -56,7 +53,7 @@ Room *RoomThread::getRoom(int id) const {
|
|||
}
|
||||
|
||||
void RoomThread::addRoom(Room *room) {
|
||||
Q_UNUSED(room);
|
||||
room->setThread(this);
|
||||
m_capacity--;
|
||||
}
|
||||
|
||||
|
@ -69,6 +66,7 @@ void RoomThread::removeRoom(Room *room) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
QString RoomThread::fetchRequest() {
|
||||
// if (!gameStarted)
|
||||
// return "";
|
||||
|
@ -124,6 +122,7 @@ void RoomThread::tryTerminate() {
|
|||
bool RoomThread::isTerminated() const {
|
||||
return terminated;
|
||||
}
|
||||
*/
|
||||
|
||||
bool RoomThread::isConsoleStart() const {
|
||||
#ifndef FK_SERVER_ONLY
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
#ifndef _ROOMTHREAD_H
|
||||
#define _ROOMTHREAD_H
|
||||
|
||||
#include <qsemaphore.h>
|
||||
class Room;
|
||||
class Server;
|
||||
class Scheduler;
|
||||
|
||||
class RoomThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
@ -21,21 +21,24 @@ class RoomThread : public QThread {
|
|||
void addRoom(Room *room);
|
||||
void removeRoom(Room *room);
|
||||
|
||||
QString fetchRequest();
|
||||
void pushRequest(const QString &req);
|
||||
void clearRequest();
|
||||
bool hasRequest();
|
||||
//QString fetchRequest();
|
||||
//void clearRequest();
|
||||
//bool hasRequest();
|
||||
|
||||
void trySleep(int ms);
|
||||
void wakeUp();
|
||||
// void trySleep(int ms);
|
||||
|
||||
void tryTerminate();
|
||||
bool isTerminated() const;
|
||||
// void tryTerminate();
|
||||
// bool isTerminated() const;
|
||||
|
||||
bool isConsoleStart() const;
|
||||
|
||||
bool isOutdated();
|
||||
|
||||
signals:
|
||||
void pushRequest(const QString &req);
|
||||
void delay(int roomId, int ms);
|
||||
void wakeUp(int roomId);
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
|
||||
|
@ -45,11 +48,11 @@ class RoomThread : public QThread {
|
|||
int m_capacity;
|
||||
QString md5;
|
||||
|
||||
lua_State *L;
|
||||
QMutex request_queue_mutex;
|
||||
QQueue<QString> request_queue; // json string
|
||||
QSemaphore sema_wake;
|
||||
volatile bool terminated;
|
||||
Scheduler *m_scheduler;
|
||||
// QMutex request_queue_mutex;
|
||||
// QQueue<QString> request_queue; // json string
|
||||
// QSemaphore sema_wake;
|
||||
// volatile bool terminated;
|
||||
};
|
||||
|
||||
#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
|
||||
|
||||
#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 <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::Server(QObject *parent) : QObject(parent) {
|
||||
ServerInstance = this;
|
||||
db = OpenDatabase();
|
||||
rsa = initServerRSA();
|
||||
QFile file("server/rsa_pub");
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QTextStream in(&file);
|
||||
public_key = in.readAll();
|
||||
md5 = calcFileMD5();
|
||||
readConfig();
|
||||
|
||||
server = new ServerSocket();
|
||||
server->setParent(this);
|
||||
auth = new AuthManager(this);
|
||||
server = new ServerSocket(this);
|
||||
connect(server, &ServerSocket::new_connection, this,
|
||||
&Server::processNewConnection);
|
||||
|
||||
udpSocket = new QUdpSocket(this);
|
||||
connect(udpSocket, &QUdpSocket::readyRead,
|
||||
this, &Server::readPendingDatagrams);
|
||||
|
||||
// 创建第一个房间,这个房间作为“大厅房间”
|
||||
nextRoomId = 0;
|
||||
createRoom(nullptr, "Lobby", INT32_MAX);
|
||||
// 大厅只要发生人员变动,就向所有人广播一下房间列表
|
||||
connect(lobby(), &Room::playerAdded, this, &Server::updateOnlineInfo);
|
||||
connect(lobby(), &Room::playerRemoved, this, &Server::updateOnlineInfo);
|
||||
nextRoomId = 1;
|
||||
m_lobby = new Lobby(this);
|
||||
|
||||
// 启动心跳包线程
|
||||
auto heartbeatThread = QThread::create([=]() {
|
||||
|
@ -90,12 +70,10 @@ Server::~Server() {
|
|||
thread->deleteLater();
|
||||
}
|
||||
sqlite3_close(db);
|
||||
RSA_free(rsa);
|
||||
}
|
||||
|
||||
bool Server::listen(const QHostAddress &address, ushort port) {
|
||||
bool ret = server->listen(address, port);
|
||||
udpSocket->bind(port);
|
||||
isListening = ret;
|
||||
return ret;
|
||||
}
|
||||
|
@ -118,7 +96,7 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
|||
}
|
||||
}
|
||||
|
||||
if (!thread && nextRoomId != 0) {
|
||||
if (!thread) {
|
||||
thread = createThread();
|
||||
}
|
||||
|
||||
|
@ -127,16 +105,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
|||
room->setId(nextRoomId);
|
||||
nextRoomId++;
|
||||
room->setAbandoned(false);
|
||||
room->setThread(thread);
|
||||
thread->addRoom(room);
|
||||
rooms.insert(room->getId(), room);
|
||||
} else {
|
||||
room = new Room(thread);
|
||||
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
|
||||
if (room->isLobby())
|
||||
m_lobby = room;
|
||||
else
|
||||
rooms.insert(room->getId(), room);
|
||||
rooms.insert(room->getId(), room);
|
||||
}
|
||||
|
||||
room->setName(name);
|
||||
|
@ -144,13 +118,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
|||
room->setTimeout(timeout);
|
||||
room->setSettings(settings);
|
||||
room->addPlayer(owner);
|
||||
if (!room->isLobby())
|
||||
room->setOwner(owner);
|
||||
room->setOwner(owner);
|
||||
}
|
||||
|
||||
Room *Server::findRoom(int id) const { return rooms.value(id); }
|
||||
|
||||
Room *Server::lobby() const { return m_lobby; }
|
||||
Lobby *Server::lobby() const { return m_lobby; }
|
||||
|
||||
RoomThread *Server::createThread() {
|
||||
RoomThread *thread = new RoomThread(this);
|
||||
|
@ -234,27 +207,6 @@ void Server::sendEarlyPacket(ClientSocket *client, const QString &type, const QS
|
|||
client->send(JsonArray2Bytes(body));
|
||||
}
|
||||
|
||||
bool Server::checkClientVersion(ClientSocket *client, const QString &cver) {
|
||||
auto client_ver = QVersionNumber::fromString(cver);
|
||||
auto ver = QVersionNumber::fromString(FK_VERSION);
|
||||
int cmp = QVersionNumber::compare(ver, client_ver);
|
||||
if (cmp != 0) {
|
||||
auto errmsg = QString();
|
||||
if (cmp < 0) {
|
||||
errmsg = QString("[\"server is still on version %%2\",\"%1\"]")
|
||||
.arg(FK_VERSION, "1");
|
||||
} else {
|
||||
errmsg = QString("[\"server is using version %%2, please update\",\"%1\"]")
|
||||
.arg(FK_VERSION, "1");
|
||||
}
|
||||
|
||||
sendEarlyPacket(client, "ErrorMsg", errmsg);
|
||||
client->disconnectFromHost();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Server::setupPlayer(ServerPlayer *player, bool all_info) {
|
||||
// tell the lobby player's basic property
|
||||
QJsonArray arr;
|
||||
|
@ -291,7 +243,7 @@ void Server::processNewConnection(ClientSocket *client) {
|
|||
}
|
||||
|
||||
if (!errmsg.isEmpty()) {
|
||||
sendEarlyPacket(client, "ErrorMsg", errmsg);
|
||||
sendEarlyPacket(client, "ErrorDlg", errmsg);
|
||||
qInfo() << "Refused banned IP:" << addr;
|
||||
client->disconnectFromHost();
|
||||
return;
|
||||
|
@ -301,7 +253,7 @@ void Server::processNewConnection(ClientSocket *client) {
|
|||
[client]() { qInfo() << client->peerAddress() << "disconnected"; });
|
||||
|
||||
// network delay test
|
||||
sendEarlyPacket(client, "NetworkDelayTest", public_key);
|
||||
sendEarlyPacket(client, "NetworkDelayTest", auth->getPublicKey());
|
||||
// Note: the client should send a setup string next
|
||||
connect(client, &ClientSocket::message_got, this, &Server::processRequest);
|
||||
client->timerSignup.start(30000);
|
||||
|
@ -328,56 +280,30 @@ void Server::processRequest(const QByteArray &msg) {
|
|||
|
||||
if (!valid) {
|
||||
qWarning() << "Invalid setup string:" << msg;
|
||||
sendEarlyPacket(client, "ErrorMsg", "INVALID SETUP STRING");
|
||||
sendEarlyPacket(client, "ErrorDlg", "INVALID SETUP STRING");
|
||||
client->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray arr = String2Json(doc[3].toString()).array();
|
||||
|
||||
if (!checkClientVersion(client, arr[3].toString())) return;
|
||||
if (!auth->checkClientVersion(client, arr[3].toString())) return;
|
||||
|
||||
auto uuid = arr[4].toString();
|
||||
auto uuid_str = arr[4].toString();
|
||||
auto result2 = QJsonArray({1});
|
||||
if (CheckSqlString(uuid)) {
|
||||
if (CheckSqlString(uuid_str)) {
|
||||
result2 = SelectFromDatabase(
|
||||
db, QString("SELECT * FROM banuuid WHERE uuid='%1';").arg(uuid));
|
||||
db, QString("SELECT * FROM banuuid WHERE uuid='%1';").arg(uuid_str));
|
||||
}
|
||||
|
||||
if (!result2.isEmpty()) {
|
||||
sendEarlyPacket(client, "ErrorMsg", "you have been banned!");
|
||||
qInfo() << "Refused banned UUID:" << uuid;
|
||||
sendEarlyPacket(client, "ErrorDlg", "you have been banned!");
|
||||
qInfo() << "Refused banned UUID:" << uuid_str;
|
||||
client->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
|
||||
handleNameAndPassword(client, arr[0].toString(), arr[1].toString(),
|
||||
arr[2].toString(), uuid);
|
||||
}
|
||||
|
||||
void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
|
||||
const QString &password,
|
||||
const QString &md5_str,
|
||||
const QString &uuid_str) {
|
||||
auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
|
||||
unsigned char buf[4096] = {0};
|
||||
RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(),
|
||||
buf, rsa, RSA_PKCS1_PADDING);
|
||||
auto decrypted_pw =
|
||||
QByteArray::fromRawData((const char *)buf, strlen((const char *)buf));
|
||||
|
||||
if (decrypted_pw.length() > 32) {
|
||||
auto aes_bytes = decrypted_pw.first(32);
|
||||
|
||||
// tell client to install aes key
|
||||
sendEarlyPacket(client, "InstallKey", "");
|
||||
|
||||
client->installAESKey(aes_bytes);
|
||||
decrypted_pw.remove(0, 32);
|
||||
} else {
|
||||
decrypted_pw = "\xFF";
|
||||
}
|
||||
|
||||
auto md5_str = arr[2].toString();
|
||||
if (md5 != md5_str) {
|
||||
sendEarlyPacket(client, "ErrorMsg", "MD5 check failed!");
|
||||
sendEarlyPacket(client, "UpdatePackage", Pacman->getPackSummary());
|
||||
|
@ -385,146 +311,53 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
|
|||
return;
|
||||
}
|
||||
|
||||
bool passed = false;
|
||||
QString error_msg;
|
||||
QJsonArray result;
|
||||
QJsonObject obj;
|
||||
auto name = arr[0].toString();
|
||||
auto password = arr[1].toString();
|
||||
auto obj = auth->checkPassword(client, name, password);
|
||||
if (obj.isEmpty()) return;
|
||||
|
||||
if (CheckSqlString(name) && checkBanWord(name)) {
|
||||
// Then we check the database,
|
||||
QString sql_find = QString("SELECT * FROM userinfo \
|
||||
WHERE name='%1';")
|
||||
.arg(name);
|
||||
result = SelectFromDatabase(db, sql_find);
|
||||
if (result.isEmpty()) {
|
||||
auto salt_gen = QRandomGenerator::securelySeeded();
|
||||
auto salt = QByteArray::number(salt_gen(), 16);
|
||||
decrypted_pw.append(salt);
|
||||
auto passwordHash =
|
||||
QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256)
|
||||
.toHex();
|
||||
// not present in database, register
|
||||
QString sql_reg = QString("INSERT INTO userinfo (name,password,salt,\
|
||||
avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4','%5',%6);")
|
||||
.arg(name)
|
||||
.arg(QString(passwordHash))
|
||||
.arg(salt)
|
||||
.arg("liubei")
|
||||
.arg(client->peerAddress())
|
||||
.arg("FALSE");
|
||||
ExecSQL(db, sql_reg);
|
||||
result = SelectFromDatabase(db, sql_find); // refresh result
|
||||
obj = result[0].toObject();
|
||||
// update lastLoginIp
|
||||
int id = obj["id"].toString().toInt();
|
||||
beginTransaction();
|
||||
auto sql_update =
|
||||
QString("UPDATE userinfo SET lastLoginIp='%1' WHERE id=%2;")
|
||||
.arg(client->peerAddress())
|
||||
.arg(id);
|
||||
ExecSQL(db, sql_update);
|
||||
|
||||
auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
|
||||
ExecSQL(db, info_update);
|
||||
auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');")
|
||||
.arg(id).arg(uuid_str);
|
||||
ExecSQL(db, uuid_update);
|
||||
|
||||
passed = true;
|
||||
} else {
|
||||
obj = result[0].toObject();
|
||||
// 来晚了,有很大可能存在已经注册但是表里面没数据的人
|
||||
ExecSQL(db, QString("INSERT OR IGNORE INTO usergameinfo (id) VALUES (%1);").arg(id));
|
||||
auto info_update = QString("UPDATE usergameinfo SET lastLoginTime=%2 where id=%1;").arg(id).arg(QDateTime::currentSecsSinceEpoch());
|
||||
ExecSQL(db, info_update);
|
||||
endTransaction();
|
||||
|
||||
// check ban account
|
||||
int id = obj["id"].toString().toInt();
|
||||
passed = obj["banned"].toString().toInt() == 0;
|
||||
if (!passed) {
|
||||
error_msg = "you have been banned!";
|
||||
}
|
||||
|
||||
// check if password is the same
|
||||
auto salt = obj["salt"].toString().toLatin1();
|
||||
decrypted_pw.append(salt);
|
||||
auto passwordHash =
|
||||
QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256)
|
||||
.toHex();
|
||||
passed = (passwordHash == obj["password"].toString());
|
||||
|
||||
if (!passed) {
|
||||
error_msg = "username or password error";
|
||||
} else if (players.value(id)) {
|
||||
auto player = players.value(id);
|
||||
// 顶号机制,如果在线的话就让他变成不在线
|
||||
if (player->getState() == Player::Online) {
|
||||
player->doNotify("ErrorMsg", "others logged in again with this name");
|
||||
emit player->kicked();
|
||||
}
|
||||
|
||||
if (player->getState() == Player::Offline) {
|
||||
auto room = player->getRoom();
|
||||
player->setSocket(client);
|
||||
player->alive = true;
|
||||
client->disconnect(this);
|
||||
if (players.count() <= 10) {
|
||||
broadcast("ServerMessage", tr("%1 backed").arg(player->getScreenName()));
|
||||
}
|
||||
|
||||
if (room && !room->isLobby()) {
|
||||
setupPlayer(player, true);
|
||||
room->pushRequest(QString("%1,reconnect").arg(id));
|
||||
} else {
|
||||
// 懒得处理掉线玩家在大厅了!踢掉得了
|
||||
player->doNotify("ErrorMsg", "Unknown Error");
|
||||
emit player->kicked();
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
error_msg = "others logged in with this name";
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_msg = "invalid user name";
|
||||
// create new ServerPlayer and setup
|
||||
ServerPlayer *player = new ServerPlayer(lobby());
|
||||
player->setSocket(client);
|
||||
client->disconnect(this);
|
||||
connect(player, &ServerPlayer::disconnected, this,
|
||||
&Server::onUserDisconnected);
|
||||
connect(player, &Player::stateChanged, this, &Server::onUserStateChanged);
|
||||
player->setScreenName(name);
|
||||
player->setAvatar(obj["avatar"].toString());
|
||||
player->setId(id);
|
||||
if (players.count() <= 10) {
|
||||
broadcast("ServerMessage", tr("%1 logged in").arg(player->getScreenName()));
|
||||
}
|
||||
players.insert(player->getId(), player);
|
||||
|
||||
if (passed) {
|
||||
// update lastLoginIp
|
||||
int id = obj["id"].toString().toInt();
|
||||
beginTransaction();
|
||||
auto sql_update =
|
||||
QString("UPDATE userinfo SET lastLoginIp='%1' WHERE id=%2;")
|
||||
.arg(client->peerAddress())
|
||||
.arg(id);
|
||||
ExecSQL(db, sql_update);
|
||||
setupPlayer(player);
|
||||
|
||||
auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');").arg(id).arg(uuid_str);
|
||||
ExecSQL(db, uuid_update);
|
||||
auto result = SelectFromDatabase(db, QString("SELECT totalGameTime FROM usergameinfo WHERE id=%1;").arg(id));
|
||||
auto time = result[0].toObject()["totalGameTime"].toString().toInt();
|
||||
player->addTotalGameTime(time);
|
||||
player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
|
||||
|
||||
// 来晚了,有很大可能存在已经注册但是表里面没数据的人
|
||||
ExecSQL(db, QString("INSERT OR IGNORE INTO usergameinfo (id) VALUES (%1);").arg(id));
|
||||
auto info_update = QString("UPDATE usergameinfo SET lastLoginTime=%2 where id=%1;").arg(id).arg(QDateTime::currentSecsSinceEpoch());
|
||||
ExecSQL(db, info_update);
|
||||
endTransaction();
|
||||
|
||||
// create new ServerPlayer and setup
|
||||
ServerPlayer *player = new ServerPlayer(lobby());
|
||||
player->setSocket(client);
|
||||
client->disconnect(this);
|
||||
connect(player, &ServerPlayer::disconnected, this,
|
||||
&Server::onUserDisconnected);
|
||||
connect(player, &Player::stateChanged, this, &Server::onUserStateChanged);
|
||||
player->setScreenName(name);
|
||||
player->setAvatar(obj["avatar"].toString());
|
||||
player->setId(id);
|
||||
if (players.count() <= 10) {
|
||||
broadcast("ServerMessage", tr("%1 logged in").arg(player->getScreenName()));
|
||||
}
|
||||
players.insert(player->getId(), player);
|
||||
|
||||
setupPlayer(player);
|
||||
|
||||
auto result = SelectFromDatabase(db, QString("SELECT totalGameTime FROM usergameinfo WHERE id=%1;").arg(id));
|
||||
auto time = result[0].toObject()["totalGameTime"].toString().toInt();
|
||||
player->addTotalGameTime(time);
|
||||
player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
|
||||
|
||||
lobby()->addPlayer(player);
|
||||
} else {
|
||||
qInfo() << client->peerAddress() << "lost connection:" << error_msg;
|
||||
sendEarlyPacket(client, "ErrorMsg", error_msg);
|
||||
client->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
lobby()->addPlayer(player);
|
||||
}
|
||||
|
||||
void Server::onRoomAbandoned() {
|
||||
|
@ -537,37 +370,49 @@ void Server::onRoomAbandoned() {
|
|||
// FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。
|
||||
// room->deleteLater();
|
||||
idle_rooms.push(room);
|
||||
room->getThread()->wakeUp(room->getId());
|
||||
room->getThread()->removeRoom(room);
|
||||
}
|
||||
|
||||
void Server::onUserDisconnected() {
|
||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
|
||||
auto player = qobject_cast<ServerPlayer *>(sender());
|
||||
qInfo() << "Player" << player->getId() << "disconnected";
|
||||
if (players.count() <= 10) {
|
||||
broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName()));
|
||||
}
|
||||
Room *room = player->getRoom();
|
||||
if (room->isStarted()) {
|
||||
if (room->getObservers().contains(player)) {
|
||||
room->removeObserver(player);
|
||||
player->deleteLater();
|
||||
return;
|
||||
}
|
||||
player->setState(Player::Offline);
|
||||
player->setSocket(nullptr);
|
||||
// TODO: add a robot
|
||||
} else {
|
||||
|
||||
auto _room = player->getRoom();
|
||||
if (_room->isLobby()) {
|
||||
player->setState(Player::Robot); // 大厅!然而又不能设Offline
|
||||
player->deleteLater();
|
||||
} else {
|
||||
auto room = qobject_cast<Room *>(_room);
|
||||
if (room->isStarted()) {
|
||||
if (room->getObservers().contains(player)) {
|
||||
room->removeObserver(player);
|
||||
player->deleteLater();
|
||||
return;
|
||||
}
|
||||
player->setState(Player::Offline);
|
||||
player->setSocket(nullptr);
|
||||
// TODO: add a robot
|
||||
} else {
|
||||
player->setState(Player::Robot); // 大厅!然而又不能设Offline
|
||||
// 这里有一个多线程问题,可能与Room::gameOver同时deleteLater导致出事
|
||||
// FIXME: 这种解法肯定不安全
|
||||
if (!room->insideGameOver)
|
||||
player->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::onUserStateChanged() {
|
||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
|
||||
auto room = player->getRoom();
|
||||
if (!room || room->isLobby() || room->isAbandoned()) {
|
||||
return;
|
||||
}
|
||||
auto _room = player->getRoom();
|
||||
if (!_room || _room->isLobby()) return;
|
||||
auto room = qobject_cast<Room *>(_room);
|
||||
if (room->isAbandoned()) return;
|
||||
|
||||
auto state = player->getState();
|
||||
room->doBroadcastNotify(room->getPlayers(), "NetStateChanged",
|
||||
QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString()));
|
||||
|
@ -579,33 +424,6 @@ void Server::onUserStateChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
RSA *Server::initServerRSA() {
|
||||
RSA *rsa = RSA_new();
|
||||
if (!QFile::exists("server/rsa_pub")) {
|
||||
BIGNUM *bne = BN_new();
|
||||
BN_set_word(bne, RSA_F4);
|
||||
RSA_generate_key_ex(rsa, 2048, bne, NULL);
|
||||
|
||||
BIO *bp_pub = BIO_new_file("server/rsa_pub", "w+");
|
||||
PEM_write_bio_RSAPublicKey(bp_pub, rsa);
|
||||
BIO *bp_pri = BIO_new_file("server/rsa", "w+");
|
||||
PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL);
|
||||
|
||||
BIO_free_all(bp_pub);
|
||||
BIO_free_all(bp_pri);
|
||||
QFile("server/rsa")
|
||||
.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||
BN_free(bne);
|
||||
}
|
||||
FILE *keyFile = fopen("server/rsa_pub", "r");
|
||||
PEM_read_RSAPublicKey(keyFile, &rsa, NULL, NULL);
|
||||
fclose(keyFile);
|
||||
keyFile = fopen("server/rsa", "r");
|
||||
PEM_read_RSAPrivateKey(keyFile, &rsa, NULL, NULL);
|
||||
fclose(keyFile);
|
||||
return rsa;
|
||||
}
|
||||
|
||||
#define SET_DEFAULT_CONFIG(k, v) do {\
|
||||
if (config.value(k).isUndefined()) { \
|
||||
config[k] = (v); \
|
||||
|
@ -705,28 +523,3 @@ void Server::refreshMd5() {
|
|||
emit p->kicked();
|
||||
}
|
||||
}
|
||||
|
||||
void Server::readPendingDatagrams() {
|
||||
while (udpSocket->hasPendingDatagrams()) {
|
||||
QNetworkDatagram datagram = udpSocket->receiveDatagram();
|
||||
if (datagram.isValid()) {
|
||||
processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
|
||||
if (msg == "fkDetectServer") {
|
||||
udpSocket->writeDatagram("me", addr, port);
|
||||
} else if (msg.startsWith("fkGetDetail,")) {
|
||||
udpSocket->writeDatagram(JsonArray2Bytes(QJsonArray({
|
||||
FK_VERSION,
|
||||
getConfig("iconUrl"),
|
||||
getConfig("description"),
|
||||
getConfig("capacity"),
|
||||
players.count(),
|
||||
msg.sliced(12).constData(),
|
||||
})), addr, port);
|
||||
}
|
||||
udpSocket->flush();
|
||||
}
|
||||
|
|
|
@ -3,17 +3,14 @@
|
|||
#ifndef _SERVER_H
|
||||
#define _SERVER_H
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonvalue.h>
|
||||
class AuthManager;
|
||||
class ServerSocket;
|
||||
class ClientSocket;
|
||||
class ServerPlayer;
|
||||
class RoomThread;
|
||||
class Lobby;
|
||||
|
||||
#include "room.h"
|
||||
#include "server/room.h"
|
||||
|
||||
class Server : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -29,7 +26,7 @@ public:
|
|||
int timeout = 15, const QByteArray &settings = "{}");
|
||||
|
||||
Room *findRoom(int id) const;
|
||||
Room *lobby() const;
|
||||
Lobby *lobby() const;
|
||||
|
||||
RoomThread *createThread();
|
||||
void removeThread(RoomThread *thread);
|
||||
|
@ -37,6 +34,7 @@ public:
|
|||
ServerPlayer *findPlayer(int id) const;
|
||||
void addPlayer(ServerPlayer *player);
|
||||
void removePlayer(int id);
|
||||
auto getPlayers() { return players; }
|
||||
|
||||
void updateRoomList(ServerPlayer *teller);
|
||||
void updateOnlineInfo();
|
||||
|
@ -44,6 +42,8 @@ public:
|
|||
sqlite3 *getDatabase();
|
||||
|
||||
void broadcast(const QString &command, const QString &jsonData);
|
||||
void sendEarlyPacket(ClientSocket *client, const QString &type, const QString &msg);
|
||||
void setupPlayer(ServerPlayer *player, bool all_info = true);
|
||||
bool isListening;
|
||||
|
||||
QJsonValue getConfig(const QString &command);
|
||||
|
@ -64,7 +64,6 @@ signals:
|
|||
public slots:
|
||||
void processNewConnection(ClientSocket *client);
|
||||
void processRequest(const QByteArray &msg);
|
||||
void readPendingDatagrams();
|
||||
|
||||
void onRoomAbandoned();
|
||||
void onUserDisconnected();
|
||||
|
@ -73,9 +72,8 @@ public slots:
|
|||
private:
|
||||
friend class Shell;
|
||||
ServerSocket *server;
|
||||
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
|
||||
|
||||
Room *m_lobby;
|
||||
Lobby *m_lobby;
|
||||
QMap<int, Room *> rooms;
|
||||
QStack<Room *> idle_rooms;
|
||||
QList<RoomThread *> threads;
|
||||
|
@ -84,26 +82,13 @@ private:
|
|||
QHash<int, ServerPlayer *> players;
|
||||
QList<QString> temp_banlist;
|
||||
|
||||
RSA *rsa;
|
||||
QString public_key;
|
||||
AuthManager *auth;
|
||||
sqlite3 *db;
|
||||
QMutex transaction_mutex;
|
||||
QString md5;
|
||||
|
||||
static RSA *initServerRSA();
|
||||
|
||||
QJsonObject config;
|
||||
void readConfig();
|
||||
|
||||
// 用于确定建立连接之前与客户端通信,连接后用doNotify
|
||||
void sendEarlyPacket(ClientSocket *client, const QString &type, const QString &msg);
|
||||
bool checkClientVersion(ClientSocket *client, const QString &ver);
|
||||
|
||||
// 某玩家刚刚连入之后,服务器告诉他关于他的一些基本信息
|
||||
void setupPlayer(ServerPlayer *player, bool all_info = true);
|
||||
void handleNameAndPassword(ClientSocket *client, const QString &name,
|
||||
const QString &password, const QString &md5_str, const QString &uuid_str);
|
||||
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
|
||||
};
|
||||
|
||||
extern Server *ServerInstance;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "serverplayer.h"
|
||||
#include "client_socket.h"
|
||||
#include "room.h"
|
||||
#include "roomthread.h"
|
||||
#include "router.h"
|
||||
#include "server.h"
|
||||
#include "server/serverplayer.h"
|
||||
#include "network/client_socket.h"
|
||||
#include "server/room.h"
|
||||
#include "server/roomthread.h"
|
||||
#include "network/router.h"
|
||||
#include "server/server.h"
|
||||
|
||||
ServerPlayer::ServerPlayer(Room *room) {
|
||||
ServerPlayer::ServerPlayer(RoomBase *room) {
|
||||
socket = nullptr;
|
||||
router = new Router(this, socket, Router::TYPE_SERVER);
|
||||
setState(Player::Online);
|
||||
|
@ -61,9 +61,9 @@ void ServerPlayer::removeSocket() {
|
|||
|
||||
Server *ServerPlayer::getServer() const { return server; }
|
||||
|
||||
Room *ServerPlayer::getRoom() const { return room; }
|
||||
RoomBase *ServerPlayer::getRoom() const { return room; }
|
||||
|
||||
void ServerPlayer::setRoom(Room *room) { this->room = room; }
|
||||
void ServerPlayer::setRoom(RoomBase *room) { this->room = room; }
|
||||
|
||||
void ServerPlayer::speak(const QString &message) { ; }
|
||||
|
||||
|
@ -112,6 +112,24 @@ void ServerPlayer::kick() {
|
|||
setSocket(nullptr);
|
||||
}
|
||||
|
||||
void ServerPlayer::reconnect(ClientSocket *client) {
|
||||
setSocket(client);
|
||||
alive = true;
|
||||
client->disconnect(this);
|
||||
if (server->getPlayers().count() <= 10) {
|
||||
server->broadcast("ServerMessage", tr("%1 backed").arg(getScreenName()));
|
||||
}
|
||||
|
||||
if (room && !room->isLobby()) {
|
||||
server->setupPlayer(this, true);
|
||||
qobject_cast<Room *>(room)->pushRequest(QString("%1,reconnect").arg(getId()));
|
||||
} else {
|
||||
// 懒得处理掉线玩家在大厅了!踢掉得了
|
||||
doNotify("ErrorMsg", "Unknown Error");
|
||||
emit kicked();
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerPlayer::thinking() {
|
||||
m_thinking_mutex.lock();
|
||||
bool ret = m_thinking;
|
||||
|
|
|
@ -3,17 +3,18 @@
|
|||
#ifndef _SERVERPLAYER_H
|
||||
#define _SERVERPLAYER_H
|
||||
|
||||
#include "player.h"
|
||||
#include "core/player.h"
|
||||
|
||||
class ClientSocket;
|
||||
class Router;
|
||||
class Server;
|
||||
class Room;
|
||||
class RoomBase;
|
||||
|
||||
class ServerPlayer : public Player {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ServerPlayer(Room *room);
|
||||
explicit ServerPlayer(RoomBase *room);
|
||||
~ServerPlayer();
|
||||
|
||||
void setSocket(ClientSocket *socket);
|
||||
|
@ -21,8 +22,8 @@ public:
|
|||
ClientSocket *getSocket() const;
|
||||
|
||||
Server *getServer() const;
|
||||
Room *getRoom() const;
|
||||
void setRoom(Room *room);
|
||||
RoomBase *getRoom() const;
|
||||
void setRoom(RoomBase *room);
|
||||
|
||||
void speak(const QString &message);
|
||||
|
||||
|
@ -37,6 +38,7 @@ public:
|
|||
|
||||
volatile bool alive; // For heartbeat
|
||||
void kick();
|
||||
void reconnect(ClientSocket *socket);
|
||||
|
||||
bool busy() const { return m_busy; }
|
||||
void setBusy(bool busy) { m_busy = busy; }
|
||||
|
@ -57,7 +59,7 @@ private:
|
|||
ClientSocket *socket; // socket for communicating with client
|
||||
Router *router;
|
||||
Server *server;
|
||||
Room *room; // Room that player is in, maybe lobby
|
||||
RoomBase *room; // Room that player is in, maybe lobby
|
||||
bool m_busy; // (Lua专用) 是否有doRequest没处理完?见于神貂蝉这种一控多的
|
||||
bool m_thinking; // 是否在烧条?
|
||||
QMutex m_thinking_mutex; // 注意setBusy只在Lua使用,所以不需要锁。
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include "shell.h"
|
||||
#include "packman.h"
|
||||
#include "server.h"
|
||||
#include "serverplayer.h"
|
||||
#include "util.h"
|
||||
#include "server/shell.h"
|
||||
#include "core/packman.h"
|
||||
#include "server/server.h"
|
||||
#include "server/serverplayer.h"
|
||||
#include "core/util.h"
|
||||
#include <readline/history.h>
|
||||
#include <readline/readline.h>
|
||||
#include <signal.h>
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
%module fk
|
||||
|
||||
%{
|
||||
#include "server.h"
|
||||
#include "serverplayer.h"
|
||||
#include "clientplayer.h"
|
||||
#include "room.h"
|
||||
#include "roomthread.h"
|
||||
#include "util.h"
|
||||
#include "qmlbackend.h"
|
||||
#include "server/server.h"
|
||||
#include "server/serverplayer.h"
|
||||
#include "client/clientplayer.h"
|
||||
#include "server/room.h"
|
||||
#include "server/roomthread.h"
|
||||
#include "core/util.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
class ClientPlayer *Self = nullptr;
|
||||
%}
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
%module fk
|
||||
|
||||
%{
|
||||
#include "client.h"
|
||||
#include "server.h"
|
||||
#include "serverplayer.h"
|
||||
#include "clientplayer.h"
|
||||
#include "room.h"
|
||||
#include "roomthread.h"
|
||||
#include "qmlbackend.h"
|
||||
#include "util.h"
|
||||
#include "client/client.h"
|
||||
#include "server/server.h"
|
||||
#include "server/serverplayer.h"
|
||||
#include "client/clientplayer.h"
|
||||
#include "server/room.h"
|
||||
#include "server/roomthread.h"
|
||||
#include "ui/qmlbackend.h"
|
||||
#include "core/util.h"
|
||||
|
||||
const char *FK_VER = FK_VERSION;
|
||||
%}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// ------------------------------------------------------
|
||||
|
||||
%{
|
||||
#include <qmlbackend.h>
|
||||
#include "ui/qmlbackend.h"
|
||||
%}
|
||||
|
||||
// Lua 5.4 特有的不能pushnumber, swig迟迟不更只好手动调教
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
%nodefaultctor Server;
|
||||
%nodefaultdtor Server;
|
||||
class Server : public QObject {
|
||||
public:
|
||||
void beginTransaction();
|
||||
void endTransaction();
|
||||
};
|
||||
extern Server *ServerInstance;
|
||||
|
||||
%nodefaultctor Room;
|
||||
%nodefaultdtor Room;
|
||||
class Room : public QObject {
|
||||
|
@ -14,11 +23,14 @@ public:
|
|||
QList<ServerPlayer *> getObservers() const;
|
||||
bool hasObserver(ServerPlayer *player) const;
|
||||
int getTimeout() const;
|
||||
void delay(int ms);
|
||||
void checkAbandoned();
|
||||
|
||||
void updateWinRate(int id, const QString &general, const QString &mode,
|
||||
int result, bool dead);
|
||||
void gameOver();
|
||||
void setRequestTimer(int ms);
|
||||
void destroyRequestTimer();
|
||||
};
|
||||
|
||||
%extend Room {
|
||||
|
@ -33,25 +45,26 @@ class RoomThread : public QThread {
|
|||
public:
|
||||
Room *getRoom(int id);
|
||||
|
||||
QString fetchRequest();
|
||||
void clearRequest();
|
||||
bool hasRequest();
|
||||
// QString fetchRequest();
|
||||
// void clearRequest();
|
||||
// bool hasRequest();
|
||||
|
||||
void trySleep(int ms);
|
||||
bool isTerminated() const;
|
||||
// void trySleep(int ms);
|
||||
// bool isTerminated() const;
|
||||
|
||||
bool isConsoleStart() const;
|
||||
bool isOutdated();
|
||||
};
|
||||
|
||||
%{
|
||||
void RoomThread::run()
|
||||
#include "server/scheduler.h"
|
||||
void Scheduler::tellThreadToLua()
|
||||
{
|
||||
lua_getglobal(L, "debug");
|
||||
lua_getfield(L, -1, "traceback");
|
||||
lua_replace(L, -2);
|
||||
lua_getglobal(L, "InitScheduler");
|
||||
SWIG_NewPointerObj(L, this, SWIGTYPE_p_RoomThread, 0);
|
||||
SWIG_NewPointerObj(L, m_thread, SWIGTYPE_p_RoomThread, 0);
|
||||
int error = lua_pcall(L, 1, 0, -2);
|
||||
lua_pop(L, 1);
|
||||
if (error) {
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qmlbackend.h"
|
||||
#include <lua.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qvariant.h>
|
||||
#include "ui/qmlbackend.h"
|
||||
|
||||
#ifndef FK_SERVER_ONLY
|
||||
#include <qaudiooutput.h>
|
||||
#include <qmediaplayer.h>
|
||||
#include <qrandom.h>
|
||||
#include <QAudioOutput>
|
||||
#include <QNetworkDatagram>
|
||||
#include <QDnsLookup>
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QMediaPlayer>
|
||||
#include "mod.h"
|
||||
#include <QMessageBox>
|
||||
// #include "mod.h"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include "server.h"
|
||||
#include "client.h"
|
||||
#include "util.h"
|
||||
#include "replayer.h"
|
||||
#include "server/server.h"
|
||||
#include "client/client.h"
|
||||
#include "core/util.h"
|
||||
#include "client/replayer.h"
|
||||
|
||||
QmlBackend *Backend = nullptr;
|
||||
|
||||
|
@ -35,6 +31,7 @@ QmlBackend::QmlBackend(QObject *parent) : QObject(parent) {
|
|||
udpSocket->bind(0);
|
||||
connect(udpSocket, &QUdpSocket::readyRead,
|
||||
this, &QmlBackend::readPendingDatagrams);
|
||||
connect(this, &QmlBackend::dialog, this, &QmlBackend::showDialog);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -464,7 +461,7 @@ void QmlBackend::installAESKey() {
|
|||
}
|
||||
|
||||
void QmlBackend::createModBackend() {
|
||||
engine->rootContext()->setContextProperty("ModBackend", new ModMaker);
|
||||
//engine->rootContext()->setContextProperty("ModBackend", new ModMaker);
|
||||
}
|
||||
|
||||
|
||||
|
@ -549,6 +546,30 @@ void QmlBackend::readPendingDatagrams() {
|
|||
}
|
||||
}
|
||||
|
||||
void QmlBackend::showDialog(const QString &type, const QString &text, const QString &orig) {
|
||||
static const QString title = tr("FreeKill") + " v" + FK_VERSION;
|
||||
QMessageBox *box = nullptr;
|
||||
if (type == "critical") {
|
||||
box = new QMessageBox(QMessageBox::Critical, title, text, QMessageBox::Ok);
|
||||
connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
|
||||
} else if (type == "info") {
|
||||
box = new QMessageBox(QMessageBox::Information, title, text, QMessageBox::Ok);
|
||||
connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
|
||||
} else if (type == "warning") {
|
||||
box = new QMessageBox(QMessageBox::Warning, title, text, QMessageBox::Ok);
|
||||
connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
if (box) {
|
||||
if (!orig.isEmpty()) {
|
||||
auto bytes = orig.toLocal8Bit().prepend("help: ");
|
||||
if (tr(bytes) != bytes) box->setInformativeText(tr(bytes));
|
||||
}
|
||||
box->setWindowModality(Qt::NonModal);
|
||||
box->show();
|
||||
}
|
||||
}
|
||||
|
||||
void QmlBackend::removeRecord(const QString &fname) {
|
||||
QFile::remove("recording/" + fname);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ public:
|
|||
Q_INVOKABLE void detectServer();
|
||||
Q_INVOKABLE void getServerInfo(const QString &addr);
|
||||
|
||||
Q_INVOKABLE void showDialog(const QString &type, const QString &text,
|
||||
const QString &orig = QString());
|
||||
|
||||
qreal volume() const { return m_volume; }
|
||||
void setVolume(qreal v) { m_volume = v; }
|
||||
|
||||
|
@ -77,6 +80,7 @@ public:
|
|||
|
||||
signals:
|
||||
void notifyUI(const QString &command, const QVariant &data);
|
||||
void dialog(const QString &type, const QString &text, const QString &orig = QString());
|
||||
void volumeChanged(qreal);
|
||||
void replayerToggle();
|
||||
void replayerSpeedUp();
|
||||
|
|
Loading…
Reference in New Issue
Block a user