169 lines
6.7 KiB
Python
169 lines
6.7 KiB
Python
"""商城英雄招募的限时 UP 神灯任务与免费神灯领取。"""
|
|
import time
|
|
|
|
from .engine import WorkflowError
|
|
|
|
|
|
class RecruitRunner:
|
|
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):
|
|
end = time.monotonic() + self.timeout
|
|
previous, stable = None, 0
|
|
while time.monotonic() < end:
|
|
shot = self.desktop.screenshot()
|
|
region = self.region_provider(shot)
|
|
frame = self.matcher.frame(shot, 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, shot, region, frame
|
|
time.sleep(.25)
|
|
raise WorkflowError('未识别到预期页面 ' + '/'.join(names) + ',已停止。')
|
|
|
|
def observe(self):
|
|
shot = self.desktop.screenshot()
|
|
region = self.region_provider(shot)
|
|
return shot, region, self.matcher.frame(shot, region)
|
|
|
|
def click(self, name, observation, hold=.8):
|
|
shot, region, _ = observation[-3:]
|
|
if self.region_provider(shot) != region:
|
|
raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。')
|
|
self.desktop.click(self.flow['clicks'][name], region, shot.size, hold=hold)
|
|
self.log('已执行:' + name)
|
|
|
|
def click_location(self, location, observation):
|
|
shot, region, _ = observation[-3:]
|
|
x, y, width, height = location
|
|
self.desktop.click(((x + width / 2) / 562, (y + height / 2) / 1002),
|
|
region, shot.size, hold=.8)
|
|
|
|
def swipe(self, name, observation):
|
|
shot, region, _ = observation[-3:]
|
|
start, end = self.flow['swipes'][name]
|
|
self.desktop.drag(start, end, region, shot.size)
|
|
time.sleep(self.flow.get('swipe_settle_seconds', .5))
|
|
|
|
def has_dot(self, frame, spec_name, search=None):
|
|
spec = dict(self.flow[spec_name])
|
|
if search:
|
|
spec['search'] = search
|
|
score, _ = self.matcher.locate(frame, spec)
|
|
return score >= spec.get('threshold', .86)
|
|
|
|
def task_buttons_with_dot(self, frame):
|
|
buttons = self.matcher.locate_all(frame, self.flow['task_button'], limit=8)
|
|
for _, location in buttons:
|
|
x, y, width, _ = location
|
|
# The red dot sits just above the task-button's top-right corner.
|
|
dot_search = [x + width - 32, max(0, y - 31), x + width + 8, y + 5]
|
|
if self.has_dot(frame, 'card_task_dot', dot_search):
|
|
yield location
|
|
|
|
def dismiss_reward(self):
|
|
reward = self.wait('reward')
|
|
self.click('dismiss_reward', reward, hold=.2)
|
|
self.wait('lamp')
|
|
|
|
def claim_visible_tasks(self):
|
|
claimed = 0
|
|
empty_scrolls = 0
|
|
while claimed < self.flow['limits']['task_claims']:
|
|
lamp = self.wait('lamp')
|
|
spec = self.flow['task_claim']
|
|
matches = self.matcher.locate_all(lamp[3], spec, limit=8)
|
|
if matches:
|
|
_, location = matches[0]
|
|
self.click_location(location, lamp)
|
|
self.log('已领取神灯任务奖励。')
|
|
self.dismiss_reward()
|
|
claimed += 1
|
|
empty_scrolls = 0
|
|
continue
|
|
if empty_scrolls >= self.flow['limits']['task_scrolls']:
|
|
break
|
|
self.swipe('task_list_up', lamp)
|
|
empty_scrolls += 1
|
|
return claimed
|
|
|
|
def process_lamp(self):
|
|
lamp = self.wait('lamp')
|
|
if self.has_dot(lamp[3], 'task_tab_dot'):
|
|
self.click('task_tab', lamp)
|
|
self.wait('lamp')
|
|
self.claim_visible_tasks()
|
|
|
|
lamp = self.wait('lamp')
|
|
if self.has_dot(lamp[3], 'gift_tab_dot'):
|
|
self.click('gift_tab', lamp)
|
|
gift = self.wait('gift')
|
|
score, location = self.matcher.locate(gift[3], self.flow['free_button'])
|
|
if score >= self.flow['free_button'].get('threshold', .86):
|
|
self.click_location(location, gift)
|
|
self.log('已领取免费神灯。')
|
|
self.dismiss_reward()
|
|
else:
|
|
self.log('神灯礼包没有可领取的免费神灯,跳过。')
|
|
|
|
lamp = self.wait('lamp')
|
|
self.click('close_lamp', lamp, hold=.2)
|
|
time.sleep(.5)
|
|
|
|
def return_home(self):
|
|
observation = self.observe()
|
|
self.click('shop_back', observation, hold=.2)
|
|
self.wait('home')
|
|
self.log('已确认返回首页。')
|
|
|
|
def run(self, dry_run=False):
|
|
home = self.wait('home')
|
|
if dry_run:
|
|
self.log('首页识别通过。计划:商城 → 英雄招募限时 UP → 神灯任务/礼包 → 返回首页。未点击。')
|
|
return 'preview'
|
|
|
|
self.click('shop', home, hold=.8)
|
|
shop = self.wait('shop')
|
|
for _ in range(self.flow['limits']['scroll_to_recruit']):
|
|
self.swipe('shop_down', shop)
|
|
shop = self.observe()
|
|
if self.matcher.locate_all(shop[2], self.flow['task_button'], limit=1):
|
|
break
|
|
|
|
handled, blank_pages = 0, 0
|
|
while handled < self.flow['limits']['cards']:
|
|
_, _, frame = self.observe()
|
|
task_button = next(self.task_buttons_with_dot(frame), None)
|
|
if task_button:
|
|
# Click the button itself, rather than its red dot.
|
|
x, y, width, height = task_button
|
|
shot, region, current = self.observe()
|
|
self.desktop.click(((x + width / 2) / 562, (y + height / 2) / 1002),
|
|
region, shot.size, hold=.8)
|
|
self.log('已打开限时 UP 卡片的神灯召唤。')
|
|
self.process_lamp()
|
|
handled += 1
|
|
blank_pages = 0
|
|
continue
|
|
if blank_pages >= self.flow['limits']['card_pages']:
|
|
break
|
|
# The card stack precedes 图腾寻宝. Pulling the list down shows
|
|
# earlier UP cards without ever touching the treasure buttons.
|
|
self.swipe('shop_down', self.observe())
|
|
blank_pages += 1
|
|
|
|
self.return_home()
|
|
if handled:
|
|
self.log('已处理 %s 张限时 UP 卡片。' % handled)
|
|
return 'claimed'
|
|
self.log('没有带红点的限时 UP 任务卡片,未领取奖励。')
|
|
return 'no_card_dot'
|