diff --git a/docs/api/server.rst b/docs/api/server.rst
index fc65c98e..0d295118 100644
--- a/docs/api/server.rst
+++ b/docs/api/server.rst
@@ -1,2 +1,7 @@
Server
============
+
+.. toctree::
+ :maxdepth: 1
+
+ server/room.rst
diff --git a/docs/api/server/room.rst b/docs/api/server/room.rst
new file mode 100644
index 00000000..7410117d
--- /dev/null
+++ b/docs/api/server/room.rst
@@ -0,0 +1,4 @@
+Room
+=============
+
+.. lua:autoclass:: Room
diff --git a/docs/conf.py b/docs/conf.py
index 9c53dd18..11cfd9c3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -34,7 +34,6 @@ extensions = [
'sphinx.ext.coverage',
'sphinx.ext.mathjax',
'sphinx.ext.ifconfig',
- 'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'sphinxcontrib.luadomain',
'sphinx_lua',
@@ -71,9 +70,28 @@ exclude_patterns = []
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = 'sphinx_rtd_theme'
+html_theme = 'sphinx_book_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
+
+# PDF option
+latex_engine = 'xelatex'
+latex_elements = {
+ 'papersize': 'a4paper',
+ 'pointsize': '12pt',
+ 'fontpkg': r'''
+''',
+ 'fncychap': r'\usepackage[Sonny]{fncychap}',
+ 'preamble': r'''
+''',
+ 'figure_align': 'H',
+}
+
+latex_documents = [
+ ('index', 'manual.tex', 'FreeKill Handbook',
+ 'Notify', 'manual', False),
+]
+
diff --git a/docs/dev/compile.rst b/docs/dev/compile.rst
index 9b97fc7f..8bfef1b9 100644
--- a/docs/dev/compile.rst
+++ b/docs/dev/compile.rst
@@ -8,10 +8,15 @@ FreeKill采用最新的Qt进行构建,因此需要先安装Qt6的开发环境
无论是Win还是Linux,都建议用\ `Qt官方的下载器 `__\ 进行安装。当然了,在一些软件更新很频繁的Linux发行版里面,可能已经能从包管理器安装Qt6,对此后文细说。这个环节介绍用Qt安装器安装的步骤。
-Qt安装的流程不赘述。为了编译FreeKill,至少需要安装以下的组件: - Qt 6:
-MinGW 11.2.0 64-bit (不支持MSVC) - Qt 6: Qt5 Compat - Qt 6: Shader
-Tools (为了使用GraphicalEffects) - Qt 6: Multimedia -
-QtCreator(这个是安装器强制要你安装的) - CMake、Ninja - OpenSSL 1.1.1
+Qt安装的流程不赘述。为了编译FreeKill,至少需要安装以下的组件:
+
+- Qt 6: MinGW 11.2.0 64-bit (不支持MSVC)
+- Qt 6: Qt5 Compat
+- Qt 6: Shader Tools (为了使用GraphicalEffects)
+- Qt 6: Multimedia
+- QtCreator(这个是安装器强制要你安装的)
+- CMake、Ninja
+- OpenSSL 1.1.1
接下来根据平台的不同,步骤也稍有区别。
@@ -26,15 +31,9 @@ Windows
接下来使用QtCreator打开项目,然后尝试编译。
-这时遇到cmake报错:OpenSSL:Crypto not found.
-这是因为我们还没有告诉编译器OpenSSL的位置,点左侧“项目”,查看构建选项,在CMake的Initial
-Configuration中,点击添加按钮,新增String型环境变量OPENSSL_ROOT_DIR,将其值设为跟Qt一同安装的OpenSSL的位置(如C:/Qt/Tools/OpenSSL/Win_x64)。然后点下方的Re-configure
-with Initial Parameters,这样就能正常编译了。
+这时遇到cmake报错:OpenSSL:Crypto not found. 这是因为我们还没有告诉编译器OpenSSL的位置,点左侧“项目”,查看构建选项,在CMake的Initial Configuration中,点击添加按钮,新增String型环境变量OPENSSL_ROOT_DIR,将其值设为跟Qt一同安装的OpenSSL的位置(如C:/Qt/Tools/OpenSSL/Win_x64)。然后点下方的Re-configure with Initial Parameters,这样就能正常编译了。
-运行的话,在Qt
-Creator的项目选项->运行中,先将工作目录改为项目所在的目录(git仓库的目录)。然后先将编译好了的FreeKill.exe放到项目目录中,在目录下打开CMD,执行windeployqt
-FreeKill.exe。调整目录下的dll文件直到能运行起来为止,之后就可以在Qt
-Creator中正常运行和调试了。
+运行的话,在Qt Creator的项目选项->运行中,先将工作目录改为项目所在的目录(git仓库的目录)。然后先将编译好了的FreeKill.exe放到项目目录中,在目录下打开CMD,执行windeployqt FreeKill.exe。调整目录下的dll文件直到能运行起来为止,之后就可以在Qt Creator中正常运行和调试了。
--------------
diff --git a/docs/diy/01-env.rst b/docs/diy/01-env.rst
index 30864c2c..9d0e140b 100644
--- a/docs/diy/01-env.rst
+++ b/docs/diy/01-env.rst
@@ -31,15 +31,14 @@ Fk是游戏本身,也是拓展包运行的平台。事实上这份文档应该
- 需要有EmmyLua插件的支持
- 需要默认UTF-8格式保存代码文件
-..
+.. note::
- EmmyLua是一种特别的Lua注释方式,可以为本来弱类型的Lua语言提供类型支持,这对于像FreeKill这种稍有规模的Lua项目是十分必要的。目前能提供开箱即用的EmmyLua插件编辑器主要有IntelliJ
- IDEA和Visual Studio
- Code。EmmyLua也能以LSP的方式运行,因此支持LSP的编辑器(这种就多了,比如vim,
- sublime)也能符合条件。
+ EmmyLua是一种特别的Lua注释方式,可以为本来弱类型的Lua语言提供类型支持,这对于像FreeKill这种稍有规模的Lua项目是十分必要的。目前能提供开箱即用的EmmyLua插件编辑器主要有IntelliJ IDEA和Visual Studio Code。EmmyLua也能以LSP的方式运行,因此支持LSP的编辑器(这种就多了,比如vim, sublime)也能符合条件。
编辑器的具体安装以及插件配置不在此赘述。
+.. hint::
+
出于易用性和免费的考虑,推荐用VSCode进行拓展。下文将以VSCode为编辑器进行进一步说明。
git
@@ -47,6 +46,8 @@ git
git就不必多介绍了吧,这里说说为什么需要配置git。这是因为在Fk中,拓展包拥有在线安装/在线更新的功能,这种功能都是依托于git进行的,因此如果你打算将自己的拓展包发布出去的话,就需要将其创建git仓库,并托管到git托管网站去。
+.. hint::
+
考虑到国内绝大部分人的访问速度,综合国内几家git托管平台,建议使用gitee。
大多数人可能从未用过git,并且git上手的门槛并不低,因此以下会对涉及git的操作进行详尽的解说。
@@ -99,6 +100,8 @@ cmd弹出命令行窗口,输入git命令,如果出来一长串英文说明
一种最常见的发布mod方式是把mod打包成zip,发到公共平台上供玩家下载。这种办法虽然可行,但并不是fk推荐的做法。
+.. hint::
+
以下介绍的其实就是新建仓库并推送到gitee的办法,熟悉git者请跳过。
下面着重介绍用git发布mod的办法。使用git进行发布的话,就可以让用户体验在线安装、在线更新等便捷之处。
diff --git a/docs/diy/02-skilltype.rst b/docs/diy/02-skilltype.rst
index f1a4e2bd..cb4408b2 100644
--- a/docs/diy/02-skilltype.rst
+++ b/docs/diy/02-skilltype.rst
@@ -22,7 +22,7 @@ fk的技能分为两大类,这两大类又各自细分为更小的分类:
- 卡牌增强技(TargetModSkill):影响卡牌使用次数上限、目标上限、距离限制等等的技能
- 锁定视为技(FilterSkill):让一张牌强制视为另一张牌的技能
-其中,触发技的逻辑最为复杂,但是\ `已经在这里分析过了 <../dev/gamelogic.rst>`__\ ,故不再赘述。
+其中,触发技的逻辑最为复杂,但是\ :doc:`已经在这里分析过了 <../dev/gamelogic>`\ ,故不再赘述。
主动技和状态技应该不算难,先按下不表。视为技与神杀有所区别,区别如下:
diff --git a/docs/diy/03-events.rst b/docs/diy/03-events.rst
index 1307e16e..7bc68d53 100644
--- a/docs/diy/03-events.rst
+++ b/docs/diy/03-events.rst
@@ -3,6 +3,10 @@ fk中的游戏事件
在进行DIY时,需要对三国杀的规则有一定了解;在编写技能时,也要熟悉游戏提供的各种事件,他的触发方式、触发时机、相关数据。必须要知道这些才能写出正确的代码。
+下面的内容介绍的Fk涉及的诸多游戏事件。对于事件具体流程的描述仅限于如何触发各种时机而已。也有可能稍微多聊一些其他方面的事情。
+
+描述触发时机以及事件详细流程的时候,使用的直接就是类似Lua的伪代码,其实你直接看源码都能得到差不多的效果。
+
.. toctree::
:maxdepth: 1
:caption: 事件列表
diff --git a/docs/diy/03-newgeneral.rst b/docs/diy/03-newgeneral.rst
index 89dabad5..4e649b3a 100644
--- a/docs/diy/03-newgeneral.rst
+++ b/docs/diy/03-newgeneral.rst
@@ -107,7 +107,7 @@ fk本身不内置多少技能,但玩家还是可以给武将添加已有的技
保存一下,进游戏就能发现多了个技能。
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-da0d53b6996941de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
+.. figure:: ../pic/diy3-pic1.webp
:alt: 添加已有技能
添加已有技能
@@ -123,23 +123,13 @@ fk本身不内置多少技能,但玩家还是可以给武将添加已有的技
fk中,武将的图片应该为250x292分辨率,并且是jpg格式。为了观感舒适,武将的人脸应该位于图片的中上方。
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-7b08fd53820d4160.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
+.. figure:: ../pic/diy3-pic2.webp
:alt: 使用GIMP切图。我倾向于开5x5参考线,并让人脸位于2行3列的格子里面
使用GIMP切图。我倾向于开5x5参考线,并让人脸位于2行3列的格子里面
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-a629150ce8a4eac8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
- :alt: 使用GIMP切图后,将尺寸缩到需要的分辨率
-
- 使用GIMP切图后,将尺寸缩到需要的分辨率
-
最后用jpg格式导出图片,图片的名字是武将的内部名称,在这里就是study_sunce。
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-7093b57e9cb53118.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
- :alt: 导出JPG
-
- 导出JPG
-
注意了,JPG图片的质量不能拉到100%,不然图片体积会很大,给他人下载你的拓展包带来不便。一般质量为90为好,此时图片大约三四十KB大小。这里图像质量只调了60,这样看起来不至于完全失真,图片的体积也相当较小。
至此我们做好了图片,接下来就是把图片放到游戏去。
@@ -154,12 +144,7 @@ fk中,武将的图片应该为250x292分辨率,并且是jpg格式。为了
│ └── study_sunce.jpg
└── init.lua
-然后打开游戏就能看到武将的图片了:
-
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-faafcd3e899f241b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
- :alt: 效果还不错吧
-
- 效果还不错吧
+然后打开游戏,进入武将一览,就能看到武将的图片了。
--------------
@@ -170,12 +155,7 @@ fk中,武将的图片应该为250x292分辨率,并且是jpg格式。为了
怎么处理mp3音频就不叙述了,可以考虑用audacity这款软件调节mp3的音量、去掉首尾的延迟等等。但是依然需要注意一点——mp3语音的体积不能太大了。为此我的建议是使用格式工厂对mp3文件再进行一次格式转换,将转换后mp3文件的码率设为128kbps,这样一来一句语音差不多就是三四十KB的感觉,而音质却不至于非常模糊。
-阵亡语音放到拓展包文件夹下的audio/death里面,命名规则是武将的内部名称。如图所示:
-
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-1ce5c371b425638e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
- :alt: 阵亡语音的命名,以及存放位置
-
- 阵亡语音的命名,以及存放位置
+阵亡语音放到拓展包文件夹下的audio/death里面,命名规则是武将的内部名称。
--------------
diff --git a/docs/diy/04-newskill.rst b/docs/diy/04-newskill.rst
index 2dd35ced..9cc579b4 100644
--- a/docs/diy/04-newskill.rst
+++ b/docs/diy/04-newskill.rst
@@ -45,7 +45,7 @@ fk体量实在太小,只有标包,欲玩到更多技能,还是得自己亲
启动游戏试试看,却给我们甩了个报错:
-.. image:: https://upload-images.jianshu.io/upload_images/21666547-b032b4f43ad13b58.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
+.. image:: ../pic/diy4-pic1.webp
原来是这个复制粘贴的技能和已有的英姿重复了。解法很简单,换个名字就行了,这里改名为“激姿”好了。按照命名习惯,为他起一个内部名称”study_jizi”。然后把所有的yingzi都改成这个名,改名后如下:
@@ -78,11 +78,6 @@ fk体量实在太小,只有标包,欲玩到更多技能,还是得自己亲
至此完事了。别忘了更新一下git,后面不赘述关于git的事情了。
-.. figure:: https://upload-images.jianshu.io/upload_images/21666547-f4c76ee91f8c15ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
- :alt: 搞定,一摸就是6张,薄纱神郭嘉
-
- 搞定,一摸就是6张,薄纱神郭嘉
-
--------------
稍微解说一下创建技能的语法
diff --git a/docs/diy/05-trigger.rst b/docs/diy/05-trigger.rst
new file mode 100644
index 00000000..b1fd10a5
--- /dev/null
+++ b/docs/diy/05-trigger.rst
@@ -0,0 +1,238 @@
+技能解析:触发技
+======================
+
+在上回中,我们创造了第一个技能“激姿”,就是一个改造版的英姿,它的作用是摸牌阶段摸牌时候让摸牌数+4。
+
+像这种在游戏特定时机被触发的技能,就称为触发技,它们通常都是形如“当xxx时,你可以xxx”的技能。
+
+下面来介绍触发技。首先介绍触发技的几个基本函数,再说明触发技的执行流程,最后说明怎么创建触发技。这篇文章大多为比较无聊的概念解析,也没有实操,建议自己对着已经写好的触发技实操。
+
+触发技的基本函数
+--------------------
+
+触发技中涉及这些函数:
+
+- ``can_trigger`` :技能能否被触发?
+- ``on_trigger`` :技能是如何执行的?
+- ``on_cost`` :技能的执行消耗是什么?
+- ``on_use`` :技能正式发动后,执行什么代码?
+
+所有这些函数的函数原型全都是一样的:
+
+.. code:: lua
+
+ ---@param self TriggerSkill
+ ---@param event Event
+ ---@param target ServerPlayer
+ ---@param player ServerPlayer
+ ---@param data any
+ function(self, event, target, player, data)
+ end
+
+这个函数原型还是稍微有些难以理解,得结合触发技的具体执行流程来看。
+
+触发技的执行流程
+--------------------
+
+第一步 触发一个时机
+~~~~~~~~~~~~~~~~~~~~~
+
+触发技若是想要被发动,那么肯定就先要有时机被触发了。而用来触发事件的函数就是如下这位:
+
+.. code:: lua
+
+ ---@param event Event
+ ---@param target ServerPlayer
+ ---@param data any
+ function GameLogic:trigger(event, target, data) end
+
+直接调查这个函数的代码就能知道触发技执行的所有细节了。但这个函数并没有那么好懂,故在此进行说明。
+
+首先,从这个函数可以看出,某一个触发时机一共有三要素:
+
+- ``event`` :具体是哪个触发时机。
+- ``target`` :这个触发时机涉及的玩家,这名玩家在后面会称为“时机的承担者”。
+- ``data`` :可以是任何值,视具体时机而定。
+
+首先,event是这个时机具体是什么,比如“受到伤害后”( ``fk.Damaged`` );target则是时机的承担者,比如“受到伤害后”这个时机,承担者就是此次伤害的目标;data就完完全全是根据时机而定了。
+
+想要知道某个时机具体对应着哪个target和data,最直接的办法就是直接从源码中找到trigger函数调用的点了,这样一下子就知道这个时机的相关数据了。不过呢,文档后面也是会一一列出的,毕竟有些时机的data还是多少复杂了点。
+
+第一步(续) 假设出一个例子情景
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+在开始接下来的解说之前,还是想象一下有这么一桌军五吧:
+
+::
+
+ 郭嘉 司马懿
+
+ *关羽 -------杀------> 郭嘉 -1
+
+ 周瑜(一号位)
+
+如图所示,关羽杀郭嘉(二号位),郭嘉掉血,此时执行到了伤害流程的“受到伤害后”时机。
+
+假设当前回合的角色是关羽。
+
+假设郭嘉拥有在这个时机可以发动的技能“遗计”,其代码如下:
+
+.. code:: lua
+
+ local easy_yiji = fk.CreateTriggerSkill{
+ name = "easy_yiji",
+ events = {fk.Damaged},
+ on_use = function(self, event, target, player, data)
+ player:drawCards(2)
+ end,
+ }
+
+为了简化说明,这是是一段简化版的遗计代码。其作用是受到伤害后,可以摸两张牌。
+
+前面说到一个触发技得有4种函数,而这里却只有个 ``on_use`` 啊。这是因为其他三个函数此处可以取默认值,所以实际写Lua的时候省略掉了。为了便于说明,现在将这4个函数补全(包括默认情况):
+
+.. code:: lua
+
+ local easy_yiji = fk.CreateTriggerSkill{
+ name = "easy_yiji",
+ events = {fk.Damaged},
+ can_trigger = function(self, event, target, player, data)
+ return target == player and target:hasSkill(self.name)
+ end,
+ on_trigger = function(self, event, target, player, data)
+ return self:doCost(event, target, player, data)
+ end,
+ on_cost = function(self, event, target, player, data)
+ return player.room:askForSkillInvoke(player, self.name)
+ end,
+ on_use = function(self, event, target, player, data)
+ player:drawCards(2)
+ end,
+ }
+
+这里假设出来的情景是“受到伤害后”时机,写成代码就是
+
+.. code:: lua
+
+ logic:trigger(fk.Damaged, guojia, data)
+
+这里不关心data。第二个参数guojia表示受到伤害后的那个郭嘉。注意场上有两个郭嘉,这是为了后面详细解释而安排的。
+
+第二步 遍历场上玩家
+~~~~~~~~~~~~~~~~~~~~
+
+现在的时机是fk.Damaged,刚好遗计的时机也是fk.Damaged,所以遗计就能在这个时机发动了。隔壁司马懿也有个反馈能在这个时机发动。所以现在能够在该时机发动的技能有:遗计、反馈。
+
+假设反馈的代码和上文的遗计一模一样,只是技能名不同罢了。
+
+确定了可能可以发动的技能后,Fk就会从当前回合角色开始,对所有角色进行遍历。每一趟遍历的步骤如下:
+
+1. 把当前正在遍历到的玩家称为player。
+2. 执行 ``can_trigger(self, event, target, player, data)``
+3. 如果第二步的执行返回了true,就执行 ``on_trigger`` 。
+
+事已至此,触发技函数中的参数也基本明朗了:
+
+- ``self`` :这个技能本身。
+- ``event`` :当前的触发时机。
+- ``target`` :时机的承担者。
+- ``player`` :当前被遍历到的玩家。
+- ``data`` : ``logic:trigger`` 函数中传入的那个额外的data参数。
+
+下面进行针对前面那桌军五,模拟一下这么个遍历流程。
+
+::
+
+ 可能可以发动的技能: 遗计,反馈
+ 当前回合角色:关羽
+ 当前时机:受到伤害后
+ 时机的承担者(target):郭嘉 - 二号位
+ 当前的data:没人在意data
+
+ 对 关羽 进行遍历,令 player 为 关羽
+ -> 遗计的can_trigger:失败,target ~= player
+ -> 反馈的can_trigger:失败,target ~= player
+
+ 对 周瑜 进行遍历,令 player 为 周瑜
+ -> 遗计的can_trigger:失败,target ~= player
+ -> 反馈的can_trigger:失败,target ~= player
+
+ 对 郭嘉二号位 进行遍历,令 player 为 郭嘉二号位
+ -> 遗计的can_trigger:通过,target == player and player:hasSkill(self.name)
+ -> 遗计的 on_trigger 开始执行
+ -> 执行 TriggerSkill:doCost
+ -> 反馈的can_trigger:失败,target == player,但是player:hasSkill(反馈)为false,郭嘉不会反馈
+
+ 对 司马懿 进行遍历,令 player 为 司马懿
+ -> 遗计的can_trigger:失败,target ~= player
+ -> 反馈的can_trigger:失败,target ~= player,虽然player拥有技能反馈
+
+ 对 郭嘉四号位 进行遍历,令 player 为 郭嘉四号位
+ -> 遗计的can_trigger:失败,target ~= player
+ -> 反馈的can_trigger:失败,target ~= player
+
+ 遍历结束了,本次触发时机也随之结束了。
+
+从上面的实机演练中我们差不多能明白 ``can_trigger`` 和 ``on_trigger`` 的执行流程。
+
+.. note::
+
+ 在实际的执行中,其实是先都执行 ``can_trigger`` ,然后将所有通过的技能暂存在表中,玩家可以从这里面选出自己想要先发动的技能,然后再去执行那个技能的 ``on_trigger`` 。
+
+第三步 询问消耗执行,以及正式发动技能
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+而 ``on_cost`` 和 ``on_use`` ,则是在on_trigger中调用doCost函数时候调用的。doCost的内容如下:
+
+.. code:: lua
+
+ -- do cost and skill effect.
+ -- DO NOT modify this function
+ function TriggerSkill:doCost(event, target, player, data)
+ local ret = self:cost(event, target, player, data)
+ if ret then
+ return player.room:useSkill(player, self, function()
+ return self:use(event, target, player, data)
+ end)
+ end
+ end
+
+在这段代码中,首先执行一下cost函数(也就是这里聊的on_use),如果返回true,那么调用useSkill函数正式发动技能。useSkill函数先播放技能发动的特效、增加技能发动次数,再去调用传入的第三个函数(这里就是on_use了)。
+
+这也就是说,on_cost函数掌握的是技能是否确实要发动,用户得在这里做出自己的选择。如果用户作出了肯定的答复,那么on_cost就返回true,这之后技能发动次数的历史记录便加一,然后开始真正执行技能的效果。
+
+创建触发技的办法
+------------------
+
+要创建一个触发技,我们使用 ``fk.CreateTriggerSkill`` 函数。该函数接收一个表作为参数,表中各种键值的含义如下:
+
+- ``name`` :技能名。别和其他技能重名了。
+- ``frequency`` :技能的发动频率,可能是锁定技。
+- ``anim_type`` :技能的动画类型。上一篇好像已经聊过了。
+- ``mute`` :技能是否静默。
+
+.. tip::
+
+ 静默的技能不会播放配音、动画、发log,如果你想播放配音,就得自己手动做这些工作。
+ 有些需要根据情况手动播放相应配音的技能,比如自书、英魂等,就得先设为静默,然后自己去技能发动的环节添加这些跟播放特效有关的代码。
+
+上面这4项其实是对所有技能都通用的。下面是一些触发技专用的:
+
+- ``global`` :是否是全局技能。全局技能必定会参与到遍历中。
+- ``events`` :一个数组,保存着可能可以触发这个技能的所有时机。
+- ``can_trigger`` :触发该技能的条件。
+- ``on_trigger`` :技能触发的内容。这个函数一般是自定义如何去询问发动、发动几次的。总之自定义的话,记得在里面调用doCost进行实际的询问和生效就行了。
+- ``on_cost`` :技能生效前要对玩家进行询问的内容,或者说是“消耗”。
+- ``on_use`` :技能生效环节。
+
+.. danger::
+
+ 除非万不得已,不要把技能的global设为true!global技能在任何情况下都会被纳入游戏的处理范围,随着global的增多,遍历的技能也会变多,这会使游戏的性能下降!
+
+有些时候我们不希望增加技能发动的次数,只想执行一些代码而已,比如说清理掉某些不可见标记等等。为了实现这个效果,触发技中还有一种称为“refresh”的行为(相对于发动技能的“use”),创建触发技的时候可以用这些来指定:
+
+- ``refresh_events`` :可能触发refresh的所有时机。
+- ``can_refresh`` :类似 ``can_trigger`` ,只不过是针对refresh的。注意这个函数没有默认值。
+- ``on_refresh`` :类似 ``on_trigger`` ,但是是针对refresh。
+
+refresh和实际发动技能也差不多,一样的遍历,判断can_refresh,执行on_refresh。在实际trigger中,是 **先执行refresh,再执行use** 。
diff --git a/docs/diy/06-active.rst b/docs/diy/06-active.rst
new file mode 100644
index 00000000..83309db5
--- /dev/null
+++ b/docs/diy/06-active.rst
@@ -0,0 +1,35 @@
+技能解析:主动技
+================
+
+上篇说到的触发技是在满足某个时机之后“被动”发动的,而本文将要阐述的技能则是在出牌阶段空闲的时候主动发动的。
+
+在技能显示页面中,主动技是有一个按钮可以按下去的,其他技能就只是文字而已。
+
+接下先看看如何创建主动技,再来简述一下主动技是如何发挥效果的。
+
+创建主动技
+----------
+
+创建主动技使用的是 ``fk.CreateActiveSkill`` 函数。这个函数和创建触发技一样,接收的参数也是一个表,表中可以指定 ``name`` 、 ``frequency`` 、 ``anim_type`` 、 ``mute`` 这四个属性,以及这些主动技特有的函数/属性:
+
+- ``can_use`` :函数原型为 ``fun(self, player)`` ,用来判断当前空闲时间点能不能使用该技能。
+- ``card_filter`` :函数原型为 ``fun(self, to_select, selected, selected_targets)`` ,用来判断某张卡牌能不能被这个技能选择。
+- ``target_filter`` :函数原型为 ``fun(self, to_select, selected, selected_cards)`` ,用来判断某名角色能不能被技能选择。
+- ``target_num`` :为了能点击确定键,需要选择的角色数量。
+- ``card_num`` :为了能点击确定键,需要选择的卡牌数量。
+- ``max_target_num`` :能点击确定键的最大选择角色数量,默认999
+- ``min_target_num`` :能点击确定键的最小选择角色数量,默认0
+- ``max_card_num`` :能点击确定键的最大选牌数量,默认999
+- ``min_card_num`` :能点击确定键的最小选牌数量,默认0
+- ``on_use`` :主动技生效部分,后面再说
+
+主动技生效之前的流程
+--------------------
+
+首先,每当玩家即将进行出牌阶段的一次出牌之前,游戏会先做出判断,来确定按钮是否能被按下。
+
+对于某个技能而言,判断按钮是否被点亮就是通过 ``can_use`` 。返回true的话就能点亮。
+
+然后,当技能按钮被按下后,就需要对手牌/装备含有的所有卡牌,以及场上所有角色,都判断能不能点亮。
+
+TODO!
diff --git a/docs/diy/event/hp.rst b/docs/diy/event/hp.rst
index 88fd0b4a..03f8ccc2 100644
--- a/docs/diy/event/hp.rst
+++ b/docs/diy/event/hp.rst
@@ -1,14 +1,117 @@
与体力值相关的事件
==================
+以下列出了一些和体力值改变有关的事件。
+
+改变体力
+--------
+
+涉及的类如下:
+
+.. lua:autoclass:: HpChangedData
+
+事件流程如下:
+
+.. code:: lua
+
+ local data = HpChangedData
+ local player = xxx -- 体力变动的那位角色
+
+ -- 先触发“体力变化前”时机 ** 可中断 **
+ logic:trigger(fk.BeforeHpChanged, player, data)
+
+ -- 然后对player的hp作出修改
+
+ -- 最后触发“体力变化后”时机
+ logic:trigger(fk.HpChanged, player, data)
+
+ -- 如果体力变化之后,玩家的hp < 1
+ -- 并且这次体力变化的变化量是负数,那么进入濒死阶段
+
伤害
----
-失去体力/体力上限
------------------
+涉及的类如下:
+
+.. lua:autoclass:: DamageStruct
+
+在整个伤害事件中,触发时机时候传递的data都是DamageStruct类型。
+
+事件的流程如下:
+
+.. code:: lua
+
+ local data = DamageStruct
+ -- 下面的data.from是伤害来源,data.to是伤害目标
+
+ -- 在处理过程中,如果伤害目标死了,事件就结束。
+
+ -- 触发时机“伤害结算开始前” ** 可中断 **
+ logic:trigger(fk.PreDamage, data.from, data)
+
+ -- 触发时机“造成伤害时” ** 可中断 **
+ logic:trigger(fk.DamageCaused, data.from, data)
+
+ -- 触发时机“受到伤害时” ** 可中断 **
+ logic:trigger(fk.DamageInflicted, data.to, data)
+
+ -- 进行一个“改变体力”事件,以修改伤害目标的体力
+
+ -- 触发时机“造成伤害后”
+ logic:trigger(fk.Damage, data.from, data)
+
+ -- 触发时机“受到伤害后”
+ logic:trigger(fk.Damaged, data.to, data)
+
+ -- 触发时机“伤害结算完成后”
+ logic:trigger(fk.DamageFinished, data.from, data)
+
+
+失去体力
+--------
+
+涉及的类如下:
+
+.. lua:autoclass:: HpLostData
+
+流程如下:
+
+.. code:: lua
+
+ local data = HpLostData
+ local player = xxx -- 失去体力的那位角色
+
+ -- 触发时机“失去体力前” ** 可中断 **
+ logic:trigger(fk.PreHpLost, player, data)
+
+ -- 进行一次“改变体力”事件,以更新受害者的hp
+
+ -- 触发时机“失去体力后”
+ logic:trigger(fk.HpLost, player, data)
回复体力
--------
-濒死和死亡
-----------
+涉及的类如下:
+
+.. lua:autoclass:: RecoverStruct
+
+流程如下:
+
+.. code:: lua
+
+ local data = HpLostData
+ local player = xxx -- 失去体力的那位角色
+
+ -- 触发时机“回复体力前” ** 可中断 **
+ logic:trigger(fk.PreHpRecover, player, data)
+
+ -- 进行一次“改变体力”事件,以更新回复者的hp
+
+ -- 触发时机“回复体力后”
+ logic:trigger(fk.HpRecover, player, data)
+
+改变体力上限
+-------------
+
+TODO
diff --git a/docs/diy/index.rst b/docs/diy/index.rst
index 0778341b..2d24ea9b 100644
--- a/docs/diy/index.rst
+++ b/docs/diy/index.rst
@@ -8,4 +8,5 @@ Diy文档
02-skilltype.rst
03-newgeneral.rst
04-newskill.rst
+ 05-trigger.rst
03-events.rst
diff --git a/docs/index.rst b/docs/index.rst
index c31f303a..79bb1943 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -9,6 +9,7 @@
.. toctree::
:maxdepth: 1
+ usr/index.rst
diy/index.rst
dev/index.rst
api/index.rst
diff --git a/docs/makebook.sh b/docs/makebook.sh
new file mode 100755
index 00000000..61640cf2
--- /dev/null
+++ b/docs/makebook.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+TEX_FILE=manual.tex
+cd build/latex
+
+# 给所有的标题加一个层级
+
+if ! grep '\\part' $TEX_FILE; then
+ sed -i 's/\\chapter/\\part/g' $TEX_FILE
+ sed -i 's/\\section/\\chapter/g' $TEX_FILE
+ sed -i 's/\\subsection/\\section/g' $TEX_FILE
+ sed -i 's/\\subsubsection/\\subsection/g' $TEX_FILE
+ sed -i 's/\\paragraph/\\subsubsection/g' $TEX_FILE
+ sed -i 's/\\subparagraph/\\paragraph/g' $TEX_FILE
+fi
+
+# webp转jpg
+sed -i 's/webp/jpg/g' $TEX_FILE
+for webp in *.webp; do
+ convert $webp -background white -alpha remove ${webp%%webp}jpg
+done
+
+# 好了,开始做pdf
+make
+
+cd ../..
diff --git a/docs/pic/diy3-pic1.webp b/docs/pic/diy3-pic1.webp
new file mode 100644
index 00000000..cd8d76ac
Binary files /dev/null and b/docs/pic/diy3-pic1.webp differ
diff --git a/docs/pic/diy3-pic2.webp b/docs/pic/diy3-pic2.webp
new file mode 100644
index 00000000..f13315b4
Binary files /dev/null and b/docs/pic/diy3-pic2.webp differ
diff --git a/docs/pic/diy4-pic1.webp b/docs/pic/diy4-pic1.webp
new file mode 100644
index 00000000..52a4c169
Binary files /dev/null and b/docs/pic/diy4-pic1.webp differ
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 52e0f74b..06494e1f 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,2 @@
sphinx-lua
-sphinx-rtd-theme
+sphinx-book-theme
diff --git a/docs/usr/00-download.rst b/docs/usr/00-download.rst
new file mode 100644
index 00000000..f32d7a59
--- /dev/null
+++ b/docs/usr/00-download.rst
@@ -0,0 +1,12 @@
+下载FK
+========
+
+FreeKill目前提供Windows版和安卓版下载。
+
+下载链接:
+
+https://github.com/Notify-ctrl/FreeKill/release
+
+https://gitee.com/notify-ctrl/FreeKill/release
+
+其中,apk文件就是安卓版,直接下载安装;7z文件是windows版,下载并解压缩后即可运行。
diff --git a/docs/usr/01-play.rst b/docs/usr/01-play.rst
new file mode 100644
index 00000000..5ada72e4
--- /dev/null
+++ b/docs/usr/01-play.rst
@@ -0,0 +1,12 @@
+游玩FK
+========
+
+进入FK之后首先是登录界面。先随意输入用户名和密码,然后单机启动,就能进入游戏大厅。
+
+在大厅中,可以查看武将、卡牌等,或者对游戏进行配置。
+
+点击“创建房间”,就会弹出创房对话框,然后做好配置后创建房间就行了。
+
+进入房间后,就进入了游戏画面。此时点击屏幕中间的添加机器人按钮即可添加机器人,人满之后游戏开始。
+
+在游戏中,随着流程的推进你将会面临各种交互,比如做出选项、思考如何出牌等等。运用手中的各种卡牌和技能,击败敌人,获取胜利吧。
diff --git a/docs/usr/02-connect.rst b/docs/usr/02-connect.rst
new file mode 100644
index 00000000..0c92db36
--- /dev/null
+++ b/docs/usr/02-connect.rst
@@ -0,0 +1,16 @@
+联机游玩FK
+===========
+
+在登录界面输入服务器IP,再输入自己的用户名、密码即可连接到服务器中。
+
+请记住自己的密码!当然了,FK也带有记住密码的功能。
+
+在游戏大厅中,你可以自己创建房间,也可以加入已有房间游戏。
+
+其他人也可以加入这个服务器,这样就是多人游玩了。
+
+想要自己开服联机的话,先在自己的电脑上单机启动,然后用内网穿透等手段获取公网IP,然后别人就能通过这个IP连接到你的服务器中。
+
+或者,在命令行中使用 ``./FreeKill -s`` 或者 ``.\FreeKill.exe -s`` ,前者是针对Linux服务器的命令。
+
+Linux服务器开服的话需要自己编译。
diff --git a/docs/usr/03-package.rst b/docs/usr/03-package.rst
new file mode 100644
index 00000000..4a165a91
--- /dev/null
+++ b/docs/usr/03-package.rst
@@ -0,0 +1,12 @@
+拓展包管理
+===========
+
+在登录界面中有一个拓展包管理按钮。
+
+在拓展包管理中,你可以安装、卸载、禁用、启动拓展包。
+
+当获知拓展包的URL后,就可以自己安装它。
+
+在连接到服务器的时候,会自动同步所有拓展包。
+
+想要自己做拓展包的话,还请继续阅读后文。
diff --git a/docs/usr/index.rst b/docs/usr/index.rst
new file mode 100644
index 00000000..556d1e7b
--- /dev/null
+++ b/docs/usr/index.rst
@@ -0,0 +1,12 @@
+入门FK
+========
+
+本章讲述了如何下载、游玩FK等等。
+
+.. toctree::
+ :maxdepth: 1
+
+ 00-download.rst
+ 01-play.rst
+ 02-connect.rst
+ 03-package.rst
diff --git a/lua/core/card.lua b/lua/core/card.lua
index 34c6aeda..dcb67ec5 100644
--- a/lua/core/card.lua
+++ b/lua/core/card.lua
@@ -1,14 +1,18 @@
+--- Card记录了FreeKill所有卡牌的基础信息。
+---
+--- 它包含了ID、所属包、牌名、花色、点数等等
+---
---@class Card : Object
----@field public id integer
----@field public package Package
----@field public name string
----@field public suit Suit
----@field public number integer
----@field public trueName string
----@field public color Color
----@field public type CardType
----@field public sub_type CardSubtype
----@field public area CardArea
+---@field public id integer @ 标志某一张卡牌唯一的数字,从1开始。若此牌是虚拟牌,则其id为0。服务器启动时为卡牌赋予ID。
+---@field public package Package @ 卡牌所属的扩展包
+---@field public name string @ 卡牌的名字
+---@field public suit Suit @ 卡牌的花色(四色及无花色)
+---@field public number integer @ 卡牌的点数(0到K)
+---@field public trueName string @ 卡牌的真名,一般用于分辨杀。
+---@field public color Color @ 卡牌的颜色(分为黑色、红色、无色)
+---@field public type CardType @ 卡牌的种类(基本牌、锦囊牌、装备牌)
+---@field public sub_type CardSubtype @ 卡牌的子种类(例如延时锦囊牌、武器、防具等)
+---@field public area CardArea @ 卡牌所在区域(例如手牌区,判定区,装备区,牌堆,弃牌堆···)
---@field public subcards integer[]
---@field public skillName string @ for virtual cards
---@field public skill Skill
@@ -57,6 +61,7 @@ Card.DrawPile = 6
Card.DiscardPile = 7
Card.Void = 8
+--- Card的构造函数。具体负责构建Card实例的函数,请参见fk_ex部分。
function Card:initialize(name, suit, number, color)
self.name = name
self.suit = suit or Card.NoSuit
@@ -84,9 +89,12 @@ function Card:initialize(name, suit, number, color)
self.skillName = ""
end
----@param suit Suit
----@param number integer
----@return Card
+--- 克隆特定卡牌并赋予花色与点数。
+---
+--- 会将skill/special_skills/equip_skill继承到克隆牌中。
+---@param suit Suit @ 克隆后的牌的花色
+---@param number integer @ 克隆后的牌的点数
+---@return Card @ 产品
function Card:clone(suit, number)
local newCard = self.class:new(self.name, suit, number)
newCard.skill = self.skill
@@ -95,10 +103,15 @@ function Card:clone(suit, number)
return newCard
end
+--- 检测是否为虚拟卡牌,如果其ID为0及以下,则为虚拟卡牌。
function Card:isVirtual()
return self.id <= 0
end
+--- 获取卡牌的ID。
+---
+--- 如果牌是虚拟牌,则返回其第一张子卡的id,没有子卡就返回nil
+---@return integer | nil
function Card:getEffectiveId()
if self:isVirtual() then
return #self.subcards > 0 and self.subcards[1] or nil
@@ -129,7 +142,8 @@ local function updateColorAndNumber(card)
card.number = number
end
----@param card integer|Card
+--- 将一张子卡牌加入某张牌中(是addSubcards的基础函数,常用addSubcards)。
+---@param card integer|Card @ 要加入的子卡
function Card:addSubcard(card)
if type(card) == "number" then
table.insert(self.subcards, card)
@@ -142,21 +156,27 @@ function Card:addSubcard(card)
updateColorAndNumber(self)
end
+--- 将一批子卡牌加入某张牌中(常用于将这批牌弃置/交给某个角色···)。
+---@param cards integer[] | Card[] @ 要加入的子卡列表
function Card:addSubcards(cards)
for _, c in ipairs(cards) do
self:addSubcard(c)
end
end
+--- 清空加入某张牌中的子卡牌。
function Card:clearSubcards()
self.subcards = {}
updateColorAndNumber(self)
end
+--- 判断此牌能否符合一个卡牌规则。
function Card:matchPattern(pattern)
return Exppattern:Parse(pattern):match(self)
end
+--- 获取卡牌花色并返回花色文字描述(如 黑桃、红桃、梅花、方块)。
+---@return string @ 描述花色的字符串
function Card:getSuitString()
local suit = self.suit
if suit == Card.Spade then
@@ -172,6 +192,8 @@ function Card:getSuitString()
end
end
+--- 获取卡牌颜色并返回点数颜色描述(例如黑色/红色/无色)。
+---@return string @ 描述颜色的字符串
function Card:getColorString()
local color = self.color
if color == Card.Black then
@@ -182,6 +204,7 @@ function Card:getColorString()
return "nocolor"
end
+--- 获取卡牌类型并返回点数类型描述(例如基本牌/锦囊牌/装备牌)。
function Card:getTypeString()
local t = self.type
if t == Card.TypeBasic then
@@ -194,6 +217,7 @@ function Card:getTypeString()
return "notype"
end
+--- 获取卡牌点数并返回点数文字描述(仅限A/J/Q/K)。
local function getNumberStr(num)
if num == 1 then
return "A"
@@ -208,6 +232,7 @@ local function getNumberStr(num)
end
-- for sendLog
+--- 获取卡牌的文字信息并准备作为log发送。
function Card:toLogString()
local ret = string.format('%s', Fk:translate(self.name) .. "[")
if self:isVirtual() then
@@ -222,6 +247,7 @@ function Card:toLogString()
return ret
end
+--- 静态方法。传入下列类型之一的参数,返回id列表。
---@param c integer|integer[]|Card|Card[]
---@return integer[]
function Card:getIdList(c)
diff --git a/lua/server/room.lua b/lua/server/room.lua
index 713fdd78..f7af079d 100644
--- a/lua/server/room.lua
+++ b/lua/server/room.lua
@@ -1,21 +1,25 @@
+--- Room是fk游戏逻辑运行的主要场所,同时也提供了许多API函数供编写技能使用。
+---
+--- 一个房间中只有一个Room实例,保存在RoomInstance全局变量中。
---@class Room : Object
----@field public room fk.Room
----@field public players ServerPlayer[]
----@field public alive_players ServerPlayer[]
----@field public observers fk.ServerPlayer[]
----@field public current ServerPlayer
----@field public game_started boolean
----@field public game_finished boolean
----@field public timeout integer
----@field public tag table
----@field public draw_pile integer[]
----@field public discard_pile integer[]
----@field public processing_area integer[]
----@field public void integer[]
----@field public card_place table
----@field public owner_map table
----@field public status_skills Skill[]
----@field public settings table
+---@field public room fk.Room @ C++层面的Room类实例,别管他就是了,用不着
+---@field public players ServerPlayer[] @ 这个房间中所有参战玩家
+---@field public alive_players ServerPlayer[] @ 所有还活着的玩家
+---@field public observers fk.ServerPlayer[] @ 旁观者清单,这是c++玩家列表,别乱动
+---@field public current ServerPlayer @ 当前回合玩家
+---@field public game_started boolean @ 游戏是否已经开始
+---@field public game_finished boolean @ 游戏是否已经结束
+---@field public timeout integer @ 出牌时长上限
+---@field public tag table @ Tag清单,其实跟Player的标记是差不多的东西
+---@field public draw_pile integer[] @ 摸牌堆,这是卡牌id的数组
+---@field public discard_pile integer[] @ 弃牌堆,也是卡牌id的数组
+---@field public processing_area integer[] @ 处理区,依然是卡牌id数组
+---@field public void integer[] @ 从游戏中除外区,一样的是卡牌id数组
+---@field public card_place table @ 每个卡牌的id对应的区域,一张表
+---@field public owner_map table @ 每个卡牌id对应的主人,表的值是那个玩家的id,可能是nil
+---@field public status_skills Skill[] @ 这个房间中含有的状态技列表
+---@field public settings table @ 房间的额外设置,差不多是json对象
+---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动
local Room = class("Room")
-- load classes used by the game
@@ -53,6 +57,7 @@ dofile "lua/server/ai/init.lua"
-- constructor
------------------------------------------------------------------------
+--- 构造函数。别去构造
---@param _room fk.Room
function Room:initialize(_room)
self.room = _room
@@ -114,7 +119,10 @@ function Room:initialize(_room)
end
end
--- When this function returns, the Room(C++) thread stopped.
+--- 正式在这个房间中开始游戏。
+---
+--- 当这个函数返回之后,整个Room线程也宣告结束。
+---@return nil
function Room:run()
for _, p in fk.qlist(self.room:getPlayers()) do
local player = ServerPlayer:new(p)
@@ -132,6 +140,7 @@ end
-- getters and setters
------------------------------------------------------------------------
+--- 基本算是私有函数,别去用
---@param cardId integer
---@param cardArea CardArea
---@param integer owner
@@ -140,8 +149,9 @@ function Room:setCardArea(cardId, cardArea, owner)
self.owner_map[cardId] = owner
end
----@param cardId integer | card
----@return CardArea
+--- 获取一张牌所处的区域。
+---@param cardId integer | Card @ 要获得区域的那张牌,可以是Card或者一个id
+---@return CardArea @ 这张牌的区域
function Room:getCardArea(cardId)
if type(cardId) ~= "number" then
assert(cardId and cardId:isInstanceOf(Card))
@@ -150,8 +160,9 @@ function Room:getCardArea(cardId)
return self.card_place[cardId] or Card.Unknown
end
----@param cardId integer | card
----@return ServerPlayer
+--- 获得拥有某一张牌的玩家。
+---@param cardId integer | card @ 要获得主人的那张牌,可以是Card实例或者id
+---@return ServerPlayer | nil @ 这张牌的主人,可能返回nil
function Room:getCardOwner(cardId)
if type(cardId) ~= "number" then
assert(cardId and cardId:isInstanceOf(Card))
@@ -160,8 +171,9 @@ function Room:getCardOwner(cardId)
return self.owner_map[cardId] and self:getPlayerById(self.owner_map[cardId]) or nil
end
----@param id integer
----@return ServerPlayer
+--- 根据玩家id,获得那名玩家本人。
+---@param id integer @ 玩家的id
+---@return ServerPlayer @ 这个id对应的ServerPlayer实例
function Room:getPlayerById(id)
if not id then return nil end
assert(type(id) == "number")
@@ -175,7 +187,8 @@ function Room:getPlayerById(id)
return nil
end
----@param playerIds integer[]
+--- 将房间中的玩家按照座位顺序重新排序。
+---@param playerIds integer[] @ 玩家id列表,这个数组会被这个函数排序
function Room:sortPlayersByAction(playerIds)
end
@@ -191,8 +204,11 @@ function Room:deadPlayerFilter(playerIds)
return newPlayerIds
end
----@param sortBySeat boolean
----@return ServerPlayer[]
+--- 获得当前房间中的所有玩家。
+---
+--- 返回的数组的第一个元素是当前回合玩家,并且按行动顺序进行排序。
+---@param sortBySeat boolean @ 是否无视按座位排序直接返回
+---@return ServerPlayer[] @ 房间中玩家的数组
function Room:getAllPlayers(sortBySeat)
if not self.game_started then
return { table.unpack(self.players) }
@@ -212,6 +228,7 @@ function Room:getAllPlayers(sortBySeat)
end
end
+--- 获得所有存活玩家,参看getAllPlayers
---@param sortBySeat boolean
---@return ServerPlayer[]
function Room:getAlivePlayers(sortBySeat)
@@ -237,10 +254,11 @@ function Room:getAlivePlayers(sortBySeat)
end
end
----@param player ServerPlayer
----@param sortBySeat boolean
----@param include_dead boolean
----@return ServerPlayer[]
+--- 获得除一名玩家外的其他玩家。
+---@param player ServerPlayer @ 要排除的玩家
+---@param sortBySeat boolean @ 是否要按座位排序?
+---@param include_dead boolean @ 是否要把死人也算进去?
+---@return ServerPlayer[] @ 其他玩家列表
function Room:getOtherPlayers(player, sortBySeat, include_dead)
if sortBySeat == nil then
sortBySeat = true
@@ -257,7 +275,10 @@ function Room:getOtherPlayers(player, sortBySeat, include_dead)
return players
end
----@return ServerPlayer | null
+--- 获得当前房间中的主公。
+---
+--- 由于某些游戏模式没有主公,该函数可能返回nil。
+---@return ServerPlayer | nil @ 主公
function Room:getLord()
local lord = self.players[1]
if lord.role == "lord" then return lord end
@@ -268,9 +289,14 @@ function Room:getLord()
return nil
end
----@param num integer
----@param from string
----@return integer[]
+--- 从摸牌堆中获取若干张牌。
+---
+--- 注意了,这个函数会对牌堆进行实际操作,也就是说它返回一系列id后,牌堆中就会少这么多id。
+---
+--- 如果牌堆中没有足够的牌可以获得,那么会触发洗牌;还是不够的话,游戏就平局。
+---@param num integer @ 要获得的牌的数量
+---@param from string @ 获得牌的位置,可以是 ``"top"`` 或者 ``"bottom"``,表示牌堆顶还是牌堆底
+---@return integer[] @ 得到的id
function Room:getNCards(num, from)
from = from or "top"
assert(from == "top" or from == "bottom")
@@ -294,9 +320,12 @@ function Room:getNCards(num, from)
return cardIds
end
----@param player ServerPlayer
----@param mark string
----@param value integer
+--- 将一名玩家的某种标记数量相应的值。
+---
+--- 在设置之后,会通知所有客户端也更新一下标记的值。之后的两个相同
+---@param player ServerPlayer @ 要被更新标记的那个玩家
+---@param mark string @ 标记的名称
+---@param value integer @ 要设为的值,其实也可以设为字符串
function Room:setPlayerMark(player, mark, value)
player:setMark(mark, value)
self:doBroadcastNotify("SetPlayerMark", json.encode{
@@ -306,6 +335,10 @@ function Room:setPlayerMark(player, mark, value)
})
end
+--- 将一名玩家的mark标记增加count个。
+---@param player ServerPlayer @ 要加标记的玩家
+---@param mark string @ 标记名称
+---@param count integer | nil @ 要增加的数量,默认为1
function Room:addPlayerMark(player, mark, count)
count = count or 1
local num = player:getMark(mark)
@@ -313,6 +346,10 @@ function Room:addPlayerMark(player, mark, count)
self:setPlayerMark(player, mark, math.max(num + count, 0))
end
+--- 将一名玩家的mark标记减少count个。
+---@param player ServerPlayer @ 要减标记的玩家
+---@param mark string @ 标记名称
+---@param count integer | nil @ 要减少的数量,默认为1
function Room:removePlayerMark(player, mark, count)
count = count or 1
local num = player:getMark(mark)
@@ -320,17 +357,23 @@ function Room:removePlayerMark(player, mark, count)
self:setPlayerMark(player, mark, math.max(num - count, 0))
end
----@param tag_name string
+--- 将房间中某个tag设为特定值。
+---
+--- 当在编程中想在服务端搞点全局变量的时候哦,不要自己设置全局变量或者上值,而是应该使用room的tag。
+---@param tag_name string @ tag名字
+---@param value any @ 值
function Room:setTag(tag_name, value)
self.tag[tag_name] = value
end
----@param tag_name string
+--- 获得某个tag的值。
+---@param tag_name string @ tag名字
function Room:getTag(tag_name)
return self.tag[tag_name]
end
----@param tag_name string
+--- 删除某个tag。
+---@param tag_name string @ tag名字
function Room:removeTag(tag_name)
self.tag[tag_name] = nil
end
@@ -339,17 +382,19 @@ end
-- network functions, notify function
------------------------------------------------------------------------
----@param player ServerPlayer
----@param property string
+--- 向所有角色广播一名角色的某个property,让大家都知道
+---@param player ServerPlayer @ 要被广而告之的那名角色
+---@param property string @ 这名角色的某种属性,像是"hp"之类的,其实就是Player类的属性名
function Room:broadcastProperty(player, property)
for _, p in ipairs(self.players) do
self:notifyProperty(p, player, property)
end
end
----@param p ServerPlayer
----@param player ServerPlayer
----@param property string
+--- 将player的属性property告诉p。
+---@param p ServerPlayer @ 要被告知相应属性的那名玩家
+---@param player ServerPlayer @ 拥有那个属性的玩家
+---@param property string @ 属性名称
function Room:notifyProperty(p, player, property)
p:doNotify("PropertyUpdate", json.encode{
player.id,
@@ -358,9 +403,10 @@ function Room:notifyProperty(p, player, property)
})
end
----@param command string
----@param jsonData string
----@param players ServerPlayer[] | nil @ default all players
+--- 向多名玩家广播一条消息。
+---@param command string @ 发出这条消息的消息类型
+---@param jsonData string @ 消息的数据,一般是JSON字符串,也可以是普通字符串,取决于client怎么处理了
+---@param players ServerPlayer[] | nil @ 要告知的玩家列表,默认为所有人
function Room:doBroadcastNotify(command, jsonData, players)
players = players or self.players
for _, p in ipairs(players) do
@@ -368,11 +414,12 @@ function Room:doBroadcastNotify(command, jsonData, players)
end
end
----@param player ServerPlayer
----@param command string
----@param jsonData string
----@param wait boolean @ default true
----@return string | nil
+--- 向某个玩家发起一次Request。
+---@param player ServerPlayer @ 发出这个请求的目标玩家
+---@param command string @ 请求的类型
+---@param jsonData string @ 请求的数据
+---@param wait boolean @ 是否要等待答复,默认为true
+---@return string | nil @ 收到的答复,如果wait为false的话就返回nil
function Room:doRequest(player, command, jsonData, wait)
if wait == nil then wait = true end
player:doRequest(command, jsonData, self.timeout)
@@ -382,8 +429,10 @@ function Room:doRequest(player, command, jsonData, wait)
end
end
----@param command string
----@param players ServerPlayer[]
+--- 向多名玩家发出请求。
+---@param command string @ 请求类型
+---@param players ServerPlayer[] @ 发出请求的玩家列表
+---@param jsonData string @ 请求数据
function Room:doBroadcastRequest(command, players, jsonData)
players = players or self.players
for _, p in ipairs(players) do
@@ -399,8 +448,15 @@ function Room:doBroadcastRequest(command, players, jsonData)
end
end
----@param command string
----@param players ServerPlayer[]
+--- 向多名玩家发出竞争请求。
+---
+--- 他们都可以做出答复,但是服务器只认可第一个做出回答的角色。
+---
+--- 返回获胜的角色,可以通过属性获得回复的具体内容。
+---@param command string @ 请求类型
+---@param players ServerPlayer[] @ 要竞争这次请求的玩家列表
+---@param jsonData string @ 请求数据
+---@return ServerPlayer | nil @ 在这次竞争请求中获胜的角色,可能是nil
function Room:doRaceRequest(command, players, jsonData)
players = players or self.players
-- self:notifyMoveFocus(players, command)
@@ -441,6 +497,7 @@ function Room:doRaceRequest(command, players, jsonData)
end
-- main loop for the request handling coroutine
+--- 这是个私有函数,不用管。
function Room:requestLoop(rest_time)
local function tellRoomToObserver(player)
local observee = self.players[1]
@@ -530,8 +587,10 @@ function Room:requestLoop(rest_time)
end
end
--- delay function, should only be used in main coroutine
----@param ms integer @ millisecond to be delayed
+--- 延迟一段时间。
+---
+--- 这个函数只应该在主协程中使用。
+---@param ms integer @ 要延迟的毫秒数
function Room:delay(ms)
local start = os.getms()
while true do
@@ -543,9 +602,10 @@ function Room:delay(ms)
end
end
----@param players ServerPlayer[]
----@param card_moves CardsMoveStruct[]
----@param forceVisible boolean
+--- 向多名玩家告知一次移牌行为。
+---@param players ServerPlayer[] | nil @ 要被告知的玩家列表,默认为全员
+---@param card_moves CardsMoveStruct[] @ 要告知的移牌信息列表
+---@param forceVisible boolean @ 是否让所有牌对告知目标可见
function Room:notifyMoveCards(players, card_moves, forceVisible)
if players == nil or players == {} then players = self.players end
for _, p in ipairs(players) do
@@ -588,8 +648,11 @@ function Room:notifyMoveCards(players, card_moves, forceVisible)
end
end
----@param players ServerPlayer | ServerPlayer[]
----@param command string
+--- 将焦点转移给一名或者多名角色,并广而告之。
+---
+--- 形象点说,就是在那些玩家下面显示一个“弃牌 思考中...”之类的烧条提示。
+---@param players ServerPlayer | ServerPlayer[] @ 要获得焦点的一名或者多名角色
+---@param command string @ 烧条的提示文字
function Room:notifyMoveFocus(players, command)
if (players.class) then
players = {players}
@@ -606,17 +669,27 @@ function Room:notifyMoveFocus(players, command)
})
end
----@param log LogMessage
+--- 向战报中发送一条log。
+---@param log LogMessage @ Log的实际内容
function Room:sendLog(log)
self:doBroadcastNotify("GameLog", json.encode(log))
end
+--- 播放某种动画效果给players看。
+---@param type string @ 动画名字
+---@param data any @ 这个动画附加的额外信息,在这个函数将会被转成json字符串
+---@param players ServerPlayer[] | nil @ 要观看动画的玩家们,默认为全员
function Room:doAnimate(type, data, players)
players = players or self.players
data.type = type
self:doBroadcastNotify("Animate", json.encode(data), players)
end
+--- 在player脸上展示名为name的emotion动效。
+---
+--- 这就是“杀”、“闪”之类的那个动画。
+---@param player ServerPlayer @ 被播放动画的那个角色
+---@param name string @ emotion名字,可以是一个路径
function Room:setEmotion(player, name)
self:doAnimate("Emotion", {
player = player.id,
@@ -624,6 +697,11 @@ function Room:setEmotion(player, name)
})
end
+--- 在一张card上播放一段emotion动效。
+---
+--- 这张card必须在处理区里面,或者至少客户端觉得它在处理区。
+---@param cid integer @ 被播放动效的那个牌的id
+---@param name string @ emotion名字,可以是一个路径
function Room:setCardEmotion(cid, name)
self:doAnimate("Emotion", {
player = cid,
@@ -632,6 +710,9 @@ function Room:setCardEmotion(cid, name)
})
end
+--- 播放一个全屏大动画。可以自己指定qml文件路径和额外的信息。
+---@param path string @ qml文件的路径,有默认值
+---@param extra_data any @ 要传递的额外信息
function Room:doSuperLightBox(path, extra_data)
path = path or "RoomElement/SuperLightBox.qml"
self:doAnimate("SuperLightBox", {
@@ -640,14 +721,16 @@ function Room:doSuperLightBox(path, extra_data)
})
end
+--- 基本上是个不常用函数就是了
function Room:sendLogEvent(type, data, players)
players = players or self.players
data.type = type
self:doBroadcastNotify("LogEvent", json.encode(data), players)
end
----@param skill_name string
----@param index integer
+--- 播放技能的语音。
+---@param skill_name string @ 技能名
+---@param index integer | nil @ 语音编号,默认为-1(也就是随机播放)
function Room:broadcastSkillInvoke(skill_name, index)
index = index or -1
self:sendLogEvent("PlaySkillSound", {
@@ -656,17 +739,20 @@ function Room:broadcastSkillInvoke(skill_name, index)
})
end
----@param skill_name string
----@param index integer
+--- 播放一段音频。
+---@param path string @ 音频文件路径
function Room:broadcastPlaySound(path)
self:sendLogEvent("PlaySound", {
name = path,
})
end
----@param player ServerPlayer
----@param skill_name string
----@param skill_type string
+--- 在player的脸上播放技能发动的特效。
+---
+--- 与此同时,在战报里面发一条“xxx发动了xxx”
+---@param player ServerPlayer @ 发动技能的那个玩家
+---@param skill_name string @ 技能名
+---@param skill_type string | nil @ 技能的动画效果,默认是那个技能的anim_type
function Room:notifySkillInvoked(player, skill_name, skill_type)
if not skill_type then
local skill = Fk.skills[skill_name]
@@ -686,8 +772,9 @@ function Room:notifySkillInvoked(player, skill_name, skill_type)
})
end
----@param source integer
----@param targets integer[]
+--- 播放从source指到targets的指示线效果。
+---@param source integer @ 指示线开始的那个玩家的id
+---@param targets integer[] @ 指示线目标玩家的id列表
function Room:doIndicate(source, targets)
local target_group = {}
for _, id in ipairs(targets) do
@@ -703,11 +790,15 @@ end
-- interactive functions
------------------------------------------------------------------------
----@param player ServerPlayer
----@param skill_name string
----@param prompt string
----@param cancelable boolean
----@param extra_data table
+--- 询问player是否要发动一个主动技。
+---
+--- 如果发动的话,那么会执行一下技能的onUse函数,然后返回选择的牌和目标等。
+---@param player ServerPlayer @ 询问目标
+---@param skill_name string @ 主动技的技能名
+---@param prompt string @ 烧条上面显示的提示文本内容
+---@param cancelable boolean @ 是否可以点取消
+---@param extra_data table @ 额外信息,因技能而异了
+---@return boolean, table
function Room:askForUseActiveSkill(player, skill_name, prompt, cancelable, extra_data)
prompt = prompt or ""
cancelable = cancelable or false
@@ -745,12 +836,17 @@ function Room:askForUseActiveSkill(player, skill_name, prompt, cancelable, extra
}
end
----@param player ServerPlayer
----@param minNum integer
----@param maxNum integer
----@param includeEquip boolean
----@param skillName string
----@param pattern string
+--- 询问一名角色弃牌。
+---
+--- 在这个函数里面牌已经被弃掉了。
+---@param player ServerPlayer @ 弃牌角色
+---@param minNum integer @ 最小值
+---@param maxNum integer @ 最大值
+---@param includeEquip boolean @ 能不能弃装备区?
+---@param skillName string @ 引发弃牌的技能名
+---@param cancelable boolean @ 能不能点取消?
+---@param pattern string @ 弃牌需要符合的规则
+---@return integer[] @ 弃掉的牌的id列表,可能是空的
function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern)
if minNum < 1 then
return nil
@@ -787,12 +883,14 @@ function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, can
return toDiscard
end
----@param player ServerPlayer
----@param targets integer[]
----@param minNum integer
----@param maxNum integer
----@param prompt string
----@return integer[]
+--- 询问一名玩家从targets中选择若干名玩家出来。
+---@param player ServerPlayer @ 要做选择的玩家
+---@param targets integer[] @ 可以选的目标范围,是玩家id数组
+---@param minNum integer @ 最小值
+---@param maxNum integer @ 最大值
+---@param prompt string @ 提示信息
+---@param skillName string @ 技能名
+---@return integer[] @ 选择的玩家id列表,可能为空
function Room:askForChoosePlayers(player, targets, minNum, maxNum, prompt, skillName)
if maxNum < 1 then
return {}
@@ -814,13 +912,17 @@ function Room:askForChoosePlayers(player, targets, minNum, maxNum, prompt, skill
end
end
----@param player ServerPlayer
----@param minNum integer
----@param maxNum integer
----@param includeEquip boolean
----@param skillName string
----@param cancelable boolean
----@param pattern string
+--- 询问一名玩家选择自己的几张牌。
+---
+--- 与askForDiscard类似,但是不对选择的牌进行操作就是了。
+---@param player ServerPlayer @ 要询问的玩家
+---@param minNum integer @ 最小值
+---@param maxNum integer @ 最大值
+---@param includeEquip boolean @ 能不能选装备
+---@param skillName string @ 技能名
+---@param cancelable boolean @ 能不能点取消
+---@param pattern string @ 选牌规则
+---@return integer[] @ 选择的牌的id列表,可能是空的
function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern)
if minNum < 1 then
return nil
@@ -856,12 +958,15 @@ function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancel
return chosenCards
end
----@param player ServerPlayer
----@param targets integer[]
----@param minNum integer
----@param maxNum integer
----@param pattern string
----@param prompt string
+--- 询问玩家选择1张牌和若干名角色。
+---
+--- 返回两个值,第一个是选择的目标列表,第二个是选择的那张牌的id
+---@param player ServerPlayer @ 要询问的玩家
+---@param targets integer[] @ 选择目标的id范围
+---@param minNum integer @ 选目标最小值
+---@param maxNum integer @ 选目标最大值
+---@param pattern string @ 选牌规则
+---@param prompt string @ 提示信息
---@return integer[], integer
function Room:askForChooseCardAndPlayers(player, targets, minNum, maxNum, pattern, prompt, skillName)
if maxNum < 1 then
@@ -884,9 +989,10 @@ function Room:askForChooseCardAndPlayers(player, targets, minNum, maxNum, patter
end
end
----@param player ServerPlayer
----@param generals string[]
----@return string
+--- 询问玩家选择一名武将。
+---@param player ServerPlayer @ 询问目标
+---@param generals string[] @ 可选武将
+---@return string @ 选择的武将
function Room:askForGeneral(player, generals)
local command = "AskForGeneral"
self:notifyMoveFocus(player, command)
@@ -908,11 +1014,12 @@ function Room:askForGeneral(player, generals)
return defaultChoice
end
----@param chooser ServerPlayer
----@param target ServerPlayer
----@param flag string @ "hej", h for handcard, e for equip, j for judge
----@param reason string
----@return integer
+--- 询问chooser,选择target的一张牌。
+---@param chooser ServerPlayer @ 要被询问的人
+---@param target ServerPlayer @ 被选牌的人
+---@param flag string @ 用"hej"三个字母的组合表示能选择哪些区域, h 手牌区, e - 装备区, j - 判定区
+---@param reason string @ 原因,一般是技能名
+---@return integer @ 选择的卡牌id
function Room:askForCardChosen(chooser, target, flag, reason)
local command = "AskForCardChosen"
self:notifyMoveFocus(chooser, command)
@@ -940,13 +1047,15 @@ function Room:askForCardChosen(chooser, target, flag, reason)
return result
end
----@param chooser ServerPlayer
----@param target ServerPlayer
----@param min integer
----@param max integer
----@param flag string @ "hej", h for handcard, e for equip, j for judge
----@param reason string
----@return integer[]
+--- 完全类似askForCardChosen,但是可以选择多张牌。
+--- 相应的,返回的是id的数组而不是单个id。
+---@param chooser ServerPlayer @ 要被询问的人
+---@param target ServerPlayer @ 被选牌的人
+---@param min integer @ 最小选牌数
+---@param max integer @ 最大选牌数
+---@param flag string @ 用"hej"三个字母的组合表示能选择哪些区域, h 手牌区, e - 装备区, j - 判定区
+---@param reason string @ 原因,一般是技能名
+---@return integer[] @ 选择的id
function Room:askForCardsChosen(chooser, target, min, max, flag, reason)
if min == 1 and max == 1 then
return { self:askForCardChosen(chooser, target, flag, reason) }
@@ -979,9 +1088,13 @@ function Room:askForCardsChosen(chooser, target, min, max, flag, reason)
return new_ret
end
----@param player ServerPlayer
----@param choices string[]
----@param skill_name string
+--- 询问一名玩家从众多选项中选择一个。
+---@param player ServerPlayer @ 要询问的玩家
+---@param choices string[] @ 可选选项列表
+---@param skill_name string @ 技能名
+---@param prompt string @ 提示信息
+---@param data any @ 暂未使用
+---@return string @ 选择的选项
function Room:askForChoice(player, choices, skill_name, prompt, data)
if #choices == 1 then return choices[1] end
local command = "AskForChoice"
@@ -994,8 +1107,10 @@ function Room:askForChoice(player, choices, skill_name, prompt, data)
return result
end
----@param player ServerPlayer
----@param skill_name string
+--- 询问玩家是否发动技能。
+---@param player ServerPlayer @ 要询问的玩家
+---@param skill_name string @ 技能名
+---@param data any @ 未使用
---@return boolean
function Room:askForSkillInvoke(player, skill_name, data)
local command = "AskForSkillInvoke"
@@ -1007,6 +1122,11 @@ function Room:askForSkillInvoke(player, skill_name, data)
end
-- TODO: guanxing type
+--- 询问玩家对若干牌进行观星。
+---
+--- 观星完成后,相关的牌会被置于牌堆顶或者牌堆底。所以这些cards最好不要来自牌堆,一般先用getNCards从牌堆拿出一些牌。
+---@param player ServerPlayer @ 要询问的玩家
+---@param cards integer[] @ 可以被观星的卡牌id列表
function Room:askForGuanxing(player, cards)
if #cards == 1 then
table.insert(self.draw_pile, 1, cards[1])
@@ -1044,6 +1164,7 @@ function Room:askForGuanxing(player, cards)
}
end
+--- 平时写DIY用不到的函数。
---@param player ServerPlayer
---@param data string
---@return CardUseStruct
@@ -1110,14 +1231,15 @@ end
-- available extra_data:
-- * must_targets: integer[]
----@param player ServerPlayer
----@param card_name string
----@param pattern string
----@param prompt string
----@param cancelable boolean
----@param extra_data integer
----@param event_data CardEffectEvent|null
----@return CardUseStruct
+--- 询问玩家使用一张牌。
+---@param player ServerPlayer @ 要询问的玩家
+---@param card_name string @ 使用牌的牌名,若pattern指定了则可随意写,它影响的是烧条的提示信息
+---@param pattern string @ 使用牌的规则,默认就是card_name的值
+---@param prompt string @ 提示信息
+---@param cancelable boolean @ 能否点取消
+---@param extra_data integer @ 额外信息
+---@param event_data CardEffectEvent|nil @ 事件信息
+---@return CardUseStruct | nil @ 返回关于本次使用牌的数据,以便后续处理
function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extra_data, event_data)
local command = "AskForUseCard"
self:notifyMoveFocus(player, card_name)
@@ -1147,11 +1269,14 @@ function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extr
return nil
end
----@param player ServerPlayer
----@param card_name string
----@param pattern string
----@param prompt string
----@param cancelable string
+--- 询问一名玩家打出一张牌。
+---@param player ServerPlayer @ 要询问的玩家
+---@param card_name string @ 牌名
+---@param pattern string @ 牌的规则
+---@param prompt string @ 提示信息
+---@param cancelable boolean @ 能否取消
+---@param extra_data any @ 额外数据
+---@return Card | nil @ 打出的牌
function Room:askForResponse(player, card_name, pattern, prompt, cancelable, extra_data)
local command = "AskForResponseCard"
self:notifyMoveFocus(player, card_name)
@@ -1183,6 +1308,16 @@ function Room:askForResponse(player, card_name, pattern, prompt, cancelable, ext
return nil
end
+--- 同时询问多名玩家是否使用某一张牌。
+---
+--- 函数名字虽然是“询问无懈可击”,不过其实也可以给别的牌用就是了。
+---@param players ServerPlayer[] @ 要询问的玩家列表
+---@param card_name string @ 询问的牌名,默认为无懈
+---@param pattern string @ 牌的规则
+---@param prompt string @ 提示信息
+---@param cancelable boolean @ 能否点取消
+---@param extra_data any @ 额外信息
+---@return CardUseStruct | nil @ 最终决胜出的卡牌使用信息
function Room:askForNullification(players, card_name, pattern, prompt, cancelable, extra_data)
if #players == 0 then
return nil
@@ -1210,11 +1345,12 @@ end
-- AG(a.k.a. Amazing Grace) functions
-- Popup a box that contains many cards, then ask player to choose one
----@param player ServerPlayer
----@param id_list integer[] | Card[]
----@param cancelable boolean
----@param reason string
----@return integer
+--- 询问玩家从AG中选择一张牌。
+---@param player ServerPlayer @ 要询问的玩家
+---@param id_list integer[] | Card[] @ 可选的卡牌列表
+---@param cancelable boolean @ 能否点取消
+---@param reason string @ 原因
+---@return integer @ 选择的卡牌
function Room:askForAG(player, id_list, cancelable, reason)
id_list = Card:getIdList(id_list)
if #id_list == 1 and not cancelable then
@@ -1231,22 +1367,28 @@ function Room:askForAG(player, id_list, cancelable, reason)
return tonumber(ret)
end
----@param player ServerPlayer
----@param id_list integer[] | Card[]
----@param disable_ids integer[] | Card[]
+--- 给player发一条消息,在他的窗口中用一系列卡牌填充一个AG。
+---@param player ServerPlayer @ 要通知的玩家
+---@param id_list integer[] | Card[] @ 要填充的卡牌
+---@param disable_ids integer[] | Card[] @ 未使用
function Room:fillAG(player, id_list, disable_ids)
id_list = Card:getIdList(id_list)
-- disable_ids = Card:getIdList(disable_ids)
player:doNotify("FillAG", json.encode{ id_list, disable_ids })
end
----@param player ServerPlayer
----@param id integer
+--- 告诉一些玩家,AG中的牌被taker取走了。
+---@param taker ServerPlayer @ 拿走牌的玩家
+---@param id integer @ 被拿走的牌
+---@param notify_list ServerPlayer[] @ 要告知的玩家,默认为全员
function Room:takeAG(taker, id, notify_list)
self:doBroadcastNotify("TakeAG", json.encode{ taker.id, id }, notify_list)
end
----@param player ServerPlayer
+--- 关闭player那侧显示的AG。
+---
+--- 若不传参(即player为nil),那么关闭所有玩家的AG。
+---@param player ServerPlayer @ 要关闭AG的玩家
function Room:closeAG(player)
if player then player:doNotify("CloseAG", "")
else self:doBroadcastNotify("CloseAG", "") end
@@ -1279,7 +1421,8 @@ local function execGameEvent(type, ...)
return ret
end
----@param cardUseEvent CardUseStruct
+--- 根据卡牌使用数据,去实际使用这个卡牌。
+---@param cardUseEvent CardUseStruct @ 使用数据
---@return boolean
function Room:useCard(cardUseEvent)
return execGameEvent(GameEvent.UseCard, cardUseEvent)
@@ -1397,6 +1540,7 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators)
return true
end
+--- 对卡牌使用数据进行生效
---@param cardUseEvent CardUseStruct
function Room:doCardUseEffect(cardUseEvent)
---@type table
@@ -1553,6 +1697,7 @@ function Room:doCardUseEffect(cardUseEvent)
end
end
+--- 对卡牌效果数据进行生效
---@param cardEffectEvent CardEffectEvent
function Room:doCardEffect(cardEffectEvent)
for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do
@@ -1662,24 +1807,28 @@ function Room:doCardEffect(cardEffectEvent)
end
end
+--- 对“打出牌”进行处理
---@param cardResponseEvent CardResponseEvent
function Room:responseCard(cardResponseEvent)
return execGameEvent(GameEvent.RespondCard, cardResponseEvent)
end
+
------------------------------------------------------------------------
-- move cards, and wrappers
------------------------------------------------------------------------
+--- 传入一系列移牌信息,去实际移动这些牌
---@vararg CardsMoveInfo
---@return boolean
function Room:moveCards(...)
return execGameEvent(GameEvent.MoveCards, ...)
end
----@param player integer|Player
----@param cid integer|Card
----@param unhide boolean
----@param reason CardMoveReason
+--- 让一名玩家获得一张牌
+---@param player integer|ServerPlayer @ 要拿牌的玩家
+---@param cid integer|Card @ 要拿到的卡牌
+---@param unhide boolean @ 是否明着拿
+---@param reason CardMoveReason @ 卡牌移动的原因
function Room:obtainCard(player, cid, unhide, reason)
if type(cid) ~= "number" then
assert(cid and cid:isInstanceOf(Card))
@@ -1704,11 +1853,12 @@ function Room:obtainCard(player, cid, unhide, reason)
})
end
----@param player ServerPlayer
----@param num integer
----@param skillName string
----@param fromPlace string
----@return integer[]
+--- 让玩家摸牌
+---@param player ServerPlayer @ 摸牌的玩家
+---@param num integer @ 摸牌数
+---@param skillName string @ 技能名
+---@param fromPlace string @ 摸牌的位置,"top" 或者 "bottom"
+---@return integer[] @ 摸到的牌
function Room:drawCards(player, num, skillName, fromPlace)
local topCards = self:getNCards(num, fromPlace)
self:moveCards({
@@ -1723,13 +1873,14 @@ function Room:drawCards(player, num, skillName, fromPlace)
return { table.unpack(topCards) }
end
----@param card Card | Card[]
----@param to_place integer
----@param target ServerPlayer
----@param reason integer
----@param skill_name string
----@param special_name string
----@param visible boolean
+--- 将一张或多张牌移动到某处
+---@param card Card | Card[] @ 要移动的牌
+---@param to_place integer @ 移动的目标位置
+---@param target ServerPlayer @ 移动的目标玩家
+---@param reason integer @ 移动时使用的移牌原因
+---@param skill_name string @ 技能名
+---@param special_name string @ 私人牌堆名
+---@param visible boolean @ 是否明置
function Room:moveCardTo(card, to_place, target, reason, skill_name, special_name, visible)
reason = reason or fk.ReasonJustMove
skill_name = skill_name or ""
@@ -1761,48 +1912,55 @@ end
-- actions related to hp
----@param player ServerPlayer
----@param num integer
----@param reason string|nil
----@param skillName string
----@param damageStruct DamageStruct|null
+--- 改变一名玩家的体力。
+---@param player ServerPlayer @ 玩家
+---@param num integer @ 变化量
+---@param reason string|nil @ 原因
+---@param skillName string @ 技能名
+---@param damageStruct DamageStruct|null @ 伤害数据
---@return boolean
function Room:changeHp(player, num, reason, skillName, damageStruct)
return execGameEvent(GameEvent.ChangeHp, player, num, reason, skillName, damageStruct)
end
----@param player ServerPlayer
----@param num integer
----@param skillName string
+--- 令一名玩家失去体力。
+---@param player ServerPlayer @ 玩家
+---@param num integer @ 失去的数量
+---@param skillName string @ 技能名
---@return boolean
function Room:loseHp(player, num, skillName)
return execGameEvent(GameEvent.LoseHp, player, num, skillName)
end
----@param player ServerPlayer
----@param num integer
+--- 改变一名玩家的体力上限。
+---@param player ServerPlayer @ 玩家
+---@param num integer @ 变化量
---@return boolean
function Room:changeMaxHp(player, num)
return execGameEvent(GameEvent.ChangeMaxHp, player, num)
end
+--- 根据伤害数据造成伤害。
---@param damageStruct DamageStruct
---@return boolean
function Room:damage(damageStruct)
return execGameEvent(GameEvent.Damage, damageStruct)
end
+--- 根据回复数据回复体力。
---@param recoverStruct RecoverStruct
---@return boolean
function Room:recover(recoverStruct)
return execGameEvent(GameEvent.Recover, recoverStruct)
end
+--- 根据濒死数据让人进入濒死。
---@param dyingStruct DyingStruct
function Room:enterDying(dyingStruct)
return execGameEvent(GameEvent.Dying, dyingStruct)
end
+--- 根据死亡数据杀死角色。
---@param deathStruct DeathStruct
function Room:killPlayer(deathStruct)
return execGameEvent(GameEvent.Death, deathStruct)
@@ -1810,10 +1968,15 @@ end
-- lose/acquire skill actions
----@param player ServerPlayer
----@param skill_names string[] | string
----@param source_skill string | Skill | null
----@param no_trigger boolean | null
+--- 令一名玩家获得/失去技能。
+---
+--- skill_names 是字符串数组或者用管道符号(|)分割的字符串。
+---
+--- 每个skill_name都是要获得的技能的名。如果在skill_name前面加上"-",那就是失去技能。
+---@param player ServerPlayer @ 玩家
+---@param skill_names string[] | string @ 要获得/失去的技能
+---@param source_skill string | Skill | null @ 源技能
+---@param no_trigger boolean | null @ 是否不触发相关时机
function Room:handleAddLoseSkills(player, skill_names, source_skill, sendlog, no_trigger)
if type(skill_names) == "string" then
skill_names = skill_names:split("|")
@@ -1885,16 +2048,18 @@ end
-- judge
+--- 根据判定数据进行判定。判定的结果直接保存在这个数据中。
---@param data JudgeStruct
function Room:judge(data)
return execGameEvent(GameEvent.Judge, data)
end
----@param card Card
----@param player ServerPlayer
----@param judge JudgeStruct
----@param skillName string
----@param exchange boolean
+--- 改判。
+---@param card Card @ 改判的牌
+---@param player ServerPlayer @ 改判的玩家
+---@param judge JudgeStruct @ 要被改判的判定数据
+---@param skillName string @ 技能名
+---@param exchange boolean @ 是否要替换原有判定牌(即类似鬼道那样)
function Room:retrial(card, player, judge, skillName, exchange)
if not card then return end
local triggerResponded = self.owner_map[card:getEffectiveId()] == player
@@ -1941,10 +2106,11 @@ function Room:retrial(card, player, judge, skillName, exchange)
end
end
----@param card_ids integer[]
----@param skillName string
----@param who ServerPlayer
----@param thrower ServerPlayer
+--- 弃置一名玩家的牌。
+---@param card_ids integer[] @ 被弃掉的牌
+---@param skillName string @ 技能名
+---@param who ServerPlayer @ 被弃牌的人
+---@param thrower ServerPlayer @ 弃别人牌的人
function Room:throwCard(card_ids, skillName, who, thrower)
if type(card_ids) == "number" then
card_ids = {card_ids}
@@ -1961,6 +2127,7 @@ function Room:throwCard(card_ids, skillName, who, thrower)
})
end
+--- 根据拼点信息开始拼点。
---@param pindianData PindianStruct
function Room:pindian(pindianData)
return execGameEvent(GameEvent.Pindian, pindianData)
@@ -1996,6 +2163,7 @@ function Room:adjustSeats()
self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle))
end
+--- 洗牌。
function Room:shuffleDrawPile()
if #self.draw_pile + #self.discard_pile == 0 then
return
@@ -2009,9 +2177,10 @@ function Room:shuffleDrawPile()
table.shuffle(self.draw_pile)
end
----@param player ServerPlayer
----@param skill Skill
----@param effect_cb fun()
+--- 使用技能。先增加技能发动次数,再执行相应的函数。
+---@param player ServerPlayer @ 发动技能的玩家
+---@param skill Skill @ 发动的技能
+---@param effect_cb fun() @ 实际要调用的函数
function Room:useSkill(player, skill, effect_cb)
if not skill.mute then
if skill.attached_equip then
@@ -2031,6 +2200,8 @@ function Room:useSkill(player, skill, effect_cb)
end
end
+--- 结束一局游戏。
+---@param winner string @ 获胜的身份,空字符串表示平局
function Room:gameOver(winner)
self.logic:trigger(fk.GameFinished, nil, winner)
self.game_started = false
diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua
index 318867d5..f109deac 100644
--- a/lua/server/system_enum.lua
+++ b/lua/server/system_enum.lua
@@ -31,15 +31,17 @@
---@field public toCard Card
---@field public winner ServerPlayer|null
+--- 描述和一次体力变化有关的数据
---@class HpChangedData
----@field public num integer
----@field public reason string
----@field public skillName string
----@field public damageEvent DamageStruct|null
+---@field public num integer @ 体力变化量,可能是正数或者负数
+---@field public reason string @ 体力变化原因
+---@field public skillName string @ 引起体力变化的技能名
+---@field public damageEvent DamageStruct|nil @ 引起这次体力变化的伤害数据
+--- 描述跟失去体力有关的数据
---@class HpLostData
----@field public num integer
----@field public skillName string
+---@field public num integer @ 失去体力的数值
+---@field public skillName string @ 导致这次失去的技能名
---@alias DamageType integer
@@ -47,22 +49,24 @@ fk.NormalDamage = 1
fk.ThunderDamage = 2
fk.FireDamage = 3
+--- DamageStruct 用来描述和伤害事件有关的数据。
---@class DamageStruct
----@field public from ServerPlayer|null
----@field public to ServerPlayer
----@field public damage integer
----@field public card Card
----@field public chain boolean
----@field public damageType DamageType
----@field public skillName string
----@field public beginnerOfTheDamage boolean|null
+---@field public from ServerPlayer|null @ 伤害来源
+---@field public to ServerPlayer @ 伤害目标
+---@field public damage integer @ 伤害值
+---@field public card Card | nil @ 造成伤害的牌
+---@field public chain boolean @ 伤害是否是铁索传导的伤害
+---@field public damageType DamageType @ 伤害的属性
+---@field public skillName string @ 造成本次伤害的技能名
+---@field public beginnerOfTheDamage boolean @ 是否是本次铁索传导的起点
+--- 用来描述和回复体力有关的数据。
---@class RecoverStruct
----@field public who ServerPlayer
----@field public num integer
----@field public recoverBy ServerPlayer|null
----@field public skillName string|null
----@field public card Card|null
+---@field public who ServerPlayer @ 回复体力的角色
+---@field public num integer @ 回复值
+---@field public recoverBy ServerPlayer|nil @ 此次回复的回复来源
+---@field public skillName string|nil @ 因何种技能而回复
+---@field public card Card|nil @ 造成此次回复的卡牌
---@class DyingStruct
---@field public who integer