- 以不存在的游戏模式开房时,自动替换成身份局
- 烧条满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 hideUseless
property var disabledGenerals: []
property var disableGeneralSchemes: []
property int disableSchemeIdx: 0
property int preferredTimeout
property int preferredLuckTime
@ -43,6 +45,10 @@ QtObject {
property bool replaying: false
property var blockedUsers: []
onDisabledGeneralsChanged: {
disableGeneralSchemes[disableSchemeIdx] = disabledGenerals;
}
function loadConf() {
conf = JSON.parse(Backend.loadConf());
winX = conf.winX ?? 100;
@ -67,6 +73,8 @@ QtObject {
preferredTimeout = conf.preferredTimeout ?? 15;
preferredLuckTime = conf.preferredLuckTime ?? 0;
disabledGenerals = conf.disabledGenerals ?? [];
disableGeneralSchemes = conf.disableGeneralSchemes ?? [ disabledGenerals ];
disableSchemeIdx = conf.disableSchemeIdx ?? 0;
}
function saveConf() {
@ -92,6 +100,8 @@ QtObject {
conf.preferredTimeout = preferredTimeout;
conf.preferredLuckTime = preferredLuckTime;
conf.disabledGenerals = disabledGenerals;
conf.disableGeneralSchemes = disableGeneralSchemes;
conf.disableSchemeIdx = disableSchemeIdx;
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 {
text: Backend.translate("Audio Settings")
}
TabButton {
text: Backend.translate("Ban General Settings")
}
}
SwipeView {
@ -36,5 +39,6 @@ Item {
UserInfo {}
BGSetting {}
AudioSetting {}
BanGeneralSetting {}
}
}

View File

@ -106,10 +106,12 @@ Item {
onClicked: {
if (pkgEnabled === "0") {
Pacman.enablePack(pkgName);
pkgEnabled = "1";
} else {
Pacman.disablePack(pkgName);
pkgEnabled = "0";
}
updatePackageList();
// updatePackageList();
}
}
@ -120,7 +122,15 @@ Item {
anchors.rightMargin: 8
onClicked: {
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
onClicked: {
Pacman.removePack(pkgName);
updatePackageList();
// updatePackageList();
packageModel.remove(index);
}
}

View File

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

View File

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

View File

@ -94,6 +94,10 @@ function Room:initialize(_room)
self.settings = json.decode(self.room:settings())
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])
self.disabled_generals = self.settings.disabledGenerals
end

View File

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

View File

@ -16,6 +16,20 @@ PackMan *Pacman;
PackMan::PackMan(QObject *parent) : QObject(parent) {
git_libgit2_init();
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
git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, "./certs");
#endif
@ -128,16 +142,24 @@ void PackMan::enablePack(const QString &pack) {
ExecSQL(
db,
QString("UPDATE packages SET enabled = 1 WHERE name = '%1';").arg(pack));
QDir d(QString("packages"));
d.rename(pack + ".disabled", pack);
QDir d("packages");
int i = 0;
while (!d.rename(pack + ".disabled", pack) && i < 3) {
QThread::currentThread()->msleep(1);
i++;
}
}
void PackMan::disablePack(const QString &pack) {
ExecSQL(
db,
QString("UPDATE packages SET enabled = 0 WHERE name = '%1';").arg(pack));
QDir d(QString("packages"));
d.rename(pack, pack + ".disabled");
QDir d("packages");
int i = 0;
while (!d.rename(pack, pack + ".disabled") && i < 3) {
QThread::currentThread()->msleep(1);
i++;
}
}
void PackMan::updatePack(const QString &pack) {

View File

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

View File

@ -636,9 +636,19 @@ void Server::temporarilyBan(int playerId) {
if (!player) return;
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);
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: 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 running rooms.", "lsroom");
HELP_MSG("%s: Reload server config file.", "reloadconf");
HELP_MSG("%s: Kick a player by his <id>.", "kick");
HELP_MSG("%s: Broadcast message.", "msg");
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() {
setObjectName("Shell");
signal(SIGINT, sigintHandler);
@ -373,6 +380,7 @@ Shell::Shell() {
handlers["unbanip"] = &Shell::unbanipCommand;
handlers["banuuid"] = &Shell::banUuidCommand;
handlers["unbanuuid"] = &Shell::unbanUuidCommand;
handlers["reloadconf"] = &Shell::reloadConfCommand;
}
handler_map = handlers;
}
@ -384,7 +392,8 @@ void Shell::run() {
"This is free software, and you are welcome to redistribute it under\n");
printf("certain conditions; For more information visit "
"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) {
char *bytes = readline("fk> ");
@ -394,6 +403,8 @@ void Shell::run() {
return;
}
qInfo("Running command: \"%s\"", bytes);
if (!strcmp(bytes, "crash")) {
qFatal("Crashing."); // should dump core
return;

View File

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

View File

@ -72,3 +72,9 @@ public:
bool thinking();
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);
}
QString QmlBackend::readClipboard() {
return QGuiApplication::clipboard()->text();
}
void QmlBackend::setAESKey(const QString &key) { aes_key = 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 copyToClipboard(const QString &s);
Q_INVOKABLE QString readClipboard();
Q_INVOKABLE void setAESKey(const QString &key);
Q_INVOKABLE QString getAESKey() const;