- 扣上限心碎
- 进服维护的各种跟后端稳定性有关的代码
- 断线重连/旁观时候计入技能次数
- ban人和banip,相应的也有解禁
- 开房设置现在可以滑动
- 完善网络错误报错
- 现在开始游戏之前需要等待和所有人准备
- 指示掉线之人和走小道之人
- 掉线和走小道的人不再被AI接管
- 延时锦囊牌素材从拓展包找
- 拓展包管理界面UI优化,下载失败的包可以在管理拓展包中删除
This commit is contained in:
notify 2023-06-04 19:31:44 +08:00 committed by GitHub
parent 5a30c69085
commit 9519d1b9a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 961 additions and 408 deletions

2
.gitignore vendored
View File

@ -24,6 +24,8 @@ freekill-wrap.cxx
/server/rsa_pub /server/rsa_pub
/freekill.client.config.json /freekill.client.config.json
/freekill.server.config.json /freekill.server.config.json
/freekill.server.error.log
/freekill.server.info.log
/flist.txt /flist.txt
# windeployqt # windeployqt

View File

@ -43,12 +43,22 @@ Flickable {
Button { Button {
text: Backend.translate("Give Shoe") text: Backend.translate("Give Shoe")
enabled: Math.random() < 0.5 enabled: Math.random() < 0.3
onClicked: { onClicked: {
root.givePresent("Shoe"); root.givePresent("Shoe");
root.finish(); root.finish();
} }
} }
Button {
text: Backend.translate("Kick From Room")
visible: !roomScene.isStarted && roomScene.isOwner
enabled: pid !== Self.id
onClicked: {
ClientInstance.notifyServer("KickPlayer", pid.toString());
root.finish();
}
}
} }
// TODO: player details // TODO: player details

View File

@ -4,194 +4,201 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
ColumnLayout { Flickable {
RowLayout { flickableDirection: Flickable.AutoFlickIfNeeded
anchors.rightMargin: 8 clip: true
spacing: 16 contentHeight: layout.height
Text {
text: Backend.translate("Room Name")
}
TextField {
id: roomName
maximumLength: 64
font.pixelSize: 18
text: Backend.translate("$RoomName").arg(Self.screenName)
}
}
RowLayout { ColumnLayout {
anchors.rightMargin: 8 id: layout
spacing: 16 RowLayout {
Text { anchors.rightMargin: 8
text: Backend.translate("Player num") spacing: 16
} Text {
SpinBox { text: Backend.translate("Room Name")
id: playerNum }
from: 2 TextField {
to: 8 id: roomName
value: config.preferedPlayerNum maximumLength: 64
font.pixelSize: 18
onValueChanged: { text: Backend.translate("$RoomName").arg(Self.screenName)
config.preferedPlayerNum = value;
} }
} }
}
RowLayout { RowLayout {
anchors.rightMargin: 8 anchors.rightMargin: 8
spacing: 16 spacing: 16
Text { Text {
text: Backend.translate("Game Mode") text: Backend.translate("Player num")
}
ComboBox {
id: gameModeCombo
textRole: "name"
model: ListModel {
id: gameModeList
} }
SpinBox {
id: playerNum
from: 2
to: 8
value: config.preferedPlayerNum
onCurrentIndexChanged: { onValueChanged: {
let data = gameModeList.get(currentIndex); config.preferedPlayerNum = value;
playerNum.from = data.minPlayer; }
playerNum.to = data.maxPlayer;
config.preferedMode = data.orig_name;
} }
} }
}
RowLayout { RowLayout {
anchors.rightMargin: 8 anchors.rightMargin: 8
spacing: 16 spacing: 16
Text { Text {
text: Backend.translate("Select general num") text: Backend.translate("Game Mode")
}
SpinBox {
id: generalNum
from: 3
to: 18
value: config.preferredGeneralNum
onValueChanged: {
config.preferredGeneralNum = value;
} }
} ComboBox {
} id: gameModeCombo
textRole: "name"
RowLayout { model: ListModel {
anchors.rightMargin: 8 id: gameModeList
spacing: 16
Text {
text: Backend.translate("Operation timeout")
}
SpinBox {
from: 10
to: 60
editable: true
value: config.preferredTimeout
onValueChanged: {
config.preferredTimeout = value;
}
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Luck Card Times")
}
SpinBox {
from: 0
to: 8
value: config.preferredLuckTime
onValueChanged: {
config.preferredLuckTime = value;
}
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Room Password")
}
TextField {
id: roomPassword
maximumLength: 16
font.pixelSize: 18
}
}
Switch {
id: freeAssignCheck
checked: Debugging ? true : false
text: Backend.translate("Enable free assign")
}
Switch {
id: deputyCheck
checked: Debugging ? true : false
text: Backend.translate("Enable deputy general")
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Button {
text: Backend.translate("OK")
onClicked: {
root.finished();
mainWindow.busy = true;
let disabledGenerals = config.disabledGenerals.slice();
if (disabledGenerals.length) {
const availablePack = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", [])).
filter((pack) => !config.disabledPack.includes(pack));
disabledGenerals = disabledGenerals.filter((general) => {
return availablePack.find((pack) => JSON.parse(Backend.callLuaFunction("GetGenerals", [pack])).includes(general));
});
disabledGenerals = Array.from(new Set(disabledGenerals));
} }
ClientInstance.notifyServer( onCurrentIndexChanged: {
"CreateRoom", let data = gameModeList.get(currentIndex);
JSON.stringify([roomName.text, playerNum.value, config.preferredTimeout, { playerNum.from = data.minPlayer;
enableFreeAssign: freeAssignCheck.checked, playerNum.to = data.maxPlayer;
enableDeputy: deputyCheck.checked,
gameMode: config.preferedMode,
disabledPack: config.disabledPack,
generalNum: config.preferredGeneralNum,
luckTime: config.preferredLuckTime,
password: roomPassword.text,
disabledGenerals,
}])
);
}
}
Button {
text: Backend.translate("Cancel")
onClicked: {
root.finished();
}
}
}
Component.onCompleted: { config.preferedMode = data.orig_name;
let mode_data = JSON.parse(Backend.callLuaFunction("GetGameModes", [])); }
let i = 0;
for (let d of mode_data) {
gameModeList.append(d);
if (d.orig_name == config.preferedMode) {
gameModeCombo.currentIndex = i;
} }
i += 1;
} }
playerNum.value = config.preferedPlayerNum; RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Select general num")
}
SpinBox {
id: generalNum
from: 3
to: 18
value: config.preferredGeneralNum
onValueChanged: {
config.preferredGeneralNum = value;
}
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Operation timeout")
}
SpinBox {
from: 10
to: 60
editable: true
value: config.preferredTimeout
onValueChanged: {
config.preferredTimeout = value;
}
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Luck Card Times")
}
SpinBox {
from: 0
to: 8
value: config.preferredLuckTime
onValueChanged: {
config.preferredLuckTime = value;
}
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Room Password")
}
TextField {
id: roomPassword
maximumLength: 16
font.pixelSize: 18
}
}
Switch {
id: freeAssignCheck
checked: Debugging ? true : false
text: Backend.translate("Enable free assign")
}
Switch {
id: deputyCheck
checked: Debugging ? true : false
text: Backend.translate("Enable deputy general")
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Button {
text: Backend.translate("OK")
onClicked: {
root.finished();
mainWindow.busy = true;
let disabledGenerals = config.disabledGenerals.slice();
if (disabledGenerals.length) {
const availablePack = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", [])).
filter((pack) => !config.disabledPack.includes(pack));
disabledGenerals = disabledGenerals.filter((general) => {
return availablePack.find((pack) => JSON.parse(Backend.callLuaFunction("GetGenerals", [pack])).includes(general));
});
disabledGenerals = Array.from(new Set(disabledGenerals));
}
ClientInstance.notifyServer(
"CreateRoom",
JSON.stringify([roomName.text, playerNum.value, config.preferredTimeout, {
enableFreeAssign: freeAssignCheck.checked,
enableDeputy: deputyCheck.checked,
gameMode: config.preferedMode,
disabledPack: config.disabledPack,
generalNum: config.preferredGeneralNum,
luckTime: config.preferredLuckTime,
password: roomPassword.text,
disabledGenerals,
}])
);
}
}
Button {
text: Backend.translate("Cancel")
onClicked: {
root.finished();
}
}
}
Component.onCompleted: {
let mode_data = JSON.parse(Backend.callLuaFunction("GetGameModes", []));
let i = 0;
for (let d of mode_data) {
gameModeList.append(d);
if (d.orig_name == config.preferedMode) {
gameModeCombo.currentIndex = i;
}
i += 1;
}
playerNum.value = config.preferedPlayerNum;
}
} }
} }

