Merge branch 'master' into dev-bugfix

This commit is contained in:
notify 2024-06-10 15:02:37 +08:00 committed by GitHub
commit 1bfcd0a0cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 1711 additions and 1063 deletions

View File

@ -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
View File

@ -15,6 +15,7 @@
/*.kdev4
/.cache/
/tags
/.luarc.json
# file produced by game
/FreeKill

View File

@ -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)

View File

@ -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>: ";

View File

@ -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();
}
}

View File

@ -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: ""

View File

@ -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
}

View File

@ -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) => {

View File

@ -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 } );
}
}
}

View File

@ -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) {

View File

@ -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"));
}

View File

@ -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

View File

@ -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);
}

View File

@ -32,7 +32,7 @@ Item {
id: menu
y: bar.height
MenuItem {
text: qsTr("Replay from file")
text: luatr("Replay from File")
onTriggered: {
fdialog.open();
}

View File

@ -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() {

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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();
}
}
}
}

View File

@ -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];
});

View File

@ -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++
}
}
}
}
}

View File

@ -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 {} }

View File

@ -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
View 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 -

View File

@ -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
View 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
}

View File

@ -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 ()

View File

@ -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;

View File

@ -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 {

View File

@ -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);

View File

@ -3,7 +3,7 @@
#ifndef _CLIENTPLAYER_H
#define _CLIENTPLAYER_H
#include "player.h"
#include "core/player.h"
class ClientPlayer : public Player {
Q_OBJECT

View File

@ -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(""),

View File

@ -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;

View File

@ -3,8 +3,6 @@
#ifndef _PACKMAN_H
#define _PACKMAN_H
#include <qtmetamacros.h>
// 管理拓展包所需的类本质上是libgit2接口的再封装。
class PackMan : public QObject {
Q_OBJECT

View File

@ -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),

View File

@ -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();

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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)

View File

@ -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();
}

View File

@ -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
View 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
View 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
View 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
View 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

View File

@ -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;
}

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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();
}

View File

@ -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;

View File

@ -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;

View File

@ -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使用所以不需要锁。

View File

@ -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>

View File

@ -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;
%}

View File

@ -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;
%}

View File

@ -5,7 +5,7 @@
// ------------------------------------------------------
%{
#include <qmlbackend.h>
#include "ui/qmlbackend.h"
%}
// Lua 5.4 特有的不能pushnumber swig迟迟不更只好手动调教

View File

@ -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) {

View File

@ -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);
}

View File

@ -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();