- 以不存在的游戏模式开房时,自动替换成身份局
- 烧条满3管直接踢
- 游戏结束时掉线玩家(可能故意杀后台逃跑的)会受到逃跑惩罚
- 修git闪退的bug
- 关于页面补全作者信息
- 增加重载配置文件的shell命令
- 禁将方案切换
This commit is contained in:
notify 2023-08-02 21:40:00 +08:00 committed by GitHub
parent 8bfe087374
commit cc0228dc03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 246 additions and 13 deletions

View File

@ -24,6 +24,8 @@ QtObject {
property bool disableMsgAudio property bool disableMsgAudio
property bool hideUseless property bool hideUseless
property var disabledGenerals: [] property var disabledGenerals: []
property var disableGeneralSchemes: []
property int disableSchemeIdx: 0
property int preferredTimeout property int preferredTimeout
property int preferredLuckTime property int preferredLuckTime
@ -43,6 +45,10 @@ QtObject {
property bool replaying: false property bool replaying: false
property var blockedUsers: [] property var blockedUsers: []
onDisabledGeneralsChanged: {
disableGeneralSchemes[disableSchemeIdx] = disabledGenerals;
}
function loadConf() { function loadConf() {
conf = JSON.parse(Backend.loadConf()); conf = JSON.parse(Backend.loadConf());
winX = conf.winX ?? 100; winX = conf.winX ?? 100;
@ -67,6 +73,8 @@ QtObject {
preferredTimeout = conf.preferredTimeout ?? 15; preferredTimeout = conf.preferredTimeout ?? 15;
preferredLuckTime = conf.preferredLuckTime ?? 0; preferredLuckTime = conf.preferredLuckTime ?? 0;
disabledGenerals = conf.disabledGenerals ?? []; disabledGenerals = conf.disabledGenerals ?? [];
disableGeneralSchemes = conf.disableGeneralSchemes ?? [ disabledGenerals ];
disableSchemeIdx = conf.disableSchemeIdx ?? 0;
} }
function saveConf() { function saveConf() {
@ -92,6 +100,8 @@ QtObject {
conf.preferredTimeout = preferredTimeout; conf.preferredTimeout = preferredTimeout;
conf.preferredLuckTime = preferredLuckTime; conf.preferredLuckTime = preferredLuckTime;
conf.disabledGenerals = disabledGenerals; conf.disabledGenerals = disabledGenerals;
conf.disableGeneralSchemes = disableGeneralSchemes;
conf.disableSchemeIdx = disableSchemeIdx;
Backend.saveConf(JSON.stringify(conf, undefined, 2)); Backend.saveConf(JSON.stringify(conf, undefined, 2));
} }

View File

@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
clip: true
ColumnLayout {
anchors.fill: parent
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "禁将方案"
}
ComboBox {
id: banCombo
textRole: "name"
model: ListModel {
id: banComboList
}
onCurrentIndexChanged: {
config.disableSchemeIdx = currentIndex;
config.disabledGenerals = config.disableGeneralSchemes[currentIndex];
}
}
Button {
text: "新建"
onClicked: {
const i = config.disableGeneralSchemes.length;
banComboList.append({
name: "方案" + (i + 1),
});
config.disableGeneralSchemes.push([]);
}
}
Button {
text: "清空"
onClicked: {
config.disabledGenerals = [];
}
}
}
Text {
Layout.fillWidth: true
Layout.margins: 8
wrapMode: Text.WrapAnywhere
text: "导出键会将这个方案的内容复制到剪贴板中;" +
"导入键会自动读取剪贴板,若可以导入则导入,不能导入则报错。"
}
RowLayout {
Button {
text: "导出"
onClicked: {
Backend.copyToClipboard(JSON.stringify(config.disabledGenerals));
toast.show("该禁将方案已经复制到剪贴板。");
}
}
Button {
text: "导入"
onClicked: {
const str = Backend.readClipboard();
let data;
try {
data = JSON.parse(str);
} catch (e) {
toast.show("导入失败不是合法的JSON字符串。");
return;
}
if (!data instanceof Array) {
toast.show("导入失败:数据格式不对。");
return;
}
for (let e of data) {
if (!(typeof e === "string" && Backend.translate(e) !== e)) {
toast.show("导入失败:含有未知的武将。");
return;
}
}
config.disabledGenerals = data;
toast.show("导入禁将方案成功。");
}
}
}
GridView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
cellWidth: width / 2
cellHeight: 24
model: config.disabledGenerals
delegate: Text {
width: listView.width
text: {
const prefix = modelData.split("__")[0];
let name = Backend.translate(modelData);
if (prefix !== modelData) {
name += (" (" + Backend.translate(prefix) + ")");
}
return name;
}
font.pixelSize: 16
}
}
}
Component.onCompleted: {
for (let i = 0; i < config.disableGeneralSchemes.length; i++) {
banComboList.append({
name: "方案" + (i + 1),
});
}
banCombo.currentIndex = config.disableSchemeIdx;
}
}

