├── .nojekyll
├── appendix
├── Images
│ ├── enery1.png
│ ├── enery2.png
│ ├── enery3.png
│ ├── enery4.png
│ ├── enery5.png
│ ├── example1.png
│ ├── example2.png
│ ├── example3.png
│ ├── example4.png
│ └── example5.png
├── share-skill.md
├── enery-skill.md
└── api.md
├── _sidebar.md
├── README.md
├── skin.md
├── online.md
├── index.html
├── animation.md
├── character.md
├── audio.md
├── structure.md
├── code-standard.md
├── basic.md
├── card.md
├── condition.md
├── effect.md
├── mark.md
├── skill.md
├── ai.md
└── trigger.md
/.nojekyll:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/appendix/Images/enery1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/enery1.png
--------------------------------------------------------------------------------
/appendix/Images/enery2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/enery2.png
--------------------------------------------------------------------------------
/appendix/Images/enery3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/enery3.png
--------------------------------------------------------------------------------
/appendix/Images/enery4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/enery4.png
--------------------------------------------------------------------------------
/appendix/Images/enery5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/enery5.png
--------------------------------------------------------------------------------
/appendix/Images/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/example1.png
--------------------------------------------------------------------------------
/appendix/Images/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/example2.png
--------------------------------------------------------------------------------
/appendix/Images/example3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/example3.png
--------------------------------------------------------------------------------
/appendix/Images/example4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/example4.png
--------------------------------------------------------------------------------
/appendix/Images/example5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antarctics/noname/HEAD/appendix/Images/example5.png
--------------------------------------------------------------------------------
/_sidebar.md:
--------------------------------------------------------------------------------
1 | - [第一章:基础知识 | 🟩 Easy](basic.md)
2 | - [第二章:扩展结构 | 🟩 Easy](structure.md)
3 | - [第三章:角色制作 | 🟩 Easy](character.md)
4 | - 第四章:技能开发 | 🟨 Medium
5 | - [4.1 技能系统](skill.md)
6 | - [4.2 触发时机](trigger.md)
7 | - [4.3 技能效果](effect.md)
8 | - [4.4 技能标记](mark.md)
9 | - [4.5 技能条件](condition.md)
10 | - [4.6 技能动画](animation.md)
11 | - [4.7 技能类型概述](skill-types.md)
12 |
13 | - [第五章:卡牌开发 | 🟩 Easy](card.md)
14 | - 第六章:进阶开发 | 🟥 Hard
15 | - [6.1 AI设计](ai.md)
16 | - [6.2 联机适配](online.md)
17 | - [6.3 配音系统](audio.md)
18 | - [6.4 皮肤系统](skin.md)
19 | - [6.5 代码规范](code-standard.md)
20 | - 附录
21 | - [A. API参考](appendix/api.md)
22 | - [B. 共享手牌技能示例](appendix/share-skill.md)
23 | - [C. 充能条技能示例](appendix/enery-skill.md)
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 无名杀扩展开发教程
2 |
3 | 欢迎来到无名杀扩展开发教程!本教程旨在帮助你学习如何制作无名杀扩展。无论你是完全的编程新手,还是有一定编程基础的开发者,都可以通过本教程学习制作扩展。
4 |
5 | ## 教程勘误、答疑解惑、新增内容等要求请于评论区进行说明!
6 | - 各个章节下滑到底部即可加载对应章节的评论区!
7 | - 若未加载,可刷新页面重试。
8 | - 也可直接访问 [Github评论区](https://github.com/Antarctics/noname/discussions)
9 |
10 | ## 目录
11 |
12 | ### [第一章:基础知识 | 🟩 Easy](basic.md)
13 | - 开发环境准备
14 | - JavaScript基础
15 |
16 | ### [第二章:扩展结构 | 🟩 Easy](structure.md)
17 | - 无名杀扩展结构
18 | - 无名杀代码风格
19 |
20 | ### [第三章:角色制作 | 🟩 Easy](character.md)
21 | - 角色系统格式
22 | - 参数实例演示
23 |
24 | ### 第四章:技能开发 | 🟨 Medium
25 | - [4.1 技能系统](skill.md)
26 | - [4.2 触发时机](trigger.md)
27 | - [4.3 技能效果](effect.md)
28 | - [4.4 技能标记](mark.md)
29 | - [4.5 技能条件](condition.md)
30 | - [4.6 技能动画](animation.md)
31 | - [4.7 技能类型概述](skill-types.md)
32 |
33 | ### [第五章:卡牌开发 | 🟩 Easy](card.md)
34 | - 卡牌类型
35 | - 卡牌效果
36 | - 卡牌动画
37 | - 卡牌音效
38 |
39 | ### 第六章:进阶开发 | 🟥 Hard
40 | - [6.1 AI设计](ai.md)
41 | - [6.2 联机适配](online.md)
42 | - [6.3 配音系统](audio.md)
43 | - [6.4 皮肤系统](skin.md)
44 | - [6.5 代码规范](code-standard.md)
45 |
46 | ### 附录
47 | - [A. API参考](appendix/api.md)
48 | - [B. 共享手牌技能示例](appendix/share-skill.md)
49 | - [C. 充能条技能示例](appendix/enery-skill.md)
50 |
51 | ### [无名杀GitHub仓库](https://github.com/libnoname/noname)
52 |
53 | ## 如何使用本教程
54 |
55 | 1. 如果你是编程新手,建议从第一章开始,按顺序学习
56 | 2. 如果你有编程基础,可以直接从第二章开始,了解无名杀的特有概念
57 | 3. 每章末尾都有练习题和实战项目,建议动手完成以加深理解
58 | 4. 遇到问题可以查看附录中的常见问题解答
59 | 5. API参考部分详细列出了可用的函数和接口,可以随时查阅
60 |
61 | ## 特别说明
62 |
63 | 1. 本教程主要采用异步函数作为代码示例
64 | 2. 示例代码来自游戏源码或官方扩展,经过实际验证
65 | 3. 教程会持续更新,欢迎提出建议和意见
66 | 4. 更多扩展实例可以参考游戏自带扩展和其他玩家的作品
67 | 5. 本教程不代表权威相关,一切以官方源码为准!
68 |
69 | 让我们开始无名杀扩展开发的学习之旅吧!
70 |
--------------------------------------------------------------------------------
/skin.md:
--------------------------------------------------------------------------------
1 | # 5.4 角色皮肤
2 |
3 | ## 1. 皮肤系统概述
4 |
5 | 无名杀的皮肤系统允许为武将设置多个不同的皮肤,每个皮肤可以包含:
6 | - 皮肤图片
7 | - 专属配音
8 | - 死亡皮肤
9 | - 皮肤描述
10 |
11 | ## 2. 基础皮肤设置
12 |
13 | ### 2.1 皮肤定义
14 | ```javascript
15 | // 以此法添加的皮肤只能使用代码更换。
16 |
17 | // 定义武将皮肤
18 | characterSubstitutes: {
19 | "my_general": [
20 | ["my_general2", ["ext:我的扩展/image/my_general2.png"]], // 皮肤2
21 | ["my_general3", ["ext:我的扩展/image/my_general3.png"]], // 皮肤3
22 | ],
23 | };
24 | ```
25 |
26 | ### 2.2 皮肤文件结构
27 | ```
28 | extension/
29 | └── 扩展名/
30 | └── image/
31 | ├── my_general.png # 默认皮肤
32 | ├── my_general2.png # 皮肤2
33 | └── my_general3.png # 皮肤3
34 | ```
35 |
36 | ### 2.3 本体换肤方法
37 | - 于本体image文件夹中新建skin文件夹。
38 | - 于skin中新建对应角色ID的文件夹
39 | - 与角色ID文件夹中按照顺序以数字命名
40 |
41 | #### 皮肤文件结构
42 | ```
43 | image/
44 | └── skin/
45 | └── 角色名/
46 | ├── l.jpg # 皮肤1
47 | ├── 2.jpg # 皮肤2
48 | └── 3.jpg # 皮肤3
49 | ```
50 | ## 3. 皮肤配音
51 |
52 | ### 3.1 皮肤专属配音
53 | ```javascript
54 | skill: {
55 | "my_skill": {
56 | audio: "ext:我的扩展/audio/skill/my_skill",
57 | // 皮肤专属配音重定向
58 | audioname2: {
59 | "my_general2": "ext:我的扩展/audio/skill/my_skill2",
60 | "my_general3": "ext:我的扩展/audio/skill/my_skill3",
61 | },
62 | content(){
63 | // 技能内容
64 | }
65 | }
66 | }
67 | ```
68 |
69 | ### 3.2 推荐音频文件结构
70 | ```
71 | extension/
72 | └── 扩展名/
73 | └── audio/
74 | └── skill/
75 | ├── my_skill.mp3 # 默认配音
76 | ├── my_skill2.mp3 # 皮肤2配音
77 | └── my_skill3.mp3 # 皮肤3配音
78 | ```
79 |
80 |
81 | 下一章我们学习代码规范
--------------------------------------------------------------------------------
/online.md:
--------------------------------------------------------------------------------
1 | # 6.2 联机适配
2 |
3 | ## 1. 联机系统概述
4 |
5 | 无名杀的联机系统需要考虑:
6 | - 数据同步
7 | - 事件处理
8 | - 资源加载
9 | - 性能优化
10 | - 错误处理
11 |
12 | ## 2. 数据同步
13 |
14 | ```javascript
15 | "sync_skill": {
16 | async content(event, trigger, player){
17 | // 同步玩家数据(联机模式下全局调用的数据均需同步)
18 | game.broadcastAll(function(player, num){
19 | player.storage.count = num;
20 | }, player, 1);
21 |
22 | // 同步动画效果
23 | game.broadcastAll(function(player){
24 | player.$draw();
25 | }, player);
26 |
27 | // 延迟等待动画
28 | game.delayx();
29 | }
30 | }
31 | ```
32 |
33 |
34 | ## 3. 事件处理
35 |
36 | ### 3.1 事件同步
37 | ```javascript
38 | "event_skill": {
39 | trigger: {player: 'phaseBegin'},
40 | async content(event, trigger, player){
41 | // 创建同步事件
42 | var next = game.createEvent('customEvent');
43 | next.player = player;
44 | next.setContent(function(){
45 | // 事件内容
46 | player.draw();
47 | });
48 |
49 | // 等待事件完成
50 | await next;
51 | }
52 | }
53 | ```
54 |
55 | ### 3.2 选择同步
56 | ```javascript
57 | "choice_skill": {
58 | async content(event, trigger, player){
59 | // 同步选择结果
60 | let result = await player.chooseControl('选项1', '选项2')
61 | .set('prompt', '请选择一个选项')
62 | .set('ai', ()=>{
63 | return '选项1';
64 | })
65 | .forResult();
66 |
67 | // 广播选择结果
68 | game.broadcastAll(function(player, choice){
69 | player.storage.choice = choice;
70 | }, player, result.control);
71 | }
72 | }
73 | ```
74 |
75 | ## 练习
76 |
77 | 1. 创建一个基础联机技能:
78 | - 实现数据同步
79 |
80 |
81 | 参考答案 | 🟩 Easy
82 |
83 | ```javascript
84 | "online_skill": {
85 | enable: "phaseUse",
86 | usable: 1,
87 | filter(event, player){
88 | return player.countCards('h') > 0;
89 | },
90 | async content(event, trigger, player){
91 | // 选择目标
92 | let result = await player.chooseTarget('选择一名角色')
93 | .set('ai', target => get.attitude(player, target))
94 | .forResult();
95 |
96 | if(result.bool){
97 | let target = result.targets[0];
98 |
99 | // 同步选择结果
100 | game.broadcastAll(function(player, target){
101 | player.line(target);
102 | }, player, target);
103 |
104 | // 处理效果
105 | await target.draw();
106 |
107 | // 同步状态
108 | game.broadcast(function(player, target){
109 | player.storage.used = true;
110 | target.update();
111 | }, player, target);
112 | }
113 | }
114 | }
115 | ```
116 |
117 |
118 |
119 | 下一节我们将学习性能优化。
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 正在加载中...
14 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/animation.md:
--------------------------------------------------------------------------------
1 | # 4.6 技能动画
2 |
3 | ## 1. 动画系统概述
4 |
5 | 无名杀的动画系统仅有动效,而无实际效果,总计包括:
6 | - 技能发动动画
7 | - 特效动画
8 | - 卡牌动画
9 | - 状态动画
10 |
11 | ## 2. 技能发动动画
12 |
13 | ### 2.1 基础动画
14 | ```javascript
15 | "animation_skill": {
16 | // 技能动画
17 | skillAnimation: true, // 开启技能动画
18 | animationStr: "技能发动", // 动画文字
19 | animationColor: "fire", // 动画颜色(fire/thunder/water/metal/soil)
20 |
21 | content(){
22 | // 技能效果
23 | }
24 | }
25 | ```
26 |
27 | ### 2.2 自定义动画
28 | ```javascript
29 | "custom_animation": {
30 | async content(event, trigger, player){
31 | // 播放指定技能动画
32 | player.$skill('技能名');
33 |
34 | // 自定义动画效果
35 | player.$fire(); // 火焰效果
36 | player.$thunder(); // 雷电效果
37 | player.$fullscreenpop('文字', 'fire'); // 全屏特效
38 | }
39 | }
40 | ```
41 |
42 | ## 3. 特效动画
43 |
44 | ### 3.1 基础特效
45 | ```javascript
46 | async content(event, trigger, player){
47 | // 基础特效
48 | player.$gain(cards); // 获得牌动画
49 | player.$give(num, target); // 给牌动画
50 | player.$throw(cards); // 弃牌动画
51 |
52 | // 状态特效
53 | player.$damage('fire'); // 受伤特效
54 | player.$recover(); // 回复特效
55 | player.$shield(); // 护盾特效
56 | }
57 | ```
58 |
59 | ### 3.2 高级特效
60 | ```javascript
61 | "effect_skill": {
62 | async content(event, trigger, player){
63 | // 连续特效
64 | player.$fire();
65 | await game.delay(0.5);
66 | player.$thunder();
67 |
68 | // 多目标特效
69 | for(let target of targets){
70 | target.$damage('fire');
71 | await game.delay(0.2);
72 | }
73 | }
74 | }
75 | ```
76 |
77 | ## 4. 卡牌动画
78 |
79 | ### 4.1 使用动画
80 | ```javascript
81 | "card_animation": {
82 | async content(event, trigger, player){
83 | // 卡牌使用动画
84 | player.$useCard(card, targets);
85 |
86 | // 卡牌打出动画
87 | player.$throw(card);
88 |
89 | // 卡牌获得动画
90 | player.$draw(num);
91 | player.$gain(cards);
92 | }
93 | }
94 | ```
95 |
96 | ### 4.2 特殊动画
97 | ```javascript
98 | "special_card": {
99 | async content(event, trigger, player){
100 | // 判定动画
101 | player.$judge(card);
102 |
103 | // 展示动画
104 | player.$showCards(cards);
105 |
106 | // 比较动画
107 | player.$compare(card1, target, card2);
108 | }
109 | }
110 | ```
111 |
112 | ## 5. 状态动画
113 |
114 | ### 5.1 基础状态
115 | ```javascript
116 | "state_animation": {
117 | async content(event, trigger, player){
118 | // 翻面动画
119 | player.$turnOver();
120 |
121 | // 横置动画
122 | player.$link();
123 |
124 | // 濒死动画
125 | player.$die();
126 | }
127 | }
128 | ```
129 |
130 | ### 5.2 标记动画
131 | ```javascript
132 | "mark_animation": {
133 | async content(event, trigger, player){
134 | // 添加标记动画
135 | player.$mark('标记名', {
136 | name: '标记名称',
137 | content: '标记描述'
138 | });
139 |
140 | // 移除标记动画
141 | player.$removeMark('标记名');
142 | }
143 | }
144 | ```
145 |
146 | ## 6. 进阶技巧
147 |
148 | ### 6.1 动画序列
149 | ```javascript
150 | "sequence_animation": {
151 | async content(event, trigger, player){
152 | // 创建动画序列
153 | await player.$fire();
154 | await game.delay(0.5);
155 | await player.$thunder();
156 | await game.delay(0.3);
157 | await player.$fullscreenpop('技能发动', 'fire');
158 | }
159 | }
160 | ```
161 |
162 | ### 6.2 条件动画
163 | ```javascript
164 | "condition_animation": {
165 | async content(event, trigger, player){
166 | // 根据条件播放不同动画
167 | if(player.hp < 3){
168 | await player.$fire();
169 | } else {
170 | await player.$thunder();
171 | }
172 |
173 | // 动态特效
174 | let type = player.hp < 3 ? 'fire' : 'thunder';
175 | await player.$damage(type);
176 | }
177 | }
178 | ```
179 |
180 |
181 | 下一章我们将以无名杀源码实例展示所有技能类型。
182 |
183 | 若无需回顾技能类型,可以直接学习
184 | - [第四章:卡牌开发 | 🟩 Easy](../chapter4-card.md)
185 |
--------------------------------------------------------------------------------
/character.md:
--------------------------------------------------------------------------------
1 | # 第三章 角色制作
2 |
3 | ## 1. 角色定义格式
4 |
5 | 在无名杀中,角色的基本定义格式如下:
6 | ### 对象形式(推荐)
7 | - 本教程将以对象形式为主!
8 |
9 | ```javascript
10 | character: {
11 | id: {
12 | sex: "male",
13 | group: "qun",
14 | hp: 3,
15 | skills: ["skill1", "skill2"],
16 | doubleGroup: ["wei", "qun"],
17 | },
18 | },
19 | translate: {
20 | "id": "武将名称",
21 | }
22 | ```
23 |
24 | ### 数组形式(传统)
25 | - 不再推荐使用数组形式
26 | ```javascript
27 | character: {
28 | "id": ["male", "shu", 4/4, ["skill1", "skill2"], [
29 | "des:武将描述",
30 | "ext:my_extension/武将图片.jpg",
31 | "die:ext:my_extension/audio/die/die_audio.mp3"
32 | ]],
33 | },
34 | translate: {
35 | "id": "武将名称",
36 | }
37 | ```
38 |
39 |
40 | ### 1.1 参数说明
41 |
42 | 1. **id**: 角色的唯一标识符
43 | - 建议使用英文字母、数字、下划线
44 | - 建议使用有意义的命名,如 `zhaoyun`、`sp_zhugeliang`
45 | - 游戏内角色首字母会读取`_`后的首个字母
46 | - 不能与现有角色ID重复
47 |
48 | 2. **sex**: 性别
49 | - 字符串:
50 | - `"male"`: 男性
51 | - `"female"`: 女性
52 | - `"none"`: 无性别
53 |
54 | 3. **group**: 势力
55 | - 字符串:
56 | - `"wei"`: 魏国
57 | - `"shu"`: 蜀国
58 | - `"wu"`: 吴国
59 | - `"qun"`: 群雄
60 | - `"jin"`: 晋国
61 | - `"shen"`: 神将
62 | - 也可以[自定义势力](#自定义势力),需要额外设置
63 |
64 | 4. **hp**: 体力值
65 | - 数字,角色的初始体力
66 |
67 | 5. **maxHp**: 体力上限
68 | - 数字,角色的初始体力上限
69 |
70 | 6. **hujia**: 护甲
71 | - 数字,角色的初始护甲
72 |
73 | 7. **skills**: 技能列表
74 | - 数组,使用技能ID
75 |
76 | 8. **isZhugong**: 常驻主
77 | - 布尔值,默认为false
78 |
79 | 9. **dieAudios**: 阵亡配音
80 | - 字符串、文本、布尔值、数组,具体用法请查看[配音系统](audio.md)。
81 |
82 | 10. **tags**: 特殊标签列表
83 | - `names`:字符串。武将姓名,无法替代翻译,仅用于判断姓、名分别是什么
84 | - `夏侯|null`:夏侯氏
85 | - `司马|懿`:司马懿
86 | - `关|兴-张|苞`:关兴和张苞,双头将
87 | - `groupBorder`:字符串。边框色,可以实现身在曹营心在汉(势力`wei`,边框色`shu`)
88 | - `groupInGuozhan`:字符串。神牌在国战模式下的势力
89 | - `isUnseen `:布尔值。是否隐藏武将,默认为false
90 | - `hasHiddenSkil`:布尔值。是否隐匿技能,默认为false
91 | - `dualSideCharacter`:字符串。双面武将牌,即翻面后切换至另一武将,需双方武将均持有`dualside`技能
92 | - `doubleGroup`:字符串数组。多势力武将。
93 | - `isAiForbidden`:布尔值。是否仅点将可用,默认为false
94 | - `extraModeData`:数组。特殊模式下读取的信息。
95 | - `clans`:字符串数组。对应的所有宗族
96 | - `img`:字符串。武将对应的图片
97 | - `initFilters`:字符串数组。武将无法享受的红利(地主、主公加成)
98 | - `tempname`:字符串数组。武将的临时名称
99 | - 更多信息请查看`noname\library\element\character.js`
100 |
101 | ## 2. 自定义势力
102 | ```javascript
103 | // 在扩展的precontent中添加
104 | lib.group.push('my_group'); // 添加势力
105 | lib.translate.my_group = '自定义'; // 势力翻译
106 | lib.translate.my_groupColor="#FFFF00", // 文字颜色(疑似失效)
107 | lib.groupnature.my_group = 'metal'; // 描边颜色
108 |
109 | /** 推荐方法
110 | * @param {string} id: 势力ID
111 | * @param {string} short: 势力名称,单字
112 | * @param {string} name: 势力全名,使用 get.translation(id2)可以获取,不填默认为short
113 | * @param {object} config:势力配置,支持color与image两种参数。
114 | *
115 | */
116 | game.addGroup(id,short,name,config)
117 |
118 | // 标准势力的描边颜色对应
119 | // 神: shen - 金色
120 | // 魏: water - 蓝色
121 | // 蜀: soil - 黄色
122 | // 吴: wood - 绿色
123 | // 群: qun - 白色
124 | // 晋: thunder - 紫色
125 | // 键: key - 紫色
126 | ```
127 |
128 | ## 4. 角色前缀
129 | ```javascript
130 | return {
131 | translates: {
132 | sheXXX: "蛇年XXX",
133 | sheXXX_prefix: "蛇年" // 蛇年作为前缀,角色名为 XXX ,可参考 界XX、神XX、手杀XXX等
134 | };
135 |
136 | // 修改前缀显示样式
137 | // precontent中填写,支持color,nature,showName,getSpan
138 | lib.namePrefix.set("蛇年",{showName: "🐍"})
139 | // 或者
140 | lib.namePrefix.set("蛇年",{getSpan: () => {
141 | const span = document.createElement("span");
142 | span.style.fontFamily= "NonameSuits";
143 | span.textContent= "🐍";
144 | return span.outerHTML
145 | }})
146 | }
147 | ```
148 |
149 | ## 练习题
150 |
151 | 1. 创建一个基本武将:
152 | - 设置基本属性
153 | - 添加技能`wusheng`、`longdan`
154 | - 设置合适的描述
155 |
156 |
157 | 参考答案 | 🟩 Easy
158 |
159 | ```javascript
160 | // 在扩展中添加武将
161 | character: {
162 | character: {
163 | "ex_guanyu": {
164 | sex: "male",
165 | group: "shu",
166 | hp: 4,
167 | skills: ["wusheng", "longdan"]
168 | img: "extension/新关羽/ex_guanyu.png"
169 | dieAudios: "ext:新关羽/audio/die/ex_guanyu.mp3"
170 | }
171 | },
172 | translate: {
173 | "ex_guanyu": "新关羽", // 武将翻译
174 | "ex_guanyu_prefix": "新"
175 | }
176 | }
177 | ```
178 |
179 |
180 | 下一节我们将学习如何设计和实现技能。
181 |
--------------------------------------------------------------------------------
/audio.md:
--------------------------------------------------------------------------------
1 | # 5.3 音频与配音
2 |
3 | ## 1. 音频系统概述
4 |
5 | 无名杀的音频系统主要包含以下几个部分:
6 | - 技能配音
7 | - 死亡配音
8 | - 皮肤专属配音
9 | - 卡牌音效
10 |
11 | ## 2. 技能配音
12 |
13 | ### 2.1 基础配音设置
14 | ```javascript
15 | skill: {
16 | "my_skill": {
17 | audio: 2, // 有2个配音文件("skill1.mp3", "skill2.mp3")
18 | // 或
19 | audio: "ext:扩展名/audio/skill:2", // 从扩展目录读取以数字后缀命名的音频("ext:扩展名/audio/skill/my_skill1.mp3", "ext:扩展名/audio/skill/my_skill2.mp3")
20 | // 或
21 | audio: true, // 只有1个配音文件("skill.mp3")
22 | // 或
23 | audio: ["dclingxi",2] // 使用技能【灵犀】的两条语音
24 | // 技能内容...
25 | }
26 | }
27 | ```
28 |
29 | ### 2.2 配音文件命名规则
30 | - 基础命名:`技能ID + 数字`
31 | - 示例:
32 | - `my_skill1.mp3`
33 | - `my_skill2.mp3`
34 |
35 | ### 2.3 角色专属配音
36 | ```javascript
37 | "my_skill": {
38 | audio: 2,
39 |
40 | audioname: ["zhaoyun", "machao"], // 赵云和马超使用专属配音
41 | // 或
42 | audioname2: {
43 | zhaoyun: "ext:扩展名/audio/skill:1", // 赵云使用扩展配音
44 | machao: true // 马超使用默认配音
45 | }
46 | }
47 | ```
48 |
49 | ## 3. 死亡配音
50 |
51 | ```javascript
52 | character: {
53 | id: {
54 | sex: "male",
55 | group: "qun",
56 | hp: 3,
57 | skills: ["skill1", "skill2"],
58 | doubleGroup: ["wei", "qun"],
59 | dieAudios: ["ext:扩展名/audio/die/my_general1.mp3","ext:扩展名/audio/die/my_general2.mp3"]
60 | },
61 | },
62 | ```
63 |
64 | ## 4. 配音台词
65 |
66 | ### 4.1 技能台词
67 | ```javascript
68 | // 在translate中添加
69 | translate: {
70 | "my_skill": "技能名",
71 | "my_skill_info": "技能描述",
72 | "#my_skill1": "发动台词1",
73 | "#my_skill2": "发动台词2",
74 | "#ext:扩展名/audio/skill/my_skill1": "发动台词1", // 使用扩展目录配音
75 | "#ext:扩展名/audio/skill/my_skill2": "发动台词2" // 使用扩展目录配音
76 | }
77 | ```
78 |
79 | ### 4.2 死亡台词
80 | ```javascript
81 | translate: {
82 | "my_general:die": "死亡台词",
83 | "#ext:acg/audio/die/my_general:die": "死亡台词" // 使用扩展目录配音
84 | }
85 | ```
86 |
87 | ## 5. 推荐目录结构
88 | ```
89 | extension/
90 | └── 扩展名/
91 | └── audio/
92 | ├── skill/ # 技能配音
93 | │ ├── my_skill1.mp3
94 | │ └── my_skill2.mp3
95 | ├── die/ # 死亡配音
96 | │ └── my_general.mp3
97 | └── skin/ # 皮肤配音
98 | └── my_general/
99 | └── skin_audio.mp3
100 | ```
101 |
102 | ## 6. 进阶技巧
103 |
104 | ### 6.1 条件配音
105 | ```javascript
106 | "my_skill": {
107 | audio(player){
108 | // 根据条件返回不同的配音设置
109 | if(player.hp <= 2) return "ext:扩展名:2";
110 | return true;
111 | }
112 | }
113 | ```
114 |
115 | ### 6.2 动态配音
116 | ```javascript
117 | "my_skill": {
118 | audio: "ext:扩展名:2",
119 | content(){
120 | // 手动播放配音
121 | game.playAudio('..', 'extension', '扩展名', 'my_skill' + (_status.event.num || 1));
122 | }
123 | }
124 | ```
125 |
126 | ## 练习
127 |
128 | 1. 为技能添加配音:
129 | - 添加基础配音文件
130 | - 设置触发台词
131 | - 测试不同角色专属配音
132 |
133 |
134 | 参考答案 | 🟩 Easy
135 |
136 | ```javascript
137 | // 在扩展中添加技能配音
138 | skill: {
139 | "my_skill": {
140 | // 基础配音
141 | audio: "ext:我的扩展/audio:2", // 有两个配音文件
142 |
143 | // 角色专属配音
144 | audioname: ["zhaoyun", "machao"], // 赵云和马超使用专属配音
145 | audioname2: {
146 | zhaoyun: "ext:我的扩展/audio/zhaoyun/skill:2", // 赵云专属配音
147 | machao: "ext:我的扩展/audio/machao/skill:2" // 马超专属配音
148 | },
149 |
150 | content(){
151 | // 技能内容
152 | }
153 | }
154 | },
155 | translate: {
156 | "my_skill": "技能名",
157 | "my_skill_info": "技能描述",
158 | "#my_skill1": "发动台词1",
159 | "#my_skill2": "发动台词2",
160 | "#ext:我的扩展/audio/zhaoyun/skill1": "赵云专属台词1",
161 | "#ext:我的扩展/audio/zhaoyun/skill2": "赵云专属台词2",
162 | "#ext:我的扩展/audio/machao/skill1": "马超专属台词1",
163 | "#ext:我的扩展/audio/machao/skill2": "马超专属台词2"
164 | }
165 |
166 | // 目录结构
167 | extension/
168 | └── 我的扩展/
169 | └── audio/
170 | ├── skill/
171 | │ ├── my_skill1.mp3 // 基础配音1
172 | │ └── my_skill2.mp3 // 基础配音2
173 | ├── zhaoyun/
174 | │ ├── skill1.mp3 // 赵云专属配音1
175 | │ └── skill2.mp3 // 赵云专属配音2
176 | └── machao/
177 | ├── skill1.mp3 // 马超专属配音1
178 | └── skill2.mp3 // 马超专属配音2
179 | ```
180 |
181 |
182 | 下一节我们将学习如何修改武将皮肤。
--------------------------------------------------------------------------------
/structure.md:
--------------------------------------------------------------------------------
1 | # 第二章:扩展结构
2 |
3 | ## 1. 扩展文件
4 | 无名杀的扩展文件位于游戏本体目录的`extension`文件夹中。
5 | 通常以以下形式存在:
6 | ```
7 | extension/
8 | └── 扩展名/
9 | ├── extension.js # 扩展主文件
10 | ├── info.json # 扩展信息
11 | ├── LICENSE # 许可证
12 | └── ... # 其他扩展文件
13 | ```
14 | ## 2. 扩展文件结构
15 |
16 | ### **extension.js**
17 | - 扩展主文件
18 | - 基础格式如下:
19 | ```javascript
20 | import { lib, game, ui, get, ai, _status } from "../../noname.js"; // 从无名杀中导入对象
21 | export const type = "extension"; // 声明此文件为扩展文件
22 | export default function () {
23 | return {
24 | // 扩展名称,需要与文件夹名称相同
25 | name: "扩展名称",
26 | // 执行顺序:precontent、prepare、content、arenaReady
27 | // 游戏界面创建之后
28 | arenaReady () {},
29 | /* 游戏数据加载后、界面加载前
30 | * config为本扩展选项、pack为本扩展包
31 | */
32 | content (config, pack) {},
33 | // 所有扩展加载后
34 | prepare () {},
35 | // 游戏数据加载前
36 | precontent () {},
37 | // 扩展选项
38 | config: {},
39 | // 扩展帮助
40 | help: {},
41 | // 扩展包
42 | package: {
43 | // 武将包
44 | character: {
45 | // 武将列表
46 | character: {},
47 | // 翻译
48 | translate: {},
49 | /*
50 | // 武将筛选
51 | characterFilter: { },
52 | // 武将描述
53 | characterIntro: { },
54 | // 武将换皮换音
55 | characterSubstitute: { },
56 | // 武将头衔
57 | characterTitle: { },
58 | // 武将切换(如新杀界徐盛、手杀界徐盛可以互相切换)
59 | characterReplace: { },
60 | // 武将分包
61 | characterSort: { },
62 | // 动态翻译
63 | dynamicTranslate: { },
64 | // 是否支持联机
65 | connect: false,
66 | */
67 | },
68 | // 卡牌包
69 | card: {
70 | // 卡牌列表
71 | card: {},
72 | translate: {},
73 | // 牌堆列表
74 | list: [],
75 | /*
76 | 卡牌类型
77 | cardType: {},
78 | // 是否支持联机
79 | connect: false,
80 | */
81 | },
82 | // 技能包
83 | skill: {
84 | // 技能列表
85 | skill: {},
86 | translate: {},
87 | /*
88 | // 动态翻译
89 | dynamicTranslate: { },
90 | // 是否支持联机
91 | connect: false,
92 | */
93 | },
94 | // 扩展描述
95 | intro: "",
96 | // 作者名称
97 | author: "无名玩家",
98 | diskURL: "",
99 | forumURL: "",
100 | // 版本号
101 | version: "1.0",
102 | },
103 | files: { "character": [], "card": [], "skill": [], "audio": [] },
104 | connect: false // 是否支持联机
105 | }
106 | };
107 | ```
108 | ### **info.json**
109 | - 用于显示扩展信息
110 | ```
111 | {"name":"扩展名","author":"作者名","diskURL":"","forumURL":"","version":"1.0"}
112 | ```
113 |
114 | ## 2. 新建扩展
115 |
116 | ### 方法一
117 | - 游戏内依次点击 `选项 -> 扩展 -> 制作扩展 -> 输入扩展名 -> 确定 -> 保存` 即可新建扩展
118 | ### 方法二
119 | 创建你的开发目录:
120 | ```
121 | ├── extension.js # 扩展主文件
122 | ├── info.json # 扩展信息
123 | ├── character.js # 角色文件(可选)
124 | ├── card.js # 卡牌文件(可选)
125 | ├── skill.js # 技能文件(可选)
126 | ├── image/ # 图片文件夹(可选)
127 | └── audio/ # 音频文件夹(可选)
128 | ```
129 | 将上述文件压缩为zip格式,随后游戏内依次点击 `选项 -> 扩展 -> 获取扩展 -> 导入扩展 -> 选择压缩包 -> 确定` 即可导入扩展
130 |
131 | ## 3. 无名杀代码风格
132 | **对于现版本的无名杀,更推荐使用Async Content格式进行异步操作!**
133 | ### 3.1 Async方法(推荐)
134 | 无名杀在v1.10.6版本后引入了async/await异步写法,这是推荐的代码风格:
135 |
136 | ```javascript
137 | async content(event, trigger, player) {
138 | // 弃置一张手牌
139 | let result = await player
140 | .chooseToDiscard(1, 'h', true)
141 | .forResult();
142 |
143 | // 未弃牌则中断
144 | if (!result.bool) return;
145 |
146 | // 选择一名其他角色
147 | let targets = await player
148 | .chooseTarget('请选择一名角色', true)
149 | .forResultTargets();
150 |
151 | if(targets && targets.length){
152 | // 对目标造成1点火焰伤害
153 | await targets[0].damage('fire');
154 | // 目标摸一张牌
155 | await targets[0].draw();
156 | }
157 | };
158 | ```
159 |
160 | 特点:
161 | - 使用async/await处理异步
162 | - 代码结构清晰直观
163 |
164 | ### 3.2 Step方法(传统)
165 | 早期无名杀使用step标记来处理异步流程:
166 |
167 | ```javascript
168 | content(){
169 | "step 0"
170 | player.chooseToDiscard(1, 'h', true); // 弃置一张手牌
171 |
172 | "step 1"
173 | if(!result.bool){
174 | event.finish(); // 未弃牌则结束事件
175 | return;
176 | }
177 |
178 | "step 2"
179 | player.chooseTarget('请选择一名角色', true); // 选择一名角色
180 |
181 | "step 3"
182 | if(result.bool){
183 | result.targets[0].damage('fire'); // 对目标造成1点火焰伤害
184 | result.targets[0].draw(); // 目标摸一张牌
185 | }
186 | };
187 | ```
188 |
189 | 特点:
190 | - 使用step标记分步执行
191 | - 通过result传递结果
192 |
193 | ## 练习题
194 |
195 | 1. 将以下Step方法改写为Async方法:
196 | ```javascript
197 | content(){
198 | "step 0"
199 | player.draw(2);
200 | "step 1"
201 | if(player.hp < 3){
202 | player.chooseToDiscard(1, true);
203 | }
204 | }
205 | ```
206 |
207 |
208 | 参考答案 | 🟩 Easy
209 |
210 | ```javascript
211 | async content(event, trigger, player){
212 | // 摸两张牌
213 | await player.draw(2);
214 |
215 | // 体力值小于3则弃置一张牌
216 | if(player.hp < 3){
217 | await player.chooseToDiscard(1, true);
218 | }
219 | } // 摸两张牌,若体力值小于3则弃置一张牌
220 | ```
221 |
222 |
223 |
224 | 下一章我们将学习如何制作角色。
--------------------------------------------------------------------------------
/code-standard.md:
--------------------------------------------------------------------------------
1 | # 5.5 代码规范
2 |
3 | ## 1. 代码规范概述
4 |
5 | 无名杀扩展开发的代码规范包括:
6 | - 命名规范
7 | - 格式规范
8 | - 注释规范
9 | - 结构规范
10 | - 最佳实践
11 |
12 | ## 2. 命名规范
13 |
14 | ### 2.1 基本命名
15 | ```javascript
16 | import { lib, game, ui, get, ai, _status } from "../../noname.js";
17 | game.import('extension', function(){
18 | return {
19 | name: 'my_extension', // 使用小写字母和下划线
20 | package: {
21 | // 武将命名
22 | character: {
23 | character: {
24 | "ex_zhaoyun": [], // 前缀区分扩展
25 | "ex_sp_zhaoyun": [] // sp等特殊标识
26 | }
27 | },
28 | // 技能命名
29 | skill: {
30 | skill: {
31 | "ex_longdan": {}, // 扩展前缀
32 | "ex_longdan_sha": {} // 子技能用下划线
33 | }
34 | }
35 | }
36 | };
37 | });
38 | ```
39 |
40 | ### 2.2 变量命名
41 | ```javascript
42 | "skill_name": {
43 | content(){
44 | // 常量使用全大写
45 | const MAX_CARDS = 5;
46 |
47 | // 变量使用驼峰命名
48 | let cardCount = player.countCards('h');
49 |
50 | // 布尔值使用is/has前缀
51 | let isEnabled = true;
52 | let hasCards = player.countCards('h') > 0;
53 |
54 | // 迭代变量使用有意义的名称
55 | for(let target of targets){
56 | // 避免使用i,j,k等无意义变量名
57 | }
58 | }
59 | }
60 | ```
61 |
62 | ## 3. 格式规范
63 |
64 | ### 3.1 缩进与空格
65 | ```javascript
66 | // 使用Tab缩进
67 | "format_skill": {
68 | trigger: {
69 | player: "phaseBegin" // 对齐冒号
70 | },
71 | filter(event, player){
72 | // 运算符前后加空格
73 | return player.hp <= 2 &&
74 | player.countCards('h') > 0;
75 | },
76 | content(){
77 | // 括号内部不加空格
78 | if(player.isDamaged()){
79 | player.recover();
80 | }
81 | }
82 | }
83 | ```
84 |
85 | ### 3.2 换行与对齐
86 | ```javascript
87 | // 长语句换行
88 | "line_skill": {
89 | content(){
90 | let result = game.filterPlayer(function(current){
91 | return current.hp < 2 &&
92 | current.countCards('h') > 0 &&
93 | !current.hasSkill('some_skill');
94 | });
95 |
96 | // 链式调用换行
97 | player.chooseTarget()
98 | .set('prompt', '选择一名角色')
99 | .set('ai', function(target){
100 | return get.attitude(player, target);
101 | });
102 | }
103 | }
104 | ```
105 |
106 | ## 4. 注释规范
107 |
108 | ### 4.1 基本注释
109 | ```javascript
110 | /**
111 | * 技能描述
112 | * @param {Object} event - 触发事件
113 | * @param {Object} player - 技能拥有者
114 | * @return {Boolean} 是否满足条件
115 | */
116 | filter(event, player){
117 | // 检查体力值
118 | if(player.hp < 2) return false;
119 |
120 | // 检查手牌数
121 | if(!player.countCards('h')) return false;
122 |
123 | return true;
124 | },
125 |
126 | // 技能效果
127 | content(){
128 | /* 多行注释
129 | * 1. 首先摸牌
130 | * 2. 然后可能失去体力
131 | */
132 | player.draw();
133 | }
134 | ```
135 |
136 | ### 4.2 文档注释
137 | ```javascript
138 | // 扩展文档
139 | help: {
140 | '扩展说明':
141 | '### 主要功能' +
142 | '1. 新增武将' +
143 | '2. 新增卡牌' +
144 | '' +
145 | '### 注意事项' +
146 | '- 需要本体版本1.0以上',
147 | },
148 |
149 | // 技能文档
150 | translate: {
151 | "skill_name": "技能名",
152 | "skill_name_info": "技能描述:出牌阶段限一次,你可以...",
153 | "skill_name_append": "技能补充说明"
154 | }
155 | ```
156 |
157 | ## 5. 结构规范
158 |
159 | ### 5.1 文件结构
160 | ```
161 | extension/
162 | └── 扩展名/
163 | ├── extension.js # 扩展主文件
164 | ├── info.json # 扩展信息
165 | ├── character.js # 武将代码(可选)
166 | ├── card.js # 卡牌代码(可选)
167 | ├── skill.js # 技能代码(可选)
168 | ├── image/ # 图片文件夹
169 | │ ├── card/ # 卡牌图片
170 | │ └── character/ # 武将图片
171 | └── audio/ # 音频文件夹
172 | ├── die/ # 阵亡配音
173 | └── skill/ # 技能配音
174 | ```
175 |
176 | ### 5.2 代码结构
177 | ```javascript
178 | // 按功能分组
179 | game.import('extension', function(){
180 | return {
181 | // 基础配置
182 | name: 'my_extension',
183 | edition: '1.0',
184 | author: 'Author',
185 |
186 | // 核心内容
187 | content(){},
188 | precontent(){},
189 |
190 | // 扩展内容
191 | character: {},
192 | card: {},
193 | skill: {},
194 |
195 | // 其他配置
196 | config: {},
197 | help: {},
198 | package: {}
199 | };
200 | });
201 | ```
202 | ## 练习
203 |
204 | 1. 规范化一个现有技能:
205 | - 检查命名规范
206 | - 优化代码格式
207 | - 完善注释文档
208 | ```javascript
209 | "wusheng":{
210 | enable:['chooseToUse','chooseToRespond'],
211 | filterCard:function(card){return get.color(card)=='red'},
212 | position:'he',
213 | viewAs:{name:'sha'},
214 | viewAsFilter:function(player){return player.countCards('he',{color:'red'})>0},
215 | prompt:'将一张红色牌当杀使用或打出',
216 | check:function(card){return 4-get.value(card)},
217 | ai:{
218 | respondSha:true,
219 | }
220 | },
221 | ```
222 |
223 |
224 | 参考答案 | 🟩 Easy
225 |
226 | ```javascript
227 | // 优化后
228 | "ex_wusheng": {
229 | // 武圣:可将红色牌当【杀】使用或打出
230 | audio: "ext:扩展名/audio/skill:2",
231 | enable: ["chooseToUse", "chooseToRespond"],
232 | position: "he",
233 | filterCard(card){
234 | return get.color(card) == 'red';
235 | },
236 | viewAs: {name: 'sha'},
237 | viewAsFilter(player){
238 | return player.countCards('he', {color: 'red'}) > 0;
239 | },
240 | prompt: "将一张红色牌当【杀】使用或打出",
241 | check(card){
242 | return 4 - get.value(card);
243 | },
244 | ai: {
245 | respondSha: true,
246 | skillTagFilter(player){
247 | return player.countCards('he', {color: 'red'}) > 0;
248 | },
249 | order: 4,
250 | useful: -1,
251 | value: -1
252 | }
253 | }
254 | ```
255 |
256 |
257 | 至此,我们完成了进阶开发的所有内容。下一章我们将通过实战案例来综合运用所学知识。
--------------------------------------------------------------------------------
/appendix/share-skill.md:
--------------------------------------------------------------------------------
1 | # 附录B:共享手牌技能示例
2 |
3 | ## 1. 技能描述
4 | - 锁定技,拥有此技能的角色共享手牌,且手牌互相可见。当你处于弃牌阶段时,你无法弃置“共享”牌。
5 |
6 | ## 2. 技能代码
7 |
8 | - 屎山代码警告
9 | - 仅经过浅测,仅供参考!
10 |
11 | ```javascript
12 | "共享": {
13 | init(player, skill) {
14 | lib.translate["共享"] = "共享"
15 | lib.translate["共享_info"] = "锁定技,拥有此技能的角色共享手牌,且手牌互相可见。当你处于弃牌阶段时,你无法弃置“共享”牌。"
16 | _status.gongxiang = _status.gongxiang || {
17 | cards: [],
18 | players: []
19 | };
20 | ui.gongxiang = ui.gongxiang || ui.create.div("#gongxiang");
21 | for (let card of player.getCards("h")) {
22 | if (_status.gongxiang.cards.some(g =>
23 | g.name === card.name && g.suit === card.suit && g.number === card.number)) continue;
24 | _status.gongxiang.cards.push(card)
25 | }
26 | _status.gongxiang.players.push(get.translation(player.name))
27 | get.event().trigger("gongxiang_update")
28 | },
29 | mark: true,
30 | direct: true,
31 | charlotte: true,
32 | intro: {
33 | content () {
34 | return "当前共享手牌的角色:" + _status.gongxiang.players
35 | }
36 | },
37 | mod: {
38 | ignoredHandcard (card, player) {
39 | return card.hasGaintag('共享')
40 | },
41 | cardDiscardable(card, player, name) {
42 | if (name == "phaseDiscard") return !card.hasGaintag('共享')
43 | return true
44 | },
45 | },
46 | ai: {
47 | viewHandcard: true,
48 | skillTagFilter(player, tag, arg) {
49 | if (arg == player) return false
50 | if (arg.hasSkill("共享_update")) return true
51 | return false
52 | }
53 | },
54 | group: ["共享_lose", "共享_gain", "共享_update"],
55 | subSkill: {
56 | lose: {
57 | charlotte: true,
58 | direct: true,
59 | trigger: {
60 | player: ['useCardBefore', 'respondBefore', "loseBegin", "addToExpansionBegin"]
61 | },
62 | filter (event, player) {
63 | if (!event.cards || !event.cards.length) return false;
64 | return event.cards.some(card =>
65 | _status.gongxiang.cards.some(g =>
66 | g.name === card.name &&
67 | g.suit === card.suit &&
68 | g.number === card.number
69 | )
70 | );
71 | },
72 | content() {
73 | const sharedCards = trigger.cards.filter(card =>
74 | _status.gongxiang.cards.some(g =>
75 | g.name === card.name &&
76 | g.suit === card.suit &&
77 | g.number === card.number
78 | )
79 | );
80 | game.players.forEach(p => {
81 | const playerCards = p.getCards("h").filter(card =>
82 | sharedCards.some(c =>
83 | c.name === card.name &&
84 | c.suit === card.suit &&
85 | c.number === card.number
86 | )
87 | );
88 | if (playerCards.some(c => _status.gongxiang.cards.includes(c))) {
89 | var cards = []
90 | player.lose(trigger.cards, ui.gongxiang)
91 | if (trigger.cards.length === 1) {
92 | cards = playerCards;
93 | } else {
94 | const newCards = trigger.cards.filter(card =>
95 | !sharedCards.some(c =>
96 | c.name === card.name &&
97 | c.suit === card.suit &&
98 | c.number === card.number
99 | )
100 | );
101 | cards = newCards.concat(
102 | playerCards.filter(card => _status.gongxiang.cards.includes(card)
103 | )
104 | );
105 | }
106 | if (event.triggername == "useCardBegin") {
107 | trigger.cancel()
108 | player.useCard(cards, trigger.targets)
109 | }
110 | if (event.triggername == "addToExpansionBegin") {
111 | trigger.cards = cards
112 | }
113 | p.discard(cards)
114 | } else {
115 | p.lose(playerCards, ui.gongxiang)
116 | }
117 | })
118 | _status.gongxiang.cards = _status.gongxiang.cards.filter(g =>
119 | !sharedCards.some(card =>
120 | g.name === card.name &&
121 | g.suit === card.suit &&
122 | g.number === card.number
123 | )
124 | );
125 | }
126 | },
127 | gain: {
128 | trigger: {
129 | player: ["gainAfter"],
130 | },
131 | charlotte: true,
132 | direct: true,
133 | filter(event, player) {
134 | return player.getCards("h").some(card => !card.hasGaintag('共享'))
135 | },
136 | content: async (event, trigger, player) => {
137 | let cards = player.getCards("h").filter(card => !card.hasGaintag('共享'))
138 | for (let card of cards) {
139 | if (!_status.gongxiang.cards.some(g =>
140 | card.name == g.name && card.suit == g.suit && card.number == g.number)) {
141 | _status.gongxiang.cards.push(card);
142 | }
143 | }
144 | event.trigger("gongxiang_update")
145 | }
146 | },
147 | update: {
148 | trigger: {
149 | global: ["gongxiang_update"],
150 | },
151 | charlotte: true,
152 | direct: true,
153 | filter(event, player) {
154 | var cards = player.getCards('h')
155 | let less = _status.gongxiang.cards.some(g =>
156 | !cards.some(card =>
157 | card.name === g.name && card.suit === g.suit && card.number === g.number
158 | )
159 | );
160 | return less
161 | },
162 | content: async (event, trigger, player) => {
163 | var cards = player.getCards('h')
164 | let less = _status.gongxiang.cards.filter(g =>
165 | !cards.some(card =>
166 | card.name === g.name && card.suit === g.suit && card.number === g.number
167 | )
168 | );
169 | cards = []
170 | for (let card of less) {
171 | cards.push(game.createCard(card))
172 | }
173 | if (less) {
174 | player.gain(cards, "bySelf").gaintag.add("共享")
175 | }
176 | }
177 | }
178 | }
179 | },
180 | ```
181 |
--------------------------------------------------------------------------------
/basic.md:
--------------------------------------------------------------------------------
1 | # 第一章:基础知识
2 |
3 | ## 1. 开发环境准备
4 |
5 | ### 1.1 必要工具
6 | - 文本编辑器(推荐使用VS Code)
7 | - 无名杀游戏本体
8 |
9 | ### 1.2 开发环境设置
10 | 1. 下载并安装VS Code
11 | 2. 安装必要插件:
12 | - Prettier(用于JavaScript格式化)
13 |
14 | ### 1.3 新建扩展文件
15 | ##### 方法一
16 | - 游戏内依次点击 `选项 -> 扩展 -> 制作扩展 -> 输入扩展名 -> 确定 -> 保存` 即可新建扩展
17 | #### 方法二
18 | 创建你的开发目录:
19 | ```
20 | ├── extension.js # 扩展主文件
21 | ├── info.json # 扩展信息
22 | ├── character.js # 角色文件(可选)
23 | ├── card.js # 卡牌文件(可选)
24 | ├── skill.js # 技能文件(可选)
25 | ├── image/ # 图片文件夹(可选)
26 | └── audio/ # 音频文件夹(可选)
27 | ```
28 | 将上述文件压缩为zip格式,随后游戏内依次点击 `选项 -> 扩展 -> 获取扩展 -> 导入扩展 -> 选择压缩包 -> 确定` 即可导入扩展
29 |
30 | ## 2. JavaScript基础
31 | ### 2.1 变量
32 | #### 说明
33 | 变量是所有编程语言的基础之一,可以用来存储数据,例如字符串、数字、布尔值、数组等,并在需要时设置、更新或者读取变量中的内容。
34 | 在 JavaScript 中,变量名称并不能随便定义,需要遵循标识符的命名规则,如下所示:
35 | - 变量名中可以包含数字、字母、下划线_、美元符号$;
36 | - 变量名中不能出现汉字;
37 | - 变量名中不能包含空格;
38 | - 变量名不能是 JavaScript 中的关键字、保留字;
39 | - 变量名不能以数字开头,即第一个字符不能为数字。
40 | - 推荐使用驼峰命名法(大驼峰:每个单词首字母大写,例如 FileType、DataArr;小驼峰:第一个单词首字母小写后面的单词首字母大写,例如 fileType、dataArr)。
41 | #### 定义变量
42 | 在 JavaScript 中,通常使用`var`、`let`、`const`关键字定义变量:
43 |
44 | ```javascript
45 | // 声明变量
46 | var name; // 创建全局变量 name
47 | let sex; // 创建局部变量 message
48 | const hp; // 创建常量 hp
49 |
50 | // 赋值变量
51 | var name = "Zhang",
52 | sex = "male" // 创建并赋值全局变量 name、sex
53 | let user = 'John', age = 25, message = 'Hello'; // 创建并赋值局部变量 user、age、message
54 | ```
55 |
56 | 需要注意的是,在大多数编程语言中,`=`的意义均为赋值,而非`等于`!
57 |
58 | 若想判断两个数据是否相等,需要使用`==`。
59 |
60 | #### 变量的区别
61 | ##### 全局变量(var)
62 | 使用`var`定义的变量生效于`函数作用域`或`全局作用域`:
63 | ```javascript
64 | // 全局作用域
65 | if (true) {
66 | var test = true;
67 | }
68 | alert(test) // true,在 if 外依旧可以读取到var变量
69 |
70 | // 函数作用域
71 | function hello() {
72 | if (true) {
73 | var test = "Hello";
74 | }
75 | alert(test); // Hello
76 | }
77 | hello();
78 | alert(test); // ReferenceError: test is not defined(test未定义)
79 | ```
80 | 使用`var`定义的变量允许重新声明和赋值:
81 | ```javascript
82 | // 重新声明
83 | var user = "Pete";
84 | var user = "John";
85 | alert(user); // John
86 |
87 | // 重新赋值
88 | var user = "Pete";
89 | user = "John";
90 | alert(user); // John
91 | ```
92 |
93 | ##### 局部变量(let)
94 | 使用`let`定义的变量仅生效于`块级作用域`:
95 | ```javascript
96 | if (true) {
97 | let test = true;
98 | alert(test) // true
99 | }
100 | alert(test) // ReferenceError: test is not defined(test未定义)
101 | ```
102 | 使用`let`定义的变量允许重新赋值,但不允许重新声明:
103 | ```javascript
104 | // 重新声明
105 | let test = true;
106 | let test = false
107 | alert(test); // SyntaxError: Identifier 'test' has already been declared (test已被定义)
108 |
109 | // 重新赋值
110 | let user = "Pete";
111 | user = "John";
112 | alert(user); // John
113 | ```
114 | ##### 常量(const)
115 | 使用`const`定义的变量仅生效于`块级作用域`:
116 | ```javascript
117 | if (true) {
118 | const test = true;
119 | alert(test) // true
120 | }
121 | alert(test) // ReferenceError: test is not defined(test未定义)
122 | ```
123 | 使用`const`定义的变量不允许重新声明和重新赋值:
124 | ```javascript
125 | // 重新声明
126 | const test = true;
127 | const test = false
128 | alert(test); // SyntaxError: Identifier 'test' has already been declared (test已被定义)
129 |
130 | // 重新赋值
131 | const user = "Pete";
132 | user = "John";
133 | alert(user); // TypeError: Assignment to constant variable.(不能对常量重新赋值)
134 | ```
135 |
136 | ### 2.2 数据类型
137 | JavaScript中的基本数据类型:
138 |
139 | - 字符串(string): ```"Hello,World!"```
140 | - 使用半角单引号`'Hello'`、半角双引号`"Hello"`、反引号`` `Hello` ``包含的文本即为字符串,不可使用全角符号(`“ ”`或`‘ ’`)。
141 | - 反引号是 __功能扩展__ 引号,后续会进行说明。
142 | - 你可以在字符串中使用引号,只要字符串中的引号与包围引号不匹配即可:
143 | - 如 `"张角闻知事露,星夜举兵,自称“天公将军”,张宝称“地公将军”,张梁称“人公将军”。"`
144 |
145 | - 数字(number):```3.1415926```
146 | - 直接使用的数字即为数字类型,可带小数,也可不带,支持科学计数法。
147 | - 数字支持使用乘法 `*`、除法 `/`、加法 `+`、减法 `-` 等等进行运算。
148 | - 这些特殊值也属于数字:
149 | - `Infinity`、`-Infinity` 和 `NaN`,它们分别代表无穷、负无穷、无法定义
150 |
151 | - 布尔(boolean):```true```、```false```
152 | - 布尔只有两个值,以下值在使用布尔转换时会赋值为```false```:
153 | - `0`、`-0`、`null`、`false`、`undefined`、`NaN`
154 | - 注意:数组`[]`、字符串`"false"`均会赋值为`true`
155 |
156 | - 对象(object): ```{name: "张飞",hp: 4}```
157 | - 用于储存数据集合和更复杂的实体的类型。
158 |
159 | - 数组(array):```[1, "hello", { key: "value" }, true]```
160 | - 一种有序集合数据类型,用于存储任意类型的值,是对象的一种特殊类型。
161 |
162 | - 空(null):`null`
163 | - 一个代表`空白`的特殊值。
164 | - 创建但赋值为空:`let hp = null`
165 |
166 | - 无(undefined):`undefined`
167 | - 代表`未被赋值`的特殊值
168 | - 创建但未赋值:`let hp`
169 |
170 | 当我们想要分别处理不同类型值的时候,或者想快速进行数据类型检验时,可以使用`typeof`运算符。
171 | 对 typeof x 的调用会以字符串的形式返回数据类型:
172 | ```javascript
173 | typeof undefined // "undefined"
174 | typeof 0 // "number"
175 | typeof true // "boolean"
176 | typeof "foo" // "string"
177 | typeof alert // "function"
178 | ```
179 |
180 | ### 2.3 函数
181 | 函数是JavaScript中最重要的概念之一:
182 | ```javascript
183 | // 基本函数定义
184 | function say(text = "hello") {
185 | alert(text);
186 | }
187 | say() // Hello
188 | say("你好") // 你好
189 |
190 | /**
191 | * 箭头函数
192 | * 一种比传统的函数表达式更简洁的表达方式,但是具有一些限制
193 | */
194 | const say = text => alert(text);
195 | const isDamaged = (player) => {
196 | return player.hp < player.maxHp
197 | };
198 | say("你好") // 你好
199 | isDmaged("张角") // true(此处仅用作教学,无名杀中的实际判断方式并非此例)
200 |
201 |
202 | // 异步函数
203 | async function drawDiscard(player) {
204 | await player.draw(2);
205 | await player.chooseToDiscard(1);
206 | }
207 | drawDiscard("张角") // 等待张角摸2牌的动作完成后后再执行弃置1牌。
208 | ```
209 | ### 2.4 运算符
210 | 运算符通常用于执行各种操作,如赋值、比较、算术计算等。
211 | ```javascript
212 | let x = 5; // 赋值:将 X 赋值为 5
213 | let sum = 1 + 2; // 加法:3
214 | let product = sum * 2; // 乘法:结果为 6
215 | 1 == 1 // 等于:结果为 true
216 | 1 != 1 // 不等于:结果为 false
217 | x === "5" // 全等:结果为 false
218 | x !== 5 // 不全等:结果为 false
219 | x > 5 // 大于:结果为 false
220 | x < 5 // 小于:结果为 false
221 | x >= 5 // 大于等于:结果为 true
222 | // 注意:=> 为箭头函数标记符,而非运算符
223 | x <= 5 // 小于等于:结果为 true
224 |
225 | x += 1 // 加法赋值:等价于 x = x + 1 ,结果为 6
226 | x++ // 自增:等价于 x += 1 ,结果为6
227 | x-- // 自减:等价于 x -= 1 ,结果为4
228 | 12 % 5 // 求余:结果为 2
229 | -x // 求负:结果为 -5
230 | +"5" // 求正:若为非数字,会试图转化为数字,结果为 5
231 | 2 ** 3 // 指数运算,结果为 8
232 |
233 | x > 6 || x == 5 // 或(||):任意一边满足即为true,结果为 true
234 | x > 6 && x == 5 // 和(&&):任意一边不满足即为false,结果为 false
235 | ```
236 |
237 | ### 2.5 条件语句
238 | ```javascript
239 | // if条件
240 | if (player.hp <= 2) { // 体力值小于等于2执行
241 | player.draw(1);
242 | } else if (player.hp == 3) { // 体力值大于2且等于3时执行
243 | player.draw(2);
244 | } else { // 均不满足时执行
245 | player.draw(3);
246 | }
247 |
248 | // ? 语句
249 | let result = isDamaged() ? player.draw() : player.discard(); // 若isDamaged()返回值为true,则result赋值为player.draw(),否则赋值为player.diascard()
250 |
251 | // switch语句
252 | switch(event.name) {
253 | case 'damage': // 若event.name == damage 时执行
254 | player.draw(1);
255 | break; // 结束本轮,等价于return
256 | case 'lose':
257 | player.recover();
258 | break;
259 | default: // 均不满足的默认情况
260 | return "无满足" // 返回指定内容
261 | }
262 | ```
263 |
264 | ### 2.6 循环语句
265 | ```javascript
266 | // if条件
267 | if (player.hp <= 2) { // 体力值小于等于2执行
268 | player.draw(1);
269 | } else if (player.hp == 3) { // 体力值大于2且等于3时执行
270 | player.draw(2);
271 | } else { // 均不满足时执行
272 | player.draw(3);
273 | }
274 |
275 | // switch语句
276 | switch(event.name) {
277 | case 'damage': // 若event.name == damage 时执行
278 | player.draw(1);
279 | break; // 结束本轮,等价于return
280 | case 'lose':
281 | player.recover();
282 | break;
283 | default: // 均不满足的默认情况
284 | return "无满足" // 返回指定内容
285 | }
286 | ```
287 |
288 |
289 | ## 练习题
290 | 1. 下面的脚本会输出什么?
291 |
292 | ```javascript
293 | let name = "张飞";
294 |
295 | alert( `hello ${1}` ); // ?
296 |
297 | alert( `hello ${"name"}` ); // ?
298 |
299 | alert( `hello ${name}` ); // ?
300 | ```
301 |
302 |
303 | 参考答案 | 🟩 Easy
304 |
305 | ```javascript
306 | // 表达式为数字 1
307 | alert( `hello ${1}` ); // hello 1
308 |
309 | // 表达式是一个字符串 "name"
310 | alert( `hello ${"name"}` ); // hello name
311 |
312 | // 表达式是一个变量。
313 | alert( `hello ${name}` ); // hello 张飞
314 | ```
315 |
316 |
317 |
318 | 2. 下面的脚本会输出什么?
319 |
320 | ```javascript
321 | let x = "5"
322 | x += 5
323 | +x
324 | ```
325 |
326 |
327 | 参考答案 | 🟩 Easy
328 |
329 | ```javascript
330 | x += 5 // 字符串"55"
331 | +x // 数字5
332 | ```
333 |
334 |
335 |
336 | 3. 下面的脚本会输出什么?
337 | ```javascript
338 | let x = 5
339 | let name = ((x > 5 || x == 4) && x == 5) ? "张角" : "张飞"
340 | switch(name) {
341 | case '张角':
342 | return "大贤良师"
343 | case '张宝':
344 | return "地公将军"
345 | default:
346 | return "人公将军?"
347 | }
348 | ```
349 |
350 |
351 | 参考答案 | 🟩 Easy
352 |
353 | ```javascript
354 | let x = 5
355 | let name = ((x > 5 || x == 4) && x == 5) ? "张角" : "张飞"
356 | switch(name) {
357 | case '张角':
358 | return "大贤良师"
359 | case '张宝':
360 | return "地公将军"
361 | default:
362 | return "人公将军?"
363 | }
364 | // 人公将军?
365 | ```
366 |
367 |
368 |
369 |
370 | 下一章我们将正式学习制作无名杀扩展。
371 |
--------------------------------------------------------------------------------
/card.md:
--------------------------------------------------------------------------------
1 | # 第五章:卡牌开发
2 |
3 | ## 1. 卡牌系统概述
4 |
5 | 无名杀的卡牌系统包括:
6 | - 基本牌
7 | - 锦囊牌
8 | - 装备牌
9 | - 延时锦囊牌
10 |
11 | ## 2. 卡牌定义
12 |
13 | ### 2.1 基本结构
14 | ```javascript
15 | card: {
16 | "my_card": {
17 | type: "basic", // 牌的类型(basic/trick/delay/equip)
18 | enable: true, // 是否可以使用
19 | filterTarget: true, // 目标选择条件
20 | content(){ // 卡牌效果
21 | target.damage();
22 | }
23 | },
24 | translate: {
25 | "my_card": "我的卡牌",
26 | "my_card_info": "卡牌描述"
27 | }
28 | }
29 | ```
30 |
31 | ### 2.2 卡牌类型
32 | ```javascript
33 | // 基本牌
34 | "basic_card": {
35 | type: "basic",
36 | enable: true,
37 | usable: 1, // 使用次数限制
38 | selectTarget: 1, // 目标数量
39 | },
40 |
41 | // 锦囊牌
42 | "trick_card": {
43 | type: "trick",
44 | enable: true,
45 | toself: false, // 是否可以对自己使用
46 | selectTarget: -1, // -1表示可以选择任意数量目标
47 | },
48 |
49 | // 装备牌
50 | "equip_card": {
51 | type: "equip",
52 | subtype: "equip1", // 装备类型(equip1武器/equip2防具/equip3防御马/equip4进攻马/equip5宝物)
53 | skills: ["my_skill"], // 装备技能
54 | distance: { // 距离修正
55 | attackFrom: -1, // 攻击距离
56 | globalFrom: -1, // 防御距离
57 | }
58 | },
59 |
60 | // 延时锦囊
61 | "delay_card": {
62 | type: "delay",
63 | enable: true,
64 | filterTarget(card, player, target){
65 | return !target.hasJudge('my_delay'); // 判断目标是否已有同名判定牌
66 | },
67 | judge(card){ // 判定函数
68 | if(get.color(card) == 'red') return 1;
69 | return 0;
70 | }
71 | }
72 | ```
73 |
74 | ## 3. 卡牌效果
75 |
76 | ### 3.1 基础效果
77 | ```javascript
78 | async content(event, trigger, player){
79 | // 造成伤害
80 | await target.damage();
81 |
82 | // 回复体力
83 | await target.recover();
84 |
85 | // 摸牌
86 | await target.draw(2);
87 |
88 | // 弃牌
89 | await target.chooseToDiscard(1, true);
90 | }
91 | ```
92 |
93 | ### 3.2 复杂效果
94 | ```javascript
95 | "complex_card": {
96 | async content(event, trigger, player){
97 | // 选择效果
98 | let choice = await player.chooseControl('选项1', '选项2')
99 | .set('prompt', '请选择一个效果')
100 | .forResult();
101 |
102 | // 条件判断
103 | if(choice.control === '选项1'){
104 | await target.damage('fire');
105 | } else {
106 | await target.draw(2);
107 | }
108 |
109 | // 多目标效果
110 | for(let current of targets){
111 | await current.damage();
112 | await game.delay(0.5);
113 | }
114 | }
115 | }
116 | ```
117 |
118 | ## 4. 卡牌动画
119 |
120 | ### 4.1 使用动画
121 | ```javascript
122 | "animation_card": {
123 | async content(event, trigger, player){
124 | // 使用动画
125 | player.$throw(card);
126 | await game.delay(0.5);
127 |
128 | // 目标动画
129 | target.$damage('fire');
130 | await game.delay(0.3);
131 |
132 | // 获得动画
133 | target.$gain(cards);
134 | }
135 | }
136 | ```
137 |
138 | ### 4.2 特殊动画
139 | ```javascript
140 | "special_animation": {
141 | async content(event, trigger, player){
142 | // 判定动画
143 | let result = await player.judge();
144 |
145 | // 展示动画
146 | await player.$showCards(cards);
147 |
148 | // 比较动画
149 | await player.$compare(card1, target, card2);
150 | }
151 | }
152 | ```
153 |
154 | ## 5. 卡牌音效
155 |
156 | ### 5.1 基础音效
157 | ```javascript
158 | "audio_card": {
159 | audio: true, // 使用默认音效
160 | // 或
161 | audio: "ext:扩展名:2", // 使用扩展音效
162 | }
163 | ```
164 |
165 | ### 5.2 条件音效
166 | ```javascript
167 | "condition_audio": {
168 | audio(player){
169 | // 根据条件返回不同音效
170 | if(player.hp < 3) return "ext:扩展名:2";
171 | return true;
172 | }
173 | }
174 | ```
175 |
176 | ## 6. 进阶功能
177 |
178 | ### 6.1 联动效果
179 | ```javascript
180 | "link_card": {
181 | init(player){
182 | // 初始化
183 | player.storage.link_count = 0;
184 | },
185 | onuse(result, player){
186 | // 使用时触发
187 | player.storage.link_count++;
188 | },
189 | async content(event, trigger, player){
190 | // 根据使用次数改变效果
191 | let count = player.storage.link_count;
192 | await target.damage(count);
193 | }
194 | }
195 | ```
196 |
197 | ### 6.2 特殊规则
198 | ```javascript
199 | "special_rule": {
200 | mod: {
201 | targetEnabled(card, player, target){
202 | // 目标限制
203 | if(target.hp > player.hp) return false;
204 | },
205 | cardUsable(card, player, num){
206 | // 使用次数修改
207 | if(player.hp < 3) return num + 1;
208 | },
209 | ignoredHandcard(card, player){
210 | // 手牌规则修改
211 | if(card.name == 'my_card') return true;
212 | }
213 | }
214 | }
215 | ```
216 |
217 | ## 7. 注意事项
218 |
219 | 1. **卡牌设计**
220 | - 效果要平衡
221 | - 规则要明确
222 | - 避免过于复杂
223 |
224 | ## 练习
225 |
226 | 1. 创建一个基本牌:
227 | - 设计基础效果
228 | - 添加使用条件
229 | - 实现动画效果
230 |
231 |
232 | 参考答案 | 🟩 Easy
233 |
234 | ```javascript
235 | // 在扩展中添加卡牌
236 | card: {
237 | "my_basic": {
238 | type: "basic", // 基本牌
239 | enable: true, // 可以使用
240 | usable: 1, // 每回合限一次
241 | filterTarget(card, player, target){
242 | return target != player; // 不能对自己使用
243 | },
244 | selectTarget: 1, // 选择一个目标
245 | async content(event, trigger, player){
246 | // 播放使用动画
247 | player.$throw(cards);
248 | game.delay(0.5);
249 |
250 | // 造成伤害
251 | await target.damage('fire');
252 | },
253 | ai: {
254 | order: 4, // 使用优先级
255 | value: 5, // 基础价值
256 | useful: 4, // 使用价值
257 | result: {
258 | target: -1.5 // 对目标效果
259 | }
260 | }
261 | }
262 | },
263 | translate: {
264 | "my_basic": "火燎",
265 | "my_basic_info": "出牌阶段限一次,对一名其他角色造成1点火焰伤害。"
266 | }
267 | ```
268 |
269 |
270 | 2. 创建一个装备牌:
271 | - 定义装备效果
272 | - 添加装备技能
273 | - 设置距离修正
274 |
275 |
276 | 参考答案 | 🟩 Easy
277 |
278 | ```javascript
279 | card: {
280 | "my_equip": {
281 | type: "equip", // 装备牌
282 | subtype: "equip1", // 武器
283 | distance: {
284 | attackFrom: -1, // 攻击距离+1
285 | },
286 | skills: ["my_equip_skill"], // 装备技能
287 | onLose(){ // 失去装备时
288 | player.chooseToDiscard("hes", true);
289 | },
290 | onGain(){ // 获得装备时
291 | player.draw();
292 | },
293 | ai: {
294 | basic: {
295 | equipValue: 5, // 装备价值
296 | order: 5, // 使用优先级
297 | useful: 2, // 使用价值
298 | }
299 | }
300 | }
301 | },
302 | skill: {
303 | "my_equip_skill": {
304 | trigger: {source: 'damageBegin1'},
305 | forced: true,
306 | filter(event, player){
307 | return event.card && event.card.name == 'sha';
308 | },
309 | content(){
310 | trigger.num++; // 伤害+1
311 | },
312 | ai: {
313 | damageBonus: true
314 | }
315 | }
316 | },
317 | translate: {
318 | "my_equip": "神剑",
319 | "my_equip_info": "装备时摸一张牌;装备后攻击范围+1。使用【杀】造成的伤害+1;失去后弃置一张牌,。",
320 | "my_equip_skill": "神剑",
321 | "my_equip_skill_info": "锁定技,你使用【杀】造成的伤害+1。"
322 | }
323 | ```
324 |
325 |
326 | 3. 创建一个延时锦囊:
327 | - 设计判定效果
328 | - 添加持续效果
329 | - 实现特殊规则
330 |
331 |
332 | 参考答案 | 🟩 Easy
333 |
334 | ```javascript
335 | card: {
336 | "my_delay": {
337 | type: "delay", // 延时锦囊
338 | enable: true, // 可以使用
339 | filterTarget(card, player, target){
340 | return !target.hasJudge('my_delay'); // 判断是否已有同名判定牌
341 | },
342 | judge(card){ // 判定函数
343 | if(get.color(card) == 'red') return 1;
344 | return 0;
345 | },
346 | effect(){ // 判定效果
347 | if(result.bool){
348 | player.draw(2); // 判定成功摸牌
349 | } else {
350 | player.damage('thunder'); // 判定失败受到伤害
351 | }
352 | },
353 | cancel(){ // 判定牌被取消时
354 | player.draw(); // 摸一张牌
355 | },
356 | ai: {
357 | basic: {
358 | order: 1,
359 | useful: [5,1], // [有利,不利]
360 | value: [5,1],
361 | },
362 | result: {
363 | target(player, target){
364 | return -1.5; // 对目标负面效果
365 | }
366 | }
367 | }
368 | }
369 | },
370 | translate: {
371 | "my_delay": "天雷",
372 | "my_delay_info": "出牌阶段,对一名角色使用。其判定阶段进行判定:若为红色,其摸两张牌;否则受到1点雷电伤害。"
373 | }
374 | ```
375 |
376 |
377 | 下一章我们将学习如何开发游戏模式。
--------------------------------------------------------------------------------
/condition.md:
--------------------------------------------------------------------------------
1 | # 4.5 技能条件
2 |
3 | ## 1. 条件判断概述
4 |
5 | 技能条件判断主要包括:
6 | - [filter函数判断](#filter)
7 | - [check函数判断](#check)
8 | - [mod条件判断](#mod)
9 | - [特殊条件判断](#特殊)
10 |
11 | ## 2. Filter函数判断
12 |
13 | ### 2.1 基本用法
14 | ```javascript
15 | "condition_skill": {
16 | trigger: {player: 'phaseBegin'},
17 | filter(event, player){
18 | // 基础条件
19 | return player.hp < 3; // 体力值小于3
20 |
21 | // 手牌条件
22 | return player.countCards('hs').some(card=> card.name == "sha"); // 有【杀】
23 |
24 | // 目标条件
25 | return target.isDamaged(); // 目标已受伤
26 |
27 | // 场上条件
28 | return game.hasPlayer(function(current){ // 场上存在满足条件的角色
29 | return current.hp < 2;
30 | });
31 | }
32 | }
33 | ```
34 |
35 | ### 2.2 复合条件
36 | ```javascript
37 | filter(event, player){
38 | // 多个条件同时满足
39 | return player.hp < 3 &&
40 | player.countCards('h') > 0 &&
41 | !player.hasSkill('some_skill');
42 |
43 | // 多个条件满足其一
44 | return player.hp < 3 ||
45 | player.countCards('h') == 0 ||
46 | player.isDamaged();
47 | }
48 | ```
49 |
50 | ## 3. Check函数判断
51 |
52 | ### 3.1 AI判断
53 | ```javascript
54 | "check_skill": {
55 | enable: 'phaseUse',
56 | check(event, player){
57 | // 基础判断
58 | if(player.hp < 2) return 1; // 推荐发动
59 | if(player.hp > 3) return 0; // 不推荐发动
60 |
61 | // 形势判断
62 | if(player.hasUnknown()) return 0; // 存在未知情况
63 | if(game.hasPlayer(function(current){
64 | return get.attitude(player,current) > 0 && current.hp == 1;
65 | })) return 2; // 队友濒死优先度高
66 | }
67 | }
68 | ```
69 |
70 | ### 3.2 选择判断
71 | ```javascript
72 | "choice_skill": {
73 | chooseButton: {
74 | dialog(event, player){
75 | return ui.create.dialog('选择一项', [
76 | ['sha', 'tao'], 'vcard'
77 | ]);
78 | },
79 | check(button){
80 | // 按钮选择判断
81 | if(button.link[2] == 'sha'){
82 | if(_status.event.player.hp < 2) return 0;
83 | return 1;
84 | }
85 | return 2; // 桃的优先度最高
86 | },
87 | backup(links, player){
88 | // 选择结果处理
89 | }
90 | }
91 | }
92 | ```
93 |
94 | ## 4. Mod条件判断
95 |
96 | ### 4.1 基本用法
97 | ```javascript
98 | "mod_skill": {
99 | mod: {
100 | // 使用条件
101 | cardEnabled(card, player){
102 | if(player.hp < 2) return false;
103 | },
104 | // 目标条件
105 | targetEnabled(card, player, target){
106 | if(target.hp < 2) return false;
107 | },
108 | // 数值条件
109 | cardUsable(card, player, num){
110 | if(card.name == 'sha') return num + 1;
111 | }
112 | }
113 | }
114 | ```
115 |
116 | ### 4.2 复杂条件
117 | ```javascript
118 | "complex_mod": {
119 | mod: {
120 | // 多重条件判断
121 | cardEnabled(card, player){
122 | if(!player.countCards('h')) return false;
123 | if(player.hasSkill('some_skill')) return false;
124 | if(_status.currentPhase != player) return false;
125 | return true;
126 | },
127 | // 动态数值判断
128 | maxHandcard(player, num){
129 | if(player.hp < 3) return num - 1;
130 | if(player.hasSkill('some_skill')) return num + 1;
131 | return num;
132 | }
133 | }
134 | }
135 | ```
136 |
137 | ## 5. 特殊条件判断
138 |
139 | ### 5.1 时机条件
140 | ```javascript
141 | "timing_skill": {
142 | enable: 'phaseUse',
143 | filter(event, player){
144 | // 判断当前时机
145 | if(_status.currentPhase != player) return false;
146 | if(event.parent.name == 'phaseUse') return true;
147 | return false;
148 | }
149 | }
150 | ```
151 |
152 | ### 5.2 状态条件
153 | ```javascript
154 | "state_skill": {
155 | trigger: {player: 'damageEnd'},
156 | filter(event, player){
157 | // 判断角色状态
158 | if(player.isTurnedOver()) return false;
159 | if(player.isLinked()) return false;
160 | if(!player.isAlive()) return false;
161 | return true;
162 | }
163 | }
164 | ```
165 |
166 | ## 6. 进阶技巧
167 |
168 | ### 6.1 条件缓存
169 | ```javascript
170 | "cache_skill": {
171 | trigger: {player: 'phaseBegin'},
172 | filter(event, player){
173 | // 缓存复杂计算结果
174 | if(player.storage.cache_result === undefined){
175 | player.storage.cache_result = game.countPlayer(function(current){
176 | return current.hp < 2;
177 | });
178 | }
179 | return player.storage.cache_result > 0;
180 | },
181 | content(){
182 | // 使用后清除缓存
183 | delete player.storage.cache_result;
184 | }
185 | }
186 | ```
187 |
188 | ### 6.2 动态条件
189 | ```javascript
190 | "dynamic_skill": {
191 | mod: {
192 | cardEnabled(card, player){
193 | // 根据场上形势动态判断
194 | var enemies = game.countPlayer(function(current){
195 | return get.attitude(player, current) < 0;
196 | });
197 | if(enemies > 2) return false;
198 | return true;
199 | }
200 | }
201 | }
202 | ```
203 |
204 | ## 7. 注意事项
205 |
206 | 1. **性能优化**
207 | - 避免复杂循环
208 | - 合理使用缓存
209 | - 减少不必要判断
210 |
211 | 2. **条件设计**
212 | - 条件要明确
213 | - 避免矛盾条件
214 | - 考虑边界情况
215 |
216 | ## 练习
217 |
218 | 1. 创建一个多重条件技能:
219 | - 包含体力值判断
220 | - 包含手牌数判断
221 | - 包含目标状态判断
222 |
223 |
224 | 参考答案 | 🟩 Easy
225 |
226 | ```javascript
227 | "multi_condition": {
228 | enable: "phaseUse",
229 | filter(event, player){
230 | // 基础条件:体力值小于3且有手牌
231 | if(player.hp >= 3 || !player.countCards('h')) return false;
232 |
233 | // 场上条件:存在受伤角色
234 | return game.hasPlayer(function(current){
235 | return current.isDamaged();
236 | });
237 | },
238 | filterTarget(card, player, target){
239 | // 目标条件
240 | return target.isDamaged() && // 目标已受伤
241 | target.countCards('h') < target.hp && // 手牌数小于体力值
242 | !target.hasSkill('multi_condition_effect'); // 没有临时效果
243 | },
244 | async content(event, trigger, player){
245 | // 根据条件给予不同效果
246 | if(target.hp <= 2){
247 | await target.recover();
248 | } else {
249 | await target.draw(2);
250 | }
251 | target.addTempSkill('multi_condition_effect');
252 | },
253 | ai: {
254 | order: 7,
255 | result: {
256 | target(player, target){
257 | if(target.hp <= 2) return 2;
258 | return 1;
259 | }
260 | }
261 | }
262 | }
263 | ```
264 |
265 |
266 | 2. 创建一个动态条件技能:
267 | - 根据场上形势变化
268 | - 包含AI判断
269 | - 实现条件缓存
270 |
271 |
272 | 参考答案 | 🟥 Hard
273 |
274 | ```javascript
275 | "dynamic_condition": {
276 | // 缓存机制
277 | init(player){
278 | player.storage.dynamic_condition = {
279 | situation: null,
280 | lastUpdate: 0
281 | };
282 | },
283 |
284 | // 场势评估(自建函数)
285 | getSituation(player){
286 | let situation = 0;
287 | // 计算我方状态
288 | situation += player.hp;
289 | situation += player.countCards('h');
290 |
291 | // 计算敌方状态
292 | game.countPlayer(function(current){
293 | if(get.attitude(player, current) < 0){
294 | situation -= current.hp;
295 | situation -= current.countCards('h');
296 | }
297 | });
298 |
299 | return situation
300 | },
301 |
302 | enable: "phaseUse",
303 | filter(event, player){
304 | // 动态条件判断
305 | let situation = lib.skill.dynamic_condition.getSituation(player);
306 |
307 | if(situation > 0){
308 | // 优势时需要有手牌
309 | return player.countCards('h') > 0;
310 | } else {
311 | // 劣势时需要体力值大于1
312 | return player.hp > 1;
313 | }
314 | },
315 | async content(event, trigger, player){
316 | let situation = lib.skill.dynamic_condition.getSituation(player);
317 |
318 | if(situation > 0){
319 | // 优势时进攻
320 | let target = await player.chooseTarget('选择一名目标角色').forResult();
321 | if(target.bool){
322 | await target.targets[0].damage();
323 | }
324 | } else {
325 | // 劣势时防守
326 | await player.draw(2);
327 | }
328 | },
329 | ai: {
330 | order(item, player){
331 | let situation = lib.skill.dynamic_condition.getSituation(player);
332 | if(situation > 0) return 8;
333 | return 4;
334 | },
335 | result: {
336 | player(player){
337 | let situation = lib.skill.dynamic_condition.getSituation(player);
338 | if(situation > 0) return 1;
339 | return 2;
340 | }
341 | }
342 | }
343 | }
344 | ```
345 |
346 |
347 | 3. 创建一个复杂mod技能:
348 | - 修改多个游戏数值
349 | - 包含多重条件判断
350 | - 实现动态修改
351 |
352 |
353 | 参考答案 | 🟥 Hard
354 |
355 | ```javascript
356 | "complex_mod": {
357 | // 初始化
358 | init(player){
359 | player.storage.complex_mod = {
360 | attackRange: 0,
361 | maxHandcard: 0,
362 | cardUsable: 0
363 | };
364 | },
365 |
366 | // 更新数值(自建函数)
367 | updateMod(player){
368 | let storage = player.storage.complex_mod;
369 |
370 | // 根据体力值修改攻击距离
371 | storage.attackRange = Math.max(0, 3 - player.hp);
372 |
373 | // 根据手牌数修改手牌上限
374 | storage.maxHandcard = Math.floor(player.countCards('h') / 2);
375 |
376 | // 根据装备数修改出牌次数
377 | storage.cardUsable = player.countCards('e');
378 | },
379 |
380 | // 触发更新
381 | trigger: {
382 | player: ["changeHp", "gainAfter", "loseAfter", "equipAfter"]
383 | },
384 | forced: true,
385 | popup: false,
386 | filter(event, player){
387 | return true;
388 | },
389 | content(){
390 | lib.skill.complex_mod.updateMod(player);
391 | },
392 |
393 | // 数值修改
394 | mod: {
395 | attackRange(player, num){
396 | return num + player.storage.complex_mod.attackRange;
397 | },
398 | maxHandcard(player, num){
399 | return num + player.storage.complex_mod.maxHandcard;
400 | },
401 | cardUsable(card, player, num){
402 | if(card.name == 'sha'){
403 | return num + player.storage.complex_mod.cardUsable;
404 | }
405 | },
406 | globalTo(from, to, distance){
407 | // 特殊条件:被翻面时防御距离+1
408 | if(to.isTurnedOver()) return distance + 1;
409 | },
410 | targetEnabled(card, player, target){
411 | // 特殊条件:目标体力值大于自己时不能指定
412 | if(target.hp > player.hp && get.tag(card, 'damage')){
413 | return false;
414 | }
415 | },
416 | ignoredHandcard(card, player){
417 | // 特殊条件:红色手牌不计入手牌上限
418 | if(get.color(card) == 'red'){
419 | return true;
420 | }
421 | }
422 | },
423 |
424 | // 显示提示
425 | mark: true,
426 | intro: {
427 | content(storage, player){
428 | lib.skill.complex_mod.updateMod(player);
429 | let str = '当前效果:n';
430 | str += '攻击距离+' + storage.attackRange + 'n';
431 | str += '手牌上限+' + storage.maxHandcard + 'n';
432 | str += '出杀次数+' + storage.cardUsable;
433 | return str;
434 | }
435 | }
436 | }
437 | ```
438 |
439 |
440 | 下一节我们将学习技能动画效果。
--------------------------------------------------------------------------------
/effect.md:
--------------------------------------------------------------------------------
1 | # 4.3 技能效果
2 |
3 | ## 1. 基础效果
4 |
5 | ### 1.1 摸牌与弃牌
6 | ```javascript
7 | async content(event, trigger, player){
8 | // 摸牌
9 | await player.draw(2); // 摸两张牌
10 | await player.drawTo(5); // 摸至五张牌
11 |
12 | // 弃牌
13 | await player.discard(player.getCards('h')); // 弃置所有手牌
14 | await player.chooseToDiscard(1, true); // 强制弃置一张牌
15 | }
16 | ```
17 |
18 | ### 1.2 体力操作
19 | ```javascript
20 | async content(event, trigger, player){
21 | // 回复体力
22 | await player.recover(); // 回复1点体力
23 | await player.recover(2); // 回复2点体力
24 |
25 | // 失去体力
26 | await player.loseHp(); // 失去1点体力
27 | await player.loseMaxHp(); // 失去1点体力上限
28 | await player.gainMaxHp(); // 获得1点体力上限
29 |
30 | // 造成伤害
31 | await player.damage('fire'); // 造成1点火焰伤害
32 | await player.damage(2, 'thunder'); // 造成2点雷电伤害
33 | await player.damage("nosource"); // 造成1点无来源伤害
34 | }
35 | ```
36 |
37 | ### 1.3 获得与给予
38 | ```javascript
39 | async content(event, trigger, player){
40 | // 获得牌
41 | await player.gain(trigger.cards, 'gain2'); // 获得牌并展示
42 | await player.gainPlayerCard(target, true); // 获得目标角色的一张牌
43 |
44 | // 给予牌
45 | await player.give(cards, target); // 给予目标角色牌
46 | await target.$give(1, player); // 播放给予动画(实际不给)
47 | }
48 | ```
49 |
50 | ## 2. 选择效果
51 |
52 | ### 2.1 选择角色
53 | ```javascript
54 | async content(event, trigger, player){
55 | // 选择一名角色
56 | let result = await player.chooseTarget('请选择一名角色', true).forResult();
57 | if(result.bool){
58 | let target = result.targets[0];
59 | await target.draw();
60 | }
61 |
62 | // 选择多名角色
63 | let result = await player.chooseTarget(2, true, '请选择两名角色').forResult();
64 | if(result.bool){
65 | for(let target of result.targets){
66 | await target.damage();
67 | }
68 | }
69 | }
70 | ```
71 |
72 | ### 2.2 选择牌
73 | ```javascript
74 | async content(event, trigger, player){
75 | // 选择手牌
76 | let result = await player.chooseCard('h', '请选择一张手牌').forResult();
77 | if(result.bool){
78 | await player.discard(result.cards);
79 | }
80 |
81 | // 选择特定牌
82 | let result = await player.chooseCard('he', {color:'red'}, '请选择一张红色牌').forResult();
83 | if(result.bool){
84 | await player.give(result.cards, target);
85 | }
86 | }
87 | ```
88 |
89 | ### 2.3 选择选项
90 | ```javascript
91 | async content(event, trigger, player){
92 | // 选择一个选项
93 | let result = await player.chooseControl('选项1', '选项2')
94 | .set('prompt', '请选择一个选项')
95 | .set('ai', ()=>{
96 | return player.hp < 3 ? '选项1' : '选项2';
97 | })
98 | .forResult();
99 |
100 | if(result.control === '选项1'){
101 | await player.draw();
102 | } else {
103 | await player.recover();
104 | }
105 | }
106 | ```
107 |
108 | ## 3. 判定效果
109 |
110 | ### 3.1 基础判定
111 | ```javascript
112 | async content(event, trigger, player){
113 | // 进行判定
114 | let result = await player.judge();
115 | if(result.color == 'red'){
116 | await player.draw(2);
117 | } else {
118 | await player.draw();
119 | }
120 | }
121 | ```
122 |
123 | ### 3.2 自定义判定
124 | ```javascript
125 | async content(event, trigger, player){
126 | let result = await player.judge(card => {
127 | if(get.color(card) == 'red') return 1;
128 | return 0;
129 | });
130 |
131 | if(result.bool){
132 | await player.draw(2);
133 | }
134 | }
135 | ```
136 |
137 | ## 4. 复杂效果
138 |
139 | ### 4.1 连续效果
140 | ```javascript
141 | async content(event, trigger, player){
142 | // 选择目标并执行连续效果
143 | let targets = await player.chooseTarget(2, true, '请选择两名角色').forResult();
144 | if(targets.bool){
145 | for(let target of targets.targets){
146 | await target.damage('fire');
147 | await target.draw();
148 | }
149 | }
150 | }
151 | ```
152 |
153 | ### 4.2 条件分支
154 | ```javascript
155 | async content(event, trigger, player){
156 | // 根据条件执行不同效果
157 | if(player.hp <= 2){
158 | let choice = await player.chooseControl('摸牌', '回血').forResult()
159 | .set('prompt', '请选择一项');
160 | if(choice.control === '摸牌'){
161 | await player.draw(2);
162 | } else {
163 | await player.recover();
164 | }
165 | } else {
166 | await player.draw();
167 | }
168 | }
169 | ```
170 |
171 | ## 5. 特殊效果
172 |
173 | ### 5.1 技能获得与失去
174 | ```javascript
175 | async content(event, trigger, player){
176 | // 获得技能
177 | player.addTempSkill('new_skill'); // 获得临时技能
178 | player.addSkill('permanent_skill'); // 获得永久技能
179 |
180 | // 失去技能
181 | player.removeSkill('some_skill'); // 失去技能
182 | player.awakenSkill('awaken_skill'); // 失效技能
183 | }
184 | ```
185 |
186 | ### 5.2 状态变化
187 | ```javascript
188 | async content(event, trigger, player){
189 | // 翻面与横置
190 | await player.turnOver(); // 翻面
191 | await player.link(); // 横置
192 |
193 | // 跳过阶段
194 | player.skip('phaseUse'); // 跳过出牌阶段
195 | player.skip('phaseDraw'); // 跳过摸牌阶段
196 | }
197 | ```
198 |
199 | ## 练习
200 |
201 | 1. 创建一个复合效果技能:
202 | - 选择一名角色
203 | - 根据判定结果执行不同效果
204 | - 添加连续效果
205 |
206 |
207 | 参考答案 | 🟨 Medium
208 |
209 | ```javascript
210 | "compound_skill": {
211 | enable: "phaseUse",
212 | usable: 1,
213 | filter(event, player){
214 | return player.countCards('h') > 0;
215 | },
216 | filterTarget(card, player, target){
217 | return target != player;
218 | },
219 | async content(event, trigger, player){
220 | // 选择一名角色
221 | let result = await player.chooseTarget('选择一名目标角色', true)
222 | .set('ai', target => -get.attitude(player, target))
223 | .forResult();
224 | if(result.bool){
225 | event.target = result.targets[0];
226 | // 进行判定
227 | let judge = await event.target.judge(function(card){
228 | if(get.color(card) == 'red') return 1;
229 | return 0;
230 | });
231 | // 根据判定结果执行效果
232 | if(result.bool){
233 | // 红色:目标摸牌
234 | await event.target.draw(2);
235 | // 连续效果:其他角色可以选择摸牌
236 | let others = game.filterPlayer(current =>
237 | current != player && current != event.target
238 | );
239 | for(let other of others){
240 | let choice = await other.chooseBool('是否摸一张牌?').forResult();
241 | if(choice.bool){
242 | await other.draw();
243 | }
244 | }
245 | } else {
246 | // 黑色:目标受到伤害
247 | await event.target.damage('thunder');
248 | // 连续效果:其他角色可以选择弃牌
249 | let others = game.filterPlayer(current =>
250 | current != player && current != event.target
251 | );
252 | for(let other of others){
253 | if(other.countCards('h') > 0){
254 | let choice = await other.chooseBool('是否弃置一张手牌?').forResult();
255 | if(choice.bool){
256 | await other.chooseToDiscard(1, true);
257 | }
258 | }
259 | }
260 | }
261 | }
262 | },
263 | ai: {
264 | order: 7,
265 | result: {
266 | target: -1.5
267 | }
268 | }
269 | }
270 | ```
271 |
272 |
273 | 2. 创建一个状态变化技能:
274 | - 改变角色状态
275 | - 添加技能获得/失去
276 | - 实现阶段跳过
277 |
278 |
279 | 参考答案 | 🟩 Easy
280 |
281 | ```javascript
282 | "state_skill": {
283 | enable: "phaseUse",
284 | usable: 1,
285 | filter(event, player){
286 | return !player.hasSkill('state_skill_effect');
287 | },
288 | async content(event, trigger, player){
289 | // 选择状态
290 | let choices = ['翻面并获得技能', '横置并跳过阶段', '废除装备栏并摸牌'];
291 | let choice = await player.chooseControl(choices)
292 | .set('prompt', '请选择一个状态效果')
293 | .set('ai', function(){
294 | if(player.isTurnedOver()) return '翻面并获得技能';
295 | if(player.countCards('h') <= 1) return '废除装备栏并摸牌';
296 | return '横置并跳过阶段';
297 | })
298 | .forResult();
299 |
300 | // 执行选择的效果
301 | switch(result.control){
302 | case '翻面并获得技能':
303 | await player.turnOver();
304 | player.addTempSkill('state_skill_effect');
305 | break;
306 |
307 | case '横置并跳过阶段':
308 | await player.link(true);
309 | player.skip('phaseUse');
310 | player.skip('phaseDiscard');
311 | break;
312 |
313 | case '废除装备栏并摸牌':
314 | player.disableEquip(1);
315 | player.disableEquip(2);
316 | await player.draw(3);
317 | break;
318 | }
319 | },
320 | subSkill: {
321 | effect: {
322 | mark: true,
323 | intro: {
324 | content: '获得技能效果'
325 | },
326 | mod: {
327 | maxHandcard(player, num){
328 | return num + 2;
329 | }
330 | }
331 | }
332 | }
333 | }
334 | ```
335 |
336 |
337 | 3. 创建一个选择类技能:
338 | - 提供多个选项
339 | - 根据条件限制选择
340 | - 实现不同效果
341 |
342 |
343 | 参考答案 | 🟨 Medium
344 |
345 | ```javascript
346 | "choice_skill": {
347 | enable: "phaseUse",
348 | filter(event, player){
349 | if(player.storage.choice_skill_used) return false;
350 | if(player.hp < 2 && !player.countCards('h')) return false;
351 | return true;
352 | },
353 | async content(event, trigger, player){
354 | // 准备选项
355 | let choices = [];
356 | if(player.isDamaged()) choices.push('回复体力');
357 | if(player.countCards('h') < player.hp) choices.push('摸牌');
358 | if(player.countCards('h') > 0) choices.push('弃牌造成伤害');
359 |
360 | let choice = await player.chooseControl(choices)
361 | .set('prompt', '请选择一个效果')
362 | .set('ai', function(){
363 | if(player.hp <= 2 && choices.contains('回复体力'))
364 | return '回复体力';
365 | if(player.countCards('h') <= 1 && choices.contains('摸牌'))
366 | return '摸牌';
367 | return '弃牌造成伤害';
368 | })
369 | .forResult();
370 |
371 | // 执行效果
372 | switch(result.control){
373 | case '回复体力':
374 | await player.recover();
375 | break;
376 |
377 | case '摸牌':
378 | await player.draw(player.hp - player.countCards('h'));
379 | break;
380 |
381 | case '弃牌造成伤害':
382 | let targets = await player.chooseTarget('选择一名目标角色', true).forResult();
383 | if(targets.bool){
384 | await player.chooseToDiscard(1, true);
385 | await targets.targets[0].damage();
386 | }
387 | break;
388 | }
389 |
390 | // 记录使用
391 | player.storage.choice_skill_used = true;
392 | player.markSkill('choice_skill_used');
393 | },
394 | group: "choice_skill_clear",
395 | subSkill: {
396 | used: {
397 | mark: true,
398 | intro: {
399 | content: '本回合已使用'
400 | },
401 | onremove: true
402 | },
403 | clear: {
404 | trigger: {player: 'phaseEnd'},
405 | forced: true,
406 | content(){
407 | delete player.storage.choice_skill_used;
408 | player.unmarkSkill('choice_skill_used');
409 | }
410 | }
411 | }
412 | }
413 | ```
414 |
415 |
416 | 下一节我们将学习如何使用技能标记系统。
--------------------------------------------------------------------------------
/mark.md:
--------------------------------------------------------------------------------
1 | # 4.4 技能标记
2 |
3 | ## 1. 标记系统概述
4 |
5 | 标记系统是无名杀中记录技能状态和数值的重要机制,主要包括:
6 | - 基础标记
7 | - 数值标记
8 | - 临时标记
9 | - 持续标记
10 |
11 | ## 2. 基础标记
12 |
13 | ### 2.1 标记定义
14 | ```javascript
15 | "mark_skill": {
16 | mark: true, // 显示标记
17 | marktext: "标", // 标记显示的文字
18 | intro: {
19 | name: "标记名称",
20 | content: "标记描述",
21 | markcount: "标记数量"
22 | },
23 | }
24 | ```
25 |
26 | ### 2.2 标记操作
27 | ```javascript
28 | async content(event, trigger, player){
29 | // 添加标记
30 | player.addMark('mark_skill');
31 |
32 | // 移除标记
33 | player.removeMark('mark_skill');
34 |
35 | // 判断是否有标记
36 | if(player.hasMark('mark_skill')){
37 | // 有标记时的操作
38 | }
39 | }
40 | ```
41 |
42 | ## 3. 数值标记
43 |
44 | ### 3.1 基本用法
45 | ```javascript
46 | "count_mark": {
47 | mark: true,
48 | marktext: "数",
49 | intro: {
50 | content: "当前标记数:#", // #会被替换为实际数值
51 | },
52 | init(player){
53 | player.storage.count_mark = 0; // 初始化标记值
54 | },
55 | content(){
56 | // 增加标记
57 | player.addMark('count_mark', 1);
58 | // 或
59 | player.storage.count_mark++;
60 |
61 | // 减少标记
62 | player.removeMark('count_mark', 1);
63 | // 或
64 | player.storage.count_mark--;
65 |
66 | // 更新所有标记
67 | player.updateMarks();
68 | // 更新标记
69 | player.markSkill('count_mark');
70 | // 删除标记
71 | player.unmarkSkill('count_mark')
72 | }
73 | }
74 | ```
75 |
76 | ### 3.2 高级用法
77 | ```javascript
78 | "complex_mark": {
79 | mark: true,
80 | marktext: "复",
81 | intro: {
82 | content(storage, player){
83 | // 自定义标记显示内容
84 | let str = '当前状态:';
85 | if(storage.count > 0){
86 | str += '可用次数:' + storage.count;
87 | }
88 | return str;
89 | }
90 | },
91 | init(player){
92 | // 复杂标记数据结构
93 | player.storage.complex_mark = {
94 | count: 0,
95 | used: false,
96 | targets: []
97 | };
98 | }
99 | }
100 | ```
101 |
102 | ## 4. 临时标记
103 |
104 | ### 4.1 回合内标记
105 | ```javascript
106 | "temp_mark": {
107 | mark: true,
108 | intro: {
109 | content: "本回合内可以发动",
110 | },
111 | content(){
112 | // 添加临时标记
113 | player.addTempSkill('temp_mark', 'phaseEnd');
114 |
115 | // 或指定多个时机
116 | player.addTempSkill('temp_mark', {
117 | phaseEnd: true,
118 | damageEnd: true
119 | });
120 | }
121 | }
122 | ```
123 |
124 | ### 4.2 条件标记
125 | ```javascript
126 | "condition_mark": {
127 | mark: true,
128 | intro: {
129 | content(storage, player){
130 | if(player.hp < 3){
131 | return "触发条件已满足";
132 | }
133 | return "触发条件未满足";
134 | }
135 | },
136 | onremove(player){
137 | // 标记移除时的操作
138 | delete player.storage.condition_mark;
139 | }
140 | }
141 | ```
142 |
143 | ## 5. 持续标记
144 |
145 | ### 5.1 回合计数
146 | ```javascript
147 | "round_mark": {
148 | mark: true,
149 | marktext: "轮",
150 | intro: {
151 | content: "剩余回合:#",
152 | },
153 | init(player){
154 | player.storage.round_mark = 3; // 持续3回合
155 | },
156 | trigger: {player: 'phaseEnd'},
157 | forced: true,
158 | content(){
159 | player.storage.round_mark--;
160 | if(player.storage.round_mark <= 0){
161 | player.removeSkill('round_mark');
162 | }
163 | }
164 | }
165 | ```
166 |
167 | ### 5.2 状态标记
168 | ```javascript
169 | "state_mark": {
170 | mark: true,
171 | marktext: "状",
172 | intro: {
173 | content(storage, player){
174 | let states = {
175 | ready: "准备状态",
176 | active: "激活状态",
177 | exhaust: "消耗状态"
178 | };
179 | return states[storage] || "未知状态";
180 | }
181 | },
182 | init(player){
183 | player.storage.state_mark = 'ready';
184 | }
185 | }
186 | ```
187 |
188 | ## 6. 进阶技巧
189 |
190 | ### 6.1 标记联动
191 | ```javascript
192 | group: ["mark_skill_1", "mark_skill_2"],
193 | subSkill: {
194 | "1": {
195 | trigger: {player: 'phaseBegin'},
196 | filter(event, player){
197 | return player.storage.mark_count > 0;
198 | },
199 | content(){
200 | // 标记数值影响技能效果
201 | }
202 | },
203 | "2": {
204 | trigger: {player: 'damageEnd'},
205 | content(){
206 | // 伤害影响标记数值
207 | player.addMark('mark_count', trigger.num);
208 | }
209 | }
210 | }
211 | ```
212 |
213 | ### 6.2 标记显示
214 | ```javascript
215 | "visual_mark": {
216 | mark: true,
217 | marktext: "★",
218 | intro: {
219 | name: "特殊标记",
220 | mark(dialog, storage, player){
221 | // 自定义标记显示界面
222 | dialog.addText('当前手牌:');
223 | dialog.add([player.getCards('h'),'vcard']);
224 | }
225 | }
226 | }
227 | ```
228 |
229 | ## 7. 注意事项
230 |
231 | 1. **标记命名**
232 | - 使用有意义的名称
233 | - 避免与现有标记重名
234 | - 建议使用前缀区分
235 |
236 | 2. **性能优化**
237 | - 及时清理无用标记
238 | - 避免过多的标记更新
239 | - 合理使用临时标记
240 |
241 | 3. **联机兼容**
242 | - 确保标记同步
243 | - 使用标准API
244 |
245 | ## 练习
246 |
247 | 1. 创建一个计数标记:
248 | - 记录技能使用次数
249 | - 显示剩余次数
250 | - 达到条件后清除
251 |
252 |
253 | 参考答案 | 🟩 Easy
254 |
255 | ```javascript
256 | "count_mark": {
257 | enable: "phaseUse",
258 | mark: true,
259 | intro: {
260 | name: "计数",
261 | content(storage) {
262 | return `已使用${storage["used"]}次,剩余可用次数:${storage["max"] - storage["used"]}`
263 | }
264 | },
265 | init(player){
266 | // 初始化标记
267 | player.storage.count_mark = {
268 | used: 0,
269 | max: 3
270 | };
271 | },
272 | filter(event, player){
273 | // 检查使用次数
274 | return player.storage.count_mark.used < player.storage.count_mark.max;
275 | },
276 | async content(event, trigger, player){
277 | // 增加使用次数
278 | player.storage.count_mark.used++;
279 | player.markSkill('count_mark');
280 | player.syncStorage('count_mark');
281 |
282 | // 执行效果
283 | await player.draw(2);
284 |
285 | // 达到次数后清除
286 | if(player.storage.count_mark.used >= player.storage.count_mark.max){
287 | player.unmarkSkill('count_mark');
288 | game.log(player, '的技能使用次数已达上限');
289 | }
290 | },
291 | ai: {
292 | order: 5,
293 | result: {
294 | player: 1
295 | }
296 | }
297 | }
298 | ```
299 |
300 |
301 | 2. 创建一个状态标记:
302 | - 记录多个状态
303 | - 状态间可以转换
304 | - 不同状态有不同效果
305 |
306 |
307 | 参考答案 | 🟩 Easy
308 |
309 | ```javascript
310 | "state_mark": {
311 | enable: "phaseUse",
312 | mark: true,
313 | intro: {
314 | name: "状态",
315 | content(storage){
316 | let states = {
317 | ready: "准备状态:可以发动技能",
318 | active: "激活状态:攻击距离+1",
319 | exhaust: "消耗状态:手牌上限+1"
320 | };
321 | return states[storage.state] || "未知状态";
322 | }
323 | },
324 | init(player){
325 | player.storage.state_mark = {
326 | state: 'ready'
327 | };
328 | },
329 | filter(event, player){
330 | return player.storage.state_mark.state == 'ready';
331 | },
332 | async content(event, trigger, player){
333 | // 选择转换的状态
334 | let states = ['active', 'exhaust'];
335 | let result = await player.chooseControl(states)
336 | .set('prompt', '请选择要转换的状态')
337 | .set('ai', function(){
338 | if(player.hp <= 2) return 'exhaust';
339 | return 'active';
340 | })
341 | .forResult();
342 |
343 | // 转换状态
344 | player.storage.state_mark.state = result.control;
345 | player.markSkill('state_mark');
346 |
347 | // 根据状态添加效果
348 | if(result.control == 'active'){
349 | player.addTempSkill('state_mark_active');
350 | } else {
351 | player.addTempSkill('state_mark_exhaust');
352 | }
353 | },
354 | group: ["state_mark_reset"],
355 | subSkill: {
356 | active: {
357 | mod: {
358 | attackRange(player, num){
359 | return num + 1;
360 | }
361 | }
362 | },
363 | exhaust: {
364 | mod: {
365 | maxHandcard(player, num){
366 | return num + 1;
367 | }
368 | }
369 | },
370 | reset: {
371 | trigger: {player: 'phaseEnd'},
372 | forced: true,
373 | filter(event, player){
374 | return player.storage.state_mark.state != 'ready';
375 | },
376 | content(){
377 | player.storage.state_mark.state = 'ready';
378 | player.markSkill('state_mark');
379 | }
380 | }
381 | }
382 | }
383 | ```
384 |
385 |
386 | 3. 创建一个复杂标记:
387 | - 包含多个数据项
388 | - 实现标记联动
389 | - 自定义显示界面
390 |
391 |
392 | 参考答案 | 🟥 Hard
393 |
394 | ```javascript
395 | "complex_mark": {
396 | mark: true,
397 | intro: {
398 | name: "复合标记",
399 | mark(dialog, storage, player){
400 | dialog.addText('当前状态:');
401 | // 显示能量值
402 | dialog.addText('能量: ' + storage.energy + '/' + storage.maxEnergy);
403 | // 显示buff列表
404 | if(storage.buffs && storage.buffs.length){
405 | dialog.addText('增益效果:');
406 | for(let buff of storage.buffs){
407 | dialog.add([[buff.card], 'vcard']);
408 | dialog.addText(buff.name + '(' + buff.duration + '回合)');
409 | }
410 | }
411 | },
412 | },
413 | init(player){
414 | player.storage.complex_mark = {
415 | energy: 0,
416 | maxEnergy: 5,
417 | buffs: []
418 | };
419 | },
420 | enable: "phaseUse",
421 | filter(event, player){
422 | return player.storage.complex_mark.energy > 0;
423 | },
424 | async content(event, trigger, player){
425 | // 选择效果
426 | let choices = ['获得增益', '造成伤害'];
427 | let result = await player.chooseControl(choices)
428 | .set('prompt', '请选择消耗能量的效果')
429 | .set('ai', function(){
430 | if(player.hp <= 2) return '获得增益';
431 | return '造成伤害';
432 | })
433 | .forResult();
434 |
435 | // 消耗能量
436 | player.storage.complex_mark.energy--;
437 |
438 | // 执行效果
439 | if(result.control == '获得增益'){
440 | // 添加buff
441 | player.storage.complex_mark.buffs.push({
442 | name: '攻击加成',
443 | duration: 2,
444 | card: ["heart","","sha","fire"]
445 | });
446 | } else {
447 | // 选择目标造成伤害
448 | let target = await player.chooseTarget('选择一名目标角色').forResult();
449 | if(target.bool){
450 | await target.targets[0].damage();
451 | }
452 | }
453 |
454 | // 更新标记
455 | player.markSkill('complex_mark');
456 | },
457 | group: ["complex_mark_gain", "complex_mark_update","complex_mark_buff"],
458 | subSkill: {
459 | gain: {
460 | trigger: {player: 'phaseBegin'},
461 | forced: true,
462 | content(){
463 | // 回合开始获得能量
464 | let storage = player.storage.complex_mark;
465 | if(storage.energy < storage.maxEnergy){
466 | storage.energy++;
467 | player.markSkill('complex_mark');
468 | }
469 | }
470 | },
471 | update: {
472 | trigger: {player: 'phaseEnd'},
473 | forced: true,
474 | content(){
475 | // 更新buff持续时间
476 | let storage = player.storage.complex_mark;
477 | if(storage.buffs && storage.buffs.length){
478 | for(let buff of storage.buffs){
479 | buff.duration--;
480 | }
481 | // 移除过期buff
482 | storage.buffs = storage.buffs.filter(buff => buff.duration > 0);
483 | player.markSkill('complex_mark');
484 | }
485 | }
486 | },
487 | buff: {
488 | trigger: {
489 | source: "damageBegin"
490 | },
491 | filter (event, player) {
492 | // 筛选加成目标
493 | let storage = player.storage.complex_mark;
494 | if (storage.buffs.some(buff => buff.name == "攻击加成")) {
495 | return event.card.name == "sha";
496 | }
497 | },
498 | content () {
499 | // 实现效果
500 | trigger.num++;
501 | trigger.nature = "fire";
502 | },
503 | }
504 | }
505 | }
506 | ```
507 |
508 |
509 | 下一节我们将学习技能的条件判断。
--------------------------------------------------------------------------------
/skill.md:
--------------------------------------------------------------------------------
1 | # 4.1 技能系统
2 |
3 | ## 1. 技能基本结构
4 |
5 | 技能是武将最核心的组成部分,一个完整的技能通常包含以下部分:
6 |
7 |
8 | 展开示例
9 |
10 | ```javascript
11 | "skill_name": {
12 | // 基础属性
13 | /**
14 | * 筛选条件
15 | * @param event 触发事件
16 | * @param player 持有角色
17 | * @param name 触发事件名
18 | * @param target 本次触发目标,需要有getIndex才可用
19 | * @description
20 | * 返回true时才可执行技能。
21 | */
22 | filter(event, player, name, target){},
23 | /**
24 | * 执行要求
25 | * @param event 技能事件
26 | * @param trigger 触发事件
27 | * @param player 持有角色
28 | * @description
29 | * 技能满足筛选条件后触发。
30 | * 此时不视为执行技能,可以用来做筛选。
31 | * 如:张辽【突袭】请选择至多两名角色获得其手牌
32 | * 若取消则不执行技能。
33 | * event.result.bool为true时执行技能。
34 | */
35 | async cost(event, trigger, player){},
36 | /**
37 | * 技能效果
38 | * @param event 技能事件
39 | * @param trigger 触发事件
40 | * @param player 持有角色
41 | * @description
42 | * 技能执行的效果
43 | */
44 | async content(event, trigger, player){}, // 技能效果
45 |
46 | // 可选属性
47 | /**
48 | * 初始化
49 | * @param player 持有角色
50 | * @param skill 当前技能名
51 | * @description
52 | * 获得技能时执行的事件
53 | */
54 | init(player, skill){},
55 | /**
56 | * 技能配音
57 | * @param {string | number | boolean | [string, number]} 详情请查看 配音系统 章节
58 | */
59 | audio: 2,
60 | /**
61 | * 触发时机,类似被动技能,不能与 主动使用 同时存在。
62 | * @param {object} 具体触发时机,详细参数请查看 触发时机 章节
63 | */
64 | trigger: {},
65 | /**
66 | * 主动使用,主动技,不能与 触发时机 同时存在
67 | * @param {string | string[]} 使用时机
68 | * @description
69 | * 该方法所支持的参数类型:
70 | * - `chooseCard` 参数:选牌时可用
71 | * - `chooseToRespond` 参数:打出牌时可用
72 | * - `chooseToUse` 参数:使用牌时可用
73 | * - `phaseUse` 参数:出牌时可用
74 | */
75 | enable: "",
76 | /**
77 | * 每回合使用次数
78 | * @param skill 当前技能
79 | * @param player 持有角色
80 | */
81 | usable: ((skill: string, player: Player) => number) | number,
82 | /**
83 | * 每轮使用次数
84 | * @param {number} num 使用次数
85 | */
86 | round: 1,
87 | /**
88 | * 技能发动次数
89 | * @param event 当前事件
90 | * @param player 持有角色
91 | * @param triggername 触发名称
92 | * @description
93 | * 返回的数组长度即为本次技能执行次数
94 | * 第X次执行的目标为数组中的第X-1个元素。
95 | */
96 | getIndex: ((event, player, triggername)=>object[]),
97 | /**
98 | * 是否强制发动
99 | * @param {boolean} 强制发动
100 | * @description
101 | * 若为true,默认为锁定技。
102 | * 需要将locked修改为false才可实现强制发动的非锁定技。
103 | */
104 | forced: true,
105 | /**
106 | * 是否锁定
107 | * @param {boolean} 锁定
108 | * @description
109 | * 能否被“非锁定技失效”封印
110 | */
111 | locked: true,
112 | /**
113 | * 自动确认
114 | * @param {boolean} 是否自动发动
115 | * @description
116 | * 可在游戏中自选是否自动,仅仅只是跳过询问这一流程。
117 | */
118 | frequent: true,
119 | /**
120 | * 衍生技
121 | * @param {string | string[]} 技能ID
122 | * @description
123 | * 在技能下面显示的对应技能的描述
124 | */
125 | derivation: [],
126 | charlotte: true, // 是否为锁定技
127 | vanish: true, // 一次性技能,使用resetSkills重置技能时直接移除此技能。
128 | popup: false, // 发动技能是否记录
129 | nopop: true, // 是否显示技能描述
130 | direct: true, // 是否强制发动技能且无记录
131 | skillAnimation: true, // 是否播放动画
132 | animationColor: "gray", // 动画文字颜色
133 | sourceSkill: "XXX", // 源技能,若存在,则当前技能实际id为"XXX_skill"
134 | group: ['subskill1'], // 关联子技能,持有此技能会同时视为持有子技能
135 | logTarget: "target", // 技能显示的目标
136 | mark: "auto", // 是否显示标记,同时也支持布尔值。
137 | equipSkill: true, // 是否为装备技能
138 | prompt: "XXX", // 发动技能提示
139 | filterCard: {}, // 是否需要筛选卡牌
140 | position: "h", // 指定卡牌位置
141 | filterTarget: (), // 是否需要筛选目标
142 | selectTarget: (), // 需要选择的目标数
143 | viewAs: {}, // 视为使用卡牌
144 | viewAsFilter: {}, // 视为使用条件
145 | onuse: {}, // 视为后执行的效果
146 | onremove: {}, // 失去技能后执行的效果
147 | intro: {}, // 标记内容
148 | check: {}, // AI是否发动技能(被动技)
149 | mod: {}, // 属性修改(视为锁定技)
150 | ai: {}, // AI策略
151 | // ....更多选项请查看源码
152 | }
153 | ```
154 |
155 |
156 |
157 |
158 | ## 2. 技能类型
159 |
160 | 下文仅列出部分常见技能类型,全部技能类型请查看
161 | - [3.7 技能类型概述](./chapter3-skill/3.7-skill-types.md)
162 |
163 | ### 2.1 主动技能
164 | ```javascript
165 | "my_skill": {
166 | enable: "phaseUse", // 出牌阶段使用
167 | usable: 1, // 每回合限一次
168 | filter(event, player){
169 | return player.countCards('h') > 0; // 需要有手牌
170 | },
171 | filterTarget(card, player, target){ // 此效果意为需要选择目标,返回值为数组,传参为event.targets。
172 | return target != player; // 不能选择自己
173 | },
174 | async content(event, trigger, player){
175 | await event.targets[0].damage(); // 对目标造成伤害
176 | }
177 | } // 出牌阶段限一次,你可以对一名其他角色造成1点伤害
178 | ```
179 |
180 | ### 2.2 触发技能
181 | ```javascript
182 | "trigger_skill": {
183 | trigger: {
184 | player: "phaseBegin", // 回合开始时
185 | global: "damageEnd", // 任何角色受到伤害后
186 | },
187 | forced: true, // 强制使用
188 | filter(event, player){
189 | return player.hp < 3; // 体力值小于3时触发
190 | },
191 | async content(event, trigger, player){
192 | await player.draw(); // 摸一张牌
193 | }
194 | } // 锁定技,你的回合开始时或任意角色受到伤害后,若你的体力值小于3,则你摸一张牌。
195 | ```
196 |
197 | ### 2.3 视为技能
198 | ```javascript
199 | "viewas_skill": {
200 | enable: ["chooseToUse", "chooseToRespond"], // 可以使用或打出
201 | filterCard: {color: "red"}, // 红色牌
202 | viewAs: {name: "sha"}, // 视为【杀】
203 | viewAsFilter(player){ // 视为技条件
204 | return player.countCards('h', {color: 'red'}) > 0;
205 | }, // 需要有手牌
206 | prompt: "将一张红色牌当【杀】使用或打出",
207 | ai: {
208 | respondSha: true, // 告诉AI,此技能可以用来响应杀
209 | skillTagFilter(player){
210 | return player.countCards('h', {color: 'red'}) > 0;
211 | } // 有红色的手牌时才告诉AI
212 | }
213 | } // 你可以将一张红色牌当【杀】使用或打出
214 | ```
215 |
216 | ### 2.4 锁定技
217 | ```javascript
218 | "lock_skill": {
219 | charlotte: true, // 锁定技标记
220 | trigger: {player: 'damageBegin4'},
221 | filter(event, player){
222 | return event.nature == 'fire'; // 火焰伤害
223 | },
224 | content(){
225 | trigger.cancel(); // 取消事件
226 | }
227 | } // 锁定技,你防止即将受到的火焰伤害
228 | ```
229 |
230 | ### 2.5 限定技
231 | ```javascript
232 | "limit_skill": {
233 | unique: true, // 独有技能(不会被“化身”获取)
234 | limited: true, // 限定技标记
235 | skillAnimation: true, // 播放技能动画
236 | animationColor: "fire", // 动画颜色
237 | enable: "phaseUse", // 出牌阶段使用
238 | filter(event, player){
239 | return !player.storage.limit_skill; // 未使用过
240 | },
241 | async content(event, trigger, player){
242 | player.awakenSkill('limit_skill'); // 废除此技能
243 | await player.draw(3); // 摸三张牌
244 | await player.recover(); // 回复1点体力
245 | }
246 | } // 限定技,出牌阶段,你可以摸三张牌并回复1点体力
247 | ```
248 |
249 | ## 3. 技能效果实现
250 |
251 | ### 3.1 基础效果
252 | ```javascript
253 | async content(event, trigger, player){
254 | // 摸牌
255 | await player.draw(2);
256 |
257 | // 摸至
258 | await player.drawTo(5);
259 |
260 | // 回复体力
261 | await player.recover();
262 |
263 | // 回复至
264 | await player.recoverTo(5)
265 |
266 | // 受到伤害
267 | await target.damage('fire');
268 |
269 | // 失去体力
270 | await player.loseHp();
271 |
272 | // 获得牌
273 | await player.gain(trigger.cards, 'gainAuto');
274 |
275 | // 弃置牌
276 | await player.discard(player.getCards('h'));
277 | } // 基础效果示例
278 | ```
279 |
280 | ### 3.2 选择效果
281 | ```javascript
282 | async content(event, trigger, player){
283 | // 选择角色
284 | let result = await player.chooseTarget('请选择一名角色', true).forResult();
285 | if(result.bool){
286 | let target = result.targets[0];
287 | await target.draw();
288 | }
289 |
290 | // 选择牌
291 | let cards = await player.chooseCard('h', '请选择一张手牌').forResult();
292 | if(cards.bool){
293 | await player.discard(cards.cards);
294 | }
295 |
296 | // 选择选项
297 | let choice = await player.chooseControl('选项1', '选项2')
298 | .set('prompt', '请选择一个选项')
299 | .forResult();
300 | if(choice.control === '选项1'){
301 | await player.draw();
302 | }
303 | } // 选择效果示例
304 | ```
305 |
306 | ### 3.3 条件判断
307 | ```javascript
308 | async content(event, trigger, player){
309 | // 体力值判断
310 | if(player.hp <= 2){
311 | await player.draw();
312 | }
313 |
314 | // 手牌数判断
315 | if(player.countCards('h') < 2){
316 | await player.draw(2);
317 | }
318 |
319 | // 距离判断
320 | if(player.inRange(target)){
321 | await target.damage();
322 | }
323 |
324 | // 势力判断
325 | if(target.group === 'shu'){
326 | await target.draw();
327 | }
328 | } // 条件判断示例
329 | ```
330 |
331 | ## 4. 技能标记系统
332 |
333 | ### 4.1 基础标记
334 | ```javascript
335 | "mark_skill": {
336 | mark: true, // 显示标记
337 | marktext: "标", // 标记文字
338 | intro: {
339 | content: "标记内容", // 标记描述
340 | },
341 | async content(event, trigger, player){
342 | player.addMark('mark_skill', 1); // 添加标记
343 | // 或
344 | player.removeMark('mark_skill', 1); // 移除标记
345 | }
346 | } // 标记系统示例
347 | ```
348 |
349 | ### 4.2 存储标记
350 | ```javascript
351 | "storage_skill": {
352 | init(player){
353 | player.storage.storage_skill = 0; // 初始化存储值
354 | },
355 | mark: true,
356 | intro: {
357 | content: "当前持有#个标记"
358 | },
359 | async content(event, trigger, player){
360 | player.storage.storage_skill++; // 增加存储值
361 | player.markSkill('storage_skill'); // 更新标记显示
362 | }
363 | } // 存储标记示例
364 | ```
365 |
366 | ## 5. 技能组合
367 |
368 | ### 5.1 子技能
369 | ```javascript
370 | "main_skill": {
371 | group: ["main_skill_1", "main_skill_2"], // 关联子技能
372 | subSkill: {
373 | "1": {
374 | trigger: {player: 'phaseBegin'},
375 | async content(event, trigger, player){
376 | player.draw();
377 | },
378 | },
379 | "2": {
380 | trigger: {player: 'phaseEnd'},
381 | async content(event, trigger, player){
382 | player.recover();
383 | },
384 | }
385 | }
386 | } // 回合开始时摸一张牌,回合结束时回复1点体力
387 | ```
388 |
389 | ### 5.2 技能联动
390 | ```javascript
391 | "skill_1": {
392 | trigger: {player: 'phaseBegin'},
393 | async content(event, trigger, player){
394 | player.addTempSkill('skill_2'); // 获得临时技能
395 | player.addTempSkill('skill_3',{ global: "roundStart" }); // 获得临时技能,持续到本轮结束
396 | }
397 | },
398 | "skill_2": {
399 | trigger: {player: 'damageEnd'},
400 | async content(event, trigger, player){
401 | player.draw();
402 | }
403 | } // 回合开始时获得临时技能,受到伤害后摸一张牌
404 | ```
405 |
406 | ## 练习题
407 |
408 | 1. 创建一个触发技能:
409 | - 回合开始时触发
410 | - 可以选择摸牌或回复体力
411 |
412 |
413 | 参考答案 | 🟩 Easy
414 |
415 | ```javascript
416 | "trigger_example": {
417 | usable: 1,
418 | trigger: {player: 'phaseBegin'},
419 | async content(event, trigger, player){
420 | // 选择效果
421 | let choice = await player.chooseControl('摸两张牌', '回复1点体力')
422 | .set('prompt', '请选择一个效果')
423 | .set('ai', function(){ // AI策略
424 | if(player.hp <= 2) return '回复1点体力'; // 血量较低时选择回血
425 | return '摸两张牌'; // 否则选择摸牌
426 | }).forResult();
427 | // 执行效果
428 | if(choice.control == '摸两张牌'){
429 | await player.draw(2);
430 | } else {
431 | await player.recover();
432 | }
433 | },
434 | } // 每回合限一次,回合开始时,你可以选择以下一项:1.摸两张牌;2.回复1点体力。
435 | ```
436 |
437 |
438 | 2. 创建一个主动技能:
439 | - 出牌阶段限一次
440 | - 弃置一张牌并指定一名角色
441 | - 目标角色受到1点伤害
442 |
443 |
444 | 参考答案 | 🟩 Easy
445 |
446 | ```javascript
447 | "active_example": {
448 | enable: "phaseUse",
449 | usable: 1,
450 | filter(event, player){
451 | return player.countCards('h') > 0;
452 | },
453 | filterTarget(card, player, target){
454 | return target != player;
455 | },
456 | filterCard: true, // 持有此效果意为需要选择牌
457 | position: "h",
458 | async content(event, trigger, player){
459 | await event.targets[0].damage();
460 | },
461 | ai: {
462 | order: 7,
463 | result: {
464 | target(player, target){ // AI选人逻辑,正数选队友,负数选敌方,0不选。
465 | var att = get.attitude(player, target) // 持有人对目标的态度,负数为敌方,正数为友方。
466 | if(target.hp == 1) att = att * 2; // 若角色血量为1,则优先级更高
467 | if(att < 0) return att; // 若为敌方,则使用技能。
468 | }
469 | },
470 | expose: 0.2
471 | }
472 | } // 出牌阶段限一次,你可以弃置一张手牌并对一名其他角色造成1点伤害。
473 | ```
474 |
475 |
476 | 3. 创建一个复杂技能:
477 | - 包含触发和主动两部分
478 | - 使用技能标记系统
479 | - 实现技能联动
480 |
481 |
482 | 参考答案 | 🟨 Medium
483 |
484 | ```javascript
485 | "complex_example": {
486 | // 初始化标记
487 | init(player){
488 | player.setMark('complex_example', 0);
489 | },
490 | // 标记显示
491 | mark: true,
492 | intro: {
493 | content: '当前标记数:#'
494 | },
495 | // 主动部分
496 | enable: "phaseUse",
497 | usable: 1,
498 | filter(event, player){
499 | return player.countMark('complex_example') > 0;
500 | },
501 | async content(event, trigger, player){
502 | // 消耗标记发动效果
503 | player.removeMark('complex_example', 1);
504 |
505 | // 选择目标造成伤害
506 | let result = await player.chooseTarget('选择一名目标角色').forResult();
507 | if(result.bool){
508 | await result.targets[0].damage();
509 | }
510 | },
511 | // 触发部分
512 | group: ['complex_example_damage'],
513 | // 子技能
514 | subSkill: {
515 | damage: {
516 | trigger: {player: 'damageEnd'},
517 | forced: true,
518 | async content(event, trigger, player){
519 | // 受到伤害获得标记
520 | player.addMark('complex_example', trigger.num); // 获得标记
521 | },
522 | }
523 | },
524 | // AI策略
525 | ai: {
526 | order: 6, // 使用技能的优先级。
527 | result: {
528 | target: -1
529 | },
530 | threaten: 1.5 // 威胁度,AI会优先激活威胁度最高的角色。
531 | }
532 | } // 锁定技,当你受到伤害后,你获得相同个数的"示例"标记。出牌阶段限一次,你可以移去一个"示例"标记并对一名角色造成1点伤害。
533 | ```
534 |
535 |
536 | 下一节我们将学习技能的触发器。
537 |
--------------------------------------------------------------------------------
/ai.md:
--------------------------------------------------------------------------------
1 | # 6.1 AI设计
2 |
3 | ## 1. AI系统概述
4 |
5 | 无名杀的AI系统主要包括:
6 | - 基础判断
7 | - 行为决策
8 | - 目标选择
9 | - 卡牌评估
10 | - 技能策略
11 |
12 | ## 2. 基础判断
13 |
14 | ### 2.1 态度判断
15 | ```javascript
16 | skill: {
17 | "ai_skill": {
18 | // 基础态度判断
19 | ai: {
20 | threaten: 1.5, // 威胁度(默认为1)
21 | effect: { // 对出牌的影响
22 | /*
23 | * 自身作为目标时的影响,会影响其他角色AI的出牌策略。
24 | * 可用 A ,[A,B],[A,B,C,D]格式
25 | * A:基础值 × 系数A
26 | * [A,B]:基础值 × 系数A + B
27 | * [A,B,C,D]:对你的影响:基础值 × 系数A + B | 对使用者的影响:基础值 × 系数C + D
28 | */
29 | target(card, player, target){
30 | if(get.tag(card, 'damage')) return 1.5; // 其他AI倾向于对持有者使用伤害牌
31 | }
32 | /*
33 | * 自身使用牌时的影响,会影响自身AI的出牌策略。
34 | * 可用 A ,[A,B],[A,B,C,D]格式
35 | * A:基础值 × 系数A
36 | * [A,B]:基础值 × 系数A + B
37 | * [A,B,C,D]:对你的影响:基础值 × 系数A + B | 对被使用者的影响:基础值 × 系数C + D
38 | */
39 | player (card, player, target) {
40 | if (get.tag(card, 'damage')) return [1,1]; // 你倾向于使用伤害牌
41 | }
42 | }
43 | }
44 | }
45 | }
46 | ```
47 |
48 | ### 2.2 形势判断
49 | ```javascript
50 | ai: {
51 | // 价值判断
52 | value(card, player){
53 | if(card.name == 'tao' && player.hp <= 2) return 8; // 濒死状态桃价值高
54 | if(card.name == 'shan' && player.hp == 1) return 7; // 濒死状态闪价值高
55 | return get.value(card); // 默认价值
56 | }
57 | }
58 | ```
59 |
60 | ### 2.3 AI 优先级
61 | ```javascript
62 | ai: {
63 | // 技能使用优先级(1-10)
64 | order(item, player){
65 | if(player.hp < 2) return 10; // 濒死优先使用
66 | if(player.hasSkill('skill_name')) return 8; // 配合其他技能
67 | return 4; // 默认优先级
68 | },
69 |
70 | // 发动技能的收益评估
71 | result: { // 二者选一个即可
72 | player(player){ // 对自身的收益,正数使用,负数取消
73 | if(player.hp < 2) return 2; // 濒死收益加倍
74 | return 1;
75 | },
76 | target(player, target){ // 对目标的收益,正数选友方,负数选敌方
77 | if(target.hp == 1) return -2; // 目标濒死收益加倍
78 | return -1;
79 | }
80 | },
81 |
82 | // 特殊标签
83 | tag: {
84 | recover: 0.5, // 回复技能
85 | gain: 1, // 摸牌技能
86 | multitarget: 1, // 多目标技能
87 | // ....
88 | }
89 | }
90 | ```
91 |
92 | ## 3. 行为决策
93 |
94 | ### 3.1 技能使用
95 | ```javascript
96 | "ai_skill": {
97 | enable: "phaseUse",
98 | filter(event, player){
99 | return player.countCards('h') > 0;
100 | },
101 | check(event, player){ // 为True发动,为False取消
102 | // 基础判断
103 | if(player.hp <= 2) return 0; // 生命危险不发动
104 |
105 | // 形势判断
106 | var enemies = game.countPlayer(function(current){
107 | return get.attitude(player, current) < 0;
108 | });
109 | if(enemies <= 1) return 0; // 敌人太少不发动
110 |
111 | // 收益判断
112 | if(player.countCards('h') >= 4) return 1; // 手牌充足可发动
113 | return 0; // 默认不发动
114 | },
115 | ai: {
116 | order: 7, // 发动优先级
117 | result: {
118 | player(player){
119 | if(player.hp <= 2) return -1; // 生命危险收益为负
120 | return 1; // 默认正收益
121 | },
122 | target(player, target){
123 | return get.damageEffect(target, player, player) > 0 ? -1 : 0; // 伤害收益
124 | }
125 | }
126 | }
127 | }
128 | ```
129 |
130 | ### 3.2 目标选择
131 | ```javascript
132 | "target_skill": {
133 | enable: "phaseUse",
134 | filterTarget(card, player, target){
135 | return target != player;
136 | },
137 | ai: {
138 | // 目标价值判断
139 | result: {
140 | target(player, target){
141 | // 基础态度
142 | var att = get.attitude(player, target);
143 | if(att > 0) return 0; // 不选择友方
144 |
145 | // 目标状态
146 | if(target.hp == 1) return -2; // 濒死目标优先
147 | if(target.countCards('h') <= 2) return -1.5; // 手牌少目标优先
148 |
149 | // 威胁判断
150 | if(target.hasSkillTag('threaten')) return -1.2; // 威胁目标优先
151 |
152 | return -1; // 默认价值
153 | }
154 | },
155 |
156 | // 目标排序
157 | expose: 0.2, // 身份暴露度
158 | threaten: 1.5, // 威胁度
159 | }
160 | }
161 | ```
162 |
163 | ## 4. 卡牌评估
164 | ```javascript
165 | ai: {
166 | basic: {
167 | // 基本牌评估
168 | useful: [4, 1],
169 |
170 | // 装备评估
171 | equipValue(card, player){
172 | if(card.name == 'bagua') return 5; // 八卦阵价值
173 | return 3; // 默认装备价值
174 | }
175 | }
176 | }
177 | ```
178 |
179 | ## 5. 技能策略
180 |
181 | ### 5.1 主动技能
182 | ```javascript
183 | "active_skill": {
184 | enable: "phaseUse",
185 | usable: 1,
186 | ai: {
187 | // 发动优先级
188 | order(item, player){
189 | if(player.hp <= 2) return 10; // 生命危险优先发动
190 | return 4; // 默认优先级
191 | },
192 |
193 | // 发动条件
194 | result: {
195 | player(player){
196 | // 收益评估
197 | var benefit = 0;
198 | if(player.hp <= 2) benefit += 2; // 生命危险收益高
199 | if(player.countCards('h') <= 2) benefit += 1; // 缺牌收益高
200 | return benefit;
201 | }
202 | }
203 | }
204 | }
205 | ```
206 |
207 | ### 5.2 触发技能
208 | ```javascript
209 | "trigger_skill": {
210 | trigger: {player: 'damageEnd'},
211 | filter(event, player){
212 | return player.countCards('h') > 0;
213 | },
214 | check(event, player){
215 | // 触发收益判断
216 | if(player.hp <= 2) return true; // 生命危险必定发动
217 | if(player.countCards('h') >= 4) return false; // 手牌多不发动
218 | return true; // 默认发动
219 | },
220 | ai: {
221 | // 收益效果
222 | effect: {
223 | target(card, player, target){
224 | if(get.tag(card, 'damage')){
225 | if(player.hasSkillTag('jueqing')) return [1,-2];
226 | if(target.hp <= 1) return [1,0,0,-2];
227 | return [1,0,0,-1.5];
228 | }
229 | }
230 | },
231 |
232 | // 触发优先级
233 | threaten: 0.8
234 | }
235 | }
236 | ```
237 |
238 | ## 6.特殊标签
239 | ```javascript
240 | ai: {
241 | // 技能标签的生效限制条件
242 | skillTagFilter(player, tag, target) {
243 | if (player == target && tag == "viewHandcard") return false;
244 | }, // 可看见除自己外所有人的手牌
245 |
246 | /*
247 | * 实际效果标签
248 | */
249 | combo: "XXX", // 组合技,持有XXX时收益增加
250 | directHit_ai: true, // 可强中
251 | filterDamage: true, // 伤害减免
252 | fireAttack: true, // 可造成火属性伤害
253 | freeShan: true, // 无消耗的【闪】
254 | guanxing: true, // 可观星
255 | halfneg: true, // 半负面技
256 | ignoreSkill: true // 忽略技能检测
257 | jiuOther: true, // 可响应【酒】
258 | left_hand: true, // 逆时针计算距离
259 | maixie_defend: true, // 卖血防御技
260 | maixie_hp: true, // 优先回血
261 | maixie: true, // 卖血技
262 | neg: true, // 负面技,强制发动
263 | noautowuxie: true, // 无法自动无懈
264 | noCompareSource: true, // 无法发起拼点
265 | noCompareTarget: true, // 无法作为拼点目标
266 | nodamage: true, // 不受伤害
267 | nodiscard: true, // 弃置牌无收益
268 | nodu: true, // 不受毒影响
269 | noe: true, // 失去装备时正收益
270 | nofire: true, // 不受火焰伤害
271 | nogain: true, // 获得牌无收益
272 | noh: true, // 没有手牌时正收益
273 | nohujia: true, // 无视护甲
274 | noLink: true, // 不能被横置
275 | nolose: true, // 失去牌无收益
276 | nomingzhi: true, // 无法明置
277 | norespond: true, // 无法响应牌
278 | noShan: true, // 不用【闪】
279 | nothunder: true, // 不受雷电伤害
280 | notrick: true, // 免疫锦囊
281 | notricksource: true, // 免疫锦囊牌造成的伤害
282 | noTurnover: true, // 不能被翻面
283 | playernowuxie: true, // 不响应无懈
284 | rejudge: true, // 可修改判定
285 | respondSha: true, // 可响应【杀】
286 | respondShan: true, // 可响应【闪】
287 | respondTao: true, // 可响应【桃】
288 | reverseEquip: true, // 反转装备优先级
289 | right_hand: true, // 顺时针计算距离
290 | save: true, // 濒死状态可救人
291 | undist: true, // 自身不计入距离
292 | unequip_ai: true, // 可无视护甲
293 | unequip: true, // 无视防具
294 | unequip2: true, // 自身防具无效
295 | usedu: true, // 使用毒有收益
296 | useShan: true, // 【闪】能用则用
297 | viewHandcard: true, // 可看见其他角色的手牌
298 | }
299 | ```
300 |
301 | ## 练习
302 |
303 | 1. 创建一个基础AI:
304 | - 实现基本判断
305 | - 添加目标选择
306 | - 设计使用策略
307 |
308 |
309 | 参考答案 | 🟩 Easy
310 |
311 | ```javascript
312 | "basic_ai": {
313 | enable: "phaseUse",
314 | usable: 1,
315 | filterTarget(card, player, target){
316 | return target != player;
317 | },
318 | filter(event, player){
319 | return player.countCards('h') > 0;
320 | },
321 | async content(event, trigger, player){
322 | await target.damage();
323 | },
324 | ai: {
325 | // 基础判断
326 | order(item, player){
327 | if(player.hp <= 2) return 0; // 生命危险时不发动
328 | return 4;
329 | },
330 |
331 | // 目标选择
332 | result: {
333 | target(player, target){
334 | // 基础态度判断
335 | let attitude = get.attitude(player, target);
336 | if(attitude > 0) return 0; // 不选择友方
337 |
338 | // 目标状态判断
339 | if(target.hp == 1) return 2; // 濒死目标优先
340 | if(target.hasSkillTag('threaten')) return 1.5; // 威胁目标优先
341 |
342 | return -1;
343 | }
344 | },
345 |
346 | // 使用策略
347 | effect: {
348 | target(card, player, target){
349 | if(get.tag(card, 'damage')){
350 | if(player.hasSkillTag('jueqing')) return [1,-2];
351 | if(target.hp == 1) return [1,0,0,-2];
352 | return [1,0,0,-1.5];
353 | }
354 | }
355 | },
356 |
357 | // 威胁度
358 | threaten: 1.2
359 | }
360 | }
361 | ```
362 |
363 |
364 | 2. 创建一个技能AI:
365 | - 设计发动条件
366 | - 实现目标选择
367 | - 添加收益判断
368 |
369 |
370 | 参考答案 | 🟩 Easy
371 |
372 | ```javascript
373 | "skill_ai": {
374 | trigger: {player: 'phaseBegin'},
375 | direct: true,
376 | filter(event, player){
377 | return player.countCards('h') > 1;
378 | },
379 | async content(event, trigger, player){
380 | // 发动条件判断
381 | let check = false;
382 | let enemies = game.filterPlayer(function(current){
383 | return get.attitude(player, current) < 0;
384 | });
385 |
386 | if(player.hp <= 2 && enemies.length >= 2){
387 | check = true; // 生命危险且敌人多时发动
388 | }
389 |
390 | let prompt = '是否发动【技能名】?';
391 | let next = player.chooseBool(prompt).forResult();
392 | next.set('ai', function(){
393 | return check;
394 | });
395 |
396 | if(next.bool){
397 | player.logSkill('skill_ai');
398 |
399 | // 目标选择
400 | let next = player
401 | .chooseTarget(2, '选择两名目标', function(card, player, target){
402 | return target != player;
403 | })
404 | .set('ai', function(target){
405 | let player = _status.event.player;
406 | let att = get.attitude(player, target);
407 |
408 | // 收益判断
409 | if(att < 0){
410 | if(target.hp == 1) return 10; // 濒死敌人优先
411 | if(target.hasSkillTag('threaten')) return 8; // 威胁目标优先
412 | return 6;
413 | }
414 | return 0;
415 | })
416 | .forResult();
417 | }
418 |
419 | if(next.bool && next.targets && next.targets.length == 2){
420 | let targets = next.targets;
421 | for(let target of targets){
422 | await target.damage();
423 | }
424 | }
425 | },
426 | ai: {
427 | expose: 0.3, // 暴露程度
428 | threaten: 1.5 // 威胁度
429 | }
430 | }
431 | ```
432 |
433 |
434 | 3. 创建一个复杂AI:
435 | - 实现动态策略
436 | - 添加场景判断
437 | - 优化性能
438 |
439 |
440 | 参考答案 | 🟨 Medium
441 |
442 | ```javascript
443 | "complex_ai": {
444 | // 缓存计算结果
445 | init(player){
446 | player.storage.aiCache = {
447 | situation: null,
448 | lastUpdate: 0
449 | };
450 | },
451 |
452 | // 场景评估
453 | getSituation(player){
454 | let now = get.utc();
455 | if(!player.storage.aiCache.situation ||
456 | now - player.storage.aiCache.lastUpdate > 1000){
457 |
458 | let situation = 0;
459 | // 我方状态
460 | situation += player.hp;
461 | situation += player.countCards('h');
462 |
463 | // 敌方状态
464 | game.countPlayer(function(current){
465 | if(get.attitude(player, current) < 0){
466 | situation -= current.hp;
467 | situation -= current.countCards('h');
468 | }
469 | });
470 |
471 | player.storage.aiCache.situation = situation;
472 | player.storage.aiCache.lastUpdate = now;
473 | }
474 | return player.storage.aiCache.situation;
475 | },
476 |
477 | enable: "phaseUse",
478 | usable: 1,
479 | filter(event, player){
480 | return player.countCards('h') > 0;
481 | },
482 | async content(event, trigger, player){
483 | // 动态策略选择
484 | let situation = lib.skill.complex_ai.getSituation(player);
485 | let strategy;
486 |
487 | if(situation > 5){
488 | strategy = 'aggressive'; // 优势时进攻
489 | } else if(situation < -5){
490 | strategy = 'defensive'; // 劣势时防守
491 | } else {
492 | strategy = 'neutral'; // 均势时中庸
493 | }
494 |
495 | // 根据策略选择效果
496 | let choices = ['效果1', '效果2', '效果3'];
497 | let next = player
498 | .chooseControl(choices)
499 | .set('ai', function(){
500 | switch(strategy){
501 | case 'aggressive':
502 | return '效果1';
503 | case 'defensive':
504 | return '效果2';
505 | default:
506 | return '效果3';
507 | }
508 | })
509 | .forResult();
510 |
511 | // 执行选择的效果
512 | switch(next.control){
513 | case '效果1':
514 | let target = await player.chooseTarget(
515 | function(card, player, target){
516 | return target != player;
517 | }
518 | ).set('ai', function(target){
519 | return -get.attitude(player, target);
520 | })
521 | .forResult();
522 | if(target.bool){
523 | await target.targets[0].damage();
524 | }
525 | break;
526 |
527 | case '效果2':
528 | await player.draw(2);
529 | break;
530 |
531 | case '效果3':
532 | await player.recover();
533 | break;
534 | }
535 | },
536 | ai: {
537 | // 动态优先级
538 | order(item, player){
539 | let situation = lib.skill.complex_ai.getSituation(player);
540 | if(situation > 5) return 8;
541 | if(situation < -5) return 4;
542 | return 6;
543 | },
544 |
545 | // 动态收益
546 | result: {
547 | player(player){
548 | let situation = lib.skill.complex_ai.getSituation(player);
549 | if(situation > 5) return 2;
550 | if(situation < -5) return 0.5;
551 | return 1;
552 | }
553 | },
554 |
555 | // 威胁度
556 | threaten(player, target){
557 | let situation = lib.skill.complex_ai.getSituation(player);
558 | if(situation > 5) return 2;
559 | return 1;
560 | }
561 | }
562 | }
563 | ```
564 |
565 |
566 | 下一节我们将学习联机适配。
567 |
--------------------------------------------------------------------------------
/appendix/enery-skill.md:
--------------------------------------------------------------------------------
1 | # 附录C:充能条技能示例
2 |
3 | ## 1. 效果展示
4 | - 描述完善
5 | - 名称、颜色均可自定义
6 | - 支持UI、标记两种模式
7 | - 支持个别角色关闭
8 | - 支持上限修改、获取方式修改
9 |
10 | 图片展示
11 |
12 | 
13 | 
14 | 
15 | 
16 | 
17 |
18 |
19 |
20 | ## 2. 使用示例
21 | 举个例子:
22 |
23 | 想要实现技能效果:```当你受到伤害后,获得5点怒气。你无法通过除此以外的效果获得怒气。```
24 |
25 | 那就创建一个技能,技能效果为受伤时触发``` player: "damageEnd" ```
26 |
27 | 初始化```player.zmld```,然后在效果中使用```game.changeNp()```来获取能量即可!
28 |
29 | 示例:
30 |
31 | ```javascript
32 | nuqi: {
33 | trigger: {
34 | player: "damageEnd"
35 | },
36 | forced: true,
37 | init(player, skill) {
38 | player.zmld = {
39 | Skip: ["gameStart", "useCardBegin", "gainBegin", "phaseBeginStart", "damageBegin"], // 跳过 游戏开始时、回合开始时、使用牌时、获得牌时、造成伤害、受到伤害时的能量回复
40 | Name: "怒气条", // 充能条名称为
41 | Color: "#DC143C", // 颜色为:猩红色,支持渐变色
42 | Max: 15, // 充能条上限
43 | Np: 2, // 充能起始值
44 | }
45 | },
46 | content () {
47 | "step 0"
48 | game.changeNp(5) // 获得五点充能,获取历史显示为“因【怒气】:+5”,可以自定义原因
49 | "step 1"
50 | if (player.zmld.Np >= player.zmld.Max) {
51 | game.changeNp(-10, "怒火攻心") // 怒气值不小于上限时,扣除10点充能
52 | game.changeNp(-5, "怒火焚天", trigger.source) // 扣除攻击者5点能量
53 | }
54 | }
55 | }
56 | ```
57 | ### 图片:
58 | #### 初始能量条:
59 | 
60 | #### 历史记录:
61 | 
62 | #### 对其他人能量条无变化:
63 | 
64 | #### 对其他人能量条修改:
65 | 
66 | #### 支持渐变色:
67 | ```javascript
68 | Color: "linear-gradient(90deg, #c01c28 0%, #ff7800 50%, #ffb380 100%, #c01c28 200%)"
69 | ```
70 | 
71 |
72 |
73 | ## 3. 技能代码
74 |
75 | - 此代码仅提供使用方法注释,对于实现方式不提供注释
76 |
77 | ```javascript
78 | /* ================================== 充能实现 ==============================
79 | * 问题反馈:Q 1337515813
80 | * 请分别修改:historyLimit、event.style函数。
81 | * 其中,historyLimit为历史记录长度,为0时不显示。
82 | * event.style为能量显示类型,若为1,则以UI形式显示,若为2,则以标记形式显示。
83 | * 若仅需部分角色使用,请为lib.skill._zmld_np添加filter
84 | *
85 | 支持参数:
86 | * 修改玩家充能
87 | * @param {Object} player - 修改对象,默认为自身
88 | * @param {number} number - 修改数值,支持正负。
89 | * @param {string} reason - 修改原因,不填则采用事件名。
90 | *
91 | game.changeNp(): 充能修改
92 | game.changeMaxNp(): 充能上限修改
93 |
94 | 数据获取:
95 | player.zmld.Np: 当前充能
96 | player.zmld.Max: 充能最大值
97 | player.zmld.History: 充能历史
98 | player.zmld.Gained: 累计获得
99 | player.zmld.Lost: 累计失去
100 | 数据修改:
101 | player.zmld.Name: 充能名称
102 | player.zmld.Color: 充能颜色
103 | player.zmld.Enable: 是否启用
104 | player.zmld.Image: 标记显示下,采用图片展示能量。
105 |
106 | * 角色充能跳过规则
107 | * 用于声明当前角色跳过的充能事件集合,支持两种配置方式:
108 | *
109 | * 1. 函数式条件 (Function)
110 | * - 命名规则:函数名必须与目标事件名称严格一致(如'useCardBegin'对应同名事件)
111 | * - 返回值:返回 true 时跳过充能,false 则正常执行
112 | * - 示例:检测到使用筹码卡时跳过充能
113 | * player.zmld.Skip.push(function useCardBegin() {
114 | * return this.cards.some(c => c.gaintag.includes('zm_chouma'))
115 | * })
116 | * // 对应事件自动传参,使用this调用。
117 | * 2. 字符串直接匹配 (String)
118 | * - 完全匹配事件名称时强制跳过
119 | * - 示例:无条件跳过'gameStart'和技能'wusheng'事件
120 | * player.zmld.Skip.push(['gameStart',"wusheng"])
121 | * 可用事件: gameStart、phaseBeginStart、useCardBegin、damageBegin、gainBegin
122 | *
123 | * 示例:
124 | * init(player){
125 | * player.zmld = {
126 | * Skip : [
127 | * "wusheng",
128 | * "gameStart",
129 | * "gainBegin",
130 | * function useCardBegin(){
131 | * if(this.card.name =="sha") return true
132 | * if(this.cards.some(card=> card.gaintag.includes("dc_shangyu"))) return true
133 | * return false
134 | * }]
135 | * }
136 | * }
137 | * // 效果:游戏开始、获得牌时、使用技能武圣时、使用“杀”时、使用“赏誉”牌时,无法获得能量。
138 | *
139 | player.zmld.Skip:[]
140 | */
141 | game.NpContent = function (player) {
142 | if (player == undefined) player = _status.event.player;
143 | let historyHtml = '';
144 | let historyLimit = lib.config?.extension_综漫乱斗_zmld_np_history || 10;
145 | if (historyLimit > 0) {
146 | let history = player.zmld?.History || [];
147 | if (history.length > 0) {
148 | historyHtml = '最近变化:
' +
149 | history.slice(0, historyLimit).map(record => {
150 | let color = record.change > 0 ? (lib.config.menu_style == "wood" ? "#1E8449" : "#33d17a") : (lib.config.menu_style == "wood" ? "#CC0000" : "#ff6b6b");
151 | let sign = record.change > 0 ? '+' : '';
152 | return `• ${record.reason}: ${sign}${record.change}
`;
153 | }).join('');
154 | }
155 | }
156 |
157 | const totalGained = player.zmld?.Gained || 0;
158 | const totalLost = player.zmld?.Lost || 0;
159 |
160 | return `
161 | 当前充能:${player.zmld?.Np}/${player.zmld?.Max}
162 | 累计获得:${totalGained}
163 | 累计失去:${totalLost}
164 | ${historyHtml}
165 | 获取方式:
166 | • 游戏开始时获得15点能量
167 | • 回合开始时获得5点能量
168 | • 使用牌时获得1点能量
169 | • 获得牌时获得等量能量
170 | • 造成伤害时获得等量能量
171 | • 受到伤害时获得等量能量
172 | • 部分角色技能可获取能量
173 | `;
174 | }
175 |
176 | game.changeNp = function () {
177 | for (var i = 0; i < arguments.length; i++) {
178 | if (typeof arguments[i] === 'number') var change = arguments[i];
179 | else if (typeof arguments[i] === "string") var reason = arguments[i];
180 | else if (typeof arguments[i] === "object") var player = arguments[i];
181 | }
182 | if (player == undefined) player = _status.event.player;
183 | if (!change) return false;
184 | if (player.zmld?.Skip.length > 0) {
185 | let match = player.zmld?.Skip.find((func) =>
186 | (typeof func == 'function' ? func.name : func) == (_status.event.getParent(1).skill == "_zmld_np" ? _status.event.getParent(1).triggername : _status.event.getParent(1).skill)
187 | )
188 | if (typeof match == 'function') {
189 | if (match.call(_status.event.getParent(3))) {
190 | get.event().trigger('np_change');
191 | return false
192 | }
193 | }
194 | else if (typeof match == 'string') {
195 | get.event().trigger('np_change');
196 | return false
197 | };
198 | }
199 | const currentNp = player.zmld.Np || 0;
200 | const maxNp = player.zmld.Max || 100;
201 | const newNp = currentNp + change;
202 |
203 | if (currentNp >= maxNp && change > 0) return false;
204 |
205 | if (!reason) {
206 | let eventName = get.translation(_status.event.name || '未知来源');
207 | reason = `因【${eventName}】`;
208 | }
209 |
210 | game.broadcastAll(function (player, change, reason, newNp) {
211 | if (!player.zmld.Gained) player.zmld.Gained = 0;
212 | if (!player.zmld.Lost) player.zmld.Lost = 0;
213 | if (!player.zmld.History) player.zmld.History = [];
214 |
215 | if (change > 0) {
216 | const actualGain = Math.min(change, player.zmld.Max - player.zmld.Np);
217 | player.zmld.Gained += actualGain;
218 | } else {
219 | player.zmld.Lost += Math.abs(change);
220 | }
221 |
222 | player.zmld.Np = Math.max(0, Math.min(newNp, player.zmld.Max));
223 |
224 | player.zmld.History.unshift({
225 | change: change,
226 | reason: reason
227 | });
228 | if (player.zmld.History.length > 10) {
229 | player.zmld.History.pop();
230 | }
231 |
232 | get.event().trigger('np_change');
233 | }, player, change, reason, newNp);
234 | return true
235 | }
236 |
237 | game.changeMaxNp = function () {
238 | for (var i = 0; i < arguments.length; i++) {
239 | if (typeof arguments[i] === 'number') var change = arguments[i];
240 | else if (typeof arguments[i] === "string") var reason = arguments[i];
241 | else if (typeof arguments[i] === "object") var player = arguments[i];
242 | }
243 | if (player == undefined) player = _status.event.player;
244 | if (!change) return false;
245 | if (change < 0) change = 0;
246 | if (change === player.zmld.Max) return false;
247 |
248 | if (!reason) {
249 | let eventName = get.translation(_status.event.name || '未知来源');
250 | reason = eventName.includes('【') ? eventName : `因【${eventName}】`;
251 | }
252 |
253 | game.broadcastAll(function (player, change, reason) {
254 | let oldMax = player.zmld.Max || 100;
255 | player.zmld.Max = change;
256 | if (player.zmld.Np >= player.zmld.Max) {
257 | player.zmld.Np = player.zmld.Max;
258 | }
259 |
260 | if (!player.zmld.History) {
261 | player.zmld.History = [];
262 | }
263 |
264 | let diff = change - oldMax;
265 | if (diff !== 0) {
266 | player.zmld.History.unshift({
267 | change: diff,
268 | reason: `${reason}上限${diff > 0 ? '增加' : '减少'}`
269 | });
270 | if (player.zmld.History.length > 10) {
271 | player.zmld.History.pop();
272 | }
273 | }
274 |
275 | get.event().trigger('np_change');
276 | }, player, change, reason);
277 | return true
278 | }
279 |
280 | lib.skill._zmld_np = {
281 | trigger: {
282 | global: ["gameStart"],
283 | player: ["phaseBeginStart", "useCardBegin", "damageBegin", "gainBegin", "np_change"],
284 | source: "damageBegin"
285 | },
286 | marktext: "Np",
287 | intro: {
288 | name: `
289 |
290 |
${_status.event.player?.zmld.Name || "能量条"}
291 |
用于《综漫乱斗》扩展
292 |
293 | `,
294 | markcount (storage, player) {
295 | return player.zmld.Np;
296 | },
297 | content (storage, player) {
298 | return game.NpContent();
299 | },
300 | ...{ ..._status.event.player?.zmld.Image ? { markimage: player.zmld.Image } : {} },
301 | },
302 | lastDo: true,
303 | forced: true,
304 | popup: false,
305 | silent: true,
306 | fixed: true,
307 | superCharlotte: true,
308 | create(player) {
309 | game.broadcastAll(function (player) {
310 | let double = player.classList.contains('fullskin2') && lib.config.layout !== 'long2';
311 | const width = player.node.avatar.clientWidth;
312 | let w = width * (double ? 2 : 1);
313 | const bar = ui.create.div();
314 | bar.className = 'energy-bar';
315 |
316 | const isMobile = lib.device == 'android' || lib.device == 'ios';
317 | const heightMultiplier = isMobile ? 0.15 : 0.1;
318 | const topOffset = lib.config.extension_综漫乱斗_zmld_ui_top != 0 ? lib.config.extension_综漫乱斗_zmld_ui_top : isMobile ? 0.2 : 0.15;
319 |
320 | bar.style.cssText = `
321 | z-index: 3;
322 | width: ${w * 1.05}px;
323 | height: ${w * heightMultiplier}px;
324 | position: absolute;
325 | top: ${w * -topOffset}px;
326 | border: 2px solid rgba(0, 0, 0, 0.9);
327 | border-radius: ${w * 0.05}px;
328 | background: rgba(0, 0, 0, 0.6);
329 | overflow: hidden;
330 | box-sizing: border-box;
331 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
332 | `;
333 | bar.setNodeIntro(
334 | `
335 |
336 |
${player.zmld.Name}
337 |
用于《综漫乱斗》扩展
338 |
339 | `,
340 | game.NpContent(player)
341 | );
342 | const fill = ui.create.div();
343 | fill.className = 'energy-fill';
344 | fill.style.cssText = `
345 | width: 0%;
346 | height: 100%;
347 | position: absolute;
348 | left: 0;
349 | top: 0;
350 | transition: all 0.8s cubic-bezier(0.22, 1, 0.36, 1);
351 | opacity: 1;
352 | background-size: 200% 100%;
353 | `;
354 | const label = ui.create.div();
355 | label.className = 'energy-label';
356 | label.style.cssText = `
357 | position: absolute;
358 | width: 100%;
359 | height: 100%;
360 | display: flex;
361 | align-items: center;
362 | justify-content: center;
363 | color: #ffffff;
364 | font-size: ${w * 0.08}px;
365 | font-weight: bold;
366 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
367 | z-index: 1;
368 | `;
369 | bar.appendChild(fill);
370 | bar.appendChild(label);
371 | player.appendChild(bar);
372 | }, player);
373 | },
374 | content () {
375 | 'step 0'
376 | event.style = lib.config?.extension_综漫乱斗_zmld_np || 1
377 | if (event.triggername == 'gameStart') {
378 | game.broadcastAll(function (player) {
379 | player.zmld = {
380 | Np: player.zmld?.Np || 0,
381 | Max: player.zmld?.Max || 100,
382 | History: player.zmld?.History || null,
383 | Gained: player.zmld?.Gained || null,
384 | Lost: player.zmld?.Lost || null,
385 | Enable: player.zmld?.Enable ?? true,
386 | Skip: player.zmld?.Skip || [],
387 | Name: player.zmld?.Name || "能量条",
388 | Color: player.zmld?.Color || null
389 | }
390 | }, player);
391 | if (!player.zmld.Enable) return;
392 | if (!player.hasSkill('subplayer')) {
393 | if (event.style == '1') {
394 | lib.skill._zmld_np.create(player)
395 | }
396 | else if (event.style == '2') {
397 | player.updateMark("_zmld_np");
398 | player.markSkill("_zmld_np");
399 | }
400 | }
401 | }
402 | 'step 1'
403 | if (event.triggername != 'np_change') {
404 | let change = 0;
405 | let reason = '';
406 | if (trigger && trigger.skill) {
407 | let skillName = get.translation(trigger.skill);
408 | skillName = skillName.includes('【') ? skillName : `【${skillName}】`;
409 | reason = `因${skillName}`;
410 | }
411 | else if (event.triggername == "gainBegin") {
412 | change = trigger.cards.length;
413 | let source = trigger.source || '系统';
414 | if (typeof source === 'object' && source.name) {
415 | source = get.translation(source.name);
416 | }
417 | reason += source === '系统' ? '获得牌' : `从${source}获得牌`;
418 | }
419 | else if (event.triggername == "phaseBeginStart") {
420 | change = 5;
421 | reason += '回合开始';
422 | }
423 | else if (event.triggername == "damageBegin") {
424 | change = trigger.num;
425 | if (player == trigger.source) {
426 | let target = trigger.player;
427 | reason += `对${get.translation(target.name)}造成伤害`;
428 | } else {
429 | let source = trigger.source;
430 | reason += source ? `受到${get.translation(source.name)}的伤害` : '受到伤害';
431 | }
432 | }
433 | else if (event.triggername == "useCardBegin") {
434 | change = 1;
435 | let cardName = get.translation(trigger.card.name);
436 | reason += `使用【${cardName}】`;
437 | }
438 | else if (event.triggername == "gameStart") {
439 | change = 15;
440 | reason += '游戏开始';
441 | }
442 |
443 | game.changeNp(player, change, reason);
444 | event.finish();
445 | } else {
446 | game.broadcastAll(function (player) {
447 | if (event.style == '1') {
448 | if (!player.zmld.Enable) {
449 | player.querySelector('.energy-bar')?.delete()
450 | return
451 | };
452 | if (!player.querySelector('.energy-bar')) {
453 | player.unmarkSkill("_zmld_np")
454 | lib.skill._zmld_np.create(player)
455 | }
456 | let energy = player.zmld.Np || 0;
457 | let maxEnergy = player.zmld.Max || 100;
458 | const bar = player.querySelector('.energy-bar');
459 | if (!bar) return;
460 | const fill = bar.querySelector('.energy-fill');
461 | const label = bar.querySelector('.energy-label');
462 | if (fill && label) {
463 | let percentage = (energy / maxEnergy) * 100;
464 | fill.style.width = `${percentage >= 0 ? percentage : 0}%`;
465 | const gradient = player.zmld?.Color || `${percentage <= 25 ?
466 | 'linear-gradient(90deg, #1a5fb4 0%, #3584e4 50%, #62a0ea 100%, #1a5fb4 200%)' :
467 | percentage <= 50 ?
468 | 'linear-gradient(90deg, #26a269 0%, #33d17a 50%, #8ff0a4 100%, #26a269 200%)' :
469 | percentage <= 75 ?
470 | 'linear-gradient(90deg, #e66100 0%, #ffa348 50%, #ffbe6f 100%, #e66100 200%)' :
471 | 'linear-gradient(90deg, #c01c28 0%, #ff7800 50%, #ffb380 100%, #c01c28 200%)'}`;
472 | fill.style.background = gradient;
473 | fill.style.boxShadow = `0 0 15px ${percentage <= 25 ? '#3584e4cc' :
474 | percentage <= 50 ? '#33d17acc' :
475 | percentage <= 75 ? '#ffa348cc' :
476 | '${lib.config.menu_style == "wood" ? "#CC6600":"#ff7800"}cc'
477 | }`;
478 | label.innerHTML = `${Math.round(energy)}/${maxEnergy}`;
479 | label.style.textShadow = `0 0 8px ${percentage <= 25 ? '#3584e4' :
480 | percentage <= 50 ? '#33d17a' :
481 | percentage <= 75 ? '#ffa348' :
482 | '${lib.config.menu_style == "wood" ? "#CC6600":"#ff7800"}'
483 | }`;
484 |
485 | bar.nodeContent = game.NpContent(player);
486 | }
487 | }
488 | else if (event.style == '2') {
489 | if (!player.zmld.Enable) {
490 | player.unmarkSkill("_zmld_np")
491 | return
492 | };
493 | if (player.querySelector('.energy-bar')) {
494 | player.querySelector('.energy-bar')?.delete()
495 | }
496 | player.updateMark("_zmld_np");
497 | }
498 | }, player);
499 | }
500 | }
501 | }
502 | ```
503 |
--------------------------------------------------------------------------------
/appendix/api.md:
--------------------------------------------------------------------------------
1 | # 附录A:API参考
2 |
3 | ## 1. 玩家(Player)相关API
4 |
5 | ### 1.1 基础属性
6 | ```javascript
7 | player.name // 武将名称
8 | player.sex // 性别(male/female)
9 | player.group // 势力
10 | player.hp // 当前体力值
11 | player.getDamagedHp // 已损失体力值
12 | player.maxHp // 体力上限
13 | player.hujia // 护甲值
14 | player.side // 玩家阵营
15 | player.identity // 身份
16 | player._trueMe // 控制权
17 | player.getSeatNum // 座位号
18 | ```
19 |
20 | ### 1.2 状态相关
21 | ```javascript
22 | player.isDead() // 是否已阵亡
23 | player.isAlive() // 是否存活
24 | player.isDamaged() // 是否受伤
25 | player.isHealthy() // 是否满血
26 | player.isLinked() // 是否横置
27 | player.isTurnedOver()// 是否翻面
28 | player.isOut() // 是否离场
29 | player.isUnseen() // 是否暗将
30 | player.isMad() // 是否混乱
31 | player.changeGroup() // 修改势力
32 | player.isPhaseUsing() // 是否为出牌阶段
33 | player.canCompare(target) // 能否拼点
34 | player.inRange(target) // 是否在攻击范围
35 | player.inRangeOf(source) // 是否处于攻击范围
36 | // 区域相关
37 | /**
38 | * 废除装备栏
39 | * - 'equip1':武器。
40 | * - 'equip2':防具。
41 | * - 'equip3':防御马。
42 | * - 'equip4':进攻马。
43 | * - 'equip5':宝物。
44 | * @param {string|number} arg - 废除装备栏的位置。
45 | */
46 | player.disableEquip(arg)
47 | player.enableEquip() // 恢复装备栏
48 | player.isDisabled() // 是否废除
49 | player.isEmpty() // 是否为空
50 | player.swapEquip(target) // 交换装备
51 | player.countDisabled() // 废除数量
52 |
53 | player.disableJudge() // 废除判定区
54 | player.enableJudge() // 恢复判定区
55 |
56 | /**
57 | * 快速获取一名角色当前轮次/倒数第X轮次的历史
58 | * @param {(event:GameEventPromise)=>boolean} filter 筛选条件,不填写默认为lib.filter.all
59 | * @param {number} [num] 获取倒数第num轮的历史,默认为0,表示当前轮
60 | * @param {boolean} [keep] 若为true,则获取倒数第num轮到现在的所有历史
61 | * @param {GameEventPromise} last 代表最后一个事件,获取该事件之前的历史
62 | */
63 | player.getRoundHistory(key, filter, num, keep, last)
64 | /**
65 | * 返回角色本回合历史
66 | *
67 | * @param { (event: GameEventPromise) => boolean } [filter] 过滤条件
68 | * @param { GameEventPromise } [last] 若有改参数,则该参数事件之后的将被排除掉
69 | */
70 | player.getHistory(key, filter, last)
71 | /**
72 | * 遍历历史
73 | * @param { (event: GameEventPromise) => void } filter 遍历过程需要执行的函数
74 | * @param { GameEventPromise } [last]
75 | */
76 | player.checkHistory(key, filter, last)
77 | player.hasHistory(key, filter, last) // 本回合是否存在历史
78 | player.getLastHistory(key, filter, last) // 返回最后一回合历史
79 | player.checkAllHistory(key, filter, last) // 遍历全局历史
80 | player.hasAllHistory(key, filter, last) // 本局是否存在历史
81 | player.getAllHistory(key, filter, last) // 返回本局历史
82 | player.getAttackRange(raw) // 返回攻击距离,是否为基础值
83 | ```
84 |
85 | ### 1.3 技能相关
86 | ```javascript
87 | // 添加技能
88 | /**
89 | * 添加技能到玩家的技能列表中。
90 | *
91 | * @param {string|Array} skill - 要添加的技能,可以是一个技能字符串或技能字符串数组。
92 | * @param {boolean} checkConflict - 是否在添加技能后检查技能冲突。默认为true。
93 | * @param {boolean} nobroadcast - 是否禁止广播技能添加事件。默认为false。
94 | * @param {boolean} addToSkills - 是否将技能添加到玩家的技能列表中。默认为true。
95 | */
96 | player.addSkill(skill, checkConflict, nobroadcast, addToSkills)
97 | /**
98 | * 添加临时技能到玩家的技能列表中。
99 | *
100 | * @param {string|string[]} skill - 要添加的临时技能,可以是一个技能字符串或技能字符串数组。
101 | * @param {SkillTrigger|SAAType} [expire] - 技能的过期条件,可以是触发器对象或触发器名称(字符串或数组)。
102 | * @param {boolean} [checkConflict] - 是否在添加技能后检查技能冲突。默认为true。
103 | */
104 | player.addTempSkill(skill, expire, checkConflict)
105 | /**
106 | * 添加额外技能到玩家的技能列表中。
107 | *
108 | * @param {string} skill - 技能的主分类或标识符,用于指定要将新技能添加到哪个技能中。
109 | * @param {string|Array} skillsToAdd - 要添加的技能,可以是一个技能字符串或技能字符串数组。
110 | * @param {boolean} keep - 是否保留原有的额外技能。如果为false,则会移除原有技能后再添加新技能。
111 | */
112 | player.addAdditionalSkill(skill, skillsToAdd, keep)
113 |
114 | // 移除技能
115 | /**
116 | * 从玩家的技能列表中移除技能。
117 | *
118 | * @param {string|string[]} skill - 要移除的技能,可以是一个技能字符串或技能字符串数组。
119 | */
120 | player.removeSkill(skill)
121 | /**
122 | * 禁用指定的技能或技能组。
123 | *
124 | * @param {string} skill - 要禁用的技能名称。
125 | * @param {string|string[]} skills - 要禁用的技能或技能组名称,可以是单个技能名称或技能名称数组。
126 | */
127 | player.disableSkill(skill, skills)
128 | /**
129 | * 启用指定的技能,将其从禁用列表中移除。
130 | *
131 | * @param {string} skill - 要启用的技能名称。
132 | */
133 | player.enableSkill(skill)
134 |
135 | // 技能判断
136 | /**
137 | * 检查当前对象是否拥有指定的技能。
138 | *
139 | * @param {string} skill - 要检查的技能名称。
140 | * @param {Parameters[0]} arg2 - 传递给 `getSkills` 方法的第一个参数。
141 | * @param {Parameters[1]} arg3 - 传递给 `getSkills` 方法的第二个参数。
142 | * @param {Parameters[2]} arg4 - 传递给 `getSkills` 方法的第三个参数。
143 | */
144 | player.hasSkill(skill, arg2, arg3, arg4)
145 | /**
146 | * 检查当前对象是否拥有指定的主公技。
147 | *
148 | * @param {string} skill - 要检查的主技能名称。
149 | */
150 | player.hasZhuSkill(skill)
151 | /**
152 | * 技能使用次数
153 | *
154 | * @param {string} skill - 要获取的技能名称
155 | */
156 | player.countSkill(skill)
157 | ```
158 |
159 | ### 1.4 卡牌操作
160 | ```javascript
161 | // 获得牌
162 | /**
163 | * 令玩家获得一些牌
164 | *
165 | * @description
166 | * 该方法支持多种参数类型:
167 | * - `player` 类型参数:指定牌的来源玩家。
168 | * - `cards` 或 `card` 类型参数:指定要获得的牌。
169 | * - `log` 参数:是否记录日志。
170 | * - `fromStorage` 参数:牌是否来自存储区。
171 | * - `fromRenku` 参数:牌是否来自“仁库”。
172 | * - `bySelf` 参数:是否由玩家自己操作。
173 | * - `string` 类型参数:指定动画类型。
174 | * - `boolean` 类型参数:是否延迟执行。
175 | *
176 | */
177 | player.gain()
178 | /**
179 | * 从其他玩家处获得牌。
180 | *
181 | * @description
182 | * 该方法支持多种参数类型:
183 | * - `player` 类型参数:指定目标玩家。
184 | * - `number` 类型参数:指定选择按钮的范围。
185 | * - `select` 类型参数:指定选择按钮的配置。
186 | * - `boolean` 类型参数:指定是否强制选择或复杂选择。
187 | * - `position` 类型参数:指定牌的位置。
188 | * - `visible` 参数:是否可见。
189 | * - `visibleMove` 参数:是否可见移动。
190 | * - `function` 类型参数:指定 AI 逻辑或按钮过滤逻辑。
191 | * - `object` 类型参数:指定按钮过滤条件。
192 | * - `string` 类型参数:指定提示信息。
193 | *
194 | */
195 | player.gainPlayerCard()
196 | /**
197 | * 令玩家摸牌
198 | *
199 | * @description
200 | * 该方法支持多种参数类型:
201 | * - `player` 类型参数:指定牌的来源玩家。
202 | * - `number` 类型参数:指定摸牌的数量。
203 | * - `boolean` 类型参数:指定是否启用动画。
204 | * - `nodelay` 参数:禁用延迟并立即执行摸牌。
205 | * - `visible` 参数:是否可见摸牌。
206 | * - `bottom` 参数:是否从牌堆底部摸牌。
207 | * - `object` 类型参数:指定牌堆配置。
208 | *
209 | * 如果未指定摸牌数量,则默认摸 1 张牌。如果摸牌数量小于等于 0,则直接跳过事件。
210 | * 在特定游戏模式下(如 "stone" 和 "deck"),会调整牌堆配置。
211 | */
212 | player.draw()
213 | /**
214 | * 将指定的卡牌添加到扩展区中,需要自行添加gaintag
215 | *
216 | * @param {string|string[]} arg - 可以是以下类型:
217 | * - "player":指定玩家作为来源。
218 | * - "cards":指定一组卡牌。
219 | * - "card":指定单张卡牌。
220 | * - "log":是否记录日志。
221 | * - "fromStorage":是否从存储中添加。
222 | * - "fromRenku":是否从Renku中添加,并标记为从存储中添加。
223 | * - "bySelf":是否由自己操作。
224 | * - "string":指定动画类型。
225 | * - "boolean":指定是否延迟执行。
226 | */
227 | player.addToExpansion()
228 |
229 | // 失去牌
230 | /**
231 | * 令玩家失去牌
232 | *
233 | * @description
234 | * 该方法支持多种参数类型:
235 | * - `player` 类型参数:指定牌的来源玩家。
236 | * - `cards` 或 `card` 类型参数:指定要失去的牌。
237 | * - `div` 或 `fragment` 类型参数:指定牌的目标位置。
238 | * - `toStorage` 参数:是否将牌移动到存储区。
239 | * - `toRenku` 参数:是否将牌移动到“仁库”。
240 | * - `visible` 参数:是否可见失去牌。
241 | * - `insert` 参数:是否插入牌。
242 | *
243 | * 该方法会过滤掉玩家不拥有的牌。如果失去的牌为空,则直接跳过事件。
244 | * 如果未指定目标位置,则默认将牌移动到弃牌堆。
245 | */
246 | player.lose()
247 | /**
248 | * 令玩家弃牌
249 | *
250 | * @description
251 | * 该方法支持多种参数类型:
252 | * - `player` 类型参数:指定牌的来源玩家。
253 | * - `cards` 或 `card` 类型参数:指定要弃掉的牌。
254 | * - `boolean` 类型参数:指定是否启用动画。
255 | * - `div` 或 `fragment` 类型参数:指定牌的目标位置。
256 | * - `notBySelf` 参数:是否不由玩家自己操作。
257 | *
258 | * 如果未指定要弃掉的牌,则直接跳过事件。
259 | */
260 | player.discard()
261 | /**
262 | * 令玩家弃置其区域内一些能被弃置的牌
263 | *
264 | * @description
265 | * 该方法支持多种参数类型:
266 | * - `player` 类型参数:指定牌的来源玩家。
267 | * - `cards` 或 `card` 类型参数:指定要弃掉的牌。
268 | * - `position` 参数:指定弃置到的区域
269 | * - `log` 参数:因对应Mod技能导致部分牌未被弃置时,是否显示对应技能名。默认'popup'
270 | *
271 | */
272 | player.modedDiscard()
273 | /**
274 | * 将卡牌添加到弃牌区域。
275 | *
276 | * @description
277 | * 该方法支持多种参数类型:
278 | * - `player` 类型参数:指定目标玩家。
279 | * - `cards` 类型参数:指定要添加的卡牌列表。
280 | * - `card` 类型参数:指定要添加的单个卡牌。
281 | * - `log` 参数:是否记录日志。
282 | * - `fromStorage` 参数:是否从存储区域添加。
283 | * - `fromRenku` 参数:是否从 Renku 区域添加。
284 | * - `bySelf` 参数:是否由玩家自己添加。
285 | * - `string` 类型参数:指定动画类型。
286 | * - `boolean` 类型参数:指定是否延迟执行。
287 | */
288 | player.chooseToDiscard()
289 |
290 | // 使用牌
291 | /**
292 | * 让玩家使用卡牌。
293 | *
294 | * @description
295 | * 该方法支持多种参数类型:
296 | * - `cards` 类型参数:指定使用的卡牌列表。
297 | * - `players` 或 `player` 类型参数:指定卡牌的目标玩家。
298 | * - `card` 类型参数:指定使用的卡牌。
299 | * - `object` 类型参数:指定卡牌信息。
300 | * - `string` 类型参数:指定技能名称或特殊选项(如 "noai" 或 "nowuxie")。
301 | * - `boolean` 类型参数:指定是否增加计数。
302 | *
303 | * 该方法会根据卡牌信息调整目标玩家列表,并记录 AI 日志(如果适用)。
304 | * 如果卡牌是单目标卡牌,则会调整目标玩家列表以匹配单目标逻辑。
305 | */
306 | player.useCard()
307 | /**
308 | * 检查当前玩家是否可以对目标玩家使用指定卡牌。
309 | *
310 | * @param {Card|VCard|object|string} card - 要使用的卡牌,可以是卡牌对象、虚拟卡牌、卡牌信息对象或卡牌名称。
311 | * @param {Player} target - 目标玩家。
312 | * @param {false} [distance] - 是否忽略距离限制。如果为 `false`,则无距离限制。
313 | * @param {boolean|GameEvent} [includecard] - 是否受使用次数限制。可以传入用于检测的事件对象。
314 | */
315 | player.canUse(card, target, distance, includecard)
316 | /**
317 | * 检查场上是否存在可以对其使用指定卡牌的目标玩家。
318 | *
319 | * @param {Card|VCard|object|string} card - 要使用的卡牌,可以是卡牌对象、虚拟卡牌、卡牌信息对象或卡牌名称。
320 | * @param {false} [distance] - 是否忽略距离限制。如果为 `false`,则无距离限制。
321 | * @param {boolean|GameEvent} [includecard] - 是否受使用次数限制。可以传入用于检测的事件对象。
322 | * @description
323 | * 该方法会遍历场上所有玩家,并调用 `canUse` 方法检查当前玩家是否可以对目标玩家使用该卡牌。
324 | * 如果存在至少一个满足条件的目标玩家,则返回 `true`,否则返回 `false`。
325 | */
326 | player.hasUseTarget(card, distance, includecard)
327 |
328 | // 判断牌
329 | /**
330 | * 获取符合条件的卡牌数量
331 | *
332 | * @param {string} [arg1] - 指定卡牌的位置类型,默认为 'h'(手牌)。支持以下值:
333 | * - 'h':手牌。
334 | * - 's':特殊区的牌。
335 | * - 'e':装备区的卡牌。
336 | * - 'j':判定区的卡牌。
337 | * - 'x':扩展区的卡牌。
338 | * @param {string|Record|((card: Card) => boolean)} [arg2] - 过滤条件,可以是以下类型:
339 | * - `string`:卡牌名称。
340 | * - `Array`:卡牌名称列表。
341 | * - `object`:卡牌属性过滤条件。
342 | * - `function`:自定义过滤函数。
343 | */
344 | player.countCards(arg1, arg2)
345 | /**
346 | * 获取符合条件的卡牌
347 | *
348 | * @param {string} [arg1] - 指定卡牌的位置类型,默认为 'h'(手牌)。支持以下值:
349 | * - 'h':手牌。
350 | * - 's':特殊区的牌。
351 | * - 'e':装备区的卡牌。
352 | * - 'j':判定区的卡牌。
353 | * - 'x':扩展区的卡牌。
354 | * @param {string|Record|((card: Card) => boolean)} [arg2] - 过滤条件,可以是以下类型:
355 | * - `string`:卡牌名称。
356 | * - `Array`:卡牌名称列表。
357 | * - `object`:卡牌属性过滤条件。
358 | * - `function`:自定义过滤函数。
359 | */
360 | player.getCards(arg1, arg2)
361 | /**
362 | * 检查当前玩家是否拥有符合条件的卡牌。
363 | *
364 | * @param {string|function} name - 卡牌名称或过滤函数。
365 | * @param {string} [position] - 卡牌的位置类型。
366 | */
367 | player.hasCard(name, position)
368 | /**
369 | * 统计具有指定标签的扩展区卡牌数量。
370 | *
371 | * @param {string} tag - 要匹配的标签。
372 | */
373 | player.countExpansions(tag)
374 | /**
375 | * 获取具有指定标签的扩展区卡牌。
376 | *
377 | * @param {string} tag - 要匹配的标签。
378 | */
379 | player.getExpansions(tag)
380 | /**
381 | * 检查是否存在具有指定标签的扩展区卡牌。
382 | *
383 | * @param {string} tag - 要匹配的标签。
384 | */
385 | player.hasExpansions(tag)
386 | /**
387 | * 获取玩家本回合内使用倒数第X+1张牌
388 | *
389 | * @param {number} num - 第X+1张牌,默认为0。
390 | */
391 | player.getLastUsed(num)
392 | ```
393 |
394 | ### 1.5 伤害与回复
395 | ```javascript
396 | // 伤害
397 | /**
398 | * 对当前玩家造成伤害。
399 | *
400 | * @description
401 | * 该方法支持多种参数类型:
402 | * - `cards` 类型参数:指定与伤害相关的卡牌列表。
403 | * - `card` 类型参数:指定与伤害相关的卡牌。
404 | * - `number` 类型参数:指定伤害值。
405 | * - `player` 类型参数:指定伤害来源玩家。
406 | * - `nocard` 参数:是否忽略卡牌。
407 | * - `nosource` 参数:是否忽略伤害来源。
408 | * - `notrigger` 参数:是否禁用触发效果。
409 | * - `unreal` 参数:是否虚拟伤害。
410 | * - `nature` 或 `natures` 类型参数:指定伤害属性(如 "fire"、"poison" 等)。
411 | * - `nohujia` 参数:是否无视护甲
412 | *
413 | * 该方法会根据伤害属性(如 "poison")和虚拟伤害标志调整触发逻辑。
414 | * 如果伤害值小于等于 0,则触发 "damageZero" 事件并结束。
415 | */
416 | player.damage()
417 | /**
418 | * 流失当前玩家的体力。
419 | *
420 | * @param {number} num - 要扣减的体力值。
421 | */
422 | player.loseHp(num)
423 | /**
424 | * 扣减当前玩家的体力上限。
425 | *
426 | * @description
427 | * 该方法支持以下参数:
428 | * - `number` 类型参数:指定扣减的体力上限值,默认为 1。
429 | * - `boolean` 类型参数:指定是否强制扣减。
430 | */
431 | player.loseMaxHp()
432 |
433 | // 回复
434 | /**
435 | * 对当前玩家进行回复。
436 | *
437 | * @description
438 | * 该方法支持多种参数类型:
439 | * - `cards` 类型参数:指定与回复相关的卡牌列表。
440 | * - `card` 类型参数:指定与回复相关的卡牌。
441 | * - `player` 类型参数:指定回复来源玩家。
442 | * - `number` 类型参数:指定回复值。
443 | * - `nocard` 参数:是否忽略卡牌。
444 | * - `nosource` 参数:是否忽略回复来源。
445 | *
446 | * 如果回复值小于等于 0 或玩家已满血,则直接结束事件。
447 | */
448 | player.recover()
449 | /**
450 | * 增加当前玩家的体力上限。
451 | *
452 | * @description
453 | * 该方法支持以下参数:
454 | * - `number` 类型参数:指定增加的体力上限值,默认为 1。
455 | * - `boolean` 类型参数:指定是否强制增加。
456 | */
457 | player.gainMaxHp()
458 |
459 | // 护甲
460 | /**
461 | * 改变当前玩家的护甲值。
462 | *
463 | * @param {number} [num] - 要改变的护甲值,默认为 1。
464 | * @param {"gain" | "lose" | "damage" | "null"} [type] - 改变的类型,默认为根据 `num` 自动判断:
465 | * - `gain`:增加护甲。
466 | * - `lose`:减少护甲。
467 | * - `damage`:护甲受到伤害。
468 | * - `null`:无变化。
469 | * @param {number} [limit] - 护甲上限。如果护甲值超过上限,则调整 `num` 以确保不超过上限。
470 | */
471 | player.changeHujia(num, type, limit)
472 | ```
473 |
474 | ### 1.6 选择与交互
475 | ```javascript
476 | // 选择目标
477 | /**
478 | * 创建选择目标事件。
479 | *
480 | * @description
481 | * 该方法支持多种参数类型,用于配置选择目标的行为:
482 | * - `number` 类型参数:指定选择目标的数量范围(最小和最大值相同)。
483 | * - `select` 类型参数:指定选择目标的配置。
484 | * - `dialog` 类型参数:指定关联的对话框,并禁用提示。
485 | * - `boolean` 类型参数:指定是否强制选择(`forced`)。
486 | * - `function` 类型参数:
487 | * - 第一个 `function` 参数:指定目标过滤逻辑。
488 | * - 第二个 `function` 参数:指定 AI 逻辑。
489 | * - `string` 类型参数:指定提示信息。
490 | *
491 | * 默认行为:
492 | * - 如果没有指定 `filterTarget`,则使用默认的目标过滤逻辑(`lib.filter.all`)。
493 | * - 如果没有指定 `selectTarget`,则默认为 `[1, 1]`。
494 | * - 如果没有指定 `ai`,则使用默认的 AI 逻辑(`get.attitude2`)。
495 | *
496 | */
497 |
498 | player.chooseTarget()
499 | player.chooseBool() // 是/否选择
500 |
501 | // 选择牌
502 | player.chooseCard() // 选择手牌
503 | /**
504 | * 创建一个选择响应卡牌的事件。
505 | *
506 | * @description
507 | * 支持的参数类型:
508 | * - `number` 或 `select` 类型参数:指定选择卡牌的数量或配置。
509 | * - `boolean` 类型参数:指定是否强制选择(`forced`)。
510 | * - `position` 类型参数:指定卡牌的位置。
511 | * - `function` 类型参数:
512 | * - 如果未指定 `filterCard`,则作为卡牌过滤逻辑。
513 | * - 如果已指定 `filterCard`,则作为 AI 逻辑。
514 | * - `object` 类型参数:指定卡牌过滤条件。
515 | * - `nosource` 参数:指定是否无来源玩家。
516 | * - `string` 类型参数:指定提示信息。
517 | *
518 | * 默认行为:
519 | * - 如果没有指定 `filterCard`,则使用默认的卡牌过滤逻辑(`lib.filter.all`)。
520 | * - 如果没有指定 `selectCard`,则默认为 `[1, 1]`。
521 | * - 如果没有指定 `source` 且未设置 `nosource`,则默认来源玩家为当前事件的玩家。
522 | * - 如果没有指定 `ai`,则使用默认的 AI 逻辑(`get.unuseful2`)。
523 | * - 如果没有指定 `ai2`,则使用默认的 AI 逻辑(始终选择第一个选项)。
524 | * - 如果没有指定 `prompt`,则自动生成提示信息。
525 | * - 默认卡牌位置为 `"hs"`。
526 | */
527 | player.chooseToRespond()
528 | player.chooseToUse() // 选择去使用
529 | /**
530 | * 创建一个选择卡牌和目标事件。
531 | *
532 | * @param {object} choose - 配置对象,用于指定选择卡牌和目标的行为。
533 | *
534 | * 配置对象支持的属性:
535 | * - `filterCard`:卡牌过滤逻辑,可以是函数或对象。如果未指定,则使用默认的卡牌过滤逻辑(`lib.filter.all`)。
536 | * - `filterTarget`:目标过滤逻辑,可以是函数或对象。如果未指定,则使用默认的目标过滤逻辑(`lib.filter.all`)。
537 | * - `selectCard`:选择卡牌的数量。如果未指定,则默认为 `1`。
538 | * - `selectTarget`:选择目标的数量。如果未指定,则默认为 `1`。
539 | * - `ai1`:卡牌选择的 AI 逻辑。如果未指定,则使用默认逻辑(`get.unuseful2`)。
540 | * - `ai2`:目标选择的 AI 逻辑。如果未指定,则使用默认逻辑(`get.attitude2`)。
541 | *
542 | */
543 | player.chooseCardTarget(choose)
544 |
545 | // 选择按钮
546 | /**
547 | * 创建并配置一个选择按钮事件。
548 | *
549 | * @description
550 | * 该方法支持多种参数类型,用于配置选择按钮的行为:
551 | * - `boolean` 类型参数:
552 | * - 第一个 `boolean` 参数:指定是否强制选择(`forced`)。
553 | * - 第二个 `boolean` 参数:指定是否为复杂选择(`complexSelect`)。
554 | * - `dialog` 类型参数:指定关联的对话框,并自动关闭对话框。
555 | * - `select` 类型参数:指定选择按钮的配置。
556 | * - `number` 类型参数:指定选择按钮的范围(最小和最大值相同)。
557 | * - `function` 类型参数:
558 | * - 第一个 `function` 参数:指定 AI 逻辑。
559 | * - 第二个 `function` 参数:指定按钮过滤逻辑。
560 | * - `array` 类型参数:指定用于创建对话框的配置。
561 | *
562 | * 默认行为:
563 | * - 如果没有指定 `forced`,则默认为 `false`。
564 | * - 如果没有指定 `filterButton`,则使用默认的按钮过滤逻辑。
565 | * - 如果没有指定 `selectButton`,则默认为 `[1, 1]`。
566 | * - 如果没有指定 `ai`,则使用默认的 AI 逻辑(始终选择第一个选项)。
567 | * - 如果没有指定 `complexSelect`,则默认为 `true`。
568 | */
569 | player.chooseButton()
570 | player.chooseControl() // 选择选项
571 | /**
572 | * 选择废除一个未废除的装备栏
573 | *
574 | * @description
575 | * 该方法支持多种参数类型,用于配置选择行为:
576 | * - `boolean` 类型参数:是否同时废除两个坐骑栏。
577 | * - `player` 类型参数:指定目标角色。
578 | * - `select` 类型参数:指定选择按钮的配置。
579 | * - `number` 类型参数:指定选择按钮的范围。
580 | */
581 | player.chooseToDisable()
582 | player.chooseToEnable() // 选择恢复一个废除的装备栏
583 | player.chooseToPSS() // 选择一名角色进行猜拳
584 | player.chooseToGuanxing() // 执行一次卜算
585 | ```
586 |
587 | ### 1.7 动画效果
588 | ```javascript
589 | player.$draw(num) // 摸牌动画
590 | player.$give(num, target) // 给牌动画
591 | player.$throw(cards) // 弃牌动画
592 | player.$gain2(cards) // 获得牌动画
593 | player.$damage(nature) // 受伤动画
594 | player.$recover() // 回复动画
595 | player.$skill(name) // 技能动画
596 | player.$fire() // 火焰动画
597 | player.$thunder() // 雷电动画
598 | player.$throwEmotion(target,'egg') // 投掷动画
599 | ```
600 |
601 | ## 2. 卡牌(Card)相关API
602 |
603 | ### 2.1 基础属性
604 | ```javascript
605 | card.name // 卡牌名称
606 | card.suit // 花色
607 | card.number // 点数
608 | card.nature // 属性
609 | card.type // 类型(basic/trick/equip)
610 | card.extraDamage // 额外伤害
611 | card.baseDamage // 基础伤害
612 | card.directHit // 强中目标
613 | card.effectCount // 执行次数
614 | ```
615 |
616 | ### 2.2 状态判断
617 | ```javascript
618 | card.hasNature(nature) // 是否有某属性
619 | card.hasPosition() // 是否在场上
620 | card.isInPile() // 是否在牌堆
621 | card.hasTag(tag) // 是否有标签
622 | ```
623 |
624 | ### 2.3 卡牌操作
625 | ```javascript
626 | // 添加/移除属性
627 | card.addNature(nature) // 添加属性
628 | card.removeNature(nature) // 移除属性
629 |
630 | // 知情者相关
631 | card.addKnower(player) // 添加知情者
632 | card.removeKnower(player) // 移除知情者
633 | card.clearKnowers() // 清除知情者
634 | card.isKnownBy(player) // 是否知情
635 |
636 | // 其他操作
637 | card.copy() // 复制卡牌
638 | card.discard() // 弃置
639 | card.init(cardData) // 初始化
640 | /**
641 | * 给此牌添加特定的cardtag(如添加应变条件)
642 | *
643 | * @param { string } tag
644 | */
645 | card.addCardtag(tag)
646 | /**
647 | * 给此牌移除特定的cardtag(如移除应变条件)
648 | *
649 | * @param { string } tag
650 | */
651 | card.removeCardtag(tag)
652 | ```
653 |
654 | ## 3. 武将(Character)相关API
655 |
656 | ### 3.1 基础属性
657 | ```javascript
658 | character.sex // 性别
659 | character.group // 势力
660 | character.hp // 体力值
661 | character.maxHp // 体力上限
662 | character.hujia // 护甲值
663 | character.skills // 技能列表
664 | ```
665 |
666 | ### 3.2 特殊标记
667 | ```javascript
668 | character.isZhugong // 是否主公
669 | character.isUnseen // 是否暗将
670 | character.isBoss // 是否Boss
671 | character.isAiForbidden // 是否禁止AI使用
672 | character.hasHiddenSkill // 是否有隐藏技能
673 | character.doubleGroup // 双势力
674 | character.clans // 势力标记
675 | ```
676 |
677 | ## 4. 事件(Event)相关API
678 |
679 | ### 4.1 事件处理
680 | ```javascript
681 | // 事件创建
682 | game.createEvent(name) // 创建事件
683 | event.trigger(name) // 触发事件
684 | event.cancel() // 取消事件
685 | event.finish() // 结束事件
686 | event.getParent() // 父级事件
687 |
688 | // 事件等待
689 | game.delay() // 延迟
690 | game.delayx() // 根据动画速度延迟
691 | ```
692 | ## 4.2.各事件AI参数
693 | ```javascript
694 | /**
695 | * @description
696 | * @param {Event} event - 当前事件的父级事件
697 | * @param {Player} player - 当前玩家对象
698 | *
699 | */
700 | chooseControl.ai(event, player)
701 | chooseToEnable.ai((event, player, list))
702 | chooseToDisable.ai((event, player, list))
703 | ```
704 |
705 |
706 | ## 5. 游戏(Game)相关API
707 |
708 | ### 5.1 游戏状态
709 | ```javascript
710 | game.players // 所有玩家
711 | game.dead // 已阵亡角色
712 | game.me // 当前玩家
713 | game.phaseNumber // 当前回合数
714 | game.roundNumber // 当前轮数
715 | ```
716 |
717 | ### 5.2 游戏操作
718 | ```javascript
719 | game.swapPlayer() // 交换玩家
720 | game.swapControl() // 交换控制权
721 | game.pause() // 暂停游戏
722 | game.resume() // 恢复游戏
723 | game.over(result) // 游戏结束
724 | game.cardsDiscard() // 将cards移动到弃牌区
725 | game.cardsGotoSpecial() // 将cards移动到特殊区
726 | game.createCard() // 创建卡牌(一次性)
727 | game.createCard2() // 创建卡牌
728 | ```
729 |
730 | ### 5.3 联机相关
731 | ```javascript
732 | game.broadcast() // 广播消息
733 | game.broadcastAll() // 广播给所有玩家
734 | game.syncState() // 同步状态
735 | game.waitForPlayer() // 等待玩家
736 | ```
737 |
738 | ## 6、读取(Get)相关API
739 | ### 6.1 卡牌相关
740 | ```javascript
741 | /**
742 | * 从指定区域获得一张牌
743 | * @param { function | string | object | true } name 牌的筛选条件或名字,true为任意一张牌
744 | * @param { string | boolean } [position] 筛选区域,默认牌堆+弃牌堆:
745 | *
746 | * cardPile: 仅牌堆;discardPile: 仅弃牌堆;filed: 牌堆+弃牌堆+场上
747 | *
748 | * 若为true且name为string | object类型,则在筛选区域内没有找到卡牌时创建一张name条件的牌
749 | *
750 | * @param { string } [start] 遍历方式。默认top
751 | *
752 | * top: 从牌堆/弃牌堆顶自顶向下遍历
753 | * bottom: 从牌堆/弃牌堆底自底向上遍历
754 | * random: 随机位置遍历
755 | * @returns { Card | ChildNode | null }
756 | */
757 | get.cardPile(name, position, start)
758 | /**
759 | * 从牌堆获得一张牌
760 | * @param { function | string | object | true } name 牌的筛选条件或名字,true为任意一张牌
761 | * @param { string } [start] 遍历方式。默认top
762 | *
763 | * top:从牌堆顶自顶向下遍历
764 | * bottom:从牌堆底自底向上遍历
765 | * random: 随机位置遍历
766 | * @returns { Card | ChildNode | null }
767 | */
768 | get.cardPile2(name, start)
769 | /**
770 | * 从弃牌堆获得一张牌
771 | * @param { function | string | object | true } name 牌的筛选条件或名字,true为任意一张牌
772 | * @param { string } [start] 遍历方式。默认top
773 | *
774 | * top:从弃牌堆顶自顶向下遍历
775 | * bottom:从弃牌堆底自底向上遍历
776 | * random: 随机位置遍历
777 | * @returns { Card | ChildNode | null }
778 | */
779 | get.discardPile(name, start)
780 | /**
781 | * 返回数字在扑克牌中的表示形式
782 | * @param { number } num
783 | * @param { boolean } [forced] 未获取点数字母对应元素时,若此参数不为false,则返回字符串格式
784 | * @returns { string }
785 | */
786 | get.strNumber(num, forced)
787 | get.numString(str, forced) // 返回扑克牌中的表示形式对应的数字
788 | get.cards(num,boolean) // 返回牌堆顶的牌
789 | get.bottomCards(num,boolean) // 返回牌堆底的牌
790 | get.effect() // 返回收益
791 | get.order() // 返回优先级
792 | get.value() // 返回价值
793 | get.is.yingbian() // 是否能应变
794 | ```
795 |
796 | ### 6.2 技能相关
797 | ```javascript
798 | /**
799 | * 获取一个技能或事件的某个属性的源技能
800 | *
801 | * @param { string | Object } skill - 传入的技能或事件
802 | * @param { string } text - 要获取的属性(不填写默认获取sourceSkill)
803 | */
804 | get.sourceSkillFor(skill, text)
805 | ```
806 |
807 | ### 6.3 其他
808 | ```javascript
809 | get.isLuckyStar() // 是否开启幸运星模式
810 | /**
811 | * 返回指定角色的所有id
812 | *
813 | * @param {Player} player
814 | */
815 | get.nameList()
816 | get.player() // 返回当前事件角色
817 | ```
818 |
819 | ## 7. 窗口(UI)相关API
820 | ### 7.1 对话框(Dialog)
821 | ```javascript
822 | /**
823 | * 创建游戏内对话框组件,支持:
824 | * - 文本/卡牌/角色/按钮/自定义元素
825 | *
826 | * @param {string} title - 对话框标题(支持HTML)
827 | * @param {Array} content - 内容定义数组,结构为:
828 | * [
829 | * contentData, // 内容数据(类型取决于contentType)
830 | * contentType // 内容类型标识符("vcard"/"character"/"textbutton"等)
831 | * ]
832 | * @param {...(string|boolean)} options - 配置选项:
833 | * - "hidden" : 创建后不自动打开(需手动调用open())
834 | * - true : 静态模式(禁用关闭功能)
835 | * - "forcebutton" : 强制显示按钮
836 | * - "notouchscroll" : 禁用触摸滚动
837 | * - "noforcebutton" : 强制隐藏按钮
838 | *
839 | * @example 基础对话框
840 | * const dialog = ui.create.dialog("提示", ["系统错误!", "text"]);
841 | *
842 | * @example 卡牌选择对话框
843 | * const dialog = ui.create.dialog("选择手牌", [player.getCards("h"), "vcard"], "forcebutton");
844 | */
845 |
846 | ui.create.dialog(title,content,options)
847 |
848 | /**
849 | * 对话框内容类型定义
850 | * @property {string} vcard - 虚拟卡牌(数据格式:[suit, number, name, natrue]或Cards数组)
851 | * @property {string} card - 实体卡牌(数据格式:[]Card数组)
852 | * @property {string} character - 角色信息(数据格式:[]角色名称)
853 | * @property {string} player - 玩家列表(数据格式:[]对象数组)
854 | * @property {string} textbutton - 文本按钮(数据格式:[link, text])
855 | * @property {string} tdnodes - 表格按钮(数据格式:[]文本数组)
856 | * @property {string} blank - 卡牌背面(数据格式:[]Card数组)
857 |
858 | */
859 |
860 | /**
861 | * 对话框实例方法
862 | * @method open() - 打开对话框
863 | * @method close() - 强制关闭对话框
864 | * @method add(content) - 添加内容
865 | * @method setCaption(text) - 设置标题
866 | * @method addSmall(content) - 添加缩小内容
867 | * @method addText(text, center) - 添加文本段落(center: 是否居中)
868 | * @method addNewRow({item,ratio}) - 添加横向按钮行
869 | * @property {boolean} supportsPagination - 分页支持开关(默认false)
870 | * @property {Map} paginationMap - 分页控制器映射表(需开启分页)
871 | * @property {boolean} static - 静态模式开关(禁用关闭)
872 | */
873 | ```
874 |
--------------------------------------------------------------------------------
/trigger.md:
--------------------------------------------------------------------------------
1 | # 4.2 触发时机
2 |
3 | ## 1. 触发时机概述
4 |
5 | 触发时机是技能发动的关键时间点,主要分为:
6 | - [阶段类触发](#阶段类)
7 | - [事件类触发](#事件类)
8 | - [全局类触发](#全局类)
9 | - [主动类触发](#主动类)
10 |
11 |
12 | 触发器列表
13 |
14 | ```javascript
15 | // 带有 ? 即为特殊模式触发事件。
16 | // 事件细分,可细分事件必须携带此类后缀
17 | "${EventWithTrigger}Before" // 事件发生前
18 | "${EventWithTrigger}Begin" // 事件开始时
19 | "${EventWithTrigger}End" // 事件结束时
20 | "${EventWithTrigger}After" // 事件发生后
21 | "${EventWithTrigger}Skipped" // 事件被跳过时
22 | // 可细分事件
23 | "${_CardName}" // 卡牌使用时
24 | "${_CardName}Cancel" // 卡牌取消时
25 | "${_CardName}ContentAfter" // 卡牌执行后
26 | "${_CardName}ContentBefore" // 卡牌执行前
27 | "[skillname]" // 技能使用时
28 | "[skillname]ContentAfter" // 技能执行后
29 | "[skillname]ContentBefore" // 技能执行前
30 | "[skillname]_cost" // 技能执行cost(选择)时
31 | "addFellowAuto" // 自动添加随从时?
32 | "addJudge" // 添加判定牌时
33 | "addToExpansion" // 置于武将牌上时
34 | "boss_jingjia" // boss 执行精甲时?
35 | "callSubPlayer" // 切换至随从时
36 | "caochuan_gain" // 草船借箭获得牌时
37 | "cardsDiscard" // 卡牌弃置时
38 | "cardsGotoOrdering" // 卡牌进入处理区时
39 | "cardsGotoPile" // 卡牌进入牌堆时
40 | "cardsGotoSpecial" // 卡牌进入特殊区时
41 | "carryOutJunling" // 执行军令时?
42 | "changeCharacter" // 修改角色时
43 | "changeGroup" // 修改势力时
44 | "changeHp" // 修改血量时
45 | "changeHujia" // 修改护甲时
46 | "changeVice" // 修改副将时
47 | "changeSkills" // 修改技能时
48 | "chessMech" // 塔防放置时?
49 | "chessMechRemove" // 塔防移除时?
50 | "chooseBool" // 选择是否时
51 | "chooseButton" // 选择按钮时
52 | "chooseButtonOL" // 联机选择按钮时
53 | "chooseCard" // 选择卡牌时
54 | "chooseCardOL" // 联机选择卡牌时
55 | "chooseCardTarget" // 选择卡牌目标时
56 | "chooseCharacter" // 选择角色时
57 | "chooseCharacterOL" // 联机选择角色时
58 | "chooseControl" // 选择选项时
59 | "chooseCooperationFor" // 选择协力时
60 | "chooseJunlingControl" // 选择军令选项时?
61 | "chooseJunlingFor" // 选择军令时?
62 | "choosePlayerCard" // 选择玩家卡牌时
63 | "chooseSkill" // 选择技能时
64 | "chooseTarget" // 选择目标时
65 | "chooseToCompare" // 选择拼点时
66 | "chooseToDebate" // 选择议事时
67 | "chooseToDisable" // 选择废弃装备时
68 | "chooseToDiscard" // 选择弃置时
69 | "chooseToDuiben" // 选择谋弈时
70 | "chooseToEnable" // 选择恢复装备时
71 | "chooseToGive" // 选择给予时
72 | "chooseToGuanxing" // 选择卜算时
73 | "chooseToMove" // 选择移动牌时
74 | "chooseToMoveChess" // 选择移动塔防时?
75 | "chooseToMove_new" // 选择移动_新时(有什么区别?暂不清楚)
76 | "chooseToPSS" // 选择猜拳时
77 | "chooseToPlayBeatmap" // 选择播放音谱时
78 | "chooseToRespond" // 选择响应时
79 | "chooseToUse" // 选择使用时
80 | "chooseUseTarget" // 选择使用目标时
81 | "compareMultiple" // 范围拼点时
82 | "damage" // 伤害时
83 | "die" // 死亡时
84 | "disableEquip" // 禁用装备栏时
85 | "disableJudge" // 禁用判定区时
86 | "discard" // 弃置时
87 | "discardPlayerCard" // 弃置玩家卡牌时
88 | "discoverCard" // 发现卡牌时
89 | "doubleDraw" // 额外抽牌时?
90 | "draw" // 摸牌时
91 | "dying" // 濒死时
92 | "enableEquip" // 恢复装备栏时
93 | "enableJudge" // 恢复判定区时
94 | "equip" // 使用装备时
95 | "equip_${_CardName}" // 使用指定装备时
96 | "executeDelayCardEffect" // 执行延迟锦囊牌效果时
97 | "exitSubPlayer" // 移除随从时
98 | "expandEquip" // 扩展装备区时
99 | "finish_game" // 游戏结束时
100 | "gain" // 获得牌时
101 | "gainMaxHp" // 获得最大血量时
102 | "gainPlayerCard" // 获得玩家卡牌时
103 | "game" // 游戏开始时
104 | "gameDraw" // 开局摸牌时
105 | "gift" // 给予赠物时
106 | "guozhanDraw" // 国战开局摸牌时?
107 | "gzzhenxi_use" // 国战震袭使用时?
108 | "hideCharacter" // 隐藏角色时
109 | "judge" // 判定时
110 | "link" // 横置时
111 | "loadMap" // 加载地图时?
112 | "loadPackage" // 加载扩展包时
113 | "lose" // 失去牌时
114 | "loseAsync" // 异步失去牌时
115 | "loseHp" // 失去血量时
116 | "loseMaxHp" // 失去体力上限时
117 | "loseToDiscardpile" // 失去到弃牌堆时
118 | "lose_${_CardName}" // 指定卡牌失去时
119 | "lose_[VEquip.name]" // 指定装备失去时
120 | "mayChangeVice" // 可以更换副将时?
121 | "moveCard" // 移动卡牌时
122 | "nvzhuang_lose" // 女装失去时
123 | "phaseDiscard" // 弃牌阶段时
124 | "phaseDraw" // 摸牌阶段时
125 | "phaseJieshu" // 结束阶段时
126 | "phaseJudge" // 判定阶段时
127 | "phaseLoop" // 阶段流转时
128 | "phaseZhunbei" // 准备阶段时
129 | "pre_[event.wuxieresult2]" // 事件结果预处理时
130 | "pre_[skillname]" // 技能预处理时
131 | "qinglong_guozhan" // 国战使用青龙刀时
132 | "recast" // 重铸时
133 | "recover" // 回血时
134 | "removeCharacter" // 移除角色时
135 | "replaceChessPlayer" // 替换塔防角色时?
136 | "replaceEquip" // 替换装备时
137 | "replaceHandcards" // 替换手牌时
138 | "replacePlayer" // 替换角色时
139 | "respond" // 响应时
140 | "showCards" // 展示卡牌时
141 | "showHandcards" // 展示手牌时
142 | "stratagemCamouflage" // 计谋伪装时
143 | "stratagemInsight" // 计谋洞察时
144 | "swapEquip" // 交换装备时
145 | "toggleSubPlayer" // 更换随从时
146 | "turnOver" // 翻面时
147 | "useCard" // 使用卡牌时
148 | "useSkill" // 使用技能时
149 | "versusDraw" // 对战摸牌时
150 | "viewCards" // 查看卡牌时
151 | "viewCharacter" // 查看角色时
152 | "yingbianEffect" // 应变效果时
153 | "yingbianZhuzhan" // 应变助战时
154 | "zhuque_clear" // 朱雀扇执行时
155 |
156 | // 不可细分事件
157 | "addShownCardsAfter" // 添加展示卡牌之后
158 | "addToExpansionBefore" // 置于武将牌上之前
159 | "boss_baonuwash" // boss 暴怒清洗时?
160 | "compare" // 拼点时
161 | "compareCardShowBefore" // 拼点牌展示前
162 | "compareFixing" // 拼点修正时
163 | "damageBegin1" // 伤害开始阶段1
164 | "damageBegin2" // 伤害开始阶段2
165 | "damageBegin3" // 伤害开始阶段3
166 | "damageBegin4" // 伤害开始阶段4
167 | "damageSource" // 伤害来源确定时
168 | "damageZero" // 伤害为零时
169 | "debateShowOpinion" // 议事展示意见时
170 | "enterGame" // 进入游戏时
171 | "eventNeutralized" // 事件被抵消时
172 | "fellow" // 获得随从时
173 | "gameStart" // 游戏开始相关
174 | "giftAccept" // 赠礼接受时
175 | "giftAccepted" // 赠礼接受后
176 | "giftDenied" // 赠礼拒绝时
177 | "giftDeny" // 赠礼拒绝后
178 | "hideShownCardsAfter" // 隐藏展示牌之后
179 | "jiananUpdate" // 建安更新时?
180 | "judgeFixing" // 判定修正时
181 | "phaseAfter" // 阶段结束后
182 | "phaseBefore" // 阶段开始前
183 | "phaseBeforeEnd" // 阶段开始前的结束时
184 | "phaseBeforeStart" // 阶段开始之前
185 | "phaseBegin" // 阶段开始时
186 | "phaseBeginStart" // 阶段开始时
187 | "phaseChange" // 阶段变化时
188 | "phaseDrawBegin1" // 摸牌阶段开始时1
189 | "phaseDrawBegin2" // 摸牌阶段开始时2
190 | "phaseEnd" // 阶段结束时
191 | "recastingGain" // 重铸获得牌时
192 | "recastingGained" // 重铸获得牌后
193 | "recastingLose" // 重铸失去牌时
194 | "recastingLost" // 重铸失去牌后
195 | "removeCharacterBefore" // 移除角色之前
196 | "removeSubPlayer" // 移除随从时
197 | "rewriteDiscardResult" // 重写弃牌结果时
198 | "rewriteGainResult" // 重写获得结果时
199 | "roundStart" // 每轮开始时
200 | "shaDamage" // 杀造成伤害时
201 | "shaHit" // 杀命中时
202 | "shaMiss" // 杀未命中时
203 | "shaUnhirt" // 杀未造成伤害时
204 | "showCharacterAfter" // 展示角色之后
205 | "showCharacterEnd" // 展示角色结束时
206 | "skillAfter" // 使用技能后
207 | "subPlayerDie" // 随从死亡时
208 | "triggerAfter" // 触发之后
209 | "triggerHidden" // 隐藏触发时
210 | "triggerInvisible" // 不可见触发时
211 | "useCard" // 使用卡牌时
212 | "useCard0" // 使用卡牌时0
213 | "useCard1" // 使用卡牌时1
214 | "useCard2" // 使用卡牌时2
215 | "washCard" // 洗牌时
216 | "wuguRemained" // 五谷剩余时
217 | "yingbian" // 应变时
218 | "zhuUpdate" // 主公更新时
219 | "[eventname]Inserted" // 事件插入时
220 | "addShownCards" // 添加展示牌时
221 | "arrangeTrigger" // 安排触发时
222 | "chooseDrawRecover" // 选择摸牌回血时
223 | "debateCallback" // 议事回调时
224 | "delay" // 延迟时
225 | "delayx" // 延迟时
226 | "dieAfter" // 死亡后
227 | "gainMultiple" // 群体获得时
228 | "judgeCallback" // 判定回调时
229 | "leaderView" // 君主视图时?
230 | "loadMode" // 加载模式时
231 | "logSkill" // 记录技能时
232 | "logSkillBegin" // 记录技能开始时。
233 | "orderingDiscard" // 处理区弃置时
234 | "qianlidanji_replace" // 千里单骑切换难度时
235 | "replacePlayer" // 替换角色时
236 | "replacePlayerSingle" // 替换单个角色时
237 | "replacePlayerTwo" // 替换两个角色时
238 | "shidianyanluo_huanren" // 十殿阎罗换人时
239 | "showCharacter" // 展示角色时
240 | "showYexings" // 展示野心时
241 | "trigger" // 触发时
242 | "useCardToExcluded" // 使用卡牌被排除时
243 | "useCardToPlayer" // 使用卡牌指定玩家时
244 | "useCardToPlayered" // 使用卡牌指定玩家后
245 | "useCardToTarget" // 使用卡牌指定目标时
246 | "useCardToTargeted" // 使用卡牌指定目标后
247 | "video" // 放视频时
248 | "waitForPlayer" // 等待玩家时
249 | "wuxianhuoli_reward" // 无限火力奖励时
250 | "year_limit_pop" // 年限弹出时
251 | "hideShownCards" // 隐藏展示卡牌时
252 | "phase" // 阶段时
253 | "phaseUse" // 阶段使用时
254 | "swapHandcards" // 交换手牌时
255 | ```
256 |
257 |
258 |
259 | ## 2. 阶段类触发
260 |
261 | ### 2.1 角色阶段
262 | ```javascript
263 | trigger: {
264 | player: [
265 | // 核心阶段触发
266 | "enterGame", // 角色进入游戏时(初始化技能)
267 | "phaseBefore", // 阶段开始前(可跳过阶段)
268 | "phaseBegin", // 阶段开始时
269 | "phaseEnd", // 阶段结束时
270 | "phaseAfter", // 阶段结束后(切换阶段前)
271 |
272 | // 细分阶段控制
273 | "phaseBeforeStart", // 阶段开始前的初始化(优先级最高)
274 | "phaseBeforeEnd", // 阶段开始前的收尾(跳过阶段前最后时机)
275 | "phaseBeginStart", // 阶段开始时的初始化(优先级最高)
276 |
277 | // 标准回合阶段
278 | "phaseZhunbei", // 准备阶段(回合初始)
279 | "phaseJudge", // 判定阶段(处理延时锦囊)
280 | "phaseDraw", // 摸牌阶段(默认摸2牌)
281 | "phaseUse", // 出牌阶段(主要行动阶段)
282 | "phaseDiscard", // 弃牌阶段(手牌调整)
283 | "phaseJieshu", // 结束阶段(回合收尾)
284 |
285 | // 阶段细分事件
286 | "phaseDrawBegin1", // 摸牌阶段开始时(可修改摸牌数)
287 | "phaseDrawBegin2", // 摸牌阶段第二触发点(稳定触发)
288 | "phaseUseBegin", // 出牌阶段开始时(初始化出牌次数)
289 | "phaseUseEnd", // 出牌阶段结束时(清理出牌状态)
290 | "phaseUseSkipped" // 出牌阶段被跳过时
291 | ]
292 | }
293 | ```
294 | #### 阶段顺序:
295 | - enterGame
296 | - phaseBefore
297 | - phaseBeforeStart
298 | - phaseBeforeEnd
299 | - phaseBeginStart
300 | - phaseBegin
301 | - phaseZhunbei
302 | - phaseJudge
303 | - phaseDraw
304 | - phaseUse
305 | - phaseDiscard
306 | - phaseJieshu
307 | - phaseEnd
308 | - phaseAfter
309 |
310 | ## 3. 事件类触发
311 |
312 | ### 3.1 伤害相关
313 | ```javascript
314 | trigger: {
315 | // 造成伤害(来源方)
316 | source: [
317 | "damageBegin1", // 伤害计算阶段1:可修改伤害值(如裸衣、酒)
318 | "damageBegin2", // 伤害计算阶段2:不可改值但可执行效果(寒冰剑弃牌)
319 | "damageSource", // 伤害来源确定时(最终来源判定)
320 | "shaDamage", // 杀造成伤害时(命中后触发)
321 | "damageBegin", // 造成伤害时
322 | "damageEnd" // 伤害结算完成后
323 | ],
324 | // 受到伤害(目标方)
325 | player: [
326 | "damageBegin3", // 【受到伤害阶段1】可转移/修改伤害(标准天香)
327 | "damageBegin4", // 【受到伤害阶段2】可取消伤害(界天香)
328 | "damageZero", // 伤害被无效时(仁王盾防黑杀)
329 | "damageBegin", // 受到伤害时
330 | "damageEnd" // 受到伤害后
331 | ],
332 | }
333 | ```
334 |
335 | ### 3.2 卡牌相关
336 | ```javascript
337 | trigger: {
338 | // 主动使用
339 | player: [
340 | "useCard", // 使用卡牌时(包括技能转化)
341 | "useCard0", // 使用卡牌时(原始牌)
342 | "useCard1", // 使用卡牌时(转换后的牌)
343 | "useCard2", // 使用卡牌时(最终生效的牌)
344 | "useCardTo", // 使用卡牌指定目标时
345 | "respond", // 打出响应牌时(如闪响应杀)
346 | "juedou", // 使用决斗时
347 | "shaHit", // 杀命中时
348 | "shaMiss", // 杀未命中时
349 | "shaDamage", // 杀命中且造成伤害时
350 | "shaUnhirt", // 杀未造成伤害时(此处hirt为源码拼写错误,实际为hurt,调用时请使用hirt)
351 | "wuguRemained", // 五谷有多余展示牌时
352 | "useCardToPlayer", // 使用牌指定目标时
353 | "useCardToPlayered" // 使用牌指定目标后
354 | ],
355 | // 成为目标
356 | target: [
357 | "useCardToTarget", // 成为卡牌目标时(指定目标阶段)
358 | "useCardToTargeted" // 成为卡牌目标后(目标确定完成)
359 | ],
360 | // 卡牌移动
361 | player: [
362 | "gain", // 获得牌时(包括摸牌、获得其他角色牌)
363 | "lose", // 失去牌时(包括弃置、被拿走)
364 | "recast", // 重铸牌时
365 | "draw", // 摸牌时
366 | "discard", // 弃牌时
367 | "addToExpansion" // 置于武将牌时
368 | ]
369 | }
370 | ```
371 |
372 | ### 3.3 状态相关
373 | ```javascript
374 | trigger: {
375 | // 血量变化
376 | player: [
377 | "changeHp", // 血量变化时(包括增减)
378 | "loseHp", // 失去体力时
379 | "recover" // 回复体力时
380 | ],
381 | // 装备变更
382 | player: [
383 | "equip", // 使用装备牌时
384 | "lose_${equipName}", // 失去特定装备时
385 | "replaceEquip" // 替换装备时
386 | ],
387 | // 角色状态
388 | player: [
389 | "die", // 角色死亡时
390 | "dying", // 进入濒死状态时
391 | "dyingEnd", // 脱离濒死状态时
392 | "turnOver", // 翻面时
393 | "changeCharacter", // 切换武将时
394 | "showCharacterEnd" // 展示角色牌后
395 | ]
396 |
397 | }
398 | ```
399 |
400 | ## 4. 全局触发
401 |
402 | ### 4.1 基本用法
403 | ```javascript
404 | trigger: {
405 | global: [
406 | "gameStart", // 游戏开始时
407 | "roundStart", // 轮数开始时
408 | "phaseBegin", // 任意角色回合开始时
409 | "damage", // 任意伤害事件(全局监听)
410 | "damageEnd", // 伤害结算完成后(反馈类技能)
411 | "washCard", // 洗牌时
412 | ]
413 | }
414 | ```
415 |
416 | ### 5. 主动触发
417 |
418 | ### 5.1 基本用法
419 | ```javascript
420 | trigger:{
421 | enable: [
422 | "phaseUse", // 出牌阶段可用
423 | "chooseToUse", // 主动使用牌时可用
424 | "chooseToRespond", // 响应阶段可用
425 | "chooseCard", // 选牌操作时可用
426 | ]
427 |
428 | }
429 | ```
430 |
431 | ## 6. 触发优先级
432 |
433 | ### 6.1 优先级设置
434 | ```javascript
435 | "priority_skill": {
436 | trigger: {player:"phaseBegin"},
437 | priority: 5, // 设置优先级(默认为1)
438 | forced: true,
439 | content(){}
440 | }
441 | ```
442 |
443 | ## 7. 触发条件
444 |
445 | ### 7.1 基本判断
446 | ```javascript
447 | filter(event, player){
448 | // 血量条件
449 | return player.hp < 3;
450 |
451 | // 手牌条件
452 | return player.countCards("h") > 0;
453 |
454 | // 目标条件
455 | return event.player != player;
456 |
457 | // 牌名条件
458 | return event.card && event.card.name =="sha";
459 | }
460 | ```
461 |
462 | ### 7.2 复杂条件
463 | ```javascript
464 | filter(event, player){
465 | // 多重条件
466 | if(player.hp < 3 && player.countCards("h") > 0){
467 | return event.player.isAlive() &&
468 | event.player.countCards("h") > 2;
469 | }
470 | return false;
471 | }
472 | ```
473 |
474 | ## 8. 进阶用法
475 |
476 | ### 8.1 触发顺序控制
477 | ```javascript
478 | "order_skill": {
479 | trigger: {player:"phaseBegin"},
480 | async content(event, trigger, player){
481 | // 打断后续触发
482 | trigger.cancel();
483 |
484 | // 自定义触发
485 | event.trigger("order_skill")
486 |
487 | // 跳过特定阶段
488 | player.skip("phaseDraw");
489 | }
490 | }
491 | ```
492 |
493 | ### 8.2 条件触发
494 | ```javascript
495 | "condition_skill": {
496 | trigger: {player:"phaseBegin"},
497 | direct: true, // 锁定技且不输出日志
498 | check(event, player){
499 | return player.hp < 3; // AI发动条件
500 | },
501 | async content(event, trigger, player){
502 | let target = await player.chooseTarget("对一名角色造成伤害,然后你失去一点体力").forResult();
503 | if (target.bool){
504 | player.logSkill("condition_skill",target.targets[0])
505 | await target.targets[0].damage()
506 | await player.loseHp()
507 | }
508 | }
509 | }
510 | ```
511 |
512 | ## 练习
513 |
514 | 1. 创建一个多重触发技能:
515 | - 在回合开始和结束时触发
516 | - 根据不同时机有不同效果
517 | - 添加触发条件
518 |
519 |
520 | 参考答案
521 | - 🟩 Easy
522 |
523 | ```javascript
524 | "multi_trigger": {
525 | // 多个触发时机
526 | trigger: {
527 | player: ["phaseBegin", "phaseEnd"]
528 | },
529 | // 触发条件
530 | filter(event, player){
531 | if(event.name =="phaseBegin"){
532 | return player.countCards("h") < 3; // 回合开始时手牌少于3
533 | }
534 | return player.hp < 3; // 回合结束时体力值少于3
535 | },
536 | // 根据时机执行不同效果
537 | async content(event, trigger, player){
538 | if(event.triggername =="phaseBegin"){
539 | // 回合开始时摸牌
540 | await player.draw(2);
541 | game.log(player,"触发了回合开始效果");
542 | } else {
543 | // 回合结束时回血
544 | await player.recover();
545 | game.log(player,"触发了回合结束效果");
546 | }
547 | },
548 | ai: {
549 | threaten: 1.5
550 | }
551 | }
552 | ```
553 |
554 |
555 | 2. 创建一个全局触发技能:
556 | - 监听所有角色的特定事件
557 | - 根据条件决定是否触发
558 | - 实现连锁效果
559 |
560 |
561 | 参考答案
562 | - 🟨 Medium
563 |
564 | ```javascript
565 | "global_trigger": {
566 | // 监听所有角色的伤害事件
567 | trigger: {
568 | global: "damageEnd"
569 | },
570 | // 触发条件
571 | filter(event, player){
572 | return event.player != player && // 不是自己受伤
573 | event.player.isAlive() && // 目标存活
574 | event.num > 0; // 伤害大于0
575 | },
576 | // 连锁效果
577 | async content(event, trigger, player){
578 | // 记录伤害数值
579 | event.num = trigger.num;
580 | // 选择效果
581 | let choices = ["摸牌","回血"];
582 | if(player.countCards("h") > 0) choices.push("弃牌造成伤害");
583 |
584 | let result = await player.chooseControl(choices)
585 | .set("prompt","请选择一个效果")
586 | .set("ai", function(){
587 | if(player.hp <= 2) return"回血";
588 | if(player.countCards("h") < 2) return"摸牌";
589 | return"弃牌造成伤害";
590 | })
591 | .forResult();
592 | // 执行效果
593 | switch(result.control){
594 | case"摸牌":
595 | await player.draw(event.num);
596 | break;
597 | case"回血":
598 | await player.recover(event.num);
599 | break;
600 | case"弃牌造成伤害":
601 | await player.chooseToDiscard(1, true);
602 | let target = await player.chooseTarget("选择一名角色造成伤害").forResult();
603 | if(target.bool){
604 | await target.targets[0].damage();
605 | }
606 | break;
607 | }
608 | },
609 | ai: {
610 | threaten: 2,
611 | expose: 0.2
612 | }
613 | }
614 | ```
615 |
616 |
617 | 3. 创建一个优先级技能:
618 | - 设置高优先级
619 | - 控制其他技能的触发
620 | - 实现特殊效果
621 |
622 |
623 | 参考答案
624 | - 🟨 Medium
625 |
626 | ```javascript
627 | "priority_skill": {
628 | // 设置高优先级
629 | priority: 10,
630 | // 触发时机
631 | trigger: {
632 | target: "useCardToTargeted"
633 | },
634 | forced: true,
635 | // 触发条件
636 | filter(event, player){
637 | return event.card.name =="sha" && // 目标是【杀】
638 | player.countCards("h") > 0; // 有手牌
639 | },
640 | // 特殊效果
641 | async content(event, trigger, player){
642 | // 取消原有事件
643 | trigger.cancel();
644 | game.log(player,"触发了优先级技能");
645 | // 展示一张手牌
646 | let card = await player.chooseCard("h", true,"请展示一张手牌").forResult();
647 | if(card.bool){
648 | await player.showCards(card.cards);
649 |
650 | // 根据花色执行效果
651 | if(get.color(card.cards[0]) =="red"){
652 | await player.draw();
653 | game.log(player,"展示红色牌,摸一张牌");
654 | } else {
655 | await trigger.player.draw();
656 | game.log(trigger.player,"展示黑色牌,使用者摸一张牌");
657 | }
658 | }
659 | },
660 | ai: {
661 | effect: {
662 | target(card, player, target){
663 | if(card.name =="sha") return 0.5;
664 | }
665 | }
666 | }
667 | }
668 | ```
669 |
670 |
671 | 下一节我们将学习如何实现技能效果。
672 |
--------------------------------------------------------------------------------