Skip to content

Commit bfc1bfc

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into ocr/update-v5
2 parents bd1c977 + a06d9da commit bfc1bfc

69 files changed

Lines changed: 1149 additions & 240 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/pipeline-guide/SKILL.md

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,18 +268,150 @@ PrintT(context, "tetris.task_done")
268268
}
269269
```
270270

271+
## SceneManager 与场景跳转
272+
273+
MaaNTE 的场景管理器提供"从任意界面自动导航到目标场景"的能力,分两层架构:
274+
275+
- **公共接口**`Interface/Scene/`)— Pipeline 任务使用的节点,名称不含 `__ScenePrivate`
276+
- **私有实现**`SceneManager/`)— 内部节点,以 `__ScenePrivate` 开头,**禁止在 Pipeline 中直接引用**
277+
278+
### 常用场景跳转接口
279+
280+
| 接口 | 说明 |
281+
|------|------|
282+
| `SceneAnyEnterWorld` | 从任意界面返回大世界 |
283+
| `SceneLoading` | 等待加载界面结束 |
284+
| `SceneClickBlankToExit` | 点击空白区域关闭弹窗 |
285+
| `SceneAnyEnterEscMenu` | 进入 Esc 菜单 |
286+
| `SceneAnyEnterBagMenu` | 进入背包 |
287+
| `SceneAnyEnterBattlePassMenu` | 进入环期赏令 |
288+
| `SceneAnyEnterCharactersMenu` | 进入角色界面 |
289+
| `SceneAnyEnterCityTycoonsMenu` | 进入都市大亨 |
290+
| `SceneAnyEnterEventsMenu` | 进入活动菜单 |
291+
| `SceneAnyEnterExplorationGuideMenu` | 进入探索指南 |
292+
| `SceneAnyEnterHethereauHobbiesMenu` | 进入都市闲趣 |
293+
294+
### InScene 场景识别
295+
296+
InScene 节点(`Interface/Scene/Status.json`)用于判断当前画面所在场景,配合 SceneManager 实现自动导航:
297+
298+
```jsonc
299+
{
300+
"MyTaskEntry": {
301+
"next": [
302+
"MyTaskCheckInWorld",
303+
"[JumpBack]SceneAnyEnterWorld" // 不在大世界时自动跳转
304+
]
305+
},
306+
"MyTaskCheckInWorld": {
307+
"recognition": { "type": "And", "param": { "all_of": ["InWorld"] } },
308+
"next": ["MyTaskNextStep"]
309+
}
310+
}
311+
```
312+
313+
> **重要**:跳转后必须有 InScene 检查节点确认已在目标场景,避免反复跳转导致死循环。
314+
315+
常用 InScene 节点:`InWorld``InEscMenu``InBagMenu``InCityTycoonMenu``InExplorationGuideMenu``InBattlePassMenu``InCharactersMenu`。完整列表见 `Interface/Scene/Status.json`
316+
317+
### 新增场景接口
318+
319+
需要新增场景跳转时:
320+
1.`SceneManager/` 下添加 `__ScenePrivate*` 私有节点处理实际导航
321+
2.`Interface/Scene/` 中添加公共接口节点
322+
3.`Status.json` 中添加状态检测节点(如 `InNewMenu`),使用 OCR 识别页面标题文字
323+
4. 在 SceneManager 的 `__ScenePrivateAnyEnterXxxSuccess` 中引用新节点,接入万能跳转链
324+
325+
## 关键编码规范
326+
327+
### 禁止硬延迟
328+
329+
尽量不用 `pre_delay` / `post_delay` / `timeout`,用中间识别节点或 `pre_wait_freezes` / `post_wait_freezes` 替代。当确实不需要延迟时,显式将 `rate_limit` / `pre_delay` / `post_delay` 设为 0(协议默认 `rate_limit=1000ms``pre_delay/post_delay=200ms`)。
330+
331+
**不要为了执行稳定而使用延迟,而是通过增加中间节点判断,因为延迟实际上是在掩盖问题,在用户设备存在高延迟时仍然不会稳定。**
332+
333+
### 第一轮即命中
334+
335+
尽可能扩充 `next` 列表,保证任何游戏画面都处于预期中,实现一次截图就命中目标节点。项目一般拒绝一切形式的重试机制,一定要保证在一次流程中完成所有任务。
336+
337+
### 识别 → 操作 → 再识别
338+
339+
每一步操作后必须重新识别确认画面变化,禁止假设操作后状态:
340+
341+
- **推荐**:识别 A → 点击 A → 识别 B → 点击 B
342+
- **禁止**:整体识别一次 → 点击 A → 点击 B → 点击 C
343+
344+
例如:点击提交按钮后必须识别确认提交成功(用户网络可能延迟,界面可能卡死)。
345+
346+
### 禁止盲目重试
347+
348+
遇到 bug 时找根因,详细到具体哪个节点失败、哪个识别不符合预期,去修补对应节点的识别/操作问题。**禁止**同样的操作再试一次、盲目添加 `max_hit`
349+
350+
### 处理弹窗和加载
351+
352+
好的流程是正常主线能跑、弹窗能处理、加载能等过去、不在目标场景时能自动跳过去。常见做法是在 `next` 里挂:
353+
354+
- `[JumpBack]SceneAnyEnterWorld`
355+
- `[JumpBack]SceneClickBlankToExit`
356+
- `[JumpBack]SceneLoading`
357+
358+
### OCR 写完整文本
359+
360+
`expected` 写完整文本,不写半截。多语言处理由 CI 工作流自动完成。需要片段或手写正则时添加 `// @i18n-skip` 标记。
361+
362+
### 先复用,再新增
363+
364+
写新节点前先查已有 Pipeline 是否已有现成能力。优先使用 SceneManager 公共接口,**禁止直接引用 `__ScenePrivate*` 内部节点**
365+
366+
### 配套文件
367+
368+
新增或修改任务时,改动通常涉及多个文件:
369+
- `assets/resource/tasks/*.json` — 任务配置
370+
- `assets/resource/base/pipeline/**/*.json` — Pipeline 节点
371+
- `assets/resource/locales/interface/` 下五种语言文件 — i18n 文案
372+
- `assets/interface.json` — 注册 task 文件
373+
374+
### switch 控制节点启停
375+
376+
```jsonc
377+
// 任务配置中通过 switch option 控制节点 enabled
378+
"MyFeature": {
379+
"type": "switch",
380+
"cases": {
381+
"Yes": { "pipeline_override": { "MyNode": { "enabled": true } } },
382+
"No": { "pipeline_override": { "MyNode": { "enabled": false } } }
383+
}
384+
}
385+
```
386+
387+
## 子模块目录
388+
389+
复杂任务建议拆分为独立目录,主流程与辅助节点分离:
390+
391+
```
392+
pipeline/WithdrawMoney/
393+
├── WithdrawMoney.json # 主流程节点
394+
└── WithdrawMoneyStatus.json # 辅助识别/动作节点
395+
```
396+
271397
## 审查清单
272398