View File

@ -24,6 +24,9 @@ Item {
TabButton { TabButton {
text: Backend.translate("Audio Settings") text: Backend.translate("Audio Settings")
} }
TabButton {
text: Backend.translate("Ban General Settings")
}
} }
SwipeView { SwipeView {
@ -36,5 +39,6 @@ Item {
UserInfo {} UserInfo {}
BGSetting {} BGSetting {}
AudioSetting {} AudioSetting {}
BanGeneralSetting {}
} }
} }

View File

@ -106,10 +106,12 @@ Item {
onClicked: { onClicked: {
if (pkgEnabled === "0") { if (pkgEnabled === "0") {
Pacman.enablePack(pkgName); Pacman.enablePack(pkgName);
pkgEnabled = "1";
} else { } else {
Pacman.disablePack(pkgName); Pacman.disablePack(pkgName);
pkgEnabled = "0";
} }
updatePackageList(); // updatePackageList();
} }
} }
@ -120,7 +122,15 @@ Item {
anchors.rightMargin: 8 anchors.rightMargin: 8
onClicked: { onClicked: {
Pacman.upgradePack(pkgName); Pacman.upgradePack(pkgName);
updatePackageList(); // updatePackageList();
const data = JSON.parse(Pacman.listPackages());
const e = data[index];
packageModel.set(index, {
pkgName: e.name,
pkgURL: e.url,
pkgVersion: e.hash.substring(0, 8),
pkgEnabled: e.enabled
});
} }
} }
@ -131,7 +141,8 @@ Item {
anchors.rightMargin: 8 anchors.rightMargin: 8
onClicked: { onClicked: {
Pacman.removePack(pkgName); Pacman.removePack(pkgName);
updatePackageList(); // updatePackageList();
packageModel.remove(index);
} }
} }

View File