View File

@ -6,195 +6,170 @@ import QtQuick.Layouts
Item { Item {
id: root id: root
Button {
text: qsTr("Quit")
anchors.right: parent.right
onClicked: {
mainStack.pop();
}
}
Component { ToolBar {
id: packageDelegate id: bar
width: parent.width
Item { RowLayout {
height: 22 anchors.fill: parent
width: packageList.width ToolButton {
icon.source: AppPath + "/image/modmaker/back"
RowLayout { onClicked: mainStack.pop();
anchors.fill: parent
spacing: 16
Text {
font.pixelSize: 20
text: pkgName
}
Text {
font.pixelSize: 20
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: pkgURL
}
Text {
font.pixelSize: 20
text: pkgVersion
}
Text {
font.pixelSize: 20
color: pkgEnabled === "1" ? "green" : "red"
text: pkgEnabled === "1" ? qsTr("Enabled") : qsTr("Disabled")
}
} }
Label {
text: qsTr("Package Manager")
horizontalAlignment: Qt.AlignHCenter
Layout.fillWidth: true
}
ToolButton {
icon.source: AppPath + "/image/modmaker/menu"
onClicked: menu.open()
TapHandler { Menu {
onTapped: { id: menu
if (packageList.currentIndex === index) { y: bar.height
packageList.currentIndex = -1;
} else { MenuItem {
packageList.currentIndex = index; text: qsTr("Enable All")
onTriggered: {
for (let i = 0; i < packageModel.count; i++) {
let name = packageModel.get(i).pkgName;
Pacman.enablePack(name);
}
updatePackageList();
}
}
MenuItem {
text: qsTr("Disable All")
onTriggered: {
for (let i = 0; i < packageModel.count; i++) {
let name = packageModel.get(i).pkgName;
Pacman.disablePack(name);
}
updatePackageList();
}
}
MenuItem {
text: qsTr("Upgrade All")
onTriggered: {
for (let i = 0; i < packageModel.count; i++) {
let name = packageModel.get(i).pkgName;
Pacman.upgradePack(name);
}
updatePackageList();
}
} }
} }
} }
} }
} }
ListModel { Rectangle {
id: packageModel width: parent.width
} height: parent.height - bar.height - urlInstaller.height
anchors.top: bar.bottom
color: "snow"
opacity: 0.75
clip: true
ColumnLayout { ListView {
anchors.fill: parent id: packageList
anchors.fill: parent
RowLayout { model: ListModel {
Layout.fillHeight: true id: packageModel
Layout.alignment: Qt.AlignHCenter
Item {
Layout.preferredWidth: root.width * 0.9
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: "#88EEEEEE"
} }
ListView { delegate: ItemDelegate {
id: packageList width: root.width
anchors.fill: parent height: 64
contentHeight: packageDelegate.height * count ColumnLayout {
ScrollBar.vertical: ScrollBar {} anchors.fill: parent
header: RowLayout { anchors.margins: 8
height: 22
width: packageList.width
spacing: 16
Text { Text {
font.pixelSize: 20 text: "<b>" + pkgName + "</b> (" + pkgVersion + ")"
text: qsTr("Name") font.pixelSize: 18
textFormat: Text.RichText
color: pkgEnabled === "1" ? "black" : "grey"
} }
Text { Text {
font.pixelSize: 20 text: pkgURL
Layout.fillWidth: true color: pkgEnabled === "1" ? "black" : "grey"
horizontalAlignment: Text.AlignHCenter
text: "URL"
}
Text {
font.pixelSize: 20
text: qsTr("Version")
}
Text {
font.pixelSize: 20
text: qsTr("Enable")
} }
} }
delegate: packageDelegate
model: packageModel
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
Component.onCompleted: { currentIndex = -1; }
}
}
ColumnLayout { Button {
Button { id: enableBtn
enabled: packageList.currentItem text: pkgEnabled === "0" ? qsTr("Enable") : qsTr("Disable")
text: qsTr("Enable") anchors.right: upgradeBtn.left
onClicked: { anchors.rightMargin: 8
let idx = packageList.currentIndex; onClicked: {
let name = packageModel.get(idx).pkgName; if (pkgEnabled === "0") {
Pacman.enablePack(name); Pacman.enablePack(pkgName);
updatePackageList(); } else {
packageList.currentIndex = idx; Pacman.disablePack(pkgName);
}
updatePackageList();
}
} }
}
Button { Button {
enabled: packageList.currentItem id: upgradeBtn
text: qsTr("Disable") text: qsTr("Upgrade")
onClicked: { anchors.right: delBtn.left
let idx = packageList.currentIndex; anchors.rightMargin: 8
let name = packageModel.get(idx).pkgName; onClicked: {
Pacman.disablePack(name); Pacman.upgradePack(pkgName);
updatePackageList(); updatePackageList();
packageList.currentIndex = idx; }
} }
}
Button { Button {
enabled: packageList.currentItem id: delBtn
text: qsTr("Upgrade") text: qsTr("Remove")
onClicked: { anchors.right: parent.right
let idx = packageList.currentIndex; anchors.rightMargin: 8
let name = packageModel.get(idx).pkgName; onClicked: {
Pacman.upgradePack(name); Pacman.removePack(pkgName);
updatePackageList(); updatePackageList();
packageList.currentIndex = idx; }
} }
}
Button {
enabled: packageList.currentItem
text: qsTr("Remove")
onClicked: { onClicked: {
let idx = packageList.currentIndex; Backend.copyToClipboard(pkgURL);
let name = packageModel.get(idx).pkgName; toast.show(qsTr("Copied %1.").arg(pkgURL));
Pacman.removePack(name);
updatePackageList();
packageList.currentIndex = idx;
}
}
Button {
enabled: packageList.currentItem
text: qsTr("Copy URL")
onClicked: {
let idx = packageList.currentIndex;
let name = packageModel.get(idx).pkgURL;
Backend.copyToClipboard(name);
toast.show(qsTr("Copied."));
} }
} }
} }
} }
RowLayout { Rectangle {
Layout.fillWidth: true id: urlInstaller
TextField { width: parent.width
id: urlEdit height: childrenRect.height
Layout.fillWidth: true color: "snow"
clip: true opacity: 0.75
} anchors.bottom: parent.bottom
Button { RowLayout {
text: qsTr("Install From URL") width: parent.width
enabled: urlEdit.text !== "" TextField {
onClicked: { id: urlEdit
let url = urlEdit.text; Layout.fillWidth: true
mainWindow.busy = true; clip: true
Pacman.downloadNewPack(url, true); }
Button {
text: qsTr("Install From URL")
enabled: urlEdit.text !== ""
onClicked: {
let url = urlEdit.text;
mainWindow.busy = true;
Pacman.downloadNewPack(url, true);
}
} }
} }
} }
}
function updatePackageList() { function updatePackageList() {
packageModel.clear(); packageModel.clear();
let data = JSON.parse(Pacman.listPackages()); let data = JSON.parse(Pacman.listPackages());

View File

@ -17,6 +17,9 @@ Item {
property bool isOwner: false property bool isOwner: false
property bool isStarted: false property bool isStarted: false
property bool isFull: false
property bool isAllReady: false
property bool isReady: false
property alias popupBox: popupBox property alias popupBox: popupBox
property alias manualBox: manualBox property alias manualBox: manualBox
@ -79,13 +82,36 @@ Item {
} }
} }
Button { Button {
text: "add robot" text: Backend.translate("Add Robot")
visible: isOwner && !isStarted visible: isOwner && !isStarted && !isFull
anchors.centerIn: parent anchors.centerIn: parent
onClicked: { onClicked: {
ClientInstance.notifyServer("AddRobot", "[]"); ClientInstance.notifyServer("AddRobot", "[]");
} }
} }
Button {
text: Backend.translate("Start Game")
visible: isOwner && !isStarted && isFull
enabled: isAllReady
anchors.centerIn: parent
onClicked: {
ClientInstance.notifyServer("StartGame", "[]");
}
}
Timer {
id: opTimer
interval: 1000
}
Button {
text: isReady ? Backend.translate("Cancel Ready") : Backend.translate("Ready")
visible: !isOwner && !isStarted
enabled: !opTimer.running
anchors.centerIn: parent
onClicked: {
opTimer.start();
ClientInstance.notifyServer("Ready", "");
}
}
states: [ states: [
State { name: "notactive" }, // Normal status State { name: "notactive" }, // Normal status
@ -195,6 +221,7 @@ Item {
Photo { Photo {
playerid: model.id playerid: model.id
general: model.general general: model.general
avatar: model.avatar
deputyGeneral: model.deputyGeneral deputyGeneral: model.deputyGeneral
screenName: model.screenName screenName: model.screenName
role: model.role role: model.role
@ -210,6 +237,7 @@ Item {
chained: model.chained chained: model.chained
drank: model.drank drank: model.drank
isOwner: model.isOwner isOwner: model.isOwner
ready: model.ready
onSelectedChanged: { onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected); Logic.updateSelectedTargets(playerid, selected);
@ -830,6 +858,36 @@ Item {
cheatDrawer.open(); cheatDrawer.open();
} }
function resetToInit() {
let datalist = [];
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id > 0) {
datalist.push({
id: item.id,
avatar: item.avatar,
name: item.screenName,
isOwner: item.isOwner,
ready: item.ready,
});
}
}
mainStack.pop();
mainStack.push(room);
mainStack.currentItem.loadPlayerData(datalist);
}
function loadPlayerData(datalist) {
datalist.forEach(d => {
if (d.id == Self.id) {
roomScene.isOwner = d.isOwner;
} else {
callbacks["AddPlayer"](JSON.stringify([d.id, d.name, d.avatar, d.ready]));
}
Logic.getPhotoModel(d.id).isOwner = d.isOwner;
});
}
Component.onCompleted: { Component.onCompleted: {
toast.show(Backend.translate("$EnterRoom")); toast.show(Backend.translate("$EnterRoom"));
playerNum = config.roomCapacity; playerNum = config.roomCapacity;
@ -839,6 +897,7 @@ Item {
id: i ? -1 : Self.id, id: i ? -1 : Self.id,
index: i, // For animating seat swap index: i, // For animating seat swap
general: i ? "" : Self.avatar, general: i ? "" : Self.avatar,
avatar: i ? "" : Self.avatar,
deputyGeneral: "", deputyGeneral: "",
screenName: i ? "" : Self.screenName, screenName: i ? "" : Self.screenName,
role: "unknown", role: "unknown",
@ -853,7 +912,8 @@ Item {
faceup: true, faceup: true,
chained: false, chained: false,
drank: 0, drank: 0,
isOwner: false isOwner: false,
ready: false,
}); });
} }

View File

@ -334,7 +334,7 @@ function changeSelf(id) {
} }
callbacks["AddPlayer"] = function(jsonData) { callbacks["AddPlayer"] = function(jsonData) {
// jsonData: int id, string screenName, string avatar // jsonData: int id, string screenName, string avatar, bool ready
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
if (item.id === -1) { if (item.id === -1) {
@ -342,9 +342,22 @@ callbacks["AddPlayer"] = function(jsonData) {
let uid = data[0]; let uid = data[0];
let name = data[1]; let name = data[1];
let avatar = data[2]; let avatar = data[2];
let ready = data[3];
item.id = uid; item.id = uid;
item.screenName = name; item.screenName = name;
item.general = avatar; item.general = avatar;
item.avatar = avatar;
item.ready = ready;
checkAllReady();
if (getPhoto(-1)) {
roomScene.isFull = false;
} else {
roomScene.isFull = true;
}
return; return;
} }
} }
@ -485,6 +498,8 @@ callbacks["RemovePlayer"] = function(jsonData) {
model.id = -1; model.id = -1;
model.screenName = ""; model.screenName = "";
model.general = ""; model.general = "";
model.isOwner = false;
roomScene.isFull = false;
} }
} }
@ -492,9 +507,7 @@ callbacks["RoomOwner"] = function(jsonData) {
// jsonData: int uid of the owner // jsonData: int uid of the owner
let uid = JSON.parse(jsonData)[0]; let uid = JSON.parse(jsonData)[0];
if (Self.id === uid) { roomScene.isOwner = (Self.id === uid);
roomScene.isOwner = true;
}
let model = getPhotoModel(uid); let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") { if (typeof(model) !== "undefined") {
@ -502,6 +515,46 @@ callbacks["RoomOwner"] = function(jsonData) {
} }
} }
function checkAllReady() {
let allReady = true;
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (!item.isOwner && !item.ready) {
allReady = false;
break;
}
}
roomScene.isAllReady = allReady;
}
callbacks["ReadyChanged"] = (j) => {
const data = JSON.parse(j);
const id = data[0];
const ready = data[1];
if (id === Self.id) {
roomScene.isReady = ready === 1;
}
let model = getPhotoModel(id);
if (typeof(model) !== "undefined") {
model.ready = ready ? true : false;
checkAllReady();
}
}
callbacks["NetStateChanged"] = (j) => {
const data = JSON.parse(j);
const id = data[0];
let state = data[1];
let model = getPhotoModel(id);
if (state == "run" && model.dead) {
state = "leave";
}
model.netstate = state;
}
callbacks["PropertyUpdate"] = function(jsonData) { callbacks["PropertyUpdate"] = function(jsonData) {
// jsonData: int id, string property_name, value // jsonData: int id, string property_name, value
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
@ -510,6 +563,7 @@ callbacks["PropertyUpdate"] = function(jsonData) {
let value = data[2]; let value = data[2];
let model = getPhotoModel(uid); let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") { if (typeof(model) !== "undefined") {
model[property_name] = value; model[property_name] = value;
} }
@ -520,6 +574,7 @@ callbacks["StartGame"] = function(jsonData) {
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
item.ready = false;
item.general = ""; item.general = "";
} }
} }
@ -1013,6 +1068,12 @@ callbacks["LogEvent"] = function(jsonData) {
Backend.playSound("./audio/system/losehp"); Backend.playSound("./audio/system/losehp");
break; break;
} }
case "ChangeMaxHp": {
if (data.num < 0) {
Backend.playSound("./audio/system/losemaxhp");
}
break;
}
case "PlaySkillSound": { case "PlaySkillSound": {
let skill = data.name; let skill = data.name;
let extension = data.extension; let extension = data.extension;
@ -1042,7 +1103,7 @@ callbacks["GameOver"] = function(jsonData) {
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GameOverBox.qml"); roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GameOverBox.qml");
let box = roomScene.popupBox.item; let box = roomScene.popupBox.item;
box.winner = jsonData; box.winner = jsonData;
roomScene.isStarted = false; // roomScene.isStarted = false;
} }
callbacks["FillAG"] = (j) => { callbacks["FillAG"] = (j) => {

View File

@ -25,7 +25,7 @@ Item {
Image { Image {
height: 55 * 0.8 height: 55 * 0.8
width: 47 * 0.8 width: 47 * 0.8
source: SkinBank.DELAYED_TRICK_DIR + name source: SkinBank.getDelayedTrickPicture(name) // SkinBank.DELAYED_TRICK_DIR + name
} }
} }
} }

