Nik Afiq 8d3da6e6e1 Refactor gem shop task for UI redesign and improve dialog handling
- Recalibrated gem shop task after client UI update, replacing the old overlay dialog with a full subscreen.
- Removed custom dialog open/close checks in favor of shared navigation functions.
- Updated method names and logic to reflect new tab structure and improve clarity.
- Enhanced error handling for dialog closure and navigation return to home.
- Verified live against real account states to ensure functionality and correctness.
2026-07-29 21:23:49 +09:00

125 lines
5.6 KiB
Python

"""Gem shop (青輝石購入) daily free package. Ports module/collect_daily_free_power.py.
Reference flow (`implement`): to_main_page -> to_purchase_pyroxenes_menu
(open the dialog via the home-screen 青輝石購入 icon) -> to_purchase_type
("package") -> detect_free_power_availability -> if purchasable,
collect_daily_free_power (click the free card, confirm the 0-yen purchase,
wait for the reward) else log "already collected" -> return_to_main_page
(close the dialog). Skips entirely on CN server (JP/Global only) -- no
equivalent server concept in this project, so not ported.
The reference detects every step via image template matching (no OCR
anywhere in this flow) -- this port uses plain color probes for the same
states instead (see config.py's "Gem shop daily free package" section for
the full live-calibration writeup and exact pixel values), matching this
project's own established equivalent for a simple, high-contrast
state-A/state-B visual difference, same as cafe.py's CLAIM_DISABLED_RGB or
stamina.py's MISSION_CLAIM_PROBE.
Live-calibrated 2026-07-15 by manually driving the real dialog end-to-end
on nik-gpu, which genuinely claimed the account's real free package for the
day (0 yen, confirmed +10 AP / +10,000 credits via before/after counter
values) -- see scratchpad/gem_shop_*.png for the captures. That exercised
the "available -> claim it" path for real, via raw xdotool/scrot rather
than this actual module.
RECALIBRATED 2026-07-29 after a real Blue Archive client UI update replaced
the old 3-top-tab overlay dialog (期間限定/青輝石/パッケージ) with a real
full subscreen (left sidebar + デイリー/ウィークリー/マンスリー sub-tabs
under 一般商品). See config.py's "Gem shop daily free package" section for
the full writeup of what was re-verified live and what's still an inferred
value. The old `_dialog_open`/`_close_gem_shop` custom probes existed only
because the pre-redesign overlay dialog was invisible to
navigation.is_on_subscreen/is_modal_open; the redesigned screen is a real
subscreen, confirmed live, so this module now shares the same
is_on_subscreen/return_to_home machinery every other subscreen-based task
(mailbox/cafe/shop/lesson) already uses, and those two custom probes are
gone.
A `reference-parity-reviewer` pass on the original version caught a real
bug: the final dialog-close step verified success via
`navigation.is_on_subscreen`, which the pre-redesign overlay dialog read
identically whether open or closed -- so that check could never fail,
making a stuck-open dialog unrecoverable. That specific blindness no longer
applies post-redesign (is_on_subscreen now genuinely distinguishes this
screen from home, confirmed live), which is what made switching to the
shared navigation.return_to_home safe here.
"""
from ba_auto import navigation
def _open_gem_shop(driver, config):
for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1):
driver.click(*config.GEM_SHOP_ICON)
driver.wait(2)
if navigation.is_on_subscreen(driver):
return True
print(f"[gem_shop] subscreen not detected after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
return False
def _open_daily_general_tab(driver, config):
for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1):
driver.click(*config.GEM_SHOP_GENERAL_PRODUCTS_TAB)
driver.wait(1)
driver.click(*config.GEM_SHOP_DAILY_SUBTAB)
driver.wait(1.5)
status = _read_free_card_status(driver, config)
if status != "unknown":
return status
print(f"[gem_shop] free card status not recognized after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
return "unknown"
def _color_matches(rgb, target, tolerance):
return all(abs(c - t) <= tolerance for c, t in zip(rgb, target))
def _read_free_card_status(driver, config):
rgb = driver.color_at(*config.GEM_SHOP_FREE_CARD_STATUS_PROBE)
if _color_matches(rgb, config.GEM_SHOP_STATUS_CLAIMED_RGB, config.GEM_SHOP_STATUS_TOLERANCE):
return "claimed"
if _color_matches(rgb, config.GEM_SHOP_STATUS_AVAILABLE_RGB, config.GEM_SHOP_STATUS_TOLERANCE):
return "available"
return "unknown"
def _claim_free_package(driver, config):
driver.click(*config.GEM_SHOP_FREE_CARD_BUY_BUTTON)
driver.wait(2)
for _ in range(config.GEM_SHOP_CLAIM_MAX_ATTEMPTS):
if _read_free_card_status(driver, config) == "claimed":
return True
driver.keypress("Return")
driver.wait(2)
return _read_free_card_status(driver, config) == "claimed"
def run(driver, config):
driver.focus_game()
if not _open_gem_shop(driver, config):
print("[gem_shop] could not confirm the gem shop screen opened, aborting without pressing further keys")
return
status = _open_daily_general_tab(driver, config)
if status == "unknown":
print("[gem_shop] could not read the free package card's status, aborting without pressing further keys")
if not navigation.return_to_home(driver):
print("[gem_shop] warning: could not confirm return to home")
return
if status == "claimed":
print("[gem_shop] daily free package already collected today")
else:
print("[gem_shop] claiming daily free package (+10 AP, +10,000 credits)")
if _claim_free_package(driver, config):
print("[gem_shop] claimed daily free package")
else:
print("[gem_shop] warning: could not confirm the free package was claimed after retries")
if not navigation.return_to_home(driver):
print("[gem_shop] warning: could not confirm return to home")
print("[gem_shop] Done.")