diff --git a/assets/hero_rally_dot.png b/assets/hero_rally_dot.png new file mode 100644 index 0000000..26fd107 Binary files /dev/null and b/assets/hero_rally_dot.png differ diff --git a/assets/hero_rally_title.png b/assets/hero_rally_title.png new file mode 100644 index 0000000..3b47c52 Binary files /dev/null and b/assets/hero_rally_title.png differ diff --git a/assets/lizhu_activity_dot.png b/assets/lizhu_activity_dot.png new file mode 100644 index 0000000..009da2c Binary files /dev/null and b/assets/lizhu_activity_dot.png differ diff --git a/assets/lizhu_activity_title.png b/assets/lizhu_activity_title.png new file mode 100644 index 0000000..e99838b Binary files /dev/null and b/assets/lizhu_activity_title.png differ diff --git a/assets/lizhu_claim.png b/assets/lizhu_claim.png new file mode 100644 index 0000000..42389e0 Binary files /dev/null and b/assets/lizhu_claim.png differ diff --git a/assets/lizhu_daily_tab.png b/assets/lizhu_daily_tab.png new file mode 100644 index 0000000..57394e2 Binary files /dev/null and b/assets/lizhu_daily_tab.png differ diff --git a/assets/lizhu_dismiss.png b/assets/lizhu_dismiss.png new file mode 100644 index 0000000..69adc31 Binary files /dev/null and b/assets/lizhu_dismiss.png differ diff --git a/assets/lizhu_reward.png b/assets/lizhu_reward.png new file mode 100644 index 0000000..91c74ea Binary files /dev/null and b/assets/lizhu_reward.png differ diff --git a/assets/lizhu_tasks_button.png b/assets/lizhu_tasks_button.png new file mode 100644 index 0000000..37a790a Binary files /dev/null and b/assets/lizhu_tasks_button.png differ diff --git a/assets/lizhu_title.png b/assets/lizhu_title.png new file mode 100644 index 0000000..110d40e Binary files /dev/null and b/assets/lizhu_title.png differ diff --git a/assets/sword_gift_available.png b/assets/sword_gift_available.png new file mode 100644 index 0000000..29274e3 Binary files /dev/null and b/assets/sword_gift_available.png differ diff --git a/assets/sword_gift_claimed.png b/assets/sword_gift_claimed.png new file mode 100644 index 0000000..81cbaa1 Binary files /dev/null and b/assets/sword_gift_claimed.png differ diff --git a/assets/sword_gift_dismiss.png b/assets/sword_gift_dismiss.png new file mode 100644 index 0000000..8d43f90 Binary files /dev/null and b/assets/sword_gift_dismiss.png differ diff --git a/assets/sword_gift_reward.png b/assets/sword_gift_reward.png new file mode 100644 index 0000000..0f1e980 Binary files /dev/null and b/assets/sword_gift_reward.png differ diff --git a/assets/sword_gift_title.png b/assets/sword_gift_title.png new file mode 100644 index 0000000..e5cb786 Binary files /dev/null and b/assets/sword_gift_title.png differ diff --git a/automation/hero_rally.py b/automation/hero_rally.py new file mode 100644 index 0000000..706f25c --- /dev/null +++ b/automation/hero_rally.py @@ -0,0 +1,83 @@ +"""英雄集结中剑来献礼与骊珠画卷领奖流程。""" +import time +from .engine import WorkflowError + + +class HeroRallyRunner: + def __init__(self, desktop, matcher, flow, region_provider, log=print, timeout=12): + self.desktop, self.matcher, self.flow = desktop, matcher, flow + self.region_provider, self.log, self.timeout = region_provider, log, timeout + + def observe(self): + shot = self.desktop.screenshot(); region = self.region_provider(shot) + return shot, region, self.matcher.frame(shot, region) + + def wait(self, *names): + deadline = time.monotonic() + self.timeout; previous = None; stable = 0 + while time.monotonic() < deadline: + shot, region, frame = self.observe() + found = next((n for n in names if self.matcher.matches(frame, self.flow['states'][n])), None) + stable = stable + 1 if found == previous and found else (1 if found else 0); previous = found + if stable >= 2: return found, shot, region, frame + time.sleep(.25) + raise WorkflowError('未识别到预期页面 ' + '/'.join(names) + ',已停止。') + + def click(self, point, obs): + _, shot, region, _ = obs + if self.region_provider(shot) != region: raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。') + self.desktop.click(point, region, shot.size, hold=.8) + + def action(self, name, obs): self.click(self.flow['clicks'][name], obs); self.log('已执行:' + name) + + def rally(self, home): self.action('open_rally', home); return self.wait('rally') + def close_home(self, rally): self.action('close_rally', rally); self.wait('home'); self.log('已确认返回首页。') + + +class SwordGiftRunner(HeroRallyRunner): + def run(self, dry_run=False): + home = self.wait('home') + if dry_run: self.log('首页识别通过。计划:英雄集结 → 剑来献礼红点 → 一键领取 → 返回首页。未点击。'); return 'preview' + rally = self.rally(home) + if not self.matcher.matches(rally[3], self.flow['sword_dot']): + self.log('剑来献礼没有红点,本次跳过。'); self.close_home(rally); return 'no_dot' + self.action('open_sword', rally); page = self.wait('sword_available', 'sword_claimed') + if page[0] == 'sword_claimed': + self.log('剑来献礼已领完,本次跳过。') + else: + self.action('claim_sword', page); reward = self.wait('reward'); self.action('dismiss_reward', reward); page = self.wait('sword_claimed') + self.action('sword_back', page); self.close_home(self.wait('rally')); return 'claimed' + + +class LizhuTaskRunner(HeroRallyRunner): + def claim_button(self, frame): + spec = self.flow['claim_button']; score, loc = self.matcher.locate(frame, spec) + if score < spec.get('threshold', .86): return None + x, y, w, h = loc; return ((x+w/2)/562, (y+h/2)/1002) + + def claim_panel(self, panel): + count = 0 + while count < self.flow['max_claims_per_panel']: + obs = self.wait(panel); point = self.claim_button(obs[3]) + if not point: return count + self.click(point, obs); count += 1; self.log('已执行:%s 第 %s 个领取按钮。' % (panel, count)) + self.action('dismiss_reward', self.wait('reward')) + raise WorkflowError('单个面板领取次数超过安全上限,已停止。') + + def run(self, dry_run=False): + home = self.wait('home') + if dry_run: self.log('首页识别通过。计划:英雄集结 → 骊珠画卷红点 → 活动/每日任务奖励 → 返回首页。未点击。'); return 'preview' + rally = self.rally(home) + if not self.matcher.matches(rally[3], self.flow['lizhu_dot']): + self.log('骊珠画卷没有红点,本次跳过。'); self.close_home(rally); return 'no_dot' + self.action('open_lizhu', rally); lizhu = self.wait('lizhu'); self.action('open_tasks', lizhu) + panel = self.wait('activity', 'daily'); total = 0 + if self.matcher.matches(panel[3], self.flow['tab_dots']['activity']): total += self.claim_panel('activity') + else: self.log('活动任务没有红点,不领取。') + panel = self.wait('activity', 'daily') + if self.matcher.matches(panel[3], self.flow['tab_dots']['daily']): + if panel[0] != 'daily': self.action('open_daily', panel) + total += self.claim_panel('daily') + else: self.log('每日任务没有红点,不切换。') + panel = self.wait('activity', 'daily'); self.action('close_tasks', panel) + self.action('lizhu_back', self.wait('lizhu')); self.close_home(self.wait('rally')) + return 'claimed' if total else 'no_claim_button' diff --git a/claim_lizhu_task_rewards.py b/claim_lizhu_task_rewards.py new file mode 100644 index 0000000..fdf5947 --- /dev/null +++ b/claim_lizhu_task_rewards.py @@ -0,0 +1,14 @@ +import argparse, sys, time +from pathlib import Path +from automation.engine import Matcher, load_flow +from automation.offers import MacGameDesktop +from automation.hero_rally import LizhuTaskRunner +ROOT=Path(__file__).resolve().parent +def main(): + p=argparse.ArgumentParser(description='英雄集结骊珠画卷:领取任务奖励'); p.add_argument('--dry-run',action='store_true'); a=p.parse_args() + if sys.platform!='darwin': return 2 + try: + d=MacGameDesktop(); d.activate(); print('3 秒后开始;请保持游戏在前台。',flush=True); time.sleep(3) + LizhuTaskRunner(d,Matcher(ROOT/'assets'),load_flow(ROOT/'workflows/lizhu_task_claim.json'),d.region_for).run(a.dry_run); return 0 + except Exception as e: print('流程停止:'+str(e),file=sys.stderr); return 1 +if __name__=='__main__': raise SystemExit(main()) diff --git a/claim_sword_gift_points.py b/claim_sword_gift_points.py new file mode 100644 index 0000000..a0b0bab --- /dev/null +++ b/claim_sword_gift_points.py @@ -0,0 +1,14 @@ +import argparse, sys, time +from pathlib import Path +from automation.engine import Matcher, load_flow +from automation.offers import MacGameDesktop +from automation.hero_rally import SwordGiftRunner +ROOT=Path(__file__).resolve().parent +def main(): + p=argparse.ArgumentParser(description='英雄集结剑来献礼:领取积分奖励'); p.add_argument('--dry-run',action='store_true'); a=p.parse_args() + if sys.platform!='darwin': return 2 + try: + d=MacGameDesktop(); d.activate(); print('3 秒后开始;请保持游戏在前台。',flush=True); time.sleep(3) + SwordGiftRunner(d,Matcher(ROOT/'assets'),load_flow(ROOT/'workflows/sword_gift_claim.json'),d.region_for).run(a.dry_run); return 0 + except Exception as e: print('流程停止:'+str(e),file=sys.stderr); return 1 +if __name__=='__main__': raise SystemExit(main()) diff --git a/workflows/lizhu_task_claim.json b/workflows/lizhu_task_claim.json new file mode 100644 index 0000000..652bb58 --- /dev/null +++ b/workflows/lizhu_task_claim.json @@ -0,0 +1 @@ +{"name":"骊珠画卷任务奖励","max_claims_per_panel":20,"states":{"home":[{"template":"home.png","search":[150,730,410,865]}],"rally":[{"template":"hero_rally_title.png","search":[120,0,450,130]}],"lizhu":[{"template":"lizhu_title.png","search":[100,35,460,170]},{"template":"lizhu_tasks_button.png","search":[320,880,480,1002]}],"activity":[{"template":"lizhu_activity_title.png","search":[70,75,390,180]}],"daily":[{"template":"lizhu_daily_tab.png","search":[270,790,480,900]}],"reward":[{"template":"lizhu_reward.png","search":[120,180,440,390],"threshold":0.8},{"template":"lizhu_dismiss.png","search":[145,600,420,730]}]},"lizhu_dot":[{"template":"hero_rally_dot.png","search":[225,630,275,690]}],"claim_button":{"template":"lizhu_claim.png","search":[340,200,520,800]},"tab_dots":{"activity":{"template":"lizhu_activity_dot.png","search":[235,790,295,855]},"daily":{"template":"hero_rally_dot.png","search":[420,790,480,855]}},"clicks":{"open_rally":[0.90,0.43],"close_rally":[0.90,0.15],"open_lizhu":[0.33,0.65],"open_tasks":[0.70,0.94],"open_daily":[0.67,0.85],"dismiss_reward":[0.50,0.67],"close_tasks":[0.92,0.11],"lizhu_back":[0.12,0.94]}} diff --git a/workflows/sword_gift_claim.json b/workflows/sword_gift_claim.json new file mode 100644 index 0000000..49f8128 --- /dev/null +++ b/workflows/sword_gift_claim.json @@ -0,0 +1 @@ +{"name":"剑来献礼积分奖励","states":{"home":[{"template":"home.png","search":[150,730,410,865]}],"rally":[{"template":"hero_rally_title.png","search":[120,0,450,130]}],"sword_available":[{"template":"sword_gift_title.png","search":[0,35,330,190]},{"template":"sword_gift_available.png","search":[0,760,220,890]}],"sword_claimed":[{"template":"sword_gift_title.png","search":[0,35,330,190]},{"template":"sword_gift_claimed.png","search":[0,760,220,890]}],"reward":[{"template":"sword_gift_reward.png","search":[120,180,440,390],"threshold":0.8},{"template":"sword_gift_dismiss.png","search":[145,600,420,730]}]},"sword_dot":[{"template":"hero_rally_dot.png","search":[235,340,285,395]}],"clicks":{"open_rally":[0.90,0.43],"close_rally":[0.90,0.15],"open_sword":[0.33,0.39],"claim_sword":[0.18,0.82],"dismiss_reward":[0.50,0.67],"sword_back":[0.12,0.94]}}