@ -38,8 +38,7 @@ Item {
width: parent.width width: parent.width
height: parent.height - bar.height height: parent.height - bar.height
anchors.top: bar.bottom anchors.top: bar.bottom
color: "snow" color: "#A0EFEFEF"
opacity: 0.75
clip: true clip: true
ListView { ListView {

View File

@ -24,6 +24,7 @@ Fk:loadTranslationTable{
["Audio Settings"] = "音频", ["Audio Settings"] = "音频",
["Disable message audio"] = "禁用聊天语音", ["Disable message audio"] = "禁用聊天语音",
["Hide unselectable cards"] = "下移不可选卡牌", ["Hide unselectable cards"] = "下移不可选卡牌",
["Ban General Settings"] = "禁将",
["Back"] = "返回", ["Back"] = "返回",
["Refresh Room List"] = "刷新房间列表", ["Refresh Room List"] = "刷新房间列表",
@ -84,13 +85,24 @@ Fk:loadTranslationTable{
便DIY为首要目的的开源三国杀游戏 便DIY为首要目的的开源三国杀游戏
https://github.com/Notify-ctrl/FreeKill https://github.com/Notify-ctrl/FreeKill
---
Notify Ho-spair
RalphR Nyutanislavsky xxyheaven
deepskybird
Mogara
]], ]],
["about_qt_description"] = [[ ["about_qt_description"] = [[
# Qt # Qt
Qt是一个C++使API Qt是一个C++使API
使Qt 6.2+QtQuick开发UI使Qt的网络库开发服务端程序 使Qt 6.4QtQuick开发UI使Qt的网络库开发服务端程序
https://www.qt.io https://www.qt.io
]], ]],

View File

@ -94,6 +94,10 @@ function Room:initialize(_room)
self.settings = json.decode(self.room:settings()) self.settings = json.decode(self.room:settings())
self.disabled_packs = self.settings.disabledPack self.disabled_packs = self.settings.disabledPack
if not Fk.game_modes[self.settings.gameMode] then
self.settings.gameMode = "aaa_role_mode"
end
table.insertTable(self.disabled_packs, Fk.game_mode_disabled[self.settings.gameMode]) table.insertTable(self.disabled_packs, Fk.game_mode_disabled[self.settings.gameMode])
self.disabled_generals = self.settings.disabledGenerals self.disabled_generals = self.settings.disabledGenerals
end end

View File

@ -14,6 +14,7 @@
---@field public phase_state table[] ---@field public phase_state table[]
---@field public phase_index integer ---@field public phase_index integer
---@field public role_shown boolean ---@field public role_shown boolean
---@field private _timewaste_count integer
---@field public ai AI ---@field public ai AI
---@field public ai_data any ---@field public ai_data any
local ServerPlayer = Player:subclass("ServerPlayer") local ServerPlayer = Player:subclass("ServerPlayer")
@ -34,6 +35,7 @@ function ServerPlayer:initialize(_self)
self.reply_cancel = false self.reply_cancel = false
self.phases = {} self.phases = {}
self.skipped_phases = {} self.skipped_phases = {}
self._timewaste_count = 0
self.ai = RandomAI:new(self) self.ai = RandomAI:new(self)
end end
@ -113,12 +115,19 @@ local function _waitForReply(player, timeout)
player.serverplayer:setThinking(true) player.serverplayer:setThinking(true)
result = player.serverplayer:waitForReply(0) result = player.serverplayer:waitForReply(0)
if result ~= "__notready" then if result ~= "__notready" then
player._timewaste_count = 0
player.serverplayer:setThinking(false) player.serverplayer:setThinking(false)
return result return result
end end
local rest = timeout * 1000 - (os.getms() - start) / 1000 local rest = timeout * 1000 - (os.getms() - start) / 1000
if timeout and rest <= 0 then if timeout and rest <= 0 then
player._timewaste_count = player._timewaste_count + 1
player.serverplayer:setThinking(false) player.serverplayer:setThinking(false)
if player._timewaste_count >= 3 then
player.serverplayer:emitKick()
end
return "" return ""
end end

View File

@ -16,6 +16,20 @@ PackMan *Pacman;
PackMan::PackMan(QObject *parent) : QObject(parent) { PackMan::PackMan(QObject *parent) : QObject(parent) {
git_libgit2_init(); git_libgit2_init();
db = OpenDatabase("./packages/packages.db", "./packages/init.sql"); db = OpenDatabase("./packages/packages.db", "./packages/init.sql");
QDir d("packages");
foreach (auto e, SelectFromDatabase(db, "SELECT name, enabled FROM packages;")) {
auto obj = e.toObject();
auto pack = obj["name"].toString();
auto enabled = obj["enabled"].toString().toInt() == 1;
if (enabled) {
d.rename(pack + ".disabled", pack);
} else {
d.rename(pack, pack + ".disabled");
}
}
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, "./certs"); git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, "./certs");
#endif #endif
@ -128,16 +142,24 @@ void PackMan::enablePack(const QString &pack) {
ExecSQL( ExecSQL(
db, db,
QString("UPDATE packages SET enabled = 1 WHERE name = '%1';").arg(pack)); QString("UPDATE packages SET enabled = 1 WHERE name = '%1';").arg(pack));
QDir d(QString("packages")); QDir d("packages");
d.rename(pack + ".disabled", pack); int i = 0;
while (!d.rename(pack + ".disabled", pack) && i < 3) {
QThread::currentThread()->msleep(1);
i++;
}
} }
void PackMan::disablePack(const QString &pack) { void PackMan::disablePack(const QString &pack) {
ExecSQL( ExecSQL(
db, db,
QString("UPDATE packages SET enabled = 0 WHERE name = '%1';").arg(pack)); QString("UPDATE packages SET enabled = 0 WHERE name = '%1';").arg(pack));
QDir d(QString("packages")); QDir d("packages");
d.rename(pack, pack + ".disabled"); int i = 0;
while (!d.rename(pack, pack + ".disabled") && i < 3) {
QThread::currentThread()->msleep(1);
i++;
}
} }
void PackMan::updatePack(const QString &pack) { void PackMan::updatePack(const QString &pack) {

View File

@ -535,6 +535,9 @@ void Room::gameOver() {
// 清理所有状态不是“在线”的玩家 // 清理所有状态不是“在线”的玩家
foreach (ServerPlayer *p, players) { foreach (ServerPlayer *p, players) {
if (p->getState() != Player::Online) { if (p->getState() != Player::Online) {
if (p->getState() == Player::Offline) {
server->temporarilyBan(p->getId());
}
p->deleteLater(); p->deleteLater();
} }
} }

View File

@ -636,9 +636,19 @@ void Server::temporarilyBan(int playerId) {
if (!player) return; if (!player) return;
auto socket = player->getSocket(); auto socket = player->getSocket();
if (!socket) return; QString addr;
if (!socket) {
QString sql_find = QString("SELECT * FROM userinfo \
WHERE id=%1;").arg(playerId);
auto result = SelectFromDatabase(db, sql_find);
if (result.isEmpty())
return;
auto addr = socket->peerAddress(); auto obj = result[0].toObject();
addr = obj["lastLoginIp"].toString();
} else {
addr = socket->peerAddress();
}
temp_banlist.append(addr); temp_banlist.append(addr);
auto time = getConfig("tempBanTime").toInt(); auto time = getConfig("tempBanTime").toInt();

View File

@ -25,8 +25,10 @@ void Shell::helpCommand(QStringList &) {
HELP_MSG("%s: Display this help message.", "help"); HELP_MSG("%s: Display this help message.", "help");
HELP_MSG("%s: Shut down the server.", "quit"); HELP_MSG("%s: Shut down the server.", "quit");
HELP_MSG("%s: Crash the server. Useful when encounter dead loop.", "crash");
HELP_MSG("%s: List all online players.", "lsplayer"); HELP_MSG("%s: List all online players.", "lsplayer");
HELP_MSG("%s: List all running rooms.", "lsroom"); HELP_MSG("%s: List all running rooms.", "lsroom");
HELP_MSG("%s: Reload server config file.", "reloadconf");
HELP_MSG("%s: Kick a player by his <id>.", "kick"); HELP_MSG("%s: Kick a player by his <id>.", "kick");
HELP_MSG("%s: Broadcast message.", "msg"); HELP_MSG("%s: Broadcast message.", "msg");
HELP_MSG("%s: Ban 1 or more accounts, IP, UUID by their <name>.", "ban"); HELP_MSG("%s: Ban 1 or more accounts, IP, UUID by their <name>.", "ban");
@ -349,6 +351,11 @@ void Shell::unbanUuidCommand(QStringList &list) {
} }
} }
void Shell::reloadConfCommand(QStringList &) {
ServerInstance->readConfig();
qInfo("Reloaded server config file.");
}
Shell::Shell() { Shell::Shell() {
setObjectName("Shell"); setObjectName("Shell");
signal(SIGINT, sigintHandler); signal(SIGINT, sigintHandler);
@ -373,6 +380,7 @@ Shell::Shell() {
handlers["unbanip"] = &Shell::unbanipCommand; handlers["unbanip"] = &Shell::unbanipCommand;
handlers["banuuid"] = &Shell::banUuidCommand; handlers["banuuid"] = &Shell::banUuidCommand;
handlers["unbanuuid"] = &Shell::unbanUuidCommand; handlers["unbanuuid"] = &Shell::unbanUuidCommand;
handlers["reloadconf"] = &Shell::reloadConfCommand;
} }
handler_map = handlers; handler_map = handlers;
} }
@ -384,7 +392,8 @@ void Shell::run() {
"This is free software, and you are welcome to redistribute it under\n"); "This is free software, and you are welcome to redistribute it under\n");
printf("certain conditions; For more information visit " printf("certain conditions; For more information visit "
"http://www.gnu.org/licenses.\n\n"); "http://www.gnu.org/licenses.\n\n");
printf("This is server cli. Enter \"help\" for usage hints.\n");
printf("[v%s] This is server cli. Enter \"help\" for usage hints.\n", FK_VERSION);
while (true) { while (true) {
char *bytes = readline("fk> "); char *bytes = readline("fk> ");
@ -394,6 +403,8 @@ void Shell::run() {
return; return;
} }
qInfo("Running command: \"%s\"", bytes);
if (!strcmp(bytes, "crash")) { if (!strcmp(bytes, "crash")) {
qFatal("Crashing."); // should dump core qFatal("Crashing."); // should dump core
return; return;

View File

@ -31,6 +31,7 @@ private:
void unbanCommand(QStringList &); void unbanCommand(QStringList &);
void unbanipCommand(QStringList &); void unbanipCommand(QStringList &);
void unbanUuidCommand(QStringList &); void unbanUuidCommand(QStringList &);
void reloadConfCommand(QStringList &);
}; };
#endif #endif

View File

@ -72,3 +72,9 @@ public:
bool thinking(); bool thinking();
void setThinking(bool t); void setThinking(bool t);
}; };
%extend ServerPlayer {
void emitKick() {
emit $self->kicked();
}
}

View File

@ -321,6 +321,10 @@ void QmlBackend::copyToClipboard(const QString &s) {
QGuiApplication::clipboard()->setText(s); QGuiApplication::clipboard()->setText(s);
} }
QString QmlBackend::readClipboard() {
return QGuiApplication::clipboard()->text();
}
void QmlBackend::setAESKey(const QString &key) { aes_key = key; } void QmlBackend::setAESKey(const QString &key) { aes_key = key; }
QString QmlBackend::getAESKey() const { return aes_key; } QString QmlBackend::getAESKey() const { return aes_key; }

View File

@ -50,6 +50,7 @@ public:
Q_INVOKABLE void playSound(const QString &name, int index = 0); Q_INVOKABLE void playSound(const QString &name, int index = 0);
Q_INVOKABLE void copyToClipboard(const QString &s); Q_INVOKABLE void copyToClipboard(const QString &s);
Q_INVOKABLE QString readClipboard();
Q_INVOKABLE void setAESKey(const QString &key); Q_INVOKABLE void setAESKey(const QString &key);
Q_INVOKABLE QString getAESKey() const; Q_INVOKABLE QString getAESKey() const;