Dev (#233)
- 以不存在的游戏模式开房时,自动替换成身份局 - 烧条满3管直接踢 - 游戏结束时掉线玩家(可能故意杀后台逃跑的)会受到逃跑惩罚 - 修git闪退的bug - 关于页面补全作者信息 - 增加重载配置文件的shell命令 - 禁将方案切换
This commit is contained in:
parent
8bfe087374
commit
cc0228dc03
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.4,主要利用QtQuick开发UI,同时也使用Qt的网络库开发服务端程序。
|
||||
|
||||
官网: https://www.qt.io
|
||||
]],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -31,6 +31,7 @@ private:
|
|||
void unbanCommand(QStringList &);
|
||||
void unbanipCommand(QStringList &);
|
||||
void unbanUuidCommand(QStringList &);
|
||||
void reloadConfCommand(QStringList &);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -72,3 +72,9 @@ public:
|
|||
bool thinking();
|
||||
void setThinking(bool t);
|
||||
};
|
||||
|
||||
%extend ServerPlayer {
|
||||
void emitKick() {
|
||||
emit $self->kicked();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue