feat(stamina): Implement daily gem reward claiming logic and fix button detection

This commit is contained in:
Nik Afiq 2026-07-16 14:07:12 +09:00
parent 5a78e8ef11
commit 3c47fabd87
4 changed files with 78 additions and 5 deletions

View File

@ -23,6 +23,25 @@ MISSION_ICON = (75, 350)
# grey (r-b<20) when not. Calibrated live at 1920x1200. # grey (r-b<20) when not. Calibrated live at 1920x1200.
MISSION_CLAIM_PROBE = (1600, 1100) MISSION_CLAIM_PROBE = (1600, 1100)
# Separate "デイリーミッションを8回クリア" (clear 8 daily missions) summary
# bar's own 受取 (claim) button, ported from the reference's own
# `collect_daily_task_power.py::implement` -- it checks TWO distinct
# button regions, not one: the main 一括受取 area (MISSION_CLAIM_PROBE
# above) is drained in a loop first, then this second, separate button
# (reference's own "claim daily pyroxenes" region, positioned to the LEFT
# of the main claim-all button in both the reference's layout and this
# client's) is checked independently -- 一括受取 does NOT also claim this,
# confirmed live (2026-07-16): a real account state showed this button
# still claimable (a gem x20 reward) after 一括受取's own area had already
# gone grey. Also unlike 一括受取, this button has no Enter keybind shown
# on screen, so it needs an actual coordinate click rather than a keypress
# (matching the reference's own raw click here too, `self.click(976, 670,
# ...)`, rather than the img_reactions-driven Enter used elsewhere).
# Same bright-yellow-vs-grey signature as MISSION_CLAIM_PROBE, confirmed
# live at this exact point; pixel-scanned to sit clearly inside the button
# and off the "受取" text glyphs (which read near-black, ~(75,33,22)).
MISSION_DAILY_GEM_CLAIM_BUTTON = (1400, 1095)
CAFE_ICON = (165, 1100) CAFE_ICON = (165, 1100)
CAFE_ROOM_SWITCH = (190, 160) CAFE_ROOM_SWITCH = (190, 160)
CAFE_INCOME = (1780, 1105) CAFE_INCOME = (1780, 1105)

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,15 @@
"""Mission/task-menu AP+pyroxene claim. Ported from baas-reference module/collect_daily_task_power.py's claim-loop pattern (its rgb_in_range checks replaced with driver.color_at probes).""" """Mission/task-menu AP+pyroxene claim. Ported from baas-reference module/collect_daily_task_power.py's claim-loop pattern (its rgb_in_range checks replaced with driver.color_at probes).
The reference's `implement` checks TWO separate button regions, not one:
the main 一括受取 (claim-all) area is drained in a loop first, then a
second, separate "claim daily pyroxenes" button is checked and clicked
independently -- 一括受取 does not also claim it. This was originally
missed here (only the first region was ported), reported live by the user
against a real account state where the second button ("デイリーミッショ
ンを8回クリア", a gem x20 reward) was still showing claimable after
一括受取 had already gone grey -- see config.py's
`MISSION_DAILY_GEM_CLAIM_BUTTON` for the live-calibration writeup.
"""
from ba_auto import navigation from ba_auto import navigation
@ -6,20 +17,46 @@ OPEN_RETRIES = 3
# One claim-all round is normally enough (button goes grey right after), but # One claim-all round is normally enough (button goes grey right after), but
# bound it in case multiple reward reveals need dismissing in sequence. # bound it in case multiple reward reveals need dismissing in sequence.
CLAIM_MAX_ROUNDS = 5 CLAIM_MAX_ROUNDS = 5
DAILY_GEM_CLAIM_MAX_ATTEMPTS = 3
# Yellow "one-click claim all" button vs. its own flat-grey disabled state -- # Yellow "one-click claim all" button vs. its own flat-grey disabled state --
# same background-pixel-probe idea as mailbox.CLAIM_ALL_PROBE/cafe.CLAIM_PROBE, # same background-pixel-probe idea as mailbox.CLAIM_ALL_PROBE/cafe.CLAIM_PROBE,
# but distinguished by hue (yellow has a big r-b gap; grey doesn't) since the # but distinguished by hue (yellow has a big r-b gap; grey doesn't) since the
# button's grey isn't a single fixed RGB the way mailbox/cafe's are. # button's grey isn't a single fixed RGB the way mailbox/cafe's are. Also
# shared by the separate daily-gem claim button (config.MISSION_DAILY_GEM_CLAIM_BUTTON),
# which uses the same bright-yellow-vs-grey visual style.
CLAIM_ENABLED_MIN_R = 230 CLAIM_ENABLED_MIN_R = 230
CLAIM_ENABLED_MIN_RB_GAP = 100 CLAIM_ENABLED_MIN_RB_GAP = 100
def _claim_enabled(driver, config): def _button_claimable(driver, probe):
r, g, b = driver.color_at(*config.MISSION_CLAIM_PROBE) r, g, b = driver.color_at(*probe)
return r > CLAIM_ENABLED_MIN_R and (r - b) > CLAIM_ENABLED_MIN_RB_GAP return r > CLAIM_ENABLED_MIN_R and (r - b) > CLAIM_ENABLED_MIN_RB_GAP
def _claim_enabled(driver, config):
return _button_claimable(driver, config.MISSION_CLAIM_PROBE)
def _daily_gem_claimable(driver, config):
return _button_claimable(driver, config.MISSION_DAILY_GEM_CLAIM_BUTTON)
def _claim_daily_gem(driver, config):
for attempt in range(1, DAILY_GEM_CLAIM_MAX_ATTEMPTS + 1):
driver.click(*config.MISSION_DAILY_GEM_CLAIM_BUTTON)
driver.wait(1.5)
# Dismiss the reward-reveal popup the same way the main claim-all
# loop does below -- this button has no Enter keybind of its own,
# but Enter still dismisses the reward card that appears after.
driver.keypress("Return")
driver.wait(1.5)
if not _daily_gem_claimable(driver, config):
return True
print(f"[stamina] daily gem reward still showing claimable after click (attempt {attempt}/{DAILY_GEM_CLAIM_MAX_ATTEMPTS})")
return not _daily_gem_claimable(driver, config)
def run(driver, config): def run(driver, config):
driver.focus_game() driver.focus_game()
@ -50,6 +87,13 @@ def run(driver, config):
driver.wait(1.5) driver.wait(1.5)
claimed_any = True claimed_any = True
if _daily_gem_claimable(driver, config):
print("[stamina] claiming daily gem mission reward")
if _claim_daily_gem(driver, config):
claimed_any = True
else:
print("[stamina] warning: could not confirm the daily gem reward was claimed")
if not claimed_any: if not claimed_any:
print("[stamina] nothing to claim") print("[stamina] nothing to claim")

