├── .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 | ![支持UI显示](Images/enery3.png) 13 | ![支持多种颜色](Images/enery4.png) 14 | ![支持标记展示](Images/enery5.png) 15 | ![描述丰富](Images/enery1.png) 16 | ![根据背景变换颜色](Images/enery2.png) 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 | ![能量](./Images/example1.png) 60 | #### 历史记录: 61 | ![能量](./Images/example2.png) 62 | #### 对其他人能量条无变化: 63 | ![能量](./Images/example3.png) 64 | #### 对其他人能量条修改: 65 | ![能量](./Images/example4.png) 66 | #### 支持渐变色: 67 | ```javascript 68 | Color: "linear-gradient(90deg, #c01c28 0%, #ff7800 50%, #ffb380 100%, #c01c28 200%)" 69 | ``` 70 | ![能量](./Images/example5.png) 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 | --------------------------------------------------------------------------------