From e4c806ff09ab1091057517ed50f027727c35ee53 Mon Sep 17 00:00:00 2001 From: notify Date: Wed, 29 Mar 2023 23:27:11 +0800 Subject: [PATCH] Doc2 (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 又写了一堆文档呢(乐 --------- Co-authored-by: deepskybird <32931830+deepskybird@users.noreply.github.com> --- docs/api/server.rst | 5 + docs/api/server/room.rst | 4 + docs/conf.py | 22 +- docs/dev/compile.rst | 23 +- docs/diy/01-env.rst | 13 +- docs/diy/02-skilltype.rst | 2 +- docs/diy/03-events.rst | 4 + docs/diy/03-newgeneral.rst | 28 +- docs/diy/04-newskill.rst | 7 +- docs/diy/05-trigger.rst | 238 ++++++++++++++++ docs/diy/06-active.rst | 35 +++ docs/diy/event/hp.rst | 111 +++++++- docs/diy/index.rst | 1 + docs/index.rst | 1 + docs/makebook.sh | 26 ++ docs/pic/diy3-pic1.webp | Bin 0 -> 8974 bytes docs/pic/diy3-pic2.webp | Bin 0 -> 41722 bytes docs/pic/diy4-pic1.webp | Bin 0 -> 23458 bytes docs/requirements.txt | 2 +- docs/usr/00-download.rst | 12 + docs/usr/01-play.rst | 12 + docs/usr/02-connect.rst | 16 ++ docs/usr/03-package.rst | 12 + docs/usr/index.rst | 12 + lua/core/card.lua | 54 +++- lua/server/room.lua | 555 ++++++++++++++++++++++++------------- lua/server/system_enum.lua | 42 +-- 27 files changed, 957 insertions(+), 280 deletions(-) create mode 100644 docs/api/server/room.rst create mode 100644 docs/diy/05-trigger.rst create mode 100644 docs/diy/06-active.rst create mode 100755 docs/makebook.sh create mode 100644 docs/pic/diy3-pic1.webp create mode 100644 docs/pic/diy3-pic2.webp create mode 100644 docs/pic/diy4-pic1.webp create mode 100644 docs/usr/00-download.rst create mode 100644 docs/usr/01-play.rst create mode 100644 docs/usr/02-connect.rst create mode 100644 docs/usr/03-package.rst create mode 100644 docs/usr/index.rst 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 0000000000000000000000000000000000000000..cd8d76acd4988ccb1fc7df37ca0ed438fcb2c198 GIT binary patch literal 8974 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 literal 0 HcmV?d00001 diff --git a/docs/pic/diy3-pic2.webp b/docs/pic/diy3-pic2.webp new file mode 100644 index 0000000000000000000000000000000000000000..f13315b48c080b3bf9542038d2189783ce3258b4 GIT binary patch literal 41722 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Ð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%0D9;yB7a*wkN=yk8_)>WzKbbr`T}|&s}`(i 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 L`;4dgyfgp+{@}dN literal 0 HcmV?d00001 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