273399
- [ ] 字段名拼写正确、类型合法(核对 Pipeline 协议)
274-
- [ ] 无不必要的 `pre_delay` / `post_delay` / `timeout`
275-
- [ ] `next` 列表覆盖所有可能画面,含弹窗/加载/异常
276-
- [ ] 每次点击后有识别验证,不假设操作后状态
400+
- [ ] 无不必要的 `pre_delay` / `post_delay` / `timeout`,不需要时显式设为 0
401+
- [ ] `next` 列表覆盖所有可能画面,含弹窗/加载/异常,力争一次截图命中
402+
- [ ] 每次操作后有识别验证,不假设操作后状态(识别→操作→再识别)
403+
- [ ] 无盲目重试逻辑(同样操作再试一次、随意加 `max_hit`
404+
- [ ] 弹窗和加载有对应的 `[JumpBack]` 处理节点
405+
- [ ] 先查已有 Pipeline 是否有现成能力,优先复用
406+
- [ ] 未直接引用 `__ScenePrivate*` 内部节点
407+
- [ ] 场景跳转后有 InScene 检查节点确认到达目标场景
277408
- [ ] ROI / target 坐标基于 1280×720
278409
- [ ] 自定义动作名与 Python `@AgentServer.custom_action("name")` 一致
279410
- [ ] 自定义识别/动作参数与 Python 代码中解析的参数名一致
280411
- [ ] 用户消息优先用 `maafocus.PrintT()`(Python 侧),简单通知用 JSON `focus`(pipeline 侧)
281412
- [ ] OCR `expected` 写完整文本
282413
- [ ] 使用 `post_wait_freezes` 或中间节点避免重复点击
414+
- [ ] 配套文件齐全(task JSON、pipeline JSON、i18n、interface.json 注册)
283415

284416
## 参考
285417

.claude/skills/python-action-guide/SKILL.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ description: MaaNTE Python 自定义动作(CustomAction)编写指南。覆
77

88
## 架构定位
99

10-
Python CustomAction 处理 Pipeline JSON 无法覆盖的复杂逻辑(图像算法、状态机、音频检测、MIDI 播放等)。禁止在 Python 中编写大规模业务流程——流程控制由 Pipeline JSON 负责。
10+
Python CustomAction 处理 Pipeline JSON 无法覆盖的复杂逻辑(图像算法、状态机、音频检测、MIDI 播放等)。**流程控制由 Pipeline 负责,Python 只处理难点。** 一句话:**Pipeline 管流程,Python 管难点。**
11+
12+
没有必要的 Python 逻辑会大大增加代码复杂度,造成下一位开发者开发调试极其困难。
1113

1214
所有坐标与图像以 **720p (1280×720)** 为基准。
1315

@@ -114,6 +116,66 @@ __all__ = [
114116
- **文件名**:snake_case。单文件简单动作直接用 `.py` 文件;复杂模块用目录 + `__init__.py`
115117
- **内部函数/变量**:snake_case,模块内部使用的加前导下划线 `_`
116118

119+
## Common 内置动作
120+
121+
以下 Custom Action 位于 `agent/custom/action/Common/`,可在 Pipeline 中直接使用,无需额外编写 Python 代码。
122+
123+
### click_override
124+
125+
自定义点击。通过 `custom_action_param` 指定目标 rect,或使用当前识别结果的 box。
126+
127+
- 注册名:`click_override`
128+
- 参数 `custom_action_param``{ "target": [x, y, w, h] }`
129+
- 若未提供 `custom_action_param`,则使用 `argv.box`(识别结果 box)
130+
131+
```jsonc
132+
{
133+
"action": {
134+
"type": "Custom",
135+
"param": {
136+
"custom_action": "click_override",
137+
"custom_action_param": { "target": [100, 200, 50, 50] }
138+
}
139+
}
140+
}
141+
```
142+
143+
### alt_click
144+
145+
Alt + 点击。先按下 Alt 键,再点击识别结果 box 位置,最后松开 Alt。
146+
147+
- 注册名:`alt_click`
148+
- 无需额外参数,点击位置由识别结果的 `box` 决定
149+
150+
```jsonc
151+
{
152+
"recognition": { "type": "TemplateMatch", "param": { "template": "xxx.png" } },
153+
"action": {
154+
"type": "Custom",
155+
"param": { "custom_action": "alt_click" }
156+
}
157+
}
158+
```
159+
160+
## Common 工具函数
161+
162+
`agent/custom/action/Common/utils.py` 提供常用辅助函数:
163+
164+
| 函数 | 说明 |
165+
|------|------|
166+
| `get_image(controller)` | 截图,返回 numpy array |
167+
| `click_rect(controller, rect, delay)` | 点击指定 rect 的中心 |
168+
| `match_template_in_region(img, region, template, min_similarity, green_mask)` | 在区域内做模板匹配,返回 `(hit, score, x, y)` |
169+
170+
```python
171+
from Common.utils import get_image, click_rect, match_template_in_region
172+
173+
img = get_image(controller)
174+
hit, score, x, y = match_template_in_region(img, [0, 0, 1280, 720], template, 0.8)
175+
if hit:
176+
click_rect(controller, [x, y, 50, 50])
177+
```
178+
117179
## CustomAction 模板
118180

119181
### 简单动作
@@ -520,6 +582,7 @@ image_dir = base / "sounds" / "dodge.wav"
520582
- [ ] 异常有合理的错误处理和返回值
521583
- [ ] 模块级状态变量有对应的 reset action 或清理逻辑
522584
- [ ] 重复逻辑已考虑抽取为 `Common/utils.py` 中的共用函数
585+
- [ ] 配套文件齐全:Python 文件在 `agent/custom/action/` 下、已在 `__init__.py` 中导入并加入 `__all__`
523586

524587
## 参考
525588

.claude/skills/task-config/SKILL.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,13 @@ switch 的 case 中可嵌套 `"option"` 数组,实现"启用某功能后才显
201201

202202
## i18n
203203

204-
- task/option 的 `label``description` 使用 `$key` 格式引用 `assets/resource/locales/interface/<lang>.json` 中的翻译
204+
- task/option 的 `label``description` 使用 `$key` 格式引用翻译,key 定义在 `assets/resource/locales/interface/` 下五种语言文件中:
205+
- `zh_cn.json` — 简体中文
206+
- `zh_tw.json` — 繁体中文
207+
- `en_us.json` — 英语
208+
- `ja_jp.json` — 日语
209+
- `ko_kr.json` — 韩语
210+
- Pipeline 中 OCR 节点的 `expected` 只需填写**完整的中文文本**,多语言同步由 `.github/workflows/i18n-sync.yml` 工作流自动完成。需要跳过时添加 `// @i18n-skip` 标记
205211
- `option_switch_case_yes` / `option_switch_case_no` 是全局 switch case 默认标签,所有语言文件都需定义。一般 switch 直接复用即可;若有特殊需求(如文案不应是简单的"启用/禁用"),可将 case 的 `label` 替换为自定义 `$key`
206212
- 纯文本(不加 `$`)直接显示,不走翻译
207213

@@ -285,7 +291,8 @@ switch 的 case 中可嵌套 `"option"` 数组,实现"启用某功能后才显
285291
- [ ] 任务 `name` 使用 PascalCase
286292
- [ ] `entry` 节点名在对应 Pipeline JSON 中存在
287293
- [ ] task 文件已注册到 `assets/interface.json``import` 数组
288-
- [ ] `label` / `description``$i18n_key` 在所有语言文件中已定义
294+
- [ ] `label` / `description``$i18n_key` 在所有五种语言文件中已定义
295+
- [ ] OCR `expected` 写完整文本,无需手动维护多语言(CI 自动同步)
289296
- [ ] switch option 有 `default_case`
290297
- [ ] `pipeline_override` 中的节点名在 Pipeline JSON 中存在
291298
- [ ] `custom_action_param` 参数名与 Python CustomAction 解析一致

.github/cliff.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[remote.github]
2-
owner = "MaaXYZ"
3-
repo = "MaaPracticeBoilerplate"
2+
owner = "1bananachicken"
3+
repo = "MaaNTE"
44
#自用时换成自己的仓库owner和repo
55
#注意,本地git user需与github一致才能实现成功@
66
[changelog]
@@ -62,6 +62,11 @@ body = """
6262

6363
# 尾部
6464
footer = """
65+
## 国内下载源
66+
67+
* [已有 Mirror酱 CDK?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=MaaNTE)
68+
* [夸克网盘](https://pan.quark.cn/s/b690fe93838b?pwd=PtXy) 提取码: PtXy
69+
* [百度网盘](https://pan.baidu.com/s/11QMC-aYfjfq52yco_UAwfg?pwd=tkmu) 提取码: tkmu
6570
"""
6671

6772
[git]
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Close Invalid Issues
2+
3+
on:
4+
issues:
5+
types: [opened, edited, reopened]
6+
7+
permissions:
8+
contents: read
9+
issues: write
10+
11+
jobs:
12+
close-invalid-issue:
13+
if: ${{ !github.event.issue.pull_request }}
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Close issues with invalid confirmation checked
17+
uses: actions/github-script@v7
18+
with:
19+
script: |
20+
const marker = '<!-- maa-auto-close-invalid-checkbox -->';
21+
const invalidPatterns = [
22+
/(?:^|\n)\s*-\s*\[[xX]\]\s*我未确认下列内容,仅仅是点了确认\s*(?:\n|$)/,
23+
/(?:^|\n)\s*-\s*\[[xX]\]\s*I did not confirm the following content;\s*I simply clicked ["“]confirm\.[”"]\s*(?:\n|$)/i,
24+
];
25+
26+
const issue = context.payload.issue;
27+
if (issue.state === 'closed') {
28+
core.info('Issue is already closed; skipping.');
29+
return;
30+
}
31+
32+
const body = issue.body || '';
33+
const shouldClose = invalidPatterns.some((pattern) => pattern.test(body));
34+
35+
if (!shouldClose) {
36+
core.info('No invalid confirmation checkbox was checked.');
37+
return;
38+
}
39+
40+
const { owner, repo } = context.repo;
41+
const issue_number = issue.number;
42+
const commentBody = [
43+
marker,
44+
'该 Issue 勾选了“未确认下列内容,仅仅是点了确认”确认项,按模板规则自动关闭。',
45+
'',
46+
'This issue checked the invalid confirmation option ("I did not confirm the following content; I simply clicked confirm.") and has been closed automatically.',
47+
].join('\n');
48+
49+
const { data: comments } = await github.rest.issues.listComments({
50+
owner,
51+
repo,
52+
issue_number,
53+
per_page: 100,
54+
});
55+
56+
const alreadyCommented = comments.some((comment) => comment.body?.includes(marker));
57+
if (!alreadyCommented) {
58+
await github.rest.issues.createComment({
59+
owner,
60+
repo,
61+
issue_number,
62+
body: commentBody,
63+
});
64+
}
65+
66+
await github.rest.issues.update({
67+
owner,
68+
repo,
69+
issue_number,
70+
state: 'closed',
71+
state_reason: 'not_planned',
72+
});

0 commit comments

Comments
 (0)