* fixbug: card use history

* doc for doRequest

* add badges for readme

* update readme
This commit is contained in:
notify 2023-02-27 11:59:24 +08:00 committed by GitHub
parent cd7e4c9bd3
commit 357c692a69
3 changed files with 160 additions and 7 deletions

View File

@ -1,13 +1,62 @@
# FreeKill # FreeKill
![](https://img.shields.io/github/repo-size/notify-ctrl/freekill?color=green)
![](https://img.shields.io/github/languages/top/Notify-ctrl/FreeKill)
![](https://img.shields.io/github/license/notify-ctrl/freekill)
![](https://img.shields.io/github/v/tag/notify-ctrl/freekill)
![](https://img.shields.io/github/issues/notify-ctrl/freekill)
![](https://img.shields.io/github/stars/notify-ctrl/freekill?style=social)
___ ___
试图打造一个最适合diy玩家游玩的民间三国杀所有的一切都是为了更好的制作diy而设计的。 ## 关于本项目
项目仍处于啥都没有的阶段。不过我为了整理思路,也写了点[文档](./doc/index.md)。 FreeKill是一款开源的三国杀游戏但其目的不在于补完官方所有武将而是着力于提供一个最适合DIY的框架。
### 依赖的库
以下是FreeKill运行所必不可少的依赖库
* [![](https://img.shields.io/badge/qt6-50D160?style=for-the-badge&logo=qt&logoColor=white)](https://www.qt.io)
* [![](https://img.shields.io/badge/lua5.4-030380?style=for-the-badge&logo=lua)](https://www.lua.org)
* [![](https://img.shields.io/badge/sqlite3-7ABEEA?style=for-the-badge&logo=sqlite)](https://www.sqlite.org)
* [![](https://img.shields.io/badge/libgit2-FFFFFF?style=for-the-badge&logo=git)](https://www.libgit2.org)
* [![](https://img.shields.io/badge/openssl-721412?style=for-the-badge&logo=openssl)](https://www.openssl.org)
FreeKill在编译过程中需要用到cmake, flex, bison, swig。
___
## 安装和使用
Release页面提供Windows版和Android版的打包好的文件请直接下载使用。
Linux用户则需要从头开始编译不过对于ArchLinux上可以从AUR中安装
$ yay -S freekill
初始界面是连入服务器的界面,可以选择加入服务器,也可以单机开始游戏。
DIY教程仍在施工中……
___ ___
## 如何构建 ## 如何构建
[编译教程](./doc/dev/compile.md) 关于如何从头构建FreeKill详见[编译教程](./doc/dev/compile.md)。
___
## 参与其中
若您能为FreeKill做出贡献我们将不胜感激。
如若您能提出良好的建议请fork本仓库然后提交PR。您也可以单纯只提出一个issue或者为repo点一个star。再次感谢您的帮助。
有关做出贡献的细节,详见`CONTRIBUTING.md`。(施工中)
___
## 许可证
本仓库使用GPLv3作为许可证。详见`LICENSE`文件。

View File

@ -62,6 +62,106 @@ ___
___ ___
## 对游戏内交互的实例分析
下面围绕着askForSkillInvoke对游戏内的交互进行简析其他交互也是一样的原理。
```lua
function Room:askForSkillInvoke(player, skill_name, data)
local command = "AskForSkillInvoke"
self:notifyMoveFocus(player, skill_name)
local invoked = false
local result = self:doRequest(player, command, skill_name)
if result ~= "" then invoked = true end
return invoked
end
```
在这期间,一共涉及两步走:
1. Room向所有玩家发送消息让大家看到进度条和进度条上显示的原因notifyMoveFocus
2. Room向询问的玩家发送一次Request信息进行询问然后返回玩家发回的reply。
首先看第一步通知。这里涉及的函数是doNotify。调查notifyMoveFocus的代码即可知道
调查`ServerPlayer:doNotify`发现:
```lua
self.serverplayer:doNotify(command, jsonData)
```
这里的self.serverplayer其实指的是C++中的ServerPlayer实例因此这一行代码实际上调用的是C++中的ServerPlayer::doNotify。调查C++中对应的函数发现实际上调用了Router::notify调查Router::notify发现发送了一个信号量调查Router::setSocket发现这个信号量连接到了ClientSocket::send。调查ClientSocket::send后发现
```cpp
void ClientSocket::send(const QByteArray &msg)
{
if (msg.length() >= 1024) {
auto comp = qCompress(msg);
auto _msg = "Compressed" + comp.toBase64() + "\n";
socket->write(_msg);
socket->flush();
}
socket->write(msg);
if (!msg.endsWith("\n"))
socket->write("\n");
socket->flush();
}
```
核心在于socket->write这里其实就调用了QTcpSocket::write正式向网络中发送数据。从前面的分析也慢慢可以发现发送的其实就是json字符串。
那么问题又来了,客户端接收到服务端发送的通知时,如何进行响应呢?
这就涉及到Router::handlePacket函数具体的信号槽连接方式不赘述这个函数在socket接收到消息时就会自行调用。
其中有这样的一段:
```cpp
if (type & TYPE_NOTIFICATION) {
if (type & DEST_CLIENT) {
ClientInstance->callLua(command, jsonData);
}
```
调用了ClientInstance::callLua函数这个函数不做详细追究只要知道他调用了这个lua函数即可
```lua
self.client.callback = function(_self, command, jsonData)
local cb = fk.client_callback[command]
if (type(cb) == "function") then
cb(jsonData)
else
self:notifyUI(command, jsonData);
end
end
```
至此我们已经可以基本得出结论Client在接收到信息时就根据信息的command类型调用相应的函数若无则直接调用qml中的函数。
接下来聊聊doRequest。和前面类似doRequest最终也是向玩家发送了一个JSON字符串但是然后它就进入了等待回复的状态。在此期间可以使用waitForReply函数尝试获取对方的reply若无则得到默认结果__notready然后在Lua侧进行进一步处理。
客户在收到request类型的消息后可以用reply对服务端进行答复。reply本身也是JSON字符串服务端在handlePacket环节发觉这个是reply后就知道自己已经收到回复了。这时用waitForReply即可得到正确的回复结果。
在Lua侧对waitForReply其实有所封装
```lua
while true do
result = player.serverplayer:waitForReply(0)
if result ~= "__notready" then
return result
end
local rest = timeout * 1000 - (os.getms() - start) / 1000
if timeout and rest <= 0 then
return ""
end
coroutine.yield(rest)
end
```
这里就是一个死循环不断的试图读取玩家的回复直到超时为止。因为waitForReply指定的等待时间为0所以会立刻返回这也是为什么waitForReply在读取reply时需要加锁的原因因为读取操作很频繁此时若lua发现玩家并未给出答复就会调用coroutine.yield切换到其他线程去做点别的事情比如处理旁观请求调用QThread::msleep睡眠一阵子等等别的协程办完事情后再次切换回这个协程yield函数返回然后开启新一轮循环如此往复直到等待时间耗尽或者收到了回复。
___
## 对掉线的处理 ## 对掉线的处理
因为每个连接都对应着一个`new ClientSocket`和`new ServerPlayer`,所以对于掉线的处理要慎重,处理不当会导致内存泄漏以及各种奇怪的错误。 因为每个连接都对应着一个`new ClientSocket`和`new ServerPlayer`,所以对于掉线的处理要慎重,处理不当会导致内存泄漏以及各种奇怪的错误。

View File

@ -182,15 +182,19 @@ GameRule = fk.CreateTriggerSkill{
end, end,
[fk.EventPhaseEnd] = function() [fk.EventPhaseEnd] = function()
if player.phase == Player.Play then if player.phase == Player.Play then
player:setCardUseHistory("", 0, Player.HistoryPhase) for _, p in ipairs(room.players) do
player:setSkillUseHistory("", 0, Player.HistoryPhase) p:setCardUseHistory("", 0, Player.HistoryPhase)
p:setSkillUseHistory("", 0, Player.HistoryPhase)
end
end end
end, end,
[fk.EventPhaseChanging] = function() [fk.EventPhaseChanging] = function()
-- TODO: copy but dont copy all -- TODO: copy but dont copy all
if data.to == Player.NotActive then if data.to == Player.NotActive then
player:setCardUseHistory("", 0, Player.HistoryTurn) for _, p in ipairs(room.players) do
player:setSkillUseHistory("", 0, Player.HistoryTurn) p:setCardUseHistory("", 0, Player.HistoryTurn)
p:setSkillUseHistory("", 0, Player.HistoryTurn)
end
end end
end, end,
[fk.AskForPeaches] = function() [fk.AskForPeaches] = function()