diff --git a/Fk/Config.qml b/Fk/Config.qml index f9fd64d6..1391182f 100644 --- a/Fk/Config.qml +++ b/Fk/Config.qml @@ -40,6 +40,7 @@ QtObject { // Client data property string serverMotd: "" property list serverHiddenPacks: [] + property bool serverEnableBot: true property int roomCapacity: 0 property int roomTimeout: 0 property bool enableFreeAssign: false diff --git a/Fk/Logic.js b/Fk/Logic.js index b6cb6ed3..e80404b2 100644 --- a/Fk/Logic.js +++ b/Fk/Logic.js @@ -100,9 +100,10 @@ callbacks["BackToStart"] = (jsonData) => { callbacks["SetServerSettings"] = (j) => { const data = JSON.parse(j); - const [ motd, hiddenPacks ] = data; + const [ motd, hiddenPacks, enableBots ] = data; config.serverMotd = motd; config.serverHiddenPacks = hiddenPacks; + config.serverEnableBot = enableBots; }; callbacks["EnterLobby"] = (jsonData) => { diff --git a/Fk/Pages/GeneralsOverview.qml b/Fk/Pages/GeneralsOverview.qml index 3cf2ab37..445b9289 100644 --- a/Fk/Pages/GeneralsOverview.qml +++ b/Fk/Pages/GeneralsOverview.qml @@ -178,7 +178,6 @@ Item { function addSkillAudio(skill) { if (addSpecialSkillAudio(skill)) return; - console.log(skill, 'normal add') const skilldata = JSON.parse(Backend.callLuaFunction("GetSkillData", [skill])); if (!skilldata) return; const extension = skilldata.extension; diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index 6f1de120..fb8c85e7 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -185,6 +185,7 @@ Item { text: Backend.translate("Add Robot") visible: isOwner && !isStarted && !isFull anchors.centerIn: parent + enabled: config.serverEnableBot onClicked: { ClientInstance.notifyServer("AddRobot", "[]"); } diff --git a/Fk/RoomElement/Photo.qml b/Fk/RoomElement/Photo.qml index 153ce8d5..e845de9f 100644 --- a/Fk/RoomElement/Photo.qml +++ b/Fk/RoomElement/Photo.qml @@ -390,7 +390,12 @@ Item { Image { // id: saveme visible: root.dead || root.dying || root.surrendered - source: SkinBank.DEATH_DIR + (root.dead ? root.role : root.surrendered ? "surrender" : "saveme") + source: { + if (root.dead) { + return SkinBank.getRoleDeathPic(root.role); + } + return SkinBank.DEATH_DIR + (root.surrendered ? "surrender" : "saveme") + } anchors.centerIn: photoMask } diff --git a/Fk/skin-bank.js b/Fk/skin-bank.js index 5360de46..74c608d0 100644 --- a/Fk/skin-bank.js +++ b/Fk/skin-bank.js @@ -19,6 +19,17 @@ var PIXANIM_DIR = AppPath + "/image/anim/" var TILE_ICON_DIR = AppPath + "/image/button/tileicon/" var LOBBY_IMG_DIR = AppPath + "/image/lobby/"; +const searchPkgResource = function(path, name, suffix) { + let ret; + for (let dir of Backend.ls(AppPath + "/packages/")) { + if (Pacman.getDisabledPacks().includes(dir) || + dir.endsWith(".disabled") // in case + ) continue; + ret = AppPath + "/packages/" + dir + path + name + suffix; + if (Backend.exists(ret)) return ret; + } +} + function getGeneralExtraPic(name, extra) { const data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [name])); const extension = data.extension; @@ -54,11 +65,8 @@ function getCardPicture(cidOrName) { if (Backend.exists(path)) { return path; } else { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - path = AppPath + "/packages/" + dir + "/image/card/" + name + ".png"; - if (Backend.exists(path)) return path; - } + let ret = searchPkgResource("/image/card/", name, ".png"); + if (ret) return ret; } return CARD_DIR + "unknown.png"; } @@ -70,11 +78,8 @@ function getDelayedTrickPicture(name) { if (Backend.exists(path)) { return path; } else { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - path = AppPath + "/packages/" + dir + "/image/card/delayedTrick/" + name + ".png"; - if (Backend.exists(path)) return path; - } + let ret = searchPkgResource("/image/card/delayedTrick/", name, ".png"); + if (ret) return ret; } return DELAYED_TRICK_DIR + "unknown.png"; } @@ -88,11 +93,8 @@ function getEquipIcon(cid, icon) { if (Backend.exists(path)) { return path; } else { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - path = AppPath + "/packages/" + dir + "/image/card/equipIcon/" + name + ".png"; - if (Backend.exists(path)) return path; - } + let ret = searchPkgResource("/image/card/equipIcon/", name, ".png"); + if (ret) return ret; } return EQUIP_ICON_DIR + "unknown.png"; } @@ -100,11 +102,8 @@ function getEquipIcon(cid, icon) { function getPhotoBack(kingdom) { let path = PHOTO_BACK_DIR + kingdom + ".png"; if (!Backend.exists(path)) { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - path = AppPath + "/packages/" + dir + "/image/kingdom/" + kingdom + "-back.png"; - if (Backend.exists(path)) return path; - } + let ret = searchPkgResource("/image/kingdom/", kingdom, "-back.png"); + if (ret) return ret; } else { return path; } @@ -114,12 +113,8 @@ function getPhotoBack(kingdom) { function getGeneralCardDir(kingdom) { let path = GENERALCARD_DIR + kingdom + ".png"; if (!Backend.exists(path)) { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - path = AppPath + "/packages/" + dir + "/image/kingdom/" + kingdom + "-back.png"; - if (Backend.exists(path)) - return AppPath + "/packages/" + dir + "/image/kingdom/"; - } + let ret = searchPkgResource("/image/kingdom/", kingdom, "-back.png"); + if (ret) return ret.slice(0, ret.lastIndexOf('/')) + "/"; } else { return GENERALCARD_DIR; } @@ -130,20 +125,25 @@ function getRolePic(role) { if (Backend.exists(path)) { return path; } else { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - path = AppPath + "/packages/" + dir + "/image/role/" + role + ".png"; - if (Backend.exists(path)) return path; - } + let ret = searchPkgResource("/image/role/", role, ".png"); + if (ret) return ret; } return ROLE_DIR + "unknown.png"; } -function getMarkPic(mark) { - for (let dir of Backend.ls(AppPath + "/packages/")) { - if (dir.endsWith(".disabled")) continue; - let path = AppPath + "/packages/" + dir + "/image/mark/" + mark + ".png"; - if (Backend.exists(path)) return path; +function getRoleDeathPic(role) { + let path = DEATH_DIR + role + ".png"; + if (Backend.exists(path)) { + return path; + } else { + let ret = searchPkgResource("/image/role/death/", role, ".png"); + if (ret) return ret; } + return DEATH_DIR + "hidden.png"; +} + +function getMarkPic(mark) { + let ret = searchPkgResource("/image/mark/", mark, ".png"); + if (ret) return ret; return ""; } diff --git a/freekill.server.config.json.example b/freekill.server.config.json.example index 0f89beea..cc807afb 100644 --- a/freekill.server.config.json.example +++ b/freekill.server.config.json.example @@ -5,5 +5,6 @@ "capacity": 100, "tempBanTime": 20, "motd": "Welcome!", - "hiddenPacks": [] + "hiddenPacks": [], + "enableBots": true } diff --git a/lua/core/engine.lua b/lua/core/engine.lua index a66d14a6..3cd53dce 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -123,8 +123,11 @@ function Engine:loadPackages() table.removeOne(directories, "standard_cards") table.removeOne(directories, "maneuvering") + local _disable_packs = json.decode(fk.GetDisabledPacks()) + for _, dir in ipairs(directories) do - if (not string.find(dir, ".disabled")) and FileIO.isDir("packages/" .. dir) + if (not string.find(dir, ".disabled")) and not table.contains(_disable_packs, dir) + and FileIO.isDir("packages/" .. dir) and FileIO.exists("packages/" .. dir .. "/init.lua") then local pack = require(string.format("packages.%s", dir)) -- Note that instance of Package is a table too diff --git a/lua/core/player.lua b/lua/core/player.lua index 6f9e3acc..5d978940 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -137,7 +137,7 @@ end function Player:getGeneralMaxHp() local general = Fk.generals[type(self:getMark("__heg_general")) == "string" and self:getMark("__heg_general") or self.general] - local deputy = Fk.generals[type(self:getMark("__heg_deputy")) == "string" and self:getMark("__heg_deputy") or self.deputy] + local deputy = Fk.generals[type(self:getMark("__heg_deputy")) == "string" and self:getMark("__heg_deputy") or self.deputyGeneral] if not deputy then return general.maxHp + general.mainMaxHpAdjustedValue @@ -492,7 +492,6 @@ function Player:distanceTo(other, mode, ignore_dead) mode = mode or "both" if other == self then return 0 end if not ignore_dead and other.dead then - print(other.general .. " is dead!") return -1 end if self:isRemoved() or other:isRemoved() then diff --git a/lua/server/ai/random_ai.lua b/lua/server/ai/random_ai.lua index 5d561e3b..f788115c 100644 --- a/lua/server/ai/random_ai.lua +++ b/lua/server/ai/random_ai.lua @@ -133,6 +133,7 @@ random_cb.AskForUseCard = function(self, jsonData) local pattern = data[2] or card_name local cancelable = data[4] or true local exp = Exppattern:Parse(pattern) + local avail_cards = table.filter(player:getCardIds("he"), function(id) return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id)) end) diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 79c21996..ab670d4c 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -116,6 +116,7 @@ local function _waitForReply(player, timeout) player.room:checkNoHuman() player.ai:readRequestData() local reply = player.ai:makeReply() + if reply == "" then reply = "__cancel" end return reply end while true do diff --git a/src/core/packman.cpp b/src/core/packman.cpp index 89c69474..4253f9c3 100644 --- a/src/core/packman.cpp +++ b/src/core/packman.cpp @@ -18,15 +18,21 @@ PackMan::PackMan(QObject *parent) : QObject(parent) { db = OpenDatabase("./packages/packages.db", "./packages/init.sql"); QDir d("packages"); + + // For old version + foreach (auto e, QmlBackend::ls("packages")) { + if (e.endsWith(".disabled") && d.exists(e) && !d.exists(e.chopped(9))) { + d.rename(e, e.chopped(9)); + } + } + 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"); + if (!enabled) { + disabled_packs << pack; } } @@ -40,6 +46,10 @@ PackMan::~PackMan() { sqlite3_close(db); } +QStringList PackMan::getDisabledPacks() { + return disabled_packs; +} + QString PackMan::getPackSummary() { return SelectFromDb( db, "SELECT name, url, hash FROM packages WHERE enabled = 1;"); @@ -142,24 +152,17 @@ void PackMan::enablePack(const QString &pack) { ExecSQL( db, QString("UPDATE packages SET enabled = 1 WHERE name = '%1';").arg(pack)); - QDir d("packages"); - int i = 0; - while (!d.rename(pack + ".disabled", pack) && i < 3) { - QThread::currentThread()->msleep(1); - i++; - } + + disabled_packs.removeOne(pack); } void PackMan::disablePack(const QString &pack) { ExecSQL( db, QString("UPDATE packages SET enabled = 0 WHERE name = '%1';").arg(pack)); - QDir d("packages"); - int i = 0; - while (!d.rename(pack, pack + ".disabled") && i < 3) { - QThread::currentThread()->msleep(1); - i++; - } + + if (!disabled_packs.contains(pack)) + disabled_packs << pack; } void PackMan::updatePack(const QString &pack) { @@ -208,7 +211,7 @@ void PackMan::removePack(const QString &pack) { return; bool enabled = result[0].toObject()["enabled"].toString().toInt() == 1; ExecSQL(db, QString("DELETE FROM packages WHERE name = '%1';").arg(pack)); - QDir d(QString("packages/%1%2").arg(pack).arg(enabled ? "" : ".disabled")); + QDir d(QString("packages/%1").arg(pack)); d.removeRecursively(); } diff --git a/src/core/packman.h b/src/core/packman.h index f45a8e26..69c845f0 100644 --- a/src/core/packman.h +++ b/src/core/packman.h @@ -3,6 +3,7 @@ #ifndef _PACKMAN_H #define _PACKMAN_H +#include class PackMan : public QObject { Q_OBJECT public: @@ -10,6 +11,7 @@ public: ~PackMan(); QString getPackSummary(); + Q_INVOKABLE QStringList getDisabledPacks(); Q_INVOKABLE void loadSummary(const QString &, bool useThread = false); Q_INVOKABLE void downloadNewPack(const QString &url, bool useThread = false); Q_INVOKABLE void enablePack(const QString &pack); @@ -27,6 +29,7 @@ private: int checkout_branch(const QString &name, const QString &branch); int status(const QString &name); // return 1 if the workdir is modified QString head(const QString &name); // get commit hash of HEAD + QStringList disabled_packs; }; extern PackMan *Pacman; diff --git a/src/core/util.cpp b/src/core/util.cpp index 1e1ab0b9..c14c3293 100644 --- a/src/core/util.cpp +++ b/src/core/util.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "util.h" +#include "packman.h" #include #include #include @@ -159,8 +160,9 @@ static void writeDirMD5(QFile &dest, const QString &dir, auto entries = d.entryInfoList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); auto re = QRegularExpression::fromWildcard(filter); + const auto disabled = Pacman->getDisabledPacks(); foreach (QFileInfo info, entries) { - if (info.isDir() && !info.fileName().endsWith(".disabled")) { + if (info.isDir() && !info.fileName().endsWith(".disabled") && !disabled.contains(info.fileName())) { writeDirMD5(dest, info.filePath(), filter); } else { if (re.match(info.fileName()).hasMatch()) { @@ -213,6 +215,10 @@ QString GetDeviceUuid() { #endif } +QString GetDisabledPacks() { + return JsonArray2Bytes(QJsonArray::fromStringList(Pacman->getDisabledPacks())); +} + QString Color(const QString &raw, fkShell::TextColor color, fkShell::TextType type) { #ifdef Q_OS_LINUX diff --git a/src/core/util.h b/src/core/util.h index 72daff01..6ac6a439 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -22,6 +22,8 @@ QJsonDocument String2Json(const QString &str); QString GetDeviceUuid(); +QString GetDisabledPacks(); + namespace fkShell { enum TextColor { Black, diff --git a/src/network/router.cpp b/src/network/router.cpp index 5b3a6e23..be73a555 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -280,7 +280,7 @@ void Router::handlePacket(const QByteArray &rawPacket) { if (command == "QuitRoom") { room->removePlayer(player); } else if (command == "AddRobot") { - room->addRobot(player); + if (ServerInstance->getConfig("enableBots").toBool()) room->addRobot(player); } else if (command == "KickPlayer") { int i = jsonData.toInt(); auto p = room->findPlayer(i); diff --git a/src/server/server.cpp b/src/server/server.cpp index 3d925f01..f1ec91f1 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -514,6 +514,7 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, player->doNotify("SetServerSettings", JsonArray2Bytes({ getConfig("motd"), getConfig("hiddenPacks"), + getConfig("enableBots"), })); lobby()->addPlayer(player); @@ -623,6 +624,7 @@ void Server::readConfig() { SET_DEFAULT_CONFIG("tempBanTime", 20); SET_DEFAULT_CONFIG("motd", "Welcome!"); SET_DEFAULT_CONFIG("hiddenPacks", QJsonArray()); + SET_DEFAULT_CONFIG("enableBots", true); } QJsonValue Server::getConfig(const QString &key) { return config.value(key); } diff --git a/src/swig/freekill.i b/src/swig/freekill.i index db297eb2..f8b95d85 100644 --- a/src/swig/freekill.i +++ b/src/swig/freekill.i @@ -22,3 +22,4 @@ const char *FK_VER = FK_VERSION; %include "server.i" extern char *FK_VER; +QString GetDisabledPacks();