- 扣上限心碎
- 进服维护的各种跟后端稳定性有关的代码
- 断线重连/旁观时候计入技能次数
- 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
/freekill.client.config.json
/freekill.server.config.json
/freekill.server.error.log
/freekill.server.info.log
/flist.txt
# windeployqt

View File

@ -43,12 +43,22 @@ Flickable {
Button {
text: Backend.translate("Give Shoe")
enabled: Math.random() < 0.5
enabled: Math.random() < 0.3
onClicked: {
root.givePresent("Shoe");
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

View File

@ -4,194 +4,201 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Room Name")
}
TextField {
id: roomName
maximumLength: 64
font.pixelSize: 18
text: Backend.translate("$RoomName").arg(Self.screenName)
}
}
Flickable {
flickableDirection: Flickable.AutoFlickIfNeeded
clip: true
contentHeight: layout.height
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Player num")
}
SpinBox {
id: playerNum
from: 2
to: 8
value: config.preferedPlayerNum
onValueChanged: {
config.preferedPlayerNum = value;
ColumnLayout {
id: layout
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Room Name")
}
TextField {
id: roomName
maximumLength: 64
font.pixelSize: 18
text: Backend.translate("$RoomName").arg(Self.screenName)
}
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Game Mode")
}
ComboBox {
id: gameModeCombo
textRole: "name"
model: ListModel {
id: gameModeList
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Player num")
}
SpinBox {
id: playerNum
from: 2
to: 8
value: config.preferedPlayerNum
onCurrentIndexChanged: {
let data = gameModeList.get(currentIndex);
playerNum.from = data.minPlayer;
playerNum.to = data.maxPlayer;
config.preferedMode = data.orig_name;
onValueChanged: {
config.preferedPlayerNum = value;
}
}
}
}
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("Game Mode")
}
}
}
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));
ComboBox {
id: gameModeCombo
textRole: "name"
model: ListModel {
id: gameModeList
}
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();
}
}
}
onCurrentIndexChanged: {
let data = gameModeList.get(currentIndex);
playerNum.from = data.minPlayer;
playerNum.to = data.maxPlayer;
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;
config.preferedMode = data.orig_name;
}
}
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 {
id: root
Button {
text: qsTr("Quit")
anchors.right: parent.right
onClicked: {
mainStack.pop();
}
}
Component {
id: packageDelegate
Item {
height: 22
width: packageList.width
RowLayout {
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")
}
ToolBar {
id: bar
width: parent.width
RowLayout {
anchors.fill: parent
ToolButton {
icon.source: AppPath + "/image/modmaker/back"
onClicked: mainStack.pop();
}
Label {
text: qsTr("Package Manager")
horizontalAlignment: Qt.AlignHCenter
Layout.fillWidth: true
}
ToolButton {
icon.source: AppPath + "/image/modmaker/menu"
onClicked: menu.open()
TapHandler {
onTapped: {
if (packageList.currentIndex === index) {
packageList.currentIndex = -1;
} else {
packageList.currentIndex = index;
Menu {
id: menu
y: bar.height
MenuItem {
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 {
id: packageModel
}
Rectangle {
width: parent.width
height: parent.height - bar.height - urlInstaller.height
anchors.top: bar.bottom
color: "snow"
opacity: 0.75
clip: true
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
Item {
Layout.preferredWidth: root.width * 0.9
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: "#88EEEEEE"
ListView {
id: packageList
anchors.fill: parent
model: ListModel {
id: packageModel
}
ListView {
id: packageList
anchors.fill: parent
delegate: ItemDelegate {
width: root.width
height: 64
contentHeight: packageDelegate.height * count
ScrollBar.vertical: ScrollBar {}
header: RowLayout {
height: 22
width: packageList.width
spacing: 16
ColumnLayout {
anchors.fill: parent
anchors.margins: 8
Text {
font.pixelSize: 20
text: qsTr("Name")
text: "<b>" + pkgName + "</b> (" + pkgVersion + ")"
font.pixelSize: 18
textFormat: Text.RichText
color: pkgEnabled === "1" ? "black" : "grey"
}
Text {
font.pixelSize: 20
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: "URL"
}
Text {
font.pixelSize: 20
text: qsTr("Version")
}
Text {
font.pixelSize: 20
text: qsTr("Enable")
text: pkgURL
color: pkgEnabled === "1" ? "black" : "grey"
}
}
delegate: packageDelegate
model: packageModel
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
Component.onCompleted: { currentIndex = -1; }
}
}
ColumnLayout {
Button {
enabled: packageList.currentItem
text: qsTr("Enable")
onClicked: {
let idx = packageList.currentIndex;
let name = packageModel.get(idx).pkgName;
Pacman.enablePack(name);
updatePackageList();
packageList.currentIndex = idx;
Button {
id: enableBtn
text: pkgEnabled === "0" ? qsTr("Enable") : qsTr("Disable")
anchors.right: upgradeBtn.left
anchors.rightMargin: 8
onClicked: {
if (pkgEnabled === "0") {
Pacman.enablePack(pkgName);
} else {
Pacman.disablePack(pkgName);
}
updatePackageList();
}
}
}
Button {
enabled: packageList.currentItem
text: qsTr("Disable")
onClicked: {
let idx = packageList.currentIndex;
let name = packageModel.get(idx).pkgName;
Pacman.disablePack(name);
updatePackageList();
packageList.currentIndex = idx;
Button {
id: upgradeBtn
text: qsTr("Upgrade")
anchors.right: delBtn.left
anchors.rightMargin: 8
onClicked: {
Pacman.upgradePack(pkgName);
updatePackageList();
}
}
}
Button {
enabled: packageList.currentItem
text: qsTr("Upgrade")
onClicked: {
let idx = packageList.currentIndex;
let name = packageModel.get(idx).pkgName;
Pacman.upgradePack(name);
updatePackageList();
packageList.currentIndex = idx;
Button {
id: delBtn
text: qsTr("Remove")
anchors.right: parent.right
anchors.rightMargin: 8
onClicked: {
Pacman.removePack(pkgName);
updatePackageList();
}
}
}
Button {
enabled: packageList.currentItem
text: qsTr("Remove")
onClicked: {
let idx = packageList.currentIndex;
let name = packageModel.get(idx).pkgName;
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."));
Backend.copyToClipboard(pkgURL);
toast.show(qsTr("Copied %1.").arg(pkgURL));
}
}
}
}
RowLayout {
Layout.fillWidth: true
TextField {
id: urlEdit
Layout.fillWidth: true
clip: true
}
Rectangle {
id: urlInstaller
width: parent.width
height: childrenRect.height
color: "snow"
opacity: 0.75
anchors.bottom: parent.bottom
Button {
text: qsTr("Install From URL")
enabled: urlEdit.text !== ""
onClicked: {
let url = urlEdit.text;
mainWindow.busy = true;
Pacman.downloadNewPack(url, true);
RowLayout {
width: parent.width
TextField {
id: urlEdit
Layout.fillWidth: true
clip: true
}
Button {
text: qsTr("Install From URL")
enabled: urlEdit.text !== ""
onClicked: {
let url = urlEdit.text;
mainWindow.busy = true;
Pacman.downloadNewPack(url, true);
}
}
}
}
}
function updatePackageList() {
packageModel.clear();
let data = JSON.parse(Pacman.listPackages());

View File

@ -17,6 +17,9 @@ Item {
property bool isOwner: false
property bool isStarted: false
property bool isFull: false
property bool isAllReady: false
property bool isReady: false
property alias popupBox: popupBox
property alias manualBox: manualBox
@ -79,13 +82,36 @@ Item {
}
}
Button {
text: "add robot"
visible: isOwner && !isStarted
text: Backend.translate("Add Robot")
visible: isOwner && !isStarted && !isFull
anchors.centerIn: parent
onClicked: {
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: [
State { name: "notactive" }, // Normal status
@ -195,6 +221,7 @@ Item {
Photo {
playerid: model.id
general: model.general
avatar: model.avatar
deputyGeneral: model.deputyGeneral
screenName: model.screenName
role: model.role
@ -210,6 +237,7 @@ Item {
chained: model.chained
drank: model.drank
isOwner: model.isOwner
ready: model.ready
onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected);
@ -830,6 +858,36 @@ Item {
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: {
toast.show(Backend.translate("$EnterRoom"));
playerNum = config.roomCapacity;
@ -839,6 +897,7 @@ Item {
id: i ? -1 : Self.id,
index: i, // For animating seat swap
general: i ? "" : Self.avatar,
avatar: i ? "" : Self.avatar,
deputyGeneral: "",
screenName: i ? "" : Self.screenName,
role: "unknown",
@ -853,7 +912,8 @@ Item {
faceup: true,
chained: false,
drank: 0,
isOwner: false
isOwner: false,
ready: false,
});
}

View File

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

View File

@ -25,7 +25,7 @@ Item {
Image {
height: 55 * 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
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
height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin

View File

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

View File

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

View File

@ -53,6 +53,22 @@ function getCardPicture(cidOrName) {
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) {
let data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid]));
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>
<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>
<source>Unknown error</source>
@ -187,14 +239,14 @@
<source>no such room</source>
<translation></translation>
</message>
<message>
<source>you have been banned!</source>
<translation></translation>
</message>
</context>
<context>
<name>PackageManage</name>
<message>
<source>Quit</source>
<translation>退</translation>
</message>
<message>
<source>Install From URL</source>
<translation>URL安装</translation>
@ -207,14 +259,6 @@
<source>Disable</source>
<translation></translation>
</message>
<message>
<source>Enabled</source>
<translation></translation>
</message>
<message>
<source>Disabled</source>
<translation></translation>
</message>
<message>
<source>Remove</source>
<translation></translation>
@ -224,20 +268,24 @@
<translation></translation>
</message>
<message>
<source>Name</source>
<translation></translation>
<source>Copied %1.</source>
<translation>(%1)</translation>
</message>
<message>
<source>Version</source>
<translation></translation>
<source>Package Manager</source>
<translation></translation>
</message>
<message>
<source>Copy URL</source>
<translation>URL</translation>
<source>Enable All</source>
<translation></translation>
</message>
<message>
<source>Copied.</source>
<translation></translation>
<source>Disable All</source>
<translation></translation>
</message>
<message>
<source>Upgrade All</source>
<translation></translation>
</message>
</context>

View File

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

View File

@ -102,12 +102,23 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self)
break
end
if table.every(room:getTag("LuckCardData").playerList, function(id)
return room:getTag("LuckCardData")[id].luckTime == 0
-- local ldata = room:getTag("LuckCardData")
local ldata = luck_data
if table.every(ldata.playerList, function(id)
return ldata[id].luckTime == 0
end) then
break
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)
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)
self:broadcastProperty(player, "maxHp")
self:sendLogEvent("ChangeMaxHp", {
player = player.id,
num = num,
})
if player.maxHp == 0 then
self:killPlayer({ who = player.id })
end

View File

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

View File

@ -102,6 +102,11 @@ local function _waitForReply(player, timeout)
local start = os.getms()
local state = player.serverplayer:getStateString()
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
local ret_msg = true
while ret_msg do
@ -259,6 +264,12 @@ function ServerPlayer:marshal(player)
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
room:notifyProperty(player, self, "role")
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 "util.h"
Client *ClientInstance;
ClientPlayer *Self;
Client *ClientInstance = nullptr;
ClientPlayer *Self = 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) {
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 = [=]() {
int error = clone(url);
if (error < 0)
return;
// if (error < 0)
// return;
auto u = url;
while (u.endsWith('/')) {
u.chop(1);
@ -94,15 +100,11 @@ void PackMan::downloadNewPack(const QString &url, bool useThread) {
if (fileName.endsWith(".git"))
fileName.chop(4);
auto result = SelectFromDatabase(db, QString("SELECT name FROM packages \
WHERE name = '%1';")
.arg(fileName));
auto result = SelectFromDatabase(db, sql_select.arg(fileName));
if (result.isEmpty()) {
ExecSQL(db, QString("INSERT INTO packages (name,url,hash,enabled) \
VALUES ('%1','%2','%3',1);")
.arg(fileName)
ExecSQL(db, sql_update.arg(fileName)
.arg(url)
.arg(head(fileName)));
.arg(error < 0 ? "XXXXXXXX" : head(fileName)));
}
};
if (useThread) {

View File

@ -97,12 +97,32 @@ static void prepareForLinux() {
}
#endif
static FILE *info_log = nullptr;
static FILE *err_log = nullptr;
void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg) {
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, "%s ",
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 threadName = QThread::currentThread()->objectName().toLatin1();
@ -111,19 +131,27 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
case QtDebugMsg:
fprintf(stderr, "%s[D] %s\n", threadName.constData(),
localMsg.constData());
fprintf(file, "%s[D] %s\n", threadName.constData(),
localMsg.constData());
break;
case QtInfoMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("I", Green).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"I", localMsg.constData());
break;
case QtWarningMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("W", Yellow, Bold).toUtf8().constData(),
localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"W", localMsg.constData());
break;
case QtCriticalMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("C", Red, Bold).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"C", localMsg.constData());
#ifndef FK_SERVER_ONLY
if (Backend != nullptr) {
Backend->notifyUI(
@ -135,6 +163,8 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
case QtFatalMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("E", Red, Bold).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"E", localMsg.constData());
break;
}
}
@ -143,6 +173,19 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
int main(int argc, char *argv[]) {
// 初始化一下各种杂项信息
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);
QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill");
@ -313,6 +356,15 @@ int main(int argc, char *argv[]) {
);
#endif
if (info_log) {
fclose(info_log);
info_log = nullptr;
}
if (err_log) {
fclose(err_log);
info_log = nullptr;
}
return ret;
#endif
}

View File

@ -2,6 +2,7 @@
#include "client_socket.h"
#include <openssl/aes.h>
#include <qabstractsocket.h>
#include <qrandom.h>
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
@ -96,8 +97,45 @@ void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) {
case QAbstractSocket::SocketAccessError:
reason = tr("Socket access error");
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:
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:
reason = tr("Unknown error");
break;

View File

@ -276,6 +276,16 @@ void Router::handlePacket(const QByteArray &rawPacket) {
room->removePlayer(player);
} else if (command == "AddRobot") {
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") {
room->chat(player, jsonData);
} else if (command == "PushRequest") {

View File

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

View File

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

View File

@ -19,7 +19,7 @@
#include "serverplayer.h"
#include "util.h"
Server *ServerInstance;
Server *ServerInstance = nullptr;
Server::Server(QObject *parent) : QObject(parent) {
ServerInstance = this;
@ -134,22 +134,36 @@ void Server::addPlayer(ServerPlayer *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() {
QJsonArray arr;
QJsonArray avail_arr;
foreach (Room *room, rooms) {
QJsonArray obj;
auto settings = QJsonDocument::fromJson(room->getSettings());
auto password = settings["password"].toString();
auto count = room->getPlayers().count(); // playerNum
auto cap = room->getCapacity(); // capacity
obj << room->getId(); // roomId
obj << room->getName(); // roomName
obj << settings["gameMode"]; // gameMode
obj << room->getPlayers().count(); // playerNum
obj << room->getCapacity(); // capacity
obj << room->getId(); // roomId
obj << room->getName(); // roomName
obj << settings["gameMode"]; // gameMode
obj << count;
obj << cap;
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);
lobby()->doBroadcastNotify(lobby()->getPlayers(), "UpdateRoomList",
@ -171,8 +185,22 @@ void Server::broadcast(const QString &command, const QString &jsonData) {
}
void Server::processNewConnection(ClientSocket *client) {
qInfo() << client->peerAddress() << "connected";
// version check, file check, ban IP, reconnect, etc
auto addr = client->peerAddress();
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,
[client]() { qInfo() << client->peerAddress() << "disconnected"; });
@ -334,7 +362,10 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
obj = result[0].toObject();
// check if this username already login
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
auto salt = obj["salt"].toString().toLatin1();
decrypted_pw.append(salt);
@ -351,12 +382,21 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
player->setSocket(client);
player->alive = true;
client->disconnect(this);
broadcast("ServerMessage",
tr("%1 backed").arg(player->getScreenName()));
room->pushRequest(QString("%1,reconnect").arg(id));
// broadcast("ServerMessage",
// tr("%1 backed").arg(player->getScreenName()));
if (room && !room->isLobby()) {
room->pushRequest(QString("%1,reconnect").arg(id));
} else {
// 懒得处理掉线玩家在大厅了!踢掉得了
player->doNotify("ErrorMsg", "Unknown Error");
player->kicked();
}
return;
} else {
error_msg = "others logged in with this name";
passed = false;
}
}
}
@ -365,6 +405,13 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
}
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
ServerPlayer *player = new ServerPlayer(lobby());
player->setSocket(client);
@ -375,7 +422,8 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
player->setScreenName(name);
player->setAvatar(obj["avatar"].toString());
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);
// tell the lobby player's basic property
@ -410,6 +458,14 @@ void Server::onRoomAbandoned() {
updateRoomList();
// room->deleteLater();
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
qDebug() << rooms.size() << "running room(s)," << idle_rooms.size()
<< "idle room(s).";
@ -419,7 +475,8 @@ void Server::onRoomAbandoned() {
void Server::onUserDisconnected() {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
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();
if (room->isStarted()) {
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 *rsa = RSA_new();
@ -451,7 +516,8 @@ RSA *Server::initServerRSA() {
BIO_free_all(bp_pub);
BIO_free_all(bp_pri);
QFile("server/rsa").setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QFile("server/rsa")
.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
BN_free(bne);
}
FILE *keyFile = fopen("server/rsa_pub", "r");

View File

@ -20,15 +20,25 @@ static void sigintHandler(int) {
void Shell::helpCommand(QStringList &) {
qInfo("Frequently used commands:");
#define HELP_MSG(a, b) \
#define HELP_MSG(a, b) \
qInfo((a), Color((b), fkShell::Cyan).toUtf8().constData());
HELP_MSG("%s: Display this help message.", "help");
HELP_MSG("%s: Shut down the server.", "quit");
HELP_MSG("%s: List all online players.", "lsplayer");
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: 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("===== Package commands =====");
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: Enable a package.", "enable");
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.");
#undef HELP_MSG
@ -86,7 +96,12 @@ void Shell::removeCommand(QStringList &list) {
void Shell::upgradeCommand(QStringList &list) {
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;
}
@ -155,6 +170,111 @@ void Shell::msgCommand(QStringList &list) {
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() {
setObjectName("Shell");
signal(SIGINT, sigintHandler);
@ -173,6 +293,10 @@ Shell::Shell() {
handlers["disable"] = &Shell::disableCommand;
handlers["kick"] = &Shell::kickCommand;
handlers["msg"] = &Shell::msgCommand;
handlers["ban"] = &Shell::banCommand;
handlers["unban"] = &Shell::unbanCommand;
handlers["banip"] = &Shell::banipCommand;
handlers["unbanip"] = &Shell::unbanipCommand;
}
handler_map = handlers;
}

View File

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