10
plan.md
View File

@ -388,6 +388,16 @@ Not yet done: Group/Club AP, and Daily Free Power (see above).
The 青輝石購入 menu deliberately left unautomated above turned out to gate a real-money purchase UI in general, but the specific card this was always about (`module/collect_daily_free_power.py`'s 毎日無料パッケージ, 0 yen, AP+credits) is safe to automate on its own — it's a fixed, non-selectable, always-free claim with no purchase decision to make, same category as this phase's own Mission-panel claim. Built as a separate `ba_auto/tasks/gem_shop.py` rather than folded into `stamina.py`, since it's a different reference file/UI entry point. See Phase 16. The 青輝石購入 menu deliberately left unautomated above turned out to gate a real-money purchase UI in general, but the specific card this was always about (`module/collect_daily_free_power.py`'s 毎日無料パッケージ, 0 yen, AP+credits) is safe to automate on its own — it's a fixed, non-selectable, always-free claim with no purchase decision to make, same category as this phase's own Mission-panel claim. Built as a separate `ba_auto/tasks/gem_shop.py` rather than folded into `stamina.py`, since it's a different reference file/UI entry point. See Phase 16.
#### Phase 8 follow-up #2: daily gem reward wasn't being claimed (2026-07-16)
Reported live by the user, with a saved reference screenshot (`screenshots/stamina/daily gem available.png`): stamina "should collect all reward in the mission, but it didn't collect the daily gem reward."
Re-reading `module/collect_daily_task_power.py::implement` (already the reference for this task, but not fully ported the first time) confirmed the gap: it checks TWO separate button regions, not one. The main 一括受取 area is drained in a loop first (region A, the only one originally ported into `stamina.py`); then, independently, a second, separate button (region B, the reference's own "claim daily pyroxenes" check, positioned to the left of region A in both the reference's raw coordinates and this client's own layout) is checked and clicked -- 一括受取 does not also claim it. Live on nik-gpu, this second button corresponds to the "デイリーミッションを8回クリア" (clear 8 daily missions) summary bar's own 受取 button, a gem x20 reward, which was confirmed still showing claimable after 一括受取 had already gone flat grey.
Ported region B as `MISSION_DAILY_GEM_CLAIM_BUTTON` (config.py), pixel-scanned live at native 1920x1200 (button spans roughly x=1385-1535, y=1080-1165; click point `(1400, 1095)` confirmed clean and off the "受取" text glyphs, which read near-black). Reuses the exact same bright-yellow-vs-grey color signature already established for `MISSION_CLAIM_PROBE` (`CLAIM_ENABLED_MIN_R`/`CLAIM_ENABLED_MIN_RB_GAP`, refactored into a shared `_button_claimable` helper rather than duplicated). Unlike 一括受取, this button shows no Enter keybind on screen, so it's claimed via an actual `driver.click` (matching the reference's own raw `self.click(976, 670, ...)` for this exact region) followed by an Enter press to dismiss the reward-reveal card, wrapped in a click-then-verify bounded retry (`_claim_daily_gem`, `DAILY_GEM_CLAIM_MAX_ATTEMPTS = 3`) matching this project's established discipline rather than trusting an unverified click.
**Confirmed live** via the real `./ba_dailies.sh stamina` CLI path against a genuinely claimable reward (the account's real daily-mission-count tracker was at 8/8, gem reward pending): gem balance went from 12,384 → 12,404 -- exactly the reward's own stated +20 -- with the existing main claim-all also firing normally in the same run, and a clean, warning-free return to the true home screen afterward.
### Phase 9: Normal/Hard story AP sweep ### Phase 9: Normal/Hard story AP sweep
**Status: Done.** Read `module/explore_tasks/sweep_task.py` and `module/explore_tasks/task_utils.py` — the reference flow reads the current region number and matches stage-name text via OCR (`swipe_search_target_str`) to navigate to a configured target stage, then runs a per-stage claim loop. This client exposes a much simpler path to the same goal (burn AP via already-3-starred stages) that avoids porting the OCR-based lookup entirely: each stage's own 任務情報 (task info) modal has a self-contained 掃討 (sweep) sub-panel with a MIN/-/+/MAX count stepper and a start button. **Status: Done.** Read `module/explore_tasks/sweep_task.py` and `module/explore_tasks/task_utils.py` — the reference flow reads the current region number and matches stage-name text via OCR (`swipe_search_target_str`) to navigate to a configured target stage, then runs a per-stage claim loop. This client exposes a much simpler path to the same goal (burn AP via already-3-starred stages) that avoids porting the OCR-based lookup entirely: each stage's own 任務情報 (task info) modal has a self-contained 掃討 (sweep) sub-panel with a MIN/-/+/MAX count stepper and a start button.