125 lines
4.4 KiB
Python
125 lines
4.4 KiB
Python
"""Cafe daily task. Ported from baas-reference module/cafe_reward.py's state-probe pattern."""
|
|
|
|
from ba_auto import detector, navigation
|
|
|
|
ROOM_OPEN_RETRIES = 3
|
|
|
|
# "受取" (claim) renders as flat grey when there is nothing to collect yet.
|
|
CLAIM_PROBE = (960, 850)
|
|
CLAIM_DISABLED_RGB = (218, 218, 218)
|
|
CLAIM_DISABLED_TOLERANCE = 15
|
|
|
|
|
|
def _claim_disabled(driver):
|
|
r, g, b = driver.color_at(*CLAIM_PROBE)
|
|
tr, tg, tb = CLAIM_DISABLED_RGB
|
|
return (
|
|
abs(r - tr) <= CLAIM_DISABLED_TOLERANCE
|
|
and abs(g - tg) <= CLAIM_DISABLED_TOLERANCE
|
|
and abs(b - tb) <= CLAIM_DISABLED_TOLERANCE
|
|
)
|
|
|
|
|
|
def _enter_room(driver, coords):
|
|
for attempt in range(1, ROOM_OPEN_RETRIES + 1):
|
|
driver.click(*coords)
|
|
driver.wait(3)
|
|
if navigation.is_on_subscreen(driver):
|
|
# dismiss the "visited student list" notice shown on room entry
|
|
driver.keypress("Return")
|
|
driver.wait(1)
|
|
return True
|
|
print(f"[cafe] room not detected after click (attempt {attempt}/{ROOM_OPEN_RETRIES})")
|
|
return False
|
|
|
|
|
|
def _dismiss_rank_up_if_shown(driver, config):
|
|
# The reference's own to_cafe() navigation (module/cafe_reward.py) treats
|
|
# 'relationship_rank_up' as a recognized, reactively-dismissed popup
|
|
# after every pat round -- this loop's original port had no equivalent,
|
|
# so a rank-up cutscene just sat there while find_cafe_sparkle() kept
|
|
# returning None against it (a full-screen character portrait, nothing
|
|
# like the sparkle template) for the rest of the room's click budget.
|
|
# That's the "freeze" -- not a timing fluke, a genuinely unhandled state.
|
|
for _ in range(config.CAFE_RANK_UP_DISMISS_RETRIES):
|
|
if navigation.is_on_subscreen(driver):
|
|
return True
|
|
driver.keypress("Return")
|
|
driver.wait(1.5)
|
|
return navigation.is_on_subscreen(driver)
|
|
|
|
|
|
def _pat_room(driver, config):
|
|
# Sparkles appear on a per-student cooldown, so most single checks find
|
|
# nothing -- the old Bash loop (and an earlier version of this one) gave
|
|
# up on the very first miss, which meant it essentially never farmed.
|
|
# Keep polling for the full budget instead of bailing early.
|
|
patted = 0
|
|
for _ in range(config.CAFE_MAX_CLICKS_PER_ROOM):
|
|
match = detector.find_cafe_sparkle()
|
|
if match is None:
|
|
driver.wait(1)
|
|
continue
|
|
x, y, score = match
|
|
driver.click(x, y)
|
|
driver.wait(1)
|
|
driver.keypress("Return")
|
|
# park the cursor away from the sparkle area so it can't occlude the
|
|
# next detection screenshot (see screenshots/cafe/sparkle/02_*_cursor_on_head.png)
|
|
driver.move_mouse(10, 1190)
|
|
if not _dismiss_rank_up_if_shown(driver, config):
|
|
print("[cafe] warning: cafe screen not confirmed after a pat (rank-up cutscene stuck?) -- stopping this room's pat loop rather than clicking blindly")
|
|
break
|
|
patted += 1
|
|
print(f"[cafe] patted sparkle at ({x}, {y}), score={score:.3f}")
|
|
print(f"[cafe] patted {patted} sparkle(s)" if patted else "[cafe] no sparkle found")
|
|
|
|
|
|
def _claim_income(driver, config):
|
|
driver.click(*config.CAFE_INCOME)
|
|
driver.wait(2)
|
|
if not navigation.is_modal_open(driver):
|
|
print("[cafe] income panel not detected, skipping claim")
|
|
return
|
|
|
|
if _claim_disabled(driver):
|
|
print("[cafe] nothing to claim")
|
|
else:
|
|
print("[cafe] claiming income")
|
|
driver.keypress("Return")
|
|
driver.wait(2)
|
|
driver.keypress("Return")
|
|
driver.wait(2)
|
|
|
|
if navigation.is_modal_open(driver):
|
|
driver.keypress("Escape")
|
|
driver.wait(1.5)
|
|
|
|
|
|
def run(driver, config):
|
|
driver.focus_game()
|
|
|
|
if not _enter_room(driver, config.CAFE_ICON):
|
|
print("[cafe] could not confirm cafe is open, aborting without pressing further keys")
|
|
return
|
|
|
|
print("[cafe] room 1: farming affection")
|
|
_pat_room(driver, config)
|
|
|
|
if not _enter_room(driver, config.CAFE_ROOM_SWITCH):
|
|
print("[cafe] could not confirm room switch, stopping before income claim")
|
|
if navigation.is_on_subscreen(driver):
|
|
driver.keypress("Escape")
|
|
driver.wait(1.5)
|
|
return
|
|
|
|
print("[cafe] room 2: farming affection")
|
|
_pat_room(driver, config)
|
|
|
|
_claim_income(driver, config)
|
|
|
|
if navigation.is_on_subscreen(driver):
|
|
driver.keypress("Escape")
|
|
driver.wait(1.5)
|
|
print("[cafe] Done.")
|