feat: add cooperation ticket claim automation
This commit is contained in:
1 parent
44d9a617d1
commit
576515d2cf
12 files changed
+224
-4
No files matched your search
@@ -76,6 +76,14 @@
|
|||||||
|
|
||||||
流程为:首页 → 领地 → 食堂 → 发现“一键领取”时领取体力 → 确认“恭喜获得”弹框 → 确认烤肉库存为 0/60 → 返回首页。没有可领体力时,脚本不点击领取并直接返回首页。使用 `--dry-run` 可只验证首页而不点击。
|
流程为:首页 → 领地 → 食堂 → 发现“一键领取”时领取体力 → 确认“恭喜获得”弹框 → 确认烤肉库存为 0/60 → 返回首页。没有可领体力时,脚本不点击领取并直接返回首页。使用 `--dry-run` 可只验证首页而不点击。
|
||||||
|
|
||||||
|
### 已验证的合作券领取脚本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python claim_cooperation_ticket.py
|
||||||
|
```
|
||||||
|
|
||||||
|
流程为:首页 → 领地 → 合作模式 → 模式选择左侧“合作模式” → 左上“可领取” → 确认奖励弹框与“已领完” → 返回首页。只操作左侧合作模式,不进入“保卫宠物”;已领完时不点击领取,直接返回首页。使用 `--dry-run` 可只验证首页而不点击。
|
||||||
|
|
||||||
需要 Python 3.9 或更新版本,以及带 Tk 支持的 Python。macOS 若当前 Python 缺少 Tk,可使用 python.org 官方安装包。
|
需要 Python 3.9 或更新版本,以及带 Tk 支持的 Python。macOS 若当前 Python 缺少 Tk,可使用 python.org 官方安装包。
|
||||||
|
|
||||||
macOS:
|
macOS:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,66 @@
|
|||||||
|
"""领地合作模式合作券领取流程。"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .engine import WorkflowError
|
||||||
|
|
||||||
|
|
||||||
|
class CooperationRunner:
|
||||||
|
def __init__(self, desktop, matcher, flow, region_provider, log=print, timeout=12):
|
||||||
|
self.desktop = desktop
|
||||||
|
self.matcher = matcher
|
||||||
|
self.flow = flow
|
||||||
|
self.region_provider = region_provider
|
||||||
|
self.log = log
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def wait(self, *names):
|
||||||
|
deadline = time.monotonic() + self.timeout
|
||||||
|
previous, stable = None, 0
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
screenshot = self.desktop.screenshot()
|
||||||
|
region = self.region_provider(screenshot)
|
||||||
|
frame = self.matcher.frame(screenshot, region)
|
||||||
|
found = next((name for name in names
|
||||||
|
if self.matcher.matches(frame, self.flow['states'][name])), None)
|
||||||
|
stable = stable + 1 if found and found == previous else 1 if found else 0
|
||||||
|
previous = found
|
||||||
|
if stable >= 2:
|
||||||
|
return found, screenshot, region
|
||||||
|
time.sleep(0.25)
|
||||||
|
raise WorkflowError('未识别到预期页面 ' + '/'.join(names) + ',已停止。')
|
||||||
|
|
||||||
|
def click(self, action, observation):
|
||||||
|
_, screenshot, region = observation
|
||||||
|
if self.region_provider(screenshot) != region:
|
||||||
|
raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。')
|
||||||
|
self.desktop.click(self.flow['clicks'][action], region, screenshot.size, hold=0.8)
|
||||||
|
self.log('已执行:' + action)
|
||||||
|
|
||||||
|
def return_home(self):
|
||||||
|
self.click('cooperation_back', self.wait('available', 'claimed'))
|
||||||
|
self.click('territory_back', self.wait('territory'))
|
||||||
|
self.wait('home')
|
||||||
|
self.log('已确认返回首页。')
|
||||||
|
|
||||||
|
def run(self, dry_run=False):
|
||||||
|
home = self.wait('home')
|
||||||
|
if dry_run:
|
||||||
|
self.log('首页识别通过。计划:领地 → 合作模式 → 合作券可领取时领取 → 返回首页。未点击。')
|
||||||
|
return 'preview'
|
||||||
|
|
||||||
|
self.click('territory', home)
|
||||||
|
self.click('open_cooperation', self.wait('territory'))
|
||||||
|
self.click('choose_cooperation', self.wait('selection'))
|
||||||
|
page = self.wait('available', 'claimed')
|
||||||
|
if page[0] == 'claimed':
|
||||||
|
self.log('合作券已领完,本次不点击领取。')
|
||||||
|
self.return_home()
|
||||||
|
return 'already_claimed'
|
||||||
|
self.click('claim_ticket', page)
|
||||||
|
reward = self.wait('reward')
|
||||||
|
self.log('已确认合作券奖励弹框。')
|
||||||
|
self.click('dismiss_reward', reward)
|
||||||
|
self.wait('claimed')
|
||||||
|
self.log('已确认合作券状态变为已领完。')
|
||||||
|
self.return_home()
|
||||||
|
return 'claimed'
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""Run from the project root: python claim_cooperation_ticket.py [--dry-run]."""
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from automation.cooperation import CooperationRunner
|
||||||
|
from automation.engine import Matcher, WorkflowError, load_flow
|
||||||
|
from automation.offers import MacGameDesktop
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='领地合作模式:领取合作券并返回首页')
|
||||||
|
parser.add_argument('--dry-run', action='store_true', help='只验证首页并显示计划,不点击')
|
||||||
|
args = parser.parse_args()
|
||||||
|
if sys.platform != 'darwin':
|
||||||
|
print('当前脚本已验证 macOS;Windows 版将在校准入口完成后接入。', file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
try:
|
||||||
|
desktop = MacGameDesktop()
|
||||||
|
desktop.activate()
|
||||||
|
print('3 秒后开始;请保持游戏在前台。鼠标移到主屏幕左上角可紧急停止。', flush=True)
|
||||||
|
time.sleep(3)
|
||||||
|
runner = CooperationRunner(desktop, Matcher(ROOT / 'assets'),
|
||||||
|
load_flow(ROOT / 'workflows/cooperation_ticket.json'), desktop.region_for)
|
||||||
|
runner.run(args.dry_run)
|
||||||
|
return 0
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('已停止。', file=sys.stderr)
|
||||||
|
return 130
|
||||||
|
except Exception as exc:
|
||||||
|
print('流程停止:' + str(exc), file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
created_at: 2026-10-04 16:07:56 CST
|
created_at: 2026-10-04 16:07:56 CST
|
||||||
type: 永远的蔚蓝星球首页、商城、领地与公会入口、日常任务与自动化适配记录
|
type: 永远的蔚蓝星球首页、商城、领地与公会入口、日常任务与自动化适配记录
|
||||||
revision: 第13次
|
revision: 第14次
|
||||||
revisions:
|
revisions:
|
||||||
- time: 2026-10-04 16:07:56 CST
|
- time: 2026-10-04 16:07:56 CST
|
||||||
version: 第1次
|
version: 第1次
|
||||||
@@ -55,7 +55,11 @@ revisions:
|
|||||||
version: 第13次
|
version: 第13次
|
||||||
author: gpt-5
|
author: gpt-5
|
||||||
content: 补充任务红点驱动的日常与周常领奖流程、无红点跳过分支、脚本和汇总表条目。
|
content: 补充任务红点驱动的日常与周常领奖流程、无红点跳过分支、脚本和汇总表条目。
|
||||||
summary: 记录首页、商城、领地和公会的日常任务入口、待完善流程及已实现脚本汇总。入口红点表示内部有奖励可领取;特惠赏金战令、小金猪领取与扫荡、任务奖励、狂鼠之家收矿与雇佣、食堂领体力均已实机验证并写成脚本。
|
- time: 2026-10-04 20:25:59 CST
|
||||||
|
version: 第14次
|
||||||
|
author: gpt-5
|
||||||
|
content: 补充合作模式合作券领取的实机验证、已领完跳过分支、脚本和汇总表条目。
|
||||||
|
summary: 记录首页、商城、领地和公会的日常任务入口、待完善流程及已实现脚本汇总。入口红点表示内部有奖励可领取;特惠赏金战令、小金猪领取与扫荡、任务奖励、狂鼠之家收矿与雇佣、食堂领体力、合作模式合作券均已实机验证并写成脚本。
|
||||||
---
|
---
|
||||||
|
|
||||||
# 首页、商城、领地与公会功能及日常任务梳理
|
# 首页、商城、领地与公会功能及日常任务梳理
|
||||||
@@ -227,7 +231,7 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完
|
|||||||
| 食堂 | 中部,带食物的建筑 | 一键领取体力已实机验证并有脚本;其他内部功能待补充 |
|
| 食堂 | 中部,带食物的建筑 | 一键领取体力已实机验证并有脚本;其他内部功能待补充 |
|
||||||
| 排行榜 | 右侧中部,石制台座区域 | 用户确认有固定流程,具体步骤待补充 |
|
| 排行榜 | 右侧中部,石制台座区域 | 用户确认有固定流程,具体步骤待补充 |
|
||||||
| 巅峰决斗 | 左上部,旗帜建筑 | 用户确认有固定流程,具体步骤待补充 |
|
| 巅峰决斗 | 左上部,旗帜建筑 | 用户确认有固定流程,具体步骤待补充 |
|
||||||
| 合作模式 | 上方中部,紫色传送门区域 | 用户确认有固定流程,具体步骤待补充 |
|
| 合作模式 | 上方中部,紫色传送门区域 | 合作券领取已实机验证并有脚本;保卫宠物及其他内部功能待补充 |
|
||||||
| 城墙 | 右上部,城门建筑 | 用户确认有固定流程,具体步骤待补充 |
|
| 城墙 | 右上部,城门建筑 | 用户确认有固定流程,具体步骤待补充 |
|
||||||
|
|
||||||
位置仅描述本次截图,不作为固定点击坐标。各栏目排列为地图布局,上表顺序沿用用户的说明,不代表执行顺序。
|
位置仅描述本次截图,不作为固定点击坐标。各栏目排列为地图布局,上表顺序沿用用户的说明,不代表执行顺序。
|
||||||
@@ -264,6 +268,18 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完
|
|||||||
|
|
||||||
脚本先确认食堂内有可领取烤肉,才点击“一键领取”;随后必须识别“恭喜获得”奖励弹框和烤肉库存 0/60,最后关闭食堂并返回首页。没有可领取体力时,会跳过领取并直接返回首页。macOS 已实机验证;Windows 仍需校准和验证。
|
脚本先确认食堂内有可领取烤肉,才点击“一键领取”;随后必须识别“恭喜获得”奖励弹框和烤肉库存 0/60,最后关闭食堂并返回首页。没有可领取体力时,会跳过领取并直接返回首页。macOS 已实机验证;Windows 仍需校准和验证。
|
||||||
|
|
||||||
|
### 合作模式:合作券领取(已实机验证)
|
||||||
|
|
||||||
|
2026-10-04 已在当前账号完成:**首页 → 领地 → 合作模式入口 → 模式选择中的左侧“合作模式” → 左上角“可领取” → 确认奖励弹框 → 确认状态变为“已领完” → 返回领地 → 返回首页**。本次合作券由 17/30 增至 18/30。
|
||||||
|
|
||||||
|
对应脚本为项目根目录的 `claim_cooperation_ticket.py`,配置在 `workflows/cooperation_ticket.json`。运行方式:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python claim_cooperation_ticket.py
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本只选择模式选择页左侧的“合作模式”,不会进入右侧“保卫宠物”。左上角显示“可领取”时才点击并等待奖励弹框;若已显示“已领完”,则跳过领取并依次退出到首页。可使用 `--dry-run` 只验证首页和执行计划,不点击游戏内容。macOS 已实机验证;Windows 仍需校准和验证。
|
||||||
|
|
||||||
## 公会页面
|
## 公会页面
|
||||||
|
|
||||||
### 入口与栏目
|
### 入口与栏目
|
||||||
@@ -306,7 +322,8 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完
|
|||||||
| 商城:关卡礼包、钻石 | 暂不处理 | 用户明确排除 |
|
| 商城:关卡礼包、钻石 | 暂不处理 | 用户明确排除 |
|
||||||
| 领地:狂鼠之家 | 已验证脚本:一键收矿、等待 10 秒、按白银/黄金/彩虹顺序一键雇佣、返回首页 | 当前仅覆盖该日常流程;其他内部功能、重置规律和 Windows 适配待验证 |
|
| 领地:狂鼠之家 | 已验证脚本:一键收矿、等待 10 秒、按白银/黄金/彩虹顺序一键雇佣、返回首页 | 当前仅覆盖该日常流程;其他内部功能、重置规律和 Windows 适配待验证 |
|
||||||
| 领地:食堂 | 已验证脚本:发现一键领取时领体力、确认奖励和库存清空、返回首页 | 当前仅覆盖体力领取;其他内部功能、重置规律和 Windows 适配待验证 |
|
| 领地:食堂 | 已验证脚本:发现一键领取时领体力、确认奖励和库存清空、返回首页 | 当前仅覆盖体力领取;其他内部功能、重置规律和 Windows 适配待验证 |
|
||||||
| 领地:玩法大厅、图鉴、排行榜、巅峰决斗、合作模式、城墙 | 待完善固定流程后确定 | 用户确认各栏目均有固定自动化流程,具体步骤、执行条件、消耗和完成判定尚未补充 |
|
| 领地:合作模式 | 已验证脚本:只选择左侧合作模式,发现左上可领取时领取合作券、确认已领完、返回首页 | 保卫宠物及其他内部功能、重置规律和 Windows 适配待验证 |
|
||||||
|
| 领地:玩法大厅、图鉴、排行榜、巅峰决斗、城墙 | 待完善固定流程后确定 | 用户确认各栏目均有固定自动化流程,具体步骤、执行条件、消耗和完成判定尚未补充 |
|
||||||
| 公会:公会商城、远征大厅、公会任务、公会祝福、每日捐献、成员大厅、公会 Boss、右下角红色角色入口 | 待完善固定流程后确定 | 用户确认八个入口均有固定自动化流程;右下角入口名称及各流程的具体步骤、消耗和完成判定待补充 |
|
| 公会:公会商城、远征大厅、公会任务、公会祝福、每日捐献、成员大厅、公会 Boss、右下角红色角色入口 | 待完善固定流程后确定 | 用户确认八个入口均有固定自动化流程;右下角入口名称及各流程的具体步骤、消耗和完成判定待补充 |
|
||||||
|
|
||||||
初步方向是让脚本执行已验证的重复步骤,让 AI 辅助识别变化页面或异常。是否每次调用 AI、调用到哪一步,待明确任务完成标志与变化范围后再决定。
|
初步方向是让脚本执行已验证的重复步骤,让 AI 辅助识别变化页面或异常。是否每次调用 AI、调用到哪一步,待明确任务完成标志与变化范围后再决定。
|
||||||
@@ -338,3 +355,4 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完
|
|||||||
| 4 | 任务奖励领取(`claim_tasks.py`) | 首页 | 首页 → 右侧任务 → 日常/周常 | 仅在任务红点存在时进入;领取当前面板的可见奖励,另一个标签有自身红点时才切换处理,最后返回首页。 | 2026-10-04 19:11 CST |
|
| 4 | 任务奖励领取(`claim_tasks.py`) | 首页 | 首页 → 右侧任务 → 日常/周常 | 仅在任务红点存在时进入;领取当前面板的可见奖励,另一个标签有自身红点时才切换处理,最后返回首页。 | 2026-10-04 19:11 CST |
|
||||||
| 5 | 收矿与雇佣(`claim_mining.py`) | 领地 | 首页 → 底部领地 → 狂鼠之家 | 发现“一键收矿”后收矿、关闭奖励弹框、等待 10 秒;按白银罗盘、黄金罗盘、彩虹探测仪顺序一键雇佣,无可用道具时关闭弹框,最后返回首页。 | 2026-10-04 18:17 CST |
|
| 5 | 收矿与雇佣(`claim_mining.py`) | 领地 | 首页 → 底部领地 → 狂鼠之家 | 发现“一键收矿”后收矿、关闭奖励弹框、等待 10 秒;按白银罗盘、黄金罗盘、彩虹探测仪顺序一键雇佣,无可用道具时关闭弹框,最后返回首页。 | 2026-10-04 18:17 CST |
|
||||||
| 6 | 领体力(`claim_stamina.py`) | 领地 | 首页 → 底部领地 → 食堂 | 发现“一键领取”时领取体力,确认“恭喜获得”弹框和烤肉库存 0/60 后返回首页;无可领体力时跳过。 | 2026-10-04 18:32 CST |
|
| 6 | 领体力(`claim_stamina.py`) | 领地 | 首页 → 底部领地 → 食堂 | 发现“一键领取”时领取体力,确认“恭喜获得”弹框和烤肉库存 0/60 后返回首页;无可领体力时跳过。 | 2026-10-04 18:32 CST |
|
||||||
|
| 7 | 领取合作券(`claim_cooperation_ticket.py`) | 领地 | 首页 → 底部领地 → 合作模式 → 左侧合作模式 | 左上显示“可领取”时领取合作券,确认奖励和“已领完”后返回首页;已领完时跳过;不会进入保卫宠物。 | 2026-10-04 20:25 CST |
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from automation.cooperation import CooperationRunner
|
||||||
|
from automation.engine import Matcher, Region, load_flow
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
class CooperationDesktop:
|
||||||
|
def __init__(self, images):
|
||||||
|
self.images = images
|
||||||
|
self.index = 0
|
||||||
|
self.clicks = []
|
||||||
|
|
||||||
|
def screenshot(self):
|
||||||
|
return self.images[min(self.index, len(self.images) - 1)]
|
||||||
|
|
||||||
|
def click(self, point, region, size, hold=0.2):
|
||||||
|
self.clicks.append((point, hold))
|
||||||
|
self.index += 1
|
||||||
|
|
||||||
|
|
||||||
|
class CooperationTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.flow = load_flow(ROOT / 'workflows/cooperation_ticket.json')
|
||||||
|
self.matcher = Matcher(ROOT / 'assets')
|
||||||
|
self.region = Region(0, 0, 562, 1002)
|
||||||
|
|
||||||
|
def image(self, state):
|
||||||
|
image = Image.new('RGB', (562, 1002), (42, 58, 73))
|
||||||
|
for spec in self.flow['states'][state]:
|
||||||
|
template = Image.open(ROOT / 'assets' / spec['template']).convert('RGB')
|
||||||
|
x, y, _, _ = spec['search']
|
||||||
|
image.paste(template, (x + 5, y + 5))
|
||||||
|
return image
|
||||||
|
|
||||||
|
def runner(self, states):
|
||||||
|
desktop = CooperationDesktop([self.image(state) for state in states])
|
||||||
|
return desktop, CooperationRunner(desktop, self.matcher, self.flow,
|
||||||
|
lambda _: self.region, log=lambda _: None, timeout=0.8)
|
||||||
|
|
||||||
|
def test_claims_ticket_and_returns_home(self):
|
||||||
|
desktop, runner = self.runner(['home', 'territory', 'selection', 'available', 'reward', 'claimed', 'territory', 'home'])
|
||||||
|
self.assertEqual(runner.run(), 'claimed')
|
||||||
|
expected = ('territory', 'open_cooperation', 'choose_cooperation', 'claim_ticket',
|
||||||
|
'dismiss_reward', 'cooperation_back', 'territory_back')
|
||||||
|
self.assertEqual([point for point, _ in desktop.clicks], [self.flow['clicks'][name] for name in expected])
|
||||||
|
|
||||||
|
def test_already_claimed_skips_ticket_and_returns_home(self):
|
||||||
|
desktop, runner = self.runner(['home', 'territory', 'selection', 'claimed', 'territory', 'home'])
|
||||||
|
self.assertEqual(runner.run(), 'already_claimed')
|
||||||
|
expected = ('territory', 'open_cooperation', 'choose_cooperation', 'cooperation_back', 'territory_back')
|
||||||
|
self.assertEqual([point for point, _ in desktop.clicks], [self.flow['clicks'][name] for name in expected])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "合作模式合作券领取",
|
||||||
|
"states": {
|
||||||
|
"home": [{"template": "home.png", "search": [150, 730, 410, 865]}],
|
||||||
|
"territory": [{"template": "territory_rat_house.png", "search": [15, 440, 220, 570]}],
|
||||||
|
"selection": [{"template": "cooperation_select_title.png", "search": [120, 80, 450, 220]}],
|
||||||
|
"available": [
|
||||||
|
{"template": "cooperation_page_title.png", "search": [150, 45, 430, 165]},
|
||||||
|
{"template": "cooperation_available.png", "search": [0, 125, 150, 330], "threshold": 0.95}
|
||||||
|
],
|
||||||
|
"claimed": [
|
||||||
|
{"template": "cooperation_page_title.png", "search": [150, 45, 430, 165]},
|
||||||
|
{"template": "cooperation_claimed.png", "search": [0, 125, 150, 330], "threshold": 0.95}
|
||||||
|
],
|
||||||
|
"reward": [
|
||||||
|
{"template": "cooperation_reward.png", "search": [120, 220, 440, 370], "threshold": 0.8},
|
||||||
|
{"template": "cooperation_dismiss.png", "search": [145, 610, 420, 730]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"clicks": {
|
||||||
|
"territory": [0.68, 0.94],
|
||||||
|
"open_cooperation": [0.60, 0.18],
|
||||||
|
"choose_cooperation": [0.27, 0.47],
|
||||||
|
"claim_ticket": [0.09, 0.20],
|
||||||
|
"dismiss_reward": [0.50, 0.67],
|
||||||
|
"cooperation_back": [0.12, 0.94],
|
||||||
|
"territory_back": [0.68, 0.94]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user