单机热更新,以及Docker (#336)

以及emo公主的判定小卡换图

---------

Co-authored-by: seven <786852516@qq.com>
This commit is contained in:
notify 2024-04-02 01:00:10 +08:00 committed by GitHub
parent e840a3b322
commit 9dd4f55c86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 275 additions and 43 deletions

View File

@ -3,6 +3,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Fk.Common
import Fk.Pages
import Fk.RoomElement
@ -22,18 +23,29 @@ Flickable {
width: parent.width - 40
x: 20
// TODO: player details
Text {
id: screenName
font.pixelSize: 18
color: "#E4D5A0"
}
RowLayout {
spacing: 8
Avatar {
id: avatar
Layout.preferredWidth: 56
Layout.preferredHeight: 56
general: "diaochan"
}
Text {
id: playerGameData
Layout.fillWidth: true
font.pixelSize: 18
color: "#E4D5A0"
ColumnLayout {
Text {
id: screenName
font.pixelSize: 18
color: "#E4D5A0"
}
Text {
id: playerGameData
Layout.fillWidth: true
font.pixelSize: 18
color: "#E4D5A0"
}
}
}
RowLayout {
@ -163,6 +175,7 @@ Flickable {
if (id === 0) return;
root.pid = id;
avatar.general = extra_data.photo.avatar;
screenName.text = extra_data.photo.screenName;
mainChara.name = extra_data.photo.general;
deputyChara.name = extra_data.photo.deputyGeneral;
@ -182,9 +195,9 @@ Flickable {
const h = (totalTime / 3600).toFixed(2);
const m = Math.floor(totalTime / 60);
if (m < 100) {
playerGameData.text += " " + luatr("TotalGameTime: %1 min").arg(m);
screenName.text += " (" + luatr("TotalGameTime: %1 min").arg(m) + ")";
} else {
playerGameData.text += " " + luatr("TotalGameTime: %1 h").arg(h);
screenName.text += " (" + luatr("TotalGameTime: %1 h").arg(h) + ")";
}
}

View File

@ -1,25 +1,29 @@
FROM debian
USER root
FROM linuxcontainers/debian-slim:latest
# install dependencies
RUN apt update -y && apt upgrade -y && \
apt install -y \
gcc g++ cmake \
liblua5.4-dev libsqlite3-dev libreadline-dev libssl-dev libgit2-dev swig qt6-base-dev qt6-tools-dev-tools \
gosu && \
apt clean -y && \
rm -rf /var/lib/apt/lists/*
# prepare source code
COPY . /FreeKill
#update apt dependencies
RUN apt update -y && apt upgrade -y
# compile and install
RUN mkdir -p /FreeKill/build && \
cd /FreeKill/build && cp -r /usr/include/lua5.4/* ../include && cmake .. -DFK_SERVER_ONLY= && make && \
cd /FreeKill && cmake --install build --config Release && \
cp /FreeKill/docker/docker-entrypoint.sh / && chmod +x /docker-entrypoint.sh && \
mkdir /data && \
cd / && rm -rf /FreeKill
#install compile tools
RUN apt install -y gcc g++ cmake
RUN apt install -y liblua5.4-dev libsqlite3-dev libreadline-dev libssl-dev libgit2-dev swig qt6-base-dev qt6-tools-dev-tools
#change workdir to FreeKill
WORKDIR /FreeKill
#compile source code
RUN mkdir build && cd build && cp -r /usr/include/lua5.4/* ../include && cmake .. -DFK_SERVER_ONLY=
RUN cd build && make
#build soft link
RUN ln -s build/FreeKill
WORKDIR /data
EXPOSE 9527
ENTRYPOINT ["/FreeKill/FreeKill", "-s"]
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["FreeKill", "-s"]

View File

@ -0,0 +1,29 @@
#!/bin/bash
USER_ID=${LOCAL_USER_ID:-1000}
if [ "${1#-}" != "$1" ]; then
set -- FreeKill -s "$@"
fi
if [ "$1" = 'FreeKill' -a "$(id -u)" = '0' ]; then
id -u freekill >&/dev/null
if [ $? -ne 0 ]; then
useradd --shell /bin/bash -u $USER_ID -o -c "" -m freekill
usermod -aG root freekill
export HOME=/home/freekill
mkdir -p $HOME/.local/share
ln -s /data $HOME/.local/share/FreeKill
chown -R freekill:freekill $HOME
if [ ! -d "/data/server" ]; then
cp -r /usr/local/share/FreeKill/server /data
fi
if [ ! -d "/data/packages" ]; then
cp -r /usr/local/share/FreeKill/packages /data
fi
fi
chown -R freekill /data
exec gosu freekill "$0" "$@"
fi
exec "$@"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -784,4 +784,8 @@ function GetMiniGame(gtype, p, data)
}
end
function ReloadPackage(path)
Fk:reloadPackage(path)
end
dofile "lua/client/i18n/init.lua"

View File

@ -47,7 +47,6 @@ function Engine:initialize()
end
Fk = self
self.extensions = {
["standard"] = { "standard" },
["standard_cards"] = { "standard_cards" },
@ -123,6 +122,76 @@ function Engine:loadPackage(pack)
self:addGameModes(pack.game_modes)
end
-- Don't do this
local package = package
function Engine:reloadPackage(path)
path = path:sub(1, #path - 4)
local oldPkg = package.loaded[path]
package.loaded[path] = nil
local ok, err = pcall(require, path)
if not ok then
package.loaded[path] = oldPkg
print("reload failed:", err)
return
end
-- 阉割版重载机制,反正单机用
local function replace(t, skill)
if not t then return end
for k, s in pairs(t) do
if s.name == skill.name then
t[k] = skill
break
end
end
end
local function f(p)
self.packages[p.name] = p
local room = Fk:currentRoom()
local skills = p:getSkills()
local related = {}
for _, skill in ipairs(skills) do
table.insertTableIfNeed(related, skill.related_skills)
end
table.insertTableIfNeed(skills, related)
for _, skill in ipairs(skills) do
if self.skills[skill.name].class ~= skill.class then
fk.qCritical("cannot change class of skill: " .. skill.name)
goto CONTINUE
end
self.skills[skill.name] = skill
if skill:isInstanceOf(TriggerSkill) and RoomInstance then
local logic = room.logic
for _, event in ipairs(skill.refresh_events) do
replace(logic.refresh_skill_table[event], skill)
end
for _, event in ipairs(skill.events) do
replace(logic.skill_table[event], skill)
end
end
if skill:isInstanceOf(StatusSkill) then
replace(room.status_skills[skill.class], skill)
end
for _, p in ipairs(room.players) do
replace(p.player_skills, skill)
end
::CONTINUE::
end
end
local pkg = package.loaded[path]
if type(pkg) ~= "table" then return end
if pkg.class and pkg:isInstanceOf(Package) then
f(pkg)
elseif path:endsWith("init") and not path:find("/ai/") then
for _, p in ipairs(pkg) do f(p) end
end
end
--- 加载所有拓展包。
---
--- Engine会在packages/下搜索所有含有init.lua的文件夹并把它们作为拓展包加载进来。
@ -215,7 +284,7 @@ end
function Engine:addSkill(skill)
assert(skill.class:isSubclassOf(Skill))
if self.skills[skill.name] ~= nil then
error(string.format("Duplicate skill %s detected", skill.name))
fk.qWarning(string.format("Duplicate skill %s detected", skill.name))
end
self.skills[skill.name] = skill

View File

@ -10,6 +10,8 @@ function RandomAI:useActiveSkill(skill, card)
local room = self.room
local player = self.player
if skill:isInstanceOf(ViewAsSkill) then return "" end
local filter_func = skill.cardFilter
if card then
filter_func = Util.FalseFunc
@ -68,6 +70,7 @@ function RandomAI:useVSSkill(skill, pattern, cancelable, extra_data)
local player = self.player
local room = self.room
local precondition
if not skill then return nil end
if self.command == "PlayCard" then
precondition = skill:enabledAtPlay(player)

View File

@ -181,6 +181,12 @@ request_handlers["newroom"] = function(s, id)
s:registerRoom(id)
end
request_handlers["reloadpackage"] = function(room, id, reqlist)
if not IsConsoleStart() then return end
local path = reqlist[3]
Fk:reloadPackage(path)
end
-- 处理异步请求的协程,本身也是个死循环就是了。
-- 为了适应调度器,目前又暂且将请求分为“耗时请求”和不耗时请求。
-- 耗时请求处理后会立刻挂起。不耗时的请求则会不断处理直到请求队列空后再挂起。

View File

@ -154,3 +154,7 @@ function InitScheduler(_thread)
requestRoom.thread = _thread
Pcall(mainLoop)
end
function IsConsoleStart()
return requestRoom.thread:isConsoleStart()
end

View File

@ -97,3 +97,25 @@ target_link_libraries(FreeKill PRIVATE
)
install(TARGETS FreeKill DESTINATION bin)
install(DIRECTORY
${PROJECT_SOURCE_DIR}/audio
${PROJECT_SOURCE_DIR}/fonts
${PROJECT_SOURCE_DIR}/image
${PROJECT_SOURCE_DIR}/lua
${PROJECT_SOURCE_DIR}/packages
${PROJECT_SOURCE_DIR}/Fk
${PROJECT_SOURCE_DIR}/server
DESTINATION share/FreeKill
)
install(FILES
${PROJECT_SOURCE_DIR}/fk_ver
DESTINATION share/FreeKill
)
if (NOT DEFINED FK_SERVER_ONLY)
install(FILES
${CMAKE_BINARY_DIR}/zh_CN.qm
${CMAKE_BINARY_DIR}/en_US.qm
DESTINATION share/FreeKill
)
endif()

View File

@ -3,7 +3,11 @@
#include "client.h"
#include "client_socket.h"
#include "clientplayer.h"
#include "qmlbackend.h"
#include "util.h"
#include "server.h"
#include <qforeach.h>
#include <qlogging.h>
Client *ClientInstance = nullptr;
ClientPlayer *Self = nullptr;
@ -78,7 +82,10 @@ void Client::changeSelf(int id) {
lua_State *Client::getLuaState() { return L; }
void Client::installAESKey(const QByteArray &key) { router->installAESKey(key); }
void Client::installAESKey(const QByteArray &key) {
startWatchFiles();
router->installAESKey(key);
}
void Client::saveRecord(const QString &json, const QString &fname) {
if (!QDir("recording").exists()) {
@ -90,6 +97,48 @@ void Client::saveRecord(const QString &json, const QString &fname) {
c.close();
}
bool Client::isConsoleStart() const {
if (!ClientInstance || !ServerInstance) {
return false;
}
return router->isConsoleStart();
}
void Client::startWatchFiles() {
if (!isConsoleStart()) return;
if (!fsWatcher.files().empty()) return;
QFile flist("flist.txt");
if (!flist.open(QIODevice::ReadOnly)) {
qCritical("Cannot open flist.txt. Won't watch files.");
fsWatcher.addPath("fk_ver"); // dummy
}
auto md5pairs = flist.readAll().split(';');
foreach (auto md5, md5pairs) {
if (md5.isEmpty()) continue;
auto fname = md5.split('=')[0];
if (fname.startsWith("packages") && fname.endsWith(".lua")) {
fsWatcher.addPath(fname);
}
}
connect(&fsWatcher, &QFileSystemWatcher::fileChanged, this,
&Client::updateLuaFiles);
}
void Client::processReplay(const QString &c, const QString &j) {
callLua(c, j);
}
void Client::updateLuaFiles(const QString &path) {
if (!isConsoleStart()) return;
Backend->showToast(tr("File %1 changed, reloading...").arg(path));
QThread::msleep(100);
Backend->callLuaFunction("ReloadPackage", { path });
ClientInstance->notifyServer("PushRequest",
QString("reloadpackage,%1").arg(path));
// according to QT documentation
if (!fsWatcher.files().contains(path) && QFile::exists(path)) {
fsWatcher.addPath(path);
}
}

View File

@ -5,6 +5,7 @@
#include "router.h"
#include "clientplayer.h"
#include <qfilesystemwatcher.h>
#ifndef FK_SERVER_ONLY
#include "qmlbackend.h"
@ -34,18 +35,24 @@ public:
void saveRecord(const QString &json, const QString &fname);
bool isConsoleStart() const;
void startWatchFiles();
signals:
void error_message(const QString &msg);
public slots:
void processReplay(const QString &, const QString &);
private slots:
void updateLuaFiles(const QString &path);
private:
Router *router;
QMap<int, ClientPlayer *> players;
ClientPlayer *self;
lua_State *L;
QFileSystemWatcher fsWatcher;
};
extern Client *ClientInstance;

View File

@ -173,6 +173,16 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
int main(int argc, char *argv[]) {
// 初始化一下各种杂项信息
QThread::currentThread()->setObjectName("Main");
qInstallMessageHandler(fkMsgHandler);
QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill");
QCoreApplication::setApplicationVersion(FK_VERSION);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
prepareForLinux();
#endif
if (!info_log) {
info_log = fopen("freekill.server.info.log", "w+");
if (!info_log) {
@ -186,15 +196,6 @@ int main(int argc, char *argv[]) {
}
}
qInstallMessageHandler(fkMsgHandler);
QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill");
QCoreApplication::setApplicationVersion(FK_VERSION);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
prepareForLinux();
#endif
#ifndef FK_CLIENT_ONLY
// 分析命令行,如果有 -s 或者 --server 就在命令行直接开服务器
QCommandLineParser parser;

View File

@ -53,6 +53,10 @@ void Router::installAESKey(const QByteArray &key) {
socket->installAESKey(key);
}
bool Router::isConsoleStart() const {
return socket->peerAddress() == "127.0.0.1";
}
#ifndef FK_CLIENT_ONLY
void Router::setReplyReadySemaphore(QSemaphore *semaphore) {
extraReplyReadySemaphore = semaphore;

View File

@ -32,6 +32,7 @@ public:
void setSocket(ClientSocket *socket);
void removeSocket();
void installAESKey(const QByteArray &key);
bool isConsoleStart() const;
#ifndef FK_CLIENT_ONLY
void setReplyReadySemaphore(QSemaphore *semaphore);

View File

@ -4,6 +4,10 @@
#include "server.h"
#include "util.h"
#ifndef FK_SERVER_ONLY
#include "client.h"
#endif
RoomThread::RoomThread(Server *m_server) {
setObjectName("Room");
this->m_server = m_server;
@ -105,3 +109,12 @@ void RoomThread::tryTerminate() {
bool RoomThread::isTerminated() const {
return terminated;
}
bool RoomThread::isConsoleStart() const {
#ifndef FK_SERVER_ONLY
if (!ClientInstance) return false;
return ClientInstance->isConsoleStart();
#else
return false;
#endif
}

View File

@ -31,6 +31,7 @@ class RoomThread : public QThread {
void tryTerminate();
bool isTerminated() const;
bool isConsoleStart() const;
protected:
virtual void run();

View File

@ -39,6 +39,8 @@ public:
void trySleep(int ms);
bool isTerminated() const;
bool isConsoleStart() const;
};
%{