mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-15 19:22:25 +08:00
Merge 'standard' (#53)
* todo * todo.md * doc for move cards * weapons excluding qinggang * equip sound and emotion * remove silence on the starting of using skill * add audio skill for TMD equip * fixbug: running and observing * when PreUseCard is broken, jump to move cards to discardPile * doc for diy * addToPile * viewPile (WIP) * fix git bug * auto update packages when md5 fail * use thread when updating pack * correct status() handling * update fkp * remove PKGBUILD since it's presented in AUR repo * fix fkp md5 bug * extensible qml * set bigAnim's z to 999 * nioh sheild * lijian * now mod can return nil * if dmg.nature == nil then = normal * disable notifyUI when qWarning * fix lijian and gender problem
This commit is contained in:
parent
4e1385fa6f
commit
afb537a661
44
PKGBUILD
44
PKGBUILD
|
@ -1,44 +0,0 @@
|
|||
# Maintainer: Notify-ctrl <notify-ctrl@qq.com>
|
||||
|
||||
pkgname=freekill
|
||||
_upper_pkgname=FreeKill
|
||||
pkgver=0.0.1
|
||||
pkgrel=1
|
||||
arch=('x86_64')
|
||||
url='https://github.com/Notify-ctrl/FreeKill'
|
||||
license=('GPL3')
|
||||
pkgdesc='A Bang-like card game'
|
||||
depends=('qt6-declarative' 'qt6-multimedia' 'qt6-5compat'
|
||||
'qt6-shadertools' 'libgit2' 'lua' 'sqlite' 'openssl'
|
||||
'readline' )
|
||||
makedepends=('cmake' 'flex' 'bison' 'qt6-tools' 'swig')
|
||||
# TODO: set source to release tarball
|
||||
source=("file:///home/notify/develop/FreeKill-demo.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
prepare() {
|
||||
cd ${srcdir}/${_upper_pkgname}
|
||||
rm -rf build
|
||||
}
|
||||
|
||||
build() {
|
||||
cd ${srcdir}/${_upper_pkgname}
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
}
|
||||
|
||||
package() {
|
||||
mkdir -p ${pkgdir}/usr/share/${_upper_pkgname}
|
||||
mkdir -p ${pkgdir}/usr/share/icons
|
||||
mkdir -p ${pkgdir}/usr/share/applications
|
||||
mkdir -p ${pkgdir}/usr/bin
|
||||
mkdir -p ${pkgdir}/usr/lib
|
||||
cd ${srcdir}/${_upper_pkgname}
|
||||
cmake --install build --prefix ${pkgdir}/usr --config Release
|
||||
|
||||
cp -r audio fonts image lua packages qml server build/zh_CN.qm \
|
||||
${pkgdir}/usr/share/${_upper_pkgname}
|
||||
install -Dm644 image/icon.png ${pkgdir}/usr/share/icons/freekill_logo.png
|
||||
install -Dm644 freekill.desktop ${pkgdir}/usr/share/applications/freekill.desktop
|
||||
}
|
|
@ -17,6 +17,7 @@ cp -r ../image assets/res
|
|||
cp -r ../lua assets/res
|
||||
# TODO: Windows hosts machine
|
||||
cp -r /etc/ca-certificates/extracted/cadir assets/res/certs
|
||||
chmod 644 assets/res/certs/*
|
||||
mkdir assets/res/packages
|
||||
cp -r ../packages/standard assets/res/packages
|
||||
cp -r ../packages/standard_cards assets/res/packages
|
||||
|
|
|
@ -122,6 +122,34 @@ ___
|
|||
|
||||
## 移动牌
|
||||
|
||||
移动牌的核心函数是`Room:moveCards(...)`。这是个变长参数函数,根据Emmy注解可知所有的参数都应该是CardsMoveInfo类型。CardsMoveInfo在[system_enum.lua](../../lua/server/system_enum.lua)里面有类型注解,来看看:
|
||||
|
||||
```lua
|
||||
---@class CardsMoveInfo
|
||||
---@field ids integer[]
|
||||
---@field from integer|null
|
||||
---@field to integer|null
|
||||
---@field toArea CardArea
|
||||
---@field moveReason CardMoveReason
|
||||
---@field proposer integer
|
||||
---@field skillName string|null
|
||||
---@field moveVisible boolean|null
|
||||
---@field specialName string|null
|
||||
---@field specialVisible boolean|null
|
||||
```
|
||||
|
||||
moveCards函数的第一步是将参数中所有的moveInfo都转化为CardsMoveStruct。CardsMoveStruct与CardsMoveInfo几乎没有区别,除了它将每一张牌都单独划分出了一个moveinfo之外。这么做是为了在同时移动来源不同的牌的时候,让牌能该明牌明牌,该暗牌暗牌。
|
||||
|
||||
全部转化完成后,先针对这个CardsMoveStruct[]触发一次BeforeCardsMove,给各种奇怪的触发技修改移动牌信息的机会。如此如此之后就正式开始移动牌了,移动完了之后再触发AfterCardsMove,这样就完成了对卡牌的移动。
|
||||
|
||||
正式移牌中,首先服务器会向各个客户端发送一条消息让客户端知道牌被移动了。
|
||||
|
||||
然后,对所有的CardsMoveStruct进行遍历,根据move.from和move.fromArea获取这张牌的id实际所在的数组,然后将这个id移动到目标数组中。如此就在服务端的数据层面移动了一张牌。移牌OK后,Room会更新这张牌的位置信息,然后视情况更新这张牌的锁定视为技信息。如果是装备牌的话,那么就做一些跟装备技能有关的事情。
|
||||
|
||||
___
|
||||
|
||||
## 使用牌
|
||||
|
||||
使用一张牌应该是全游戏最复杂而又最常见的一种事件了。说他复杂,其实也是被狗卡各种乱七八糟的技能和规则搞得很复杂的。
|
||||
|
||||
使用牌的核心函数是`Room:useCard`,接收的参数是CardUseStruct。不行太复杂了,过一阵子再来看吧。
|
||||
|
|
42
doc/dev/todo.md
Normal file
42
doc/dev/todo.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# TODO list
|
||||
|
||||
___
|
||||
|
||||
本文档用来记载一些可能会需要实现但暂且无暇的想法。留待日后再做或者同伴帮忙做了。
|
||||
|
||||
## 服务端的包验证
|
||||
|
||||
当客户端连接到服务端遇到MD5失败时,考虑:
|
||||
|
||||
1. 服务端除了告知失败之外还告知客户端自己的打包属性,即自己启用了哪些包,包的URL和版本等。
|
||||
2. 客户端根据信息禁用掉不需要的包,下载没有的包,更新启用的包,将需要的包都切换到服务器提供的版本。
|
||||
|
||||
___
|
||||
|
||||
## UI主题可拓展
|
||||
|
||||
考虑影响一下skin-bank.js,使其根据某个config的不同,将相应的值设为不同的路径。然后确保所有图片资源(关于页面和logo除外)都通过skin-bank.js访问所需图片。
|
||||
|
||||
由于所有拓展包都是只能通过init.lua访问,考虑为QmlBackend提供相应的Lua接口使其能够注册新的配置方案。配置文件本身的组织考虑JSON。
|
||||
|
||||
考虑在skin-bank.js中加入更多信息,例如各个组件的x, y, width, height等。因为影响到的都是Image所以设置这些应该是够了。
|
||||
|
||||
___
|
||||
|
||||
## 对话框可拓展
|
||||
|
||||
考虑劫营,严教等含有特殊交互框的例子。
|
||||
|
||||
可以通过类似fk.Backend:loadDialog的方式弹出对话框。在Lua文件中相应的地方影响client_callback这张全局表。但同时考虑到拓展包比server/client相关的Lua先加载,或许需要一些特殊的办法,比如弄一张全局表,然后client.lua初始回调函数列表的时候先把那个表的结果加进去之类的。
|
||||
|
||||
至于Lua如何与QML进行交互,毫无疑问的是通过JSON字符串。QML最后需要自己将数据发回给服务端,这里需要用到ClientInstance手动发送了,不能用RoomLogic.js。同样的需要有统一的方法初始化QML对话框的数据,考虑都传入JSON字符串,然后QML在Component.onComplete时候加载初始化数据。
|
||||
|
||||
skin-bank.js的话依然可以用相对位置进行加载,这个理论上应该是不会被影响到。
|
||||
|
||||
___
|
||||
|
||||
## 代码简洁化
|
||||
|
||||
目前FK的lua代码中仍有不少地方的代码重用度不高,典型的例子是fk_ex.lua。考虑在这里用local function将重复代码合并一下。
|
||||
|
||||
还有视为技/触发技/主动技这些能够被“发动”的技能(即继承于UsableSkill的基本),它们的技能生效环节都有很多重复,比如播放声音动画和具体生效等。考虑在某处用一个函数总结一下,至于具体生效部分可以包在一个函数里面`function() skill:onEffect(room, effect) end`然后作为参数传递到useskill函数中。如果后面要做SkillUsed之类的时机的话,这方面就更加重要了。
|
227
doc/diy/01-env.md
Normal file
227
doc/diy/01-env.md
Normal file
|
@ -0,0 +1,227 @@
|
|||
# Fk DIY - 环境搭建
|
||||
|
||||
> [diy](./index.md) > 环境搭建
|
||||
|
||||
* [DIY总览](#diy总览)
|
||||
* [环境搭建](#环境搭建)
|
||||
* [Fk](#fk)
|
||||
* [代码编辑器](#代码编辑器)
|
||||
* [git](#git)
|
||||
* [安装git](#安装git)
|
||||
* [新增mod](#新增mod)
|
||||
* [发布mod](#发布mod)
|
||||
* [将终端切换为Git Bash](#将终端切换为git-bash)
|
||||
* [配置ssh key](#配置ssh-key)
|
||||
* [新建git仓库](#新建git仓库)
|
||||
* [让他人安装并游玩你的mod](#让他人安装并游玩你的mod)
|
||||
* [更新mod](#更新mod)
|
||||
|
||||
___
|
||||
|
||||
## DIY总览
|
||||
|
||||
正如[项目README](../../README.md)所言,FreeKill“试图打造一个最适合diy玩家游玩的民间三国杀”。即便是最开始游戏功能尚未完善,FreeKill也已经具备了对DIY的支持。所有拓展包都列在packages/文件夹下,感兴趣者可以自行查看。
|
||||
|
||||
欲为FreeKill进行DIY,需要使用的编程语言为Lua。若您对Lua语言完全不熟悉,推荐去[菜鸟教程](https://www.runoob.com/lua/lua-tutorial.html)速通一遍基本语法。剩下的就基本是在实践中慢慢领会了。
|
||||
|
||||
FreeKill本体中自带有标准包和标准卡牌包,可作为DIY时候的例子。事实上,其他DIY包也是像这样子组织的。
|
||||
|
||||
接下来讲述如何配置环境。
|
||||
|
||||
___
|
||||
|
||||
## 环境搭建
|
||||
|
||||
### Fk
|
||||
|
||||
Fk是游戏本身,也是拓展包运行的平台。事实上这份文档应该与Fk一同发布的,如果您正在阅读这份文档,那么您理应已经接收到了Fk本身。
|
||||
|
||||
### 代码编辑器
|
||||
|
||||
代码编辑器任选一种即可,但一定要确保以下几点:
|
||||
|
||||
- 至少要是一款**代码**编辑器,要有语法高亮功能
|
||||
- 需要有EmmyLua插件的支持
|
||||
- 需要默认UTF-8格式保存代码文件
|
||||
|
||||
> EmmyLua是一种特别的Lua注释方式,可以为本来弱类型的Lua语言提供类型支持,这对于像FreeKill这种稍有规模的Lua项目是十分必要的。目前能提供开箱即用的EmmyLua插件编辑器主要有IntelliJ IDEA和Visual Studio Code。EmmyLua也能以LSP的方式运行,因此支持LSP的编辑器(这种就多了,比如vim, sublime)也能符合条件。
|
||||
|
||||
编辑器的具体安装以及插件配置不在此赘述。
|
||||
|
||||
> 出于易用性和免费的考虑,推荐用VSCode进行拓展。下文将以VSCode为编辑器进行进一步说明。
|
||||
|
||||
### git
|
||||
|
||||
git就不必多介绍了吧,这里说说为什么需要配置git。这是因为在Fk中,拓展包拥有在线安装/在线更新的功能,这种功能都是依托于git进行的,因此如果你打算将自己的拓展包发布出去的话,就需要将其创建git仓库,并托管到git托管网站去。
|
||||
|
||||
> 考虑到国内绝大部分人的访问速度,综合国内几家git托管平台,建议使用gitee。
|
||||
|
||||
大多数人可能从未用过git,并且git上手的门槛并不低,因此以下会对涉及git的操作进行详尽的解说。
|
||||
|
||||
#### 安装git
|
||||
|
||||
前往[官网](https://git-scm.com/download/win)下载git,下载64-bit Git for Windows Setup。这样应该会为您下载一个exe安装包。
|
||||
|
||||
考虑到官网的下载链接实际上指向github,而且可能连官网的都进不去,所以也考虑[从清华源下载Git](https://mirrors.tuna.tsinghua.edu.cn/github-release/git-for-windows/git/)。
|
||||
|
||||
欲验证安装是否完成,可以按下Win+R -> cmd弹出命令行窗口,输入git命令,如果出来一长串英文说明安装成功了。
|
||||
|
||||
___
|
||||
|
||||
## 新增mod
|
||||
|
||||
这只是新增mod的一个例子。当然了,以后有啥要做的实例也会继续用这个拓展包的。
|
||||
|
||||
首先前往packages下,新建名为fk_study的文件夹。
|
||||
|
||||
再在fk_study下新建init.lua文件,写入以下内容:
|
||||
|
||||
```lua
|
||||
local extension = Package("fk_study")
|
||||
|
||||
Fk:loadTranslationTable{
|
||||
["fk_study"] = "fk学习包",
|
||||
}
|
||||
|
||||
return { extension }
|
||||
```
|
||||
|
||||
保存退出,打开Fk,进武将一览。你现在应该能在武将一览里面看到“fk学习包”了,但也仅此而已了,毕竟这还只是个空壳包而已。
|
||||
|
||||
至此我们已经创建了最为简单的mod。mod的文件结构如下:
|
||||
|
||||
fk_study
|
||||
└── init.lua
|
||||
|
||||
___
|
||||
|
||||
## 发布mod
|
||||
|
||||
一种最常见的发布mod方式是把mod打包成zip,发到公共平台上供玩家下载。这种办法虽然可行,但并不是fk推荐的做法。
|
||||
|
||||
> 以下介绍的其实就是新建仓库并推送到gitee的办法,熟悉git者请跳过。
|
||||
|
||||
下面着重介绍用git发布mod的办法。使用git进行发布的话,就可以让用户体验在线安装、在线更新等便捷之处。
|
||||
|
||||
以下假设你使用vscode进行代码编辑。你是先用vscode打开了整个FreeKill文件夹,再在其中新建文件夹和文件、然后进行编辑的。
|
||||
|
||||
菜单栏 -> 终端 -> 新建终端。我们接下来的工作都在终端中完成。
|
||||
|
||||
### 将终端切换为Git Bash
|
||||
|
||||
启动终端后,终端的内容大概是:
|
||||
|
||||
```plain
|
||||
Mincrosoft Windows 10 [版本号啥的]
|
||||
xxxxxxxx 保留所有权利。
|
||||
|
||||
C:\FreeKill>
|
||||
```
|
||||
|
||||
这个是Windows自带的cmd,我们不使用这个,而是去用git bash。此时终端上面应该有这么一条:
|
||||
|
||||
```plain
|
||||
问题 输出 调试控制台 _终端_ cmd + v 分屏 删除
|
||||
注意这个加号
|
||||
```
|
||||
|
||||
这时候点击加号右边那个下拉箭头,选择"Git Bash"。这样就成功的切换到了git bash中,终端看起来应该像这样:
|
||||
|
||||
```plain
|
||||
xxx@xxxxx MINGW64 /c/FreeKill
|
||||
$
|
||||
```
|
||||
|
||||
### 配置ssh key
|
||||
|
||||
你应该已经注册好了自己的gitee账号。首先在Git bash中输入这些命令(#号后面的是命令注释,不用照搬;命令开头的\$符号是模拟shell的界面,不要输入进去):
|
||||
|
||||
```sh
|
||||
$ cd ~/.ssh
|
||||
$ ssh-keygen -t rsa -C "你注册用的邮箱地址" # 换成自己真正的邮箱
|
||||
# 出来一堆东西,一路点回车就是了
|
||||
$ cat id_rsa.pub
|
||||
# 出来一堆乱七八糟的东西:ssh-rsa <一大堆乱七八糟的内容> <你的邮箱>
|
||||
$ cd -
|
||||
```
|
||||
|
||||
在cat id_rsa.pub中,出来的那一堆以ssh-rsa的输出,就是这里要用到的“公钥”。然后在gitee中:
|
||||
|
||||
1. 点右上角你的头像,点账号设置
|
||||
2. 点左侧栏中 安全设置 - SSH公钥
|
||||
3. 此时弹出公钥添加界面,标题任选,下面公钥那一栏中,将刚刚生成的公钥复制粘贴上去
|
||||
4. 点确定
|
||||
|
||||
这样就配置好了ssh公钥。进行验证,在bash中使用命令:
|
||||
|
||||
```sh
|
||||
$ ssh -T git@gitee.com
|
||||
Hi xxxx! You've successfully authenticated, but GITEE.COM does not provide shell access.
|
||||
```
|
||||
|
||||
输出像Hi xxx!这样的信息,就说明配置成功了。否则需要进一步检查自己的操作,上网查一下吧。
|
||||
|
||||
### 新建git仓库
|
||||
|
||||
现在终端的工作目录应该还是FreeKill根目录,我们先切换到mod的目录去,然后再在shell中进行一系列操作。
|
||||
|
||||
```sh
|
||||
$ cd packages/fk_study
|
||||
$ git init # 创建新的空仓库
|
||||
$ git add . # 将文件夹中所有的文件都加入暂存区
|
||||
$ git commit -m "init" # 提交目前所有的文件,这样文件就正式存在于仓库里面了
|
||||
作者身份未知
|
||||
*** 请告诉我您是谁。
|
||||
运行
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
|
||||
来设置您账号的缺省身份标识。如果仅在本仓库设置身份标识,则省略 --global 参数。
|
||||
```
|
||||
|
||||
看来我们初次安装Git,Git还不知道我们的身份呢,不过git已经告诉了配置所需的命令了。运行前一条命令告知自己的名字,运行后一条命令告知自己的邮箱。如此就OK了,然后再commit一次。
|
||||
|
||||
然后在gitee中也新建一个仓库,取名为fk_study。接下来回到终端里面:
|
||||
|
||||
```sh
|
||||
$ git remote add origin git@gitee.com:xxx/fk_study # 其中这个xxx是你的用户名
|
||||
$ git push -u origin master
|
||||
```
|
||||
|
||||
OK了,刷新你新建的那个仓库的页面,可以看到里面已经有init.lua了。此时距离发布mod只有最后一步,那就是把仓库设置为开源。请自行在gitee中设置吧。
|
||||
|
||||
### 让他人安装并游玩你的mod
|
||||
|
||||
注意到Fk初始界面里面的“管理拓展包”了不?这个就是让你安装、删除、更新拓展包用的。在那个页面里面有个输入框,在浏览器中复制仓库的地址(比如https://gitee.com/xxx/fk_study/ ),粘贴到输入框,然后单击“从URL安装”即可安装拓展包了。
|
||||
|
||||
### 更新mod
|
||||
|
||||
现在mod要发生更新了,更新内容为一个武将。先在init.lua中新增武将吧。
|
||||
|
||||
```lua
|
||||
local study_sunce = General(extension, "study_sunce", "wu", 4)
|
||||
Fk:loadTranslationTable{
|
||||
["study_sunce"] = "孙伯符",
|
||||
}
|
||||
```
|
||||
|
||||
保存,此时注意vscode左侧栏变成了:
|
||||
|
||||
v fk_study
|
||||
└── init.lua M
|
||||
|
||||
init.lua后面出现了“M”,并且文件名字也变成了黄色,这表示这个文件已经被修改过了,接下来我们把修改文件提交到仓库中:
|
||||
|
||||
```sh
|
||||
$ git add . # 将当前目录下的文件暂存
|
||||
$ git commit -m "add general sunce" # 提交更改,提交说明为add general sunce
|
||||
$ git push # “推”到远端,也就是把本地的更新传给远端
|
||||
```
|
||||
|
||||
不喜欢用命令行的话,也可以用vscode自带的git支持完成这些操作,这里就不赘述了。做完git push后,实际上就已经完成更新了,可以让大伙点点更新按钮来更新你的新版本了。
|
||||
|
||||
___
|
||||
|
||||
以上介绍了大致的创建mod以及更新的流程。至于资源文件组织等等杂七杂八的问题,请参考已有的例子拓展包。
|
||||
|
||||
下一篇: [fk技能类型总览](./02-skilltype.md)
|
45
doc/diy/02-skilltype.md
Normal file
45
doc/diy/02-skilltype.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
# fk技能类型总览
|
||||
|
||||
> [diy](./index.md) > fk技能类型总览
|
||||
|
||||
___
|
||||
|
||||
fk的目的是便于三国杀的DIY,而三国杀DIY的核心就是制作各种技能了。
|
||||
|
||||
fk的技能分为两大类,这两大类又各自细分为更小的分类:
|
||||
|
||||
(关于这部分的源码详见lua/core/skill.lua和lua/core/skill_type下的所有文件)
|
||||
|
||||
* 可使用类技能(UsableSkill)
|
||||
* 触发技(TriggerSkill):在满足一定条件时,能够通过被动触发发挥效果的技能
|
||||
* 主动技(ActiveSkill):玩家主动发动的技能
|
||||
* 视为技(ViewAsSkill):将一张牌当做另一张牌的技能
|
||||
* 状态技(StatusSkill)
|
||||
* 距离技(DistanceSkill):影响距离计算的技能
|
||||
* 攻击范围技(DistanceSkill):影响攻击范围计算的技能
|
||||
* 手牌上限技(MaxCardsSkill):影响手牌上限计算的技能
|
||||
* 禁止技(ProhibitSkill):禁止成为卡牌目标的技能
|
||||
* 卡牌增强技(TargetModSkill):影响卡牌使用次数上限、目标上限、距离限制等等的技能
|
||||
* 锁定视为技(FilterSkill):让一张牌强制视为另一张牌的技能
|
||||
|
||||
其中,触发技的逻辑最为复杂,但是[已经在这里分析过了](../dev/gamelogic.md),故不再赘述。
|
||||
|
||||
主动技和状态技应该不算难,先按下不表。视为技与神杀有所区别,区别如下:
|
||||
|
||||
在神杀中,视为技是否可响应是专门写在enabled_at_response的,fk则不然,看倾国的代码:
|
||||
|
||||
```lua
|
||||
local qingguo = fk.CreateViewAsSkill{
|
||||
name = "qingguo",
|
||||
anim_type = "defensive",
|
||||
pattern = "jink",
|
||||
card_filter = function(self, to_select, selected)
|
||||
-- ...
|
||||
end,
|
||||
view_as = function(self, cards)
|
||||
-- ...
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
可见并没有编写跟响应时候有关的函数,也没有声明出牌阶段不可用。其中的奥妙就在于pattern中,视为技可以转化的卡牌都应该写在pattern里面,Fk会根据pattern的内容判断技能出牌阶段是否可用、是否能够响应等。
|
9
doc/diy/03-events.md
Normal file
9
doc/diy/03-events.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# fk中的游戏事件
|
||||
|
||||
在进行DIY时,需要对三国杀的规则有一定了解;在编写技能时,也要熟悉游戏提供的各种事件,他的触发方式、触发时机、相关数据。必须要知道这些才能写出正确的代码。
|
||||
|
||||
- [与游戏流程相关的事件](./event/gameflow.md)
|
||||
- [与体力值相关的事件](./event/hp.md)
|
||||
- [与卡牌使用有关的事件](./event/usecard.md)
|
||||
- [与移动牌有关的事件](./event/movecard.md)
|
||||
- [杂项](./event/misc.md)
|
33
doc/diy/event/gameflow.md
Normal file
33
doc/diy/event/gameflow.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# 与游戏流程有关的事件
|
||||
|
||||
先来看游戏流程本身。以下节选自lua/server/gamelogic.lua
|
||||
|
||||
```lua
|
||||
function GameLogic:action()
|
||||
self:trigger(fk.GameStart)
|
||||
local room = self.room
|
||||
|
||||
for _, p in ipairs(room.alive_players) do
|
||||
self:trigger(fk.DrawInitialCards, p, { num = 4 })
|
||||
end
|
||||
|
||||
local function checkNoHuman()
|
||||
-- 如果房里已经没有人类玩家了就结束游戏
|
||||
end
|
||||
|
||||
while true do
|
||||
self:trigger(fk.TurnStart, room.current)
|
||||
if room.game_finished then break end
|
||||
room.current = room.current:getNextAlive()
|
||||
if checkNoHuman() then
|
||||
room:gameOver("")
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
以上这段代码,述说的就是整个游戏流程的核心。首先开始游戏、摸初始手牌,然后按照座位顺序每人依次执行回合直到游戏结束。
|
||||
|
||||
___
|
||||
|
||||
TODO
|
11
doc/diy/event/hp.md
Normal file
11
doc/diy/event/hp.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# 与体力值相关的事件
|
||||
|
||||
___
|
||||
|
||||
## 伤害
|
||||
|
||||
## 失去体力/体力上限
|
||||
|
||||
## 回复体力
|
||||
|
||||
## 濒死和死亡
|
0
doc/diy/event/misc.md
Normal file
0
doc/diy/event/misc.md
Normal file
0
doc/diy/event/movecard.md
Normal file
0
doc/diy/event/movecard.md
Normal file
0
doc/diy/event/usecard.md
Normal file
0
doc/diy/event/usecard.md
Normal file
|
@ -3,3 +3,14 @@
|
|||
> diy
|
||||
|
||||
___
|
||||
|
||||
以下是一系列文档,旨在向从未接触过FreeKill(以后简称为Fk)的DIYer介绍Fk的DIY接口,以及如何打包、发布。
|
||||
|
||||
本系列文档针对对神杀Lua有基础的读者编写。
|
||||
|
||||
由于对于Win系统而言,fk仅仅支持Win 10及以上的64位系统,因此本文档假设您正使用Windows 10作为操作系统,且使用着64位的处理器。
|
||||
|
||||
文档中蓝色字均为超链接。
|
||||
|
||||
1. [环境搭建](./01-env.md)
|
||||
2. [fk技能类型总览](./02-skilltype.md)
|
||||
|
|
2
fkparse
2
fkparse
|
@ -1 +1 @@
|
|||
Subproject commit d150d2eec986c49a16f9e84772525a4fb7a84926
|
||||
Subproject commit 64481662879765f5631a291d512f7a74125051f3
|
|
@ -51,6 +51,10 @@
|
|||
<source>PackageManage</source>
|
||||
<translation>管理拓展包</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>updated packages for md5</source>
|
||||
<translation>已为您与服务器同步拓展包,请尝试再次连入</translation>
|
||||
</message>
|
||||
</context>
|
||||
|
||||
<context>
|
||||
|
|
|
@ -71,14 +71,14 @@ function Client:moveCards(moves)
|
|||
table.remove(from.player_cards[Player.Hand])
|
||||
end
|
||||
else
|
||||
from:removeCards(move.fromArea, move.ids)
|
||||
from:removeCards(move.fromArea, move.ids, move.fromSpecialName)
|
||||
end
|
||||
elseif move.fromArea == Card.DiscardPile then
|
||||
table.removeOne(self.discard_pile, move.ids[1])
|
||||
end
|
||||
|
||||
if move.to and move.toArea then
|
||||
self:getPlayerById(move.to):addCards(move.toArea, move.ids)
|
||||
self:getPlayerById(move.to):addCards(move.toArea, move.ids, move.specialName)
|
||||
elseif move.toArea == Card.DiscardPile then
|
||||
table.insert(self.discard_pile, move.ids[1])
|
||||
end
|
||||
|
@ -294,6 +294,8 @@ local function separateMoves(moves)
|
|||
toArea = move.toArea,
|
||||
fromArea = info.fromArea,
|
||||
moveReason = move.moveReason,
|
||||
specialName = move.specialName,
|
||||
fromSpecialName = info.fromSpecialName,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
@ -305,8 +307,9 @@ local function mergeMoves(moves)
|
|||
local ret = {}
|
||||
local temp = {}
|
||||
for _, move in ipairs(moves) do
|
||||
local info = string.format("%q,%q,%q,%q",
|
||||
move.from, move.to, move.fromArea, move.toArea)
|
||||
local info = string.format("%q,%q,%q,%q,%s,%s",
|
||||
move.from, move.to, move.fromArea, move.toArea,
|
||||
move.specialName, move.fromSpecialName)
|
||||
if temp[info] == nil then
|
||||
temp[info] = {
|
||||
ids = {},
|
||||
|
@ -315,6 +318,8 @@ local function mergeMoves(moves)
|
|||
fromArea = move.fromArea,
|
||||
toArea = move.toArea,
|
||||
moveReason = move.moveReason,
|
||||
specialName = move.specialName,
|
||||
fromSpecialName = move.fromSpecialName,
|
||||
}
|
||||
end
|
||||
table.insert(temp[info].ids, move.ids[1])
|
||||
|
|
|
@ -117,6 +117,10 @@ function DistanceTo(from, to)
|
|||
return a:distanceTo(b)
|
||||
end
|
||||
|
||||
function GetPile(id, name)
|
||||
return json.encode(ClientInstance:getPlayerById(id):getPile(name) or {})
|
||||
end
|
||||
|
||||
---@param card string | integer
|
||||
---@param player integer
|
||||
function CanUseCard(card, player)
|
||||
|
@ -138,7 +142,7 @@ function CanUseCard(card, player)
|
|||
end
|
||||
end
|
||||
|
||||
local ret = c.skill:canUse(ClientInstance:getPlayerById(player))
|
||||
local ret = c.skill:canUse(ClientInstance:getPlayerById(player), c)
|
||||
return json.encode(ret)
|
||||
end
|
||||
|
||||
|
@ -160,7 +164,7 @@ function CanUseCardToTarget(card, to_select, selected)
|
|||
return ActiveTargetFilter(t.skill, to_select, selected, t.subcards)
|
||||
end
|
||||
|
||||
local ret = c.skill:targetFilter(to_select, selected, selected_cards)
|
||||
local ret = c.skill:targetFilter(to_select, selected, selected_cards, c)
|
||||
if ret then
|
||||
local r = Fk:currentRoom()
|
||||
local status_skills = r.status_skills[ProhibitSkill] or {}
|
||||
|
@ -242,7 +246,7 @@ function ActiveCanUse(skill_name)
|
|||
end
|
||||
for _, n in ipairs(cnames) do
|
||||
local c = Fk:cloneCard(n)
|
||||
ret = c.skill:canUse(Self)
|
||||
ret = c.skill:canUse(Self, c)
|
||||
if ret then break end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,7 +59,9 @@ function Card:initialize(name, suit, number, color)
|
|||
self.name = name
|
||||
self.suit = suit or Card.NoSuit
|
||||
self.number = number or 0
|
||||
self.trueName = name
|
||||
|
||||
local name_splited = name:split("__")
|
||||
self.trueName = name_splited[#name_splited]
|
||||
|
||||
if suit == Card.Spade or suit == Card.Club then
|
||||
self.color = Card.Black
|
||||
|
@ -217,4 +219,26 @@ function Card:toLogString()
|
|||
return ret
|
||||
end
|
||||
|
||||
---@param c integer|integer[]|Card|Card[]
|
||||
---@return integer[]
|
||||
function Card.static:getIdList(c)
|
||||
if type(c) == "number" then
|
||||
return {c}
|
||||
end
|
||||
if c.class and c:isInstanceOf(Card) then
|
||||
if c:isVirtual() then
|
||||
return table.clone(c.subcards)
|
||||
else
|
||||
return {c.id}
|
||||
end
|
||||
end
|
||||
|
||||
-- array
|
||||
local ret = {}
|
||||
for _, c2 in ipairs(c) do
|
||||
table.insertTable(ret, Card:getIdList(c))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
return Card
|
||||
|
|
|
@ -68,12 +68,14 @@ function Engine:loadPackages()
|
|||
local pack = require(string.format("packages.%s", dir))
|
||||
-- Note that instance of Package is a table too
|
||||
-- so dont use type(pack) == "table" here
|
||||
if pack[1] ~= nil then
|
||||
for _, p in ipairs(pack) do
|
||||
self:loadPackage(p)
|
||||
if type(pack) == "table" then
|
||||
if pack[1] ~= nil then
|
||||
for _, p in ipairs(pack) do
|
||||
self:loadPackage(p)
|
||||
end
|
||||
else
|
||||
self:loadPackage(pack)
|
||||
end
|
||||
else
|
||||
self:loadPackage(pack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -259,6 +259,19 @@ function Player:getCardIds(playerAreas, specialName)
|
|||
return cardIds
|
||||
end
|
||||
|
||||
---@param name string
|
||||
function Player:getPile(name)
|
||||
return self.special_cards[name] or {}
|
||||
end
|
||||
|
||||
---@param id integer
|
||||
---@return string|null
|
||||
function Player:getPileNameOfId(id)
|
||||
for k, v in pairs(self.special_cards) do
|
||||
if table.contains(v, id) then return k end
|
||||
end
|
||||
end
|
||||
|
||||
-- for fkp only
|
||||
function Player:getHandcardNum()
|
||||
return #self:getCardIds(Player.Hand)
|
||||
|
|
|
@ -3,19 +3,19 @@ local TargetModSkill = StatusSkill:subclass("TargetModSkill")
|
|||
|
||||
---@param player Player
|
||||
---@param card_skill ActiveSkill
|
||||
function TargetModSkill:getResidueNum(player, card_skill, scope)
|
||||
function TargetModSkill:getResidueNum(player, card_skill, scope, card)
|
||||
return 0
|
||||
end
|
||||
|
||||
---@param player Player
|
||||
---@param card_skill ActiveSkill
|
||||
function TargetModSkill:getDistanceLimit(player, card_skill)
|
||||
function TargetModSkill:getDistanceLimit(player, card_skill, card)
|
||||
return 0
|
||||
end
|
||||
|
||||
---@param player Player
|
||||
---@param card_skill ActiveSkill
|
||||
function TargetModSkill:getExtraTargetNum(player, card_skill)
|
||||
function TargetModSkill:getExtraTargetNum(player, card_skill, card)
|
||||
return 0
|
||||
end
|
||||
|
||||
|
|
|
@ -56,14 +56,9 @@ end
|
|||
function TriggerSkill:doCost(event, target, player, data)
|
||||
local ret = self:cost(event, target, player, data)
|
||||
if ret then
|
||||
local room = player.room
|
||||
if not self.mute then
|
||||
room:broadcastSkillInvoke(self.name)
|
||||
end
|
||||
room:notifySkillInvoked(player, self.name)
|
||||
player:addSkillUseHistory(self.name)
|
||||
ret = self:use(event, target, player, data)
|
||||
return ret
|
||||
return player.room:useSkill(player, self, function()
|
||||
return self:use(event, target, player, data)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,39 +14,39 @@ function UsableSkill:initialize(name, frequency)
|
|||
end
|
||||
|
||||
---@param player Player
|
||||
function UsableSkill:getMinTargetNum(player)
|
||||
function UsableSkill:getMinTargetNum(player, card)
|
||||
local ret = type(self.target_num) == "table" and self.target_num[1] or self.target_num
|
||||
return ret
|
||||
end
|
||||
|
||||
function UsableSkill:getMaxTargetNum(player)
|
||||
function UsableSkill:getMaxTargetNum(player, card)
|
||||
local ret = type(self.target_num) == "table" and self.target_num[2] or self.target_num
|
||||
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or {}
|
||||
for _, skill in ipairs(status_skills) do
|
||||
local correct = skill:getExtraTargetNum(player, self)
|
||||
local correct = skill:getExtraTargetNum(player, self, card)
|
||||
if correct == nil then correct = 0 end
|
||||
ret = ret + correct
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function UsableSkill:getMaxUseTime(player, scope)
|
||||
function UsableSkill:getMaxUseTime(player, scope, card)
|
||||
scope = scope or Player.HistoryTurn
|
||||
local ret = self.max_use_time[scope]
|
||||
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or {}
|
||||
for _, skill in ipairs(status_skills) do
|
||||
local correct = skill:getResidueNum(player, self, scope)
|
||||
local correct = skill:getResidueNum(player, self, scope, card)
|
||||
if correct == nil then correct = 0 end
|
||||
ret = ret + correct
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function UsableSkill:getDistanceLimit(player)
|
||||
function UsableSkill:getDistanceLimit(player, card)
|
||||
local ret = self.distance_limit
|
||||
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or {}
|
||||
for _, skill in ipairs(status_skills) do
|
||||
local correct = skill:getDistanceLimit(player, self)
|
||||
local correct = skill:getDistanceLimit(player, self, card)
|
||||
if correct == nil then correct = 0 end
|
||||
ret = ret + correct
|
||||
end
|
||||
|
|
|
@ -9,6 +9,57 @@ function fk.qlist(list)
|
|||
return qlist_iterator, list, -1
|
||||
end
|
||||
|
||||
---@param func fun(element, index, array)
|
||||
function table:forEach(func)
|
||||
for i, v in ipairs(self) do
|
||||
func(v, i, self)
|
||||
end
|
||||
end
|
||||
|
||||
---@param func fun(element, index, array)
|
||||
function table:every(func)
|
||||
for i, v in ipairs(self) do
|
||||
if not func(v, i, self) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---@generic T
|
||||
---@param self T[]
|
||||
---@param func fun(element, index, array)
|
||||
---@return T[]
|
||||
function table.filter(self, func)
|
||||
local ret = {}
|
||||
for i, v in ipairs(self) do
|
||||
if func(v, i, self) then
|
||||
table.insert(ret, v)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param func fun(element, index, array)
|
||||
function table.map(self, func)
|
||||
local ret = {}
|
||||
for i, v in ipairs(self) do
|
||||
table.insert(ret, func(v, i, self))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@generic T
|
||||
---@param self T[]
|
||||
---@return T[]
|
||||
function table.reverse(self)
|
||||
local ret = {}
|
||||
for _, e in ipairs(self) do
|
||||
table.insert(ret, 1, e)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table:contains(element)
|
||||
if #self == 0 then return false end
|
||||
for _, e in ipairs(self) do
|
||||
|
@ -120,7 +171,7 @@ Sql = {
|
|||
--- Execute a `SELECT` SQL statement.
|
||||
---@param db fk.SQLite3
|
||||
---@param sql string
|
||||
---@return table @ { [columnName] --> result : string[] }
|
||||
---@return table[] @ Array of Json object, the key is column name and value is row value
|
||||
exec_select = function(db, sql)
|
||||
return json.decode(fk.SelectFromDb(db, sql))
|
||||
end,
|
||||
|
|
|
@ -355,6 +355,7 @@ end
|
|||
|
||||
---@class CardSpec: Card
|
||||
---@field skill Skill
|
||||
---@field equip_skill Skill
|
||||
|
||||
local defaultCardSkill = fk.CreateActiveSkill{
|
||||
name = "default_card_skill",
|
||||
|
|
|
@ -96,6 +96,62 @@ end
|
|||
fkp.functions.hasSkill = function(p, s) return p:hasSkill(s) end
|
||||
fkp.functions.turnOver = function(p) p:turnOver() end
|
||||
fkp.functions.distanceTo = function(p1, p2) return p1:distanceTo(p2) end
|
||||
fkp.functions.getCards = function(p, area)
|
||||
return table.map(p:getCardIds(area), function(id) return Fk:getCardById(id) end)
|
||||
end
|
||||
|
||||
-- interactive methods
|
||||
|
||||
fkp.functions.buildPrompt = function(base, src, dest, arg, arg2)
|
||||
if src == nil then
|
||||
src = ""
|
||||
else
|
||||
src = src.id
|
||||
end
|
||||
if dest == nil then
|
||||
dest = ""
|
||||
else
|
||||
dest = dest.id
|
||||
end
|
||||
if arg == nil then arg = "" end
|
||||
if arg2 == nil then arg2 = "" end
|
||||
|
||||
local prompt_tab = {src, dest, arg, arg2}
|
||||
if arg2 == "" then
|
||||
table.remove(prompt_tab, 4)
|
||||
if arg == "" then
|
||||
table.remove(prompt_tab, 3)
|
||||
if dest == "" then
|
||||
table.remove(prompt_tab, 2)
|
||||
if src == "" then
|
||||
table.remove(prompt_tab, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, str in ipairs(prompt_tab) do
|
||||
base = base .. ":" .. str
|
||||
end
|
||||
|
||||
return base
|
||||
end
|
||||
|
||||
fkp.functions.askForChoice = function(player, choices, reason)
|
||||
return player.room:askForChoice(player, choices, reason)
|
||||
end
|
||||
|
||||
fkp.functions.askForPlayerChosen = function(player, targets, reason, prompt, optional, notify)
|
||||
return player.room:askForChoosePlayers(player, targets, 1, 1, prompt, reason)
|
||||
end
|
||||
|
||||
fkp.functions.askForSkillInvoke = function(player, skill)
|
||||
return player:askForSkillInvoke(skill)
|
||||
end
|
||||
|
||||
fkp.functions.askRespondForCard = function(player, pattern, prompt, isRetrial, skill_name)
|
||||
return player.room:askForResponse(player, skill_name, pattern, prompt, true)
|
||||
end
|
||||
|
||||
-- skill prototypes
|
||||
--------------------------------------------
|
||||
|
@ -277,18 +333,18 @@ fkp.CreateTargetModSkill = function(_spec)
|
|||
return Fk:cloneCard(str)
|
||||
end
|
||||
if _spec.residue_func then
|
||||
spec.residue_func = function(self, target, skill, scope)
|
||||
return _spec.residue_func(self, target, getVCardFromActiveSkill(skill))
|
||||
spec.residue_func = function(self, target, skill, scope, card)
|
||||
return _spec.residue_func(self, target, card or getVCardFromActiveSkill(skill))
|
||||
end
|
||||
end
|
||||
if _spec.distance_limit_func then
|
||||
spec.distance_limit_func = function(self, target, skill)
|
||||
return _spec.distance_limit_func(self, target, getVCardFromActiveSkill(skill))
|
||||
spec.distance_limit_func = function(self, target, skill, card)
|
||||
return _spec.distance_limit_func(self, target, card or getVCardFromActiveSkill(skill))
|
||||
end
|
||||
end
|
||||
if _spec.extra_target_func then
|
||||
spec.extra_target_func = function(self, target, skill)
|
||||
return _spec.extra_target_func(self, target, getVCardFromActiveSkill(skill))
|
||||
spec.extra_target_func = function(self, target, skill, card)
|
||||
return _spec.extra_target_func(self, target, card or getVCardFromActiveSkill(skill))
|
||||
end
|
||||
end
|
||||
return fk.CreateTargetModSkill(spec)
|
||||
|
|
|
@ -63,7 +63,7 @@ function GameLogic:chooseGenerals()
|
|||
player.general = general
|
||||
player.gender = Fk.generals[general].gender
|
||||
self.room:notifyProperty(player, player, "general")
|
||||
self.room:notifyProperty(player, player, "gender")
|
||||
self.room:broadcastProperty(player, "gender")
|
||||
end
|
||||
local lord = room:getLord()
|
||||
local lord_general = nil
|
||||
|
|
|
@ -491,9 +491,9 @@ end
|
|||
-- delay function, should only be used in main coroutine
|
||||
---@param ms integer @ millisecond to be delayed
|
||||
function Room:delay(ms)
|
||||
local start = fk.GetMicroSecond()
|
||||
local start = os.getms()
|
||||
while true do
|
||||
if fk.GetMicroSecond() - start >= ms * 1000 then
|
||||
if os.getms() - start >= ms * 1000 then
|
||||
break
|
||||
end
|
||||
coroutine.yield()
|
||||
|
@ -521,9 +521,9 @@ function Room:notifyMoveCards(players, card_moves, forceVisible)
|
|||
|
||||
-- forceVisible make the move visible
|
||||
-- FIXME: move.moveInfo is an array, fix this
|
||||
move.moveVisible = (forceVisible)
|
||||
move.moveVisible = move.moveVisible or (forceVisible)
|
||||
-- if move is relevant to player, it should be open
|
||||
or ((move.from == p.id) or (move.to == p.id and move.toArea ~= Card.PlayerSpecial))
|
||||
or ((move.from == p.id) or (move.to == p.id))
|
||||
-- cards move from/to equip/judge/discard/processing should be open
|
||||
or infosContainArea(move.moveInfo, Card.PlayerEquip)
|
||||
or move.toArea == Card.PlayerEquip
|
||||
|
@ -589,6 +589,14 @@ function Room:setCardEmotion(cid, name)
|
|||
})
|
||||
end
|
||||
|
||||
function Room:doSuperLightBox(path, extra_data)
|
||||
path = path or "RoomElement/SuperLightBox.qml"
|
||||
self:doAnimate("SuperLightBox", {
|
||||
path = path,
|
||||
data = extra_data,
|
||||
})
|
||||
end
|
||||
|
||||
function Room:sendLogEvent(type, data, players)
|
||||
players = players or self.players
|
||||
data.type = type
|
||||
|
@ -885,26 +893,19 @@ function Room:handleUseCardReply(player, data)
|
|||
local skill = Fk.skills[card_data.skill]
|
||||
local selected_cards = card_data.subcards
|
||||
if skill:isInstanceOf(ActiveSkill) then
|
||||
if not skill.mute then
|
||||
self:broadcastSkillInvoke(skill.name)
|
||||
end
|
||||
self:notifySkillInvoked(player, skill.name)
|
||||
player:addSkillUseHistory(skill.name)
|
||||
self:doIndicate(player.id, targets)
|
||||
skill:onUse(self, {
|
||||
from = player.id,
|
||||
cards = selected_cards,
|
||||
tos = targets,
|
||||
})
|
||||
self:useSkill(player, skill, function()
|
||||
self:doIndicate(player.id, targets)
|
||||
skill:onUse(self, {
|
||||
from = player.id,
|
||||
cards = selected_cards,
|
||||
tos = targets,
|
||||
})
|
||||
end)
|
||||
return nil
|
||||
elseif skill:isInstanceOf(ViewAsSkill) then
|
||||
local c = skill:viewAs(selected_cards)
|
||||
if c then
|
||||
if not skill.mute then
|
||||
self:broadcastSkillInvoke(skill.name)
|
||||
end
|
||||
self:notifySkillInvoked(player, skill.name)
|
||||
player:addSkillUseHistory(skill.name)
|
||||
self:useSkill(player, skill)
|
||||
local use = {} ---@type CardUseStruct
|
||||
use.from = player.id
|
||||
use.tos = {}
|
||||
|
@ -1004,10 +1005,52 @@ function Room:askForNullification(players, card_name, pattern, prompt, cancelabl
|
|||
return nil
|
||||
end
|
||||
|
||||
-- Show a qml dialog and return qml's ClientInstance.replyToServer
|
||||
-- Do anything you like through this function
|
||||
|
||||
---@param player ServerPlayer
|
||||
---@param focustxt string
|
||||
---@param qmlPath string
|
||||
---@param extra_data any
|
||||
---@return string
|
||||
function Room:askForCustomDialog(player, focustxt, qmlPath, extra_data)
|
||||
local command = "CustomDialog"
|
||||
self:notifyMoveFocus(player, focustxt)
|
||||
return self:doRequest(player, command, json.encode{
|
||||
path = qmlPath,
|
||||
data = extra_data,
|
||||
})
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- use card logic, and wrappers
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local playCardEmotionAndSound = function(room, player, card)
|
||||
if card.type ~= Card.TypeEquip then
|
||||
room:setEmotion(player, "./packages/" ..
|
||||
card.package.extensionName .. "/image/anim/" .. card.name)
|
||||
end
|
||||
|
||||
local soundName
|
||||
if card.type == Card.TypeEquip then
|
||||
local subTypeStr
|
||||
if card.sub_type == Card.SubtypeDefensiveRide or card.sub_type == Card.SubtypeOffensiveRide then
|
||||
subTypeStr = "horse"
|
||||
elseif card.sub_type == Card.SubtypeWeapon then
|
||||
subTypeStr = "weapon"
|
||||
else
|
||||
subTypeStr = "armor"
|
||||
end
|
||||
|
||||
soundName = "./audio/card/common/" .. subTypeStr
|
||||
else
|
||||
soundName = "./packages/" .. card.package.extensionName .. "/audio/card/"
|
||||
.. (player.gender == General.Male and "male/" or "female/") .. card.name
|
||||
end
|
||||
room:broadcastPlaySound(soundName)
|
||||
end
|
||||
|
||||
---@param room Room
|
||||
---@param cardUseEvent CardUseStruct
|
||||
local sendCardEmotionAndLog = function(room, cardUseEvent)
|
||||
|
@ -1023,26 +1066,7 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
|
|||
card = temp.card
|
||||
end
|
||||
|
||||
room:setEmotion(room:getPlayerById(from), card.name)
|
||||
|
||||
local soundName
|
||||
if card.type == Card.TypeEquip then
|
||||
local subTypeStr
|
||||
if card.sub_type == Card.SubtypeDefensiveRide or card.sub_type == Card.SubtypeOffensiveRide then
|
||||
subTypeStr = "horse"
|
||||
elseif card.sub_type == Card.SubtypeWeapon then
|
||||
subTypeStr = "weapon"
|
||||
else
|
||||
subTypeStr = "armor"
|
||||
end
|
||||
|
||||
soundName = "./audio/card/common/" .. subTypeStr
|
||||
else
|
||||
soundName = "./packages/" .. card.package.extensionName .. "/audio/card/"
|
||||
.. (room:getPlayerById(from).gender == General.Male and "male/" or "female/") .. card.name
|
||||
end
|
||||
room:broadcastPlaySound(soundName)
|
||||
|
||||
playCardEmotionAndSound(room, room:getPlayerById(from), card)
|
||||
room:doAnimate("Indicate", {
|
||||
from = from,
|
||||
to = cardUseEvent.tos or {},
|
||||
|
@ -1272,7 +1296,7 @@ function Room:useCard(cardUseEvent)
|
|||
sendCardEmotionAndLog(self, cardUseEvent)
|
||||
|
||||
if self.logic:trigger(fk.PreCardUse, self:getPlayerById(cardUseEvent.from), cardUseEvent) then
|
||||
return false
|
||||
goto clean
|
||||
end
|
||||
|
||||
if not cardUseEvent.extraUse then
|
||||
|
@ -1291,153 +1315,13 @@ function Room:useCard(cardUseEvent)
|
|||
|
||||
self.logic:trigger(event, self:getPlayerById(cardUseEvent.from), cardUseEvent)
|
||||
if event == fk.CardUsing then
|
||||
---@type table<string, AimStruct>
|
||||
local aimEventCollaborators = {}
|
||||
if cardUseEvent.tos and not onAim(self, cardUseEvent, aimEventCollaborators) then
|
||||
break
|
||||
end
|
||||
|
||||
local realCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
|
||||
if cardUseEvent.card.type == Card.TypeEquip then
|
||||
if #realCardIds == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then
|
||||
self.moveCards({
|
||||
ids = realCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
else
|
||||
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
||||
local existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type)
|
||||
if existingEquipId then
|
||||
self:moveCards(
|
||||
{
|
||||
ids = { existingEquipId },
|
||||
from = target,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
},
|
||||
{
|
||||
ids = realCardIds,
|
||||
to = target,
|
||||
toArea = Card.PlayerEquip,
|
||||
moveReason = fk.ReasonUse,
|
||||
}
|
||||
)
|
||||
else
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
to = target,
|
||||
toArea = Card.PlayerEquip,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
break
|
||||
elseif cardUseEvent.card.sub_type == Card.SubtypeDelayedTrick then
|
||||
if #realCardIds == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
||||
if not self:getPlayerById(target).dead then
|
||||
local findSameCard = false
|
||||
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do
|
||||
if Fk:getCardById(cardId).trueName == cardUseEvent.card.trueName then
|
||||
findSameCard = true
|
||||
end
|
||||
end
|
||||
|
||||
if not findSameCard then
|
||||
if cardUseEvent.card:isVirtual() then
|
||||
self:getPlayerById(target):addVirtualEquip(cardUseEvent.card)
|
||||
end
|
||||
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
to = target,
|
||||
toArea = Card.PlayerJudge,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
if cardUseEvent.card.skill then
|
||||
---@type CardEffectEvent
|
||||
local cardEffectEvent = {
|
||||
from = cardUseEvent.from,
|
||||
tos = cardUseEvent.tos,
|
||||
card = cardUseEvent.card,
|
||||
toCard = cardUseEvent.toCard,
|
||||
responseToEvent = cardUseEvent.responseToEvent,
|
||||
nullifiedTargets = cardUseEvent.nullifiedTargets,
|
||||
disresponsiveList = cardUseEvent.disresponsiveList,
|
||||
unoffsetableList = cardUseEvent.unoffsetableList,
|
||||
addtionalDamage = cardUseEvent.addtionalDamage,
|
||||
cardIdsResponded = cardUseEvent.nullifiedTargets,
|
||||
}
|
||||
|
||||
if cardUseEvent.toCard ~= nil then
|
||||
self:doCardEffect(cardEffectEvent)
|
||||
else
|
||||
local collaboratorsIndex = {}
|
||||
for _, toId in ipairs(TargetGroup:getRealTargets(cardUseEvent.tos)) do
|
||||
if not table.contains(cardUseEvent.nullifiedTargets, toId) and self:getPlayerById(toId):isAlive() then
|
||||
if aimEventCollaborators[toId] then
|
||||
cardEffectEvent.to = toId
|
||||
collaboratorsIndex[toId] = collaboratorsIndex[toId] or 1
|
||||
local curAimEvent = aimEventCollaborators[toId][collaboratorsIndex[toId]]
|
||||
|
||||
cardEffectEvent.subTargets = curAimEvent.subTargets
|
||||
cardEffectEvent.addtionalDamage = curAimEvent.additionalDamage
|
||||
|
||||
if curAimEvent.disresponsiveList then
|
||||
for _, disresponsivePlayer in ipairs(curAimEvent.disresponsiveList) do
|
||||
if not table.contains(cardEffectEvent.disresponsiveList, disresponsivePlayer) then
|
||||
table.insert(cardEffectEvent.disresponsiveList, disresponsivePlayer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if curAimEvent.unoffsetableList then
|
||||
for _, unoffsetablePlayer in ipairs(curAimEvent.unoffsetableList) do
|
||||
if not table.contains(cardEffectEvent.unoffsetablePlayer, unoffsetablePlayer) then
|
||||
table.insert(cardEffectEvent.unoffsetablePlayer, unoffsetablePlayer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cardEffectEvent.disresponsive = curAimEvent.disresponsive
|
||||
cardEffectEvent.unoffsetable = curAimEvent.unoffsetable
|
||||
|
||||
collaboratorsIndex[toId] = collaboratorsIndex[toId] + 1
|
||||
|
||||
self:doCardEffect(table.simpleClone(cardEffectEvent))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:doCardUseEffect(cardUseEvent)
|
||||
end
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.CardUseFinished, self:getPlayerById(cardUseEvent.from), cardUseEvent)
|
||||
|
||||
::clean::
|
||||
local leftRealCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
|
||||
if #leftRealCardIds > 0 then
|
||||
self:moveCards({
|
||||
|
@ -1448,6 +1332,159 @@ function Room:useCard(cardUseEvent)
|
|||
end
|
||||
end
|
||||
|
||||
---@param cardUseEvent CardUseStruct
|
||||
function Room:doCardUseEffect(cardUseEvent)
|
||||
---@type table<string, AimStruct>
|
||||
local aimEventCollaborators = {}
|
||||
if cardUseEvent.tos and not onAim(self, cardUseEvent, aimEventCollaborators) then
|
||||
return
|
||||
end
|
||||
|
||||
local realCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
|
||||
|
||||
-- If using Equip or Delayed trick, move them to the area and return
|
||||
if cardUseEvent.card.type == Card.TypeEquip then
|
||||
if #realCardIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then
|
||||
self.moveCards({
|
||||
ids = realCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
else
|
||||
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
||||
local existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type)
|
||||
if existingEquipId then
|
||||
self:moveCards(
|
||||
{
|
||||
ids = { existingEquipId },
|
||||
from = target,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
},
|
||||
{
|
||||
ids = realCardIds,
|
||||
to = target,
|
||||
toArea = Card.PlayerEquip,
|
||||
moveReason = fk.ReasonUse,
|
||||
}
|
||||
)
|
||||
else
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
to = target,
|
||||
toArea = Card.PlayerEquip,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
elseif cardUseEvent.card.sub_type == Card.SubtypeDelayedTrick then
|
||||
if #realCardIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
||||
if not self:getPlayerById(target).dead then
|
||||
local findSameCard = false
|
||||
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do
|
||||
if Fk:getCardById(cardId).trueName == cardUseEvent.card.trueName then
|
||||
findSameCard = true
|
||||
end
|
||||
end
|
||||
|
||||
if not findSameCard then
|
||||
if cardUseEvent.card:isVirtual() then
|
||||
self:getPlayerById(target):addVirtualEquip(cardUseEvent.card)
|
||||
end
|
||||
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
to = target,
|
||||
toArea = Card.PlayerJudge,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not cardUseEvent.card.skill then
|
||||
return
|
||||
end
|
||||
|
||||
---@type CardEffectEvent
|
||||
local cardEffectEvent = {
|
||||
from = cardUseEvent.from,
|
||||
tos = cardUseEvent.tos,
|
||||
card = cardUseEvent.card,
|
||||
toCard = cardUseEvent.toCard,
|
||||
responseToEvent = cardUseEvent.responseToEvent,
|
||||
nullifiedTargets = cardUseEvent.nullifiedTargets,
|
||||
disresponsiveList = cardUseEvent.disresponsiveList,
|
||||
unoffsetableList = cardUseEvent.unoffsetableList,
|
||||
addtionalDamage = cardUseEvent.addtionalDamage,
|
||||
cardIdsResponded = cardUseEvent.nullifiedTargets,
|
||||
}
|
||||
|
||||
-- If using card to other card (like jink or nullification), simply effect and return
|
||||
if cardUseEvent.toCard ~= nil then
|
||||
self:doCardEffect(cardEffectEvent)
|
||||
return
|
||||
end
|
||||
|
||||
-- Else: do effect to all targets
|
||||
local collaboratorsIndex = {}
|
||||
for _, toId in ipairs(TargetGroup:getRealTargets(cardUseEvent.tos)) do
|
||||
if not table.contains(cardUseEvent.nullifiedTargets, toId) and self:getPlayerById(toId):isAlive() then
|
||||
if aimEventCollaborators[toId] then
|
||||
cardEffectEvent.to = toId
|
||||
collaboratorsIndex[toId] = collaboratorsIndex[toId] or 1
|
||||
local curAimEvent = aimEventCollaborators[toId][collaboratorsIndex[toId]]
|
||||
|
||||
cardEffectEvent.subTargets = curAimEvent.subTargets
|
||||
cardEffectEvent.addtionalDamage = curAimEvent.additionalDamage
|
||||
|
||||
if curAimEvent.disresponsiveList then
|
||||
for _, disresponsivePlayer in ipairs(curAimEvent.disresponsiveList) do
|
||||
if not table.contains(cardEffectEvent.disresponsiveList, disresponsivePlayer) then
|
||||
table.insert(cardEffectEvent.disresponsiveList, disresponsivePlayer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if curAimEvent.unoffsetableList then
|
||||
for _, unoffsetablePlayer in ipairs(curAimEvent.unoffsetableList) do
|
||||
if not table.contains(cardEffectEvent.unoffsetablePlayer, unoffsetablePlayer) then
|
||||
table.insert(cardEffectEvent.unoffsetablePlayer, unoffsetablePlayer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cardEffectEvent.disresponsive = curAimEvent.disresponsive
|
||||
cardEffectEvent.unoffsetable = curAimEvent.unoffsetable
|
||||
|
||||
collaboratorsIndex[toId] = collaboratorsIndex[toId] + 1
|
||||
|
||||
self:doCardEffect(table.simpleClone(cardEffectEvent))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param cardEffectEvent CardEffectEvent
|
||||
function Room:doCardEffect(cardEffectEvent)
|
||||
for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do
|
||||
|
@ -1490,12 +1527,14 @@ function Room:doCardEffect(cardEffectEvent)
|
|||
use.responseToEvent = cardEffectEvent
|
||||
self:useCard(use)
|
||||
end
|
||||
elseif cardEffectEvent.card.type == Card.TypeTrick then
|
||||
elseif cardEffectEvent.card.type == Card.TypeTrick and
|
||||
not cardEffectEvent.disresponsive then
|
||||
local players = {}
|
||||
for _, p in ipairs(self.alive_players) do
|
||||
local cards = p.player_cards[Player.Hand]
|
||||
local cards = p:getCardIds(Player.Hand)
|
||||
for _, cid in ipairs(cards) do
|
||||
if Fk:getCardById(cid).name == "nullification" then
|
||||
if Fk:getCardById(cid).name == "nullification" and
|
||||
not table.contains(cardEffectEvent.disresponsiveList or {}, p.id) then
|
||||
table.insert(players, p)
|
||||
break
|
||||
end
|
||||
|
@ -1560,9 +1599,7 @@ function Room:responseCard(cardResponseEvent)
|
|||
moveReason = fk.ReasonResonpse,
|
||||
})
|
||||
|
||||
self:setEmotion(self:getPlayerById(from), card.name)
|
||||
local soundName = (self:getPlayerById(from).gender == General.Male and "male/" or "female/") .. card.name
|
||||
self:broadcastPlaySound("./audio/card/" .. soundName)
|
||||
playCardEmotionAndSound(self, self:getPlayerById(from), card)
|
||||
|
||||
for _, event in ipairs({ fk.PreCardRespond, fk.CardResponding, fk.CardRespondFinished }) do
|
||||
self.logic:trigger(event, self:getPlayerById(cardResponseEvent.from), cardResponseEvent)
|
||||
|
@ -1599,7 +1636,11 @@ function Room:moveCards(...)
|
|||
---@type MoveInfo[]
|
||||
local infos = {}
|
||||
for _, id in ipairs(cardsMoveInfo.ids) do
|
||||
table.insert(infos, { cardId = id, fromArea = self:getCardArea(id) })
|
||||
table.insert(infos, {
|
||||
cardId = id,
|
||||
fromArea = self:getCardArea(id),
|
||||
fromSpecialName = cardsMoveInfo.from and self:getPlayerById(cardsMoveInfo.from):getPileNameOfId(id),
|
||||
})
|
||||
end
|
||||
|
||||
---@type CardsMoveStruct
|
||||
|
@ -1640,7 +1681,7 @@ function Room:moveCards(...)
|
|||
local playerAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special }
|
||||
|
||||
if table.contains(playerAreas, realFromArea) and data.from then
|
||||
self:getPlayerById(data.from):removeCards(realFromArea, { info.cardId }, data.specialName)
|
||||
self:getPlayerById(data.from):removeCards(realFromArea, { info.cardId }, info.fromSpecialName)
|
||||
elseif realFromArea ~= Card.Unknown then
|
||||
local fromAreaIds = {}
|
||||
if realFromArea == Card.Processing then
|
||||
|
@ -1743,18 +1784,12 @@ end
|
|||
---@param reason integer
|
||||
---@param skill_name string
|
||||
---@param special_name string
|
||||
function Room:moveCardTo(card, to_place, target, reason, skill_name, special_name)
|
||||
---@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 ""
|
||||
special_name = special_name or ""
|
||||
local ids = {}
|
||||
if card[1] ~= nil then
|
||||
for _, cd in ipairs(card) do
|
||||
table.insertTable(ids, self:getSubcardsByRule(card))
|
||||
end
|
||||
else
|
||||
ids = self:getSubcardsByRule(card)
|
||||
end
|
||||
local ids = Card:getIdList(card)
|
||||
|
||||
local to
|
||||
if table.contains(
|
||||
|
@ -1770,7 +1805,8 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam
|
|||
toArea = to_place,
|
||||
moveReason = reason,
|
||||
skillName = skill_name,
|
||||
specialName = special_name
|
||||
specialName = special_name,
|
||||
moveVisible = visible,
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -1933,6 +1969,7 @@ function Room:damage(damageStruct)
|
|||
if damageStruct.damage < 1 then
|
||||
return false
|
||||
end
|
||||
damageStruct.damageType = damageStruct.damageType or fk.NormalDamage
|
||||
|
||||
if damageStruct.from and not damageStruct.from:isAlive() then
|
||||
damageStruct.from = nil
|
||||
|
@ -2246,6 +2283,11 @@ function Room:throwCard(card_ids, skillName, who, thrower)
|
|||
})
|
||||
end
|
||||
|
||||
---@param pindianStruct PindianStruct
|
||||
function Room:pindian(pindianStruct)
|
||||
|
||||
end
|
||||
|
||||
-- other helpers
|
||||
|
||||
function Room:adjustSeats()
|
||||
|
@ -2289,7 +2331,30 @@ function Room:shuffleDrawPile()
|
|||
table.shuffle(self.draw_pile)
|
||||
end
|
||||
|
||||
---@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
|
||||
local equip = Fk:cloneCard(skill.attached_equip)
|
||||
local pkgPath = "./packages/" .. equip.package.extensionName
|
||||
local soundName = pkgPath .. "/audio/card/" .. equip.name
|
||||
self:broadcastPlaySound(soundName)
|
||||
self:setEmotion(player, pkgPath .. "/image/anim/" .. equip.name)
|
||||
else
|
||||
self:broadcastSkillInvoke(skill.name)
|
||||
self:notifySkillInvoked(player, skill.name)
|
||||
end
|
||||
end
|
||||
player:addSkillUseHistory(skill.name)
|
||||
if effect_cb then
|
||||
return effect_cb()
|
||||
end
|
||||
end
|
||||
|
||||
function Room:gameOver(winner)
|
||||
self.logic:trigger(fk.GameFinished, nil, winner)
|
||||
self.game_started = false
|
||||
self.game_finished = true
|
||||
|
||||
|
|
|
@ -60,13 +60,13 @@ end
|
|||
|
||||
local function _waitForReply(player, timeout)
|
||||
local result
|
||||
local start = fk.GetMicroSecond()
|
||||
local start = os.getms()
|
||||
while true do
|
||||
result = player.serverplayer:waitForReply(0)
|
||||
if result ~= "__notready" then
|
||||
return result
|
||||
end
|
||||
if timeout and (fk.GetMicroSecond() - start) / 1000 >= timeout * 1000 then
|
||||
if timeout and (os.getms() - start) / 1000 >= timeout * 1000 then
|
||||
return ""
|
||||
end
|
||||
coroutine.yield()
|
||||
|
@ -176,7 +176,9 @@ function ServerPlayer:marshal(player)
|
|||
end
|
||||
|
||||
for k, v in pairs(self.cardUsedHistory) do
|
||||
player:doNotify("AddCardUseHistory", json.encode{k, v[1]})
|
||||
if v[1] > 0 then
|
||||
player:doNotify("AddCardUseHistory", json.encode{k, v[1]})
|
||||
end
|
||||
end
|
||||
|
||||
if self.role_shown then
|
||||
|
@ -402,6 +404,15 @@ function ServerPlayer:drawCards(num, skillName, fromPlace)
|
|||
return self.room:drawCards(self, num, skillName, fromPlace)
|
||||
end
|
||||
|
||||
---@param pile_name string
|
||||
---@param card integer|Card
|
||||
---@param visible boolean
|
||||
---@param skillName string
|
||||
function ServerPlayer:addToPile(pile_name, card, visible, skillName)
|
||||
local room = self.room
|
||||
room:moveCardTo(card, Card.PlayerSpecial, self, fk.ReasonJustMove, skillName, pile_name, visible)
|
||||
end
|
||||
|
||||
function ServerPlayer:bury()
|
||||
-- self:clearFlags()
|
||||
-- self:clearHistory()
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
---@class MoveInfo
|
||||
---@field cardId integer
|
||||
---@field fromArea CardArea
|
||||
---@field fromSpecialName string|null
|
||||
|
||||
---@class CardsMoveStruct
|
||||
---@field moveInfo MoveInfo[]
|
||||
|
@ -25,7 +26,6 @@
|
|||
---@field moveVisible boolean|null
|
||||
---@field specialName string|null
|
||||
---@field specialVisible boolean|null
|
||||
---@field fromSpecialName string|null
|
||||
|
||||
---@class HpChangedData
|
||||
---@field num integer
|
||||
|
@ -99,7 +99,7 @@ fk.FireDamage = 3
|
|||
---@field tos TargetGroup
|
||||
---@field card Card
|
||||
---@field toCard Card|null
|
||||
---@field responseToEvent CardEffectStruct|null
|
||||
---@field responseToEvent CardEffectEvent|null
|
||||
---@field nullifiedTargets interger[]|null
|
||||
---@field extraUse boolean|null
|
||||
---@field disresponsiveList integer[]|null
|
||||
|
@ -142,6 +142,16 @@ fk.ReasonExchange = 8
|
|||
fk.ReasonUse = 9
|
||||
fk.ReasonResonpse = 10
|
||||
|
||||
---@class PindianStruct
|
||||
---@field from ServerPlayer
|
||||
---@field to ServerPlayer
|
||||
---@field from_card Card
|
||||
---@field to_card Card
|
||||
---@field from_number integer
|
||||
---@field to_number integer
|
||||
---@field reason string
|
||||
---@field winner ServerPlayer|null
|
||||
|
||||
---@class LogMessage
|
||||
---@field type string
|
||||
---@field from integer
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user