View File

@ -18,7 +18,8 @@ GraphicsBox {
} }
id: root id: root
title.text: Backend.translate("$ChooseGeneral").arg(choiceNum) title.text: Backend.translate("$ChooseGeneral").arg(choiceNum) +
(config.enableFreeAssign ? "(" + Backend.translate("Enable free assign") + ")" : "")
width: generalArea.width + body.anchors.leftMargin + body.anchors.rightMargin width: generalArea.width + body.anchors.leftMargin + body.anchors.rightMargin
height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin

View File

@ -22,6 +22,16 @@ GraphicsBox {
color: "#E4D5A0" color: "#E4D5A0"
} }
MetroButton {
text: Backend.translate("Back To Room")
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
roomScene.resetToInit();
finished();
}
}
MetroButton { MetroButton {
text: Backend.translate("Back To Lobby") text: Backend.translate("Back To Lobby")
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@ -13,6 +13,7 @@ Item {
scale: 0.75 scale: 0.75
property int playerid: 0 property int playerid: 0
property string general: "" property string general: ""
property string avatar: ""
property string deputyGeneral: "" property string deputyGeneral: ""
property string screenName: "" property string screenName: ""
property string role: "unknown" property string role: "unknown"
@ -29,6 +30,7 @@ Item {
property bool chained: false property bool chained: false
property int drank: 0 property int drank: 0
property bool isOwner: false property bool isOwner: false
property bool ready: false
property int distance: 0 property int distance: 0
property string status: "normal" property string status: "normal"
property int maxCard: 0 property int maxCard: 0
@ -249,7 +251,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.bottomMargin: 8 anchors.bottomMargin: 8
anchors.rightMargin: 4 anchors.rightMargin: 4
source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : "ready") source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : (ready ? "ready" : "notready"))
visible: screenName != "" && !roomScene.isStarted visible: screenName != "" && !roomScene.isStarted
} }
@ -342,6 +344,8 @@ Item {
source: SkinBank.STATE_DIR + root.netstate source: SkinBank.STATE_DIR + root.netstate
x: photoMask.x x: photoMask.x
y: photoMask.y y: photoMask.y
scale: 0.9
transformOrigin: Item.TopLeft
} }
Image { Image {
@ -596,6 +600,10 @@ Item {
} }
function showDetail() { function showDetail() {
if (playerid === 0 || playerid === -1) {
return;
}
roomScene.startCheat("PlayerDetail", { photo: this }); roomScene.startCheat("PlayerDetail", { photo: this });
} }
} }

