Misc (#169)
- 扣上限心碎 - 进服维护的各种跟后端稳定性有关的代码 - 断线重连/旁观时候计入技能次数 - ban人和banip,相应的也有解禁 - 开房设置现在可以滑动 - 完善网络错误报错 - 现在开始游戏之前需要等待和所有人准备 - 指示掉线之人和走小道之人 - 掉线和走小道的人不再被AI接管 - 延时锦囊牌素材从拓展包找 - 拓展包管理界面UI优化,下载失败的包可以在管理拓展包中删除
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
image/photo/notready.png
Normal file
After Width: | Height: | Size: 112 B |
BIN
image/photo/state/run.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
packages/standard_cards/image/card/delayedTrick/lightning.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
52
src/main.cpp
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -59,6 +59,7 @@ class Room : public QThread {
|
|||
void initLua();
|
||||
|
||||
void roomStart();
|
||||
void manuallyStart();
|
||||
LuaFunction startGame;
|
||||
|
||||
QString fetchRequest();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|