View File

@ -53,6 +53,22 @@ function getCardPicture(cidOrName) {
return CARD_DIR + "unknown.png"; return CARD_DIR + "unknown.png";
} }
function getDelayedTrickPicture(name) {
let extension = Backend.callLuaFunction("GetCardExtensionByName", [name]);
let path = AppPath + "/packages/" + extension + "/image/card/delayedTrick/" + name + ".png";
if (Backend.exists(path)) {
return path;
} else {
for (let dir of Backend.ls(AppPath + "/packages/")) {
path = AppPath + "/packages/" + dir + "/image/card/delayedTrick/" + name + ".png";
if (Backend.exists(path)) return path;
}
}
return DELAYED_TRICK_DIR + "unknown.png";
}
function getEquipIcon(cid, icon) { function getEquipIcon(cid, icon) {
let data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid])); let data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid]));
let extension = data.extension; let extension = data.extension;

BIN
audio/system/losemaxhp.mp3 Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
image/photo/notready.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

BIN
image/photo/state/run.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -15,7 +15,59 @@
</message> </message>
<message> <message>
<source>Socket access error</source> <source>Socket access error</source>
<translation>访</translation> <translation>访</translation>
</message>
<message>
<source>Socket resource error</source>
<translation></translation>
</message>
<message>
<source>Socket timeout error</source>
<translation></translation>
</message>
<message>
<source>Datagram too large error</source>
<translation></translation>
</message>
<message>
<source>Network error</source>
<translation></translation>
</message>
<message>
<source>Unsupprted socket operation</source>
<translation></translation>
</message>
<message>
<source>Unfinished socket operation</source>
<translation></translation>
</message>
<message>
<source>Proxy auth error</source>
<translation></translation>
</message>
<message>
<source>Proxy refused</source>
<translation></translation>
</message>
<message>
<source>Proxy closed</source>
<translation></translation>
</message>
<message>
<source>Proxy timeout</source>
<translation></translation>
</message>
<message>
<source>Proxy protocol error</source>
<translation></translation>
</message>
<message>
<source>Operation error</source>
<translation></translation>
</message>
<message>
<source>Temporary error</source>
<translation></translation>
</message> </message>
<message> <message>
<source>Unknown error</source> <source>Unknown error</source>
@ -187,14 +239,14 @@
<source>no such room</source> <source>no such room</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>you have been banned!</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>PackageManage</name> <name>PackageManage</name>
<message>
<source>Quit</source>
<translation>退</translation>
</message>
<message> <message>
<source>Install From URL</source> <source>Install From URL</source>
<translation>URL安装</translation> <translation>URL安装</translation>
@ -207,14 +259,6 @@
<source>Disable</source> <source>Disable</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Enabled</source>
<translation></translation>
</message>
<message>
<source>Disabled</source>
<translation></translation>
</message>
<message> <message>
<source>Remove</source> <source>Remove</source>
<translation></translation> <translation></translation>
@ -224,20 +268,24 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Name</source> <source>Copied %1.</source>
<translation></translation> <translation>(%1)</translation>
</message> </message>
<message> <message>
<source>Version</source> <source>Package Manager</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Copy URL</source> <source>Enable All</source>
<translation>URL</translation> <translation></translation>
</message> </message>
<message> <message>
<source>Copied.</source> <source>Disable All</source>
<translation></translation> <translation></translation>
</message>
<message>
<source>Upgrade All</source>
<translation></translation>
</message> </message>
</context> </context>

View File

@ -34,6 +34,10 @@ Fk:loadTranslationTable{
["Luck Card Times"] = "手气卡次数", ["Luck Card Times"] = "手气卡次数",
["Room Password"] = "房间密码", ["Room Password"] = "房间密码",
["Please input room's password"] = "请输入房间的密码", ["Please input room's password"] = "请输入房间的密码",
["Add Robot"] = "添加机器人",
["Start Game"] = "开始游戏",
["Ready"] = "准备",
["Cancel Ready"] = "取消准备",
["Game Mode"] = "游戏模式", ["Game Mode"] = "游戏模式",
["Enable free assign"] = "自由选将", ["Enable free assign"] = "自由选将",
["Enable deputy general"] = "启用副将机制", ["Enable deputy general"] = "启用副将机制",
@ -45,6 +49,7 @@ Fk:loadTranslationTable{
["Give Flower"] = "送花", ["Give Flower"] = "送花",
["Give Egg"] = "砸蛋", ["Give Egg"] = "砸蛋",
["Give Shoe"] = "拖鞋", ["Give Shoe"] = "拖鞋",
["Kick From Room"] = "踢出房间",
["$OnlineInfo"] = "大厅人数:%1总在线人数%2", ["$OnlineInfo"] = "大厅人数:%1总在线人数%2",
@ -206,6 +211,7 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["$GameOver"] = "游戏结束", ["$GameOver"] = "游戏结束",
["$Winner"] = "%1 获胜", ["$Winner"] = "%1 获胜",
["$NoWinner"] = "平局!", ["$NoWinner"] = "平局!",
["Back To Room"] = "回到房间",
["Back To Lobby"] = "返回大厅", ["Back To Lobby"] = "返回大厅",
["Bulletin Info"] = [==[<h2>v0.2.0 </h2> ["Bulletin Info"] = [==[<h2>v0.2.0 </h2>

View File

@ -102,12 +102,23 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self)
break break
end end
if table.every(room:getTag("LuckCardData").playerList, function(id) -- local ldata = room:getTag("LuckCardData")
return room:getTag("LuckCardData")[id].luckTime == 0 local ldata = luck_data
if table.every(ldata.playerList, function(id)
return ldata[id].luckTime == 0
end) then end) then
break break
end end
for _, id in ipairs(ldata.playerList) do
if room:getPlayerById(id)._splayer:getStateString() ~= "online" then
ldata[id].luckTime = 0
end
end
-- room:setTag("LuckCardData", ldata)
checkNoHuman(room) checkNoHuman(room)
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000) coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)

View File

@ -242,6 +242,10 @@ GameEvent.functions[GameEvent.ChangeMaxHp] = function(self)
player.maxHp = math.max(player.maxHp + num, 0) player.maxHp = math.max(player.maxHp + num, 0)
self:broadcastProperty(player, "maxHp") self:broadcastProperty(player, "maxHp")
self:sendLogEvent("ChangeMaxHp", {
player = player.id,
num = num,
})
if player.maxHp == 0 then if player.maxHp == 0 then
self:killPlayer({ who = player.id }) self:killPlayer({ who = player.id })
end end

View File

@ -70,7 +70,9 @@ end
local request_handlers = {} local request_handlers = {}
request_handlers["reconnect"] = function(room, id, reqlist) request_handlers["reconnect"] = function(room, id, reqlist)
local p = room:getPlayerById(id) local p = room:getPlayerById(id)
p:reconnect() if p then
p:reconnect()
end
end end
request_handlers["observe"] = function(room, id, reqlist) request_handlers["observe"] = function(room, id, reqlist)

View File

@ -102,6 +102,11 @@ local function _waitForReply(player, timeout)
local start = os.getms() local start = os.getms()
local state = player.serverplayer:getStateString() local state = player.serverplayer:getStateString()
if state ~= "online" then if state ~= "online" then
if state ~= "robot" then
checkNoHuman(player.room)
player.room:delay(500)
return "__cancel"
end
-- Let AI make reply. First handle request -- Let AI make reply. First handle request
local ret_msg = true local ret_msg = true
while ret_msg do while ret_msg do
@ -259,6 +264,12 @@ function ServerPlayer:marshal(player)
end end
end end
for k, v in pairs(self.skillUsedHistory) do
if v[1] > 0 then
player:doNotify("AddSkillUseHistory", json.encode{self.id, k, v[1]})
end
end
if self.role_shown then if self.role_shown then
room:notifyProperty(player, self, "role") room:notifyProperty(player, self, "role")
end end

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -5,8 +5,8 @@
#include "clientplayer.h" #include "clientplayer.h"
#include "util.h" #include "util.h"
Client *ClientInstance; Client *ClientInstance = nullptr;
ClientPlayer *Self; ClientPlayer *Self = nullptr;
static ClientPlayer dummyPlayer(0, nullptr); static ClientPlayer dummyPlayer(0, nullptr);

View File

@ -82,10 +82,16 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
} }
void PackMan::downloadNewPack(const QString &url, bool useThread) { void PackMan::downloadNewPack(const QString &url, bool useThread) {
static auto sql_select = QString("SELECT name FROM packages \
WHERE name = '%1';");
static auto sql_update = QString("INSERT INTO packages (name,url,hash,enabled) \
VALUES ('%1','%2','%3',1);");
auto threadFunc = [=]() { auto threadFunc = [=]() {
int error = clone(url); int error = clone(url);
if (error < 0) // if (error < 0)
return; // return;
auto u = url; auto u = url;
while (u.endsWith('/')) { while (u.endsWith('/')) {
u.chop(1); u.chop(1);
@ -94,15 +100,11 @@ void PackMan::downloadNewPack(const QString &url, bool useThread) {
if (fileName.endsWith(".git")) if (fileName.endsWith(".git"))
fileName.chop(4); fileName.chop(4);
auto result = SelectFromDatabase(db, QString("SELECT name FROM packages \ auto result = SelectFromDatabase(db, sql_select.arg(fileName));
WHERE name = '%1';")
.arg(fileName));
if (result.isEmpty()) { if (result.isEmpty()) {
ExecSQL(db, QString("INSERT INTO packages (name,url,hash,enabled) \ ExecSQL(db, sql_update.arg(fileName)
VALUES ('%1','%2','%3',1);")
.arg(fileName)
.arg(url) .arg(url)
.arg(head(fileName))); .arg(error < 0 ? "XXXXXXXX" : head(fileName)));
} }
}; };
if (useThread) { if (useThread) {

View File

@ -97,12 +97,32 @@ static void prepareForLinux() {
} }
#endif #endif
static FILE *info_log = nullptr;
static FILE *err_log = nullptr;
void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg) { const QString &msg) {
auto date = QDate::currentDate(); auto date = QDate::currentDate();
FILE *file;
switch (type) {
case QtDebugMsg:
case QtInfoMsg:
file = info_log;
break;
case QtWarningMsg:
case QtCriticalMsg:
case QtFatalMsg:
file = err_log;
break;
}
fprintf(stderr, "\r%02d/%02d ", date.month(), date.day()); fprintf(stderr, "\r%02d/%02d ", date.month(), date.day());
fprintf(stderr, "%s ", fprintf(stderr, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData()); QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
fprintf(file, "\r%02d/%02d ", date.month(), date.day());
fprintf(file, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
auto localMsg = msg.toUtf8(); auto localMsg = msg.toUtf8();
auto threadName = QThread::currentThread()->objectName().toLatin1(); auto threadName = QThread::currentThread()->objectName().toLatin1();
@ -111,19 +131,27 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
case QtDebugMsg: case QtDebugMsg:
fprintf(stderr, "%s[D] %s\n", threadName.constData(), fprintf(stderr, "%s[D] %s\n", threadName.constData(),
localMsg.constData()); localMsg.constData());
fprintf(file, "%s[D] %s\n", threadName.constData(),
localMsg.constData());
break; break;
case QtInfoMsg: case QtInfoMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(), fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("I", Green).toUtf8().constData(), localMsg.constData()); Color("I", Green).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"I", localMsg.constData());
break; break;
case QtWarningMsg: case QtWarningMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(), fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("W", Yellow, Bold).toUtf8().constData(), Color("W", Yellow, Bold).toUtf8().constData(),
localMsg.constData()); localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"W", localMsg.constData());
break; break;
case QtCriticalMsg: case QtCriticalMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(), fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("C", Red, Bold).toUtf8().constData(), localMsg.constData()); Color("C", Red, Bold).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"C", localMsg.constData());
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
if (Backend != nullptr) { if (Backend != nullptr) {
Backend->notifyUI( Backend->notifyUI(
@ -135,6 +163,8 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
case QtFatalMsg: case QtFatalMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(), fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("E", Red, Bold).toUtf8().constData(), localMsg.constData()); Color("E", Red, Bold).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"E", localMsg.constData());
break; break;
} }
} }
@ -143,6 +173,19 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// 初始化一下各种杂项信息 // 初始化一下各种杂项信息
QThread::currentThread()->setObjectName("Main"); QThread::currentThread()->setObjectName("Main");
if (!info_log) {
info_log = fopen("freekill.server.info.log", "a+");
if (!info_log) {
qFatal("Cannot open info.log");
}
}
if (!err_log) {
err_log = fopen("freekill.server.error.log", "a+");
if (!err_log) {
qFatal("Cannot open error.log");
}
}
qInstallMessageHandler(fkMsgHandler); qInstallMessageHandler(fkMsgHandler);
QCoreApplication *app; QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill"); QCoreApplication::setApplicationName("FreeKill");
@ -313,6 +356,15 @@ int main(int argc, char *argv[]) {
); );
#endif #endif
if (info_log) {
fclose(info_log);
info_log = nullptr;
}
if (err_log) {
fclose(err_log);
info_log = nullptr;
}
return ret; return ret;
#endif #endif
} }

View File

@ -2,6 +2,7 @@
#include "client_socket.h" #include "client_socket.h"
#include <openssl/aes.h> #include <openssl/aes.h>
#include <qabstractsocket.h>
#include <qrandom.h> #include <qrandom.h>
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
@ -96,8 +97,45 @@ void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) {
case QAbstractSocket::SocketAccessError: case QAbstractSocket::SocketAccessError:
reason = tr("Socket access error"); reason = tr("Socket access error");
break; break;
case QAbstractSocket::SocketResourceError:
reason = tr("Socket resource error");
break;
case QAbstractSocket::SocketTimeoutError:
reason = tr("Socket timeout error");
break;
case QAbstractSocket::DatagramTooLargeError:
reason = tr("Datagram too large error");
break;
case QAbstractSocket::NetworkError: case QAbstractSocket::NetworkError:
return; // this error is ignored ... reason = tr("Network error");
break;
case QAbstractSocket::UnsupportedSocketOperationError:
reason = tr("Unsupprted socket operation");
break;
case QAbstractSocket::UnfinishedSocketOperationError:
reason = tr("Unfinished socket operation");
break;
case QAbstractSocket::ProxyAuthenticationRequiredError:
reason = tr("Proxy auth error");
break;
case QAbstractSocket::ProxyConnectionRefusedError:
reason = tr("Proxy refused");
break;
case QAbstractSocket::ProxyConnectionClosedError:
reason = tr("Proxy closed");
break;
case QAbstractSocket::ProxyConnectionTimeoutError:
reason = tr("Proxy timeout");
break;
case QAbstractSocket::ProxyProtocolError:
reason = tr("Proxy protocol error");
break;
case QAbstractSocket::OperationError:
reason = tr("Operation error");
break;
case QAbstractSocket::TemporaryError:
reason = tr("Temporary error");
break;
default: default:
reason = tr("Unknown error"); reason = tr("Unknown error");
break; break;

View File

@ -276,6 +276,16 @@ void Router::handlePacket(const QByteArray &rawPacket) {
room->removePlayer(player); room->removePlayer(player);
} else if (command == "AddRobot") { } else if (command == "AddRobot") {
room->addRobot(player); room->addRobot(player);
} else if (command == "KickPlayer") {
int i = jsonData.toInt();
auto p = room->findPlayer(i);
if (p) room->removePlayer(p);
} else if (command == "Ready") {
player->setReady(!player->isReady());
room->doBroadcastNotify(room->getPlayers(), "ReadyChanged",
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
} else if (command == "StartGame") {
room->manuallyStart();
} else if (command == "Chat") { } else if (command == "Chat") {
room->chat(player, jsonData); room->chat(player, jsonData);
} else if (command == "PushRequest") { } else if (command == "PushRequest") {

View File

@ -114,6 +114,7 @@ void Room::addPlayer(ServerPlayer *player) {
jsonData << player->getId(); jsonData << player->getId();
jsonData << player->getScreenName(); jsonData << player->getScreenName();
jsonData << player->getAvatar(); jsonData << player->getAvatar();
jsonData << player->isReady();
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData)); doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
} }
@ -121,7 +122,13 @@ void Room::addPlayer(ServerPlayer *player) {
player->setRoom(this); player->setRoom(this);
if (isLobby()) { if (isLobby()) {
player->doNotify("EnterLobby", "[]"); // 有机器人进入大厅(可能因为被踢),那么改为销毁
if (player->getState() == Player::Robot) {
removePlayer(player);
player->deleteLater();
} else {
player->doNotify("EnterLobby", "[]");
}
} else { } else {
// Second, let the player enter room and add other players // Second, let the player enter room and add other players
jsonData = QJsonArray(); jsonData = QJsonArray();
@ -135,6 +142,7 @@ void Room::addPlayer(ServerPlayer *player) {
jsonData << p->getId(); jsonData << p->getId();
jsonData << p->getScreenName(); jsonData << p->getScreenName();
jsonData << p->getAvatar(); jsonData << p->getAvatar();
jsonData << p->isReady();
player->doNotify("AddPlayer", JsonArray2Bytes(jsonData)); player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
} }
@ -144,8 +152,9 @@ void Room::addPlayer(ServerPlayer *player) {
player->doNotify("RoomOwner", JsonArray2Bytes(jsonData)); player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
} }
if (isFull() && !gameStarted) // 玩家手动启动
start(); // if (isFull() && !gameStarted)
// start();
} }
emit playerAdded(player); emit playerAdded(player);
} }
@ -159,6 +168,7 @@ void Room::addRobot(ServerPlayer *player) {
robot->setId(robot_id); robot->setId(robot_id);
robot->setAvatar("guanyu"); robot->setAvatar("guanyu");
robot->setScreenName(QString("COMP-%1").arg(robot_id)); robot->setScreenName(QString("COMP-%1").arg(robot_id));
robot->setReady(true);
robot_id--; robot_id--;
// FIXME: 会触发Lobby:removePlayer // FIXME: 会触发Lobby:removePlayer
@ -174,7 +184,7 @@ void Room::removePlayer(ServerPlayer *player) {
if (!gameStarted) { if (!gameStarted) {
// 游戏还没开始的话,直接删除这名玩家 // 游戏还没开始的话,直接删除这名玩家
if (players.contains(player)) { if (players.contains(player) && !players.isEmpty()) {
players.removeOne(player); players.removeOne(player);
} }
emit playerRemoved(player); emit playerRemoved(player);
@ -213,9 +223,13 @@ void Room::removePlayer(ServerPlayer *player) {
} }
// 如果房间空了就把房间标为废弃Server有信号处理函数的 // 如果房间空了就把房间标为废弃Server有信号处理函数的
if (isAbandoned() && !m_abandoned) { if (isAbandoned()) {
bool tmp = m_abandoned;
m_abandoned = true; m_abandoned = true;
emit abandoned(); // 只释放一次信号就行了他销毁机器人的时候会多次调用removePlayer
if (!tmp) {
emit abandoned();
}
} else if (player == owner) { } else if (player == owner) {
setOwner(players.first()); setOwner(players.first());
} }
@ -377,9 +391,19 @@ void Room::gameOver() {
} }
// 旁观者不能在这清除因为removePlayer逻辑不一样 // 旁观者不能在这清除因为removePlayer逻辑不一样
// observers.clear(); // observers.clear();
players.clear(); // 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
// players.clear();
// owner = nullptr;
clearRequest(); clearRequest();
owner = nullptr; }
void Room::manuallyStart() {
if (isFull() && !gameStarted) {
foreach (auto p, players) {
p->setReady(false);
}
start();
}
} }
QString Room::fetchRequest() { QString Room::fetchRequest() {

View File

@ -59,6 +59,7 @@ class Room : public QThread {
void initLua(); void initLua();
void roomStart(); void roomStart();
void manuallyStart();
LuaFunction startGame; LuaFunction startGame;
QString fetchRequest(); QString fetchRequest();

View File

@ -19,7 +19,7 @@
#include "serverplayer.h" #include "serverplayer.h"
#include "util.h" #include "util.h"
Server *ServerInstance; Server *ServerInstance = nullptr;
Server::Server(QObject *parent) : QObject(parent) { Server::Server(QObject *parent) : QObject(parent) {
ServerInstance = this; ServerInstance = this;
@ -134,22 +134,36 @@ void Server::addPlayer(ServerPlayer *player) {
players.insert(id, player); players.insert(id, player);
} }
void Server::removePlayer(int id) { players.remove(id); } void Server::removePlayer(int id) {
if (players[id]) {
players.remove(id);
}
}
void Server::updateRoomList() { void Server::updateRoomList() {
QJsonArray arr; QJsonArray arr;
QJsonArray avail_arr;
foreach (Room *room, rooms) { foreach (Room *room, rooms) {
QJsonArray obj; QJsonArray obj;
auto settings = QJsonDocument::fromJson(room->getSettings()); auto settings = QJsonDocument::fromJson(room->getSettings());
auto password = settings["password"].toString(); auto password = settings["password"].toString();
auto count = room->getPlayers().count(); // playerNum
auto cap = room->getCapacity(); // capacity
obj << room->getId(); // roomId obj << room->getId(); // roomId
obj << room->getName(); // roomName obj << room->getName(); // roomName
obj << settings["gameMode"]; // gameMode obj << settings["gameMode"]; // gameMode
obj << room->getPlayers().count(); // playerNum obj << count;
obj << room->getCapacity(); // capacity obj << cap;
obj << !password.isEmpty(); obj << !password.isEmpty();
arr << obj;
if (count == cap)
arr << obj;
else
avail_arr << obj;
}
foreach (auto v, avail_arr) {
arr.prepend(v);
} }
auto jsonData = JsonArray2Bytes(arr); auto jsonData = JsonArray2Bytes(arr);
lobby()->doBroadcastNotify(lobby()->getPlayers(), "UpdateRoomList", lobby()->doBroadcastNotify(lobby()->getPlayers(), "UpdateRoomList",
@ -171,8 +185,22 @@ void Server::broadcast(const QString &command, const QString &jsonData) {
} }
void Server::processNewConnection(ClientSocket *client) { void Server::processNewConnection(ClientSocket *client) {
qInfo() << client->peerAddress() << "connected"; auto addr = client->peerAddress();
// version check, file check, ban IP, reconnect, etc qInfo() << addr << "connected";
auto result = SelectFromDatabase(
db, QString("SELECT * FROM banip WHERE ip='%1';").arg(addr));
if (!result.isEmpty()) {
QJsonArray body;
body << -2;
body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER |
Router::DEST_CLIENT);
body << "ErrorMsg";
body << "you have been banned!";
client->send(JsonArray2Bytes(body));
qInfo() << "Refused banned IP:" << addr;
client->disconnectFromHost();
return;
}
connect(client, &ClientSocket::disconnected, this, connect(client, &ClientSocket::disconnected, this,
[client]() { qInfo() << client->peerAddress() << "disconnected"; }); [client]() { qInfo() << client->peerAddress() << "disconnected"; });
@ -334,7 +362,10 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
obj = result[0].toObject(); obj = result[0].toObject();
// check if this username already login // check if this username already login
int id = obj["id"].toString().toInt(); int id = obj["id"].toString().toInt();
if (!players.value(id)) { passed = obj["banned"].toString().toInt() == 0;
if (!passed) {
error_msg = "you have been banned!";
} else if (!players.value(id)) {
// check if password is the same // check if password is the same
auto salt = obj["salt"].toString().toLatin1(); auto salt = obj["salt"].toString().toLatin1();
decrypted_pw.append(salt); decrypted_pw.append(salt);
@ -351,12 +382,21 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
player->setSocket(client); player->setSocket(client);
player->alive = true; player->alive = true;
client->disconnect(this); client->disconnect(this);
broadcast("ServerMessage", // broadcast("ServerMessage",
tr("%1 backed").arg(player->getScreenName())); // tr("%1 backed").arg(player->getScreenName()));
room->pushRequest(QString("%1,reconnect").arg(id));
if (room && !room->isLobby()) {
room->pushRequest(QString("%1,reconnect").arg(id));
} else {
// 懒得处理掉线玩家在大厅了!踢掉得了
player->doNotify("ErrorMsg", "Unknown Error");
player->kicked();
}
return; return;
} else { } else {
error_msg = "others logged in with this name"; error_msg = "others logged in with this name";
passed = false;
} }
} }
} }
@ -365,6 +405,13 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
} }
if (passed) { if (passed) {
// update lastLoginIp
auto sql_update =
QString("UPDATE userinfo SET lastLoginIp='%1' WHERE id=%2;")
.arg(client->peerAddress())
.arg(obj["id"].toString().toInt());
ExecSQL(db, sql_update);
// create new ServerPlayer and setup // create new ServerPlayer and setup
ServerPlayer *player = new ServerPlayer(lobby()); ServerPlayer *player = new ServerPlayer(lobby());
player->setSocket(client); player->setSocket(client);
@ -375,7 +422,8 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
player->setScreenName(name); player->setScreenName(name);
player->setAvatar(obj["avatar"].toString()); player->setAvatar(obj["avatar"].toString());
player->setId(obj["id"].toString().toInt()); player->setId(obj["id"].toString().toInt());
broadcast("ServerMessage", tr("%1 logged in").arg(player->getScreenName())); // broadcast("ServerMessage", tr("%1 logged
// in").arg(player->getScreenName()));
players.insert(player->getId(), player); players.insert(player->getId(), player);
// tell the lobby player's basic property // tell the lobby player's basic property
@ -410,6 +458,14 @@ void Server::onRoomAbandoned() {
updateRoomList(); updateRoomList();
// room->deleteLater(); // room->deleteLater();
idle_rooms.push(room); idle_rooms.push(room);
// 懒得改了!
// 这里出bug的原因还是在于room的销毁工作没做好
// room销毁这块bug很多
// if (idle_rooms.length() > 10) {
// auto junk = idle_rooms[0];
// idle_rooms.removeFirst();
// junk->deleteLater();
// }
#ifdef QT_DEBUG #ifdef QT_DEBUG
qDebug() << rooms.size() << "running room(s)," << idle_rooms.size() qDebug() << rooms.size() << "running room(s)," << idle_rooms.size()
<< "idle room(s)."; << "idle room(s).";
@ -419,7 +475,8 @@ void Server::onRoomAbandoned() {
void Server::onUserDisconnected() { void Server::onUserDisconnected() {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender()); ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
qInfo() << "Player" << player->getId() << "disconnected"; qInfo() << "Player" << player->getId() << "disconnected";
broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName())); // broadcast("ServerMessage", tr("%1 logged
// out").arg(player->getScreenName()));
Room *room = player->getRoom(); Room *room = player->getRoom();
if (room->isStarted()) { if (room->isStarted()) {
if (room->getObservers().contains(player)) { if (room->getObservers().contains(player)) {
@ -435,7 +492,15 @@ void Server::onUserDisconnected() {
} }
} }
void Server::onUserStateChanged() {} void Server::onUserStateChanged() {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
auto room = player->getRoom();
if (!room || room->isLobby() || room->isAbandoned()) {
return;
}
room->doBroadcastNotify(room->getPlayers(), "NetStateChanged",
QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString()));
}
RSA *Server::initServerRSA() { RSA *Server::initServerRSA() {
RSA *rsa = RSA_new(); RSA *rsa = RSA_new();
@ -451,7 +516,8 @@ RSA *Server::initServerRSA() {
BIO_free_all(bp_pub); BIO_free_all(bp_pub);
BIO_free_all(bp_pri); BIO_free_all(bp_pri);
QFile("server/rsa").setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); QFile("server/rsa")
.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
BN_free(bne); BN_free(bne);
} }
FILE *keyFile = fopen("server/rsa_pub", "r"); FILE *keyFile = fopen("server/rsa_pub", "r");

View File

@ -20,15 +20,25 @@ static void sigintHandler(int) {
void Shell::helpCommand(QStringList &) { void Shell::helpCommand(QStringList &) {
qInfo("Frequently used commands:"); qInfo("Frequently used commands:");
#define HELP_MSG(a, b) \ #define HELP_MSG(a, b) \
qInfo((a), Color((b), fkShell::Cyan).toUtf8().constData()); qInfo((a), Color((b), fkShell::Cyan).toUtf8().constData());
HELP_MSG("%s: Display this help message.", "help"); HELP_MSG("%s: Display this help message.", "help");
HELP_MSG("%s: Shut down the server.", "quit"); HELP_MSG("%s: Shut down the server.", "quit");
HELP_MSG("%s: List all online players.", "lsplayer"); HELP_MSG("%s: List all online players.", "lsplayer");
HELP_MSG("%s: List all running rooms.", "lsroom"); HELP_MSG("%s: List all running rooms.", "lsroom");
HELP_MSG("%s: Kick a player by his id.", "kick"); HELP_MSG("%s: Kick a player by his <id>.", "kick");
HELP_MSG("%s: Broadcast message.", "msg"); HELP_MSG("%s: Broadcast message.", "msg");
HELP_MSG("%s: Ban 1 or more accounts by their <name>.", "ban");
HELP_MSG("%s: Unban 1 or more accounts by their <name>.", "unban");
HELP_MSG(
"%s: Ban 1 or more IP address according to somebody's 'lastLoginIp'. "
"At least 1 <name> required.",
"banip");
HELP_MSG(
"%s: Unban 1 or more IP address according to somebody's 'lastLoginIp'. "
"At least 1 <name> required.",
"unbanip");
qInfo(); qInfo();
qInfo("===== Package commands ====="); qInfo("===== Package commands =====");
HELP_MSG("%s: Install a new package from <url>.", "install"); HELP_MSG("%s: Install a new package from <url>.", "install");
@ -36,7 +46,7 @@ void Shell::helpCommand(QStringList &) {
HELP_MSG("%s: List all packages.", "lspkg"); HELP_MSG("%s: List all packages.", "lspkg");
HELP_MSG("%s: Enable a package.", "enable"); HELP_MSG("%s: Enable a package.", "enable");
HELP_MSG("%s: Disable a package.", "disable"); HELP_MSG("%s: Disable a package.", "disable");
HELP_MSG("%s: Upgrade a package.", "upgrade"); HELP_MSG("%s: Upgrade a package. Leave empty to upgrade all.", "upgrade");
qInfo("For more commands, check the documentation."); qInfo("For more commands, check the documentation.");
#undef HELP_MSG #undef HELP_MSG
@ -86,7 +96,12 @@ void Shell::removeCommand(QStringList &list) {
void Shell::upgradeCommand(QStringList &list) { void Shell::upgradeCommand(QStringList &list) {
if (list.isEmpty()) { if (list.isEmpty()) {
qWarning("The 'upgrade' command need a package name to upgrade."); // qWarning("The 'upgrade' command need a package name to upgrade.");
auto arr = QJsonDocument::fromJson(Pacman->listPackages().toUtf8()).array();
foreach (auto a, arr) {
auto obj = a.toObject();
Pacman->upgradePack(obj["name"].toString());
}
return; return;
} }
@ -155,6 +170,111 @@ void Shell::msgCommand(QStringList &list) {
ServerInstance->broadcast("ServerMessage", msg); ServerInstance->broadcast("ServerMessage", msg);
} }
static void banAccount(sqlite3 *db, const QString &name, bool banned) {
if (!CheckSqlString(name))
return;
QString sql_find = QString("SELECT * FROM userinfo \
WHERE name='%1';")
.arg(name);
auto result = SelectFromDatabase(db, sql_find);
if (result.isEmpty())
return;
auto obj = result[0].toObject();
int id = obj["id"].toString().toInt();
ExecSQL(db, QString("UPDATE userinfo SET banned=%2 WHERE id=%1;")
.arg(id)
.arg(banned ? 1 : 0));
if (banned) {
auto p = ServerInstance->findPlayer(id);
if (p) {
p->kicked();
}
qInfo("Banned %s.", name.toUtf8().constData());
} else {
qInfo("Unbanned %s.", name.toUtf8().constData());
}
}
void Shell::banCommand(QStringList &list) {
if (list.isEmpty()) {
qWarning("The 'ban' command needs at least 1 <name>.");
return;
}
auto db = ServerInstance->getDatabase();
foreach (auto name, list) {
banAccount(db, name, true);
}
}
void Shell::unbanCommand(QStringList &list) {
if (list.isEmpty()) {
qWarning("The 'unban' command needs at least 1 <name>.");
return;
}
auto db = ServerInstance->getDatabase();
foreach (auto name, list) {
banAccount(db, name, false);
}
}
static void banIPByName(sqlite3 *db, const QString &name, bool banned) {
if (!CheckSqlString(name))
return;
QString sql_find = QString("SELECT * FROM userinfo \
WHERE name='%1';")
.arg(name);
auto result = SelectFromDatabase(db, sql_find);
if (result.isEmpty())
return;
auto obj = result[0].toObject();
int id = obj["id"].toString().toInt();
auto addr = obj["lastLoginIp"].toString();
if (banned) {
ExecSQL(db, QString("INSERT INTO banip VALUES('%1');").arg(addr));
auto p = ServerInstance->findPlayer(id);
if (p) {
p->kicked();
}
qInfo("Banned IP %s.", addr.toUtf8().constData());
} else {
ExecSQL(db, QString("DELETE FROM banip WHERE ip='%1';").arg(addr));
qInfo("Unbanned IP %s.", addr.toUtf8().constData());
}
}
void Shell::banipCommand(QStringList &list) {
if (list.isEmpty()) {
qWarning("The 'banip' command needs at least 1 <name>.");
return;
}
auto db = ServerInstance->getDatabase();
foreach (auto name, list) {
banIPByName(db, name, true);
}
}
void Shell::unbanipCommand(QStringList &list) {
if (list.isEmpty()) {
qWarning("The 'unbanip' command needs at least 1 <name>.");
return;
}
auto db = ServerInstance->getDatabase();
foreach (auto name, list) {
banIPByName(db, name, false);
}
}
Shell::Shell() { Shell::Shell() {
setObjectName("Shell"); setObjectName("Shell");
signal(SIGINT, sigintHandler); signal(SIGINT, sigintHandler);
@ -173,6 +293,10 @@ Shell::Shell() {
handlers["disable"] = &Shell::disableCommand; handlers["disable"] = &Shell::disableCommand;
handlers["kick"] = &Shell::kickCommand; handlers["kick"] = &Shell::kickCommand;
handlers["msg"] = &Shell::msgCommand; handlers["msg"] = &Shell::msgCommand;
handlers["ban"] = &Shell::banCommand;
handlers["unban"] = &Shell::unbanCommand;
handlers["banip"] = &Shell::banipCommand;
handlers["unbanip"] = &Shell::unbanipCommand;
} }
handler_map = handlers; handler_map = handlers;
} }

View File

@ -25,6 +25,10 @@ private:
void disableCommand(QStringList &); void disableCommand(QStringList &);
void kickCommand(QStringList &); void kickCommand(QStringList &);
void msgCommand(QStringList &); void msgCommand(QStringList &);
void banCommand(QStringList &);
void banipCommand(QStringList &);
void unbanCommand(QStringList &);
void unbanipCommand(QStringList &);
}; };
#endif #endif