From 24f572eed12e04c764f1fc413f1a511bbbf993cd Mon Sep 17 00:00:00 2001 From: AI Agent Date: Mon, 2 Mar 2026 22:44:00 -0600 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20create=20adb=5Fvision/=20?= =?UTF-8?q?=E2=80=94=20clean=20ADB=20+=20VLM=20game=20automation=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clean MCP server (server.py) with 6 tools: screenshot, tap, swipe, keyevent, launch_game, get_focus - Pluggable screenshot backends (screenshot.py) with dispatch + stubs for DroidCast, scrcpy, u2, screencap - 10/10 unit tests passing (all mocked, no device needed) - Gemini CLI wired as vision driver via MCP (drive.bat + system prompt) - .mcp.json updated to point to adb_vision server - .gemini/settings.json registers adb-vision MCP server GitHub issues created for Jules: - #40: DroidCast APK HTTP stream backend - #41: scrcpy virtual display capture backend - #42: uiautomator2 ATX HTTP backend Zero ALAS dependency. Pure async ADB + FastMCP. --- .gemini/settings.json | 15 +- .mcp.json | 8 +- adb_vision/.gitignore | 7 + adb_vision/GEMINI_SYSTEM_PROMPT.md | 47 +++++ adb_vision/README.md | 65 ++++++ adb_vision/__init__.py | 4 + adb_vision/conftest.py | 5 + adb_vision/drive.bat | 10 + adb_vision/pyproject.toml | 23 +++ adb_vision/screenshot.py | 120 +++++++++++ adb_vision/server.py | 310 +++++++++++++++++++++++++++++ adb_vision/test_server.py | 186 +++++++++++++++++ 12 files changed, 795 insertions(+), 5 deletions(-) create mode 100644 adb_vision/.gitignore create mode 100644 adb_vision/GEMINI_SYSTEM_PROMPT.md create mode 100644 adb_vision/README.md create mode 100644 adb_vision/__init__.py create mode 100644 adb_vision/conftest.py create mode 100644 adb_vision/drive.bat create mode 100644 adb_vision/pyproject.toml create mode 100644 adb_vision/screenshot.py create mode 100644 adb_vision/server.py create mode 100644 adb_vision/test_server.py diff --git a/.gemini/settings.json b/.gemini/settings.json index 2c63c08510..1db56c6b93 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -1,2 +1,15 @@ { -} + "mcpServers": { + "adb-vision": { + "command": "uv", + "args": [ + "run", + "--directory", + "adb_vision", + "server.py", + "--serial", + "127.0.0.1:21513" + ] + } + } +} \ No newline at end of file diff --git a/.mcp.json b/.mcp.json index c4538b2954..4eeaa7e20c 100644 --- a/.mcp.json +++ b/.mcp.json @@ -5,10 +5,10 @@ "args": [ "run", "--directory", - "agent_orchestrator", - "alas_mcp_server.py", - "--config", - "alas" + "adb_vision", + "server.py", + "--serial", + "127.0.0.1:21513" ], "env": { "PYTHONIOENCODING": "utf-8" diff --git a/adb_vision/.gitignore b/adb_vision/.gitignore new file mode 100644 index 0000000000..95ca92ce51 --- /dev/null +++ b/adb_vision/.gitignore @@ -0,0 +1,7 @@ +# Runtime artifacts +mcp_actions.jsonl +mcp_screenshots/ +.venv/ +__pycache__/ +*.pyc +.pytest_cache/ diff --git a/adb_vision/GEMINI_SYSTEM_PROMPT.md b/adb_vision/GEMINI_SYSTEM_PROMPT.md new file mode 100644 index 0000000000..43d3ff279e --- /dev/null +++ b/adb_vision/GEMINI_SYSTEM_PROMPT.md @@ -0,0 +1,47 @@ +You are an Azur Lane game automation agent. You control an Android emulator +running Azur Lane (EN) via MCP tools. + +## Available MCP Tools + +- **adb_screenshot(method)** — Capture the current screen. Returns a PNG image. + Use `method="screencap"` initially; if the image looks blank/black, try + `method="droidcast"` or `method="u2"`. +- **adb_tap(x, y)** — Tap a coordinate on screen (1280×720 resolution). +- **adb_swipe(x1, y1, x2, y2, duration_ms)** — Swipe gesture. +- **adb_keyevent(keycode)** — Send key event (4=BACK, 3=HOME). +- **adb_launch_game()** — Launch Azur Lane if not running. +- **adb_get_focus()** — Check which app/activity is in foreground. + +## Workflow + +1. **Always start** by taking a screenshot to see the current state. +2. **Describe** what you see on screen (menus, buttons, dialogs, text). +3. **Decide** what action to take and **explain your reasoning**. +4. **Execute** the action (tap, swipe, etc.). +5. **Take another screenshot** to verify the result. +6. **Repeat** until the goal is achieved. + +## Rules + +- The screen resolution is **1280×720** pixels. +- Always take a screenshot BEFORE and AFTER every action. +- If you see a dialog or popup, dismiss it before proceeding. +- If the game is not running, use `adb_launch_game()` first. +- If a screenshot looks blank/black (solid color), report it — the screenshot + method may need to be changed. +- Never tap blindly — always verify what's on screen first. +- Log your reasoning for every action. + +## Common Azur Lane UI Elements + +- **Main lobby**: Shows your secretary ship, bottom menu bar with buttons + (Battle, Dock, Academy, Shop, etc.) +- **Commission/expedition popups**: Dismiss by tapping outside or the X button. +- **Daily login rewards**: Tap to claim, then tap outside to dismiss. +- **Loading screens**: Wait and take another screenshot. + +## Goal + +The user will tell you what to do. Follow their instructions using the tools +above. If no specific goal is given, take a screenshot and describe what you +see. diff --git a/adb_vision/README.md b/adb_vision/README.md new file mode 100644 index 0000000000..7761d0da1d --- /dev/null +++ b/adb_vision/README.md @@ -0,0 +1,65 @@ +# adb_vision — Clean ADB + VLM Game Automation + +Zero ALAS dependency. Pure async ADB over subprocess + pluggable screenshot backends + Gemini CLI as the vision driver. + +## Quick Start + +```bash +# Run tests (no device needed) +cd adb_vision +uv run pytest test_server.py -v + +# Launch Gemini CLI with game control tools +cd adb_vision +drive.bat + +# Or with an initial prompt +drive.bat "Take a screenshot and describe what you see" + +# Headless single-shot (non-interactive) +gemini --policy adb_vision/GEMINI_SYSTEM_PROMPT.md -p "Take a screenshot and tell me what screen the game is on" +``` + +## Architecture + +``` +adb_vision/ +├── server.py — MCP server (FastMCP, stdio transport) +├── screenshot.py — Pluggable screenshot backends (dispatch + stubs) +├── test_server.py — Unit tests (all mocked, no device needed) +├── conftest.py — pytest path setup +├── drive.bat — Launch Gemini CLI with MCP tools +├── GEMINI_SYSTEM_PROMPT.md — System instructions for Gemini +└── pyproject.toml — Dependencies (fastmcp, pillow, aiofiles) +``` + +## MCP Tools + +| Tool | Description | +|------|-------------| +| `adb_screenshot(method)` | Screenshot via pluggable backend (auto/droidcast/scrcpy/u2/screencap) | +| `adb_tap(x, y)` | Tap coordinate | +| `adb_swipe(x1, y1, x2, y2, duration_ms)` | Swipe gesture | +| `adb_keyevent(keycode)` | Send key event (4=BACK, 3=HOME) | +| `adb_launch_game()` | Launch Azur Lane | +| `adb_get_focus()` | Get foreground app/activity | + +## Screenshot Backends + +The screenshot problem: **`adb shell screencap` returns blank images on MEmu/VirtualBox** because the GPU never populates the Linux framebuffer. + +Three alternative backends are being implemented (see GitHub issues #40-#42): + +1. **DroidCast** (#40) — APK that streams screen over HTTP via SurfaceControl API +2. **scrcpy** (#41) — H.264 stream decoded to single frame +3. **uiautomator2 ATX** (#42) — ATX agent HTTP API screenshot endpoint + +The `method="auto"` default tries each backend in order until one returns a valid (>5KB) image. + +## How It Works + +1. Gemini CLI connects to the MCP server via stdio +2. Gemini calls `adb_screenshot()` to see the screen +3. Gemini analyzes the image and decides what to do +4. Gemini calls `adb_tap()` / `adb_swipe()` to interact +5. Repeat — Gemini IS the bot diff --git a/adb_vision/__init__.py b/adb_vision/__init__.py new file mode 100644 index 0000000000..0df6384143 --- /dev/null +++ b/adb_vision/__init__.py @@ -0,0 +1,4 @@ +"""adb_vision — Clean ADB + VLM-driven game automation. + +No ALAS dependency. Pure async ADB over subprocess + pluggable screenshot backends. +""" diff --git a/adb_vision/conftest.py b/adb_vision/conftest.py new file mode 100644 index 0000000000..0c4ceb8071 --- /dev/null +++ b/adb_vision/conftest.py @@ -0,0 +1,5 @@ +"""pytest configuration — add adb_vision/ to sys.path so local imports work.""" +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) diff --git a/adb_vision/drive.bat b/adb_vision/drive.bat new file mode 100644 index 0000000000..121fe8c908 --- /dev/null +++ b/adb_vision/drive.bat @@ -0,0 +1,10 @@ +@echo off +REM Launch Gemini CLI with the adb-vision MCP server in interactive mode +REM Usage: drive.bat [optional initial prompt] +cd /d "%~dp0.." + +if "%~1"=="" ( + gemini --policy adb_vision/GEMINI_SYSTEM_PROMPT.md +) else ( + gemini --policy adb_vision/GEMINI_SYSTEM_PROMPT.md -i "%*" +) diff --git a/adb_vision/pyproject.toml b/adb_vision/pyproject.toml new file mode 100644 index 0000000000..5252954e6e --- /dev/null +++ b/adb_vision/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "adb-vision" +version = "0.1.0" +description = "Clean ADB + VLM-driven game automation — no ALAS dependency" +requires-python = ">=3.11" +dependencies = [ + "fastmcp>=3.0.0b1", + "pillow>=10.0.0", + "aiofiles>=25.1.0", +] + +[tool.hatch.build.targets.wheel] +only-include = ["server.py", "screenshot.py", "__init__.py"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = [ + "pytest>=9.0.2", + "pytest-asyncio>=1.3.0", +] diff --git a/adb_vision/screenshot.py b/adb_vision/screenshot.py new file mode 100644 index 0000000000..816e097574 --- /dev/null +++ b/adb_vision/screenshot.py @@ -0,0 +1,120 @@ +"""Pluggable screenshot backends for adb-vision. + +Each backend implements: + async def capture(adb_run, serial, adb_exe) -> str # base64-encoded PNG + +The ``take_screenshot`` dispatcher tries backends in order until one succeeds. +""" +from __future__ import annotations + +import base64 +import logging +from typing import Callable, Awaitable + +log = logging.getLogger(__name__) + +# Type alias for the _adb_run helper passed from the server +AdbRunFn = Callable[..., Awaitable[bytes]] + + +async def take_screenshot( + *, + adb_run: AdbRunFn, + serial: str, + adb_exe: str, + method: str = "auto", +) -> str: + """Capture a screenshot and return base64-encoded PNG. + + Args: + adb_run: async helper that runs adb commands + serial: ADB device serial + adb_exe: path to adb executable + method: "droidcast", "scrcpy", "u2", "screencap", or "auto" + + Returns: + base64-encoded PNG string + """ + backends = _resolve_backends(method) + last_error: Exception | None = None + + for name, capture_fn in backends: + try: + log.debug("trying screenshot backend: %s", name) + b64 = await capture_fn(adb_run=adb_run, serial=serial, adb_exe=adb_exe) + if b64 and len(base64.b64decode(b64)) > 5000: + # Sanity check: real screenshots are >> 5KB; blank MEmu + # screencaps are ~3.6KB + log.info("screenshot captured via %s", name) + return b64 + log.warning("%s returned suspiciously small image (%d bytes)", name, len(base64.b64decode(b64))) + last_error = RuntimeError(f"{name}: image too small, likely blank") + except Exception as exc: + log.warning("screenshot backend %s failed: %s", name, exc) + last_error = exc + + raise RuntimeError( + f"All screenshot backends failed. Last error: {last_error}" + ) + + +def _resolve_backends(method: str): + """Return list of (name, capture_fn) tuples to try.""" + all_backends = [ + ("droidcast", _capture_droidcast), + ("scrcpy", _capture_scrcpy), + ("u2", _capture_u2), + ("screencap", _capture_screencap), + ] + if method == "auto": + return all_backends + for name, fn in all_backends: + if name == method: + return [(name, fn)] + raise ValueError(f"Unknown screenshot method: {method}") + + +# --------------------------------------------------------------------------- +# Backend: screencap (adb shell screencap — BROKEN on MEmu/VirtualBox) +# --------------------------------------------------------------------------- +async def _capture_screencap(*, adb_run: AdbRunFn, serial: str, adb_exe: str) -> str: + """Capture via ``adb exec-out screencap -p``. + + NOTE: This returns a blank image on MEmu because VirtualBox GPU never + populates the Linux framebuffer. Kept as a fallback for real devices. + """ + png_data = await adb_run("exec-out", "screencap", "-p", timeout=10.0) + return base64.b64encode(png_data).decode("ascii") + + +# --------------------------------------------------------------------------- +# Backend: DroidCast (APK HTTP stream) +# Stub — implementation will be filled by a Jules issue. +# --------------------------------------------------------------------------- +async def _capture_droidcast(*, adb_run: AdbRunFn, serial: str, adb_exe: str) -> str: + raise NotImplementedError( + "DroidCast backend not yet implemented. " + "See GitHub issue for Jules: 'Screenshot via DroidCast APK HTTP stream'" + ) + + +# --------------------------------------------------------------------------- +# Backend: scrcpy (virtual display capture) +# Stub — implementation will be filled by a Jules issue. +# --------------------------------------------------------------------------- +async def _capture_scrcpy(*, adb_run: AdbRunFn, serial: str, adb_exe: str) -> str: + raise NotImplementedError( + "scrcpy backend not yet implemented. " + "See GitHub issue for Jules: 'Screenshot via scrcpy virtual display capture'" + ) + + +# --------------------------------------------------------------------------- +# Backend: uiautomator2 ATX agent HTTP +# Stub — implementation will be filled by a Jules issue. +# --------------------------------------------------------------------------- +async def _capture_u2(*, adb_run: AdbRunFn, serial: str, adb_exe: str) -> str: + raise NotImplementedError( + "u2 backend not yet implemented. " + "See GitHub issue for Jules: 'Screenshot via direct uiautomator2 ATX HTTP'" + ) diff --git a/adb_vision/server.py b/adb_vision/server.py new file mode 100644 index 0000000000..8ee29c5b11 --- /dev/null +++ b/adb_vision/server.py @@ -0,0 +1,310 @@ +"""Clean ADB + VLM MCP server — no ALAS dependency. + +Exposes: + adb_screenshot — pluggable backend (DroidCast / scrcpy / u2 / screencap) + adb_tap — input tap via ADB CLI + adb_swipe — input swipe via ADB CLI + adb_launch_game — am start Azur Lane + adb_get_focus — dumpsys mCurrentFocus + adb_keyevent — input keyevent via ADB CLI + +Every tool call is logged to mcp_actions.jsonl. Screenshots are saved to +mcp_screenshots/. +""" +from __future__ import annotations + +import argparse +import asyncio +import base64 +import json +import os +import re +import shutil +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Dict, Optional + +from screenshot import take_screenshot + +# --------------------------------------------------------------------------- +# FastMCP +# --------------------------------------------------------------------------- +try: + from fastmcp import FastMCP +except ModuleNotFoundError: + raise SystemExit( + "fastmcp is not installed. Run: uv pip install fastmcp>=3.0.0b1" + ) + +mcp = FastMCP("adb-vision", version="0.1.0") + +# --------------------------------------------------------------------------- +# Action log — every MCP tool call appended as a JSONL line. +# --------------------------------------------------------------------------- +_ACTION_LOG_PATH = Path(__file__).parent / "mcp_actions.jsonl" +_SCREENSHOT_DIR = Path(__file__).parent / "mcp_screenshots" +_action_seq = 0 + + +def _action_log( + tool: str, + args: dict, + result_summary: str, + error: str = "", + duration_ms: int = 0, +): + global _action_seq + _action_seq += 1 + record = { + "seq": _action_seq, + "ts": datetime.now(timezone.utc).isoformat(), + "tool": tool, + "args": args, + "result": result_summary, + "error": error, + "duration_ms": duration_ms, + } + try: + _ACTION_LOG_PATH.parent.mkdir(parents=True, exist_ok=True) + with open(_ACTION_LOG_PATH, "a", encoding="utf-8") as fh: + fh.write(json.dumps(record, ensure_ascii=True) + "\n") + except Exception: + pass + + +def _save_screenshot_png(data_b64: str, seq: int) -> str: + try: + _SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True) + ts = datetime.now().strftime("%Y%m%dT%H%M%S") + fname = _SCREENSHOT_DIR / f"{seq:05d}_{ts}.png" + fname.write_bytes(base64.b64decode(data_b64)) + return str(fname) + except Exception: + return "" + + +# --------------------------------------------------------------------------- +# ADB helpers +# --------------------------------------------------------------------------- +def _find_adb() -> str: + found = shutil.which("adb") + if found: + return found + candidates = [ + r"D:\Program Files\Microvirt\MEmu\adb.exe", + r"C:\Program Files\Microvirt\MEmu\adb.exe", + r"C:\Program Files (x86)\Microvirt\MEmu\adb.exe", + r"C:\Program Files\Android\platform-tools\adb.exe", + ] + for c in candidates: + if os.path.isfile(c): + return c + return "adb" + + +ADB_EXECUTABLE: str = _find_adb() +ADB_SERIAL: str = "127.0.0.1:21513" + + +async def _adb_run(*args: str, timeout: float = 10.0) -> bytes: + """Run ``adb -s `` as a non-blocking subprocess.""" + proc = await asyncio.create_subprocess_exec( + ADB_EXECUTABLE, + "-s", + ADB_SERIAL, + *args, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + try: + stdout, stderr = await asyncio.wait_for( + proc.communicate(), timeout=timeout + ) + except asyncio.TimeoutError: + proc.kill() + await proc.wait() + raise TimeoutError( + f"adb timed out after {timeout}s: adb -s {ADB_SERIAL} {' '.join(args)}" + ) + if proc.returncode != 0: + raise RuntimeError( + f"adb {' '.join(args)} failed (exit {proc.returncode}): " + f"{stderr.decode(errors='replace').strip()}" + ) + return stdout + + +# --------------------------------------------------------------------------- +# MCP Tools +# --------------------------------------------------------------------------- + +@mcp.tool() +async def adb_screenshot(method: str = "auto") -> Dict[str, Any]: + """Take a screenshot from the emulator. + + Args: + method: Screenshot backend — "droidcast", "scrcpy", "u2", "screencap", + or "auto" (tries each until one works). + + Returns image content with mimeType image/png and base64 data. + """ + t0 = time.monotonic() + png_b64 = await take_screenshot( + adb_run=_adb_run, + serial=ADB_SERIAL, + adb_exe=ADB_EXECUTABLE, + method=method, + ) + ms = int((time.monotonic() - t0) * 1000) + png_bytes = base64.b64decode(png_b64) + saved = _save_screenshot_png(png_b64, _action_seq + 1) + _action_log( + "adb_screenshot", + {"serial": ADB_SERIAL, "method": method}, + f"png_bytes={len(png_bytes)} saved={saved}", + "", + ms, + ) + return {"content": [{"type": "image", "mimeType": "image/png", "data": png_b64}]} + + +@mcp.tool() +async def adb_tap(x: int, y: int) -> str: + """Tap a coordinate on the emulator. + + Args: + x: X coordinate + y: Y coordinate + """ + t0 = time.monotonic() + await _adb_run("shell", "input", "tap", str(x), str(y), timeout=5.0) + ms = int((time.monotonic() - t0) * 1000) + _action_log("adb_tap", {"x": x, "y": y}, f"tapped {x},{y}", "", ms) + return f"tapped {x},{y}" + + +@mcp.tool() +async def adb_swipe( + x1: int, y1: int, x2: int, y2: int, duration_ms: int = 300 +) -> str: + """Swipe between coordinates on the emulator. + + Args: + x1: Start X + y1: Start Y + x2: End X + y2: End Y + duration_ms: Duration in milliseconds (default 300) + """ + t0 = time.monotonic() + await _adb_run( + "shell", + "input", + "swipe", + str(x1), + str(y1), + str(x2), + str(y2), + str(duration_ms), + timeout=5.0 + duration_ms / 1000.0, + ) + ms = int((time.monotonic() - t0) * 1000) + _action_log( + "adb_swipe", + {"x1": x1, "y1": y1, "x2": x2, "y2": y2, "duration_ms": duration_ms}, + f"swiped {x1},{y1}->{x2},{y2}", + "", + ms, + ) + return f"swiped {x1},{y1}->{x2},{y2}" + + +@mcp.tool() +async def adb_keyevent(keycode: int) -> str: + """Send a key event to the emulator. + + Args: + keycode: Android keycode (e.g. 4=BACK, 3=HOME, 82=MENU) + """ + t0 = time.monotonic() + await _adb_run("shell", "input", "keyevent", str(keycode), timeout=5.0) + ms = int((time.monotonic() - t0) * 1000) + _action_log("adb_keyevent", {"keycode": keycode}, f"keyevent {keycode}", "", ms) + return f"keyevent {keycode}" + + +@mcp.tool() +async def adb_launch_game() -> str: + """Launch Azur Lane (EN) in the foreground.""" + t0 = time.monotonic() + await _adb_run( + "shell", + "am", + "start", + "-a", + "android.intent.action.MAIN", + "-c", + "android.intent.category.LAUNCHER", + "-n", + "com.YoStarEN.AzurLane/com.manjuu.azurlane.PrePermissionActivity", + timeout=10.0, + ) + ms = int((time.monotonic() - t0) * 1000) + _action_log("adb_launch_game", {"serial": ADB_SERIAL}, "launch intent sent", "", ms) + return "Azur Lane launch intent sent" + + +@mcp.tool() +async def adb_get_focus() -> Dict[str, Any]: + """Return the currently focused Android window/package/activity. + + Returns: + {"raw": "", "package": "...", "activity": "..."} + """ + t0 = time.monotonic() + stdout = await _adb_run("shell", "dumpsys", "window", "windows", timeout=8.0) + ms = int((time.monotonic() - t0) * 1000) + raw_text = stdout.decode(errors="replace") + focus_line = "" + for line in raw_text.splitlines(): + if "mCurrentFocus" in line: + focus_line = line.strip() + break + + package: Optional[str] = None + activity: Optional[str] = None + m = re.search(r"(\S+)/(\S+)\}", focus_line) + if m: + package = m.group(1) + activity = m.group(2) + + result = {"raw": focus_line, "package": package, "activity": activity} + _action_log("adb_get_focus", {"serial": ADB_SERIAL}, f"{package}/{activity}", "", ms) + return result + + +# --------------------------------------------------------------------------- +# Entrypoint +# --------------------------------------------------------------------------- +def main(): + global ADB_SERIAL + parser = argparse.ArgumentParser(description="adb-vision MCP server") + parser.add_argument( + "--serial", + default=os.environ.get("ADB_SERIAL", "127.0.0.1:21513"), + help="ADB device serial (default: $ADB_SERIAL or 127.0.0.1:21513)", + ) + parser.add_argument( + "--screenshot-method", + default="auto", + choices=["auto", "droidcast", "scrcpy", "u2", "screencap"], + help="Default screenshot method", + ) + args = parser.parse_args() + ADB_SERIAL = args.serial + mcp.run(transport="stdio") + + +if __name__ == "__main__": + main() diff --git a/adb_vision/test_server.py b/adb_vision/test_server.py new file mode 100644 index 0000000000..f6e4c05348 --- /dev/null +++ b/adb_vision/test_server.py @@ -0,0 +1,186 @@ +"""Unit tests for adb-vision MCP server — no device needed, fully mocked.""" +from __future__ import annotations + +import base64 +from unittest import mock + +import pytest + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _fake_png(width: int = 1280, height: int = 720) -> bytes: + """Generate a valid PNG with random noise that is > 5KB.""" + from PIL import Image + import io + import random + + random.seed(42) + # Random pixel data won't compress as small as solid color + pixels = bytes(random.randint(0, 255) for _ in range(width * height * 3)) + img = Image.frombytes("RGB", (width, height), pixels) + buf = io.BytesIO() + img.save(buf, format="PNG") + return buf.getvalue() + + +FAKE_PNG_B64 = base64.b64encode(_fake_png()).decode("ascii") +FAKE_PNG_BYTES = base64.b64decode(FAKE_PNG_B64) + +# --------------------------------------------------------------------------- +# Mock adb_run +# --------------------------------------------------------------------------- + +async def _mock_adb_run(*args, timeout=10.0) -> bytes: + """Default mock that returns empty bytes.""" + return b"" + + +# --------------------------------------------------------------------------- +# Tests — screenshot dispatch +# --------------------------------------------------------------------------- + +@pytest.mark.asyncio +async def test_take_screenshot_screencap_backend(): + """screencap backend returns base64 PNG when the image is large enough.""" + from screenshot import take_screenshot + + async def mock_run(*args, timeout=10.0): + return _fake_png() # raw PNG bytes + + result = await take_screenshot( + adb_run=mock_run, serial="test", adb_exe="adb", method="screencap" + ) + decoded = base64.b64decode(result) + assert len(decoded) > 5000 + assert decoded[:4] == b"\x89PNG" + + +@pytest.mark.asyncio +async def test_take_screenshot_auto_falls_through(): + """auto mode tries backends in order; stubs raise NotImplementedError, + screencap should eventually be reached.""" + from screenshot import take_screenshot + + async def mock_run(*args, timeout=10.0): + return _fake_png() + + result = await take_screenshot( + adb_run=mock_run, serial="test", adb_exe="adb", method="auto" + ) + decoded = base64.b64decode(result) + assert len(decoded) > 5000 + + +@pytest.mark.asyncio +async def test_take_screenshot_blank_rejected(): + """A suspiciously small image (like MEmu blank screencap) is rejected.""" + from screenshot import take_screenshot + + # 3669-byte blank PNG (smaller than threshold) + tiny_png = b"\x89PNG" + b"\x00" * 3665 + + async def mock_run(*args, timeout=10.0): + return tiny_png + + with pytest.raises(RuntimeError, match="All screenshot backends failed"): + await take_screenshot( + adb_run=mock_run, serial="test", adb_exe="adb", method="screencap" + ) + + +@pytest.mark.asyncio +async def test_take_screenshot_unknown_method(): + from screenshot import take_screenshot + + with pytest.raises(ValueError, match="Unknown screenshot method"): + await take_screenshot( + adb_run=_mock_adb_run, serial="test", adb_exe="adb", method="bogus" + ) + + +# --------------------------------------------------------------------------- +# Tests — MCP tools (tap, swipe, keyevent, launch, focus) +# --------------------------------------------------------------------------- + +@pytest.fixture +def patch_adb_run(): + """Patch server._adb_run with a mock that records calls.""" + import server + + calls = [] + + async def mock_run(*args, timeout=10.0): + calls.append(args) + return b"" + + with mock.patch.object(server, "_adb_run", side_effect=mock_run): + yield calls + + +@pytest.mark.asyncio +async def test_adb_tap(patch_adb_run): + import server + + result = await server.adb_tap(100, 200) + assert "100,200" in result + assert ("shell", "input", "tap", "100", "200") in patch_adb_run + + +@pytest.mark.asyncio +async def test_adb_swipe(patch_adb_run): + import server + + result = await server.adb_swipe(10, 20, 300, 400, duration_ms=500) + assert "10,20->300,400" in result + assert ("shell", "input", "swipe", "10", "20", "300", "400", "500") in patch_adb_run + + +@pytest.mark.asyncio +async def test_adb_keyevent(patch_adb_run): + import server + + result = await server.adb_keyevent(4) + assert "keyevent 4" in result + assert ("shell", "input", "keyevent", "4") in patch_adb_run + + +@pytest.mark.asyncio +async def test_adb_launch_game(patch_adb_run): + import server + + result = await server.adb_launch_game() + assert "launch intent sent" in result + assert any("am" in a for a in patch_adb_run[0]) + + +@pytest.mark.asyncio +async def test_adb_get_focus(patch_adb_run): + import server + + # Override to return a realistic dumpsys line + async def focus_run(*args, timeout=10.0): + return b" mCurrentFocus=Window{abc u0 com.YoStarEN.AzurLane/com.manjuu.azurlane.MainActivity}\n" + + with mock.patch.object(server, "_adb_run", side_effect=focus_run): + result = await server.adb_get_focus() + assert result["package"] == "com.YoStarEN.AzurLane" + assert result["activity"] == "com.manjuu.azurlane.MainActivity" + + +@pytest.mark.asyncio +async def test_adb_screenshot_tool(): + """The adb_screenshot MCP tool returns image content.""" + import server + + async def mock_take_screenshot(**kwargs): + return FAKE_PNG_B64 + + with mock.patch("server.take_screenshot", side_effect=mock_take_screenshot): + result = await server.adb_screenshot() + + assert result["content"][0]["type"] == "image" + assert result["content"][0]["mimeType"] == "image/png" + data = result["content"][0]["data"] + assert len(base64.b64decode(data)) > 5000 From 39e29363000c73624cad9640974a47e72a6fe70e Mon Sep 17 00:00:00 2001 From: AI Agent Date: Tue, 3 Mar 2026 10:42:51 -0600 Subject: [PATCH 02/10] chore: commit validated workspace changes - add adb_vision live test and DroidCast debug/setup scripts - include current alas_wrapped updates, assets, configs, and campaign content - retain existing adb_vision unit test baseline (10 passed) --- adb_vision/debug_droidcast.py | 33 + adb_vision/setup_droidcast.py | 116 +++ adb_vision/test_live.py | 213 +++++ alas_wrapped/.gitignore | 1 - alas_wrapped/README.md | 181 +++- alas_wrapped/alas.py | 207 +--- .../assets/cn/combat_ui/PAUSE_Ancient.png | Bin 0 -> 3526 bytes .../cn/os_handler/MISSION_OVERVIEW_EMPTY.png | Bin 0 -> 7843 bytes .../assets/cn/raid/CHANGWU_OCR_PT.png | Bin 0 -> 7317 bytes .../cn/raid/CHANGWU_OCR_REMAIN_EASY.png | Bin 0 -> 5134 bytes .../assets/cn/raid/CHANGWU_OCR_REMAIN_EX.png | Bin 0 -> 3305 bytes .../cn/raid/CHANGWU_OCR_REMAIN_HARD.png | Bin 0 -> 5120 bytes .../cn/raid/CHANGWU_OCR_REMAIN_NORMAL.png | Bin 0 -> 5084 bytes .../assets/cn/raid/CHANGWU_RAID_EASY.png | Bin 0 -> 6103 bytes .../assets/cn/raid/CHANGWU_RAID_EX.png | Bin 0 -> 7518 bytes .../assets/cn/raid/CHANGWU_RAID_HARD.png | Bin 0 -> 6385 bytes .../assets/cn/raid/CHANGWU_RAID_NORMAL.png | Bin 0 -> 6761 bytes alas_wrapped/assets/cn/raid/RPG_BACK.png | Bin 0 -> 4092 bytes alas_wrapped/assets/cn/raid/RPG_RAID_EX.png | Bin 7576 -> 4640 bytes .../TEMPLATE_REVELATIONS_OF_DUST.png | Bin 0 -> 6306 bytes .../en/os_handler/MISSION_OVERVIEW_EMPTY.png | Bin 0 -> 7843 bytes .../assets/en/raid/CHANGWU_OCR_PT.png | Bin 0 -> 7317 bytes .../en/raid/CHANGWU_OCR_REMAIN_EASY.png | Bin 0 -> 5134 bytes .../assets/en/raid/CHANGWU_OCR_REMAIN_EX.png | Bin 0 -> 3305 bytes .../en/raid/CHANGWU_OCR_REMAIN_HARD.png | Bin 0 -> 5120 bytes .../en/raid/CHANGWU_OCR_REMAIN_NORMAL.png | Bin 0 -> 5084 bytes .../assets/en/raid/CHANGWU_RAID_EASY.png | Bin 0 -> 5316 bytes .../assets/en/raid/CHANGWU_RAID_EX.png | Bin 0 -> 7518 bytes .../assets/en/raid/CHANGWU_RAID_HARD.png | Bin 0 -> 4919 bytes .../assets/en/raid/CHANGWU_RAID_NORMAL.png | Bin 0 -> 6262 bytes alas_wrapped/assets/en/raid/RPG_BACK.png | Bin 0 -> 4092 bytes alas_wrapped/assets/en/raid/RPG_RAID_EX.png | Bin 7576 -> 4640 bytes .../TEMPLATE_REVELATIONS_OF_DUST.png | Bin 0 -> 3172 bytes alas_wrapped/assets/gui/css/alas-mobile.css | 30 - alas_wrapped/assets/gui/css/alas-pc.css | 2 +- alas_wrapped/assets/gui/css/alas.css | 75 +- alas_wrapped/assets/gui/css/dark-alas.css | 14 +- alas_wrapped/assets/gui/css/light-alas.css | 12 - .../jp/os_handler/MISSION_OVERVIEW_EMPTY.png | Bin 0 -> 7843 bytes .../assets/jp/raid/CHANGWU_OCR_PT.png | Bin 0 -> 7317 bytes .../jp/raid/CHANGWU_OCR_REMAIN_EASY.png | Bin 0 -> 5134 bytes .../assets/jp/raid/CHANGWU_OCR_REMAIN_EX.png | Bin 0 -> 3305 bytes .../jp/raid/CHANGWU_OCR_REMAIN_HARD.png | Bin 0 -> 5120 bytes .../jp/raid/CHANGWU_OCR_REMAIN_NORMAL.png | Bin 0 -> 5084 bytes .../assets/jp/raid/CHANGWU_RAID_EASY.png | Bin 0 -> 5732 bytes .../assets/jp/raid/CHANGWU_RAID_EX.png | Bin 0 -> 7518 bytes .../assets/jp/raid/CHANGWU_RAID_HARD.png | Bin 0 -> 6547 bytes .../assets/jp/raid/CHANGWU_RAID_NORMAL.png | Bin 0 -> 6190 bytes alas_wrapped/assets/jp/raid/RPG_BACK.png | Bin 0 -> 4092 bytes alas_wrapped/assets/jp/raid/RPG_RAID_EX.png | Bin 7576 -> 4640 bytes .../tw/coalition/FASHION_COALITION_CHECK.png | Bin 0 -> 8711 bytes .../tw/coalition/FASHION_MODE_BATTLE.png | Bin 0 -> 7516 bytes .../tw/coalition/FASHION_MODE_STORY.png | Bin 0 -> 8906 bytes .../tw/coalition/FASHION_SWITCH_MULTI.png | Bin 0 -> 9767 bytes .../tw/coalition/FASHION_SWITCH_SINGLE.png | Bin 0 -> 10012 bytes .../tw/os_handler/MISSION_OVERVIEW_EMPTY.png | Bin 0 -> 7843 bytes .../assets/tw/raid/CHANGWU_OCR_PT.png | Bin 0 -> 7317 bytes .../tw/raid/CHANGWU_OCR_REMAIN_EASY.png | Bin 0 -> 5134 bytes .../assets/tw/raid/CHANGWU_OCR_REMAIN_EX.png | Bin 0 -> 3305 bytes .../tw/raid/CHANGWU_OCR_REMAIN_HARD.png | Bin 0 -> 5120 bytes .../tw/raid/CHANGWU_OCR_REMAIN_NORMAL.png | Bin 0 -> 5084 bytes .../assets/tw/raid/CHANGWU_RAID_EASY.png | Bin 0 -> 6266 bytes .../assets/tw/raid/CHANGWU_RAID_EX.png | Bin 0 -> 7518 bytes .../assets/tw/raid/CHANGWU_RAID_HARD.png | Bin 0 -> 6447 bytes .../assets/tw/raid/CHANGWU_RAID_NORMAL.png | Bin 0 -> 6708 bytes alas_wrapped/assets/tw/raid/RPG_BACK.png | Bin 0 -> 4092 bytes alas_wrapped/assets/tw/raid/RPG_RAID_EX.png | Bin 7576 -> 4640 bytes alas_wrapped/campaign/Readme.md | 5 + alas_wrapped/campaign/event_20260226_cn/a1.py | 84 ++ alas_wrapped/campaign/event_20260226_cn/a2.py | 74 ++ alas_wrapped/campaign/event_20260226_cn/a3.py | 75 ++ alas_wrapped/campaign/event_20260226_cn/b1.py | 85 ++ alas_wrapped/campaign/event_20260226_cn/b2.py | 76 ++ alas_wrapped/campaign/event_20260226_cn/b3.py | 82 ++ alas_wrapped/campaign/event_20260226_cn/c1.py | 84 ++ alas_wrapped/campaign/event_20260226_cn/c2.py | 74 ++ alas_wrapped/campaign/event_20260226_cn/c3.py | 76 ++ alas_wrapped/campaign/event_20260226_cn/d1.py | 85 ++ alas_wrapped/campaign/event_20260226_cn/d2.py | 85 ++ alas_wrapped/campaign/event_20260226_cn/d3.py | 91 ++ alas_wrapped/campaign/event_20260226_cn/sp.py | 96 ++ .../campaign/war_archives_20230223_cn/a1.py | 78 ++ .../campaign/war_archives_20230223_cn/a2.py | 76 ++ .../campaign/war_archives_20230223_cn/a3.py | 79 ++ .../campaign/war_archives_20230223_cn/b1.py | 94 ++ .../campaign/war_archives_20230223_cn/b2.py | 80 ++ .../campaign/war_archives_20230223_cn/b3.py | 83 ++ .../campaign/war_archives_20230223_cn/c1.py | 78 ++ .../campaign/war_archives_20230223_cn/c2.py | 76 ++ .../campaign/war_archives_20230223_cn/c3.py | 80 ++ .../campaign/war_archives_20230223_cn/d1.py | 94 ++ .../campaign/war_archives_20230223_cn/d2.py | 89 ++ .../campaign/war_archives_20230223_cn/d3.py | 92 ++ alas_wrapped/config/PatrickCustom.json | 20 +- alas_wrapped/config/deploy.template.yaml | 22 +- alas_wrapped/config/template.json | 70 +- alas_wrapped/deploy/Readme.md | 4 +- alas_wrapped/deploy/Windows/template.yaml | 19 +- alas_wrapped/deploy/launcher/Alas.bat | 30 +- alas_wrapped/dev_tools/alas2.bat | 883 ++++++++++++++++++ alas_wrapped/module/base/template.py | 84 +- alas_wrapped/module/campaign/assets.py | 3 - .../module/campaign/campaign_event.py | 24 +- alas_wrapped/module/campaign/campaign_ocr.py | 16 +- .../module/campaign/campaign_status.py | 81 +- alas_wrapped/module/campaign/run.py | 17 +- alas_wrapped/module/coalition/assets.py | 10 +- alas_wrapped/module/coalition/coalition.py | 3 - .../module/combat/auto_search_combat.py | 2 +- alas_wrapped/module/combat/combat.py | 2 + alas_wrapped/module/combat_ui/assets.py | 1 + alas_wrapped/module/config/argument/args.json | 330 ++----- .../module/config/argument/argument.yaml | 58 +- alas_wrapped/module/config/argument/gui.yaml | 22 - alas_wrapped/module/config/argument/menu.json | 6 +- alas_wrapped/module/config/argument/task.yaml | 24 +- .../module/config/config_generated.py | 70 +- alas_wrapped/module/config/config_updater.py | 17 +- alas_wrapped/module/config/i18n/en-US.json | 294 +----- alas_wrapped/module/config/i18n/ja-JP.json | 282 +----- alas_wrapped/module/config/i18n/zh-CN.json | 286 +----- alas_wrapped/module/config/i18n/zh-TW.json | 284 +----- alas_wrapped/module/config/server.py | 2 +- alas_wrapped/module/config/utils.py | 47 +- alas_wrapped/module/device/connection.py | 2 - alas_wrapped/module/device/connection_attr.py | 39 +- alas_wrapped/module/device/device.py | 26 +- .../module/device/method/minitouch.py | 6 +- .../module/device/method/uiautomator_2.py | 10 +- alas_wrapped/module/device/method/utils.py | 100 +- .../device/platform/platform_windows.py | 15 +- alas_wrapped/module/exception.py | 4 - alas_wrapped/module/exercise/exercise.py | 10 - alas_wrapped/module/exercise/hp_daemon.py | 1 + alas_wrapped/module/exercise/opponent.py | 19 - alas_wrapped/module/gacha/gacha_reward.py | 6 - alas_wrapped/module/handler/auto_search.py | 58 +- alas_wrapped/module/handler/login.py | 140 +-- alas_wrapped/module/logger.py | 2 +- alas_wrapped/module/minigame/minigame.py | 18 +- alas_wrapped/module/os/fleet.py | 21 +- alas_wrapped/module/os/map_operation.py | 8 +- .../module/os_handler/action_point.py | 6 +- alas_wrapped/module/os_handler/assets.py | 1 + alas_wrapped/module/os_handler/mission.py | 27 +- alas_wrapped/module/os_handler/os_status.py | 8 +- alas_wrapped/module/os_handler/port.py | 12 +- alas_wrapped/module/raid/assets.py | 12 +- alas_wrapped/module/raid/raid.py | 81 +- alas_wrapped/module/raid/run.py | 22 +- alas_wrapped/module/shop/shop_status.py | 13 - alas_wrapped/module/storage/storage.py | 19 +- .../module/tactical/tactical_class.py | 2 +- alas_wrapped/module/ui/page.py | 12 +- alas_wrapped/module/ui/setting.py | 4 +- alas_wrapped/module/ui/ui.py | 17 +- alas_wrapped/module/war_archives/assets.py | 1 + .../module/war_archives/dictionary.py | 1 + alas_wrapped/module/webui/app.py | 203 +--- alas_wrapped/module/webui/patch.py | 24 +- alas_wrapped/module/webui/pin.py | 2 +- alas_wrapped/module/webui/widgets.py | 12 - alas_wrapped/requirements-in.txt | 6 +- alas_wrapped/requirements.txt | 45 +- .../webapp/packages/main/src/config.ts | 7 +- 165 files changed, 4161 insertions(+), 2829 deletions(-) create mode 100644 adb_vision/debug_droidcast.py create mode 100644 adb_vision/setup_droidcast.py create mode 100644 adb_vision/test_live.py create mode 100644 alas_wrapped/assets/cn/combat_ui/PAUSE_Ancient.png create mode 100644 alas_wrapped/assets/cn/os_handler/MISSION_OVERVIEW_EMPTY.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_OCR_PT.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_EASY.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_EX.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_HARD.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_NORMAL.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_RAID_EASY.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_RAID_EX.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_RAID_HARD.png create mode 100644 alas_wrapped/assets/cn/raid/CHANGWU_RAID_NORMAL.png create mode 100644 alas_wrapped/assets/cn/raid/RPG_BACK.png create mode 100644 alas_wrapped/assets/cn/war_archives/TEMPLATE_REVELATIONS_OF_DUST.png create mode 100644 alas_wrapped/assets/en/os_handler/MISSION_OVERVIEW_EMPTY.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_OCR_PT.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_EASY.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_EX.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_HARD.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_NORMAL.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_RAID_EASY.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_RAID_EX.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_RAID_HARD.png create mode 100644 alas_wrapped/assets/en/raid/CHANGWU_RAID_NORMAL.png create mode 100644 alas_wrapped/assets/en/raid/RPG_BACK.png create mode 100644 alas_wrapped/assets/en/war_archives/TEMPLATE_REVELATIONS_OF_DUST.png create mode 100644 alas_wrapped/assets/jp/os_handler/MISSION_OVERVIEW_EMPTY.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_OCR_PT.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_EASY.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_EX.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_HARD.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_NORMAL.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_RAID_EASY.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_RAID_EX.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_RAID_HARD.png create mode 100644 alas_wrapped/assets/jp/raid/CHANGWU_RAID_NORMAL.png create mode 100644 alas_wrapped/assets/jp/raid/RPG_BACK.png create mode 100644 alas_wrapped/assets/tw/coalition/FASHION_COALITION_CHECK.png create mode 100644 alas_wrapped/assets/tw/coalition/FASHION_MODE_BATTLE.png create mode 100644 alas_wrapped/assets/tw/coalition/FASHION_MODE_STORY.png create mode 100644 alas_wrapped/assets/tw/coalition/FASHION_SWITCH_MULTI.png create mode 100644 alas_wrapped/assets/tw/coalition/FASHION_SWITCH_SINGLE.png create mode 100644 alas_wrapped/assets/tw/os_handler/MISSION_OVERVIEW_EMPTY.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_OCR_PT.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_EASY.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_EX.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_HARD.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_NORMAL.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_RAID_EASY.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_RAID_EX.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_RAID_HARD.png create mode 100644 alas_wrapped/assets/tw/raid/CHANGWU_RAID_NORMAL.png create mode 100644 alas_wrapped/assets/tw/raid/RPG_BACK.png create mode 100644 alas_wrapped/campaign/event_20260226_cn/a1.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/a2.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/a3.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/b1.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/b2.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/b3.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/c1.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/c2.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/c3.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/d1.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/d2.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/d3.py create mode 100644 alas_wrapped/campaign/event_20260226_cn/sp.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/a1.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/a2.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/a3.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/b1.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/b2.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/b3.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/c1.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/c2.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/c3.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/d1.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/d2.py create mode 100644 alas_wrapped/campaign/war_archives_20230223_cn/d3.py create mode 100644 alas_wrapped/dev_tools/alas2.bat diff --git a/adb_vision/debug_droidcast.py b/adb_vision/debug_droidcast.py new file mode 100644 index 0000000000..c45ec90005 --- /dev/null +++ b/adb_vision/debug_droidcast.py @@ -0,0 +1,33 @@ +"""Debug DroidCast_raw startup.""" +import asyncio +import os +import sys + +sys.path.insert(0, os.path.dirname(__file__)) +from server import _adb_run, ADB_EXECUTABLE, ADB_SERIAL + + +async def main(): + print(f"Trying to start DroidCast_raw on {ADB_SERIAL}...") + + # Run directly (not backgrounded) to see output + proc = await asyncio.create_subprocess_exec( + ADB_EXECUTABLE, "-s", ADB_SERIAL, + "shell", + "CLASSPATH=/data/local/tmp/DroidCast_raw.apk", + "app_process", "/", "ink.mol.droidcast_raw.Main", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + try: + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=8.0) + print(f"Exit code: {proc.returncode}") + print(f"Stdout: {stdout.decode(errors='replace')[:1000]}") + print(f"Stderr: {stderr.decode(errors='replace')[:1000]}") + except asyncio.TimeoutError: + print("Process still running after 8s — this is GOOD for a server!") + proc.kill() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/adb_vision/setup_droidcast.py b/adb_vision/setup_droidcast.py new file mode 100644 index 0000000000..988b5c35b1 --- /dev/null +++ b/adb_vision/setup_droidcast.py @@ -0,0 +1,116 @@ +"""Manual DroidCast setup and test script. + +Run: cd adb_vision && uv run python setup_droidcast.py +""" +import asyncio +import os +import sys +import urllib.request +import urllib.error + +sys.path.insert(0, os.path.dirname(__file__)) +from server import _adb_run, ADB_EXECUTABLE, ADB_SERIAL + +DROIDCAST_APK_LOCAL = os.path.join( + os.path.dirname(__file__), "..", "alas_wrapped", "bin", "DroidCast", + "DroidCast_raw-release-1.0.apk" +) +DROIDCAST_APK_REMOTE = "/data/local/tmp/DroidCast_raw.apk" +DROIDCAST_PORT = 53516 + + +async def setup_and_test(): + print(f"ADB: {ADB_EXECUTABLE}") + print(f"Serial: {ADB_SERIAL}") + print(f"APK: {DROIDCAST_APK_LOCAL} (exists={os.path.isfile(DROIDCAST_APK_LOCAL)})") + + # 1. Check emulator + try: + state = await _adb_run("get-state", timeout=5.0) + print(f"1. Emulator state: {state.decode().strip()}") + except Exception as e: + print(f"1. FAIL: Cannot reach emulator: {e}") + return + + # 2. Push APK + print("2. Pushing DroidCast APK...") + try: + proc = await asyncio.create_subprocess_exec( + ADB_EXECUTABLE, "-s", ADB_SERIAL, + "push", DROIDCAST_APK_LOCAL, DROIDCAST_APK_REMOTE, + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=15.0) + print(f" Push result: {stdout.decode().strip()} {stderr.decode().strip()}") + except Exception as e: + print(f" Push FAILED: {e}") + return + + # 3. Kill existing DroidCast processes + print("3. Killing old DroidCast processes...") + try: + await _adb_run("shell", "pkill", "-f", "droidcast_raw", timeout=5.0) + print(" Killed existing process") + except Exception: + print(" No existing process (or pkill not available)") + + await asyncio.sleep(1) + + # 4. Start DroidCast_raw in background + print("4. Starting DroidCast_raw server on device...") + try: + proc = await asyncio.create_subprocess_exec( + ADB_EXECUTABLE, "-s", ADB_SERIAL, + "shell", + "nohup", "sh", "-c", + f"CLASSPATH={DROIDCAST_APK_REMOTE} app_process / ink.mol.droidcast_raw.Main", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + ) + # Don't wait for completion — it's a background server + # Give it a moment to start + try: + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=3.0) + print(f" Process exited (unexpected): stdout={stdout[:200]}, stderr={stderr[:200]}") + except asyncio.TimeoutError: + print(" DroidCast_raw process is running (good - it didn't exit)") + except Exception as e: + print(f" Start FAILED: {e}") + return + + await asyncio.sleep(2) + + # 5. Forward port + print(f"5. Forwarding port tcp:{DROIDCAST_PORT}...") + try: + await _adb_run("forward", f"tcp:{DROIDCAST_PORT}", f"tcp:{DROIDCAST_PORT}", timeout=5.0) + print(f" Port {DROIDCAST_PORT} forwarded") + except Exception as e: + print(f" Forward FAILED: {e}") + return + + # 6. Test connectivity + print("6. Testing DroidCast HTTP endpoint...") + for endpoint in ["/", "/preview", "/screenshot"]: + url = f"http://127.0.0.1:{DROIDCAST_PORT}{endpoint}" + try: + req = urllib.request.Request(url) + with urllib.request.urlopen(req, timeout=5) as resp: + data = resp.read() + print(f" GET {endpoint}: status=200, {len(data)} bytes, content-type={resp.headers.get('Content-Type', 'unknown')}") + if endpoint == "/preview" and len(data) > 5000: + # Save it! + out_path = os.path.join(os.path.dirname(__file__), "test_droidcast_preview.png") + with open(out_path, "wb") as f: + f.write(data) + print(f" SAVED real screenshot to: {out_path} ({len(data)} bytes)") + print(f" PNG header check: {data[:4] == b'\\x89PNG'}") + except urllib.error.HTTPError as e: + print(f" GET {endpoint}: HTTP {e.code} (expected for / endpoint)") + except Exception as e: + print(f" GET {endpoint}: FAILED: {e}") + + print("\nDone!") + + +if __name__ == "__main__": + asyncio.run(setup_and_test()) diff --git a/adb_vision/test_live.py b/adb_vision/test_live.py new file mode 100644 index 0000000000..0893818290 --- /dev/null +++ b/adb_vision/test_live.py @@ -0,0 +1,213 @@ +"""Live integration tests for adb_vision against a real emulator. + +Prerequisites: + - MEmu emulator running with ADB at 127.0.0.1:21513 + - Azur Lane installed (not necessarily running) + +Run: + cd adb_vision && uv run pytest test_live.py -v -s +""" +from __future__ import annotations + +import asyncio +import base64 +import os +import sys + +import pytest + +# Skip entire module if no live emulator is available +pytestmark = pytest.mark.skipif( + os.environ.get("SKIP_LIVE_TESTS", "0") == "1", + reason="SKIP_LIVE_TESTS=1", +) + +# Add parent dir for imports +sys.path.insert(0, os.path.dirname(__file__)) + +import server # noqa: E402 + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +async def _check_emulator_reachable() -> bool: + """Return True if we can talk to the emulator via ADB.""" + try: + await server._adb_run("get-state", timeout=5.0) + return True + except Exception: + return False + + +@pytest.fixture(autouse=True) +def skip_if_no_emulator(): + """Skip tests if the emulator is not reachable.""" + reachable = asyncio.run(_check_emulator_reachable()) + if not reachable: + pytest.skip("Emulator not reachable at " + server.ADB_SERIAL) + + +# --------------------------------------------------------------------------- +# Tests — ADB control tools (no screenshot) +# --------------------------------------------------------------------------- + +class TestAdbControl: + """Tests that verify basic ADB control against the live emulator.""" + + @pytest.mark.asyncio + async def test_get_focus_returns_valid_data(self): + """adb_get_focus should return structured focus data.""" + result = await server.adb_get_focus() + assert "raw" in result + assert "package" in result + assert "activity" in result + # Should have SOME focused window + assert result["raw"], "mCurrentFocus line was empty" + print(f" Focus: {result['package']}/{result['activity']}") + + @pytest.mark.asyncio + async def test_get_focus_game_running(self): + """If Azur Lane is running, we should see it.""" + result = await server.adb_get_focus() + if result["package"] == "com.YoStarEN.AzurLane": + print(" Game is in foreground") + else: + print(f" Game NOT in foreground — got: {result['package']}") + # Not a failure — game might not be running + + @pytest.mark.asyncio + async def test_adb_tap_executes(self): + """adb_tap should complete without error. + + Taps a safe coordinate (center of screen) that won't break anything. + """ + result = await server.adb_tap(640, 360) + assert "640,360" in result + + @pytest.mark.asyncio + async def test_adb_keyevent_back(self): + """adb_keyevent BACK (4) should execute without error.""" + result = await server.adb_keyevent(4) + assert "keyevent 4" in result + + @pytest.mark.asyncio + async def test_adb_swipe_executes(self): + """adb_swipe should complete without error.""" + result = await server.adb_swipe(640, 500, 640, 200, duration_ms=200) + assert "swiped" in result + + @pytest.mark.asyncio + async def test_adb_launch_game(self): + """Launch game should send intent without error.""" + result = await server.adb_launch_game() + assert "launch intent sent" in result + # Give it a moment to process + await asyncio.sleep(2) + focus = await server.adb_get_focus() + print(f" After launch: {focus['package']}/{focus['activity']}") + + +# --------------------------------------------------------------------------- +# Tests — Screenshot +# --------------------------------------------------------------------------- + +class TestScreenshot: + """Tests for screenshot capture from the live emulator.""" + + @pytest.mark.asyncio + async def test_screencap_returns_data(self): + """screencap method returns SOME data (even if potentially blank on MEmu).""" + from screenshot import _capture_screencap + b64 = await _capture_screencap( + adb_run=server._adb_run, serial=server.ADB_SERIAL, adb_exe=server.ADB_EXECUTABLE + ) + raw = base64.b64decode(b64) + print(f" screencap: {len(raw)} bytes") + # On MEmu this may be ~3669 bytes (blank). We just verify it runs. + assert len(raw) > 0 + assert raw[:4] == b"\x89PNG" + + @pytest.mark.asyncio + async def test_screencap_likely_blank_on_memu(self): + """Document: screencap returns tiny blank image on MEmu.""" + from screenshot import _capture_screencap + b64 = await _capture_screencap( + adb_run=server._adb_run, serial=server.ADB_SERIAL, adb_exe=server.ADB_EXECUTABLE + ) + raw = base64.b64decode(b64) + if len(raw) < 5000: + print(f" CONFIRMED: screencap is blank ({len(raw)} bytes) — MEmu/VirtualBox issue") + else: + print(f" screencap returned real image ({len(raw)} bytes)") + + @pytest.mark.asyncio + async def test_droidcast_returns_real_image(self): + """DroidCast backend should return a real non-blank screenshot.""" + from screenshot import _capture_droidcast + try: + b64 = await _capture_droidcast( + adb_run=server._adb_run, serial=server.ADB_SERIAL, adb_exe=server.ADB_EXECUTABLE + ) + raw = base64.b64decode(b64) + print(f" DroidCast: {len(raw)} bytes") + assert len(raw) > 5000, f"DroidCast image too small ({len(raw)} bytes)" + assert raw[:4] == b"\x89PNG" + except NotImplementedError: + pytest.skip("DroidCast backend not yet implemented") + + @pytest.mark.asyncio + async def test_adb_screenshot_tool_auto(self): + """The adb_screenshot MCP tool with auto method should return an image.""" + try: + result = await server.adb_screenshot(method="auto") + content = result["content"][0] + assert content["type"] == "image" + assert content["mimeType"] == "image/png" + raw = base64.b64decode(content["data"]) + print(f" adb_screenshot(auto): {len(raw)} bytes") + assert len(raw) > 5000, f"Screenshot too small ({len(raw)} bytes)" + except RuntimeError as e: + if "All screenshot backends failed" in str(e): + pytest.skip(f"No working screenshot backend: {e}") + raise + + +# --------------------------------------------------------------------------- +# Tests — End-to-end flow +# --------------------------------------------------------------------------- + +class TestEndToEnd: + """End-to-end: screenshot → tap → screenshot → verify change.""" + + @pytest.mark.asyncio + async def test_tap_changes_state(self): + """Tap should produce a visible change in screenshots.""" + # This test requires a working screenshot backend + try: + shot1 = await server.adb_screenshot(method="auto") + except RuntimeError: + pytest.skip("No working screenshot backend") + + data1 = base64.b64decode(shot1["content"][0]["data"]) + print(f" Before tap: {len(data1)} bytes") + + # Tap center of screen + await server.adb_tap(640, 360) + await asyncio.sleep(1) + + try: + shot2 = await server.adb_screenshot(method="auto") + except RuntimeError: + pytest.skip("Screenshot failed after tap") + + data2 = base64.b64decode(shot2["content"][0]["data"]) + print(f" After tap: {len(data2)} bytes") + + # The images should be DIFFERENT (tap should have done something) + # Note: they could be the same if the tap hit a non-interactive area + if data1 == data2: + print(" WARNING: Images identical — tap may not have changed state") + else: + print(" SUCCESS: Images differ — tap changed the screen") diff --git a/alas_wrapped/.gitignore b/alas_wrapped/.gitignore index 6ffc6a717e..73ce58daea 100644 --- a/alas_wrapped/.gitignore +++ b/alas_wrapped/.gitignore @@ -8,7 +8,6 @@ config/*.yaml config/*.json config/tmp* !config/template*.json -!config/PatrickCustom.json *.pyw dev_tools/debug_tools .idea diff --git a/alas_wrapped/README.md b/alas_wrapped/README.md index 459e08f229..976a3f83e9 100644 --- a/alas_wrapped/README.md +++ b/alas_wrapped/README.md @@ -1,11 +1,180 @@ -相比于源库增加了仪表盘 +**| [English](README_en.md) | 简体中文 | [日本語](README_jp.md) |** -提供了一个较为实用的仪表盘,感谢@MengNianxiaoyao 作出的美观调整 +# AzurLaneAutoScript -![image](https://github.com/Zuosizhu/Alas-with-Dashboard/assets/60862861/ee2e3e8f-9a19-417e-8e5f-441ecdee1ae6) +#### Discord [![](https://img.shields.io/discord/720789890354249748?logo=discord&logoColor=ffffff&color=4e4c97)](https://discord.gg/AQN6GeJ) QQ群 ![](https://img.shields.io/badge/QQ%20Group-1087735381-4e4c97) +Azur Lane bot with GUI (Supports CN, EN, JP, TW, able to support other servers), designed for 24/7 running scenes, can take over almost all Azur Lane gameplay. Azur Lane, as a mobile game, has entered the late stage of its life cycle. During the period from now to the server down, please reduce the time spent on the Azur Lane and leave everything to Alas. -![image](https://github.com/Zuosizhu/Alas-with-Dashboard/assets/60862861/55f95cb3-5234-45d3-a265-6b5e0ab5fc3e) +Alas is a free open source software, link: https://github.com/LmeSzinc/AzurLaneAutoScript -![image](https://github.com/Zuosizhu/Alas-with-Dashboard/assets/60862861/6033931a-c4ea-4262-853f-f315f076d305) +Alas,一个带GUI的碧蓝航线脚本(支持国服, 国际服, 日服, 台服, 可以支持其他服务器),为 7x24 运行的场景而设计,能接管近乎全部的碧蓝航线玩法。碧蓝航线,作为一个手游,已经进入了生命周期的晚期。从现在到关服的这段时间里,请减少花费在碧蓝航线上的时间,把一切都交给 Alas。 + +Alas 是一款免费开源软件,地址:https://github.com/LmeSzinc/AzurLaneAutoScript + +EN support, thanks **[@whoamikyo](https://github.com/whoamikyo)** and **[@nEEtdo0d](https://github.com/nEEtdo0d)**. + +JP support, thanks **[@ferina8-14](https://github.com/ferina8-14)**, **[@noname94](https://github.com/noname94)** and **[@railzy](https://github.com/railzy)**. + +TW support, thanks **[@Zorachristine](https://github.com/Zorachristine)** , some features might not work. + +GUI development, thanks **[@18870](https://github.com/18870)** , say HURRAY. + +![](https://img.shields.io/github/commit-activity/m/LmeSzinc/AzurLaneAutoScript?color=4e4c97) ![](https://img.shields.io/tokei/lines/github/LmeSzinc/AzurLaneAutoScript?color=4e4c97) ![](https://img.shields.io/github/repo-size/LmeSzinc/AzurLaneAutoScript?color=4e4c97) ![](https://img.shields.io/github/issues-closed/LmeSzinc/AzurLaneAutoScript?color=4e4c97) ![](https://img.shields.io/github/issues-pr-closed/LmeSzinc/AzurLaneAutoScript?color=4e4c97) + +这里是一张GUI预览图: +![gui](https://raw.githubusercontent.com/LmeSzinc/AzurLaneAutoScript/master/doc/README.assets/gui.png) + + + +## 功能 Features + +- **出击**:主线图,活动图,共斗活动,紧急委托刷钻石。 +- **收获**:委托,战术学院,科研,后宅,指挥喵,大舰队,收获,商店购买,开发船坞,每日抽卡,档案密钥。 +- **每日**:每日任务,困难图,演习,潜艇图,活动每日AB图,活动每日SP图,共斗活动每日,作战档案。 +- **大世界**:余烬信标,每月开荒,大世界每日,隐秘海域,短猫相接,深渊海域,塞壬要塞。 + +#### 突出特性: + +- **心情控制**:计算心情防止红脸或者保持经验加成状态。 +- **活动图开荒**:支持在非周回模式下运行,能处理移动距离限制,光之壁,岸防炮,地图解谜,地图迷宫等特殊机制。 +- **无缝收菜**:时间管理大师,计算委托科研等的完成时间,完成后立即收获。 +- **大世界**:一条龙完成,接大世界每日,买空港口商店,做大世界每日,短猫相接,购买明石商店,每27分钟清理隐秘海域,清理深渊海域和塞壬要塞,~~计划作战模式是什么垃圾,感觉不如Alas......好用~~。 +- **大世界月初开荒**:大世界每月重置后,不需要购买作战记录仪(5000油道具)即可开荒。 + + + +## 安装 Installation [![](https://img.shields.io/github/downloads/LmeSzinc/AzurLaneAutoScript/total?color=4e4c97)](https://github.com/LmeSzinc/AzurLaneAutoScript/releases) + +[中文安装教程](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Installation_cn),包含自动安装教程,使用教程,手动安装教程,远程控制教程。 + +[设备支持文档](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Emulator_cn),包含模拟器运行、云手机运行以及解锁各种骚方式运行。 + + + +## 正确地使用调度器 + +- **理解 *任务* 和 *调度器* 的概念** + + 在 Alas 中每个任务都是独立运行的,被一个统一的调度器调度,任务执行完成后会自动设置这个任务的下一次运行时间。例如,*科研* 任务执行了一个 4 小时的科研,调度器就会把 *科研* 任务推迟 4 小时,以达到无缝收菜的目的。 + +- **理解 *自动心情控制* 机制** + + Alas 的心情控制以预防为主,不会等到出现红脸弹窗才去解决,这样可以保持心情值在 120 以上,贪到 20% 的经验。例如,当前心情值是 113,放置于后宅二楼(+50/h),未婚(+0/h),Alas 会等到 12 分钟之后,心情值回复到 120 以上再继续出击。而在这个等待的期间,Alas 也会穿插执行其他任务。 + +- **正确地使用调度器** + + 调度器的 **错误使用方法是只开一两个** 任务,手动管理任务或开关 Alas,调度器的 **正确使用方法是启用全部** 你觉得可能有用的任务,让调度器自动调度,把模拟器和 Alas 都最小化到托盘,忘记碧蓝航线这个游戏。 + + + +## 修改游戏设置 + +对照这个表格修改游戏内的设置,~~正常玩过游戏的都这么设置~~。 + +> 对着改的意思是,这是统一的标准,照着给定的内容执行,不要问为什么,不允许有不一样的。 + +主界面 => 右下角:设置 => 左侧边栏:选项 + +| 设置名称 | 值 | +| ----------------------------------- | ---- | +| 帧数设置 | 60帧 | +| 大型作战设置 - 减少TB引导 | 开 | +| 大型作战设置 - 自律时自动提交道具 | 开 | +| 大型作战设置 - 安全海域默认开启自律 | 关 | +| 剧情自动播放 | 开启 | +| 剧情自动播放速度调整 | 特快 | +| 待机模式设置 - 启用待机模式 | 关 | +| 其他设置 - 重复角色获得提示 | 关 | +| 其他设置 - 快速更换二次确认界面 | 关 | +| 其他设置 - 展示结算角色 | 关 | + +大世界 => 右上角:雷达 => 指令模块(order):潜艇支援: +| 设置名称 | 值 | +| -------------------------------------------------------- | ---------------- | +| X 消耗时潜艇出击 |取消勾选| + +主界面 => 右下角:建造 => 左侧边栏: 退役 => 左侧齿轮图标:一键退役设置: + +| 设置名称 | 值 | +| -------------------------------------------------------- | ---------------- | +| 选择优先级1 | R | +| 选择优先级2 | SR | +| 选择优先级3 | N | +| 「拥有」满星的同名舰船时,保留几艘符合退役条件的同名舰船 | 不保留 | +| 「没有」满星的同名舰船时,保留几艘符合退役条件的同名舰船 | 满星所需或不保留 | + +将角色设备的装备外观移除,以免影响图像识别 + +## 如何上报bug How to Report Bugs + +在提问题之前至少花费 5 分钟来思考和准备,才会有人花费他的 5 分钟来帮助你。"XX怎么运行不了","XX卡住了",这样的描述将不会得到回复。 + +- 在提问题前,请先阅读 [常见问题(FAQ)](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/FAQ_en_cn)。 +- 检查 Alas 的更新和最近的 commit,确认使用的是最新版。 +- 上传出错 log,在 `log/error` 目录下,以毫秒时间戳为文件夹名,包含 log.txt 和最近的截图。若不是错误而是非预期的行为,提供在 `log` 目录下当天的 log和至少一张游戏截图。 + + + +## 已知问题 Known Issues + +- **无法处理网络波动**,重连弹窗,跳小黄鸡。 +- **在极低配电脑上运行可能会出现各种问题**,极低配指截图耗时大于1s,一般电脑耗时约0.5s,高配耗时约0.3s。 +- **演习可能SL失败**,演习看的是屏幕上方的血槽,血槽可能被立绘遮挡,因此需要一定时间(默认1s)血量低于一定值(默认40%)才会触发SL。一个血皮后排就有30%左右的血槽,所以有可能在 1s 内被打死。 +- **极少数情况下 ADB 和 uiautomator2 会抽风**,是模拟器的问题,重启模拟器即可。 +- **拖动操作在模拟器卡顿时,会被视为点击** + + + +## Alas 社区准则 Alas Community Guidelines + +见 [#1416](https://github.com/LmeSzinc/AzurLaneAutoScript/issues/1416)。 + + + +## 文档 Documents + +[海图识别 perspective](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/perspective) + +`海图识别` 是一个碧蓝航线脚本的核心,如果只是单纯地使用 `模板匹配 (Template matching)` 来进行索敌,就不可避免地会出现 BOSS被小怪堵住 的情况。 Alas 提供了一个更好的海图识别方法,在 `module.map_detection` 中,你将可以得到完整的海域信息,比如: + +``` +2020-03-10 22:09:03.830 | INFO | A B C D E F G H +2020-03-10 22:09:03.830 | INFO | 1 -- ++ 2E -- -- -- -- -- +2020-03-10 22:09:03.830 | INFO | 2 -- ++ ++ MY -- -- 2E -- +2020-03-10 22:09:03.830 | INFO | 3 == -- FL -- -- -- 2E MY +2020-03-10 22:09:03.830 | INFO | 4 -- == -- -- -- -- ++ ++ +2020-03-10 22:09:03.830 | INFO | 5 -- -- -- 2E -- 2E ++ ++ +``` + +更多文档,请前往 [WIKI](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki)。 + + + +## 参与开发 Join Development + +Alas 仍在活跃开发中,我们会不定期发布未来的工作在 [Issues](https://github.com/LmeSzinc/AzurLaneAutoScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) 上并标记为 `help wanted`,欢迎向 Alas 提交 [Pull Requests](https://github.com/LmeSzinc/AzurLaneAutoScript/pulls),我们会认真阅读你的每一行代码的。 + +哦对,别忘了阅读 [开发文档](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/1.-Start)。 + + + +## 相关项目 Relative Repositories + +- [AzurStats](https://azur-stats.lyoko.io/),基于 Alas 实现的碧蓝航线掉落统计平台。 +- [AzurLaneUncensored](https://github.com/LmeSzinc/AzurLaneUncensored),与 Alas 对接的碧蓝航线反和谐。 +- [ALAuto](https://github.com/Egoistically/ALAuto),EN服的碧蓝航线脚本,已不再维护,Alas 模仿了其架构。 +- [ALAuto homg_trans_beta](https://github.com/asd111333/ALAuto/tree/homg_trans_beta),Alas 引入了其中的单应性变换至海图识别模块中。 +- [PyWebIO](https://github.com/pywebio/PyWebIO),Alas 使用的 GUI 库。 +- [MaaAssistantArknights](https://github.com/MaaAssistantArknights/MaaAssistantArknights),明日方舟小助手,全日常一键长草,现已加入Alas豪华午餐 -> [MAA 插件使用教程](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/submodule_maa_cn) +- [FGO-py](https://github.com/hgjazhgj/FGO-py),全自动免配置跨平台开箱即用的Fate/Grand Order助手.启动脚本,上床睡觉,养肝护发,满加成圣诞了解一下? +- [StarRailCopilot](https://github.com/LmeSzinc/StarRailCopilot),星铁速溶茶,崩坏:星穹铁道脚本,基于下一代Alas框架。 + + + +## 联系我们 Contact Us + +- Discord: [https://discord.gg/AQN6GeJ](https://discord.gg/AQN6GeJ) +- QQ 八群:[938081688](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=3h8Gl323WkIt6yGx8Jx5Ht93puZxeA8T&authKey=xPT6kPm7W9jWO2TNzPdohJ27l1njxorwKmkDrbwwYGGA6Oni1xQSJhHsRIJ8w7GZ&noverify=0&group_code=938081688) +- QQ 一群:[1087735381](https://jq.qq.com/?_wv=1027&k=I4NSqX7g) (有开发意向请加一群,入群需要提供你的Github用户名) +- Bilibili 直播间:https://live.bilibili.com/22216705 ,偶尔直播写Alas,~~为了拯救Alas,Lme决定出道成为偶像~~ -![image](https://github.com/Zuosizhu/Alas-with-Dashboard/assets/60862861/6fafb159-2092-4423-9d58-3d6c1262e691) diff --git a/alas_wrapped/alas.py b/alas_wrapped/alas.py index cf897ade87..6268c638a7 100644 --- a/alas_wrapped/alas.py +++ b/alas_wrapped/alas.py @@ -6,41 +6,14 @@ import inflection from cached_property import cached_property -try: - from adbutils.errors import AdbError -except ImportError: - class AdbError(Exception): - pass from module.base.decorator import del_cached_property -from module.base.jsonl import append_jsonl from module.config.config import AzurLaneConfig, TaskEnd from module.config.deep import deep_get, deep_set from module.exception import * from module.logger import logger from module.notify import handle_notify -# Command whitelist for security (frozenset for O(1) lookup) -_ALLOWED_COMMANDS = frozenset([ - 'restart', 'start', 'goto_main', 'research', 'commission', 'tactical', - 'dorm', 'meowfficer', 'guild', 'reward', 'awaken', 'shop_frequent', - 'shop_once', 'shipyard', 'gacha', 'freebies', 'minigame', 'private_quarters', - 'daily', 'hard', 'exercise', 'sos', 'war_archives', 'raid_daily', - 'event_a', 'event_b', 'event_c', 'event_d', 'event_sp', 'maritime_escort', - 'opsi_ash_assist', 'opsi_ash_beacon', 'opsi_explore', 'opsi_shop', - 'opsi_voucher', 'opsi_daily', 'opsi_obscure', 'opsi_month_boss', - 'opsi_abyssal', 'opsi_archive', 'opsi_stronghold', 'opsi_meowfficer_farming', - 'opsi_hazard1_leveling', 'opsi_cross_month', 'main', 'main2', 'main3', - 'event', 'event2', 'raid', 'hospital', 'coalition', 'coalition_sp', - 'c72_mystery_farming', 'c122_medium_leveling', 'c124_large_leveling', - 'gems_farming', 'daemon', 'opsi_daemon', 'event_story', - 'azur_lane_uncensored', 'benchmark', 'game_manager' -]) - -_RUNTIME_ROOT = os.path.dirname(os.path.abspath(__file__)) -_SCHEDULE_STATUS_FILE = os.path.join(_RUNTIME_ROOT, 'log', 'schedule_status.jsonl') -_JSONL_ROTATE_BYTES = 20 * 1024 * 1024 - class AzurLaneAutoScript: stop_event: threading.Event = None @@ -53,13 +26,6 @@ def __init__(self, config_name='alas'): # Failure count of tasks # Key: str, task name, value: int, failure count self.failure_record = {} - # Used by sidecar schedule status logs for correlation. - self.last_task = None - # Count transport failures across consecutive run attempts: - # first failure may be transient; repeated failures force restart path. - self.transport_error_streak = 0 - # Prevents duplicate restart calls when one restart is already queued. - self.restart_dedupe_seconds = 30 @cached_property def config(self): @@ -68,10 +34,10 @@ def config(self): return config except RequestHumanTakeover: logger.critical('Request human takeover') - raise + exit(1) except Exception as e: logger.exception(e) - raise RequestHumanTakeover(str(e)) from e + exit(1) @cached_property def device(self): @@ -81,10 +47,10 @@ def device(self): return device except RequestHumanTakeover: logger.critical('Request human takeover') - raise + exit(1) except Exception as e: logger.exception(e) - raise RequestHumanTakeover(str(e)) from e + exit(1) @cached_property def checker(self): @@ -94,156 +60,52 @@ def checker(self): return checker except Exception as e: logger.exception(e) - raise RequestHumanTakeover(str(e)) from e - - @cached_property - def state_machine(self): - try: - from module.state_machine import StateMachine - from module.ui.ui import UI - ui = UI(config=self.config, device=self.device) - return StateMachine(ui=ui) - except Exception as e: - logger.exception(e) - raise RequestHumanTakeover(str(e)) from e - - def _write_schedule_status(self, next_task): - # Emit scheduler intent and task queues to a separate stream so - # external tooling can inspect current/next task order. - pending = [f.command for f in getattr(self.config, 'pending_task', [])] - waiting = [f.command for f in getattr(self.config, 'waiting_task', [])] - payload = { - 'ts': datetime.utcnow().isoformat(timespec='milliseconds') + 'Z', - 'config': self.config_name, - 'current_task': self.last_task, - 'next_task': next_task, - 'pending': pending, - 'next_waiting': waiting[:10], - 'pending_count': len(pending), - 'waiting_count': len(waiting), - 'source': 'scheduler_loop', - } - append_jsonl( - _SCHEDULE_STATUS_FILE, - payload, - rotate_bytes=_JSONL_ROTATE_BYTES, - error_callback=lambda e: logger.warning( - f'Failed to append JSONL `{_SCHEDULE_STATUS_FILE}`: {e}' - ), - ) - - def _is_restart_pending_soon(self, within_seconds=None): - # Treat an existing upcoming Restart task as already-reported work. - within_seconds = self.restart_dedupe_seconds if within_seconds is None else within_seconds - restart_next_run = deep_get(self.config.data, keys='Restart.Scheduler.NextRun', default=None) - restart_enabled = bool(deep_get(self.config.data, keys='Restart.Scheduler.Enable', default=False)) - if not restart_enabled or not isinstance(restart_next_run, datetime): - return False - return restart_next_run <= datetime.now() + timedelta(seconds=max(int(within_seconds), 0)) - - def _schedule_restart(self, reason, within_seconds=None): - # Centralize all restart scheduling through one dedupe-aware path. - if self._is_restart_pending_soon(within_seconds=within_seconds): - logger.info(f'Task call: Restart (deduped, already pending) reason={reason}') - return False - logger.info(f'Task call: Restart (reason={reason})') - self.config.task_call('Restart') - return True - - def _recover_transport_once(self, reason): - # Use a single reconnect-screenshot probe before declaring transport - # failure terminal and letting restart logic run. - logger.warning(f'Transport failure detected ({reason}), attempting adb_reconnect once') - try: - self.device.adb_reconnect() - self.device.screenshot() - logger.info('Transport recovery succeeded') - return True - except Exception as e: - logger.warning(f'Transport recovery failed: {type(e).__name__}: {e}') - return False + exit(1) def run(self, command, skip_first_screenshot=False): - """ - Args: - command (str): Task name to run. - skip_first_screenshot (bool): - """ - if command not in _ALLOWED_COMMANDS: - logger.error(f'Command "{command}" is not in the whitelist.') - return False - try: if not skip_first_screenshot: self.device.screenshot() self.__getattribute__(command)() - self.transport_error_streak = 0 return True except TaskEnd: - self.transport_error_streak = 0 return True except GameNotRunningError as e: - self.transport_error_streak = 0 logger.warning(e) - self._schedule_restart(reason='game_not_running') + self.config.task_call('Restart') return False except (GameStuckError, GameTooManyClickError) as e: - self.transport_error_streak = 0 logger.error(e) self.save_error_log() logger.warning(f'Game stuck, {self.device.package} will be restarted in 10 seconds') logger.warning('If you are playing by hand, please stop Alas') - self._schedule_restart(reason=f'ui_stuck:{type(e).__name__}') - self.device.sleep(10) - return False - except (AdbError, GameTransportError) as e: - self.transport_error_streak += 1 - logger.error(f'{type(e).__name__}: {e}') - self.save_error_log() - # One-shot recovery: avoid immediate restart churn on one-off ADB blips. - if self.transport_error_streak == 1 and self._recover_transport_once(reason=f'{command}:{type(e).__name__}'): - logger.warning('Transport recovered on first failure, skip immediate restart') - self.device.sleep(2) - return False - logger.warning('Transport failure repeated or unrecoverable, schedule restart') - self._schedule_restart(reason=f'transport_failure:{type(e).__name__}') + self.config.task_call('Restart') self.device.sleep(10) return False except GameBugError as e: - self.transport_error_streak = 0 logger.warning(e) self.save_error_log() logger.warning('An error has occurred in Azur Lane game client, Alas is unable to handle') logger.warning(f'Restarting {self.device.package} to fix it') - self._schedule_restart(reason='game_bug') + self.config.task_call('Restart') self.device.sleep(10) return False except GamePageUnknownError: - self.transport_error_streak = 0 logger.info('Game server may be under maintenance or network may be broken, check server status now') self.checker.check_now() if self.checker.is_available(): - if self.config.Error_RestartOnUnknownPage: - logger.warning('Game page unknown, server is available. Attempting restart.') - self.save_error_log() - self._schedule_restart(reason='unknown_page') - self.device.sleep(10) - return False - else: - logger.critical('Game page unknown') - self.save_error_log() - handle_notify( - self.config.Error_OnePushConfig, - title=f"Alas <{self.config_name}> crashed", - content=f"<{self.config_name}> GamePageUnknownError", - ) - raise RequestHumanTakeover('GamePageUnknownError') + logger.critical('Game page unknown') + self.save_error_log() + handle_notify( + self.config.Error_OnePushConfig, + title=f"Alas <{self.config_name}> crashed", + content=f"<{self.config_name}> GamePageUnknownError", + ) + exit(1) else: - logger.warning('Game server is under maintenance or network is broken, Alas will wait for it') self.checker.wait_until_available() return False except ScriptError as e: - self.transport_error_streak = 0 logger.exception(e) logger.critical('This is likely to be a mistake of developers, but sometimes just random issues') handle_notify( @@ -251,18 +113,16 @@ def run(self, command, skip_first_screenshot=False): title=f"Alas <{self.config_name}> crashed", content=f"<{self.config_name}> ScriptError", ) - raise RequestHumanTakeover(str(e)) from e + exit(1) except RequestHumanTakeover: - self.transport_error_streak = 0 logger.critical('Request human takeover') handle_notify( self.config.Error_OnePushConfig, title=f"Alas <{self.config_name}> crashed", content=f"<{self.config_name}> RequestHumanTakeover", ) - raise + exit(1) except Exception as e: - self.transport_error_streak = 0 logger.exception(e) self.save_error_log() handle_notify( @@ -270,7 +130,8 @@ def run(self, command, skip_first_screenshot=False): title=f"Alas <{self.config_name}> crashed", content=f"<{self.config_name}> Exception occured", ) - raise RequestHumanTakeover(str(e)) from e + exit(1) + def save_error_log(self): """ Save last 60 screenshots in ./log/error/ @@ -589,7 +450,7 @@ def wait_until(self, future): if self.stop_event.is_set(): logger.info("Update event detected") logger.info(f"[{self.config_name}] exited. Reason: Update") - return False + exit(0) time.sleep(5) @@ -623,7 +484,7 @@ def get_next_task(self): del_cached_property(self, 'config') continue if task.command != 'Restart': - self._schedule_restart(reason='close_game_wait') + self.config.task_call('Restart') del_cached_property(self, 'config') continue elif method == 'goto_main': @@ -673,10 +534,9 @@ def loop(self): # So update it once recovered del_cached_property(self, 'config') logger.info('Server or network is recovered. Restart game client') - self._schedule_restart(reason='server_recovered') + self.config.task_call('Restart') # Get task task = self.get_next_task() - self._write_schedule_status(next_task=task) # Init device and change server _ = self.device self.device.config = self.config @@ -695,39 +555,24 @@ def loop(self): success = self.run(inflection.underscore(task)) logger.info(f'Scheduler: End task `{task}`') self.is_first_task = False - self.last_task = task # Check failures - failed = self.failure_record.get(task, 0) + failed = deep_get(self.failure_record, keys=task, default=0) failed = 0 if success else failed + 1 - self.failure_record[task] = failed + deep_set(self.failure_record, keys=task, value=failed) if failed >= 3: logger.critical(f"Task `{task}` failed 3 or more times.") logger.critical("Possible reason #1: You haven't used it correctly. " "Please read the help text of the options.") logger.critical("Possible reason #2: There is a problem with this task. " "Please contact developers or try to fix it yourself.") - if self.config.Error_HandleError: - logger.warning(f"Auto recovery enabled, skip human takeover for task `{task}`") - logger.warning(f"Delay task `{task}` for 10 minutes and schedule `Restart`") - self.failure_record[task] = 0 - self.config.task_delay(task=task, minute=10) - self._schedule_restart(reason=f'task_failed_3x:{task}') - if task == 'Restart': - logger.warning('Restart task failed repeatedly, wait 60 seconds before next scheduler cycle') - # Use time.sleep directly: device.sleep is only a thin - # wrapper today, but when Restart itself has failed the - # device/ADB connection may be in an unknown state. - time.sleep(60) - del_cached_property(self, 'config') - continue logger.critical('Request human takeover') handle_notify( self.config.Error_OnePushConfig, title=f"Alas <{self.config_name}> crashed", content=f"<{self.config_name}> RequestHumanTakeover\nTask `{task}` failed 3 or more times.", ) - raise RequestHumanTakeover(f"Task `{task}` failed 3 or more times.") + exit(1) if success: del_cached_property(self, 'config') diff --git a/alas_wrapped/assets/cn/combat_ui/PAUSE_Ancient.png b/alas_wrapped/assets/cn/combat_ui/PAUSE_Ancient.png new file mode 100644 index 0000000000000000000000000000000000000000..2f9c7623ba26ace3f4466226b1eb36ec774ff129 GIT binary patch literal 3526 zcmeAS@N?(olHy`uVBq!ia0y~yU}H5l;xwcB8GY0goFwRPv_7%KuT83f?J`^9jeGY;&k`F-~K@5gr|xEJ!c z2LAbZeYShQ94}k5xPIK6KgEAu-g~>srTbOPieA?m3wu%m(|_gzhm=W+g}+bGI`(QzHMte zyr{?3RO#8(84`~Rvb`CY85l0uM$ewTaVBG5?*Fj)Jlo~$YIbbiTwGRGRs8vu&HC-h zeVNv-F(K)!20*Xf-5kYSQz&0@;`5_7Z`Ry@e?4C>jD7odPxENkcv+Ce2X6Vq?O!Lf z!}pF^dHmjA)pqm48Ea~4WbAH6f6f1$Z?usOftM_h+W!fWEUpJ340jOq|`uXmwz8AJe_zopr0GWHLB>(^b literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/os_handler/MISSION_OVERVIEW_EMPTY.png b/alas_wrapped/assets/cn/os_handler/MISSION_OVERVIEW_EMPTY.png new file mode 100644 index 0000000000000000000000000000000000000000..3769c8611f9e40aa7f92d53638f71516ea679880 GIT binary patch literal 7843 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 Yb?Tq?+pl+v{<+QPSHELq?~6D810!WYQ2+n{ literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_EASY.png b/alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..81cc31b9691bbaaca1dbfa765fcb7c6d4a3cb76a GIT binary patch literal 5134 zcmeH}>t7OP8^_VLbPEqlO_zkLRjfkoVAj-hR%Xu74pUPCmqn&>&=M3Obs1W@Qfq38 z#1s!DiYW>LsU4utPofs@IMepHJp0rwfr}kFxSfz ziWKbm*X6b(K}}W>j9e&w3K2wlMf@a&IZ-RO5G;lnQM~ zUb)OnS>Dl`U#wIZ2E7uaH8-Bomz5JUbo|?2vAYL2a{o~VDbkKq4y zg01L0bk0Vx3Gf8~u;_fvl3&NK=kx0?C7`C%wDJJYg?y3VCpS%YxxI1+eX*zAtFivu ztgYeXX{DxN+N7x|3k=!-s=(MeN#|sQ>qEH>x#EU0%ZFYDt^vs7N|cmXcyQ3@BR)sh zAp*DcC^k10oW$Nq+I|1ZT&qUl7h&NKSPcL)=DL^}P4z>(LeGSqK{m6SiQE^N+Qbh# zZmPSp4^sKqxA9TLs07@C$sCvW*lvrecP#rtX34!=RAHGtRq2!C@Yv&3?>5EjUBKFU zawHKIbuOmbev^yDZ!H7o-b1}W9DURz&_37f=YuGk?4vElI{|`Ib5)|4-YVB%)wJ02rNHb^O4suWY-L1uedo~0f z`gxyz$G^NkAPZ0%G3KI-jc0_e5^M>Y8T_U-lAS8oHO- zN_jcvVUcmKcgM|Y6)6%jlK(vJ*z=8EwylZ#u}AKX4L5cQiz9=rVK1Jlt`p$^<_~Z6 z`6kYERmMAbLuLv4^bscTd&=!NdYvisvwFa>KQ@+d_Hg$N+Z5xF5I?aCF()fMB!7fq zdC_)t+H22ko%gbt+j%U*2bgV5b(pd8d3{8S^Rqk8xP92XMC zlqeCmSOI}T6XG|JpacnEaRQM+OkgFWsP;COJHn|n1g(^ z4L)yrL2u&lQLd0~y9p&{j-5{;B*jhWVlVAA#|@_cYH{rI_|mX7xE`V6Jr31J4MAKr zdpgx~H$x!&Gjc)ijoV$2{@XK4cLy{MU8C{*dwzVMqG$uX^*R!6 z?D+XD-M-+uC#Re~$KxRlpru^4Zer?0UIja<3WlF>+bUlmN!O$;i42Q_hFOVoA%_q@ z%i}eHS&4ex&M59I$EUcvRH`Mhmx$nG_B}q+4LbvSHq`M{$=C$km3eknhqimGoz|1m zC{NqHQf8T=WGk#|wZBNI&zZAY#;)Ld4)yb`>qJ%+wPRX6G!pzzs6Ol_gcg2qUKN;sANJc zE(KQ34@ArkbTHM-Dpn1P<;aCVh!LN?>u44GHb5kNKH>0J79pL0OxO;~M4ov8dWODU z5fpl#R+Jz+IEpyK?vRLF=CI^2uM=meH9o-ry*T1+TpU?GPQY7D2-m!ZizTAo!QqJg(t`7)wfS`DrVb^ z!w}oX>?%j@zJhU^8>l3fa(keTW&%HZ{nOp*{<#Oy<8@HmJhd?6!T4Q9cU>hZt=V^a zX{XF$x#x3;oRN=7LRX%rX(P`Q{{xxam;ySfku~I&s)8qVSzvYR#V|=A_`rrNNI$+N z1dXnuf$6YzwjEX4Geix0jStzfqkuj}ZM&TMTwJ?4SYX2Q{L#ZqaTbMtST*qQ3GoDZ z;*B1AJu-`)Rnrz{Lm_7)rcBg&XinY<}Pf;W97dnrDxn47mQ(^Jrdgq1O__7Z4Zsoe8kv^W(u5t-Z zU{85^V3&~{0MO^E`v@yVV*?;x-`FFf)&b)h9M9+e)g_2KaQQ`R&`F`c7gx(u-ABbJ znR;(c2V;-}@0Nc*ES)p*NBE5Xz4kJ?aOD&1D_8e!Oa80cy zg@cEZYv=EgJRccRzrN0DmgQC{py@OPAHq8YffoD`zhC?F=&L_7j>p%&mH%nI6C@bo zG->JAy!AKkczw3>;JyHEpeNj-SzpspBFD~xo^JIdZ`=G zmJN@#@$qlxvyZ&?{>!INuYP{MnGoPTscQT0gUkD)tABf@l?O901054HY1!K5<~?^K zoxl!>W5zRh~{ zSz6c?UN5jA(P8>|ugewNj?SL{c(-%zx!cp#&t;ucLK6UN=uRU5 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_HARD.png b/alas_wrapped/assets/cn/raid/CHANGWU_OCR_REMAIN_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..058042521e3019848acca214cf16e15586cfca49 GIT binary patch literal 5120 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/raid/CHANGWU_RAID_EASY.png b/alas_wrapped/assets/cn/raid/CHANGWU_RAID_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..f77fe6413bf9f1c90dae3b7616d7332a658c1c0f GIT binary patch literal 6103 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/raid/CHANGWU_RAID_EX.png b/alas_wrapped/assets/cn/raid/CHANGWU_RAID_EX.png new file mode 100644 index 0000000000000000000000000000000000000000..6488b54c7d6b5608455c5edf4498ae37fc90bd07 GIT binary patch literal 7518 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 J@PB$d{VxDniS+;g literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/raid/CHANGWU_RAID_HARD.png b/alas_wrapped/assets/cn/raid/CHANGWU_RAID_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..e221a3764a97b253957a759cc63c6a477343d495 GIT binary patch literal 6385 zcmeH~XNm-h4oQ}?@V}_)r zW~KzDOrfHn;*y#xii(1=xZ(RLc#Ie457Gj6CA^USgo&~2%(olaVuIw^cxYJC);*Ro zB2&Dqt?{g*=<1T=3lj6`j|W$fQr8DDGPY!Z;eK3E_1pPI;2VK&1ilgY-y-lt`LfVg zA<1Dce;9jYSEGTdClR@K=ar-XN(g-a@Ru*h&e7z>+|!!61yh)dUR)Ey+KRb97chiN zzXm>nyJ#LzZS^vg&372q^phQBAE~P~&sL2T*B#b8{Ud73-!0>im0aI+^zMA7irA?O z_}yuLl6Aj#WMuiibdJ9%wVRg6a>0qPqh20!JWALmDSI;>Xm&#B-MSQ2tS|ejMI;VO$6@Hb!Ki2aynelG3W1}9jHkSCEkp8vTd?9 zICYiSmkqC7TR3L#C|~OyX@uRS^r{8jcv+eVZ+a9WEzJ$ejkQYK^}&-Pmk+tQB^UdY z*DN;6%@_`wVmgBn7i4qY<|yf9pReB6ieW1=DVt15+{6UoOR2yvYQuG_?xraO{~yB| zE#suY(a&!+-Ygbh(R2=x$ai=&kI=$GMKj)^5{8uX>670BD(g|GMODmRs$q_*TA&QK zwiY>SH)MZ(^w$)->mGTvk7kw=sQh6DqA_c=VzPOi29q!G>g)64_Uj~=+U&1WAT`p& zWm#w)pPdcP&IZ|Yp~1n$Ol*X%{V9;{>m!IffV=T-AzufY1LG z%MU^psDj}3jASxnbpr^)pqGj%mXwH4#KcK<%L<5LWo5P6geQ>w=dtptmH{%%69$uZ zP*TxL(<37z?`N_|nVB4O*k#7{g8;zIiC+qPpIK%uck@Q#pE3?5shu>84AZQM5fYO@ z?2fKZH`hD*E&3jL@_WcTN3UpUr;tdNJYGO#r1fGdHB4|SZk07Y?&Tfn?HuRg?d@Ha zKW1rdeX@Fm{c`|4Hd-Ry>?hAUZEc(#_^liIjqrKPGLU0cj2@JXk1xB{?!}ePnYcYo zIiQTxZV229e(|zvLLG~+$mue!E<-fVJUz_iviTZ2Pb)f3Q2z5NdK~D|vAWhHF`Fx3 z$1iiYBUj21A=I*yg`TanW?JT4NOfcP!hOx19!{Gvg7WNvFG{GpUq7AhYt}=i3q-ep zweDMJguM2J}lYQAGT z^qL7XGr{TYztarFeEqzbWyzLUAB;t86-`Cqq!3`!i0AZ+^+Qtu_p1pY})Z3sTCU(7dfI9@`a<5xtj%NAY zzP)dV3MZD3zyF-D^k&M{j>qXakVmPiI$`b9H{3V4)(kS68?Gv;C?e5FGXnW06+NJ| z^m*SKnf$`~euYh`p9cs)(a8tonJ->wAJziX?1u;YE(ZjZ$87QWoxuZ9?jFot>Z#4C zX=#hY{+J$?*Xg4Mdk*8lc=E6fYOQ!}+KB8(j~0lR0LRln?VS_y@iwn3&y6%OTO6Re(}%Hq9p2*i z?b*soN>1xj-MsuBbe!}#)z#Hi#||}mUC;@{RFaE>17sK%5fdgGtKY{7dHk<;Mf+GfDi5e59h4uM8jn3{R*LXdA{#E`&M^$@}*^Oc|pRmH9&$5P3Qy+!Aw8YjB zzqcKP+)wW6Je@9gcbt=-U3x3<-IEpc9aSp;VByuW1l%_^)_ z1Fkoi`!mA&nV5C%vA9@#XPPP4-!}BQO5@b$pY+3)1*RRM*}gtkj4ANk?*uoXFF#2v zf~~Vb&h4+)O`?Mz1eV--cOF66qn;X28;*{ph5sZB%6nW!DOlOs_aT^# zls65TZH_r@hQcN6rSNMPw;BH(y>bT`RAxIh`$bhg8-kM|zKn@NG;vV?)IT)LHm zg^%IzD+0Y3v1oum?0$FTidLyf>JFgjY;+mb$M34!I6}5Cm}sf2j_t?JhSBuWiKI)%``OZzbAJH< zPn6Ful+3~*A^vpqm&w}Np9S(sY-s>U1K zq#hfmhysXd?O+TPFRkNw@$i+sb+tDMp1fnMISA;Q9pk}8l z^J8kA=KZglSsS|#WS~u5l!!psAjH8+Uay>&V~@4Q!j~e57?<}b@$dw;Dw0Yvw6wFs z2ov-(@+=Jl-huW%P3|T36{ODHe-J9BV<<&Mw32e$9CvGL>%RUzWB+bYv}EmLU-!bG zBJ&yyyE(+nk^Hp8IoG=c^HSjjx#~@YWZbHRS`jiUwtKvu9iIngU^efCnKD8;tX0RH zdglDatHR5Vr)-ikfRV4mfp13BL!pXP_)9PI0%~ETzN?E3c4SZst4=uKnfrYcQshi? zvIDX0&F2JjpKDOHukyAB7Wzu=idW=N?AqA--~sq=vNMm*6(>x+%f$pM6bl z&~n=+8qHk^S0_&JZ(eK8fG9QjpzpV^bL{EN9QwwH^HcG#j@BWqFCy$_>gwvGef+Ve z#*7pbreIRaXE!(h24=`N_$^UQ`Z6*MiyWUK&Uvwc$G zYhGH157Qpcy89U*qsmOQS#0*kYPRzE7ClI0NeTR|2^@7xkgHfrDk7u%`}>jrmS0qs+-eH~+DViF_VC+PUi`{bf#;uf^P>Z!{cSqCYq{>^*PaFP=KB79`W4V5ig5*S~%-qwnuo$fv&zFU10nPQ`qIg zzt@VvH3znM-Isl#o&hS2Hn1_DWXp&jp6n|Gm^wadb=9uz_pWb9PI=a9<5yf yMjJ-Pr`K95GXLB8M&KKPZv_5d0&a#-4k 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 Rdh_Gh6)OizvW55G{|oGFnM(iw literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/cn/raid/RPG_BACK.png b/alas_wrapped/assets/cn/raid/RPG_BACK.png new file mode 100644 index 0000000000000000000000000000000000000000..6c02673af7abdfc501bfbfc08ec689fc4b13747d GIT binary patch literal 4092 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 Y2k=cl!H*`SegFUf07*qoM6N<$f+zUnGynhq literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/en/os_handler/MISSION_OVERVIEW_EMPTY.png b/alas_wrapped/assets/en/os_handler/MISSION_OVERVIEW_EMPTY.png new file mode 100644 index 0000000000000000000000000000000000000000..3769c8611f9e40aa7f92d53638f71516ea679880 GIT binary patch literal 7843 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 Yb?Tq?+pl+v{<+QPSHELq?~6D810!WYQ2+n{ literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_EASY.png b/alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..81cc31b9691bbaaca1dbfa765fcb7c6d4a3cb76a GIT binary patch literal 5134 zcmeH}>t7OP8^_VLbPEqlO_zkLRjfkoVAj-hR%Xu74pUPCmqn&>&=M3Obs1W@Qfq38 z#1s!DiYW>LsU4utPofs@IMepHJp0rwfr}kFxSfz ziWKbm*X6b(K}}W>j9e&w3K2wlMf@a&IZ-RO5G;lnQM~ zUb)OnS>Dl`U#wIZ2E7uaH8-Bomz5JUbo|?2vAYL2a{o~VDbkKq4y zg01L0bk0Vx3Gf8~u;_fvl3&NK=kx0?C7`C%wDJJYg?y3VCpS%YxxI1+eX*zAtFivu ztgYeXX{DxN+N7x|3k=!-s=(MeN#|sQ>qEH>x#EU0%ZFYDt^vs7N|cmXcyQ3@BR)sh zAp*DcC^k10oW$Nq+I|1ZT&qUl7h&NKSPcL)=DL^}P4z>(LeGSqK{m6SiQE^N+Qbh# zZmPSp4^sKqxA9TLs07@C$sCvW*lvrecP#rtX34!=RAHGtRq2!C@Yv&3?>5EjUBKFU zawHKIbuOmbev^yDZ!H7o-b1}W9DURz&_37f=YuGk?4vElI{|`Ib5)|4-YVB%)wJ02rNHb^O4suWY-L1uedo~0f z`gxyz$G^NkAPZ0%G3KI-jc0_e5^M>Y8T_U-lAS8oHO- zN_jcvVUcmKcgM|Y6)6%jlK(vJ*z=8EwylZ#u}AKX4L5cQiz9=rVK1Jlt`p$^<_~Z6 z`6kYERmMAbLuLv4^bscTd&=!NdYvisvwFa>KQ@+d_Hg$N+Z5xF5I?aCF()fMB!7fq zdC_)t+H22ko%gbt+j%U*2bgV5b(pd8d3{8S^Rqk8xP92XMC zlqeCmSOI}T6XG|JpacnEaRQM+OkgFWsP;COJHn|n1g(^ z4L)yrL2u&lQLd0~y9p&{j-5{;B*jhWVlVAA#|@_cYH{rI_|mX7xE`V6Jr31J4MAKr zdpgx~H$x!&Gjc)ijoV$2{@XK4cLy{MU8C{*dwzVMqG$uX^*R!6 z?D+XD-M-+uC#Re~$KxRlpru^4Zer?0UIja<3WlF>+bUlmN!O$;i42Q_hFOVoA%_q@ z%i}eHS&4ex&M59I$EUcvRH`Mhmx$nG_B}q+4LbvSHq`M{$=C$km3eknhqimGoz|1m zC{NqHQf8T=WGk#|wZBNI&zZAY#;)Ld4)yb`>qJ%+wPRX6G!pzzs6Ol_gcg2qUKN;sANJc zE(KQ34@ArkbTHM-Dpn1P<;aCVh!LN?>u44GHb5kNKH>0J79pL0OxO;~M4ov8dWODU z5fpl#R+Jz+IEpyK?vRLF=CI^2uM=meH9o-ry*T1+TpU?GPQY7D2-m!ZizTAo!QqJg(t`7)wfS`DrVb^ z!w}oX>?%j@zJhU^8>l3fa(keTW&%HZ{nOp*{<#Oy<8@HmJhd?6!T4Q9cU>hZt=V^a zX{XF$x#x3;oRN=7LRX%rX(P`Q{{xxam;ySfku~I&s)8qVSzvYR#V|=A_`rrNNI$+N z1dXnuf$6YzwjEX4Geix0jStzfqkuj}ZM&TMTwJ?4SYX2Q{L#ZqaTbMtST*qQ3GoDZ z;*B1AJu-`)Rnrz{Lm_7)rcBg&XinY<}Pf;W97dnrDxn47mQ(^Jrdgq1O__7Z4Zsoe8kv^W(u5t-Z zU{85^V3&~{0MO^E`v@yVV*?;x-`FFf)&b)h9M9+e)g_2KaQQ`R&`F`c7gx(u-ABbJ znR;(c2V;-}@0Nc*ES)p*NBE5Xz4kJ?aOD&1D_8e!Oa80cy zg@cEZYv=EgJRccRzrN0DmgQC{py@OPAHq8YffoD`zhC?F=&L_7j>p%&mH%nI6C@bo zG->JAy!AKkczw3>;JyHEpeNj-SzpspBFD~xo^JIdZ`=G zmJN@#@$qlxvyZ&?{>!INuYP{MnGoPTscQT0gUkD)tABf@l?O901054HY1!K5<~?^K zoxl!>W5zRh~{ zSz6c?UN5jA(P8>|ugewNj?SL{c(-%zx!cp#&t;ucLK6UN=uRU5 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_HARD.png b/alas_wrapped/assets/en/raid/CHANGWU_OCR_REMAIN_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..058042521e3019848acca214cf16e15586cfca49 GIT binary patch literal 5120 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/en/raid/CHANGWU_RAID_EASY.png b/alas_wrapped/assets/en/raid/CHANGWU_RAID_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..2d92d8c58b76860d41cd5603708daff694c13e16 GIT binary patch literal 5316 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 J@PB$d{VxDniS+;g literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/en/raid/CHANGWU_RAID_HARD.png b/alas_wrapped/assets/en/raid/CHANGWU_RAID_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..e04d8b506fa82b7c580e564c8bcc09aad5739466 GIT binary patch literal 4919 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 bQnv9!0tv>YM3< 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 KMNUMnLSTX>!4PWz literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/gui/css/alas-mobile.css b/alas_wrapped/assets/gui/css/alas-mobile.css index eb94121448..9acad6eb77 100644 --- a/alas_wrapped/assets/gui/css/alas-mobile.css +++ b/alas_wrapped/assets/gui/css/alas-mobile.css @@ -35,14 +35,6 @@ grid-template-columns: 1fr auto; grid-template-rows: 1fr auto; } -#pywebio-scope-dashboard { - font-weight: 400; - width: 100%; - display: grid; - grid-auto-flow: row; - grid-template-columns: repeat(auto-fit,minmax(6rem,1fr)); - overflow: hidden; -} #pywebio-scope-_groups { grid-template-columns: 0fr 1fr; @@ -67,26 +59,4 @@ #pywebio-scope-waiting, #pywebio-scope-log { overflow-y: auto; -} - -#output-container .status-point { - margin: 45% 50% 55% 50%; - width: .4rem; - height: .4rem; - border-radius: 50%; -} - -*[style*="--dashboard-limit--"] { - font-size: .8rem; - padding: 0.2rem 0 0 0; -} -*[style*="--dashboard-value--"] { - font-size: 1rem; - margin: 0 0 0 0.4rem !important; -} -*[style*="--dashboard-total--"] { - font-size: 1rem; -} -*[style*="--dashboard-help--"] { - font-size: .6rem; } \ No newline at end of file diff --git a/alas_wrapped/assets/gui/css/alas-pc.css b/alas_wrapped/assets/gui/css/alas-pc.css index 0adfa25911..9240171a0c 100644 --- a/alas_wrapped/assets/gui/css/alas-pc.css +++ b/alas_wrapped/assets/gui/css/alas-pc.css @@ -44,4 +44,4 @@ display: grid; grid-auto-flow: column; grid-template-columns: auto auto; -} +} \ No newline at end of file diff --git a/alas_wrapped/assets/gui/css/alas.css b/alas_wrapped/assets/gui/css/alas.css index c73287919f..94e696480f 100644 --- a/alas_wrapped/assets/gui/css/alas.css +++ b/alas_wrapped/assets/gui/css/alas.css @@ -133,13 +133,6 @@ footer { font-size: 0.875rem; } -.status-point { - margin: 37% 50% 63% 50%; - width: .75rem; - height: .75rem; - border-radius: 50%; -} - input[type="checkbox"] { width: 1.25rem; height: 1.25rem; @@ -406,31 +399,11 @@ pre.rich-traceback-code { justify-content: space-between; } -#pywebio-scope-log-bar { - flex-wrap: wrap; -} - #pywebio-scope-log-bar-btns { display: grid; grid-auto-flow: column; } -#pywebio-scope-log-bar .hr-group { - width: 100%; -} - -#pywebio-scope-dashboard { - font-weight: 400; - width: 100%; - display: grid; - grid-auto-flow: row; - grid-template-columns: repeat(auto-fit,minmax(10rem,1fr)); -} - -#pywebio-scope-dashboard .form-control{ - padding: 0 0 0; - } - #pywebio-scope-log { line-height: 1.2; font-size: 0.85rem; @@ -550,50 +523,4 @@ pre.rich-traceback-code { width: 1.5rem; height: 1.5rem; border: .2em solid currentColor; -} - -/**[style*="--dashboard-value--"] {*/ -/* font-size: 1.3rem;*/ -/* font-weight: 400;*/ -/* margin: 0 0 -0.2rem 0.6rem !important;*/ -/* overflow-wrap: break-word;*/ -/* overflow: visible;*/ -/* border-bottom: 0;*/ -/*}*/ - -*[style*="--dashboard-value--"] { - font-size: 1.2rem; - font-weight: 400; - margin: 0 0 0 0.6rem !important; - font-family: - "Arial", - serif; -} - -*[style*="--dashboard-total--"] { - font-size: 1.2rem; - font-weight: 400; - margin: 0 0 0 0.15rem !important; - font-family: - "Arial", - serif; -} - -*[style*="--dashboard-help--"] { - font-size: .8rem; - margin: 0 0 0 0.6rem !important; - font-family: - "Arial", - serif; -} - -*[style*="--dashboard-limit--"] { - font-weight: 400; - font-size: .9rem; - margin: 0 0 0 0 !important; - vertical-align: text-bottom; - overflow-wrap: normal; - font-family: - "Arial", - serif; -} +} \ No newline at end of file diff --git a/alas_wrapped/assets/gui/css/dark-alas.css b/alas_wrapped/assets/gui/css/dark-alas.css index 7d097c06af..c36fa31b06 100644 --- a/alas_wrapped/assets/gui/css/dark-alas.css +++ b/alas_wrapped/assets/gui/css/dark-alas.css @@ -149,19 +149,7 @@ pre.rich-traceback-code { border-bottom: 1px solid #36393f; } -#pywebio-scope-dashboard input { - background-color: #2f3136 !important; -} - *[style*="--arg-help--"], [id^="pywebio-scope-group_"] > p + p { color: #adb5bd; -} - -*[style*="--dashboard-help--"]{ - color: #adb5bd; -} - -*[style*="--dashboard-limit--"]{ - color: #adb5bd; -} +} \ No newline at end of file diff --git a/alas_wrapped/assets/gui/css/light-alas.css b/alas_wrapped/assets/gui/css/light-alas.css index af57893987..b65067f113 100644 --- a/alas_wrapped/assets/gui/css/light-alas.css +++ b/alas_wrapped/assets/gui/css/light-alas.css @@ -60,10 +60,6 @@ input[type="checkbox"] { accent-color: #7a77bb; } -#pywebio-scope-dashboard input { - background-color: white !important; -} - select { background-image: url("data:image/png;base64,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"); } @@ -155,12 +151,4 @@ pre.rich-traceback-code { *[style*="--arg-help--"], [id^="pywebio-scope-group_"] > p + p { color: #777777; -} - -*[style*="--dashboard-help--"]{ - color: #777777; -} - -*[style*="--dashboard-limit--"]{ - color: #777777; } \ No newline at end of file diff --git a/alas_wrapped/assets/jp/os_handler/MISSION_OVERVIEW_EMPTY.png b/alas_wrapped/assets/jp/os_handler/MISSION_OVERVIEW_EMPTY.png new file mode 100644 index 0000000000000000000000000000000000000000..3769c8611f9e40aa7f92d53638f71516ea679880 GIT binary patch literal 7843 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 Yb?Tq?+pl+v{<+QPSHELq?~6D810!WYQ2+n{ literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_EASY.png b/alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..81cc31b9691bbaaca1dbfa765fcb7c6d4a3cb76a GIT binary patch literal 5134 zcmeH}>t7OP8^_VLbPEqlO_zkLRjfkoVAj-hR%Xu74pUPCmqn&>&=M3Obs1W@Qfq38 z#1s!DiYW>LsU4utPofs@IMepHJp0rwfr}kFxSfz ziWKbm*X6b(K}}W>j9e&w3K2wlMf@a&IZ-RO5G;lnQM~ zUb)OnS>Dl`U#wIZ2E7uaH8-Bomz5JUbo|?2vAYL2a{o~VDbkKq4y zg01L0bk0Vx3Gf8~u;_fvl3&NK=kx0?C7`C%wDJJYg?y3VCpS%YxxI1+eX*zAtFivu ztgYeXX{DxN+N7x|3k=!-s=(MeN#|sQ>qEH>x#EU0%ZFYDt^vs7N|cmXcyQ3@BR)sh zAp*DcC^k10oW$Nq+I|1ZT&qUl7h&NKSPcL)=DL^}P4z>(LeGSqK{m6SiQE^N+Qbh# zZmPSp4^sKqxA9TLs07@C$sCvW*lvrecP#rtX34!=RAHGtRq2!C@Yv&3?>5EjUBKFU zawHKIbuOmbev^yDZ!H7o-b1}W9DURz&_37f=YuGk?4vElI{|`Ib5)|4-YVB%)wJ02rNHb^O4suWY-L1uedo~0f z`gxyz$G^NkAPZ0%G3KI-jc0_e5^M>Y8T_U-lAS8oHO- zN_jcvVUcmKcgM|Y6)6%jlK(vJ*z=8EwylZ#u}AKX4L5cQiz9=rVK1Jlt`p$^<_~Z6 z`6kYERmMAbLuLv4^bscTd&=!NdYvisvwFa>KQ@+d_Hg$N+Z5xF5I?aCF()fMB!7fq zdC_)t+H22ko%gbt+j%U*2bgV5b(pd8d3{8S^Rqk8xP92XMC zlqeCmSOI}T6XG|JpacnEaRQM+OkgFWsP;COJHn|n1g(^ z4L)yrL2u&lQLd0~y9p&{j-5{;B*jhWVlVAA#|@_cYH{rI_|mX7xE`V6Jr31J4MAKr zdpgx~H$x!&Gjc)ijoV$2{@XK4cLy{MU8C{*dwzVMqG$uX^*R!6 z?D+XD-M-+uC#Re~$KxRlpru^4Zer?0UIja<3WlF>+bUlmN!O$;i42Q_hFOVoA%_q@ z%i}eHS&4ex&M59I$EUcvRH`Mhmx$nG_B}q+4LbvSHq`M{$=C$km3eknhqimGoz|1m zC{NqHQf8T=WGk#|wZBNI&zZAY#;)Ld4)yb`>qJ%+wPRX6G!pzzs6Ol_gcg2qUKN;sANJc zE(KQ34@ArkbTHM-Dpn1P<;aCVh!LN?>u44GHb5kNKH>0J79pL0OxO;~M4ov8dWODU z5fpl#R+Jz+IEpyK?vRLF=CI^2uM=meH9o-ry*T1+TpU?GPQY7D2-m!ZizTAo!QqJg(t`7)wfS`DrVb^ z!w}oX>?%j@zJhU^8>l3fa(keTW&%HZ{nOp*{<#Oy<8@HmJhd?6!T4Q9cU>hZt=V^a zX{XF$x#x3;oRN=7LRX%rX(P`Q{{xxam;ySfku~I&s)8qVSzvYR#V|=A_`rrNNI$+N z1dXnuf$6YzwjEX4Geix0jStzfqkuj}ZM&TMTwJ?4SYX2Q{L#ZqaTbMtST*qQ3GoDZ z;*B1AJu-`)Rnrz{Lm_7)rcBg&XinY<}Pf;W97dnrDxn47mQ(^Jrdgq1O__7Z4Zsoe8kv^W(u5t-Z zU{85^V3&~{0MO^E`v@yVV*?;x-`FFf)&b)h9M9+e)g_2KaQQ`R&`F`c7gx(u-ABbJ znR;(c2V;-}@0Nc*ES)p*NBE5Xz4kJ?aOD&1D_8e!Oa80cy zg@cEZYv=EgJRccRzrN0DmgQC{py@OPAHq8YffoD`zhC?F=&L_7j>p%&mH%nI6C@bo zG->JAy!AKkczw3>;JyHEpeNj-SzpspBFD~xo^JIdZ`=G zmJN@#@$qlxvyZ&?{>!INuYP{MnGoPTscQT0gUkD)tABf@l?O901054HY1!K5<~?^K zoxl!>W5zRh~{ zSz6c?UN5jA(P8>|ugewNj?SL{c(-%zx!cp#&t;ucLK6UN=uRU5 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_HARD.png b/alas_wrapped/assets/jp/raid/CHANGWU_OCR_REMAIN_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..058042521e3019848acca214cf16e15586cfca49 GIT binary patch literal 5120 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/jp/raid/CHANGWU_RAID_EASY.png b/alas_wrapped/assets/jp/raid/CHANGWU_RAID_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..57b3d424eac0322332498b3d26b61571e5c0a4d8 GIT binary patch literal 5732 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 J@PB$d{VxDniS+;g literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/jp/raid/CHANGWU_RAID_HARD.png b/alas_wrapped/assets/jp/raid/CHANGWU_RAID_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..a3078d7995dbd25dbf035858c3467241cad3c270 GIT binary patch literal 6547 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/jp/raid/CHANGWU_RAID_NORMAL.png b/alas_wrapped/assets/jp/raid/CHANGWU_RAID_NORMAL.png new file mode 100644 index 0000000000000000000000000000000000000000..1e12dc27998e59e36402275359fc72a487d2f11e GIT binary patch literal 6190 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/jp/raid/RPG_BACK.png b/alas_wrapped/assets/jp/raid/RPG_BACK.png new file mode 100644 index 0000000000000000000000000000000000000000..6c02673af7abdfc501bfbfc08ec689fc4b13747d GIT binary patch literal 4092 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 Yb?Tq?+pl+v{<+QPSHELq?~6D810!WYQ2+n{ literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_EASY.png b/alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..81cc31b9691bbaaca1dbfa765fcb7c6d4a3cb76a GIT binary patch literal 5134 zcmeH}>t7OP8^_VLbPEqlO_zkLRjfkoVAj-hR%Xu74pUPCmqn&>&=M3Obs1W@Qfq38 z#1s!DiYW>LsU4utPofs@IMepHJp0rwfr}kFxSfz ziWKbm*X6b(K}}W>j9e&w3K2wlMf@a&IZ-RO5G;lnQM~ zUb)OnS>Dl`U#wIZ2E7uaH8-Bomz5JUbo|?2vAYL2a{o~VDbkKq4y zg01L0bk0Vx3Gf8~u;_fvl3&NK=kx0?C7`C%wDJJYg?y3VCpS%YxxI1+eX*zAtFivu ztgYeXX{DxN+N7x|3k=!-s=(MeN#|sQ>qEH>x#EU0%ZFYDt^vs7N|cmXcyQ3@BR)sh zAp*DcC^k10oW$Nq+I|1ZT&qUl7h&NKSPcL)=DL^}P4z>(LeGSqK{m6SiQE^N+Qbh# zZmPSp4^sKqxA9TLs07@C$sCvW*lvrecP#rtX34!=RAHGtRq2!C@Yv&3?>5EjUBKFU zawHKIbuOmbev^yDZ!H7o-b1}W9DURz&_37f=YuGk?4vElI{|`Ib5)|4-YVB%)wJ02rNHb^O4suWY-L1uedo~0f z`gxyz$G^NkAPZ0%G3KI-jc0_e5^M>Y8T_U-lAS8oHO- zN_jcvVUcmKcgM|Y6)6%jlK(vJ*z=8EwylZ#u}AKX4L5cQiz9=rVK1Jlt`p$^<_~Z6 z`6kYERmMAbLuLv4^bscTd&=!NdYvisvwFa>KQ@+d_Hg$N+Z5xF5I?aCF()fMB!7fq zdC_)t+H22ko%gbt+j%U*2bgV5b(pd8d3{8S^Rqk8xP92XMC zlqeCmSOI}T6XG|JpacnEaRQM+OkgFWsP;COJHn|n1g(^ z4L)yrL2u&lQLd0~y9p&{j-5{;B*jhWVlVAA#|@_cYH{rI_|mX7xE`V6Jr31J4MAKr zdpgx~H$x!&Gjc)ijoV$2{@XK4cLy{MU8C{*dwzVMqG$uX^*R!6 z?D+XD-M-+uC#Re~$KxRlpru^4Zer?0UIja<3WlF>+bUlmN!O$;i42Q_hFOVoA%_q@ z%i}eHS&4ex&M59I$EUcvRH`Mhmx$nG_B}q+4LbvSHq`M{$=C$km3eknhqimGoz|1m zC{NqHQf8T=WGk#|wZBNI&zZAY#;)Ld4)yb`>qJ%+wPRX6G!pzzs6Ol_gcg2qUKN;sANJc zE(KQ34@ArkbTHM-Dpn1P<;aCVh!LN?>u44GHb5kNKH>0J79pL0OxO;~M4ov8dWODU z5fpl#R+Jz+IEpyK?vRLF=CI^2uM=meH9o-ry*T1+TpU?GPQY7D2-m!ZizTAo!QqJg(t`7)wfS`DrVb^ z!w}oX>?%j@zJhU^8>l3fa(keTW&%HZ{nOp*{<#Oy<8@HmJhd?6!T4Q9cU>hZt=V^a zX{XF$x#x3;oRN=7LRX%rX(P`Q{{xxam;ySfku~I&s)8qVSzvYR#V|=A_`rrNNI$+N z1dXnuf$6YzwjEX4Geix0jStzfqkuj}ZM&TMTwJ?4SYX2Q{L#ZqaTbMtST*qQ3GoDZ z;*B1AJu-`)Rnrz{Lm_7)rcBg&XinY<}Pf;W97dnrDxn47mQ(^Jrdgq1O__7Z4Zsoe8kv^W(u5t-Z zU{85^V3&~{0MO^E`v@yVV*?;x-`FFf)&b)h9M9+e)g_2KaQQ`R&`F`c7gx(u-ABbJ znR;(c2V;-}@0Nc*ES)p*NBE5Xz4kJ?aOD&1D_8e!Oa80cy zg@cEZYv=EgJRccRzrN0DmgQC{py@OPAHq8YffoD`zhC?F=&L_7j>p%&mH%nI6C@bo zG->JAy!AKkczw3>;JyHEpeNj-SzpspBFD~xo^JIdZ`=G zmJN@#@$qlxvyZ&?{>!INuYP{MnGoPTscQT0gUkD)tABf@l?O901054HY1!K5<~?^K zoxl!>W5zRh~{ zSz6c?UN5jA(P8>|ugewNj?SL{c(-%zx!cp#&t;ucLK6UN=uRU5 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_HARD.png b/alas_wrapped/assets/tw/raid/CHANGWU_OCR_REMAIN_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..058042521e3019848acca214cf16e15586cfca49 GIT binary patch literal 5120 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/tw/raid/CHANGWU_RAID_EASY.png b/alas_wrapped/assets/tw/raid/CHANGWU_RAID_EASY.png new file mode 100644 index 0000000000000000000000000000000000000000..a05ce4ed84ac6abb0f135e7dff19d0cb17de1e39 GIT binary patch literal 6266 zcmeH}`#;nD|HoIZ4p%Pe;}R~zu8Jt416mAS7DC$PoO7j!IiHzPS2Y(x(GZ$XIiGVr z4v|yNhYiDo*ygaY%{IpNU3L5Z^!*dQJ%4$>e|o*1ulw`yd_JDBf8RC|IVOD!004*> z-@IV~02~th9vnG(Q1H19GC2SMNd0Ae<1ec)!p5A#3)yYtk|Y(*#*UO{V~>JDk2 znvy@eya+rj^sCkL>wk)vpM0L{$(XezDq9y9pEoF$&XpHa5H&lAJ}*-L7z*f_I8%=uHx;wb17$?Z$XZ$4alfX{` zKMDLK@RPt#0{@2qr)DZzu%|d$vwddMySGv=IGBjoMMv`~)spe}(uR|;4%zU?9SkJ? zeKW0^VlUy>=l{uj!T|tMeivNw;7ylK<43 zy*|)9|I1U+kdOhbko766#(@3{^^o0OTd0*B2m}HGSm=2g4qw$^t#jVVfl?a=1tpO zV_Gt%d+ZLagSAo<`GLO;?(bYEu_=onzl*7QR8vv0@4{c+DJm*bYJvx4`VVEL9%9(4+3igwMemg!yLj*-0C4e&Ps>*K3A3dzh!+e78_w3_V-OOd!NJ~y zno2Ad$ygZ_gzHjxh6LO`!hOBln{WjEkx=b1BS z^mh7S`T6-{xkZkudV9DH5sKfVNY=DFX7KT-yD?O38T~S6ezta4<411o9nXbrE1YFDyL)<~Ih~+Z#O8o)D;-@|mztQE2$|oe z_p9}ocjlXAHZ?V=IyP)hSi-j1Y&EPKuFiW#A3$ASA7CKg!H%|Xt2<@(uh#o4jjNa0 ztRiv<;&y5Ao$pAWK7A^fiO1kz5vUuo_@_^wmjCB_$%LqLQMPag*T&0R%p?pFK0He4 z%MUE*Nh|YI(GZ_*sHmV#ja~_zt=wPiiXYZt&tk5^ZkuU1uahLfCk4wnJ4Y|D#3G1YQfiLtkdB)Tod>)+jza3M@K;&J1&*5r|!qwDWzTzK@fQw=^dbsH-plSZ}E7;SBBAP@*YG;&QjB2Z^`B)#tUPp^u(yJWP@6%sH6E65cuh2)=h8yo z%`;8Y2mNn~TsD@mDdZAIw-AS+X0@(i(ET{|3(Izv829a%lp}`^`s-*djN7#2#2H^-7s4cWyFw>|?Id~otrq%q zMjUW|@xpu|FDx(%{dqYcSwe5J!exAJF1aAZp?=QdoT6Zs4=bCmN}mD<0RUJ2YhQ*Z zIjFkj$GtCesK*nBE@)_K8k+z6sSD9!X=%A#AHJp|(3|<*05DC>#aaFk>AjuQO=b@I@}_GiogF}dv`j80nz!@vX(ub+tRBxV zHi(ya4s8Lv_^$JGuqP*IXb)H6T%j=awY-@cH?yQ%@g<+4h@NuoNyP51miSMICvOmH z`IfiL^k)ZCl!_jNt6$UL5nn;WgMG*@Z^|`OGv0??exav%Eh6m8-!>lQwwXEhX5i8z zi89%k&tpn~2`3&O06czkyz^9s&H376e&)wpA+CXD$Q(>?XUG&CSg@gp!U0@pn4EeAx`_-vCNWORu6y5ieEI&kp`_7XY|>)+$~a4t@|Qq~m~Ir2wu&g7LPp9vStlYCy9<&VtOgb7N}yJ- zrXOb?LOHr|%G%xkwN>>+&tBZAUv1xe{OFhldo3+30)b#t;jH6JY+3V_DuvNn?svsm z6p7>Kd93E2gBR7+YkF zxYE((ANQq3P-=DeaqaEbkG6XdE88+oY&1(*jf@wgDegn-MSz;0{3Y%OIv_g9Ku9-HEEtzfEOkdji`irH?4{JOw$uN5RS;==RbUbP2 zsN%nYiu^*Jj*X2~_ihNg1T!%xPoP`KXviBwWzycCuwTnQIApqg+OH9EO-JXI(K0wO zMlInt7wJ7tf4gPZdDYWCGS(f*Ur#}daC9c~>y7;>6;(Wf)%r(jL%#HzBf{V8gQK@a z&Pl?%Jy9*QuPwH3+$+n$U@%I~7X{W@$SU^RD-!d&+3$F>y=`|zj~}Pb)T<3UKE7w3JaF`hZ@^%@$Dhe)XKLll9>rUK?({moT2||KLHc9Fom$<4#61>X()}K}e># z^SRGe%Kq6=*reap;DB;j>R?%EtLCB-!_wNAZ%$(5E3M)(xwQxF@ZO9&?;8bK4*N`bGONZ&I{PulM%c)WkKF8i?&+G9%wqtY$)bs}*xgr63jeoU~%|BU}91UBb)V7#Ee4Hy19HvR3% O#s;@D2J+hN&g3wH-xup=zr&mWrjCP&5)tsx3McqguNd zqNO3Riv~$iYb-^q5h)r$tO+6^$uFJfc>aRt&GVwyaUaKhzq;=8{+!?Idw%b~?pRq! zA5=I9005*duV1?h0PL4M?*07JUdio>%8fk$fb#E_*RI-yPb||H(%hI)>TA`L5j%C% z0NhWis?YDmUTtal_2m`MKRjX{&J@k4)>WD!-Y2-dFPdxHJ)mu`ZELG-n|A9ge*F_( zRR(g=!#lm=-q?ww1_ypT^%3$%j_mWlE7BccJaovMA#0Kp_4|FY-&}8Kl(Bx)&Q!G_ z>llF%i9oY>*N#Z)`M$mr_)g$Ef&V*!UGcfCe91N*GNSo36*d`G@s&p)Uhr*6yW*gD@cDgf{^uMc`K91{6&}rB1{}dC?n;D;Qi6fd$Z0sy;9^zK&|H!W ze)o>%0gLr+Z7apb3J?vn!eqJ`??=l6_tmu_3hGJs?GC5H+g6| zona{HhtDq*qdy>NBccMU;vG1HlPWx6@W=6QZ?9}0qBl!LY%Q=v(=U5MdQn4khd?8>~hLv-;BZuH{EDLlVvZWQEwPox{}? zp~`h`M~mUG1tWX3NvIeD7MGF{4;Zvl_X$JI@37g<+{nCN|5b<8jGPUGRwakVu)0)(is{+o!bgBY+$3dJk)zt0KwdPjX*nkzce+*X;Qc| za{W!=8*2ltJ3!%FWrq(G?;WJe^hml;Gfqs8*{ba6DChJRS6sRH?od+a=clhjLbyUg|PH=#2@`r zL;I9s+hSaLA%q+sAAxjM)yf_`H!$;`ofdq)m>{lzBO_09vbx6v0us2#t$t16!*=wv_QM$s*}Yl=buTzjbZmXjHM59% zP|b&zXp8pl(Y~+LW}%dt0lln~eQL;mpuvy|v6}IN?1tE+K=EJ&M0R_M@XNYA+aX26 zbuy(}rDJqG)pr^D`KkCp`dwCm)y$ zEec@~fR-;a%&hHxk)@VrResDNE4DfSyBv3@&xyR!0|Jk`wNtI&L>Z#Jl?=0>)M_fo z84)%GmZ>k9I~G6urqKQPLcj*+oB`Z8Vjwn0W5+|hax?w>RFw~9qV|C!&eYoWmlHT_uzH|KOo-Dbanq@-pod7i+|+D7MW=v%d5 z`Dbja(L3jf*+DMl;}S2^)g4{{*=9gB_JvfB`^7a6_3HCS!mzK-%60_99u^g8#M`$m z?5v=|BfY%VNIn2}0@TgUK8wUN0467;Uvze@arl0H*v%v`vGHHbEv)9jrLo za$$BoO$i!rTfJM^5xq7N1CrHv_*gv=OR_)0v{#2I#6EE;8p=Cgc0HF_aL_@Cn1Rs( z)}Kwb@0F5$BKi5sO!`<%4b(H?@~z{w2QP&Z9Hzt8XHv&iNBnA>drREI35szl8J91= zCnA0OQWC@UVz-c}(&UK%i-CM(BM%2hZx2{Qi3`MLsc;@0j@q47U*vfnnE7lcACst> zG;6pF@mcC!yh{z69oBE#{Ox0KaRP!5^aI;Y9!h0&w0^V6*sGUCOc zCgGxEJWx53fbu^Pnd|Td*PUeFz#ZNE()`B51kZsljoAQK7A^dXn%N&i ztA;xo*$O6Y7h6WbC^{R~SO@Q)`q^)&Mp4$Vo9%nxZN;mapH7#Wj!8cd%N$0?>@gVJ zcUUreuiPyg*}&c3=Q13kXkiB9Pn~_2ek8=}ks!+#B^p8RV_wGGG&jwFj&*k`)@I|h z1eX_+Zs5(YI=b9DT#v6l9zCi|HhUZW6nezhCUSf(DbYf%&5S@c!_`;*gW&dpq-$lv zh-QSB(>8a9QTPO-QJ`b5i3YYUYw^09@fCS`Y1r#^YiEER}W=zW=rw#3=UN0kdZ!-^Ysh};pF_*1XC>PKYW z+r_^k{%U|@E*)!6z=p-JJ&aZ4_ex9l?%1WRmPD-%Ku2(YLIxGjxKa09B6IszU$d-} zk~S5)x|^oeZ>>x3&o9;Q2{w7RHMJpiZFGMnwqPK*Cg}dO3TpFa>!CP`cSo|ac*Kp- zARHn$RS51igwL93LZ`NOwflRMAK{;c8e!@ah{1L}x6aF0q^WkX`~f51IIkI>UOSU~ z>!r2ivbyz}Mx|0WLW_6U3JZ@L@-AvfIn`DKO=M=Bn#@sgrK^@pnU9ukJBVj4@#nV` zDPgsj55UY1X8* z779XkldwNgR=nkFG7Ke9ETq{xY6U-rmHd?Vi5VO09A+)&`F?sant1?mGHk!Zl>t+j zOt(*dkHPM*U9w;XPG|Z#M`bsc%XtlgrE1Yv#_qbKXoFImUs=U)b~{{Zc%eU0;En2f ztN%mBH2|=4 zsoneE2;B3-aG|*2k{Lh)`}D3h^W7r)i~Hf5$l9{*ep)oUl?4u8LCEE58&2ZkaT9i7 zg)*}Rv!Yjxn@bF21!sP~vjaw(3VdE@b@XVOIGll@$DA5rqAgB_Z+t3I1lNX9-cdA- z&JcLuS$7u9?s!)Mi|wce*aragoB{OPk+BY6ldKcct7~5;&KD88ozQaja_odR=k(kj z4IDUtlljNe2!MPSUkCb`2-S1eniS<()9bwWgMwE(B<^Q4J;EAU2PSO~rlp9!Jhw=g zUC!U0#}4~$s8se1hB?jahsR%~S$0nOxaMbov(A40AD<6)m$ue0^~V?;(}ld-|~k`(HcYyY&g(FXUmwjhWciRwjR_qj+oCM$E3fe&{OV8V)F;KozQq_17JpkugS2P^>nzcOMfLa>+v?RFxEn)(K zO{COqFsbO!5;`;`B}I-Lo%97#ifEOlE%M6AB1pPVuoA3cj+4R=q{bdH^<+iCP9nEs z;^CsDtmaaUsnD*lQCE91YPQ!1Xu=5>-$BhlUG0h8#PQR>-B~_rYq*&La;=)yCZHB` z7~4A{^z`(WGHOFg&NbA%`tV1y$_O5*T-e4~iuPV@-0eOb^W(S3r$qt1^y#DUiu0o< zET`+~C$Xa%*gB*%#wpQUHhLj3->p9As&Ji|0hY|dV^PELnx#W9|IKV|*oWnjg2xS_ zv6BH08@94FC)eW(nEz48&#Y&oS8prjXCktR;r4FLXUlm_!Yu=X6(Kb1xfCp_>m4w# zVGFDE^+Prxa{R5zSjF_yi$e3^>ulWz4sMSY;`o9$3wyrRW(&ZN4E84al?;qUcEvu6 z&+eTYVGtKd5hqJnf~^~#_D7L&8m{48Z{;N1TF*K5LFV%MjWgc)|I(3YVeuo)i5TeL zKu4p|#OAkmwtlmNd{25Y6*-IZLPz_=UHNuA+pxo>_=&o=x5%s2 zc}=h-3UPdFeI2SE@udN{Om8Zm5-+kNM7!l8=+N(S8&CaF>;p$63BEs9dMtR-Pqh~^ zQKqgcHxSDnbw{*Gq3BT=8uD}`QwqhOSZP>^UYoD1eANGyN)-ZE#`sjcq;qYefNul; z%ej1^or%NSr18EsiI(&Or5_9Qy3+=W1_Ep1;?&yJtIxATi3qeWg8DKdgM}zYiR^yi z(ZH`YuBUC+Fqa+9W#t;{Q3I^3-}tsWRcRg6PLMkcBslwnC<&E+EnvLU=vKx?^j^-= zviH%`{;qH5)&>(?Oj`@8LH$*5b#(#nMoJjlshd8ngAKTb65>~T$mKmh`$;0IIHT(P zs>PS^Ur%&cUNoHS6)mvZK^Xd0+>-Jm zI5qP&%gny)oMDgMfMry?#0I{v?*zUR_)g$Ef&ae*KEk$8dw%aO<<(w0`Q%%fmS$Gh J@PB$d{VxDniS+;g literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/tw/raid/CHANGWU_RAID_HARD.png b/alas_wrapped/assets/tw/raid/CHANGWU_RAID_HARD.png new file mode 100644 index 0000000000000000000000000000000000000000..bb8b6bbd683101fcda8af589c91a67b82c3280b2 GIT binary patch literal 6447 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 Znndgnz{fzKbN?=Y{dn;l?d+9b{|{n{B60u# literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/tw/raid/CHANGWU_RAID_NORMAL.png b/alas_wrapped/assets/tw/raid/CHANGWU_RAID_NORMAL.png new file mode 100644 index 0000000000000000000000000000000000000000..9d49039789942b2ae4cb12d8e31e2ceeb23fdbed GIT binary patch literal 6708 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 literal 0 HcmV?d00001 diff --git a/alas_wrapped/assets/tw/raid/RPG_BACK.png b/alas_wrapped/assets/tw/raid/RPG_BACK.png new file mode 100644 index 0000000000000000000000000000000000000000..6c02673af7abdfc501bfbfc08ec689fc4b13747d GIT binary patch literal 4092 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 +) else ( + start "Alas" "%_root%\toolkit\webapp\alas.exe" +) diff --git a/alas_wrapped/dev_tools/alas2.bat b/alas_wrapped/dev_tools/alas2.bat new file mode 100644 index 0000000000..fb3524a56f --- /dev/null +++ b/alas_wrapped/dev_tools/alas2.bat @@ -0,0 +1,883 @@ +@echo off +rem @SETLOCAL EnableExtensions EnableDelayedExpansion +pushd "%~dp0" +set ver=2.7 +title Alas Run Tool %ver% +:: ----------------------------------------------------------------------------- +rem :check_Permissions +rem echo Administrative permissions required. Detecting permissions... +rem net session >nul 2>&1 +rem if %errorLevel% == 0 ( +rem echo Success: Administrative permissions confirmed. +rem echo Press any to continue... +rem pause >nul +rem call :continue +rem ) else ( +rem echo Failure: Current permissions inadequate. +rem ) +rem pause >nul +:: ----------------------------------------------------------------------------- +:continue +set ALAS_PATH=%~dp0 +:: ----------------------------------------------------------------------------- +set ADB=%ALAS_PATH%toolkit\Lib\site-packages\adbutils\binaries\adb.exe +set PYTHON=%ALAS_PATH%toolkit\python.exe +set GIT=%ALAS_PATH%toolkit\Git\cmd\git.exe +set LMESZINC=https://github.com/LmeSzinc/AzurLaneAutoScript.git +set WHOAMIKYO=https://github.com/whoamikyo/AzurLaneAutoScript.git +set ALAS_ENV=https://github.com/whoamikyo/alas-env.git +set ALAS_ENV_GITEE=https://gitee.com/lmeszinc/alas-env.git +set GITEE_URL=https://gitee.com/lmeszinc/AzurLaneAutoScript.git +set ADB_P=%ALAS_PATH%config\adb_port.ini +set CURL=%ALAS_PATH%toolkit\Git\mingw64\bin\curl.exe +set API_JSON=%ALAS_PATH%log\api_git.json +set config=%~dp0config\alas.ini +set configtemp=%~dp0config\alastemp.ini +set template=%~dp0config\template.ini +set git_log="%GIT% log --pretty=format:%%H%%n%%aI -1" +:: ----------------------------------------------------------------------------- +:first_run +if exist %~dp0config\alas.ini set first_run=1 +if defined first_run ( + call :is_using_git +) else ( + call :not_using_git +) +:: ----------------------------------------------------------------------------- +set using_git= +if exist ".git\" set using_git=1 +if defined using_git ( + call :is_using_git +) else ( + call :not_using_git +) +:: ----------------------------------------------------------------------------- +:is_using_git +setlocal enabledelayedexpansion +for /f "delims=" %%a in (!config!) do ( + set line=%%a + if "x!line:~0,15!"=="xgithub_token = " ( + set github_token=!line:~15! + + ) +) +:: ----------------------------------------------------------------------------- +:bypass_first_run +rem %CURL% -s https://api.github.com/repos/lmeszinc/AzurLaneAutoScript/git/refs/heads/master?access_token=!github_token! > %~dp0log\api_git.json +%CURL% -s https://api.github.com/repos/lmeszinc/AzurLaneAutoScript/commits/master?access_token=!github_token! > %~dp0log\api_git.json +endlocal +rem for /f "skip=5 tokens=2 delims=:," %%I IN (%API_JSON%) DO IF NOT DEFINED sha SET sha=%%I +rem set sha=%sha:"=% +rem set sha=%sha: =% +for /f "skip=1 tokens=2 delims=:," %%I IN (%API_JSON%) DO IF NOT DEFINED sha SET sha=%%I +set sha=%sha:"=% +set sha=%sha: =% +for /f "skip=14 tokens=3 delims=:" %%I IN (%API_JSON%) DO IF NOT DEFINED message SET message=%%I +set message=%message:"=% +set message=%message:,=% +for /f %%i in ('git rev-parse --abbrev-ref HEAD') do set BRANCH=%%i +for /f "delims=" %%i IN ('%GIT% log -1 "--pretty=%%H"') DO set LAST_LOCAL_GIT=%%i +for /f "tokens=1,2" %%A in ('%GIT% log -1 "--format=%%h %%ct" -- .') do ( + set GIT_SHA1=%%A + call :gmTime GIT_CTIME %%B +) +:: ----------------------------------------------------------------------------- +:time_parsed +if %LAST_LOCAL_GIT% == %sha% ( + echo ---------------------------------------------------------------- + echo Remote Git hash: %sha% + echo Remote Git message: %message% + echo ---------------------------------------------------------------- + echo Local Git hash: %LAST_LOCAL_GIT% + echo Local commit date: %GIT_CTIME% + echo Local Branch: %BRANCH% + echo ---------------------------------------------------------------- + echo your ALAS is updated + echo Press any to continue... + pause > NUL + call :adb_kill +) else ( + echo ---------------------------------------------------------------- + echo Remote Git hash: %sha% + echo Remote Git message: %message% + echo ---------------------------------------------------------------- + echo Local Git hash: %LAST_LOCAL_GIT% + echo Local commit date: %GIT_CTIME% + echo Local Branch: %BRANCH% + echo ---------------------------------------------------------------- + popup.exe + choice /t 10 /c yn /d y /m "There is an update for ALAS. Download now?" + if errorlevel 2 call :adb_kill + if errorlevel 1 call :choose_update_mode +) +:: ----------------------------------------------------------------------------- +:not_using_git +set TOOLKIT_GIT=%~dp0toolkit\.git +if not exist %TOOLKIT_GIT% ( + echo You may need to update your dependencies + echo Press any key to update + pause > NUL + call :toolkit_choose +) else ( + call :adb_kill +) +:: ----------------------------------------------------------------------------- +:adb_kill +cls +call %ADB% kill-server > nul 2>&1 +:: ----------------------------------------------------------------------------- +set SCREENSHOT_FOLDER=%~dp0screenshots +if not exist %SCREENSHOT_FOLDER% ( + mkdir %SCREENSHOT_FOLDER% +) +:: ----------------------------------------------------------------------------- +:: if config\adb_port.ini dont exist, will be created + if not exist %ADB_P% ( + cd . > %ADB_P% + ) +:: ----------------------------------------------------------------------------- +:prompt +REM if adb_port is empty, prompt HOST:PORT +set adb_empty=%~dp0config\adb_port.ini +for %%A in (%adb_empty%) do if %%~zA==0 ( + echo Enter your HOST:PORT eg: 127.0.0.1:5555 for default bluestacks + echo If you misstype, you can edit the file in config/adb_port.ini + set /p adb_input= + ) +:: ----------------------------------------------------------------------------- +REM if adb_input = 0 load from adb_port.ini +:adb_input +if [%adb_input%]==[] ( + call :CHECK_BST_BETA + ) else ( + REM write adb_input on adb_port.ini + echo %adb_input% >> %ADB_P% + call :FINDSTR +) +:: ----------------------------------------------------------------------------- +:: Will search for 127.0.0.1:62001 and replace for %ADB_PORT% +:FINDSTR +REM setlocal enableextensions disabledelayedexpansion +set search=127.0.0.1:62001 +set replace=%adb_input% + +for /f "delims=" %%i in ('type "%template%" ^& break ^> "%template%" ') do ( + set line=%%i + setlocal enabledelayedexpansion + >>"%template%" echo(!line:%search%=%replace%! + endlocal + ) +) +call :CHECK_BST_BETA +:: ----------------------------------------------------------------------------- +:CHECK_BST_BETA +reg query HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv >nul +if %errorlevel% equ 0 ( + echo ------------------------------------------------------------------------------------------ + choice /t 10 /c yn /d n /m "Bluestacks Hyper-V BETA detected, would you like to use realtime_connection mode?" + echo ------------------------------------------------------------------------------------------ + if errorlevel 2 call :load + if errorlevel 1 call :realtime_connection +) else ( + call :load +) +:: ----------------------------------------------------------------------------- +:realtime_connection +ECHO. Connecting with realtime mode ... +for /f "tokens=3" %%a in ('reg query HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests\Android\Config /v BstAdbPort') do (set /a port = %%a) +set SERIAL_REALTIME=127.0.0.1:%port% +echo ---------------------------------------------------------------- +echo connecting at %SERIAL_REALTIME% +call %ADB% connect %SERIAL_REALTIME% +echo ---------------------------------------------------------------- +call :replace_serial +:: ----------------------------------------------------------------------------- +:replace_serial +set config=%~dp0config\alas.ini +setlocal enabledelayedexpansion +for /f "delims=" %%i in (!config!) do ( + set line=%%i + if "x!line:~0,9!"=="xserial = " ( + set serial=!line:~9! + ) +) +set search=%serial% +set replace=%SERIAL_REALTIME% +echo ---------------------------------------------------------------- +echo Old Serial: %serial% +echo New Serial: %SERIAL_REALTIME% +echo ---------------------------------------------------------------- +echo Press any to continue... +pause > NUL +for /f "delims=" %%i in ('type "%config%" ^& break ^> "%config%" ') do ( + set line=%%i + >>"%config%" echo(!line:%search%=%replace%! + ) +) +endlocal +call :init +:: ----------------------------------------------------------------------------- +:: Deprecated +REM set /a search=104 +REM set replace=serial = %SERIAL_REALTIME% +REM (for /f "tokens=1*delims=:" %%a IN ('findstr /n "^" "%config%"') do ( +REM set Line=%%b +REM IF %%a equ %search% set Line=%replace% +REM setlocal enabledelayedexpansion +REM ECHO(!Line! +REM endlocal +REM ))> %~dp0config\alastemp.ini +REM pause +REM del %config% +REM MOVE %configtemp% %config% +REM ) +:: ----------------------------------------------------------------------------- +:load +if defined first_run ( + call :load_alas +) else ( + call :load_input_serial +) +:: ----------------------------------------------------------------------------- +:load_alas +set config=%~dp0config\alas.ini +setlocal enabledelayedexpansion +for /f "delims=" %%i in (!config!) do ( + set line=%%i + if "x!line:~0,9!"=="xserial = " ( + set serial=!line:~9! + ) +) +call :load_alas_serial +:: ----------------------------------------------------------------------------- +:load_input_serial +echo ---------------------------------------------------------------- +echo connecting at %adb_input% +call %ADB% connect %adb_input% +echo ---------------------------------------------------------------- +call :init +:: ----------------------------------------------------------------------------- +:load_alas_serial +echo ---------------------------------------------------------------- +echo connecting at !serial! +call !ADB! connect !serial! +echo ---------------------------------------------------------------- +call :init +:: ----------------------------------------------------------------------------- +endlocal +:: ----------------------------------------------------------------------------- +:: Deprecated +REM Load adb_port.ini +REM +REM set /p ADB_PORT=<%ADB_P% +REM echo connecting at %ADB_PORT% +REM call %ADB% connect %ADB_PORT% +:: ----------------------------------------------------------------------------- +:init +echo ---------------------------------------------------------------- +echo initializing uiautomator2 +call %PYTHON% -m uiautomator2 init +echo ---------------------------------------------------------------- +echo Press any to continue... +pause > NUL +:: uncomment the pause to catch errors +REM pause +call :alas +:: ----------------------------------------------------------------------------- + +:alas + cls + echo. + echo :: Alas run + echo. + echo Choose your option + echo. + echo 1. EN + echo 2. CN + echo 3. JP + echo 4. UPDATER + echo. + echo :: Type a 'number' and press ENTER + echo :: Type 'exit' to quit + echo. + set /P menu= || Set menu=Nothing + if %menu%==1 call :en + if %menu%==2 call :cn + if %menu%==3 call :jp + if %menu%==4 call :choose_update_mode + if %menu%==exit call :EOF + if %menu%==Nothing call :alas + else ( + cls + echo. + echo :: Incorrect Input Entered + echo. + echo Please type a 'number' or 'exit' + echo Press any key to retry to the menu... + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:en + call %PYTHON% --version >nul + if %errorlevel% == 0 ( + echo ---------------------------------------------------------------- + echo Python Found in %PYTHON% Proceeding.. + echo Opening alas_en.pyw in %ALAS_PATH% + call %PYTHON% alas_en.pyw + pause > NUL + call :alas + ) else ( + echo :: it was not possible to open alas_en.pyw, make sure you have a folder toolkit + echo :: inside AzurLaneAutoScript folder. + echo Alas PATH: %ALAS_PATH% + echo Python Path: %PYTHON% + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:cn + call %PYTHON% --version >nul + if %errorlevel% == 0 ( + echo ---------------------------------------------------------------- + echo Python Found in %PYTHON% Proceeding.. + echo Opening alas_en.pyw in %ALAS_PATH% + call %PYTHON% alas_cn.pyw + pause > NUL + call :alas + ) else ( + echo :: it was not possible to open alas_cn.pyw, make sure you have a folder toolkit + echo :: inside AzurLaneAutoScript folder. + echo Alas PATH: %ALAS_PATH% + echo Python Path: %PYTHON% + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:jp + call %PYTHON% --version >nul + if %errorlevel% == 0 ( + echo ---------------------------------------------------------------- + echo Python Found in %PYTHON% Proceeding.. + echo Opening alas_en.pyw in %ALAS_PATH% + call %PYTHON% alas_jp.pyw + pause > NUL + call :alas + ) else ( + echo :: it was not possible to open alas_jp.pyw, make sure you have a folder toolkit + echo :: inside AzurLaneAutoScript folder. + echo Alas PATH: %ALAS_PATH% + echo Python Path: %PYTHON% + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:updater_menu + cls + echo. + echo :: This update only will work if you downloaded ALAS on + echo :: Release tab and installed with Easy_Install-v2.bat + echo. + echo ::Overwrite local changes:: + echo. + echo. + echo 1) https://github.com/LmeSzinc/AzurLaneAutoScript (Main Repo, When in doubt, use it) + echo 2) https://github.com/whoamikyo/AzurLaneAutoScript (Mirrored Fork) + echo 3) https://github.com/whoamikyo/AzurLaneAutoScript (nightly build, dont use) + echo 4) https://gitee.com/lmeszinc/AzurLaneAutoScript.git (Recommended for CN users) + echo 5) https://github.com/LmeSzinc/AzurLaneAutoScript (Dev build, use only if you know what you are doing) + echo 6) Toolkit tools updater + echo 7) Back to main menu + echo. + echo :: Type a 'number' and press ENTER + echo :: Type 'exit' to quit + echo. + set /P choice= + if %choice%==1 call :LmeSzinc + if %choice%==2 call :whoamikyo + if %choice%==3 call :nightly + if %choice%==4 call :gitee + if %choice%==5 call :LmeSzincD + if %choice%==6 call :toolkit_updater + if %choice%==7 call :alas + if %choice%==exit call :EOF + else ( + cls + echo. + echo :: Incorrect Input Entered + echo. + echo Please type a 'number' or 'exit' + echo Press any key to return to the menu... + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:update_menu_local + cls + echo. + echo :: This update only will work if you downloaded ALAS on + echo :: Release tab and installed with Easy_Install-v2.bat + echo. + echo ::Keep local changes:: + echo. + echo. + echo 1) https://github.com/LmeSzinc/AzurLaneAutoScript (Main Repo, When in doubt, use it) + echo 2) https://github.com/whoamikyo/AzurLaneAutoScript (Mirrored Fork) + echo 3) https://github.com/whoamikyo/AzurLaneAutoScript (nightly build, dont use) + echo 4) https://gitee.com/lmeszinc/AzurLaneAutoScript.git (Recommended for CN users) + echo 5) Back to main menu + echo. + echo :: Type a 'number' and press ENTER + echo :: Type 'exit' to quit + echo. + set /P choice= + if %choice%==1 call :LmeSzinc_local + if %choice%==2 call :whoamikyo_local + if %choice%==3 call :nightly_local + if %choice%==4 call :gitee_local + if %choice%==5 call :alas + if %choice%==exit call :EOF + else ( + cls + echo. + echo :: Incorrect Input Entered + echo. + echo Please type a 'number' or 'exit' + echo Press any key to return to the menu... + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:LmeSzinc + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from LmeSzinc repository.. + call %GIT% fetch origin master + call %GIT% reset --hard origin/master + call %GIT% pull --ff-only origin master + echo DONE! + echo Press any key to proceed + pause > NUL + call :updater_menu + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:LmeSzincD + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from LmeSzinc Dev branch.. + call %GIT% fetch origin dev + call %GIT% reset --hard origin/dev + call %GIT% pull --ff-only origin dev + echo DONE! + echo Press any key to proceed + pause > NUL + call :updater_menu + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:whoamikyo + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from whoamikyo repository.. + call %GIT% fetch whoamikyo master + call %GIT% reset --hard whoamikyo/master + call %GIT% pull --ff-only whoamikyo master + echo DONE! + echo Press any key to proceed + pause > NUL + call :updater_menu + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:nightly + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from whoamikyo nightly repository.. + call %GIT% fetch whoamikyo nightly + call %GIT% reset --hard whoamikyo/nightly + call %GIT% pull --ff-only whoamikyo nightly + echo Press any key to proceed + pause > NUL + call :alas + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:gitee + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from LmeSzinc repository.. + call %GIT% fetch lmeszincgitee master + call %GIT% reset --hard lmeszincgitee/master + call %GIT% pull --ff-only lmeszincgitee master + echo DONE! + echo Press any key to proceed + pause > NUL + call :updater_menu + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +rem :check_connection +rem cls +rem echo. +rem echo :: Checking For Internet Connection to Github... +rem echo. +rem timeout /t 2 /nobreak > NUL + +rem ping -n 1 google.com -w 20000 >nul + +rem if %errorlevel% == 0 ( +rem echo You have a good connection with Github! Proceeding... +rem echo press any to proceed +rem pause > NUL +rem call updater_menu +rem ) else ( +rem echo :: You don't have a good connection out of China +rem echo :: It might be better to update using Gitee +rem echo :: Redirecting... +rem echo. +rem echo Press any key to continue... +rem pause > NUL +rem call start_gitee +rem ) +:: ----------------------------------------------------------------------------- +rem Keep local changes +:: ----------------------------------------------------------------------------- +:choose_update_mode + cls + echo. + echo. + echo ::Choose update method:: + echo. + echo 1) Overwrite local changes (Will undo any local changes) + echo 2) Keep local changes (Useful if you have customized a map) + echo 3) Back to main menu + echo. + echo :: Type a 'number' and press ENTER + echo :: Type 'exit' to quit + echo. + set /P choice= + if %choice%==1 call :updater_menu + if %choice%==2 call :update_menu_local + if %choice%==3 call :alas + if %choice%==exit call EOF + else ( + cls + echo. + echo :: Incorrect Input Entered + echo. + echo Please type a 'number' or 'exit' + echo Press any key to return to the menu... + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:LmeSzinc_local + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from LmeSzinc repository.. + call %GIT% stash + call %GIT% pull origin master + call %GIT% stash pop + echo DONE! + echo Press any key to proceed + pause > NUL + call :update_menu_local + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:whoamikyo_local + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from whoamikyo repository.. + call %GIT% stash + call %GIT% pull whoamikyo master + call %GIT% stash pop + echo DONE! + echo Press any key to proceed + pause > NUL + call :update_menu_local + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:nightly_local + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from whoamikyo nightly repository.. + call %GIT% stash + call %GIT% pull whoamikyo nightly + call %GIT% stash pop + echo Press any key to proceed + pause > NUL + call :update_menu_local + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:gitee_local + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating from LmeSzinc repository.. + call %GIT% stash + call %GIT% pull lmeszincgitee master + call %GIT% stash pop + echo DONE! + echo Press any key to proceed + pause > NUL + call :update_menu_local + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:toolkit_choose + cls + echo. + echo :: This will add the toolkit repository for updating + echo. + echo ::Toolkit:: + echo. + echo. + echo 1) https://github.com/whoamikyo/alas-env.git (Default Github) + echo 2) https://gitee.com/lmeszinc/alas-env.git (Recommended for CN users) + echo 3) Back to main menu + echo. + echo :: Type a 'number' and press ENTER + echo :: Type 'exit' to quit + echo. + set /P choice= + if %choice%==1 call :toolkit_github + if %choice%==2 call :toolkit_gitee + if %choice%==3 call :alas + if %choice%==exit call :EOF + else ( + cls + echo. + echo :: Incorrect Input Entered + echo. + echo Please type a 'number' or 'exit' + echo Press any key to return to the menu... + echo. + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:toolkit_github + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating toolkit.. + call cd toolkit + echo ## initializing toolkit.. + call %GIT% init + call %GIT% config --global core.autocrlf false + echo ## Adding files + echo ## This process may take a while + call %GIT% add -A + echo ## adding origin.. + call %GIT% remote add origin %ALAS_ENV% + echo Fething... + call %GIT% fetch origin master + call %GIT% reset --hard origin/master + echo Pulling... + call %GIT% pull --ff-only origin master + call cd .. + echo DONE! + echo Press any key to proceed + pause > NUL + call :adb_kill + ) else ( + echo :: Git not found, maybe there was an installation issue + echo :: check if you have this directory %GIT% + pause > NUL + call :adb_kill + ) +:: ----------------------------------------------------------------------------- +:toolkit_gitee + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating toolkit.. + call cd toolkit + echo ## initializing toolkit.. + call %GIT% init + call %GIT% config --global core.autocrlf false + echo ## Adding files + echo ## This process may take a while + call %GIT% add -A + echo ## adding origin.. + call %GIT% remote add origin %ALAS_ENV_GITEE% + echo Fething... + call %GIT% fetch origin master + call %GIT% reset --hard origin/master + echo Pulling... + call %GIT% pull --ff-only origin master + call cd .. + echo DONE! + echo Press any key to proceed + pause > NUL + call :adb_kill + ) else ( + echo :: Git not found, maybe there was an installation issue + echo :: check if you have this directory %GIT% + pause > NUL + call :adb_kill + ) +:: ----------------------------------------------------------------------------- +:toolkit_updater + call %GIT% --version >nul + if %errorlevel% == 0 ( + echo GIT Found in %GIT% Proceeding + echo Updating toolkit.. + call cd toolkit + call %GIT% fetch origin master + call %GIT% reset --hard origin/master + echo Pulling... + call %GIT% pull --ff-only origin master + echo DONE! + call cd .. + echo Press any key to proceed + pause > NUL + call :updater_menu + ) else ( + echo :: Git not detected, maybe there was an installation issue + echo check if you have this directory: + echo AzurLaneAutoScript\toolkit\Git\cmd + pause > NUL + call :alas + ) +:: ----------------------------------------------------------------------------- +:gmtime +setlocal +set /a z=%2/86400+719468,d=z%%146097,y=^(d-d/1460+d/36525-d/146096^)/365,d-=365*y+y/4-y/100,m=^(5*d+2^)/153 +set /a d-=^(153*m+2^)/5-1,y+=z/146097*400+m/11,m=^(m+2^)%%12+1 +set /a h=%2/3600%%24,mi=%2%%3600/60,s=%2%%60 +if %m% lss 10 set m=0%m% +if %d% lss 10 set d=0%d% +if %h% lss 10 set h=0%h% +if %mi% lss 10 set mi=0%mi% +if %s% lss 10 set s=0%s% +endlocal & set %1=%y%-%m%-%d% %h%:%mi%:%s% +call :time_parsed +:: ----------------------------------------------------------------------------- +rem :git_update_checker +rem %CURL% -s https://api.github.com/repos/lmeszinc/AzurLaneAutoScript/git/refs/heads/master?access_token=%github_token% > %~dp0log\API_GIT.json +rem FOR /f "skip=5 tokens=2 delims=:," %%I IN (%API_JSON%) DO IF NOT DEFINED sha SET sha=%%I +rem set sha=%sha:"=% +rem set sha=%sha: =% +rem FOR /F "delims=" %%i IN ('%GIT% log -1 "--pretty=%%H"') DO set LAST_LOCAL_GIT=%%i +:: ----------------------------------------------------------------------------- +:: ----------------------------------------------------------------------------- +rem if %LAST_LOCAL_GIT% equ %sha% SET run_update=1 +rem call :alas + +:: ----------------------------------------------------------------------------- +::Add paths +rem call :AddPath %ALAS_PATH% +rem call :AddPath %ADB% +rem call :AddPath %PYTHON% +rem call :AddPath %GIT% + +rem :UpdateEnv +rem ECHO Making updated PATH go live . . . +rem REG delete HKCU\Environment /F /V TEMPVAR > nul 2>&1 +rem setx TEMPVAR 1 > nul 2>&1 +rem REG delete HKCU\Environment /F /V TEMPVAR > nul 2>&1 +:: ----------------------------------------------------------------------------- +rem :AddPath +rem ECHO %PATH% | FINDSTR /C:"%~1" > nul +rem IF ERRORLEVEL 1 ( +rem REG add "HKLM\SYSTEM\CurrentControlset\Control\Session Manager\Environment" /f /v PATH /t REG_SZ /d "%PATH%;%~1" >> add-paths-detail.log +rem IF ERRORLEVEL 0 ( +rem ECHO Adding %1 . . . Success! >> add-paths.log +rem set "PATH=%PATH%;%~1" +rem rem set UPDATE=1 +rem ) ELSE ( +rem ECHO Adding %1 . . . FAILED. Run this script with administrator privileges. >> add-paths.log +rem ) +rem ) ELSE ( +rem ECHO Skipping %1 - Already in PATH >> add-paths.log +rem ) +:: ----------------------------------------------------------------------------- +rem :AddPath +rem ECHO %PATH% | FINDSTR /C:"%~1" > nul +rem IF ERRORLEVEL 1 ( +rem REG add "HKLM\SYSTEM\CurrentControlset\Control\Session Manager\Environment" /f /v PATH /t REG_SZ /d "%PATH%;%~1" > nul 2>&1 +rem IF ERRORLEVEL 0 ( +rem ECHO Adding %1 . . . Success! +rem set "PATH=%PATH%;%~1" +rem set UPDATE=1 +rem ) ELSE ( +rem ECHO Adding %1 . . . FAILED. Run this script with administrator privileges. +rem ) +rem ) ELSE ( +rem ECHO Skipping %1 - Already in PATH +rem ) +:: ----------------------------------------------------------------------------- +:EOF +echo Exiting +pause +exit diff --git a/alas_wrapped/module/base/template.py b/alas_wrapped/module/base/template.py index 53481f30a2..6a16533255 100644 --- a/alas_wrapped/module/base/template.py +++ b/alas_wrapped/module/base/template.py @@ -7,7 +7,6 @@ from module.base.resource import Resource from module.base.utils import * from module.config.server import VALID_SERVER -from module.logger import logger from module.map_detection.utils import Points @@ -115,61 +114,6 @@ def size(self): else: return self.image.shape[0:2][::-1] - def _safe_match_template(self, image, template): - """ - Args: - image (np.ndarray): Image to search in. - template (np.ndarray): Template to search for. - - Returns: - np.ndarray: Result of cv2.matchTemplate - """ - # 2026-01-25 Fix: Added safety check for channel mismatch (Gray vs RGB) to prevent crashes. - image_channels = 1 if len(image.shape) == 2 else 3 - template_channels = 1 if len(template.shape) == 2 else 3 - - coerced = False - if image_channels != template_channels: - coerced = True - if image_channels == 1: - # Image is Gray, Template is RGB -> Convert Template to Gray - template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY) - else: - # Image is RGB, Template is Gray -> Convert Image to Gray - image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - - res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) - - if coerced: - _, sim, _, point = cv2.minMaxLoc(res) - # 2026-02-04: Debounce log spam and save debug info for near-misses - if not hasattr(self, '_mismatch_count'): - self._mismatch_count = 0 - self._mismatch_count += 1 - - # Log every 100th occurrence or if similarity is very high - should_log = (self._mismatch_count % 100 == 1) or (sim > 0.8) - - if should_log: - logger.warning(f'Channel mismatch fixed in {self.name}. Sim: {sim:.3f} (Count: {self._mismatch_count})') - - # Targeted Akashi Debugging: Capture near-misses for inspection - if 0.70 < sim < 0.85 and 'AKASHI' in self.name: - from datetime import datetime - now = datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f') - debug_path = f'./log/debug_mismatch_{self.name}_{now}.png' - try: - h, w = template.shape[:2] - x, y = point - crop = image[y:y+h, x:x+w] - cv2.imwrite(debug_path, crop) - if should_log: - logger.info(f'Saved debug mismatch image to {debug_path}') - except Exception: - pass - - return res - def match(self, image, scaling=1.0, similarity=0.85): """ Args: @@ -180,18 +124,13 @@ def match(self, image, scaling=1.0, similarity=0.85): Returns: bool: If matches. """ - # Targeted Threshold for Akashi: Override similarity if we are looking for the merchant - if 'AKASHI' in self.name: - similarity = 0.75 - scaling = 1 / scaling if scaling != 1.0: image = cv2.resize(image, None, fx=scaling, fy=scaling) if self.is_gif: for template in self.image: - # 2026-01-25 Fix: Use _safe_match_template to handle channel mismatch - res = self._safe_match_template(image, template) + res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) _, sim, _, _ = cv2.minMaxLoc(res) # print(self.file, sim) if sim > similarity: @@ -200,8 +139,7 @@ def match(self, image, scaling=1.0, similarity=0.85): return False else: - # 2026-01-25 Fix: Use _safe_match_template to handle channel mismatch - res = self._safe_match_template(image, self.image) + res = cv2.matchTemplate(image, self.image, cv2.TM_CCOEFF_NORMED) _, sim, _, _ = cv2.minMaxLoc(res) # print(self.file, sim) return sim > similarity @@ -245,8 +183,7 @@ def match_binary(self, image, similarity=0.85): def match_luma(self, image, similarity=0.85): if self.is_gif: - if len(image.shape) == 3: - image = rgb2luma(image) + image = rgb2luma(image) for template in self.image_luma: res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) _, sim, _, _ = cv2.minMaxLoc(res) @@ -257,9 +194,7 @@ def match_luma(self, image, similarity=0.85): return False else: - if len(image.shape) == 3: - image = rgb2luma(image) - res = cv2.matchTemplate(image, self.image_luma, cv2.TM_CCOEFF_NORMED) + res = cv2.matchTemplate(image, self.image, cv2.TM_CCOEFF_NORMED) _, sim, _, _ = cv2.minMaxLoc(res) # print(self.file, sim) return sim > similarity @@ -292,8 +227,7 @@ def match_result(self, image, name=None): float: Similarity Button: """ - # 2026-01-25 Fix: Use _safe_match_template to handle channel mismatch - res = self._safe_match_template(image, self.image) + res = cv2.matchTemplate(image, self.image, cv2.TM_CCOEFF_NORMED) _, sim, _, point = cv2.minMaxLoc(res) # print(self.file, sim) @@ -329,15 +263,13 @@ def match_multi(self, image, scaling=1.0, similarity=0.85, threshold=3, name=Non if self.is_gif: result = [] for template in self.image: - # 2026-01-25 Fix: Use _safe_match_template to handle channel mismatch - res = self._safe_match_template(image, template) + res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) res = np.array(np.where(res > similarity)).T[:, ::-1].tolist() result += res result = np.array(result) else: - # 2026-01-25 Fix: Use _safe_match_template to handle channel mismatch - res = self._safe_match_template(image, self.image) - result = np.array(np.where(res > similarity)).T[:, ::-1] + result = cv2.matchTemplate(image, self.image, cv2.TM_CCOEFF_NORMED) + result = np.array(np.where(result > similarity)).T[:, ::-1] # result: np.array([[x0, y0], [x1, y1], ...) if scaling != 1.0: diff --git a/alas_wrapped/module/campaign/assets.py b/alas_wrapped/module/campaign/assets.py index 064c0ef030..8eb11c412d 100644 --- a/alas_wrapped/module/campaign/assets.py +++ b/alas_wrapped/module/campaign/assets.py @@ -16,11 +16,8 @@ EVENT_20230817_STORY = Button(area={'cn': (610, 320, 670, 380), 'en': (610, 320, 670, 380), 'jp': (610, 320, 670, 380), 'tw': (610, 320, 670, 380)}, color={'cn': (183, 180, 190), 'en': (183, 180, 190), 'jp': (183, 180, 190), 'tw': (183, 180, 190)}, button={'cn': (610, 320, 670, 380), 'en': (610, 320, 670, 380), 'jp': (610, 320, 670, 380), 'tw': (610, 320, 670, 380)}, file={'cn': './assets/cn/campaign/EVENT_20230817_STORY.png', 'en': './assets/en/campaign/EVENT_20230817_STORY.png', 'jp': './assets/jp/campaign/EVENT_20230817_STORY.png', 'tw': './assets/tw/campaign/EVENT_20230817_STORY.png'}) EVENT_20250724_PT_ICON = Button(area={'cn': (1102, 106, 1139, 121), 'en': (1067, 108, 1104, 123), 'jp': (1102, 106, 1139, 121), 'tw': (1102, 106, 1139, 121)}, color={'cn': (95, 103, 93), 'en': (97, 101, 94), 'jp': (95, 103, 93), 'tw': (95, 103, 93)}, button={'cn': (1102, 106, 1139, 121), 'en': (1067, 108, 1104, 123), 'jp': (1102, 106, 1139, 121), 'tw': (1102, 106, 1139, 121)}, file={'cn': './assets/cn/campaign/EVENT_20250724_PT_ICON.png', 'en': './assets/en/campaign/EVENT_20250724_PT_ICON.png', 'jp': './assets/cn/campaign/EVENT_20250724_PT_ICON.png', 'tw': './assets/cn/campaign/EVENT_20250724_PT_ICON.png'}) OCR_COIN = Button(area={'cn': (815, 23, 922, 51), 'en': (815, 23, 922, 51), 'jp': (815, 23, 922, 51), 'tw': (815, 23, 922, 51)}, color={'cn': (61, 61, 73), 'en': (61, 61, 73), 'jp': (61, 61, 73), 'tw': (61, 61, 73)}, button={'cn': (815, 23, 922, 51), 'en': (815, 23, 922, 51), 'jp': (815, 23, 922, 51), 'tw': (815, 23, 922, 51)}, file={'cn': './assets/cn/campaign/OCR_COIN.png', 'en': './assets/en/campaign/OCR_COIN.png', 'jp': './assets/jp/campaign/OCR_COIN.png', 'tw': './assets/tw/campaign/OCR_COIN.png'}) -OCR_COIN_LIMIT = Button(area={'cn': (807, 0, 944, 19), 'en': (807, 0, 944, 19), 'jp': (807, 0, 944, 19), 'tw': (807, 0, 944, 19)}, color={'cn': (206, 206, 206), 'en': (206, 206, 206), 'jp': (206, 206, 206), 'tw': (206, 206, 206)}, button={'cn': (807, 0, 944, 19), 'en': (807, 0, 944, 19), 'jp': (807, 0, 944, 19), 'tw': (807, 0, 944, 19)}, file={'cn': './assets/cn/campaign/OCR_COIN_LIMIT.png', 'en': './assets/en/campaign/OCR_COIN_LIMIT.png', 'jp': './assets/jp/campaign/OCR_COIN_LIMIT.png', 'tw': './assets/tw/campaign/OCR_COIN_LIMIT.png'}) OCR_EVENT_PT = Button(area={'cn': (1196, 109, 1280, 131), 'en': (1190, 109, 1280, 129), 'jp': (1196, 109, 1280, 131), 'tw': (1196, 109, 1280, 131)}, color={'cn': (121, 110, 59), 'en': (88, 78, 51), 'jp': (121, 110, 59), 'tw': (121, 110, 59)}, button={'cn': (1196, 109, 1280, 131), 'en': (1190, 109, 1280, 129), 'jp': (1196, 109, 1280, 131), 'tw': (1196, 109, 1280, 131)}, file={'cn': './assets/cn/campaign/OCR_EVENT_PT.png', 'en': './assets/en/campaign/OCR_EVENT_PT.png', 'jp': './assets/jp/campaign/OCR_EVENT_PT.png', 'tw': './assets/tw/campaign/OCR_EVENT_PT.png'}) -OCR_GEM = Button(area={'cn': (1024, 23, 1137, 51), 'en': (1024, 23, 1137, 51), 'jp': (1024, 23, 1137, 51), 'tw': (1024, 23, 1137, 51)}, color={'cn': (102, 102, 100), 'en': (102, 102, 100), 'jp': (102, 102, 100), 'tw': (102, 102, 100)}, button={'cn': (1024, 23, 1137, 51), 'en': (1024, 23, 1137, 51), 'jp': (1024, 23, 1137, 51), 'tw': (1024, 23, 1137, 51)}, file={'cn': './assets/cn/campaign/OCR_GEM.png', 'en': './assets/cn/campaign/OCR_GEM.png', 'jp': './assets/cn/campaign/OCR_GEM.png', 'tw': './assets/cn/campaign/OCR_GEM.png'}) OCR_OIL = Button(area={'cn': (614, 23, 714, 51), 'en': (614, 23, 714, 51), 'jp': (614, 23, 714, 51), 'tw': (614, 23, 714, 51)}, color={'cn': (64, 65, 79), 'en': (64, 65, 79), 'jp': (64, 65, 79), 'tw': (64, 65, 79)}, button={'cn': (614, 23, 714, 51), 'en': (614, 23, 714, 51), 'jp': (614, 23, 714, 51), 'tw': (614, 23, 714, 51)}, file={'cn': './assets/cn/campaign/OCR_OIL.png', 'en': './assets/en/campaign/OCR_OIL.png', 'jp': './assets/jp/campaign/OCR_OIL.png', 'tw': './assets/tw/campaign/OCR_OIL.png'}) -OCR_OIL_LIMIT = Button(area={'cn': (608, 0, 736, 19), 'en': (608, 0, 736, 19), 'jp': (608, 0, 736, 19), 'tw': (608, 0, 736, 19)}, color={'cn': (202, 202, 202), 'en': (202, 202, 202), 'jp': (202, 202, 202), 'tw': (202, 202, 202)}, button={'cn': (608, 0, 736, 19), 'en': (608, 0, 736, 19), 'jp': (608, 0, 736, 19), 'tw': (608, 0, 736, 19)}, file={'cn': './assets/cn/campaign/OCR_OIL_LIMIT.png', 'en': './assets/en/campaign/OCR_OIL_LIMIT.png', 'jp': './assets/jp/campaign/OCR_OIL_LIMIT.png', 'tw': './assets/tw/campaign/OCR_OIL_LIMIT.png'}) OCR_OIL_CHECK = Button(area={'cn': (573, 30, 592, 49), 'en': (573, 30, 592, 49), 'jp': (573, 30, 592, 49), 'tw': (573, 30, 592, 49)}, color={'cn': (82, 82, 82), 'en': (82, 82, 82), 'jp': (82, 82, 82), 'tw': (82, 82, 82)}, button={'cn': (573, 30, 592, 49), 'en': (573, 30, 592, 49), 'jp': (573, 30, 592, 49), 'tw': (573, 30, 592, 49)}, file={'cn': './assets/cn/campaign/OCR_OIL_CHECK.png', 'en': './assets/en/campaign/OCR_OIL_CHECK.png', 'jp': './assets/jp/campaign/OCR_OIL_CHECK.png', 'tw': './assets/tw/campaign/OCR_OIL_CHECK.png'}) SWITCH_1_HARD = Button(area={'cn': (82, 641, 148, 675), 'en': (87, 642, 148, 676), 'jp': (24, 645, 150, 697), 'tw': (82, 641, 148, 675)}, color={'cn': (233, 141, 128), 'en': (234, 139, 124), 'jp': (219, 116, 106), 'tw': (236, 159, 148)}, button={'cn': (82, 641, 148, 675), 'en': (87, 642, 148, 676), 'jp': (24, 645, 150, 697), 'tw': (82, 641, 148, 675)}, file={'cn': './assets/cn/campaign/SWITCH_1_HARD.png', 'en': './assets/en/campaign/SWITCH_1_HARD.png', 'jp': './assets/jp/campaign/SWITCH_1_HARD.png', 'tw': './assets/tw/campaign/SWITCH_1_HARD.png'}) SWITCH_1_NORMAL = Button(area={'cn': (80, 641, 148, 675), 'en': (79, 638, 147, 675), 'jp': (24, 644, 150, 697), 'tw': (79, 641, 148, 675)}, color={'cn': (157, 180, 227), 'en': (157, 180, 227), 'jp': (143, 169, 222), 'tw': (156, 179, 227)}, button={'cn': (80, 641, 148, 675), 'en': (79, 638, 147, 675), 'jp': (24, 644, 150, 697), 'tw': (79, 641, 148, 675)}, file={'cn': './assets/cn/campaign/SWITCH_1_NORMAL.png', 'en': './assets/en/campaign/SWITCH_1_NORMAL.png', 'jp': './assets/jp/campaign/SWITCH_1_NORMAL.png', 'tw': './assets/tw/campaign/SWITCH_1_NORMAL.png'}) diff --git a/alas_wrapped/module/campaign/campaign_event.py b/alas_wrapped/module/campaign/campaign_event.py index ba7bd4d91a..541cb00a55 100644 --- a/alas_wrapped/module/campaign/campaign_event.py +++ b/alas_wrapped/module/campaign/campaign_event.py @@ -62,10 +62,8 @@ def event_pt_limit_triggered(self): tasks = EVENTS + RAIDS + COALITIONS + GEMS_FARMINGS + HOSPITAL command = self.config.Scheduler_Command if limit <= 0 or command not in tasks: - self.get_event_pt() return False if command in GEMS_FARMINGS and self.stage_is_main(self.config.Campaign_Name): - self.get_event_pt() return False pt = self.get_event_pt() @@ -109,17 +107,8 @@ def triggered_task_balancer(self): Pages: in: page_event or page_sp """ - from module.config.deep import deep_get limit = self.config.TaskBalancer_CoinLimit - coin = deep_get(self.config.data, 'Dashboard.Coin.Value') - logger.attr('Coin Count', coin) - tasks = [ - 'Event', - 'Event2', - 'Raid', - 'GemsFarming', - ] - command = self.config.Scheduler_Command + coin = self.get_coin() # Check Coin if coin == 0: # Avoid wrong/zero OCR result @@ -136,12 +125,11 @@ def triggered_task_balancer(self): return False def handle_task_balancer(self): - if self.config.TaskBalancer_Enable and self.triggered_task_balancer(): - self.config.task_delay(minute=5) - next_task = self.config.TaskBalancer_TaskCall - logger.hr(f'TaskBalancer triggered, switching task to {next_task}') - self.config.task_call(next_task) - self.config.task_stop() + self.config.task_delay(minute=5) + next_task = self.config.TaskBalancer_TaskCall + logger.hr(f'TaskBalancer triggered, switching task to {next_task}') + self.config.task_call(next_task) + self.config.task_stop() def is_event_entrance_available(self): """ diff --git a/alas_wrapped/module/campaign/campaign_ocr.py b/alas_wrapped/module/campaign/campaign_ocr.py index 9dc04193e5..f39a29ae80 100644 --- a/alas_wrapped/module/campaign/campaign_ocr.py +++ b/alas_wrapped/module/campaign/campaign_ocr.py @@ -41,11 +41,21 @@ def _campaign_get_chapter_index(name): @staticmethod def _campaign_ocr_result_process(result): # The result will be like '7--2', because tha dash in game is '–' not '-' - result = result.lower().replace('--', '-').replace('--', '-') - if result.startswith('-'): - result = result[1:] + result = result.replace('--', '-').replace('--', '-').lstrip('-') + + # Replace wrong 'I' from results like 'I1-1', '1I-1', 'I-I', '11-I', 'I4-4', to '1' + # while keeping results like 'isp-2', 'sp1' + def replace_func(match): + segment = match.group(0) + return segment.replace('I', '1') + + result = re.sub(r'[0-9I]+-[0-9I]+', replace_func, result, count=1) + + # Convert '72' to '7-2' if len(result) == 2 and result[0].isdigit(): result = '-'.join(result) + + result = result.lower() return result @staticmethod diff --git a/alas_wrapped/module/campaign/campaign_status.py b/alas_wrapped/module/campaign/campaign_status.py index ac4a494822..45e8179fc1 100644 --- a/alas_wrapped/module/campaign/campaign_status.py +++ b/alas_wrapped/module/campaign/campaign_status.py @@ -1,4 +1,3 @@ -import datetime import re import cv2 @@ -7,17 +6,16 @@ import module.config.server as server from module.base.timer import Timer -from module.campaign.assets import OCR_EVENT_PT, OCR_COIN, OCR_OIL, OCR_COIN_LIMIT, OCR_OIL_LIMIT, OCR_OIL_CHECK from module.base.utils import color_similar, get_color +from module.campaign.assets import OCR_COIN, OCR_EVENT_PT, OCR_OIL, OCR_OIL_CHECK from module.logger import logger from module.ocr.ocr import Digit, Ocr from module.ui.ui import UI -from module.log_res.log_res import LogRes -#if server.server != 'jp': -# OCR_COIN = Digit(OCR_COIN, name='OCR_COIN', letter=(239, 239, 239), threshold=128) -#else: -# OCR_COIN = Digit(OCR_COIN, name='OCR_COIN', letter=(201, 201, 201), threshold=128) +if server.server != 'jp': + OCR_COIN = Digit(OCR_COIN, name='OCR_COIN', letter=(239, 239, 239), threshold=128) +else: + OCR_COIN = Digit(OCR_COIN, name='OCR_COIN', letter=(201, 201, 201), threshold=128) class PtOcr(Ocr): @@ -45,7 +43,7 @@ def pre_process(self, image): class CampaignStatus(UI): - def get_event_pt(self, update=False): + def get_event_pt(self): """ Returns: int: PT amount, or 0 if unable to parse @@ -56,20 +54,17 @@ def get_event_pt(self, update=False): if res: pt = int(res.group(1)) logger.attr('Event_PT', pt) - LogRes(self.config).Pt = pt + return pt else: logger.warning(f'Invalid pt result: {pt}') - pt = 0 - if update: - self.config.update() - return pt + return 0 - def get_coin(self, skip_first_screenshot=True, update=False): + def get_coin(self, skip_first_screenshot=True): """ Returns: int: Coin amount """ - _coin = {} + amount = 0 timeout = Timer(1, count=2).start() while 1: if skip_first_screenshot: @@ -81,17 +76,11 @@ def get_coin(self, skip_first_screenshot=True, update=False): logger.warning('Get coin timeout') break - _coin = { - 'Value': self._get_num(OCR_COIN, 'OCR_COIN'), - 'Limit': self._get_num(OCR_COIN_LIMIT, 'OCR_COIN_LIMIT') - } - if _coin['Value'] >= 100: + amount = OCR_COIN.ocr(self.device.image) + if amount >= 100: break - LogRes(self.config).Coin = _coin - if update: - self.config.update() - return _coin['Value'] + return amount def _get_oil(self): # Update offset @@ -113,32 +102,12 @@ def _get_oil(self): return ocr.ocr(self.device.image) - def _get_num(self, _button, name): - # Update offset - _ = self.appear(OCR_OIL_CHECK) - - color = get_color(self.device.image, OCR_OIL_CHECK.button) - if color_similar(color, OCR_OIL_CHECK.color): - # Original color - if server.server != 'jp': - ocr = Digit(_button, name=name, letter=(247, 247, 247), threshold=128) - else: - ocr = Digit(_button, name=name, letter=(201, 201, 201), threshold=128) - elif color_similar(color, (59, 59, 64)): - # With black overlay - ocr = Digit(_button, name=name, letter=(165, 165, 165), threshold=128) - else: - logger.warning(f'Unexpected OCR_OIL_CHECK color') - ocr = Digit(_button, name=name, letter=(247, 247, 247), threshold=128) - - return ocr.ocr(self.device.image) - - def get_oil(self, skip_first_screenshot=True, update=False): + def get_oil(self, skip_first_screenshot=True): """ Returns: int: Oil amount """ - _oil = {} + amount = 0 timeout = Timer(1, count=2).start() while 1: if skip_first_screenshot: @@ -146,25 +115,19 @@ def get_oil(self, skip_first_screenshot=True, update=False): else: self.device.screenshot() - if not self.appear(OCR_OIL_CHECK, offset=(10, 2)): - logger.info('No oil icon') - self.device.sleep(1) - if timeout.reached(): logger.warning('Get oil timeout') break - _oil = { - 'Value': self._get_num(OCR_OIL, 'OCR_OIL'), - 'Limit': self._get_num(OCR_OIL_LIMIT, 'OCR_OIL_LIMIT') - } - if _oil['Value'] >= 100: + if not self.appear(OCR_OIL_CHECK, offset=(10, 2)): + logger.info('No oil icon') + continue + + amount = self._get_oil() + if amount >= 100: break - LogRes(self.config).Oil = _oil - if update: - self.config.update() - return _oil['Value'] + return amount def is_balancer_task(self): """ diff --git a/alas_wrapped/module/campaign/run.py b/alas_wrapped/module/campaign/run.py index 01a2f1bb26..ad3251c544 100644 --- a/alas_wrapped/module/campaign/run.py +++ b/alas_wrapped/module/campaign/run.py @@ -5,7 +5,6 @@ from module.campaign.campaign_base import CampaignBase from module.campaign.campaign_event import CampaignEvent -from module.shop.shop_status import ShopStatus from module.campaign.campaign_ui import MODE_SWITCH_1 from module.config.config import AzurLaneConfig from module.exception import CampaignEnd, RequestHumanTakeover, ScriptEnd @@ -15,7 +14,7 @@ from module.ui.page import page_campaign -class CampaignRun(CampaignEvent, ShopStatus): +class CampaignRun(CampaignEvent): folder: str name: str stage: str @@ -95,10 +94,7 @@ def triggered_stop_condition(self, oil_check=True): return True # Oil limit if oil_check: - self.status_get_gems() - self.get_coin() - _oil = self.get_oil() - if _oil < max(500, self.config.StopCondition_OilLimit): + if self.get_oil() < max(500, self.config.StopCondition_OilLimit): logger.hr('Triggered stop condition: Oil limit') self.config.task_delay(minute=(120, 240)) return True @@ -423,11 +419,6 @@ def run(self, name, folder='campaign_main', mode='normal', total=0): if self.triggered_stop_condition(oil_check=not self.campaign.is_in_auto_search_menu()): break - # Update config - if len(self.config.modified): - logger.info('Updating config for dashboard') - self.config.update() - # Run self.device.stuck_record_clear() self.device.click_record_clear() @@ -438,10 +429,6 @@ def run(self, name, folder='campaign_main', mode='normal', total=0): logger.info(str(e)) break - # Update config - if len(self.campaign.config.modified): - logger.info('Updating config for dashboard') - self.campaign.config.update() # After run self.run_count += 1 if self.config.StopCondition_RunCount: diff --git a/alas_wrapped/module/coalition/assets.py b/alas_wrapped/module/coalition/assets.py index a869c36968..890b255256 100644 --- a/alas_wrapped/module/coalition/assets.py +++ b/alas_wrapped/module/coalition/assets.py @@ -36,17 +36,17 @@ DAL_SWITCH_SINGLE = Button(area={'cn': (910, 473, 1055, 500), 'en': (915, 475, 1051, 499), 'jp': (917, 474, 1035, 499), 'tw': (945, 478, 1013, 495)}, color={'cn': (223, 223, 223), 'en': (193, 193, 193), 'jp': (215, 215, 215), 'tw': (168, 168, 168)}, button={'cn': (910, 473, 1055, 500), 'en': (915, 475, 1051, 499), 'jp': (917, 474, 1035, 499), 'tw': (945, 478, 1013, 495)}, file={'cn': './assets/cn/coalition/DAL_SWITCH_SINGLE.png', 'en': './assets/en/coalition/DAL_SWITCH_SINGLE.png', 'jp': './assets/jp/coalition/DAL_SWITCH_SINGLE.png', 'tw': './assets/tw/coalition/DAL_SWITCH_SINGLE.png'}) EMPTY_FLAGSHIP = Button(area={'cn': (247, 237, 277, 267), 'en': (247, 237, 277, 267), 'jp': (247, 237, 277, 267), 'tw': (247, 237, 277, 267)}, color={'cn': (76, 64, 56), 'en': (76, 64, 56), 'jp': (76, 64, 56), 'tw': (76, 64, 56)}, button={'cn': (247, 237, 277, 267), 'en': (247, 237, 277, 267), 'jp': (247, 237, 277, 267), 'tw': (247, 237, 277, 267)}, file={'cn': './assets/cn/coalition/EMPTY_FLAGSHIP.png', 'en': './assets/cn/coalition/EMPTY_FLAGSHIP.png', 'jp': './assets/cn/coalition/EMPTY_FLAGSHIP.png', 'tw': './assets/cn/coalition/EMPTY_FLAGSHIP.png'}) EMPTY_VANGUARD = Button(area={'cn': (515, 237, 545, 267), 'en': (515, 237, 545, 267), 'jp': (515, 237, 545, 267), 'tw': (515, 237, 545, 267)}, color={'cn': (52, 52, 53), 'en': (52, 52, 53), 'jp': (52, 52, 53), 'tw': (52, 52, 53)}, button={'cn': (515, 237, 545, 267), 'en': (515, 237, 545, 267), 'jp': (515, 237, 545, 267), 'tw': (515, 237, 545, 267)}, file={'cn': './assets/cn/coalition/EMPTY_VANGUARD.png', 'en': './assets/cn/coalition/EMPTY_VANGUARD.png', 'jp': './assets/cn/coalition/EMPTY_VANGUARD.png', 'tw': './assets/cn/coalition/EMPTY_VANGUARD.png'}) -FASHION_COALITION_CHECK = Button(area={'cn': (102, 19, 177, 51), 'en': (118, 31, 183, 50), 'jp': (101, 18, 175, 51), 'tw': (102, 19, 177, 51)}, color={'cn': (109, 104, 89), 'en': (131, 124, 102), 'jp': (122, 116, 101), 'tw': (109, 104, 89)}, button={'cn': (102, 19, 177, 51), 'en': (118, 31, 183, 50), 'jp': (101, 18, 175, 51), 'tw': (102, 19, 177, 51)}, file={'cn': './assets/cn/coalition/FASHION_COALITION_CHECK.png', 'en': './assets/en/coalition/FASHION_COALITION_CHECK.png', 'jp': './assets/jp/coalition/FASHION_COALITION_CHECK.png', 'tw': './assets/cn/coalition/FASHION_COALITION_CHECK.png'}) +FASHION_COALITION_CHECK = Button(area={'cn': (102, 19, 177, 51), 'en': (118, 31, 183, 50), 'jp': (101, 18, 175, 51), 'tw': (101, 17, 177, 51)}, color={'cn': (109, 104, 89), 'en': (131, 124, 102), 'jp': (122, 116, 101), 'tw': (108, 103, 89)}, button={'cn': (102, 19, 177, 51), 'en': (118, 31, 183, 50), 'jp': (101, 18, 175, 51), 'tw': (101, 17, 177, 51)}, file={'cn': './assets/cn/coalition/FASHION_COALITION_CHECK.png', 'en': './assets/en/coalition/FASHION_COALITION_CHECK.png', 'jp': './assets/jp/coalition/FASHION_COALITION_CHECK.png', 'tw': './assets/tw/coalition/FASHION_COALITION_CHECK.png'}) FASHION_EASY = Button(area={'cn': (136, 223, 199, 263), 'en': (136, 223, 199, 263), 'jp': (136, 223, 199, 263), 'tw': (136, 223, 199, 263)}, color={'cn': (225, 199, 197), 'en': (225, 199, 197), 'jp': (225, 199, 197), 'tw': (225, 199, 197)}, button={'cn': (136, 223, 199, 263), 'en': (136, 223, 199, 263), 'jp': (136, 223, 199, 263), 'tw': (136, 223, 199, 263)}, file={'cn': './assets/cn/coalition/FASHION_EASY.png', 'en': './assets/cn/coalition/FASHION_EASY.png', 'jp': './assets/cn/coalition/FASHION_EASY.png', 'tw': './assets/cn/coalition/FASHION_EASY.png'}) FASHION_EX = Button(area={'cn': (844, 246, 923, 301), 'en': (844, 246, 923, 301), 'jp': (844, 246, 923, 301), 'tw': (844, 246, 923, 301)}, color={'cn': (140, 115, 114), 'en': (140, 115, 114), 'jp': (140, 115, 114), 'tw': (140, 115, 114)}, button={'cn': (844, 246, 923, 301), 'en': (844, 246, 923, 301), 'jp': (844, 246, 923, 301), 'tw': (844, 246, 923, 301)}, file={'cn': './assets/cn/coalition/FASHION_EX.png', 'en': './assets/cn/coalition/FASHION_EX.png', 'jp': './assets/cn/coalition/FASHION_EX.png', 'tw': './assets/cn/coalition/FASHION_EX.png'}) FASHION_HARD = Button(area={'cn': (485, 167, 554, 215), 'en': (485, 167, 554, 215), 'jp': (485, 167, 554, 215), 'tw': (485, 167, 554, 215)}, color={'cn': (152, 136, 129), 'en': (152, 136, 129), 'jp': (152, 136, 129), 'tw': (152, 136, 129)}, button={'cn': (485, 167, 554, 215), 'en': (485, 167, 554, 215), 'jp': (485, 167, 554, 215), 'tw': (485, 167, 554, 215)}, file={'cn': './assets/cn/coalition/FASHION_HARD.png', 'en': './assets/cn/coalition/FASHION_HARD.png', 'jp': './assets/cn/coalition/FASHION_HARD.png', 'tw': './assets/cn/coalition/FASHION_HARD.png'}) -FASHION_MODE_BATTLE = Button(area={'cn': (152, 635, 213, 669), 'en': (108, 644, 188, 668), 'jp': (150, 636, 215, 668), 'tw': (152, 635, 213, 669)}, color={'cn': (140, 133, 117), 'en': (150, 143, 128), 'jp': (144, 137, 119), 'tw': (140, 133, 117)}, button={'cn': (152, 635, 213, 669), 'en': (108, 644, 188, 668), 'jp': (150, 636, 215, 668), 'tw': (152, 635, 213, 669)}, file={'cn': './assets/cn/coalition/FASHION_MODE_BATTLE.png', 'en': './assets/en/coalition/FASHION_MODE_BATTLE.png', 'jp': './assets/jp/coalition/FASHION_MODE_BATTLE.png', 'tw': './assets/cn/coalition/FASHION_MODE_BATTLE.png'}) -FASHION_MODE_STORY = Button(area={'cn': (154, 629, 220, 666), 'en': (117, 642, 195, 667), 'jp': (156, 629, 219, 665), 'tw': (154, 629, 220, 666)}, color={'cn': (141, 134, 116), 'en': (158, 148, 132), 'jp': (151, 143, 123), 'tw': (141, 134, 116)}, button={'cn': (154, 629, 220, 666), 'en': (117, 642, 195, 667), 'jp': (156, 629, 219, 665), 'tw': (154, 629, 220, 666)}, file={'cn': './assets/cn/coalition/FASHION_MODE_STORY.png', 'en': './assets/en/coalition/FASHION_MODE_STORY.png', 'jp': './assets/jp/coalition/FASHION_MODE_STORY.png', 'tw': './assets/cn/coalition/FASHION_MODE_STORY.png'}) +FASHION_MODE_BATTLE = Button(area={'cn': (152, 635, 213, 669), 'en': (108, 644, 188, 668), 'jp': (150, 636, 215, 668), 'tw': (150, 637, 212, 667)}, color={'cn': (140, 133, 117), 'en': (150, 143, 128), 'jp': (144, 137, 119), 'tw': (128, 123, 109)}, button={'cn': (152, 635, 213, 669), 'en': (108, 644, 188, 668), 'jp': (150, 636, 215, 668), 'tw': (150, 637, 212, 667)}, file={'cn': './assets/cn/coalition/FASHION_MODE_BATTLE.png', 'en': './assets/en/coalition/FASHION_MODE_BATTLE.png', 'jp': './assets/jp/coalition/FASHION_MODE_BATTLE.png', 'tw': './assets/tw/coalition/FASHION_MODE_BATTLE.png'}) +FASHION_MODE_STORY = Button(area={'cn': (154, 629, 220, 666), 'en': (117, 642, 195, 667), 'jp': (156, 629, 219, 665), 'tw': (154, 629, 219, 666)}, color={'cn': (141, 134, 116), 'en': (158, 148, 132), 'jp': (151, 143, 123), 'tw': (139, 132, 115)}, button={'cn': (154, 629, 220, 666), 'en': (117, 642, 195, 667), 'jp': (156, 629, 219, 665), 'tw': (154, 629, 219, 666)}, file={'cn': './assets/cn/coalition/FASHION_MODE_STORY.png', 'en': './assets/en/coalition/FASHION_MODE_STORY.png', 'jp': './assets/jp/coalition/FASHION_MODE_STORY.png', 'tw': './assets/tw/coalition/FASHION_MODE_STORY.png'}) FASHION_NORMAL = Button(area={'cn': (322, 295, 392, 334), 'en': (322, 295, 392, 334), 'jp': (322, 295, 392, 334), 'tw': (322, 295, 392, 334)}, color={'cn': (219, 196, 198), 'en': (219, 196, 198), 'jp': (219, 196, 198), 'tw': (219, 196, 198)}, button={'cn': (322, 295, 392, 334), 'en': (322, 295, 392, 334), 'jp': (322, 295, 392, 334), 'tw': (322, 295, 392, 334)}, file={'cn': './assets/cn/coalition/FASHION_NORMAL.png', 'en': './assets/cn/coalition/FASHION_NORMAL.png', 'jp': './assets/cn/coalition/FASHION_NORMAL.png', 'tw': './assets/cn/coalition/FASHION_NORMAL.png'}) FASHION_PT_OCR = Button(area={'cn': (881, 658, 937, 674), 'en': (881, 658, 937, 674), 'jp': (881, 658, 937, 674), 'tw': (881, 658, 937, 674)}, color={'cn': (136, 127, 122), 'en': (136, 127, 122), 'jp': (136, 127, 122), 'tw': (136, 127, 122)}, button={'cn': (881, 658, 937, 674), 'en': (881, 658, 937, 674), 'jp': (881, 658, 937, 674), 'tw': (881, 658, 937, 674)}, file={'cn': './assets/cn/coalition/FASHION_PT_OCR.png', 'en': './assets/cn/coalition/FASHION_PT_OCR.png', 'jp': './assets/cn/coalition/FASHION_PT_OCR.png', 'tw': './assets/cn/coalition/FASHION_PT_OCR.png'}) FASHION_SP = Button(area={'cn': (704, 194, 762, 242), 'en': (704, 194, 762, 242), 'jp': (704, 194, 762, 242), 'tw': (704, 194, 762, 242)}, color={'cn': (146, 133, 135), 'en': (146, 133, 135), 'jp': (146, 133, 135), 'tw': (146, 133, 135)}, button={'cn': (704, 194, 762, 242), 'en': (704, 194, 762, 242), 'jp': (704, 194, 762, 242), 'tw': (704, 194, 762, 242)}, file={'cn': './assets/cn/coalition/FASHION_SP.png', 'en': './assets/cn/coalition/FASHION_SP.png', 'jp': './assets/cn/coalition/FASHION_SP.png', 'tw': './assets/cn/coalition/FASHION_SP.png'}) -FASHION_SWITCH_MULTI = Button(area={'cn': (1075, 457, 1206, 485), 'en': (1076, 457, 1206, 485), 'jp': (1075, 457, 1206, 485), 'tw': (1075, 457, 1206, 485)}, color={'cn': (233, 183, 63), 'en': (201, 158, 54), 'jp': (227, 178, 61), 'tw': (233, 183, 63)}, button={'cn': (1075, 457, 1206, 485), 'en': (1076, 457, 1206, 485), 'jp': (1075, 457, 1206, 485), 'tw': (1075, 457, 1206, 485)}, file={'cn': './assets/cn/coalition/FASHION_SWITCH_MULTI.png', 'en': './assets/en/coalition/FASHION_SWITCH_MULTI.png', 'jp': './assets/jp/coalition/FASHION_SWITCH_MULTI.png', 'tw': './assets/cn/coalition/FASHION_SWITCH_MULTI.png'}) -FASHION_SWITCH_SINGLE = Button(area={'cn': (929, 457, 1059, 485), 'en': (929, 457, 1059, 485), 'jp': (929, 457, 1059, 485), 'tw': (929, 457, 1059, 485)}, color={'cn': (230, 181, 62), 'en': (202, 159, 54), 'jp': (227, 178, 61), 'tw': (230, 181, 62)}, button={'cn': (929, 457, 1059, 485), 'en': (929, 457, 1059, 485), 'jp': (929, 457, 1059, 485), 'tw': (929, 457, 1059, 485)}, file={'cn': './assets/cn/coalition/FASHION_SWITCH_SINGLE.png', 'en': './assets/en/coalition/FASHION_SWITCH_SINGLE.png', 'jp': './assets/jp/coalition/FASHION_SWITCH_SINGLE.png', 'tw': './assets/cn/coalition/FASHION_SWITCH_SINGLE.png'}) +FASHION_SWITCH_MULTI = Button(area={'cn': (1075, 457, 1206, 485), 'en': (1076, 457, 1206, 485), 'jp': (1075, 457, 1206, 485), 'tw': (1075, 457, 1206, 485)}, color={'cn': (233, 183, 63), 'en': (201, 158, 54), 'jp': (227, 178, 61), 'tw': (229, 181, 62)}, button={'cn': (1075, 457, 1206, 485), 'en': (1076, 457, 1206, 485), 'jp': (1075, 457, 1206, 485), 'tw': (1075, 457, 1206, 485)}, file={'cn': './assets/cn/coalition/FASHION_SWITCH_MULTI.png', 'en': './assets/en/coalition/FASHION_SWITCH_MULTI.png', 'jp': './assets/jp/coalition/FASHION_SWITCH_MULTI.png', 'tw': './assets/tw/coalition/FASHION_SWITCH_MULTI.png'}) +FASHION_SWITCH_SINGLE = Button(area={'cn': (929, 457, 1059, 485), 'en': (929, 457, 1059, 485), 'jp': (929, 457, 1059, 485), 'tw': (929, 457, 1059, 485)}, color={'cn': (230, 181, 62), 'en': (202, 159, 54), 'jp': (227, 178, 61), 'tw': (225, 177, 61)}, button={'cn': (929, 457, 1059, 485), 'en': (929, 457, 1059, 485), 'jp': (929, 457, 1059, 485), 'tw': (929, 457, 1059, 485)}, file={'cn': './assets/cn/coalition/FASHION_SWITCH_SINGLE.png', 'en': './assets/en/coalition/FASHION_SWITCH_SINGLE.png', 'jp': './assets/jp/coalition/FASHION_SWITCH_SINGLE.png', 'tw': './assets/tw/coalition/FASHION_SWITCH_SINGLE.png'}) FLEET_NOT_PREPARED = Button(area={'cn': (1008, 310, 1110, 334), 'en': (1008, 310, 1110, 334), 'jp': (1008, 310, 1110, 334), 'tw': (1008, 310, 1110, 334)}, color={'cn': (106, 106, 112), 'en': (106, 106, 112), 'jp': (106, 106, 112), 'tw': (108, 107, 112)}, button={'cn': (1008, 310, 1110, 334), 'en': (1008, 310, 1110, 334), 'jp': (1008, 310, 1110, 334), 'tw': (1008, 310, 1110, 334)}, file={'cn': './assets/cn/coalition/FLEET_NOT_PREPARED.png', 'en': './assets/cn/coalition/FLEET_NOT_PREPARED.png', 'jp': './assets/cn/coalition/FLEET_NOT_PREPARED.png', 'tw': './assets/tw/coalition/FLEET_NOT_PREPARED.png'}) FROSTFALL_COALITION_CHECK = Button(area={'cn': (118, 14, 227, 39), 'en': (118, 16, 221, 36), 'jp': (118, 14, 227, 39), 'tw': (118, 14, 227, 39)}, color={'cn': (145, 161, 200), 'en': (116, 130, 168), 'jp': (150, 166, 204), 'tw': (152, 168, 206)}, button={'cn': (118, 14, 227, 39), 'en': (118, 16, 221, 36), 'jp': (118, 14, 227, 39), 'tw': (118, 14, 227, 39)}, file={'cn': './assets/cn/coalition/FROSTFALL_COALITION_CHECK.png', 'en': './assets/en/coalition/FROSTFALL_COALITION_CHECK.png', 'jp': './assets/jp/coalition/FROSTFALL_COALITION_CHECK.png', 'tw': './assets/tw/coalition/FROSTFALL_COALITION_CHECK.png'}) FROSTFALL_EX = Button(area={'cn': (622, 372, 649, 384), 'en': (622, 372, 649, 384), 'jp': (622, 372, 649, 384), 'tw': (622, 372, 649, 384)}, color={'cn': (198, 152, 252), 'en': (198, 152, 252), 'jp': (198, 152, 252), 'tw': (182, 127, 252)}, button={'cn': (622, 372, 649, 384), 'en': (622, 372, 649, 384), 'jp': (622, 372, 649, 384), 'tw': (622, 372, 649, 384)}, file={'cn': './assets/cn/coalition/FROSTFALL_EX.png', 'en': './assets/en/coalition/FROSTFALL_EX.png', 'jp': './assets/jp/coalition/FROSTFALL_EX.png', 'tw': './assets/tw/coalition/FROSTFALL_EX.png'}) diff --git a/alas_wrapped/module/coalition/coalition.py b/alas_wrapped/module/coalition/coalition.py index 0aeb0673d2..eab449e3da 100644 --- a/alas_wrapped/module/coalition/coalition.py +++ b/alas_wrapped/module/coalition/coalition.py @@ -6,7 +6,6 @@ from module.exception import ScriptEnd, ScriptError from module.logger import logger from module.ocr.ocr import Digit -from module.log_res.log_res import LogRes from module.ui.page import page_campaign_menu @@ -70,8 +69,6 @@ def get_event_pt(self): pt = ocr.ocr(self.device.image) # 999999 seems to be a default value, wait if pt not in [999999]: - LogRes(self.config).Pt = pt - self.config.update() break else: logger.warning('Wait PT timeout, assume it is') diff --git a/alas_wrapped/module/combat/auto_search_combat.py b/alas_wrapped/module/combat/auto_search_combat.py index 9937fb8bb8..5dbf433932 100644 --- a/alas_wrapped/module/combat/auto_search_combat.py +++ b/alas_wrapped/module/combat/auto_search_combat.py @@ -94,7 +94,7 @@ def auto_search_watch_oil(self, checked=False): This will set auto_search_oil_limit_triggered. """ if not checked: - oil = self.get_oil() + oil = self._get_oil() if oil == 0: logger.warning('Oil not found') else: diff --git a/alas_wrapped/module/combat/combat.py b/alas_wrapped/module/combat/combat.py index f2c1feb7f6..818d3de9b6 100644 --- a/alas_wrapped/module/combat/combat.py +++ b/alas_wrapped/module/combat/combat.py @@ -124,6 +124,8 @@ def is_combat_executing(self): return PAUSE_ShadowPuppetry if PAUSE_MaidCafe.match_template_color(self.device.image, offset=(10, 10)): return PAUSE_MaidCafe + if PAUSE_Ancient.match_template_color(self.device.image, offset=(10, 10)): + return PAUSE_Ancient return False def handle_combat_quit(self, offset=(20, 20), interval=3): diff --git a/alas_wrapped/module/combat_ui/assets.py b/alas_wrapped/module/combat_ui/assets.py index f66c58ff1f..cf309cd150 100644 --- a/alas_wrapped/module/combat_ui/assets.py +++ b/alas_wrapped/module/combat_ui/assets.py @@ -5,6 +5,7 @@ # Don't modify it manually. PAUSE = Button(area={'cn': (1158, 40, 1199, 58), 'en': (1155, 38, 1216, 51), 'jp': (1232, 36, 1240, 60), 'tw': (1217, 36, 1225, 59)}, color={'cn': (189, 190, 202), 'en': (164, 169, 181), 'jp': (244, 241, 246), 'tw': (247, 243, 247)}, button={'cn': (1157, 34, 1241, 61), 'en': (1136, 26, 1270, 63), 'jp': (1141, 38, 1220, 57), 'tw': (1157, 34, 1241, 61)}, file={'cn': './assets/cn/combat_ui/PAUSE.png', 'en': './assets/en/combat_ui/PAUSE.png', 'jp': './assets/jp/combat_ui/PAUSE.png', 'tw': './assets/tw/combat_ui/PAUSE.png'}) +PAUSE_Ancient = Button(area={'cn': (1228, 36, 1245, 55), 'en': (1228, 36, 1245, 55), 'jp': (1228, 36, 1245, 55), 'tw': (1228, 36, 1245, 55)}, color={'cn': (172, 161, 144), 'en': (172, 161, 144), 'jp': (172, 161, 144), 'tw': (172, 161, 144)}, button={'cn': (1228, 36, 1245, 55), 'en': (1228, 36, 1245, 55), 'jp': (1228, 36, 1245, 55), 'tw': (1228, 36, 1245, 55)}, file={'cn': './assets/cn/combat_ui/PAUSE_Ancient.png', 'en': './assets/cn/combat_ui/PAUSE_Ancient.png', 'jp': './assets/cn/combat_ui/PAUSE_Ancient.png', 'tw': './assets/cn/combat_ui/PAUSE_Ancient.png'}) PAUSE_Christmas = Button(area={'cn': (1234, 35, 1250, 56), 'en': (1234, 35, 1250, 56), 'jp': (1234, 35, 1250, 56), 'tw': (1234, 35, 1250, 56)}, color={'cn': (158, 181, 210), 'en': (158, 181, 210), 'jp': (158, 181, 210), 'tw': (158, 181, 210)}, button={'cn': (1234, 35, 1250, 56), 'en': (1234, 35, 1250, 56), 'jp': (1234, 35, 1250, 56), 'tw': (1234, 35, 1250, 56)}, file={'cn': './assets/cn/combat_ui/PAUSE_Christmas.png', 'en': './assets/cn/combat_ui/PAUSE_Christmas.png', 'jp': './assets/cn/combat_ui/PAUSE_Christmas.png', 'tw': './assets/cn/combat_ui/PAUSE_Christmas.png'}) PAUSE_Cyber = Button(area={'cn': (1231, 32, 1253, 59), 'en': (1231, 32, 1253, 59), 'jp': (1231, 32, 1253, 59), 'tw': (1231, 32, 1253, 59)}, color={'cn': (40, 140, 157), 'en': (40, 140, 157), 'jp': (40, 140, 157), 'tw': (40, 140, 157)}, button={'cn': (1231, 32, 1253, 59), 'en': (1231, 32, 1253, 59), 'jp': (1231, 32, 1253, 59), 'tw': (1231, 32, 1253, 59)}, file={'cn': './assets/cn/combat_ui/PAUSE_Cyber.png', 'en': './assets/cn/combat_ui/PAUSE_Cyber.png', 'jp': './assets/cn/combat_ui/PAUSE_Cyber.png', 'tw': './assets/cn/combat_ui/PAUSE_Cyber.png'}) PAUSE_DOUBLE_CHECK = Button(area={'cn': (1226, 35, 1231, 60), 'en': (1226, 35, 1231, 61), 'jp': (1226, 35, 1230, 60), 'tw': (1226, 35, 1231, 60)}, color={'cn': (96, 104, 136), 'en': (83, 98, 118), 'jp': (97, 102, 120), 'tw': (96, 104, 136)}, button={'cn': (1226, 35, 1231, 60), 'en': (1226, 35, 1231, 61), 'jp': (1226, 35, 1230, 60), 'tw': (1226, 35, 1231, 60)}, file={'cn': './assets/cn/combat_ui/PAUSE_DOUBLE_CHECK.png', 'en': './assets/en/combat_ui/PAUSE_DOUBLE_CHECK.png', 'jp': './assets/jp/combat_ui/PAUSE_DOUBLE_CHECK.png', 'tw': './assets/tw/combat_ui/PAUSE_DOUBLE_CHECK.png'}) diff --git a/alas_wrapped/module/config/argument/args.json b/alas_wrapped/module/config/argument/args.json index 2bc9941252..fe703db495 100644 --- a/alas_wrapped/module/config/argument/args.json +++ b/alas_wrapped/module/config/argument/args.json @@ -1,206 +1,4 @@ { - "Dashboard": { - "Oil": { - "Value": { - "type": "input", - "value": 0 - }, - "Limit": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^000000" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Coin": { - "Value": { - "type": "input", - "value": 0 - }, - "Limit": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^FFAA33" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Gem": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^FF3333" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Pt": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^00BFFF" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Cube": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^33FFFF" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "ActionPoint": { - "Value": { - "type": "input", - "value": 0 - }, - "Total": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^0000FF" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "YellowCoin": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^FF8800" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "PurpleCoin": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^7700BB" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Core": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^AAAAAA" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Medal": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^FFDD00" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Merit": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^FFFF00" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "GuildCoin": { - "Value": { - "type": "input", - "value": 0 - }, - "Color": { - "type": "input", - "value": "^AAAAAA" - }, - "Record": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - } - }, - "Storage": { - "Storage": { - "type": "storage", - "value": {}, - "valuetype": "ignore", - "display": "disabled" - } - } - }, "Alas": { "Emulator": { "Serial": { @@ -269,6 +67,7 @@ "cn_android-25", "cn_android-26", "cn_android-27", + "cn_android-28", "cn_ios-0", "cn_ios-1", "cn_ios-2", @@ -394,10 +193,6 @@ "ScreenshotLength": { "type": "input", "value": 1 - }, - "RestartOnUnknownPage": { - "type": "checkbox", - "value": true } }, "Optimization": { @@ -1841,24 +1636,24 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "display": "hide", "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -2122,23 +1917,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -2517,23 +2312,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -2928,22 +2723,22 @@ "type": "state", "value": "campaign_main", "option": [ - "raid_20250116" + "raid_20260212" ], "option_cn": [ - "raid_20250116" + "raid_20260212" ], "option_en": [ - "raid_20250116" + "raid_20260212" ], "option_jp": [ - "raid_20250116" + "raid_20260212" ], "option_tw": [ - "raid_20250116" + "raid_20260212" ], "option_bold": [ - "raid_20250116" + "raid_20260212" ] }, "Mode": { @@ -3327,7 +3122,6 @@ "type": "state", "value": "campaign_main", "option": [ - "coalition_20251120", "coalition_20260122" ], "option_cn": [ @@ -3340,10 +3134,9 @@ "coalition_20260122" ], "option_tw": [ - "coalition_20251120" + "coalition_20260122" ], "option_bold": [ - "coalition_20251120", "coalition_20260122" ] }, @@ -3667,9 +3460,11 @@ "war_archives_20220728_cn", "war_archives_20220915_cn", "war_archives_20221222_cn", + "war_archives_20230223_cn", "war_archives_20231026_cn" ], "option_cn": [ + "war_archives_20230223_cn", "war_archives_20221222_cn", "war_archives_20220915_cn", "war_archives_20231026_cn", @@ -3715,6 +3510,7 @@ "war_archives_20181020_en" ], "option_en": [ + "war_archives_20230223_cn", "war_archives_20221222_cn", "war_archives_20220915_cn", "war_archives_20231026_cn", @@ -3760,6 +3556,7 @@ "war_archives_20181020_en" ], "option_jp": [ + "war_archives_20230223_cn", "war_archives_20221222_cn", "war_archives_20220915_cn", "war_archives_20231026_cn", @@ -3805,6 +3602,7 @@ "war_archives_20181020_en" ], "option_tw": [ + "war_archives_20230223_cn", "war_archives_20221222_cn", "war_archives_20220915_cn", "war_archives_20231026_cn", @@ -4237,23 +4035,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -4649,23 +4447,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -5061,23 +4859,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -5473,23 +5271,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -5875,23 +5673,23 @@ "value": "campaign_main", "option": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ], "option_cn": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_en": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_jp": [ - "event_20231221_cn" + "event_20260226_cn" ], "option_tw": [ "event_20220526_cn" ], "option_bold": [ "event_20220526_cn", - "event_20231221_cn" + "event_20260226_cn" ] }, "Mode": { @@ -6283,22 +6081,22 @@ "type": "state", "value": "campaign_main", "option": [ - "raid_20250116" + "raid_20260212" ], "option_cn": [ - "raid_20250116" + "raid_20260212" ], "option_en": [ - "raid_20250116" + "raid_20260212" ], "option_jp": [ - "raid_20250116" + "raid_20260212" ], "option_tw": [ - "raid_20250116" + "raid_20260212" ], "option_bold": [ - "raid_20250116" + "raid_20260212" ] }, "Mode": { @@ -6510,7 +6308,6 @@ "type": "state", "value": "campaign_main", "option": [ - "coalition_20251120", "coalition_20260122" ], "option_cn": [ @@ -6523,10 +6320,9 @@ "coalition_20260122" ], "option_tw": [ - "coalition_20251120" + "coalition_20260122" ], "option_bold": [ - "coalition_20251120", "coalition_20260122" ] }, @@ -8083,7 +7879,8 @@ "option": [ 2, 3, - 4 + 4, + 5 ] }, "ShipIndex": { @@ -8118,7 +7915,8 @@ 2, 3, 4, - 5 + 5, + 6 ] }, "ShipIndex": { diff --git a/alas_wrapped/module/config/argument/argument.yaml b/alas_wrapped/module/config/argument/argument.yaml index e35a9b7164..4cc3205db9 100644 --- a/alas_wrapped/module/config/argument/argument.yaml +++ b/alas_wrapped/module/config/argument/argument.yaml @@ -2,59 +2,6 @@ # Define arguments. # -------------------- -# ==================== Dashboard ==================== -Oil: - Value: 0 - Limit: 0 - Color: ^000000 - Record: 2020-01-01 00:00:00 -Coin: - Value: 0 - Limit: 0 - Color: ^FFAA33 - Record: 2020-01-01 00:00:00 -Gem: - Value: 0 - Color: ^FF3333 - Record: 2020-01-01 00:00:00 -Pt: - Value: 0 - Color: ^00BFFF - Record: 2020-01-01 00:00:00 -YellowCoin: - Value: 0 - Color: ^FF8800 - Record: 2020-01-01 00:00:00 -PurpleCoin: - Value: 0 - Color: ^7700BB - Record: 2020-01-01 00:00:00 -ActionPoint: - Value: 0 - Total: 0 - Color: ^0000FF - Record: 2020-01-01 00:00:00 -Merit: - Value: 0 - Color: ^FFFF00 - Record: 2020-01-01 00:00:00 -Cube: - Value: 0 - Color: ^33FFFF - Record: 2020-01-01 00:00:00 -Core: - Value: 0 - Color: ^AAAAAA - Record: 2020-01-01 00:00:00 -Medal: - Value: 0 - Color: ^FFDD00 - Record: 2020-01-01 00:00:00 -GuildCoin: - Value: 0 - Color: ^AAAAAA - Record: 2020-01-01 00:00:00 - # ==================== Alas ==================== Scheduler: @@ -141,7 +88,6 @@ Error: mode: yaml value: 'provider: null' ScreenshotLength: 1 - RestartOnUnknownPage: true Optimization: ScreenshotInterval: 0.3 CombatScreenshotInterval: 1.0 @@ -595,7 +541,7 @@ CoreShop: ShipyardDr: ResearchSeries: value: 2 - option: [ 2, 3, 4 ] + option: [ 2, 3, 4, 5 ] ShipIndex: value: 0 option: [ 0, 1, 2, 3, 4, 5, 6 ] @@ -604,7 +550,7 @@ ShipyardDr: Shipyard: ResearchSeries: value: 1 - option: [ 1, 2, 3, 4, 5 ] + option: [ 1, 2, 3, 4, 5, 6 ] ShipIndex: value: 0 option: [ 0, 1, 2, 3, 4, 5, 6 ] diff --git a/alas_wrapped/module/config/argument/gui.yaml b/alas_wrapped/module/config/argument/gui.yaml index d6c36792ad..82302d3ea2 100644 --- a/alas_wrapped/module/config/argument/gui.yaml +++ b/alas_wrapped/module/config/argument/gui.yaml @@ -14,8 +14,6 @@ Button: Stop: ScrollON: ScrollOFF: - DashboardON: - DashboardOFF: ClearLog: Setting: CheckUpdate: @@ -48,26 +46,6 @@ MenuDevelop: Overview: Scheduler: - Dashboard: - SecondsAgo: - MinutesAgo: - HoursAgo: - DaysAgo: - MonthsAgo: - YearsAgo: - NoData: - Oil: - Coin: - Gem: - Cube: - Pt: - YellowCoin: - PurpleCoin: - ActionPoint: - Merit: - Medal: - Core: - GuildCoin: Log: Running: Pending: diff --git a/alas_wrapped/module/config/argument/menu.json b/alas_wrapped/module/config/argument/menu.json index 7a2c2243d8..abdfe9c9b7 100644 --- a/alas_wrapped/module/config/argument/menu.json +++ b/alas_wrapped/module/config/argument/menu.json @@ -23,11 +23,11 @@ "page": "setting", "tasks": [ "EventGeneral", - "Coalition", "Event", "Event2", "Raid", "Hospital", + "Coalition", "MaritimeEscort", "WarArchives" ] @@ -36,13 +36,13 @@ "menu": "collapse", "page": "setting", "tasks": [ - "CoalitionSp", "EventA", "EventB", "EventC", "EventD", "EventSp", - "RaidDaily" + "RaidDaily", + "CoalitionSp" ] }, "Reward": { diff --git a/alas_wrapped/module/config/argument/task.yaml b/alas_wrapped/module/config/argument/task.yaml index 2cfc4b3729..c6f20bb00d 100644 --- a/alas_wrapped/module/config/argument/task.yaml +++ b/alas_wrapped/module/config/argument/task.yaml @@ -71,12 +71,6 @@ Event: EventGeneral: - EventGeneral - TaskBalancer - Coalition: - - Scheduler - - Campaign - - Coalition - - StopCondition - - Emotion Event: - Scheduler - Campaign @@ -106,6 +100,12 @@ Event: - Hospital - StopCondition - Emotion + Coalition: + - Scheduler + - Campaign + - Coalition + - StopCondition + - Emotion MaritimeEscort: - Scheduler - MaritimeEscort @@ -126,12 +126,6 @@ EventDaily: menu: 'collapse' page: 'setting' tasks: - CoalitionSp: - - Scheduler - - Campaign - - Coalition - - StopCondition - - Emotion EventA: - Scheduler - EventDaily @@ -187,6 +181,12 @@ EventDaily: - Campaign - StopCondition - Emotion + CoalitionSp: + - Scheduler + - Campaign + - Coalition + - StopCondition + - Emotion # ==================== Reward ==================== diff --git a/alas_wrapped/module/config/config_generated.py b/alas_wrapped/module/config/config_generated.py index 67e1524dd8..7c057779a7 100644 --- a/alas_wrapped/module/config/config_generated.py +++ b/alas_wrapped/module/config/config_generated.py @@ -9,69 +9,6 @@ class GeneratedConfig: Auto generated configuration """ - # Group `Oil` - Oil_Value = 0 - Oil_Limit = 0 - Oil_Color = '^000000' - Oil_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Coin` - Coin_Value = 0 - Coin_Limit = 0 - Coin_Color = '^FFAA33' - Coin_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Gem` - Gem_Value = 0 - Gem_Color = '^FF3333' - Gem_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Pt` - Pt_Value = 0 - Pt_Color = '^00BFFF' - Pt_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `YellowCoin` - YellowCoin_Value = 0 - YellowCoin_Color = '^FF8800' - YellowCoin_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `PurpleCoin` - PurpleCoin_Value = 0 - PurpleCoin_Color = '^7700BB' - PurpleCoin_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `ActionPoint` - ActionPoint_Value = 0 - ActionPoint_Total = 0 - ActionPoint_Color = '^0000FF' - ActionPoint_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Merit` - Merit_Value = 0 - Merit_Color = '^FFFF00' - Merit_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Cube` - Cube_Value = 0 - Cube_Color = '^33FFFF' - Cube_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Core` - Core_Value = 0 - Core_Color = '^AAAAAA' - Core_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `Medal` - Medal_Value = 0 - Medal_Color = '^FFDD00' - Medal_Record = datetime.datetime(2020, 1, 1, 0, 0) - - # Group `GuildCoin` - GuildCoin_Value = 0 - GuildCoin_Color = '^AAAAAA' - GuildCoin_Record = datetime.datetime(2020, 1, 1, 0, 0) - # Group `Scheduler` Scheduler_Enable = False # True, False Scheduler_NextRun = datetime.datetime(2020, 1, 1, 0, 0) @@ -83,7 +20,7 @@ class GeneratedConfig: # Group `Emulator` Emulator_Serial = 'auto' Emulator_PackageName = 'auto' # auto, com.bilibili.azurlane, com.YoStarEN.AzurLane, com.YoStarJP.AzurLane, com.hkmanjuu.azurlane.gp, com.bilibili.blhx.huawei, com.bilibili.blhx.honor, com.bilibili.blhx.mi, com.tencent.tmgp.bilibili.blhx, com.bilibili.blhx.baidu, com.bilibili.blhx.qihoo, com.bilibili.blhx.nearme.gamecenter, com.bilibili.blhx.vivo, com.bilibili.blhx.mz, com.bilibili.blhx.dl, com.bilibili.blhx.lenovo, com.bilibili.blhx.uc, com.bilibili.blhx.mzw, com.yiwu.blhx.yx15, com.bilibili.blhx.m4399, com.bilibili.blhx.bilibiliMove, com.hkmanjuu.azurlane.gp.mc - Emulator_ServerName = 'disabled' # disabled, cn_android-0, cn_android-1, cn_android-2, cn_android-3, cn_android-4, cn_android-5, cn_android-6, cn_android-7, cn_android-8, cn_android-9, cn_android-10, cn_android-11, cn_android-12, cn_android-13, cn_android-14, cn_android-15, cn_android-16, cn_android-17, cn_android-18, cn_android-19, cn_android-20, cn_android-21, cn_android-22, cn_android-23, cn_android-24, cn_android-25, cn_android-26, cn_android-27, cn_ios-0, cn_ios-1, cn_ios-2, cn_ios-3, cn_ios-4, cn_ios-5, cn_ios-6, cn_ios-7, cn_ios-8, cn_ios-9, cn_ios-10, cn_channel-0, cn_channel-1, cn_channel-2, cn_channel-3, cn_channel-4, en-0, en-1, en-2, en-3, en-4, en-5, jp-0, jp-1, jp-2, jp-3, jp-4, jp-5, jp-6, jp-7, jp-8, jp-9, jp-10, jp-11, jp-12, jp-13, jp-14, jp-15, jp-16, jp-17 + Emulator_ServerName = 'disabled' # disabled, cn_android-0, cn_android-1, cn_android-2, cn_android-3, cn_android-4, cn_android-5, cn_android-6, cn_android-7, cn_android-8, cn_android-9, cn_android-10, cn_android-11, cn_android-12, cn_android-13, cn_android-14, cn_android-15, cn_android-16, cn_android-17, cn_android-18, cn_android-19, cn_android-20, cn_android-21, cn_android-22, cn_android-23, cn_android-24, cn_android-25, cn_android-26, cn_android-27, cn_android-28, cn_ios-0, cn_ios-1, cn_ios-2, cn_ios-3, cn_ios-4, cn_ios-5, cn_ios-6, cn_ios-7, cn_ios-8, cn_ios-9, cn_ios-10, cn_channel-0, cn_channel-1, cn_channel-2, cn_channel-3, cn_channel-4, en-0, en-1, en-2, en-3, en-4, en-5, jp-0, jp-1, jp-2, jp-3, jp-4, jp-5, jp-6, jp-7, jp-8, jp-9, jp-10, jp-11, jp-12, jp-13, jp-14, jp-15, jp-16, jp-17 Emulator_ScreenshotMethod = 'auto' # auto, ADB, ADB_nc, uiautomator2, aScreenCap, aScreenCap_nc, DroidCast, DroidCast_raw, nemu_ipc, ldopengl Emulator_ControlMethod = 'MaaTouch' # ADB, uiautomator2, minitouch, Hermit, MaaTouch Emulator_ScreenshotDedithering = False @@ -99,7 +36,6 @@ class GeneratedConfig: Error_SaveError = True Error_OnePushConfig = 'provider: null' Error_ScreenshotLength = 1 - Error_RestartOnUnknownPage = True # Group `Optimization` Optimization_ScreenshotInterval = 0.3 @@ -361,13 +297,13 @@ class GeneratedConfig: CoreShop_Filter = 'Array' # Group `ShipyardDr` - ShipyardDr_ResearchSeries = 2 # 2, 3, 4 + ShipyardDr_ResearchSeries = 2 # 2, 3, 4, 5 ShipyardDr_ShipIndex = 0 # 0, 1, 2, 3, 4, 5, 6 ShipyardDr_BuyAmount = 2 ShipyardDr_LastRun = datetime.datetime(2020, 1, 1, 0, 0) # Group `Shipyard` - Shipyard_ResearchSeries = 1 # 1, 2, 3, 4, 5 + Shipyard_ResearchSeries = 1 # 1, 2, 3, 4, 5, 6 Shipyard_ShipIndex = 0 # 0, 1, 2, 3, 4, 5, 6 Shipyard_BuyAmount = 2 Shipyard_LastRun = datetime.datetime(2020, 1, 1, 0, 0) diff --git a/alas_wrapped/module/config/config_updater.py b/alas_wrapped/module/config/config_updater.py index 19c700e9f6..5a95e04ec4 100644 --- a/alas_wrapped/module/config/config_updater.py +++ b/alas_wrapped/module/config/config_updater.py @@ -149,15 +149,6 @@ def gui(self): """ return read_file(filepath_argument('gui')) - @cached_property - def dashboard(self): - """ - - - - """ - return read_file(filepath_argument('dashboard')) - - @cached_property @timer def args(self): @@ -172,12 +163,10 @@ def args(self): """ # Construct args data = {} - # Add dashboard to args - dashboard_and_task = {**self.dashboard,**self.task} - for path, groups in deep_iter(dashboard_and_task, depth=3): - if 'tasks' not in path and 'Dashboard' not in path: + for path, groups in deep_iter(self.task, depth=3): + if 'tasks' not in path: continue - task = path[2] if 'tasks' in path else path[0] + task = path[2] # Add storage to all task groups.append('Storage') for group in groups: diff --git a/alas_wrapped/module/config/i18n/en-US.json b/alas_wrapped/module/config/i18n/en-US.json index 6c99da4e37..153b3f6d97 100644 --- a/alas_wrapped/module/config/i18n/en-US.json +++ b/alas_wrapped/module/config/i18n/en-US.json @@ -66,10 +66,6 @@ "name": "Event General", "help": "" }, - "Coalition": { - "name": "Light & Shadow Fashion Shoot", - "help": "" - }, "Event": { "name": "Event", "help": "" @@ -86,6 +82,10 @@ "name": "Valley Hospital", "help": "" }, + "Coalition": { + "name": "Light & Shadow Fashion Shoot", + "help": "" + }, "MaritimeEscort": { "name": "Maritime Escort", "help": "" @@ -94,10 +94,6 @@ "name": "War Archives", "help": "Due to the lack of maintenance of war archives, continuous clear may not work normally, if Alas runs abnormally, Please manually finish clearing and use auto search" }, - "CoalitionSp": { - "name": "Light & Shadow Fashion Shoot SP", - "help": "" - }, "EventA": { "name": "Event Daily A", "help": "" @@ -122,6 +118,10 @@ "name": "Raid Daily", "help": "" }, + "CoalitionSp": { + "name": "Light & Shadow Fashion Shoot SP", + "help": "" + }, "Commission": { "name": "Commission", "help": "" @@ -279,234 +279,6 @@ "help": "" } }, - "Oil": { - "_info": { - "name": "Oil._info.name", - "help": "Oil._info.help" - }, - "Value": { - "name": "Oil.Value.name", - "help": "Oil.Value.help" - }, - "Limit": { - "name": "Oil.Limit.name", - "help": "Oil.Limit.help" - }, - "Color": { - "name": "Oil.Color.name", - "help": "Oil.Color.help" - }, - "Record": { - "name": "Oil.Record.name", - "help": "Oil.Record.help" - } - }, - "Coin": { - "_info": { - "name": "Coin._info.name", - "help": "Coin._info.help" - }, - "Value": { - "name": "Coin.Value.name", - "help": "Coin.Value.help" - }, - "Limit": { - "name": "Coin.Limit.name", - "help": "Coin.Limit.help" - }, - "Color": { - "name": "Coin.Color.name", - "help": "Coin.Color.help" - }, - "Record": { - "name": "Coin.Record.name", - "help": "Coin.Record.help" - } - }, - "Gem": { - "_info": { - "name": "Gem._info.name", - "help": "Gem._info.help" - }, - "Value": { - "name": "Gem.Value.name", - "help": "Gem.Value.help" - }, - "Color": { - "name": "Gem.Color.name", - "help": "Gem.Color.help" - }, - "Record": { - "name": "Gem.Record.name", - "help": "Gem.Record.help" - } - }, - "Pt": { - "_info": { - "name": "Pt._info.name", - "help": "Pt._info.help" - }, - "Value": { - "name": "Pt.Value.name", - "help": "Pt.Value.help" - }, - "Color": { - "name": "Pt.Color.name", - "help": "Pt.Color.help" - }, - "Record": { - "name": "Pt.Record.name", - "help": "Pt.Record.help" - } - }, - "YellowCoin": { - "_info": { - "name": "YellowCoin._info.name", - "help": "YellowCoin._info.help" - }, - "Value": { - "name": "YellowCoin.Value.name", - "help": "YellowCoin.Value.help" - }, - "Color": { - "name": "YellowCoin.Color.name", - "help": "YellowCoin.Color.help" - }, - "Record": { - "name": "YellowCoin.Record.name", - "help": "YellowCoin.Record.help" - } - }, - "PurpleCoin": { - "_info": { - "name": "PurpleCoin._info.name", - "help": "PurpleCoin._info.help" - }, - "Value": { - "name": "PurpleCoin.Value.name", - "help": "PurpleCoin.Value.help" - }, - "Color": { - "name": "PurpleCoin.Color.name", - "help": "PurpleCoin.Color.help" - }, - "Record": { - "name": "PurpleCoin.Record.name", - "help": "PurpleCoin.Record.help" - } - }, - "ActionPoint": { - "_info": { - "name": "ActionPoint._info.name", - "help": "ActionPoint._info.help" - }, - "Value": { - "name": "ActionPoint.Value.name", - "help": "ActionPoint.Value.help" - }, - "Total": { - "name": "ActionPoint.Total.name", - "help": "ActionPoint.Total.help" - }, - "Color": { - "name": "ActionPoint.Color.name", - "help": "ActionPoint.Color.help" - }, - "Record": { - "name": "ActionPoint.Record.name", - "help": "ActionPoint.Record.help" - } - }, - "Merit": { - "_info": { - "name": "Merit._info.name", - "help": "Merit._info.help" - }, - "Value": { - "name": "Merit.Value.name", - "help": "Merit.Value.help" - }, - "Color": { - "name": "Merit.Color.name", - "help": "Merit.Color.help" - }, - "Record": { - "name": "Merit.Record.name", - "help": "Merit.Record.help" - } - }, - "Cube": { - "_info": { - "name": "Cube._info.name", - "help": "Cube._info.help" - }, - "Value": { - "name": "Cube.Value.name", - "help": "Cube.Value.help" - }, - "Color": { - "name": "Cube.Color.name", - "help": "Cube.Color.help" - }, - "Record": { - "name": "Cube.Record.name", - "help": "Cube.Record.help" - } - }, - "Core": { - "_info": { - "name": "Core._info.name", - "help": "Core._info.help" - }, - "Value": { - "name": "Core.Value.name", - "help": "Core.Value.help" - }, - "Color": { - "name": "Core.Color.name", - "help": "Core.Color.help" - }, - "Record": { - "name": "Core.Record.name", - "help": "Core.Record.help" - } - }, - "Medal": { - "_info": { - "name": "Medal._info.name", - "help": "Medal._info.help" - }, - "Value": { - "name": "Medal.Value.name", - "help": "Medal.Value.help" - }, - "Color": { - "name": "Medal.Color.name", - "help": "Medal.Color.help" - }, - "Record": { - "name": "Medal.Record.name", - "help": "Medal.Record.help" - } - }, - "GuildCoin": { - "_info": { - "name": "GuildCoin._info.name", - "help": "GuildCoin._info.help" - }, - "Value": { - "name": "GuildCoin.Value.name", - "help": "GuildCoin.Value.help" - }, - "Color": { - "name": "GuildCoin.Color.name", - "help": "GuildCoin.Color.help" - }, - "Record": { - "name": "GuildCoin.Record.name", - "help": "GuildCoin.Record.help" - } - }, "Scheduler": { "_info": { "name": "Scheduler", @@ -606,6 +378,7 @@ "cn_android-25": "[国服] 水仙行动", "cn_android-26": "[国服] 冬月计划", "cn_android-27": "[国服] 长弓计划", + "cn_android-28": "[国服] 裁决协议", "cn_ios-0": "[国服] 夏威夷", "cn_ios-1": "[国服] 珊瑚海", "cn_ios-2": "[国服] 中途岛", @@ -731,10 +504,6 @@ "ScreenshotLength": { "name": "Record Screenshot(s)", "help": "Number of screenshots saved when exception occurs" - }, - "RestartOnUnknownPage": { - "name": "Restart on Unknown Page", - "help": "When an unknown page is encountered and the server is online, restart the game instead of stopping (only applies when Enable Exception Handling is turned on). After 3 consecutive task failures, ALAS will still request human takeover." } }, "Optimization": { @@ -997,6 +766,7 @@ "event_20250912_cn": "A Dance for Amahara Above", "event_20251023_cn": "Tempesta and Islas de Libertád", "event_20251218_cn": "A Note Through the Firmament", + "event_20260226_cn": "Springtide Inn Online", "raid_20200624": "Air Raid Drills with Essex Rerun", "raid_20210708": "Cross Wave rerun", "raid_20220127": "Mystery Investigation", @@ -1005,8 +775,9 @@ "raid_20230118": "Winter Pathfinder", "raid_20230629": "Reflections of the Oasis", "raid_20240130": "Spring Festive Fiasco", - "raid_20240328": "From Zero to Hero", + "raid_20240328": "From Zero to Hero Rerun", "raid_20250116": "Spring Fashion Festa", + "raid_20260212": "Spring Auction Adventure", "war_archives_20180607_cn": "archives Ink Stained Steel Sakura", "war_archives_20180726_cn": "archives Iris of Light and Dark", "war_archives_20181020_en": "archives Strive Wish and Strategize", @@ -1049,6 +820,7 @@ "war_archives_20220728_cn": "archives Aquilifers Ballade", "war_archives_20220915_cn": "archives Violet Tempest Blooming Lycoris", "war_archives_20221222_cn": "archives Parallel Superimposition", + "war_archives_20230223_cn": "archives Revelations of Dust", "war_archives_20231026_cn": "archives Tempesta and the Fountain of Youth" }, "Mode": { @@ -1501,10 +1273,10 @@ "Mode": { "name": "Raid Mode", "help": "", - "easy": "easy", - "normal": "normal", - "hard": "hard", - "ex": "ex" + "easy": "Easy", + "normal": "Normal", + "hard": "Hard", + "ex": "EX" }, "UseTicket": { "name": "Use Ticket(s)", @@ -2117,7 +1889,8 @@ "help": "", "2": "DR2", "3": "DR3", - "4": "DR4" + "4": "DR4", + "5": "DR5" }, "ShipIndex": { "name": "Ship Index", @@ -2151,7 +1924,8 @@ "2": "PR2", "3": "PR3", "4": "PR4", - "5": "PR5" + "5": "PR5", + "6": "PR6" }, "ShipIndex": { "name": "Ship Index", @@ -2462,7 +2236,7 @@ }, "OpponentChooseMode": { "name": "Opponent Choose Mode", - "help": "IMPORTANT: In Exercise, opponents are ALWAYS sorted by rank (left = highest, right = lowest).\n• leftmost = Fight highest rank player (MAXIMUM merit points/rewards) - RECOMMENDED\n• max_exp = Calculate and fight highest level opponent\n• easiest = Fight lowest level opponent\n• easiest_else_exp = Try easiest first, switch to max_exp if failed", + "help": "", "max_exp": "Most Exp.", "easiest": "Easiest", "leftmost": "Highest ranking", @@ -2470,7 +2244,7 @@ }, "OpponentTrial": { "name": "Each Opponent Try X Time(s)", - "help": "Number of times to retry the SAME opponent before moving to the next one.\nIf you quit battles due to low HP, increase this value (e.g., 10) to retry the same opponent multiple times instead of switching.\nRecommended: 10 for consistent targeting of the best opponent.\n1 ~ Positive Infinity" + "help": "1 ~ Positive Infinity" }, "ExerciseStrategy": { "name": "Exercise Strategy (Keep X number remains)", @@ -2870,8 +2644,6 @@ "Stop": "Stop", "ScrollON": "Auto Scroll ON", "ScrollOFF": "Auto Scroll OFF", - "DashboardON": "Fold", - "DashboardOFF": "Unfold", "ClearLog": "Clear Log", "Setting": "Setting", "CheckUpdate": "Check update", @@ -2904,26 +2676,6 @@ }, "Overview": { "Scheduler": "Scheduler", - "Dashboard": "Gui.Overview.Dashboard", - "SecondsAgo": "Seconds ago", - "MinutesAgo": "Minutes ago", - "HoursAgo": "Hours ago", - "DaysAgo": "Days ago", - "MonthsAgo": "Months ago", - "YearsAgo": "Years ago", - "NoData": "No data", - "Oil": "Oil", - "Coin": "Coin", - "Gem": "Gem", - "Cube": "Cube", - "Pt": "Event Pt", - "YellowCoin": "Operation Supply Coin", - "PurpleCoin": "Special Item Token", - "ActionPoint": "Action Point", - "Merit": "Merit", - "Medal": "Medal", - "Core": "Core Data", - "GuildCoin": "Guild Coin", "Log": "Log", "Running": "Running", "Pending": "Pending", diff --git a/alas_wrapped/module/config/i18n/ja-JP.json b/alas_wrapped/module/config/i18n/ja-JP.json index 4676d758c7..2de0d9a8b3 100644 --- a/alas_wrapped/module/config/i18n/ja-JP.json +++ b/alas_wrapped/module/config/i18n/ja-JP.json @@ -66,10 +66,6 @@ "name": "イベント共通設定", "help": "" }, - "Coalition": { - "name": "特集写真-撮影進行中", - "help": "" - }, "Event": { "name": "イベント海域", "help": "" @@ -86,6 +82,10 @@ "name": "病院探訪", "help": "" }, + "Coalition": { + "name": "特集写真-撮影進行中", + "help": "" + }, "MaritimeEscort": { "name": "Maritime Escort", "help": "" @@ -94,10 +94,6 @@ "name": "作戦履歴", "help": "" }, - "CoalitionSp": { - "name": "特集写真-撮影進行中SP", - "help": "" - }, "EventA": { "name": "毎日イベント海域A", "help": "" @@ -122,6 +118,10 @@ "name": "Raid Daily", "help": "" }, + "CoalitionSp": { + "name": "特集写真-撮影進行中SP", + "help": "" + }, "Commission": { "name": "委託", "help": "" @@ -279,234 +279,6 @@ "help": "" } }, - "Oil": { - "_info": { - "name": "Oil._info.name", - "help": "Oil._info.help" - }, - "Value": { - "name": "Oil.Value.name", - "help": "Oil.Value.help" - }, - "Limit": { - "name": "Oil.Limit.name", - "help": "Oil.Limit.help" - }, - "Color": { - "name": "Oil.Color.name", - "help": "Oil.Color.help" - }, - "Record": { - "name": "Oil.Record.name", - "help": "Oil.Record.help" - } - }, - "Coin": { - "_info": { - "name": "Coin._info.name", - "help": "Coin._info.help" - }, - "Value": { - "name": "Coin.Value.name", - "help": "Coin.Value.help" - }, - "Limit": { - "name": "Coin.Limit.name", - "help": "Coin.Limit.help" - }, - "Color": { - "name": "Coin.Color.name", - "help": "Coin.Color.help" - }, - "Record": { - "name": "Coin.Record.name", - "help": "Coin.Record.help" - } - }, - "Gem": { - "_info": { - "name": "Gem._info.name", - "help": "Gem._info.help" - }, - "Value": { - "name": "Gem.Value.name", - "help": "Gem.Value.help" - }, - "Color": { - "name": "Gem.Color.name", - "help": "Gem.Color.help" - }, - "Record": { - "name": "Gem.Record.name", - "help": "Gem.Record.help" - } - }, - "Pt": { - "_info": { - "name": "Pt._info.name", - "help": "Pt._info.help" - }, - "Value": { - "name": "Pt.Value.name", - "help": "Pt.Value.help" - }, - "Color": { - "name": "Pt.Color.name", - "help": "Pt.Color.help" - }, - "Record": { - "name": "Pt.Record.name", - "help": "Pt.Record.help" - } - }, - "YellowCoin": { - "_info": { - "name": "YellowCoin._info.name", - "help": "YellowCoin._info.help" - }, - "Value": { - "name": "YellowCoin.Value.name", - "help": "YellowCoin.Value.help" - }, - "Color": { - "name": "YellowCoin.Color.name", - "help": "YellowCoin.Color.help" - }, - "Record": { - "name": "YellowCoin.Record.name", - "help": "YellowCoin.Record.help" - } - }, - "PurpleCoin": { - "_info": { - "name": "PurpleCoin._info.name", - "help": "PurpleCoin._info.help" - }, - "Value": { - "name": "PurpleCoin.Value.name", - "help": "PurpleCoin.Value.help" - }, - "Color": { - "name": "PurpleCoin.Color.name", - "help": "PurpleCoin.Color.help" - }, - "Record": { - "name": "PurpleCoin.Record.name", - "help": "PurpleCoin.Record.help" - } - }, - "ActionPoint": { - "_info": { - "name": "ActionPoint._info.name", - "help": "ActionPoint._info.help" - }, - "Value": { - "name": "ActionPoint.Value.name", - "help": "ActionPoint.Value.help" - }, - "Total": { - "name": "ActionPoint.Total.name", - "help": "ActionPoint.Total.help" - }, - "Color": { - "name": "ActionPoint.Color.name", - "help": "ActionPoint.Color.help" - }, - "Record": { - "name": "ActionPoint.Record.name", - "help": "ActionPoint.Record.help" - } - }, - "Merit": { - "_info": { - "name": "Merit._info.name", - "help": "Merit._info.help" - }, - "Value": { - "name": "Merit.Value.name", - "help": "Merit.Value.help" - }, - "Color": { - "name": "Merit.Color.name", - "help": "Merit.Color.help" - }, - "Record": { - "name": "Merit.Record.name", - "help": "Merit.Record.help" - } - }, - "Cube": { - "_info": { - "name": "Cube._info.name", - "help": "Cube._info.help" - }, - "Value": { - "name": "Cube.Value.name", - "help": "Cube.Value.help" - }, - "Color": { - "name": "Cube.Color.name", - "help": "Cube.Color.help" - }, - "Record": { - "name": "Cube.Record.name", - "help": "Cube.Record.help" - } - }, - "Core": { - "_info": { - "name": "Core._info.name", - "help": "Core._info.help" - }, - "Value": { - "name": "Core.Value.name", - "help": "Core.Value.help" - }, - "Color": { - "name": "Core.Color.name", - "help": "Core.Color.help" - }, - "Record": { - "name": "Core.Record.name", - "help": "Core.Record.help" - } - }, - "Medal": { - "_info": { - "name": "Medal._info.name", - "help": "Medal._info.help" - }, - "Value": { - "name": "Medal.Value.name", - "help": "Medal.Value.help" - }, - "Color": { - "name": "Medal.Color.name", - "help": "Medal.Color.help" - }, - "Record": { - "name": "Medal.Record.name", - "help": "Medal.Record.help" - } - }, - "GuildCoin": { - "_info": { - "name": "GuildCoin._info.name", - "help": "GuildCoin._info.help" - }, - "Value": { - "name": "GuildCoin.Value.name", - "help": "GuildCoin.Value.help" - }, - "Color": { - "name": "GuildCoin.Color.name", - "help": "GuildCoin.Color.help" - }, - "Record": { - "name": "GuildCoin.Record.name", - "help": "GuildCoin.Record.help" - } - }, "Scheduler": { "_info": { "name": "Scheduler._info.name", @@ -606,6 +378,7 @@ "cn_android-25": "[国服] 水仙行动", "cn_android-26": "[国服] 冬月计划", "cn_android-27": "[国服] 长弓计划", + "cn_android-28": "[国服] 裁决协议", "cn_ios-0": "[国服] 夏威夷", "cn_ios-1": "[国服] 珊瑚海", "cn_ios-2": "[国服] 中途岛", @@ -731,10 +504,6 @@ "ScreenshotLength": { "name": "Error.ScreenshotLength.name", "help": "Error.ScreenshotLength.help" - }, - "RestartOnUnknownPage": { - "name": "Error.RestartOnUnknownPage.name", - "help": "Error.RestartOnUnknownPage.help" } }, "Optimization": { @@ -997,6 +766,7 @@ "event_20250912_cn": "アマハラに舞い奉れ", "event_20251023_cn": "テンペスタと自由群島", "event_20251218_cn": "天穹に響く音謡", + "event_20260226_cn": "春色旅籠Online", "raid_20200624": "特別演習超空強襲波(復刻)", "raid_20210708": "交錯する新たな波 (復刻)", "raid_20220127": "秘密事件調査", @@ -1005,8 +775,9 @@ "raid_20230118": "冬の案内人", "raid_20230629": "緑地伽話", "raid_20240130": "新春宴会狂騒曲", - "raid_20240328": "ゼロから頑張る魔王討伐", + "raid_20240328": "ゼロから頑張る魔王討伐(復刻)", "raid_20250116": "新春華裳協奏曲", + "raid_20260212": "新春玉逸品会", "war_archives_20180607_cn": "檔案 墨染まりし鋼の桜", "war_archives_20180726_cn": "檔案 光と影のアイリス", "war_archives_20181020_en": "檔案 努力希望と計画", @@ -1049,6 +820,7 @@ "war_archives_20220728_cn": "檔案 鋼鷲の冒険譚", "war_archives_20220915_cn": "檔案 赫の涙月 菫の暁風", "war_archives_20221222_cn": "檔案 積重なる事象の幻界", + "war_archives_20230223_cn": "檔案 黙示の遺構", "war_archives_20231026_cn": "檔案 テンペスタと若返りの泉" }, "Mode": { @@ -2117,7 +1889,8 @@ "help": "ShipyardDr.ResearchSeries.help", "2": "2", "3": "3", - "4": "4" + "4": "4", + "5": "5" }, "ShipIndex": { "name": "ShipyardDr.ShipIndex.name", @@ -2151,7 +1924,8 @@ "2": "2", "3": "3", "4": "4", - "5": "5" + "5": "5", + "6": "6" }, "ShipIndex": { "name": "Shipyard.ShipIndex.name", @@ -2870,8 +2644,6 @@ "Stop": "中止", "ScrollON": "自動スクロール ON", "ScrollOFF": "自動スクロール OFF", - "DashboardON": "Gui.Button.DashboardON", - "DashboardOFF": "Gui.Button.DashboardOFF", "ClearLog": "ログクリーニング", "Setting": "設定", "CheckUpdate": "アップデータチェック", @@ -2904,26 +2676,6 @@ }, "Overview": { "Scheduler": "スケジューラー", - "Dashboard": "Gui.Overview.Dashboard", - "SecondsAgo": "Seconds ago", - "MinutesAgo": "Minutes ago", - "HoursAgo": "Hours ago", - "DaysAgo": "Days ago", - "MonthsAgo": "Months ago", - "YearsAgo": "Years ago", - "NoData": "No data", - "Oil": "Oil", - "Coin": "Coin", - "Gem": "Gem", - "Cube": "Cube", - "Pt": "Event Pt", - "YellowCoin": "Operation Supply Coin", - "PurpleCoin": "Special Item Token", - "ActionPoint": "Action Point", - "Merit": "Gui.Overview.Merit", - "Medal": "Gui.Overview.Medal", - "Core": "Gui.Overview.Core", - "GuildCoin": "Gui.Overview.GuildCoin", "Log": "ログ", "Running": "実行中", "Pending": "隊列中", diff --git a/alas_wrapped/module/config/i18n/zh-CN.json b/alas_wrapped/module/config/i18n/zh-CN.json index 5f1641fc1c..5af93089b6 100644 --- a/alas_wrapped/module/config/i18n/zh-CN.json +++ b/alas_wrapped/module/config/i18n/zh-CN.json @@ -66,10 +66,6 @@ "name": "活动通用设置", "help": "" }, - "Coalition": { - "name": "光影风尚-拍摄进行时", - "help": "" - }, "Event": { "name": "活动图", "help": "" @@ -86,6 +82,10 @@ "name": "深谷来信", "help": "" }, + "Coalition": { + "name": "光影风尚-拍摄进行时", + "help": "" + }, "MaritimeEscort": { "name": "商船护航", "help": "" @@ -94,10 +94,6 @@ "name": "作战档案", "help": "由于作战档案缺少维护,开荒功能不一定能正常使用,如果发现Alas运行异常,请手动完成开荒后使用自律寻敌功能" }, - "CoalitionSp": { - "name": "光影风尚-拍摄进行时SP", - "help": "" - }, "EventA": { "name": "活动每日A图", "help": "" @@ -122,6 +118,10 @@ "name": "共斗活动每日", "help": "" }, + "CoalitionSp": { + "name": "光影风尚-拍摄进行时SP", + "help": "" + }, "Commission": { "name": "委托", "help": "" @@ -279,234 +279,6 @@ "help": "" } }, - "Oil": { - "_info": { - "name": "Oil._info.name", - "help": "Oil._info.help" - }, - "Value": { - "name": "Oil.Value.name", - "help": "Oil.Value.help" - }, - "Limit": { - "name": "Oil.Limit.name", - "help": "Oil.Limit.help" - }, - "Color": { - "name": "Oil.Color.name", - "help": "Oil.Color.help" - }, - "Record": { - "name": "Oil.Record.name", - "help": "Oil.Record.help" - } - }, - "Coin": { - "_info": { - "name": "Coin._info.name", - "help": "Coin._info.help" - }, - "Value": { - "name": "Coin.Value.name", - "help": "Coin.Value.help" - }, - "Limit": { - "name": "Coin.Limit.name", - "help": "Coin.Limit.help" - }, - "Color": { - "name": "Coin.Color.name", - "help": "Coin.Color.help" - }, - "Record": { - "name": "Coin.Record.name", - "help": "Coin.Record.help" - } - }, - "Gem": { - "_info": { - "name": "Gem._info.name", - "help": "Gem._info.help" - }, - "Value": { - "name": "Gem.Value.name", - "help": "Gem.Value.help" - }, - "Color": { - "name": "Gem.Color.name", - "help": "Gem.Color.help" - }, - "Record": { - "name": "Gem.Record.name", - "help": "Gem.Record.help" - } - }, - "Pt": { - "_info": { - "name": "Pt._info.name", - "help": "Pt._info.help" - }, - "Value": { - "name": "Pt.Value.name", - "help": "Pt.Value.help" - }, - "Color": { - "name": "Pt.Color.name", - "help": "Pt.Color.help" - }, - "Record": { - "name": "Pt.Record.name", - "help": "Pt.Record.help" - } - }, - "YellowCoin": { - "_info": { - "name": "YellowCoin._info.name", - "help": "YellowCoin._info.help" - }, - "Value": { - "name": "YellowCoin.Value.name", - "help": "YellowCoin.Value.help" - }, - "Color": { - "name": "YellowCoin.Color.name", - "help": "YellowCoin.Color.help" - }, - "Record": { - "name": "YellowCoin.Record.name", - "help": "YellowCoin.Record.help" - } - }, - "PurpleCoin": { - "_info": { - "name": "PurpleCoin._info.name", - "help": "PurpleCoin._info.help" - }, - "Value": { - "name": "PurpleCoin.Value.name", - "help": "PurpleCoin.Value.help" - }, - "Color": { - "name": "PurpleCoin.Color.name", - "help": "PurpleCoin.Color.help" - }, - "Record": { - "name": "PurpleCoin.Record.name", - "help": "PurpleCoin.Record.help" - } - }, - "ActionPoint": { - "_info": { - "name": "ActionPoint._info.name", - "help": "ActionPoint._info.help" - }, - "Value": { - "name": "ActionPoint.Value.name", - "help": "ActionPoint.Value.help" - }, - "Total": { - "name": "ActionPoint.Total.name", - "help": "ActionPoint.Total.help" - }, - "Color": { - "name": "ActionPoint.Color.name", - "help": "ActionPoint.Color.help" - }, - "Record": { - "name": "ActionPoint.Record.name", - "help": "ActionPoint.Record.help" - } - }, - "Merit": { - "_info": { - "name": "Merit._info.name", - "help": "Merit._info.help" - }, - "Value": { - "name": "Merit.Value.name", - "help": "Merit.Value.help" - }, - "Color": { - "name": "Merit.Color.name", - "help": "Merit.Color.help" - }, - "Record": { - "name": "Merit.Record.name", - "help": "Merit.Record.help" - } - }, - "Cube": { - "_info": { - "name": "Cube._info.name", - "help": "Cube._info.help" - }, - "Value": { - "name": "Cube.Value.name", - "help": "Cube.Value.help" - }, - "Color": { - "name": "Cube.Color.name", - "help": "Cube.Color.help" - }, - "Record": { - "name": "Cube.Record.name", - "help": "Cube.Record.help" - } - }, - "Core": { - "_info": { - "name": "Core._info.name", - "help": "Core._info.help" - }, - "Value": { - "name": "Core.Value.name", - "help": "Core.Value.help" - }, - "Color": { - "name": "Core.Color.name", - "help": "Core.Color.help" - }, - "Record": { - "name": "Core.Record.name", - "help": "Core.Record.help" - } - }, - "Medal": { - "_info": { - "name": "Medal._info.name", - "help": "Medal._info.help" - }, - "Value": { - "name": "Medal.Value.name", - "help": "Medal.Value.help" - }, - "Color": { - "name": "Medal.Color.name", - "help": "Medal.Color.help" - }, - "Record": { - "name": "Medal.Record.name", - "help": "Medal.Record.help" - } - }, - "GuildCoin": { - "_info": { - "name": "GuildCoin._info.name", - "help": "GuildCoin._info.help" - }, - "Value": { - "name": "GuildCoin.Value.name", - "help": "GuildCoin.Value.help" - }, - "Color": { - "name": "GuildCoin.Color.name", - "help": "GuildCoin.Color.help" - }, - "Record": { - "name": "GuildCoin.Record.name", - "help": "GuildCoin.Record.help" - } - }, "Scheduler": { "_info": { "name": "任务设置", @@ -606,6 +378,7 @@ "cn_android-25": "[国服] 水仙行动", "cn_android-26": "[国服] 冬月计划", "cn_android-27": "[国服] 长弓计划", + "cn_android-28": "[国服] 裁决协议", "cn_ios-0": "[国服] 夏威夷", "cn_ios-1": "[国服] 珊瑚海", "cn_ios-2": "[国服] 中途岛", @@ -731,10 +504,6 @@ "ScreenshotLength": { "name": "出错时,保留最后 X 张截图", "help": "" - }, - "RestartOnUnknownPage": { - "name": "遇到未知页面时重启", - "help": "当遇到未知页面且服务器在线时,重启游戏而不是停止(此功能依赖已启用\"启用异常处理\")。连续3次任务失败后,ALAS仍会请求人工接管。" } }, "Optimization": { @@ -997,6 +766,7 @@ "event_20250912_cn": "起舞于天原之上", "event_20251023_cn": "飓风与自由群岛", "event_20251218_cn": "响彻于天穹之音", + "event_20260226_cn": "春满客栈Online", "raid_20200624": "复刻特别演习埃塞克斯级", "raid_20210708": "复刻穿越彼方的水线", "raid_20220127": "演习神秘事件调查", @@ -1005,8 +775,9 @@ "raid_20230118": "冬日的寻路人", "raid_20230629": "绿洲往事", "raid_20240130": "寰昌宇定家事忙", - "raid_20240328": "从零开始的魔王讨伐之旅", + "raid_20240328": "复刻从零开始的魔王讨伐之旅", "raid_20250116": "华裳巧展喜事长", + "raid_20260212": "春宴怀玉香满庭", "war_archives_20180607_cn": "档案 墨染的钢铁之花", "war_archives_20180726_cn": "档案 光与影的鸢尾之华", "war_archives_20181020_en": "档案 努力希望和计划", @@ -1049,6 +820,7 @@ "war_archives_20220728_cn": "档案 雄鹰的叙事歌", "war_archives_20220915_cn": "档案 紫绛槿岚", "war_archives_20221222_cn": "档案 定向折叠", + "war_archives_20230223_cn": "档案 湮烬尘墟", "war_archives_20231026_cn": "档案 飓风与青春之泉" }, "Mode": { @@ -1504,7 +1276,7 @@ "easy": "简单", "normal": "普通", "hard": "困难", - "ex": "ex" + "ex": "EX" }, "UseTicket": { "name": "使用演习券", @@ -2117,7 +1889,8 @@ "help": "", "2": "二期科研", "3": "三期科研", - "4": "四期科研" + "4": "四期科研", + "5": "五期科研" }, "ShipIndex": { "name": "舰船序号", @@ -2151,7 +1924,8 @@ "2": "二期科研", "3": "三期科研", "4": "四期科研", - "5": "五期科研" + "5": "五期科研", + "6": "六期科研" }, "ShipIndex": { "name": "舰船序号", @@ -2870,8 +2644,6 @@ "Stop": "停止", "ScrollON": "自动滚动 开", "ScrollOFF": "自动滚动 关", - "DashboardON": "折叠", - "DashboardOFF": "展开", "ClearLog": "清空日志", "Setting": "设置", "CheckUpdate": "检查更新", @@ -2904,26 +2676,6 @@ }, "Overview": { "Scheduler": "调度器", - "Dashboard": "Gui.Overview.Dashboard", - "SecondsAgo": "秒前", - "MinutesAgo": "分钟前", - "HoursAgo": "小时前", - "DaysAgo": "天前", - "MonthsAgo": "月前", - "YearsAgo": "年前", - "NoData": "无数据", - "Oil": "石油", - "Coin": "物资", - "Gem": "钻石", - "Cube": "魔方", - "Pt": "活动PT", - "YellowCoin": "大世界黄币", - "PurpleCoin": "大世界紫币", - "ActionPoint": "行动力", - "Merit": "功勋", - "Medal": "勋章", - "Core": "核心数据", - "GuildCoin": "舰队币", "Log": "日志", "Running": "运行中", "Pending": "队列中", @@ -2994,4 +2746,4 @@ "ChooseFile": "选择文件" } } -} +} \ No newline at end of file diff --git a/alas_wrapped/module/config/i18n/zh-TW.json b/alas_wrapped/module/config/i18n/zh-TW.json index 3c3af15c5e..7687ff8aaf 100644 --- a/alas_wrapped/module/config/i18n/zh-TW.json +++ b/alas_wrapped/module/config/i18n/zh-TW.json @@ -66,10 +66,6 @@ "name": "活動通用", "help": "" }, - "Coalition": { - "name": "光影風尚-拍攝進行時", - "help": "" - }, "Event": { "name": "活動圖", "help": "" @@ -86,6 +82,10 @@ "name": "深谷来信", "help": "" }, + "Coalition": { + "name": "光影風尚-拍攝進行時", + "help": "" + }, "MaritimeEscort": { "name": "商船護航", "help": "" @@ -94,10 +94,6 @@ "name": "作戰檔案", "help": "由於作戰檔案缺少維護,開荒功能不一定能正常使用,如果發現Alas運行異常,請手動完成開荒後使用自律尋敵功能" }, - "CoalitionSp": { - "name": "光影風尚-拍攝進行時SP", - "help": "" - }, "EventA": { "name": "活動每日A圖", "help": "" @@ -122,6 +118,10 @@ "name": "共鬥活動每日", "help": "" }, + "CoalitionSp": { + "name": "光影風尚-拍攝進行時SP", + "help": "" + }, "Commission": { "name": "委託", "help": "" @@ -279,234 +279,6 @@ "help": "" } }, - "Oil": { - "_info": { - "name": "Oil._info.name", - "help": "Oil._info.help" - }, - "Value": { - "name": "Oil.Value.name", - "help": "Oil.Value.help" - }, - "Limit": { - "name": "Oil.Limit.name", - "help": "Oil.Limit.help" - }, - "Color": { - "name": "Oil.Color.name", - "help": "Oil.Color.help" - }, - "Record": { - "name": "Oil.Record.name", - "help": "Oil.Record.help" - } - }, - "Coin": { - "_info": { - "name": "Coin._info.name", - "help": "Coin._info.help" - }, - "Value": { - "name": "Coin.Value.name", - "help": "Coin.Value.help" - }, - "Limit": { - "name": "Coin.Limit.name", - "help": "Coin.Limit.help" - }, - "Color": { - "name": "Coin.Color.name", - "help": "Coin.Color.help" - }, - "Record": { - "name": "Coin.Record.name", - "help": "Coin.Record.help" - } - }, - "Gem": { - "_info": { - "name": "Gem._info.name", - "help": "Gem._info.help" - }, - "Value": { - "name": "Gem.Value.name", - "help": "Gem.Value.help" - }, - "Color": { - "name": "Gem.Color.name", - "help": "Gem.Color.help" - }, - "Record": { - "name": "Gem.Record.name", - "help": "Gem.Record.help" - } - }, - "Pt": { - "_info": { - "name": "Pt._info.name", - "help": "Pt._info.help" - }, - "Value": { - "name": "Pt.Value.name", - "help": "Pt.Value.help" - }, - "Color": { - "name": "Pt.Color.name", - "help": "Pt.Color.help" - }, - "Record": { - "name": "Pt.Record.name", - "help": "Pt.Record.help" - } - }, - "YellowCoin": { - "_info": { - "name": "YellowCoin._info.name", - "help": "YellowCoin._info.help" - }, - "Value": { - "name": "YellowCoin.Value.name", - "help": "YellowCoin.Value.help" - }, - "Color": { - "name": "YellowCoin.Color.name", - "help": "YellowCoin.Color.help" - }, - "Record": { - "name": "YellowCoin.Record.name", - "help": "YellowCoin.Record.help" - } - }, - "PurpleCoin": { - "_info": { - "name": "PurpleCoin._info.name", - "help": "PurpleCoin._info.help" - }, - "Value": { - "name": "PurpleCoin.Value.name", - "help": "PurpleCoin.Value.help" - }, - "Color": { - "name": "PurpleCoin.Color.name", - "help": "PurpleCoin.Color.help" - }, - "Record": { - "name": "PurpleCoin.Record.name", - "help": "PurpleCoin.Record.help" - } - }, - "ActionPoint": { - "_info": { - "name": "ActionPoint._info.name", - "help": "ActionPoint._info.help" - }, - "Value": { - "name": "ActionPoint.Value.name", - "help": "ActionPoint.Value.help" - }, - "Total": { - "name": "ActionPoint.Total.name", - "help": "ActionPoint.Total.help" - }, - "Color": { - "name": "ActionPoint.Color.name", - "help": "ActionPoint.Color.help" - }, - "Record": { - "name": "ActionPoint.Record.name", - "help": "ActionPoint.Record.help" - } - }, - "Merit": { - "_info": { - "name": "Merit._info.name", - "help": "Merit._info.help" - }, - "Value": { - "name": "Merit.Value.name", - "help": "Merit.Value.help" - }, - "Color": { - "name": "Merit.Color.name", - "help": "Merit.Color.help" - }, - "Record": { - "name": "Merit.Record.name", - "help": "Merit.Record.help" - } - }, - "Cube": { - "_info": { - "name": "Cube._info.name", - "help": "Cube._info.help" - }, - "Value": { - "name": "Cube.Value.name", - "help": "Cube.Value.help" - }, - "Color": { - "name": "Cube.Color.name", - "help": "Cube.Color.help" - }, - "Record": { - "name": "Cube.Record.name", - "help": "Cube.Record.help" - } - }, - "Core": { - "_info": { - "name": "Core._info.name", - "help": "Core._info.help" - }, - "Value": { - "name": "Core.Value.name", - "help": "Core.Value.help" - }, - "Color": { - "name": "Core.Color.name", - "help": "Core.Color.help" - }, - "Record": { - "name": "Core.Record.name", - "help": "Core.Record.help" - } - }, - "Medal": { - "_info": { - "name": "Medal._info.name", - "help": "Medal._info.help" - }, - "Value": { - "name": "Medal.Value.name", - "help": "Medal.Value.help" - }, - "Color": { - "name": "Medal.Color.name", - "help": "Medal.Color.help" - }, - "Record": { - "name": "Medal.Record.name", - "help": "Medal.Record.help" - } - }, - "GuildCoin": { - "_info": { - "name": "GuildCoin._info.name", - "help": "GuildCoin._info.help" - }, - "Value": { - "name": "GuildCoin.Value.name", - "help": "GuildCoin.Value.help" - }, - "Color": { - "name": "GuildCoin.Color.name", - "help": "GuildCoin.Color.help" - }, - "Record": { - "name": "GuildCoin.Record.name", - "help": "GuildCoin.Record.help" - } - }, "Scheduler": { "_info": { "name": "任務設定", @@ -606,6 +378,7 @@ "cn_android-25": "[国服] 水仙行动", "cn_android-26": "[国服] 冬月计划", "cn_android-27": "[国服] 长弓计划", + "cn_android-28": "[国服] 裁决协议", "cn_ios-0": "[国服] 夏威夷", "cn_ios-1": "[国服] 珊瑚海", "cn_ios-2": "[国服] 中途岛", @@ -731,10 +504,6 @@ "ScreenshotLength": { "name": "出錯時,保留最後 X 張截圖", "help": "" - }, - "RestartOnUnknownPage": { - "name": "遇到未知頁面時重啟", - "help": "當遇到未知頁面且伺服器在線時,重啟遊戲而不是停止(此功能需搭配啟用「啟用異常處理」)。連續3次任務失敗後,ALAS仍會請求人工接管。" } }, "Optimization": { @@ -916,7 +685,7 @@ "coalition_20240627": "歡迎來到童心學院", "coalition_20250626": "迷彩都市的尋蹤者", "coalition_20251120": "DATE A LANE", - "coalition_20260122": "Light & Shadow Fashion Shoot!", + "coalition_20260122": "光影風尚-拍攝進行時", "event_20200227_cn": "Northern Overture", "event_20200312_cn": "斯圖爾特的硝煙", "event_20200326_cn": "Microlayer Medley", @@ -997,6 +766,7 @@ "event_20250912_cn": "起舞於天原之上", "event_20251023_cn": "颶風與自由群島", "event_20251218_cn": "響徹於天穹之音", + "event_20260226_cn": "Springtide Inn Online", "raid_20200624": "特別演習埃塞克斯級(復刻)", "raid_20210708": "復刻穿越彼方的水線", "raid_20220127": "演習神秘事件調查", @@ -1007,6 +777,7 @@ "raid_20240130": "寰昌宇定家事忙", "raid_20240328": "從零開始的魔王討伐之旅", "raid_20250116": "華裳巧展喜事長", + "raid_20260212": "春宴懷玉香滿庭", "war_archives_20180607_cn": "檔案 墨染的鋼鐵之花", "war_archives_20180726_cn": "檔案 光與影的鳶尾之華", "war_archives_20181020_en": "檔案 努力希望和計劃", @@ -1049,6 +820,7 @@ "war_archives_20220728_cn": "檔案 雄鷹的敘事歌", "war_archives_20220915_cn": "檔案 紫絳槿嵐", "war_archives_20221222_cn": "檔案 定向折疊", + "war_archives_20230223_cn": "檔案 湮燼塵墟", "war_archives_20231026_cn": "檔案 飓風與青春之泉" }, "Mode": { @@ -1504,7 +1276,7 @@ "easy": "簡單", "normal": "普通", "hard": "困難", - "ex": "ex" + "ex": "EX" }, "UseTicket": { "name": "使用演習券", @@ -2117,7 +1889,8 @@ "help": "", "2": "二期科研", "3": "三期科研", - "4": "四期科研" + "4": "四期科研", + "5": "五期科研" }, "ShipIndex": { "name": "艦船序號", @@ -2151,7 +1924,8 @@ "2": "二期科研", "3": "三期科研", "4": "四期科研", - "5": "五期科研" + "5": "五期科研", + "6": "六期科研" }, "ShipIndex": { "name": "艦船序號", @@ -2870,8 +2644,6 @@ "Stop": "停止", "ScrollON": "自動滾動 開", "ScrollOFF": "自動滾動 關", - "DashboardON": "開", - "DashboardOFF": "關", "ClearLog": "清空日誌", "Setting": "設定", "CheckUpdate": "檢查更新", @@ -2904,26 +2676,6 @@ }, "Overview": { "Scheduler": "調度器", - "Dashboard": "儀表盤", - "SecondsAgo": "秒前", - "MinutesAgo": "分鐘前", - "HoursAgo": "小時前", - "DaysAgo": "天前", - "MonthsAgo": "月前", - "YearsAgo": "年前", - "NoData": "無數據", - "Oil": "石油", - "Coin": "物資", - "Gem": "鑽石", - "Cube": "魔方", - "Pt": "活動PT", - "YellowCoin": "大世界黃幣", - "PurpleCoin": "大世界紫幣", - "ActionPoint": "行動力", - "Merit": "功勳", - "Medal": "勳章", - "Core": "覈心數據", - "GuildCoin": "艦隊幣", "Log": "日誌", "Running": "執行中", "Pending": "佇列中", diff --git a/alas_wrapped/module/config/server.py b/alas_wrapped/module/config/server.py index e91fa48ce8..3cf519518c 100644 --- a/alas_wrapped/module/config/server.py +++ b/alas_wrapped/module/config/server.py @@ -74,7 +74,7 @@ '小王冠行动', '波茨坦公告', '白色方案', '瓦尔基里行动', '曼哈顿计划', '八月风暴', '秋季旅行', '水星行动', '莱茵河卫兵', '北极光计划', '长戟计划', '暴雨行动', '水仙行动', '冬月计划', - '长弓计划' + '长弓计划', '裁决协议', ], 'cn_ios': [ '夏威夷', '珊瑚海', '中途岛', '铁底湾', '所罗门', '马里亚纳', diff --git a/alas_wrapped/module/config/utils.py b/alas_wrapped/module/config/utils.py index 2cde34af87..f26443fd99 100644 --- a/alas_wrapped/module/config/utils.py +++ b/alas_wrapped/module/config/utils.py @@ -77,6 +77,7 @@ def read_file(file): Returns: dict, list: """ + print(f'read: {file}') if file.endswith('.json'): content = atomic_read_bytes(file) if not content: @@ -91,6 +92,7 @@ def read_file(file): data = {} return data else: + print(f'Unsupported config file extension: {file}') return {} @@ -102,6 +104,7 @@ def write_file(file, data): file (str): data (dict, list): """ + print(f'write: {file}') if file.endswith('.json'): content = json.dumps(data, indent=2, ensure_ascii=False, sort_keys=False, default=str) atomic_write(file, content) @@ -114,7 +117,7 @@ def write_file(file, data): data, default_flow_style=False, encoding='utf-8', allow_unicode=True, sort_keys=False) atomic_write(file, content) else: - pass + print(f'Unsupported config file extension: {file}') def iter_folder(folder, is_dir=False, ext=None): @@ -539,47 +542,5 @@ def type_to_str(typ): return str(typ) -def time_delta(_timedelta): - """ - Output the delta between two times - - Args: - _timedelta : datetime.timedelta - - Returns: - dict : { - 'Y' : int, - 'M' : int, - 'D' : int, - 'h' : int, - 'm' : int, - 's' : int - } - """ - _time_delta = abs(_timedelta.total_seconds()) - d_base = datetime(2010, 1, 1, 0, 0, 0) - d = datetime(2010, 1, 1, 0, 0, 0)-_timedelta - _time_dict = { - 'Y': d.year - d_base.year, - 'M': d.month - d_base.month, - 'D': d.day - d_base.day, - 'h': d.hour - d_base.hour, - 'm': d.minute - d_base.minute, - 's': d.second - d_base.second - } - # _sec ={ - # 'Y': 365*24*60*60, - # 'M': 30*24*60*60, - # 'D': 24*60*60, - # 'h': 60*60, - # 'm': 60, - # 's': 1 - # } - # for _key in _time_dict: - # _time_dict[_key] = int(_time_delta//_sec[_key]) - # _time_delta = _time_delta%_sec[_key] - return _time_dict - - if __name__ == '__main__': get_os_reset_remain() diff --git a/alas_wrapped/module/device/connection.py b/alas_wrapped/module/device/connection.py index 51ecbdcb00..fe5f140123 100644 --- a/alas_wrapped/module/device/connection.py +++ b/alas_wrapped/module/device/connection.py @@ -9,8 +9,6 @@ import uiautomator2 as u2 from adbutils import AdbClient, AdbDevice, AdbTimeout, ForwardItem, ReverseItem -if not hasattr(AdbClient, '_connect') and hasattr(AdbClient, 'make_connection'): - AdbClient._connect = AdbClient.make_connection from adbutils.errors import AdbError from module.base.decorator import Config, cached_property, del_cached_property, run_once diff --git a/alas_wrapped/module/device/connection_attr.py b/alas_wrapped/module/device/connection_attr.py index 5f053c97fd..4cb47065e8 100644 --- a/alas_wrapped/module/device/connection_attr.py +++ b/alas_wrapped/module/device/connection_attr.py @@ -71,28 +71,39 @@ def __init__(self, config): self.config.DEVICE_OVER_HTTP = self.is_over_http @staticmethod - def revise_serial(serial): - serial = serial.replace(' ', '') + def revise_serial(serial: str): + """ + Tons of fool-proof fixes to handle manual serial input + To load a serial: + serial = SerialStr.revise_serial(serial) + """ + serial = serial.strip().replace(' ', '') # 127。0。0。1:5555 serial = serial.replace('。', '.').replace(',', '.').replace(',', '.').replace(':', ':') # 127.0.0.1.5555 serial = serial.replace('127.0.0.1.', '127.0.0.1:') - # Mumu12 5.0 shows double serials, some people may just copy-paste it - # 5555,16384 -> replaced to 5555.16384 + # 5555,16384 (actually "5555.16384" because replace(',', '.')) if '.' in serial: left, _, right = serial.partition('.') - if left.startswith('55') and right.startswith('16'): - serial = right + try: + left = int(left) + right = int(right) + if 5500 < left < 6000 and 16300 < right < 20000: + serial = str(right) + except ValueError: + pass # 16384 - try: - port = int(serial) - if 1000 < port < 65536: - serial = f'127.0.0.1:{port}' - except ValueError: - pass + if serial.isdigit(): + try: + port = int(serial) + if 1000 < port < 65536: + serial = f'127.0.0.1:{port}' + except ValueError: + pass # 夜神模拟器 127.0.0.1:62001 # MuMu模拟器12127.0.0.1:16384 if '模拟' in serial: + import re res = re.search(r'(127\.\d+\.\d+\.\d+:\d+)', serial) if res: serial = res.group(1) @@ -348,7 +359,7 @@ def u2(self) -> u2.Device: device = u2.connect(self.serial) # Stay alive - # device.set_new_command_timeout(604800) # Removed - method dropped in uiautomator2 >= 3.x + device.set_new_command_timeout(604800) - logger.attr('u2.Device', f'Device(serial={self.serial})') + logger.attr('u2.Device', f'Device(atx_agent_url={device._get_atx_agent_url()})') return device diff --git a/alas_wrapped/module/device/device.py b/alas_wrapped/module/device/device.py index 6a14cd8f63..a1519df472 100644 --- a/alas_wrapped/module/device/device.py +++ b/alas_wrapped/module/device/device.py @@ -70,31 +70,23 @@ class Device(Screenshot, Control, AppControl): stuck_long_wait_list = ['BATTLE_STATUS_S', 'PAUSE', 'LOGIN_CHECK'] def __init__(self, *args, **kwargs): - # ConnectionAttr.__init__ (inside super()) initializes self.config. - # Read retry policy from constructor kwargs first to avoid pre-init access. - cfg = kwargs.get('config') - handle_error = bool(getattr(cfg, 'Error_HandleError', False)) - max_retry = 30 if handle_error else 4 - for trial in range(1, max_retry + 1): + for trial in range(4): try: super().__init__(*args, **kwargs) break except EmulatorNotRunningError: - if getattr(self, 'emulator_instance', None) is None: + if trial >= 3: + logger.critical('Failed to start emulator after 3 trial') + raise RequestHumanTakeover + # Try to start emulator + if self.emulator_instance is not None: + self.emulator_start() + else: logger.critical( - f'No emulator with serial "{getattr(cfg, "Emulator_Serial", "unknown")}" found, ' + f'No emulator with serial "{self.config.Emulator_Serial}" found, ' f'please set a correct serial' ) raise RequestHumanTakeover - if trial >= max_retry: - logger.critical(f'Failed to start emulator after {trial} trial') - raise RequestHumanTakeover - if handle_error and trial >= 4: - logger.warning(f'Failed to start emulator after {trial} trial, keep retrying') - # Try to start emulator - self.emulator_start() - if handle_error: - self.sleep(5) # Auto-fill emulator info if IS_WINDOWS and self.config.EmulatorInfo_Emulator == 'auto': diff --git a/alas_wrapped/module/device/method/minitouch.py b/alas_wrapped/module/device/method/minitouch.py index 06a6ed3a7e..d97c2ca884 100644 --- a/alas_wrapped/module/device/method/minitouch.py +++ b/alas_wrapped/module/device/method/minitouch.py @@ -8,11 +8,7 @@ import websockets from adbutils.errors import AdbError -try: - from uiautomator2 import _Service -except ImportError: - class _Service: - pass +from uiautomator2 import _Service from module.base.decorator import Config, cached_property, del_cached_property, has_cached_property from module.base.timer import Timer diff --git a/alas_wrapped/module/device/method/uiautomator_2.py b/alas_wrapped/module/device/method/uiautomator_2.py index e0db47c7f6..fb5274356d 100644 --- a/alas_wrapped/module/device/method/uiautomator_2.py +++ b/alas_wrapped/module/device/method/uiautomator_2.py @@ -119,10 +119,14 @@ class ShellBackgroundResponse: class Uiautomator2(Connection): @retry def screenshot_uiautomator2(self): - # uiautomator2 >= 3.x dropped format='raw'; use 'opencv' to get BGR ndarray directly - image = self.u2.screenshot(format='opencv') + image = self.u2.screenshot(format='raw') + image = np.frombuffer(image, np.uint8) if image is None: - raise ImageTruncated('Empty image after screenshot') + raise ImageTruncated('Empty image after reading from buffer') + + image = cv2.imdecode(image, cv2.IMREAD_COLOR) + if image is None: + raise ImageTruncated('Empty image after cv2.imdecode') cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image) if image is None: diff --git a/alas_wrapped/module/device/method/utils.py b/alas_wrapped/module/device/method/utils.py index f9e6f2cf4b..6c0dd85367 100644 --- a/alas_wrapped/module/device/method/utils.py +++ b/alas_wrapped/module/device/method/utils.py @@ -54,8 +54,7 @@ def shell(self, RETRY_DELAY = 3 # Patch uiautomator2 appdir -if hasattr(u2, 'init'): - u2.init.appdir = os.path.dirname(uiautomator2cache.__file__) +u2.init.appdir = os.path.dirname(uiautomator2cache.__file__) # Patch uiautomator2 logger u2_logger = u2.logger @@ -71,40 +70,39 @@ def setup_logger(*args, **kwargs): u2.setup_logger = setup_logger -if hasattr(u2, 'init'): - u2.init.setup_logger = setup_logger - - - # Patch Initer - class PatchedIniter(u2.init.Initer): - @property - def atx_agent_url(self): - files = { - 'armeabi-v7a': 'atx-agent_{v}_linux_armv7.tar.gz', - # 'arm64-v8a': 'atx-agent_{v}_linux_armv7.tar.gz', - 'arm64-v8a': 'atx-agent_{v}_linux_arm64.tar.gz', - 'armeabi': 'atx-agent_{v}_linux_armv6.tar.gz', - 'x86': 'atx-agent_{v}_linux_386.tar.gz', - 'x86_64': 'atx-agent_{v}_linux_386.tar.gz', - } - name = None - for abi in self.abis: - name = files.get(abi) - if name: - break - if not name: - raise Exception( - "arch(%s) need to be supported yet, please report an issue in github" - % self.abis) - return u2.init.GITHUB_BASEURL + '/atx-agent/releases/download/%s/%s' % ( - u2.version.__atx_agent_version__, name.format(v=u2.version.__atx_agent_version__)) - - @property - def minicap_urls(self): - return [] - - - u2.init.Initer = PatchedIniter +u2.init.setup_logger = setup_logger + + +# Patch Initer +class PatchedIniter(u2.init.Initer): + @property + def atx_agent_url(self): + files = { + 'armeabi-v7a': 'atx-agent_{v}_linux_armv7.tar.gz', + # 'arm64-v8a': 'atx-agent_{v}_linux_armv7.tar.gz', + 'arm64-v8a': 'atx-agent_{v}_linux_arm64.tar.gz', + 'armeabi': 'atx-agent_{v}_linux_armv6.tar.gz', + 'x86': 'atx-agent_{v}_linux_386.tar.gz', + 'x86_64': 'atx-agent_{v}_linux_386.tar.gz', + } + name = None + for abi in self.abis: + name = files.get(abi) + if name: + break + if not name: + raise Exception( + "arch(%s) need to be supported yet, please report an issue in github" + % self.abis) + return u2.init.GITHUB_BASEURL + '/atx-agent/releases/download/%s/%s' % ( + u2.version.__atx_agent_version__, name.format(v=u2.version.__atx_agent_version__)) + + @property + def minicap_urls(self): + return [] + + +u2.init.Initer = PatchedIniter def is_port_using(port_num): @@ -281,19 +279,19 @@ def get_serial_pair(serial): serial (str): Returns: - str, str: `127.0.0.1:5555+{X}` and `emulator-5554+{X}`, 0 <= X <= 32 + tuple[Optional[str], Optional[str]]: `127.0.0.1:5555+{X}` and `emulator-5554+{X}`, 0 <= X <= 32 """ if serial.startswith('127.0.0.1:'): try: port = int(serial[10:]) - if 5555 <= port <= 5555 + 32: + if 5555 <= port <= 5555 + 64: return f'127.0.0.1:{port}', f'emulator-{port - 1}' except (ValueError, IndexError): pass if serial.startswith('emulator-'): try: port = int(serial[9:]) - if 5554 <= port <= 5554 + 32: + if 5554 <= port <= 5554 + 64: return f'127.0.0.1:{port + 1}', f'emulator-{port}' except (ValueError, IndexError): pass @@ -397,19 +395,16 @@ def remove_shell_warning(s): return s -if hasattr(u2, 'init'): - class IniterNoMinicap(u2.init.Initer): - @property - def minicap_urls(self): - """ - Don't install minicap on emulators, return empty urls. +class IniterNoMinicap(u2.init.Initer): + @property + def minicap_urls(self): + """ + Don't install minicap on emulators, return empty urls. - binary from https://github.com/openatx/stf-binaries - only got abi: armeabi-v7a and arm64-v8a - """ - return [] -else: - IniterNoMinicap = None + binary from https://github.com/openatx/stf-binaries + only got abi: armeabi-v7a and arm64-v8a + """ + return [] class Device(u2.Device): @@ -421,8 +416,7 @@ def show_float_window(self, show=True): # Monkey patch -if hasattr(u2, 'init'): - u2.init.Initer = IniterNoMinicap +u2.init.Initer = IniterNoMinicap u2.Device = Device diff --git a/alas_wrapped/module/device/platform/platform_windows.py b/alas_wrapped/module/device/platform/platform_windows.py index 6334402b90..c7fbeb4971 100644 --- a/alas_wrapped/module/device/platform/platform_windows.py +++ b/alas_wrapped/module/device/platform/platform_windows.py @@ -310,9 +310,8 @@ def emulator_start(self): logger.hr('Emulator start', level=1) for _ in range(3): # Stop - stopped = self._emulator_function_wrapper(self._emulator_stop) - if not stopped: - logger.warning('Emulator stop failed, try start anyway') + if not self._emulator_function_wrapper(self._emulator_stop): + return False # Start if self._emulator_function_wrapper(self._emulator_start): # Success @@ -320,12 +319,10 @@ def emulator_start(self): return True else: # Failed to start, stop and start again - if stopped and self._emulator_function_wrapper(self._emulator_stop): + if self._emulator_function_wrapper(self._emulator_stop): continue - logger.warning( - 'Emulator start failed; stop before retry also failed, ' - 'continuing without clean state' - ) + else: + return False logger.error('Failed to start emulator 3 times, stopped') return False @@ -350,4 +347,4 @@ def emulator_stop(self): if __name__ == '__main__': self = PlatformWindows('alas') d = self.emulator_instance - print(d) + print(d) \ No newline at end of file diff --git a/alas_wrapped/module/exception.py b/alas_wrapped/module/exception.py index 0eb33fcea7..e0d3ecf392 100644 --- a/alas_wrapped/module/exception.py +++ b/alas_wrapped/module/exception.py @@ -41,10 +41,6 @@ class GameBugError(Exception): pass -class GameTransportError(Exception): - """Reserved for transport-layer failures (currently not raised directly).""" - - class GameTooManyClickError(Exception): pass diff --git a/alas_wrapped/module/exercise/exercise.py b/alas_wrapped/module/exercise/exercise.py index ebdbd3033e..6b78f76e4c 100644 --- a/alas_wrapped/module/exercise/exercise.py +++ b/alas_wrapped/module/exercise/exercise.py @@ -93,21 +93,11 @@ def _opponent_fleet_check_all(self): super()._opponent_fleet_check_all() def _opponent_sort(self, method=None): - """ - Sort opponents by selection strategy. - - Important: In Azur Lane's Exercise mode, opponents are ALWAYS sorted by rank from left to right. - - Opponent 0 (leftmost) = Highest rank player = Maximum merit points/rewards - - Opponent 3 (rightmost) = Lowest rank player = Minimum merit points/rewards - - Using 'leftmost' mode ensures you always fight the best opponent for maximum rewards. - """ if method is None: method = self.config.Exercise_OpponentChooseMode if method != 'leftmost': return super()._opponent_sort(method=method) else: - # Fight opponents left-to-right: highest rank first return [0, 1, 2, 3] def _exercise_once(self): diff --git a/alas_wrapped/module/exercise/hp_daemon.py b/alas_wrapped/module/exercise/hp_daemon.py index e79bce505d..321a88d24f 100644 --- a/alas_wrapped/module/exercise/hp_daemon.py +++ b/alas_wrapped/module/exercise/hp_daemon.py @@ -76,6 +76,7 @@ def _at_low_hp(self, image, pause=PAUSE): PAUSE_Ninja, PAUSE_ShadowPuppetry, PAUSE_MaidCafe, + PAUSE_Ancient, ]: self.attacker_hp = self._calculate_hp(image, area=ATTACKER_HP_AREA_New.area, reverse=True) self.defender_hp = self._calculate_hp(image, area=DEFENDER_HP_AREA_New.area, reverse=True) diff --git a/alas_wrapped/module/exercise/opponent.py b/alas_wrapped/module/exercise/opponent.py index e6aea7413a..22379def51 100644 --- a/alas_wrapped/module/exercise/opponent.py +++ b/alas_wrapped/module/exercise/opponent.py @@ -8,18 +8,6 @@ from module.ui.assets import BACK_ARROW from module.ui.ui import UI -""" -Exercise Opponent Selection - -In Azur Lane's Exercise/PvP mode: -- Opponents are ALWAYS displayed sorted by rank (left = highest, right = lowest) -- Opponent 0 (leftmost): Highest ranked player, yields maximum merit points -- Opponent 3 (rightmost): Lowest ranked player, yields minimum merit points -- Defeating higher-ranked opponents gives better rewards and more rank points - -Grid layout: [Opponent 0] [Opponent 1] [Opponent 2] [Opponent 3] - (Best) (Good) (Medium) (Weakest) -""" OPPONENT = ButtonGrid(origin=(104, 77), delta=(244, 0), button_shape=(212, 304), grid_shape=(4, 1)) # Mode 'easiest' constants @@ -86,12 +74,6 @@ def get_power(self, image): def get_priority(self, method="max_exp"): """ - Calculate opponent priority for selection strategy. - - Note: This is only used for 'max_exp' and 'easiest' modes. - For 'leftmost' mode, opponents are fought in order [0,1,2,3] without priority calculation, - which is optimal since the game always sorts opponents by rank (leftmost = highest rank). - Args: method: EXERCISE_CHOOSE_MODE @@ -105,7 +87,6 @@ def get_priority(self, method="max_exp"): avg_team_pwr = np.sum(self.power) / team_pwr_div priority = level - avg_team_pwr else: - # max_exp mode: prioritize by total level (approximates difficulty/rewards) priority = np.sum(self.level) / 6 return priority diff --git a/alas_wrapped/module/gacha/gacha_reward.py b/alas_wrapped/module/gacha/gacha_reward.py index ba8be1cc93..41c1af129c 100644 --- a/alas_wrapped/module/gacha/gacha_reward.py +++ b/alas_wrapped/module/gacha/gacha_reward.py @@ -8,7 +8,6 @@ from module.logger import logger from module.ocr.ocr import Digit from module.retire.retirement import Retirement -from module.log_res.log_res import LogRes RECORD_GACHA_OPTION = ('RewardRecord', 'gacha') RECORD_GACHA_SINCE = (0,) @@ -126,8 +125,6 @@ def gacha_calculate(self, target_count, gold_cost, cube_cost): logger.info(f'Able to submit up to {target_count} build orders') self.build_coin_count -= gold_total self.build_cube_count -= cube_total - LogRes(self.config).Cube = self.build_cube_count - self.config.update() return target_count def gacha_goto_pool(self, target_pool): @@ -325,9 +322,6 @@ def gacha_run(self): buy[0] = self.build_ticket_count # Calculate rolls allowed based on configurations and resources buy[1] = self.gacha_calculate(self.config.Gacha_Amount - self.build_ticket_count, gold_cost, cube_cost) - else: - LogRes(self.config).Cube = self.build_cube_count - self.config.update() # Submit 'buy_count' and execute if capable # Cannot use handle_popup_confirm, this window diff --git a/alas_wrapped/module/handler/auto_search.py b/alas_wrapped/module/handler/auto_search.py index d5d0987911..48e67dfbe0 100644 --- a/alas_wrapped/module/handler/auto_search.py +++ b/alas_wrapped/module/handler/auto_search.py @@ -2,6 +2,7 @@ from module.base.button import ButtonGrid from module.base.decorator import Config +from module.base.timer import Timer from module.handler.assets import * from module.handler.enemy_searching import EnemySearchingHandler from module.logger import logger @@ -49,21 +50,14 @@ def _fleet_sidebar(self): origin=(1185, 155 + offset), delta=(0, 111), button_shape=(53, 104), grid_shape=(1, 3), name='FLEET_SIDEBAR') - def _fleet_preparation_sidebar_click(self, index): + def _fleet_preparation_get(self): """ - Args: - index (int): + Returns: + int: 1 for formation 2 for meowfficers 3 for auto search setting - - Returns: - bool: If changed. """ - if index <= 0 or index > 3: - logger.warning(f'Sidebar index cannot be clicked, {index}, limit to 1 through 5 only') - return False - current = 0 total = 0 sidebar = self._fleet_sidebar() @@ -81,46 +75,38 @@ def _fleet_preparation_sidebar_click(self, index): if not current: logger.warning('No fleet sidebar active.') logger.attr('Fleet_sidebar', f'{current}/{total}') - if current == index: - return False + return current - self.device.click(sidebar[0, index - 1]) - return True - - def fleet_preparation_sidebar_ensure(self, index, skip_first_screenshot=True): + def fleet_preparation_sidebar_ensure(self, index): """ Args: index (int): 1 for formation 2 for meowfficers 3 for auto search setting - skip_first_screenshot (bool): - Returns: - bool: whether sidebar could be ensured - at most 3 attempts are made before - return False otherwise True + Returns: + bool: whether sidebar could be ensured + at most 3 attempts are made before + return False otherwise True """ if index <= 0 or index > 5: logger.warning(f'Sidebar index cannot be ensured, {index}, limit 1 through 5 only') return False - counter = 0 - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - if self._fleet_preparation_sidebar_click(index): - if counter >= 2: - logger.warning('Sidebar could not be ensured') - return False - counter += 1 - self.device.sleep((0.3, 0.5)) - continue - else: + interval = Timer(1, count=2) + sidebar = self._fleet_sidebar() + for _ in self.loop(timeout=3): + current = self._fleet_preparation_get() + if current == index: return True + if interval.reached(): + self.device.click(sidebar[0, index - 1]) + interval.reset() + continue + else: + logger.warning('Sidebar could not be ensured') + return False def _auto_search_set_click(self, setting): """ diff --git a/alas_wrapped/module/handler/login.py b/alas_wrapped/module/handler/login.py index 7cc4f90d37..f5139ac94f 100644 --- a/alas_wrapped/module/handler/login.py +++ b/alas_wrapped/module/handler/login.py @@ -1,6 +1,3 @@ -import time -from datetime import datetime -from pathlib import Path from typing import Union import numpy as np @@ -11,54 +8,16 @@ import module.config.server as server from module.base.timer import Timer -from module.base.jsonl import append_jsonl from module.base.utils import color_similarity_2d, crop, random_rectangle_point from module.handler.assets import * from module.logger import logger from module.map.assets import * -from module.exception import GameStuckError from module.ui.assets import * from module.ui.page import page_campaign_menu from module.ui.ui import UI class LoginHandler(UI): - LOGIN_MAX_TOTAL_SECONDS = 300 - LOGIN_MAX_NO_PROGRESS_SECONDS = 180 - LOGIN_TRACE_ROTATE_BYTES = 20 * 1024 * 1024 - _RUNTIME_ROOT = Path(__file__).resolve().parents[2] - _trace_write_warned = False - # Side-channel trace file: append-only JSONL so external parsers can - # reconstruct login decisions without touching the normal logger stream. - LOGIN_TRACE_FILE = str(_RUNTIME_ROOT / 'log' / 'login_trace.jsonl') - - def _trace_login_event(self, phase, detected=None, action=None, result=None, error=None, elapsed_ms=None): - # Keep trace writes isolated from bot control flow; telemetry must - # never alter runtime behavior. - payload = { - 'ts': datetime.utcnow().isoformat(timespec='milliseconds') + 'Z', - 'config': getattr(self.config, 'config_name', 'unknown'), - 'phase': phase, - 'detected': detected, - 'action': action, - 'result': result, - 'error': error, - 'elapsed_ms': elapsed_ms, - } - - def _on_trace_error(e): - # Trace logging must never break login flow. - if not self._trace_write_warned: - logger.warning(f'login_trace telemetry disabled: {type(e).__name__}: {e}') - self._trace_write_warned = True - - append_jsonl( - self.LOGIN_TRACE_FILE, - payload, - rotate_bytes=self.LOGIN_TRACE_ROTATE_BYTES, - error_callback=_on_trace_error, - ) - def _handle_app_login(self): """ Pages: @@ -75,55 +34,10 @@ def _handle_app_login(self): confirm_timer = Timer(1.5, count=4).start() orientation_timer = Timer(5) login_success = False - started_at = time.monotonic() - last_progress_at = started_at self.device.stuck_record_clear() self.device.click_record_clear() - self._trace_login_event(phase='start', result='begin', elapsed_ms=0) - - def elapsed_ms(): - return int((time.monotonic() - started_at) * 1000) - - def mark_progress(detected, action, result='progress'): - nonlocal last_progress_at - last_progress_at = time.monotonic() - self._trace_login_event( - phase='progress', - detected=detected, - action=action, - result=result, - elapsed_ms=elapsed_ms(), - ) while 1: - now = time.monotonic() - total_elapsed = now - started_at - idle_elapsed = now - last_progress_at - if total_elapsed > self.LOGIN_MAX_TOTAL_SECONDS: - # Bound total login wall-clock runtime to avoid long blind loops. - self._trace_login_event( - phase='guard', - action='abort', - result='timeout_total', - error=f'elapsed={total_elapsed:.1f}s', - elapsed_ms=elapsed_ms(), - ) - raise GameStuckError( - f'Login timeout after {total_elapsed:.1f}s' - ) - if idle_elapsed > self.LOGIN_MAX_NO_PROGRESS_SECONDS: - # Bound no-progress window so we fail fast when UI is not changing. - self._trace_login_event( - phase='guard', - action='abort', - result='timeout_no_progress', - error=f'idle={idle_elapsed:.1f}s', - elapsed_ms=elapsed_ms(), - ) - raise GameStuckError( - f'Login no progress for {idle_elapsed:.1f}s' - ) - # Watch device rotation if not login_success and orientation_timer.reached(): # Screen may rotate after starting an app @@ -136,14 +50,6 @@ def mark_progress(detected, action, result='progress'): if self.is_in_main(): if confirm_timer.reached(): logger.info('Login to main confirm') - mark_progress(detected='page_main', action='confirm', result='success') - self._trace_login_event( - phase='end', - detected='page_main', - action='return', - result='success', - elapsed_ms=elapsed_ms(), - ) break else: confirm_timer.reset() @@ -151,75 +57,45 @@ def mark_progress(detected, action, result='progress'): # Login if self.match_template_color(LOGIN_CHECK, offset=(30, 30), interval=5): self.device.click(LOGIN_CHECK) - mark_progress(detected='LOGIN_CHECK', action='click') if not login_success: logger.info('Login success') - self._trace_login_event( - phase='login', - detected='LOGIN_CHECK', - action='login_success', - result='success', - elapsed_ms=elapsed_ms(), - ) login_success = True if self.appear(ANDROID_NO_RESPOND, offset=(30, 30), interval=5): logger.warning('Emulator no respond') self.device.click_record_add(ANDROID_NO_RESPOND) self.device.click_record_check() self.device.click(ANDROID_NO_RESPOND, control_check=False) - mark_progress(detected='ANDROID_NO_RESPOND', action='click') continue if self.appear_then_click(LOGIN_ANNOUNCE, offset=(30, 30), interval=5): - mark_progress(detected='LOGIN_ANNOUNCE', action='click') continue if self.appear_then_click(LOGIN_ANNOUNCE_2, offset=(30, 30), interval=5): - mark_progress(detected='LOGIN_ANNOUNCE_2', action='click') continue if self.appear(EVENT_LIST_CHECK, offset=(30, 30), interval=5): self.device.click(BACK_ARROW) - mark_progress(detected='EVENT_LIST_CHECK', action='click_back') continue # Updates and maintenance if self.appear_then_click(MAINTENANCE_ANNOUNCE, offset=(30, 30), interval=5): - mark_progress(detected='MAINTENANCE_ANNOUNCE', action='click') continue if self.appear_then_click(LOGIN_GAME_UPDATE, offset=(30, 30), interval=5): - mark_progress(detected='LOGIN_GAME_UPDATE', action='click') continue if server.server == 'cn' and not login_success: if self.handle_cn_user_agreement(): - mark_progress(detected='CN_USER_AGREEMENT', action='handle') continue # Player return if self.appear_then_click(LOGIN_RETURN_SIGN, offset=(30, 30), interval=5): - mark_progress(detected='LOGIN_RETURN_SIGN', action='click') continue if self.appear_then_click(LOGIN_RETURN_INFO, offset=(30, 30), interval=5): - mark_progress(detected='LOGIN_RETURN_INFO', action='click') continue # Popups if self.handle_popup_confirm('LOGIN'): - mark_progress(detected='POPUP_CONFIRM', action='handle') continue if self.handle_urgent_commission(): - mark_progress(detected='URGENT_COMMISSION', action='handle') continue - # Popups appear at page_main. - # If popup handling succeeds, treat it as successful convergence for - # login and exit immediately. + # Popups appear at page_main if self.ui_page_main_popups(get_ship=login_success): - mark_progress(detected='MAIN_POPUPS', action='handle', result='success') - self._trace_login_event( - phase='end', - detected='MAIN_POPUPS', - action='return', - result='success', - elapsed_ms=elapsed_ms(), - ) return True # Always goto page_main if self.appear_then_click(GOTO_MAIN, offset=(30, 30), interval=5): - mark_progress(detected='GOTO_MAIN', action='click') continue return True @@ -266,17 +142,7 @@ def handle_app_login(self): logger.info('handle_app_login') self.device.screenshot_interval_set(1.0) try: - return self._handle_app_login() - except Exception as e: - # Preserve all existing logger behavior; we only add a side-channel - # event so callers keep current failure semantics. - self._trace_login_event( - phase='error', - action='raise', - result='failed', - error=type(e).__name__, - ) - raise + self._handle_app_login() finally: self.device.screenshot_interval_set() @@ -287,7 +153,6 @@ def app_stop(self): def app_start(self): logger.hr('App start') self.device.app_start() - # Raises on failure; return value is informational for callers that need it. self.handle_app_login() # self.ensure_no_unfinished_campaign() @@ -295,7 +160,6 @@ def app_restart(self): logger.hr('App restart') self.device.app_stop() self.device.app_start() - # Raises on failure; return value is informational for callers that need it. self.handle_app_login() # self.ensure_no_unfinished_campaign() self.config.task_delay(server_update=True) diff --git a/alas_wrapped/module/logger.py b/alas_wrapped/module/logger.py index 4f1c729da0..97dc148dec 100644 --- a/alas_wrapped/module/logger.py +++ b/alas_wrapped/module/logger.py @@ -133,7 +133,7 @@ class Highlighter(RegexHighlighter): # Logger init -logger_debug = True +logger_debug = False logger = logging.getLogger('alas') logger.setLevel(logging.DEBUG if logger_debug else logging.INFO) file_formatter = logging.Formatter( diff --git a/alas_wrapped/module/minigame/minigame.py b/alas_wrapped/module/minigame/minigame.py index 6537dc1de8..7af1520459 100644 --- a/alas_wrapped/module/minigame/minigame.py +++ b/alas_wrapped/module/minigame/minigame.py @@ -3,8 +3,8 @@ from module.logger import logger from module.minigame.assets import * from module.ocr.ocr import Digit -from module.ui.assets import GAME_ROOM_CHECK -from module.ui.page import page_game_room +from module.ui.assets import ACADEMY_GOTO_GAME_ROOM, GAME_ROOM_CHECK +from module.ui.page import page_academy, page_game_room from module.ui.scroll import Scroll from module.ui.ui import UI @@ -131,6 +131,7 @@ def go_to_main_page(self, skip_first_screenshot=True): in: page_game_room main_page/choose_game_page out: page_game_room main_page """ + logger.info('minigame go_to_main_page') while 1: if skip_first_screenshot: skip_first_screenshot = False @@ -183,8 +184,19 @@ def run(self): in: Any page out: page_game_room """ + # TEMP: 2026.02.18 separate self.ui_ensure(page_game_room) into 2 steps + # EN has different page_academy detection, to use ui_ensure(page_game_room), + # ui_goto must use `if self.ui_page_appear(page)` instead of `if self.appear(page.check_button)` + # But that would cause page_main/page_main_white clicking a static switch button + self.ui_ensure(page_academy) + # page_academy -> page_game_room + for _ in self.loop(): + if self.ui_page_appear(page_game_room): + break + if self.ui_page_appear(page_academy, interval=5): + self.device.click(ACADEMY_GOTO_GAME_ROOM) + continue - self.ui_ensure(page_game_room) # game room and choose game have same header, go to game room first self.go_to_main_page() coin_collected = False diff --git a/alas_wrapped/module/os/fleet.py b/alas_wrapped/module/os/fleet.py index c39c40c3e2..6d0f20bf22 100644 --- a/alas_wrapped/module/os/fleet.py +++ b/alas_wrapped/module/os/fleet.py @@ -20,7 +20,7 @@ from module.os.camera import OSCamera from module.os.map_base import OSCampaignMap from module.os_ash.ash import OSAsh -from module.os_combat.combat import Combat +from module.os_combat.combat import Combat, BATTLE_PREPARATION, SIREN_PREPARATION from module.os_handler.assets import AUTO_SEARCH_REWARD, CLICK_SAFE_AREA, IN_MAP, PORT_ENTER from module.os_shop.assets import PORT_SUPPLY_CHECK from module.ui.assets import BACK_ARROW @@ -283,6 +283,13 @@ def wait_until_walk_stable(self, confirm_timer=None, skip_first_screenshot=False clicked_story = False stuck_timer = Timer(20, count=5).start() confirm_timer.reset() + + def abyssal_expected_end(): + # add handle_map_event() because OSCombat.combat_status() removes get_items + if self.handle_map_event(drop=drop): + return False + return self.is_in_map() + for _ in self.loop(skip_first=skip_first_screenshot): # Map event event = self.handle_map_event(drop=drop) @@ -347,7 +354,7 @@ def wait_until_walk_stable(self, confirm_timer=None, skip_first_screenshot=False if self.combat_appear(): # Use ui_back() for testing, because there are too few abyssal loggers every month. # self.ui_back(check_button=self.is_in_map) - self.combat(expected_end=self.is_in_map, fleet_index=self.fleet_show_index, save_get_items=drop) + self.combat(expected_end=abyssal_expected_end, fleet_index=self.fleet_show_index, save_get_items=drop) confirm_timer.reset() stuck_timer.reset() result.add('event') @@ -700,6 +707,7 @@ def boss_leave(self): logger.hr('BOSS leave') # Update local view self.update_os() + self.predict() click_timer = Timer(3) pause_interval = Timer(0.5, count=1) @@ -713,8 +721,13 @@ def boss_leave(self): # Re-enter boss accidentally if pause_interval.reached(): - if self.combat_appear(): - logger.info(f'combat_appear -> {BACK_ARROW}') + if self.appear(BATTLE_PREPARATION): + logger.info(f'{BATTLE_PREPARATION} -> {BACK_ARROW}') + self.device.click(BACK_ARROW) + pause_interval.reset() + continue + if self.appear(SIREN_PREPARATION, offset=(20, 20)): + logger.info(f'{SIREN_PREPARATION} -> {BACK_ARROW}') self.device.click(BACK_ARROW) pause_interval.reset() continue diff --git a/alas_wrapped/module/os/map_operation.py b/alas_wrapped/module/os/map_operation.py index 1330cdfcb3..1f9ea5697d 100644 --- a/alas_wrapped/module/os/map_operation.py +++ b/alas_wrapped/module/os/map_operation.py @@ -47,7 +47,7 @@ def get_zone_name(self): name = ocr.ocr(self.device.image) name = "".join(name.split()) name = name.lower() - name = name.strip('\\/-') + name = name.strip('\\/-—–-') if '-' in name: name = name.split('-')[0] if 'é' in name: # Méditerranée name maps @@ -80,7 +80,7 @@ def get_zone_name(self): # For JP only ocr = Ocr(MAP_NAME, lang='jp', letter=(157, 173, 192), threshold=127, name='OCR_OS_MAP_NAME') name = ocr.ocr(self.device.image) - name = name.strip('\\/-') + name = name.strip('\\/-—–-') self.is_zone_name_hidden = '安全' in name # Remove punctuations for char in '・': @@ -109,7 +109,7 @@ def get_zone_name(self): # For TW only ocr = Ocr(MAP_NAME, lang='tw', letter=(198, 215, 239), threshold=127, name='OCR_OS_MAP_NAME') name = ocr.ocr(self.device.image) - name = name.strip('\\/-') + name = name.strip('\\/-—–-') self.is_zone_name_hidden = '安全' in name # Remove '塞壬要塞海域' if '塞' in name: @@ -123,7 +123,7 @@ def get_zone_name(self): # For CN only ocr = Ocr(MAP_NAME, lang='cnocr', letter=(214, 231, 255), threshold=127, name='OCR_OS_MAP_NAME') name = ocr.ocr(self.device.image) - name = name.strip('\\/-') + name = name.strip('\\/-—–-') self.is_zone_name_hidden = '安全' in name if '-' in name: name = name.split('-')[0] diff --git a/alas_wrapped/module/os_handler/action_point.py b/alas_wrapped/module/os_handler/action_point.py index 337846ca79..3f3eb64d91 100644 --- a/alas_wrapped/module/os_handler/action_point.py +++ b/alas_wrapped/module/os_handler/action_point.py @@ -12,8 +12,6 @@ from module.statistics.item import Item, ItemGrid from module.ui.assets import OS_CHECK from module.ui.ui import UI -from module.config.deep import deep_get -from module.log_res.log_res import LogRes OCR_ACTION_POINT_REMAIN = Digit(ACTION_POINT_REMAIN, letter=(255, 219, 66), name='OCR_ACTION_POINT_REMAIN') OCR_ACTION_POINT_REMAIN_OS = Digit(ACTION_POINT_REMAIN_OS, letter=(239, 239, 239), @@ -138,10 +136,8 @@ def action_point_update(self): if self.config.OS_ACTION_POINT_BOX_USE: total += np.sum(np.array(box) * tuple(ACTION_POINT_BOX.values())) oil = box[0] - LogRes(self.config).Oil = oil + logger.info(f'Action points: {current}({total}), oil: {oil}') - LogRes(self.config).ActionPoint = {'Value': current, 'Total': total} - self.config.update() self._action_point_current = current self._action_point_box = box self._action_point_total = total diff --git a/alas_wrapped/module/os_handler/assets.py b/alas_wrapped/module/os_handler/assets.py index b3f85d8036..70dad15744 100644 --- a/alas_wrapped/module/os_handler/assets.py +++ b/alas_wrapped/module/os_handler/assets.py @@ -33,6 +33,7 @@ MISSION_OVERVIEW_ACCEPT = Button(area={'cn': (1072, 12, 1130, 40), 'en': (1082, 16, 1123, 54), 'jp': (1069, 5, 1132, 43), 'tw': (1069, 10, 1131, 42)}, color={'cn': (230, 193, 168), 'en': (228, 174, 128), 'jp': (224, 166, 120), 'tw': (227, 180, 146)}, button={'cn': (1072, 12, 1130, 40), 'en': (1082, 16, 1123, 54), 'jp': (1069, 5, 1132, 43), 'tw': (1069, 10, 1131, 42)}, file={'cn': './assets/cn/os_handler/MISSION_OVERVIEW_ACCEPT.png', 'en': './assets/en/os_handler/MISSION_OVERVIEW_ACCEPT.png', 'jp': './assets/jp/os_handler/MISSION_OVERVIEW_ACCEPT.png', 'tw': './assets/tw/os_handler/MISSION_OVERVIEW_ACCEPT.png'}) MISSION_OVERVIEW_ACCEPT_SINGLE = Button(area={'cn': (1066, 121, 1138, 149), 'en': (1068, 127, 1138, 149), 'jp': (1067, 121, 1138, 149), 'tw': (1066, 121, 1138, 149)}, color={'cn': (145, 182, 231), 'en': (156, 196, 237), 'jp': (133, 173, 227), 'tw': (145, 182, 231)}, button={'cn': (1066, 121, 1138, 149), 'en': (1068, 127, 1138, 149), 'jp': (1067, 121, 1138, 149), 'tw': (1066, 121, 1138, 149)}, file={'cn': './assets/cn/os_handler/MISSION_OVERVIEW_ACCEPT_SINGLE.png', 'en': './assets/en/os_handler/MISSION_OVERVIEW_ACCEPT_SINGLE.png', 'jp': './assets/jp/os_handler/MISSION_OVERVIEW_ACCEPT_SINGLE.png', 'tw': './assets/cn/os_handler/MISSION_OVERVIEW_ACCEPT_SINGLE.png'}) MISSION_OVERVIEW_CHECK = Button(area={'cn': (127, 17, 262, 42), 'en': (128, 18, 300, 37), 'jp': (126, 16, 263, 42), 'tw': (126, 16, 263, 42)}, color={'cn': (148, 165, 209), 'en': (120, 136, 182), 'jp': (95, 109, 148), 'tw': (147, 164, 208)}, button={'cn': (127, 17, 262, 42), 'en': (128, 18, 300, 37), 'jp': (126, 16, 263, 42), 'tw': (126, 16, 263, 42)}, file={'cn': './assets/cn/os_handler/MISSION_OVERVIEW_CHECK.png', 'en': './assets/en/os_handler/MISSION_OVERVIEW_CHECK.png', 'jp': './assets/jp/os_handler/MISSION_OVERVIEW_CHECK.png', 'tw': './assets/tw/os_handler/MISSION_OVERVIEW_CHECK.png'}) +MISSION_OVERVIEW_EMPTY = Button(area={'cn': (1052, 320, 1067, 390), 'en': (1052, 320, 1067, 390), 'jp': (1052, 320, 1067, 390), 'tw': (1052, 320, 1067, 390)}, color={'cn': (144, 91, 99), 'en': (144, 91, 99), 'jp': (144, 91, 99), 'tw': (144, 91, 99)}, button={'cn': (1052, 320, 1067, 390), 'en': (1052, 320, 1067, 390), 'jp': (1052, 320, 1067, 390), 'tw': (1052, 320, 1067, 390)}, file={'cn': './assets/cn/os_handler/MISSION_OVERVIEW_EMPTY.png', 'en': './assets/en/os_handler/MISSION_OVERVIEW_EMPTY.png', 'jp': './assets/jp/os_handler/MISSION_OVERVIEW_EMPTY.png', 'tw': './assets/tw/os_handler/MISSION_OVERVIEW_EMPTY.png'}) MISSION_OVERVIEW_ENTER = Button(area={'cn': (1111, 672, 1207, 690), 'en': (1112, 662, 1183, 689), 'jp': (1108, 670, 1224, 691), 'tw': (1110, 671, 1207, 690)}, color={'cn': (66, 68, 72), 'en': (60, 67, 78), 'jp': (38, 43, 50), 'tw': (67, 70, 75)}, button={'cn': (1105, 629, 1255, 693), 'en': (1105, 629, 1254, 693), 'jp': (1105, 629, 1254, 693), 'tw': (1105, 629, 1255, 693)}, file={'cn': './assets/cn/os_handler/MISSION_OVERVIEW_ENTER.png', 'en': './assets/en/os_handler/MISSION_OVERVIEW_ENTER.png', 'jp': './assets/jp/os_handler/MISSION_OVERVIEW_ENTER.png', 'tw': './assets/tw/os_handler/MISSION_OVERVIEW_ENTER.png'}) MISSION_QUIT = Button(area={'cn': (1086, 111, 1152, 155), 'en': (1086, 111, 1152, 155), 'jp': (1086, 111, 1152, 155), 'tw': (1086, 111, 1152, 155)}, color={'cn': (152, 38, 35), 'en': (152, 38, 35), 'jp': (152, 38, 35), 'tw': (152, 38, 35)}, button={'cn': (1086, 111, 1152, 155), 'en': (1086, 111, 1152, 155), 'jp': (1086, 111, 1152, 155), 'tw': (1086, 111, 1152, 155)}, file={'cn': './assets/cn/os_handler/MISSION_QUIT.png', 'en': './assets/en/os_handler/MISSION_QUIT.png', 'jp': './assets/jp/os_handler/MISSION_QUIT.png', 'tw': './assets/tw/os_handler/MISSION_QUIT.png'}) ORDER_CHECK = Button(area={'cn': (60, 623, 98, 659), 'en': (60, 623, 98, 659), 'jp': (60, 623, 98, 659), 'tw': (60, 623, 98, 659)}, color={'cn': (60, 77, 122), 'en': (60, 77, 122), 'jp': (60, 77, 122), 'tw': (60, 77, 122)}, button={'cn': (106, 77, 224, 94), 'en': (101, 79, 306, 93), 'jp': (65, 64, 147, 95), 'tw': (106, 76, 226, 95)}, file={'cn': './assets/cn/os_handler/ORDER_CHECK.png', 'en': './assets/en/os_handler/ORDER_CHECK.png', 'jp': './assets/jp/os_handler/ORDER_CHECK.png', 'tw': './assets/tw/os_handler/ORDER_CHECK.png'}) diff --git a/alas_wrapped/module/os_handler/mission.py b/alas_wrapped/module/os_handler/mission.py index e20caa8797..376ac1db97 100644 --- a/alas_wrapped/module/os_handler/mission.py +++ b/alas_wrapped/module/os_handler/mission.py @@ -176,27 +176,28 @@ def os_mission_overview_accept(self): offset=(200, 20), retry_wait=3, additional=self.handle_manjuu, skip_first_screenshot=True) + timeout = 5 + accept_button_timer = Timer(timeout) + self.interval_timer[MISSION_OVERVIEW_ACCEPT_SINGLE.name] = accept_button_timer + self.interval_timer[MISSION_OVERVIEW_ACCEPT.name] = accept_button_timer # MISSION_OVERVIEW_CHECK - confirm_timer = Timer(1, count=3).start() success = True for _ in self.loop(): - if self.handle_manjuu(): - confirm_timer.reset() - continue + # End + if self.appear(MISSION_OVERVIEW_EMPTY, offset=(20, 20)): + success = True + break if self.info_bar_count(): logger.info('Unable to accept missions, because reached the maximum number of missions') success = False break - if self.appear_then_click(MISSION_OVERVIEW_ACCEPT, offset=(20, 20), interval=0.2): - confirm_timer.reset() + + if self.handle_manjuu(): continue - else: - # End - if confirm_timer.reached(): - success = True - break - if self.appear_then_click(MISSION_OVERVIEW_ACCEPT_SINGLE, offset=(20, 20), interval=0.2): - confirm_timer.reset() + # Click + if self.appear_then_click(MISSION_OVERVIEW_ACCEPT, offset=(20, 20), interval=timeout): + continue + if self.appear_then_click(MISSION_OVERVIEW_ACCEPT_SINGLE, offset=(20, 20), interval=timeout): continue # is_in_globe diff --git a/alas_wrapped/module/os_handler/os_status.py b/alas_wrapped/module/os_handler/os_status.py index cf55e45611..8f43a40c29 100644 --- a/alas_wrapped/module/os_handler/os_status.py +++ b/alas_wrapped/module/os_handler/os_status.py @@ -10,7 +10,6 @@ from module.ocr.ocr import Digit from module.os_shop.assets import OS_SHOP_CHECK, OS_SHOP_PURPLE_COINS, SHOP_PURPLE_COINS, SHOP_YELLOW_COINS from module.ui.ui import UI -from module.log_res.log_res import LogRes if server.server != 'jp': OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(239, 239, 239), threshold=160, name='OCR_SHOP_YELLOW_COINS') @@ -77,17 +76,14 @@ def get_yellow_coins(self) -> int: continue else: break - LogRes(self.config).YellowCoin = yellow_coins return yellow_coins def get_purple_coins(self) -> int: if self.appear(OS_SHOP_CHECK): - amount = OCR_OS_SHOP_PURPLE_COINS.ocr(self.device.image) + return OCR_OS_SHOP_PURPLE_COINS.ocr(self.device.image) else: - amount = OCR_SHOP_PURPLE_COINS.ocr(self.device.image) - LogRes(self.config).PurpleCoin = amount - return amount + return OCR_SHOP_PURPLE_COINS.ocr(self.device.image) def os_shop_get_coins(self): self._shop_yellow_coins = self.get_yellow_coins() diff --git a/alas_wrapped/module/os_handler/port.py b/alas_wrapped/module/os_handler/port.py index b2ebca8039..fac02df5ed 100644 --- a/alas_wrapped/module/os_handler/port.py +++ b/alas_wrapped/module/os_handler/port.py @@ -11,13 +11,20 @@ class PortHandler(OSShop): - def port_enter(self, skip_first_screenshot=True): + def port_enter(self): """ Pages: in: IN_MAP out: PORT_CHECK """ - self.ui_click(PORT_ENTER, check_button=PORT_CHECK, skip_first_screenshot=skip_first_screenshot) + logger.info('Port enter') + for _ in self.loop(): + if self.appear(PORT_CHECK, offset=(20, 20)): + break + if self.appear_then_click(PORT_ENTER, offset=(20, 20), interval=5): + continue + if self.handle_map_event(): + continue # Buttons at the bottom has an animation to show pass # Already ensured in ui_click @@ -27,6 +34,7 @@ def port_quit(self, skip_first_screenshot=True): in: PORT_CHECK out: IN_MAP """ + logger.info('Port quit') self.ui_back(appear_button=PORT_CHECK, check_button=self.is_in_map, skip_first_screenshot=skip_first_screenshot) # Buttons at the bottom has an animation to show diff --git a/alas_wrapped/module/raid/assets.py b/alas_wrapped/module/raid/assets.py index 7965f5718b..172c02e781 100644 --- a/alas_wrapped/module/raid/assets.py +++ b/alas_wrapped/module/raid/assets.py @@ -17,6 +17,15 @@ BRISTOL_RAID_EASY = Button(area={'cn': (1151, 490, 1203, 528), 'en': (1155, 504, 1197, 524), 'jp': (1152, 491, 1203, 528), 'tw': (1151, 490, 1204, 519)}, color={'cn': (141, 164, 177), 'en': (145, 167, 177), 'jp': (126, 151, 166), 'tw': (117, 142, 159)}, button={'cn': (1151, 490, 1203, 528), 'en': (1155, 504, 1197, 524), 'jp': (1152, 491, 1203, 528), 'tw': (1151, 490, 1204, 519)}, file={'cn': './assets/cn/raid/BRISTOL_RAID_EASY.png', 'en': './assets/en/raid/BRISTOL_RAID_EASY.png', 'jp': './assets/jp/raid/BRISTOL_RAID_EASY.png', 'tw': './assets/tw/raid/BRISTOL_RAID_EASY.png'}) BRISTOL_RAID_HARD = Button(area={'cn': (1167, 246, 1220, 285), 'en': (1156, 259, 1231, 278), 'jp': (1159, 250, 1229, 285), 'tw': (1170, 249, 1217, 275)}, color={'cn': (136, 160, 173), 'en': (133, 156, 168), 'jp': (145, 168, 181), 'tw': (100, 126, 145)}, button={'cn': (1167, 246, 1220, 285), 'en': (1156, 259, 1231, 278), 'jp': (1159, 250, 1229, 285), 'tw': (1170, 249, 1217, 275)}, file={'cn': './assets/cn/raid/BRISTOL_RAID_HARD.png', 'en': './assets/en/raid/BRISTOL_RAID_HARD.png', 'jp': './assets/jp/raid/BRISTOL_RAID_HARD.png', 'tw': './assets/tw/raid/BRISTOL_RAID_HARD.png'}) BRISTOL_RAID_NORMAL = Button(area={'cn': (1098, 367, 1150, 404), 'en': (1089, 380, 1158, 396), 'jp': (1099, 368, 1149, 404), 'tw': (1097, 367, 1151, 393)}, color={'cn': (129, 152, 165), 'en': (131, 153, 164), 'jp': (119, 146, 163), 'tw': (121, 144, 159)}, button={'cn': (1098, 367, 1150, 404), 'en': (1089, 380, 1158, 396), 'jp': (1099, 368, 1149, 404), 'tw': (1097, 367, 1151, 393)}, file={'cn': './assets/cn/raid/BRISTOL_RAID_NORMAL.png', 'en': './assets/en/raid/BRISTOL_RAID_NORMAL.png', 'jp': './assets/jp/raid/BRISTOL_RAID_NORMAL.png', 'tw': './assets/tw/raid/BRISTOL_RAID_NORMAL.png'}) +CHANGWU_OCR_PT = Button(area={'cn': (1174, 603, 1280, 632), 'en': (1174, 603, 1280, 632), 'jp': (1174, 603, 1280, 632), 'tw': (1174, 603, 1280, 632)}, color={'cn': (183, 118, 101), 'en': (183, 118, 101), 'jp': (183, 118, 101), 'tw': (183, 118, 101)}, button={'cn': (1174, 603, 1280, 632), 'en': (1174, 603, 1280, 632), 'jp': (1174, 603, 1280, 632), 'tw': (1174, 603, 1280, 632)}, file={'cn': './assets/cn/raid/CHANGWU_OCR_PT.png', 'en': './assets/en/raid/CHANGWU_OCR_PT.png', 'jp': './assets/jp/raid/CHANGWU_OCR_PT.png', 'tw': './assets/tw/raid/CHANGWU_OCR_PT.png'}) +CHANGWU_OCR_REMAIN_EASY = Button(area={'cn': (1057, 565, 1116, 585), 'en': (1057, 565, 1116, 585), 'jp': (1057, 565, 1116, 585), 'tw': (1057, 565, 1116, 585)}, color={'cn': (96, 83, 81), 'en': (96, 83, 81), 'jp': (96, 83, 81), 'tw': (96, 83, 81)}, button={'cn': (1057, 565, 1116, 585), 'en': (1057, 565, 1116, 585), 'jp': (1057, 565, 1116, 585), 'tw': (1057, 565, 1116, 585)}, file={'cn': './assets/cn/raid/CHANGWU_OCR_REMAIN_EASY.png', 'en': './assets/en/raid/CHANGWU_OCR_REMAIN_EASY.png', 'jp': './assets/jp/raid/CHANGWU_OCR_REMAIN_EASY.png', 'tw': './assets/tw/raid/CHANGWU_OCR_REMAIN_EASY.png'}) +CHANGWU_OCR_REMAIN_EX = Button(area={'cn': (1138, 26, 1185, 49), 'en': (1138, 26, 1185, 49), 'jp': (1138, 26, 1185, 49), 'tw': (1138, 26, 1185, 49)}, color={'cn': (213, 128, 103), 'en': (213, 128, 103), 'jp': (213, 128, 103), 'tw': (213, 128, 103)}, button={'cn': (1138, 26, 1185, 49), 'en': (1138, 26, 1185, 49), 'jp': (1138, 26, 1185, 49), 'tw': (1138, 26, 1185, 49)}, file={'cn': './assets/cn/raid/CHANGWU_OCR_REMAIN_EX.png', 'en': './assets/en/raid/CHANGWU_OCR_REMAIN_EX.png', 'jp': './assets/jp/raid/CHANGWU_OCR_REMAIN_EX.png', 'tw': './assets/tw/raid/CHANGWU_OCR_REMAIN_EX.png'}) +CHANGWU_OCR_REMAIN_HARD = Button(area={'cn': (1169, 409, 1229, 429), 'en': (1169, 409, 1229, 429), 'jp': (1169, 409, 1229, 429), 'tw': (1169, 409, 1229, 429)}, color={'cn': (100, 86, 82), 'en': (100, 86, 82), 'jp': (100, 86, 82), 'tw': (100, 86, 82)}, button={'cn': (1169, 409, 1229, 429), 'en': (1169, 409, 1229, 429), 'jp': (1169, 409, 1229, 429), 'tw': (1169, 409, 1229, 429)}, file={'cn': './assets/cn/raid/CHANGWU_OCR_REMAIN_HARD.png', 'en': './assets/en/raid/CHANGWU_OCR_REMAIN_HARD.png', 'jp': './assets/jp/raid/CHANGWU_OCR_REMAIN_HARD.png', 'tw': './assets/tw/raid/CHANGWU_OCR_REMAIN_HARD.png'}) +CHANGWU_OCR_REMAIN_NORMAL = Button(area={'cn': (1112, 487, 1170, 506), 'en': (1112, 487, 1170, 506), 'jp': (1112, 487, 1170, 506), 'tw': (1112, 487, 1170, 506)}, color={'cn': (100, 87, 84), 'en': (100, 87, 84), 'jp': (100, 87, 84), 'tw': (100, 87, 84)}, button={'cn': (1112, 487, 1170, 506), 'en': (1112, 487, 1170, 506), 'jp': (1112, 487, 1170, 506), 'tw': (1112, 487, 1170, 506)}, file={'cn': './assets/cn/raid/CHANGWU_OCR_REMAIN_NORMAL.png', 'en': './assets/en/raid/CHANGWU_OCR_REMAIN_NORMAL.png', 'jp': './assets/jp/raid/CHANGWU_OCR_REMAIN_NORMAL.png', 'tw': './assets/tw/raid/CHANGWU_OCR_REMAIN_NORMAL.png'}) +CHANGWU_RAID_EASY = Button(area={'cn': (976, 559, 1032, 590), 'en': (971, 562, 1037, 584), 'jp': (978, 561, 1031, 588), 'tw': (977, 560, 1031, 588)}, color={'cn': (144, 130, 122), 'en': (158, 144, 134), 'jp': (151, 137, 128), 'tw': (162, 147, 137)}, button={'cn': (976, 559, 1032, 590), 'en': (971, 562, 1037, 584), 'jp': (978, 561, 1031, 588), 'tw': (977, 560, 1031, 588)}, file={'cn': './assets/cn/raid/CHANGWU_RAID_EASY.png', 'en': './assets/en/raid/CHANGWU_RAID_EASY.png', 'jp': './assets/jp/raid/CHANGWU_RAID_EASY.png', 'tw': './assets/tw/raid/CHANGWU_RAID_EASY.png'}) +CHANGWU_RAID_EX = Button(area={'cn': (1135, 296, 1209, 331), 'en': (1135, 296, 1209, 331), 'jp': (1135, 296, 1209, 331), 'tw': (1135, 296, 1209, 331)}, color={'cn': (151, 138, 125), 'en': (151, 138, 125), 'jp': (151, 138, 125), 'tw': (151, 138, 125)}, button={'cn': (1135, 296, 1209, 331), 'en': (1135, 296, 1209, 331), 'jp': (1135, 296, 1209, 331), 'tw': (1135, 296, 1209, 331)}, file={'cn': './assets/cn/raid/CHANGWU_RAID_EX.png', 'en': './assets/en/raid/CHANGWU_RAID_EX.png', 'jp': './assets/jp/raid/CHANGWU_RAID_EX.png', 'tw': './assets/tw/raid/CHANGWU_RAID_EX.png'}) +CHANGWU_RAID_HARD = Button(area={'cn': (1087, 403, 1146, 433), 'en': (1083, 407, 1150, 428), 'jp': (1073, 405, 1143, 431), 'tw': (1089, 405, 1145, 432)}, color={'cn': (154, 139, 130), 'en': (173, 157, 145), 'jp': (133, 119, 112), 'tw': (173, 157, 145)}, button={'cn': (1087, 403, 1146, 433), 'en': (1083, 407, 1150, 428), 'jp': (1073, 405, 1143, 431), 'tw': (1089, 405, 1145, 432)}, file={'cn': './assets/cn/raid/CHANGWU_RAID_HARD.png', 'en': './assets/en/raid/CHANGWU_RAID_HARD.png', 'jp': './assets/jp/raid/CHANGWU_RAID_HARD.png', 'tw': './assets/tw/raid/CHANGWU_RAID_HARD.png'}) +CHANGWU_RAID_NORMAL = Button(area={'cn': (1032, 480, 1091, 511), 'en': (1009, 485, 1104, 506), 'jp': (1034, 482, 1089, 510), 'tw': (1033, 481, 1089, 510)}, color={'cn': (144, 130, 122), 'en': (168, 153, 142), 'jp': (146, 131, 123), 'tw': (151, 136, 128)}, button={'cn': (1032, 480, 1091, 511), 'en': (1009, 485, 1104, 506), 'jp': (1034, 482, 1089, 510), 'tw': (1033, 481, 1089, 510)}, file={'cn': './assets/cn/raid/CHANGWU_RAID_NORMAL.png', 'en': './assets/en/raid/CHANGWU_RAID_NORMAL.png', 'jp': './assets/jp/raid/CHANGWU_RAID_NORMAL.png', 'tw': './assets/tw/raid/CHANGWU_RAID_NORMAL.png'}) CHIENWU_OCR_PT = Button(area={'cn': (1166, 604, 1279, 632), 'en': (1166, 604, 1279, 632), 'jp': (1166, 604, 1279, 632), 'tw': (1166, 604, 1279, 632)}, color={'cn': (126, 40, 47), 'en': (126, 40, 47), 'jp': (126, 40, 47), 'tw': (126, 40, 47)}, button={'cn': (1166, 604, 1279, 632), 'en': (1166, 604, 1279, 632), 'jp': (1166, 604, 1279, 632), 'tw': (1166, 604, 1279, 632)}, file={'cn': './assets/cn/raid/CHIENWU_OCR_PT.png', 'en': './assets/cn/raid/CHIENWU_OCR_PT.png', 'jp': './assets/cn/raid/CHIENWU_OCR_PT.png', 'tw': './assets/cn/raid/CHIENWU_OCR_PT.png'}) CHIENWU_OCR_REMAIN_EASY = Button(area={'cn': (1111, 528, 1163, 549), 'en': (1111, 528, 1163, 549), 'jp': (1111, 528, 1163, 549), 'tw': (1111, 528, 1163, 549)}, color={'cn': (174, 153, 133), 'en': (174, 153, 133), 'jp': (174, 153, 133), 'tw': (174, 153, 133)}, button={'cn': (1111, 528, 1163, 549), 'en': (1111, 528, 1163, 549), 'jp': (1111, 528, 1163, 549), 'tw': (1111, 528, 1163, 549)}, file={'cn': './assets/cn/raid/CHIENWU_OCR_REMAIN_EASY.png', 'en': './assets/cn/raid/CHIENWU_OCR_REMAIN_EASY.png', 'jp': './assets/cn/raid/CHIENWU_OCR_REMAIN_EASY.png', 'tw': './assets/cn/raid/CHIENWU_OCR_REMAIN_EASY.png'}) CHIENWU_OCR_REMAIN_EX = Button(area={'cn': (1086, 16, 1152, 44), 'en': (1086, 16, 1152, 44), 'jp': (1086, 16, 1152, 44), 'tw': (1086, 16, 1152, 44)}, color={'cn': (90, 39, 34), 'en': (90, 39, 34), 'jp': (90, 39, 34), 'tw': (90, 39, 34)}, button={'cn': (1086, 16, 1152, 44), 'en': (1086, 16, 1152, 44), 'jp': (1086, 16, 1152, 44), 'tw': (1086, 16, 1152, 44)}, file={'cn': './assets/cn/raid/CHIENWU_OCR_REMAIN_EX.png', 'en': './assets/cn/raid/CHIENWU_OCR_REMAIN_EX.png', 'jp': './assets/cn/raid/CHIENWU_OCR_REMAIN_EX.png', 'tw': './assets/cn/raid/CHIENWU_OCR_REMAIN_EX.png'}) @@ -68,12 +77,13 @@ KUYBYSHEY_RAID_NORMAL = Button(area={'cn': (1045, 423, 1097, 451), 'en': (1036, 424, 1099, 449), 'jp': (1048, 427, 1091, 448), 'tw': (1044, 423, 1096, 452)}, color={'cn': (86, 95, 109), 'en': (81, 92, 105), 'jp': (131, 143, 154), 'tw': (86, 95, 109)}, button={'cn': (1045, 423, 1097, 451), 'en': (1036, 424, 1099, 449), 'jp': (1048, 427, 1091, 448), 'tw': (1044, 423, 1096, 452)}, file={'cn': './assets/cn/raid/KUYBYSHEY_RAID_NORMAL.png', 'en': './assets/en/raid/KUYBYSHEY_RAID_NORMAL.png', 'jp': './assets/jp/raid/KUYBYSHEY_RAID_NORMAL.png', 'tw': './assets/tw/raid/KUYBYSHEY_RAID_NORMAL.png'}) RAID_FLEET_PREPARATION = Button(area={'cn': (983, 577, 1181, 638), 'en': (1041, 592, 1121, 631), 'jp': (983, 579, 1180, 635), 'tw': (983, 577, 1181, 638)}, color={'cn': (236, 188, 115), 'en': (236, 184, 117), 'jp': (235, 183, 103), 'tw': (236, 188, 115)}, button={'cn': (983, 577, 1181, 638), 'en': (1041, 592, 1121, 631), 'jp': (983, 579, 1180, 635), 'tw': (983, 577, 1181, 638)}, file={'cn': './assets/cn/raid/RAID_FLEET_PREPARATION.png', 'en': './assets/en/raid/RAID_FLEET_PREPARATION.png', 'jp': './assets/jp/raid/RAID_FLEET_PREPARATION.png', 'tw': './assets/tw/raid/RAID_FLEET_PREPARATION.png'}) RAID_REWARDS = Button(area={'cn': (836, 127, 900, 169), 'en': (836, 127, 900, 169), 'jp': (836, 127, 900, 169), 'tw': (836, 127, 900, 169)}, color={'cn': (217, 103, 98), 'en': (217, 103, 98), 'jp': (217, 103, 98), 'tw': (217, 103, 98)}, button={'cn': (836, 127, 900, 169), 'en': (836, 127, 900, 169), 'jp': (836, 127, 900, 169), 'tw': (836, 127, 900, 169)}, file={'cn': './assets/cn/raid/RAID_REWARDS.png', 'en': './assets/en/raid/RAID_REWARDS.png', 'jp': './assets/jp/raid/RAID_REWARDS.png', 'tw': './assets/tw/raid/RAID_REWARDS.png'}) +RPG_BACK = Button(area={'cn': (40, 30, 59, 57), 'en': (40, 30, 59, 57), 'jp': (40, 30, 59, 57), 'tw': (40, 30, 59, 57)}, color={'cn': (154, 127, 105), 'en': (154, 127, 105), 'jp': (154, 127, 105), 'tw': (154, 127, 105)}, button={'cn': (40, 30, 59, 57), 'en': (40, 30, 59, 57), 'jp': (40, 30, 59, 57), 'tw': (40, 30, 59, 57)}, file={'cn': './assets/cn/raid/RPG_BACK.png', 'en': './assets/en/raid/RPG_BACK.png', 'jp': './assets/jp/raid/RPG_BACK.png', 'tw': './assets/tw/raid/RPG_BACK.png'}) RPG_GOTO_STAGE = Button(area={'cn': (55, 495, 80, 520), 'en': (55, 495, 80, 520), 'jp': (55, 495, 80, 520), 'tw': (55, 495, 80, 520)}, color={'cn': (174, 168, 160), 'en': (174, 168, 160), 'jp': (174, 168, 160), 'tw': (174, 168, 160)}, button={'cn': (55, 495, 80, 520), 'en': (55, 495, 80, 520), 'jp': (55, 495, 80, 520), 'tw': (55, 495, 80, 520)}, file={'cn': './assets/cn/raid/RPG_GOTO_STAGE.png', 'en': './assets/en/raid/RPG_GOTO_STAGE.png', 'jp': './assets/jp/raid/RPG_GOTO_STAGE.png', 'tw': './assets/tw/raid/RPG_GOTO_STAGE.png'}) RPG_GOTO_STORY = Button(area={'cn': (59, 491, 84, 516), 'en': (59, 491, 84, 516), 'jp': (59, 491, 84, 516), 'tw': (59, 491, 84, 516)}, color={'cn': (182, 122, 105), 'en': (182, 122, 105), 'jp': (182, 122, 105), 'tw': (182, 122, 105)}, button={'cn': (59, 491, 84, 516), 'en': (59, 491, 84, 516), 'jp': (59, 491, 84, 516), 'tw': (59, 491, 84, 516)}, file={'cn': './assets/cn/raid/RPG_GOTO_STORY.png', 'en': './assets/en/raid/RPG_GOTO_STORY.png', 'jp': './assets/jp/raid/RPG_GOTO_STORY.png', 'tw': './assets/tw/raid/RPG_GOTO_STORY.png'}) RPG_HOME = Button(area={'cn': (1222, 29, 1240, 51), 'en': (1222, 29, 1240, 51), 'jp': (1222, 29, 1240, 51), 'tw': (1222, 29, 1240, 51)}, color={'cn': (197, 181, 158), 'en': (197, 181, 158), 'jp': (197, 181, 158), 'tw': (197, 181, 158)}, button={'cn': (1222, 29, 1240, 51), 'en': (1222, 29, 1240, 51), 'jp': (1222, 29, 1240, 51), 'tw': (1222, 29, 1240, 51)}, file={'cn': './assets/cn/raid/RPG_HOME.png', 'en': './assets/en/raid/RPG_HOME.png', 'jp': './assets/jp/raid/RPG_HOME.png', 'tw': './assets/tw/raid/RPG_HOME.png'}) RPG_LEAVE_CITY = Button(area={'cn': (688, 642, 711, 664), 'en': (688, 642, 711, 664), 'jp': (688, 642, 711, 664), 'tw': (688, 642, 711, 664)}, color={'cn': (158, 130, 109), 'en': (158, 130, 109), 'jp': (158, 130, 109), 'tw': (158, 130, 109)}, button={'cn': (688, 642, 711, 664), 'en': (688, 642, 711, 664), 'jp': (688, 642, 711, 664), 'tw': (688, 642, 711, 664)}, file={'cn': './assets/cn/raid/RPG_LEAVE_CITY.png', 'en': './assets/en/raid/RPG_LEAVE_CITY.png', 'jp': './assets/jp/raid/RPG_LEAVE_CITY.png', 'tw': './assets/tw/raid/RPG_LEAVE_CITY.png'}) RPG_RAID_EASY = Button(area={'cn': (149, 561, 179, 591), 'en': (149, 561, 179, 591), 'jp': (149, 561, 179, 591), 'tw': (149, 561, 179, 591)}, color={'cn': (152, 57, 59), 'en': (152, 57, 59), 'jp': (152, 57, 59), 'tw': (152, 57, 59)}, button={'cn': (149, 561, 179, 591), 'en': (149, 561, 179, 591), 'jp': (149, 561, 179, 591), 'tw': (149, 561, 179, 591)}, file={'cn': './assets/cn/raid/RPG_RAID_EASY.png', 'en': './assets/en/raid/RPG_RAID_EASY.png', 'jp': './assets/jp/raid/RPG_RAID_EASY.png', 'tw': './assets/tw/raid/RPG_RAID_EASY.png'}) -RPG_RAID_EX = Button(area={'cn': (949, 518, 976, 565), 'en': (949, 518, 976, 565), 'jp': (949, 518, 976, 565), 'tw': (949, 518, 976, 565)}, color={'cn': (166, 66, 69), 'en': (166, 66, 69), 'jp': (166, 66, 69), 'tw': (166, 66, 69)}, button={'cn': (949, 518, 976, 565), 'en': (949, 518, 976, 565), 'jp': (949, 518, 976, 565), 'tw': (949, 518, 976, 565)}, file={'cn': './assets/cn/raid/RPG_RAID_EX.png', 'en': './assets/en/raid/RPG_RAID_EX.png', 'jp': './assets/jp/raid/RPG_RAID_EX.png', 'tw': './assets/tw/raid/RPG_RAID_EX.png'}) +RPG_RAID_EX = Button(area={'cn': (979, 223, 999, 258), 'en': (979, 223, 999, 258), 'jp': (979, 223, 999, 258), 'tw': (979, 223, 999, 258)}, color={'cn': (231, 198, 84), 'en': (231, 198, 84), 'jp': (231, 198, 84), 'tw': (231, 198, 84)}, button={'cn': (979, 223, 999, 258), 'en': (979, 223, 999, 258), 'jp': (979, 223, 999, 258), 'tw': (979, 223, 999, 258)}, file={'cn': './assets/cn/raid/RPG_RAID_EX.png', 'en': './assets/en/raid/RPG_RAID_EX.png', 'jp': './assets/jp/raid/RPG_RAID_EX.png', 'tw': './assets/tw/raid/RPG_RAID_EX.png'}) RPG_RAID_HARD = Button(area={'cn': (475, 108, 505, 138), 'en': (475, 108, 505, 138), 'jp': (475, 108, 505, 138), 'tw': (475, 108, 505, 138)}, color={'cn': (97, 59, 59), 'en': (97, 59, 59), 'jp': (97, 59, 59), 'tw': (97, 59, 59)}, button={'cn': (475, 108, 505, 138), 'en': (475, 108, 505, 138), 'jp': (475, 108, 505, 138), 'tw': (475, 108, 505, 138)}, file={'cn': './assets/cn/raid/RPG_RAID_HARD.png', 'en': './assets/en/raid/RPG_RAID_HARD.png', 'jp': './assets/jp/raid/RPG_RAID_HARD.png', 'tw': './assets/tw/raid/RPG_RAID_HARD.png'}) RPG_RAID_NORMAL = Button(area={'cn': (313, 259, 343, 289), 'en': (313, 259, 343, 289), 'jp': (313, 259, 343, 289), 'tw': (313, 259, 343, 289)}, color={'cn': (147, 61, 62), 'en': (147, 61, 62), 'jp': (147, 61, 62), 'tw': (147, 61, 62)}, button={'cn': (313, 259, 343, 289), 'en': (313, 259, 343, 289), 'jp': (313, 259, 343, 289), 'tw': (313, 259, 343, 289)}, file={'cn': './assets/cn/raid/RPG_RAID_NORMAL.png', 'en': './assets/en/raid/RPG_RAID_NORMAL.png', 'jp': './assets/jp/raid/RPG_RAID_NORMAL.png', 'tw': './assets/tw/raid/RPG_RAID_NORMAL.png'}) RPG_STATUS_POPUP = Button(area={'cn': (1120, 97, 1144, 121), 'en': (1120, 97, 1144, 121), 'jp': (1120, 97, 1144, 121), 'tw': (1120, 97, 1144, 121)}, color={'cn': (158, 165, 176), 'en': (158, 165, 176), 'jp': (158, 165, 176), 'tw': (158, 165, 176)}, button={'cn': (1120, 97, 1144, 121), 'en': (1120, 97, 1144, 121), 'jp': (1120, 97, 1144, 121), 'tw': (1120, 97, 1144, 121)}, file={'cn': './assets/cn/raid/RPG_STATUS_POPUP.png', 'en': './assets/en/raid/RPG_STATUS_POPUP.png', 'jp': './assets/jp/raid/RPG_STATUS_POPUP.png', 'tw': './assets/tw/raid/RPG_STATUS_POPUP.png'}) diff --git a/alas_wrapped/module/raid/raid.py b/alas_wrapped/module/raid/raid.py index 49eb2e2646..2d160eb8ab 100644 --- a/alas_wrapped/module/raid/raid.py +++ b/alas_wrapped/module/raid/raid.py @@ -2,11 +2,10 @@ import numpy as np import module.config.server as server -from module.base.decorator import run_once from module.base.timer import Timer from module.campaign.campaign_event import CampaignEvent from module.combat.assets import * -from module.exception import OilExhausted, ScriptError +from module.exception import ScriptError from module.logger import logger from module.map.map_operation import MapOperation from module.ocr.ocr import Digit, DigitCounter @@ -16,6 +15,15 @@ from module.ui.page import page_rpg_stage +class RaidCounterPostMixin(DigitCounter): + def after_process(self, result): + # fix result like "915/", "1515" + result = result.strip('/') + if result.isdigit() and len(result) > 2 and result.endswith('15'): + result = f'{result[:-2]}/15' + return result + + class RaidCounter(DigitCounter): def pre_process(self, image): image = super().pre_process(image) @@ -83,6 +91,8 @@ def raid_name_shorten(name): return "RPG" elif name == 'raid_20250116': return 'CHIENWU' + elif name == 'raid_20260212': + return 'CHANGWU' else: raise ScriptError(f'Unknown raid name: {name}') @@ -158,6 +168,11 @@ def raid_ocr(raid, mode): return Digit(button, letter=(247, 223, 222), threshold=128) else: return DigitCounter(button, letter=(0, 0, 0), threshold=128) + elif raid == 'CHANGWU': + if mode == 'ex': + return Digit(button, letter=(255, 239, 215), threshold=128) + else: + return RaidCounterPostMixin(button, lang='cnocr', letter=(154, 148, 133), threshold=128) def pt_ocr(raid): @@ -187,9 +202,44 @@ def pt_ocr(raid): return HuanChangPtOcr(button, letter=(23, 20, 6), threshold=128) elif raid == 'CHIENWU': return Digit(button, letter=(255, 231, 231), threshold=128) + elif raid == 'CHANGWU': + return Digit(button, letter=(255, 239, 215), threshold=128) class Raid(MapOperation, RaidCombat, CampaignEvent): + @property + def _raid_has_oil_icon(self): + """ + Game devs are too asshole to drop oil display for UI design + https://github.com/LmeSzinc/AzurLaneAutoScript/issues/5214 + """ + return False + + def triggered_stop_condition(self, oil_check=False, pt_check=False, coin_check=False): + """ + Returns: + bool: If triggered a stop condition. + """ + # Oil limit + if oil_check: + if self.get_oil() < max(500, self.config.StopCondition_OilLimit): + logger.hr('Triggered stop condition: Oil limit') + self.config.task_delay(minute=(120, 240)) + return True + # Event limit + if pt_check: + if self.event_pt_limit_triggered(): + logger.hr('Triggered stop condition: Event PT limit') + return True + # TaskBalancer + if coin_check: + if self.config.TaskBalancer_Enable and self.triggered_task_balancer(): + logger.hr('Triggered stop condition: Coin limit') + self.handle_task_balancer() + return True + + return False + def combat_preparation(self, balance_hp=False, emotion_reduce=False, auto='combat_auto', fleet_index=1): """ Args: @@ -199,32 +249,20 @@ def combat_preparation(self, balance_hp=False, emotion_reduce=False, auto='comba fleet_index (int): """ logger.info('Combat preparation.') - skip_first_screenshot = True # No need, already waited in `raid_execute_once()` # if emotion_reduce: # self.emotion.wait(fleet_index) - @run_once - def check_oil(): - if self.get_oil() < max(500, self.config.StopCondition_OilLimit): - logger.hr('Triggered oil limit') - raise OilExhausted - - @run_once - def check_coin(): - if self.config.TaskBalancer_Enable and self.triggered_task_balancer(): - logger.hr('Triggered stop condition: Coin limit') - self.handle_task_balancer() - return True - + checked = False for _ in self.loop(): - if self.appear(BATTLE_PREPARATION, offset=(30, 20)): if self.handle_combat_automation_set(auto=auto == 'combat_auto'): continue - check_oil() - check_coin() + if not checked and self._raid_has_oil_icon: + checked = True + if self.triggered_stop_condition(oil_check=True, coin_check=True): + self.config.task_stop() if self.handle_raid_ticket_use(): continue if self.handle_retirement(): @@ -281,7 +319,7 @@ def raid_enter(self, mode, raid, skip_first_screenshot=True): if self.appear(entrance, offset=(10, 10), interval=5): # Items appear from right # Check PT when entrance appear - if self.event_pt_limit_triggered(): + if self.triggered_stop_condition(pt_check=True): self.config.task_stop() self.device.click(entrance) continue @@ -341,7 +379,6 @@ def get_event_pt(self): Pages: in: page_raid """ - from module.log_res.log_res import LogRes skip_first_screenshot = True timeout = Timer(1.5, count=5).start() ocr = pt_ocr(self.config.Campaign_Event) @@ -356,12 +393,10 @@ def get_event_pt(self): pt = ocr.ocr(self.device.image) if timeout.reached(): logger.warning('Wait PT timeout, assume it is') - LogRes(self.config).Pt = pt return pt if pt in [70000, 70001]: continue else: - LogRes(self.config).Pt = pt return pt else: logger.info(f'Raid {self.config.Campaign_Event} does not support PT ocr, skip') diff --git a/alas_wrapped/module/raid/run.py b/alas_wrapped/module/raid/run.py index e561c05ff4..d9a26050b3 100644 --- a/alas_wrapped/module/raid/run.py +++ b/alas_wrapped/module/raid/run.py @@ -1,17 +1,17 @@ from module.base.timer import Timer from module.campaign.campaign_event import CampaignEvent -from module.exception import OilExhausted, ScriptEnd, ScriptError +from module.exception import ScriptEnd, ScriptError from module.logger import logger from module.raid.assets import RAID_REWARDS from module.raid.raid import Raid, raid_ocr -from module.ui.page import page_raid, page_rpg_stage +from module.ui.page import page_campaign_menu, page_raid, page_rpg_stage class RaidRun(Raid, CampaignEvent): run_count: int run_limit: int - def triggered_stop_condition(self): + def triggered_stop_condition(self, oil_check=False, pt_check=False, coin_check=False): """ Returns: bool: If triggered a stop condition. @@ -23,7 +23,7 @@ def triggered_stop_condition(self): self.config.Scheduler_Enable = False return True - return False + return super().triggered_stop_condition(oil_check=oil_check, pt_check=pt_check, coin_check=coin_check) def get_remain(self, mode, skip_first_screenshot=True): """ @@ -93,9 +93,11 @@ def run(self, name='', mode='', total=0): else: logger.info(f'Count: {self.run_count}') - # End - if self.triggered_stop_condition(): - break + # UI switches + if not self._raid_has_oil_icon: + self.ui_ensure(page_campaign_menu) + if self.triggered_stop_condition(oil_check=True, coin_check=True): + break # UI ensure self.device.stuck_record_clear() @@ -108,7 +110,7 @@ def run(self, name='', mode='', total=0): self.disable_event_on_raid() # End for mode EX - if mode == 'ex': + if mode == 'ex' and not self.is_raid_rpg(): if not self.get_remain(mode): logger.info('Triggered stop condition: Zero ' 'raid tickets to do EX mode') @@ -123,10 +125,6 @@ def run(self, name='', mode='', total=0): self.device.click_record_clear() try: self.raid_execute_once(mode=mode, raid=name) - except OilExhausted: - logger.hr('Triggered stop condition: Oil limit') - self.config.task_delay(minute=(120, 240)) - break except ScriptEnd as e: logger.hr('Script end') logger.info(str(e)) diff --git a/alas_wrapped/module/shop/shop_status.py b/alas_wrapped/module/shop/shop_status.py index 472e676820..af0644dfce 100644 --- a/alas_wrapped/module/shop/shop_status.py +++ b/alas_wrapped/module/shop/shop_status.py @@ -2,7 +2,6 @@ from module.ocr.ocr import Digit from module.shop.assets import * from module.ui.ui import UI -from module.log_res.log_res import LogRes if server.server != 'jp': OCR_SHOP_GEMS = Digit(SHOP_GEMS, letter=(255, 243, 82), name='OCR_SHOP_GEMS') @@ -34,8 +33,6 @@ def status_get_gold_coins(self): in: """ amount = OCR_SHOP_GOLD_COINS.ocr(self.device.image) - LogRes(self.config).Coin = amount - self.config.update() return amount def status_get_gems(self): @@ -47,8 +44,6 @@ def status_get_gems(self): in: page_shop, medal shop """ amount = OCR_SHOP_GEMS.ocr(self.device.image) - LogRes(self.config).Gem = amount - self.config.update() return amount def status_get_medal(self): @@ -60,8 +55,6 @@ def status_get_medal(self): in: page_shop, medal shop """ amount = OCR_SHOP_MEDAL.ocr(self.device.image) - LogRes(self.config).Medal = amount - self.config.update() return amount def status_get_merit(self): @@ -73,8 +66,6 @@ def status_get_merit(self): in: page_shop, merit shop """ amount = OCR_SHOP_MERIT.ocr(self.device.image) - LogRes(self.config).Merit = amount - self.config.update() return amount def status_get_guild_coins(self): @@ -86,8 +77,6 @@ def status_get_guild_coins(self): in: page_shop, guild shop """ amount = OCR_SHOP_GUILD_COINS.ocr(self.device.image) - LogRes(self.config).GuildCoin = amount - self.config.update() return amount def status_get_core(self): @@ -99,8 +88,6 @@ def status_get_core(self): in: page_shop, core shop """ amount = OCR_SHOP_CORE.ocr(self.device.image) - LogRes(self.config).Core = amount - self.config.update() return amount def status_get_voucher(self): diff --git a/alas_wrapped/module/storage/storage.py b/alas_wrapped/module/storage/storage.py index a18423c6aa..62be757ff5 100644 --- a/alas_wrapped/module/storage/storage.py +++ b/alas_wrapped/module/storage/storage.py @@ -45,8 +45,12 @@ def _storage_box_template(rarity): def _handle_use_box_amount(self, amount): """ + Args: + amount (int): Expected amount to set + Returns: - bool: if clicked + int: Actual amount set in the UI. + May be less than expected if not enough boxes available. Pages: in: SHOP_BUY_CONFIRM_AMOUNT @@ -84,6 +88,7 @@ def _handle_use_box_amount(self, amount): logger.info(f'Set box amount: {amount}') skip_first = True retry = Timer(1, count=2) + click_count = 0 for _ in self.loop(): if skip_first: skip_first = False @@ -92,13 +97,19 @@ def _handle_use_box_amount(self, amount): diff = amount - current if diff == 0: break + if click_count >= 2: + logger.warning(f'Box amount stuck at {current}, ' + f'requested {amount} but only {current} available') + break if retry.reached(): button = AMOUNT_PLUS if diff > 0 else AMOUNT_MINUS self.device.multi_click(button, n=abs(diff), interval=(0.1, 0.2)) + click_count += 1 retry.reset() - return True + logger.info(f'Box amount set to {current}') + return current def _storage_use_one_box(self, button, amount=1): """ @@ -155,10 +166,10 @@ def _storage_use_one_box(self, button, amount=1): # use match_template_color on BOX_AMOUNT_CONFIRM # a long animation that opens a box, will be on the top of BOX_AMOUNT_CONFIRM if self.match_template_color(BOX_AMOUNT_CONFIRM, offset=(20, 20), interval=5): - self._handle_use_box_amount(amount) + actual = self._handle_use_box_amount(amount) self.device.click(BOX_AMOUNT_CONFIRM) self.interval_reset(BOX_AMOUNT_CONFIRM) - used = amount + used = actual continue if self.appear_then_click(EQUIP_CONFIRM, offset=(20, 20), interval=5): self.interval_reset(MATERIAL_CHECK) diff --git a/alas_wrapped/module/tactical/tactical_class.py b/alas_wrapped/module/tactical/tactical_class.py index 672e705790..b25f13a485 100644 --- a/alas_wrapped/module/tactical/tactical_class.py +++ b/alas_wrapped/module/tactical/tactical_class.py @@ -495,7 +495,7 @@ def tactical_class_receive(self, skip_first_screenshot=True): if self.appear(MISSION_POPUP_GO, offset=self._popup_offset, interval=2): self.device.click(MISSION_POPUP_ACK) continue - if not study_finished and self.appear(TACTICAL_CLASS_CANCEL, offset=(30, 30), interval=2) \ + if self.appear(TACTICAL_CLASS_CANCEL, offset=(30, 30), interval=2) \ and self.appear(TACTICAL_CLASS_START, offset=(30, 30)): if self._tactical_books_choose(): self.dock_select_index = 0 diff --git a/alas_wrapped/module/ui/page.py b/alas_wrapped/module/ui/page.py index 4ea6a94036..2705a53a43 100644 --- a/alas_wrapped/module/ui/page.py +++ b/alas_wrapped/module/ui/page.py @@ -215,10 +215,16 @@ def link(self, button, destination): page_main_white.link(button=MAIN_GOTO_EVENT_LIST_WHITE, destination=page_event_list) # Raid +# before +# page_raid = Page(RAID_CHECK) +# page_raid.link(button=GOTO_MAIN, destination=page_main) +# page_main.link(button=MAIN_GOTO_RAID, destination=page_raid) +# page_main_white.link(button=MAIN_GOTO_RAID_WHITE, destination=page_raid) +# after 2026.02.12 page_raid = Page(RAID_CHECK) page_raid.link(button=GOTO_MAIN, destination=page_main) -page_main.link(button=MAIN_GOTO_RAID, destination=page_raid) -page_main_white.link(button=MAIN_GOTO_RAID_WHITE, destination=page_raid) +page_raid.link(button=BACK_ARROW, destination=page_campaign_menu) +page_campaign_menu.link(button=CAMPAIGN_MENU_GOTO_EVENT, destination=page_raid) # Dock page_dock = Page(DOCK_CHECK) @@ -328,8 +334,10 @@ def link(self, button, destination): page_rpg_story = Page(RPG_GOTO_STAGE) page_rpg_stage.link(button=RPG_GOTO_STORY, destination=page_rpg_story) page_rpg_stage.link(button=RPG_HOME, destination=page_main) +page_rpg_stage.link(button=RPG_BACK, destination=page_campaign_menu) page_rpg_story.link(button=RPG_GOTO_STAGE, destination=page_rpg_stage) page_rpg_story.link(button=RPG_HOME, destination=page_main) +page_rpg_story.link(button=RPG_BACK, destination=page_campaign_menu) page_campaign_menu.link(button=CAMPAIGN_MENU_GOTO_EVENT, destination=page_rpg_stage) # page_main.link(button=MAIN_GOTO_RAID, destination=page_rpg_stage) diff --git a/alas_wrapped/module/ui/setting.py b/alas_wrapped/module/ui/setting.py index 84f3e4b6dd..7a87fd7ef7 100644 --- a/alas_wrapped/module/ui/setting.py +++ b/alas_wrapped/module/ui/setting.py @@ -146,9 +146,7 @@ def _set_execute(self, **kwargs): if clicks: if retry.reached(): for button in clicks: - # Setting toggles can legitimately require repeated taps; - # rely on timeout here instead of global click-loop detector. - self.main.device.click(button, control_check=False) + self.main.device.click(button) retry.reset() else: return True diff --git a/alas_wrapped/module/ui/ui.py b/alas_wrapped/module/ui/ui.py index 55dfbe8346..92ec1a8255 100644 --- a/alas_wrapped/module/ui/ui.py +++ b/alas_wrapped/module/ui/ui.py @@ -1,9 +1,7 @@ from module.base.button import Button from module.base.decorator import run_once from module.base.timer import Timer -from module.coalition.assets import NEONCITY_FLEET_PREPARATION, NEONCITY_PREPARATION_EXIT, DAL_DIFFICULTY_EXIT from module.combat.assets import GET_ITEMS_1, GET_ITEMS_2, GET_SHIP -from module.event_hospital.assets import HOSIPITAL_CLUE_CHECK, HOSPITAL_BATTLE_EXIT from module.exception import (GameNotRunningError, GamePageUnknownError, RequestHumanTakeover) from module.exercise.assets import EXERCISE_PREPARATION @@ -19,7 +17,7 @@ from module.os_handler.assets import (AUTO_SEARCH_REWARD, EXCHANGE_CHECK, RESET_FLEET_PREPARATION, RESET_TICKET_POPUP) from module.raid.assets import * from module.ui.assets import * -from module.ui.page import Page, page_campaign, page_event, page_main, page_main_white, page_sp +from module.ui.page import Page, page_academy, page_campaign, page_event, page_main, page_main_white, page_sp from module.ui_white.assets import * @@ -39,6 +37,11 @@ def ui_page_appear(self, page, offset=(30, 30), interval=0): if self.appear(page_main.check_button, offset=(5, 5), interval=interval): return True return False + # shitty EN localization changing font width of ACADEMY title, + # check other buttons also + if self.config.SERVER == 'en' and page == page_academy: + if self.appear(ACADEMY_GOTO_MUNITIONS, offset=offset, interval=interval): + return True return self.appear(page.check_button, offset=offset, interval=interval) def is_in_main(self, offset=(30, 30), interval=0): @@ -560,10 +563,10 @@ def ui_additional(self, get_ship=True): # return True # Neon city (coalition_20250626) # FASHION (coalition_20260122) reuse NEONCITY - if self.appear(NEONCITY_FLEET_PREPARATION, offset=(20, 20), interval=3): - logger.info(f'{NEONCITY_FLEET_PREPARATION} -> {NEONCITY_PREPARATION_EXIT}') - self.device.click(NEONCITY_PREPARATION_EXIT) - return True + # if self.appear(NEONCITY_FLEET_PREPARATION, offset=(20, 20), interval=3): + # logger.info(f'{NEONCITY_FLEET_PREPARATION} -> {NEONCITY_PREPARATION_EXIT}') + # self.device.click(NEONCITY_PREPARATION_EXIT) + # return True # DATE A LANE (coalition_20251120) # if self.appear_then_click(DAL_DIFFICULTY_EXIT, offset=(20, 20), interval=3): # return True diff --git a/alas_wrapped/module/war_archives/assets.py b/alas_wrapped/module/war_archives/assets.py index 561299fcc7..b95de8d8ce 100644 --- a/alas_wrapped/module/war_archives/assets.py +++ b/alas_wrapped/module/war_archives/assets.py @@ -30,6 +30,7 @@ TEMPLATE_PARALLEL_SUPERIMPOSITION = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_PARALLEL_SUPERIMPOSITION.png', 'en': './assets/cn/war_archives/TEMPLATE_PARALLEL_SUPERIMPOSITION.png', 'jp': './assets/cn/war_archives/TEMPLATE_PARALLEL_SUPERIMPOSITION.png', 'tw': './assets/cn/war_archives/TEMPLATE_PARALLEL_SUPERIMPOSITION.png'}) TEMPLATE_PLEDGE_OF_THE_RADIANT_COURT = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_PLEDGE_OF_THE_RADIANT_COURT.png', 'en': './assets/cn/war_archives/TEMPLATE_PLEDGE_OF_THE_RADIANT_COURT.png', 'jp': './assets/cn/war_archives/TEMPLATE_PLEDGE_OF_THE_RADIANT_COURT.png', 'tw': './assets/cn/war_archives/TEMPLATE_PLEDGE_OF_THE_RADIANT_COURT.png'}) TEMPLATE_PRELUDE_UNDER_THE_MOON = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_PRELUDE_UNDER_THE_MOON.png', 'en': './assets/cn/war_archives/TEMPLATE_PRELUDE_UNDER_THE_MOON.png', 'jp': './assets/cn/war_archives/TEMPLATE_PRELUDE_UNDER_THE_MOON.png', 'tw': './assets/cn/war_archives/TEMPLATE_PRELUDE_UNDER_THE_MOON.png'}) +TEMPLATE_REVELATIONS_OF_DUST = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_REVELATIONS_OF_DUST.png', 'en': './assets/en/war_archives/TEMPLATE_REVELATIONS_OF_DUST.png', 'jp': './assets/cn/war_archives/TEMPLATE_REVELATIONS_OF_DUST.png', 'tw': './assets/cn/war_archives/TEMPLATE_REVELATIONS_OF_DUST.png'}) TEMPLATE_RONDO_AT_RAINBOWS_END = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_RONDO_AT_RAINBOWS_END.png', 'en': './assets/en/war_archives/TEMPLATE_RONDO_AT_RAINBOWS_END.png', 'jp': './assets/cn/war_archives/TEMPLATE_RONDO_AT_RAINBOWS_END.png', 'tw': './assets/cn/war_archives/TEMPLATE_RONDO_AT_RAINBOWS_END.png'}) TEMPLATE_SCHERZO_OF_IRON_AND_BLOOD = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_SCHERZO_OF_IRON_AND_BLOOD.png', 'en': './assets/en/war_archives/TEMPLATE_SCHERZO_OF_IRON_AND_BLOOD.png', 'jp': './assets/cn/war_archives/TEMPLATE_SCHERZO_OF_IRON_AND_BLOOD.png', 'tw': './assets/cn/war_archives/TEMPLATE_SCHERZO_OF_IRON_AND_BLOOD.png'}) TEMPLATE_SKYBOUND_ORATORIO = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_SKYBOUND_ORATORIO.png', 'en': './assets/en/war_archives/TEMPLATE_SKYBOUND_ORATORIO.png', 'jp': './assets/cn/war_archives/TEMPLATE_SKYBOUND_ORATORIO.png', 'tw': './assets/cn/war_archives/TEMPLATE_SKYBOUND_ORATORIO.png'}) diff --git a/alas_wrapped/module/war_archives/dictionary.py b/alas_wrapped/module/war_archives/dictionary.py index 92bcdf53ca..d7cce38881 100644 --- a/alas_wrapped/module/war_archives/dictionary.py +++ b/alas_wrapped/module/war_archives/dictionary.py @@ -44,4 +44,5 @@ 'war_archives_20231026_cn': TEMPLATE_TEMPESTA_AND_THE_FOUNTAIN_OF_YOUTH, 'war_archives_20220915_cn': TEMPLATE_VIOLET_TEMPEST_BLOOMING_LYCORIS, 'war_archives_20221222_cn': TEMPLATE_PARALLEL_SUPERIMPOSITION, + 'war_archives_20230223_cn': TEMPLATE_REVELATIONS_OF_DUST, } diff --git a/alas_wrapped/module/webui/app.py b/alas_wrapped/module/webui/app.py index 369356a687..5848d8b812 100644 --- a/alas_wrapped/module/webui/app.py +++ b/alas_wrapped/module/webui/app.py @@ -1,4 +1,3 @@ -import re import argparse import json import queue @@ -52,8 +51,6 @@ filepath_config, read_file, ) -from module.config.utils import time_delta -from module.log_res.log_res import LogRes from module.logger import logger from module.ocr.rpc import start_ocr_server_process, stop_ocr_server_process from module.submodule.submodule import load_config @@ -99,34 +96,10 @@ task_handler = TaskHandler() -def timedelta_to_text(delta=None): - time_delta_name_suffix_dict = { - 'Y': 'YearsAgo', - 'M': 'MonthsAgo', - 'D': 'DaysAgo', - 'h': 'HoursAgo', - 'm': 'MinutesAgo', - 's': 'SecondsAgo', - } - time_delta_name_prefix = 'Gui.Overview.' - time_delta_name_suffix = 'NoData' - time_delta_display = '' - if isinstance(delta, dict): - for _key in delta: - if delta[_key]: - time_delta_name_suffix = time_delta_name_suffix_dict[_key] - time_delta_display = delta[_key] - break - time_delta_display = str(time_delta_display) - time_delta_name = time_delta_name_prefix + time_delta_name_suffix - return time_delta_display + t(time_delta_name) - - class AlasGUI(Frame): ALAS_MENU: Dict[str, Dict[str, List[str]]] ALAS_ARGS: Dict[str, Dict[str, Dict[str, Dict[str, str]]]] theme = "default" - _log = RichLog def initial(self) -> None: self.ALAS_MENU = read_file(filepath_args("menu", self.alas_mod)) @@ -469,43 +442,22 @@ def alas_overview(self) -> None: ) log = RichLog("log") - self._log = log - self._log.dashboard_arg_group = LogRes(self.alas_config).groups with use_scope("logs"): - if 'Maa' in self.ALAS_ARGS: - put_scope( - "log-bar", - [ - put_text(t("Gui.Overview.Log")).style( - "font-size: 1.25rem; margin: auto .5rem auto;" - ), - put_scope( - "log-bar-btns", - [ - put_scope("log_scroll_btn"), - ], - ), - ], - ), - else: - put_scope( - "log-bar", - [ - put_text(t("Gui.Overview.Log")).style( - "font-size: 1.25rem; margin: auto .5rem auto;" - ), - put_scope( - "log-bar-btns", - [ - put_scope("log_scroll_btn"), - put_scope("dashboard_btn"), - ], - ), - put_html('
'), - put_scope("dashboard"), - ], - ), + put_scope( + "log-bar", + [ + put_text(t("Gui.Overview.Log")).style( + "font-size: 1.25rem; margin: auto .5rem auto;" + ), + put_scope( + "log-bar-btns", + [ + put_scope("log_scroll_btn"), + ], + ), + ], + ) put_scope("log", [put_html("")]) log.console.width = log.get_width() @@ -520,29 +472,12 @@ def alas_overview(self) -> None: color_off="off", scope="log_scroll_btn", ) - switch_dashboard = BinarySwitchButton( - label_on=t("Gui.Button.DashboardON"), - label_off=t("Gui.Button.DashboardOFF"), - onclick_on=lambda: self.set_dashboard_display(False), - onclick_off=lambda: self.set_dashboard_display(True), - get_state=lambda: log.display_dashboard, - color_on="off", - color_off="on", - scope="dashboard_btn", - ) + self.task_handler.add(switch_scheduler.g(), 1, True) self.task_handler.add(switch_log_scroll.g(), 1, True) - if 'Maa' not in self.ALAS_ARGS: - self.task_handler.add(switch_dashboard.g(), 1, True) self.task_handler.add(self.alas_update_overview_task, 10, True) - if 'Maa' not in self.ALAS_ARGS: - self.task_handler.add(self.alas_update_dashboard, 10, True) self.task_handler.add(log.put_log(self.alas), 0.25, True) - def set_dashboard_display(self, b): - self._log.set_dashboard_display(b) - self.alas_update_dashboard(True) - def _init_alas_config_watcher(self) -> None: def put_queue(path, value): self.modified_config_queue.put({"name": path, "value": value}) @@ -579,7 +514,6 @@ def _save_config( config_updater: AzurLaneConfig = State.config_updater, ) -> None: try: - skip_time_record = False valid = [] invalid = [] config = config_updater.read_file(config_name) @@ -683,105 +617,6 @@ def put_task(func: Function): else: put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") - def _update_dashboard(self, num=None, groups_to_display=None): - x = 0 - _num = 10000 if num is None else num - _arg_group = self._log.dashboard_arg_group if groups_to_display is None else groups_to_display - time_now = datetime.now().replace(microsecond=0) - for group_name in _arg_group: - group = deep_get(d=self.alas_config.data, keys=f'Dashboard.{group_name}') - if group is None: - continue - - value = str(group['Value']) - if 'Limit' in group.keys(): - value_limit = f' / {group["Limit"]}' - value_total = '' - elif 'Total' in group.keys(): - value_total = f' ({group["Total"]})' - value_limit = '' - elif group_name == 'Pt': - value_limit = ' / ' + re.sub(r'[,.\'",。]', '', - str(deep_get(self.alas_config.data, 'EventGeneral.EventGeneral.PtLimit'))) - if value_limit == ' / 0': - value_limit = '' - else: - value_limit = '' - value_total = '' - # value = value + value_limit + value_total - - value_time = group['Record'] - if value_time is None or value_time == datetime(2020, 1, 1, 0, 0, 0): - value_time = datetime(2023, 1, 1, 0, 0, 0) - - # Handle time delta - if value_time == datetime(2023, 1, 1, 0, 0, 0): - value = 'None' - delta = timedelta_to_text() - else: - delta = timedelta_to_text(time_delta(value_time - time_now)) - if group_name not in self._log.last_display_time.keys(): - self._log.last_display_time[group_name] = '' - if self._log.last_display_time[group_name] == delta and not self._log.first_display: - continue - self._log.last_display_time[group_name] = delta - - # if self._log.first_display: - # Handle width - # value_width = len(value) * 0.7 + 0.6 if value != 'None' else 4.5 - # value_width = str(value_width/1.12) + 'rem' if self.is_mobile else str(value_width) + 'rem' - value_limit = '' if value == 'None' else value_limit - # limit_width = len(value_limit) * 0.7 - # limit_width = str(limit_width) + 'rem' - value_total = '' if value == 'None' else value_total - limit_style = '--dashboard-limit--' if value_limit else '--dashboard-total--' - value_limit = value_limit if value_limit else value_total - # Handle dot color - _color = f"""background-color:{deep_get(d=group, keys='Color').replace('^', '#')}""" - color = f'
' - with use_scope(group_name, clear=True): - put_row( - [ - put_html(color), - put_scope( - f"{group_name}_group", - [ - put_column( - [ - put_row( - [ - put_text(value - ).style(f'--dashboard-value--'), - put_text(value_limit - ).style(limit_style), - ], - ).style('grid-template-columns:min-content auto;align-items: baseline;'), - put_text( - t(f'Gui.Overview.{group_name}') + " - " + delta - ).style('---dashboard-help--') - ], - size="auto auto", - ), - ], - ), - ], - size="20px 1fr" - ).style("height: 1fr"), - x += 1 - if x >= _num: - break - if self._log.first_display: - self._log.first_display = False - - def alas_update_dashboard(self, _clear=False): - if not self.visible: - return - with use_scope("dashboard", clear=_clear): - if not self._log.display_dashboard: - self._update_dashboard(num=4, groups_to_display=['Oil', 'Coin', 'Gem', 'Pt']) - elif self._log.display_dashboard: - self._update_dashboard() - @use_scope("content", clear=True) def alas_daemon_overview(self, task: str) -> None: self.init_menu(name=task) @@ -1100,17 +935,17 @@ def u(state): def dev_utils(self) -> None: self.init_menu(name="Utils") self.set_title(t("Gui.MenuDevelop.Utils")) - put_button(label=t("Gui.MenuDevelop.RaiseException"), onclick=raise_exception) + put_button(label="Raise exception", onclick=raise_exception) def _force_restart(): if State.restart_event is not None: - toast(t("Gui.Toast.AlasRestart"), duration=0, color="error") + toast("Alas will restart in 3 seconds", duration=0, color="error") clearup() State.restart_event.set() else: - toast(t("Gui.Toast.ReloadEnabled"), color="error") + toast("Reload not enabled", color="error") - put_button(label=t("Gui.MenuDevelop.ForceRestart"), onclick=_force_restart) + put_button(label="Force restart", onclick=_force_restart) @use_scope("content", clear=True) def dev_remote(self) -> None: diff --git a/alas_wrapped/module/webui/patch.py b/alas_wrapped/module/webui/patch.py index 1e4d7407f5..6f799d85f7 100644 --- a/alas_wrapped/module/webui/patch.py +++ b/alas_wrapped/module/webui/patch.py @@ -50,20 +50,16 @@ def patch_mimetype(): all deployment, we use the builtin mimetype table only. """ import mimetypes - if mimetypes.inited: - # ohno mimetypes already inited - db = mimetypes.MimeTypes() - mimetypes._db = db - # override global variable - mimetypes.encodings_map = db.encodings_map - mimetypes.suffix_map = db.suffix_map - mimetypes.types_map = db.types_map[True] - mimetypes.common_types = db.types_map[False] - else: - # init db with the default table - db = mimetypes.MimeTypes() - mimetypes._db = db - mimetypes.inited = True + # lock as inited + mimetypes.inited = True + # create a new clean instance + db = mimetypes.MimeTypes(filenames=()) + mimetypes._db = db + # override global variable + mimetypes.encodings_map = db.encodings_map + mimetypes.suffix_map = db.suffix_map + mimetypes.types_map = db.types_map[True] + mimetypes.common_types = db.types_map[False] def fix_py37_subprocess_communicate(): diff --git a/alas_wrapped/module/webui/pin.py b/alas_wrapped/module/webui/pin.py index 80ef586236..4d70243450 100644 --- a/alas_wrapped/module/webui/pin.py +++ b/alas_wrapped/module/webui/pin.py @@ -5,7 +5,7 @@ from pywebio.io_ctrl import Output from pywebio.output import OutputPosition -from pywebio.pin import _pin_output, check_dom_name_value, pin_update +from pywebio.pin import _pin_output, check_dom_name_value def put_input(name, type='text', *, label='', value=None, placeholder=None, readonly=None, datalist=None, diff --git a/alas_wrapped/module/webui/widgets.py b/alas_wrapped/module/webui/widgets.py index 86e8153559..ad879d9ddd 100644 --- a/alas_wrapped/module/webui/widgets.py +++ b/alas_wrapped/module/webui/widgets.py @@ -1,6 +1,5 @@ import copy import json -import pywebio.pin import random import string from typing import Any, Callable, Dict, Generator, List, Optional, TYPE_CHECKING, Union @@ -75,8 +74,6 @@ def set_scroll(self, b: bool) -> None: class RichLog: - last_display_time: dict - def __init__(self, scope, font_width="0.559") -> None: self.scope = scope self.font_width = font_width @@ -96,10 +93,6 @@ def __init__(self, scope, font_width="0.559") -> None: # self._callback_thread = None # self._width = 80 self.keep_bottom = True - self.display_dashboard = False - self.first_display = True - self.last_display_time = {} - self.dashboard_arg_group = None if State.theme == "dark": self.terminal_theme = DARK_TERMINAL_THEME else: @@ -145,11 +138,6 @@ def set_scroll(self, b: bool) -> None: # use for lambda callback function self.keep_bottom = b - def set_dashboard_display(self, b: bool) -> None: - # use for lambda callback function. Copied. - self.display_dashboard = b - self.first_display = True - def get_width(self): js = """ let canvas = document.createElement('canvas'); diff --git a/alas_wrapped/requirements-in.txt b/alas_wrapped/requirements-in.txt index 0dfe2d1d25..3dedf41d90 100644 --- a/alas_wrapped/requirements-in.txt +++ b/alas_wrapped/requirements-in.txt @@ -1,6 +1,6 @@ # Image processing -numpy==1.19.5 -scipy==1.7.3 +numpy==1.16.6 +scipy==1.4.1 pillow opencv-python imageio==2.27.0 @@ -12,7 +12,7 @@ uiautomator2cache==0.3.0.1 wrapt==1.13.1 retrying lz4 -av==12.0.0 +av==10.0.0 psutil==5.9.3 # Utils diff --git a/alas_wrapped/requirements.txt b/alas_wrapped/requirements.txt index 4c4f6a3d30..72ed5c457b 100644 --- a/alas_wrapped/requirements.txt +++ b/alas_wrapped/requirements.txt @@ -1,13 +1,18 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile requirements-in.txt --python-version=3.9 --override=overrides.txt --output-file=requirements.txt --annotation-style=line --only-binary av -adbutils==0.11.0 # via uiautomator2, -r requirements-in.txt +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile --annotation-style=line --output-file=requirements.txt requirements-in.txt +# + +adbutils==0.11.0 # via -r requirements-in.txt, uiautomator2 aiofiles==0.7.0 # via -r requirements-in.txt alas-webapp==0.3.7 # via -r requirements-in.txt anyio==1.3.1 # via -r requirements-in.txt apkutils2==1.0.0 # via adbutils asgiref==3.4.1 # via uvicorn async-generator==1.10 # via anyio -av==12.0.0 # via -r requirements-in.txt +av==10.0.0 # via -r requirements-in.txt cached-property==1.5.2 # via uiautomator2 certifi==2021.5.30 # via requests cffi==1.15.0 # via gevent @@ -31,6 +36,7 @@ h11==0.12.0 # via uvicorn httptools==0.4.0 # via uvicorn idna==2.6 # via requests imageio==2.27.0 # via -r requirements-in.txt +importlib-metadata==4.8.1 # via click, prettytable inflection==0.5.1 # via -r requirements-in.txt jellyfish==0.11.2 # via -r requirements-in.txt kiwisolver==1.3.2 # via matplotlib @@ -39,19 +45,19 @@ lxml==4.6.3 # via uiautomator2 lz4==3.1.3 # via -r requirements-in.txt matplotlib==3.4.3 # via gluoncv msgpack==1.0.3 # via zerorpc -mxnet==1.6.0 # via cnocr, -r requirements-in.txt -numpy==1.19.5 # via cnocr, gluoncv, imageio, matplotlib, mxnet, opencv-python, scipy, --override overrides.txt, -r requirements-in.txt +mxnet==1.6.0 # via -r requirements-in.txt, cnocr +numpy==1.16.6 # via -r requirements-in.txt, cnocr, gluoncv, imageio, matplotlib, mxnet, opencv-python, scipy onepush==1.4.0 # via -r requirements-in.txt opencv-python==4.5.3.56 # via -r requirements-in.txt packaging==20.9 # via deprecation, uiautomator2 -pillow==8.3.2 # via cnocr, gluoncv, imageio, matplotlib, uiautomator2, -r requirements-in.txt +pillow==8.3.2 # via -r requirements-in.txt, cnocr, gluoncv, imageio, matplotlib, uiautomator2 portalocker==2.3.2 # via gluoncv prettytable==2.2.1 # via -r requirements-in.txt progress==1.6 # via uiautomator2 psutil==5.9.3 # via -r requirements-in.txt py==1.10.0 # via retry pycparser==2.21 # via cffi -pycryptodome==3.9.9 # via onepush, -r requirements-in.txt +pycryptodome==3.9.9 # via onepush pydantic==1.10.2 # via -r requirements-in.txt pyelftools==0.27 # via apkutils2 pygments==2.12.0 # via rich @@ -61,32 +67,35 @@ python-dateutil==2.8.2 # via matplotlib python-dotenv==0.19.0 # via uvicorn pywebio==1.6.2 # via -r requirements-in.txt pywin32==301 # via portalocker -pyyaml==5.4.1 # via uvicorn, -r requirements-in.txt -pyzmq==22.3.0 # via zerorpc, -r requirements-in.txt +pyyaml==5.4.1 # via -r requirements-in.txt, uvicorn +pyzmq==22.3.0 # via -r requirements-in.txt, zerorpc requests==2.18.4 # via adbutils, gluoncv, mxnet, onepush, uiautomator2 retry==0.9.2 # via adbutils, uiautomator2 retrying==1.3.3 # via -r requirements-in.txt rich==11.2.0 # via -r requirements-in.txt -scipy==1.7.3 # via gluoncv, -r requirements-in.txt -setuptools==81.0.0 # via gevent, zope-event, zope-interface +scipy==1.4.1 # via -r requirements-in.txt, gluoncv six==1.16.0 # via adbutils, cycler, python-dateutil, retrying, uiautomator2 sniffio==1.2.0 # via anyio starlette==0.14.2 # via -r requirements-in.txt tornado==6.1 # via pywebio -tqdm==4.62.3 # via gluoncv, -r requirements-in.txt -typing-extensions==4.3.0 # via pydantic +tqdm==4.62.3 # via -r requirements-in.txt, gluoncv +typing-extensions==4.3.0 # via asgiref, importlib-metadata, pydantic, rich, uvicorn ua-parser==0.10.0 # via user-agents uiautomator2==2.16.17 # via -r requirements-in.txt uiautomator2cache==0.3.0.1 # via -r requirements-in.txt urllib3==1.22 # via requests user-agents==2.2.0 # via pywebio -uvicorn==0.17.6 # via -r requirements-in.txt +uvicorn[standard]==0.17.6 # via -r requirements-in.txt watchgod==0.7 # via uvicorn wcwidth==0.2.5 # via prettytable websockets==10.0 # via uvicorn whichcraft==0.6.1 # via adbutils, uiautomator2 -wrapt==1.13.1 # via deprecated, -r requirements-in.txt +wrapt==1.13.1 # via -r requirements-in.txt, deprecated xmltodict==0.12.0 # via apkutils2 zerorpc==0.6.3 # via -r requirements-in.txt -zope-event==4.5.0 # via gevent -zope-interface==5.4.0 # via gevent +zipp==3.6.0 # via importlib-metadata +zope.event==4.5.0 # via gevent +zope.interface==5.4.0 # via gevent + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/alas_wrapped/webapp/packages/main/src/config.ts b/alas_wrapped/webapp/packages/main/src/config.ts index 7f11332122..b295c5eae3 100644 --- a/alas_wrapped/webapp/packages/main/src/config.ts +++ b/alas_wrapped/webapp/packages/main/src/config.ts @@ -3,20 +3,15 @@ const fs = require('fs'); const path = require('path'); // export const alasPath = 'D:/AzurLaneAutoScript'; -// When running from webapp/, ALAS root is the parent directory. -export const alasPath = path.resolve(process.cwd(), '..'); +export const alasPath = process.cwd(); const file = fs.readFileSync(path.join(alasPath, './config/deploy.yaml'), 'utf8'); const config = yaml.parse(file); const PythonExecutable = config.Deploy.Python.PythonExecutable; const WebuiPort = config.Deploy.Webui.WebuiPort.toString(); -const runConfig = (process.env.ALAS_RUN_CONFIG || '').trim(); export const pythonPath = (path.isAbsolute(PythonExecutable) ? PythonExecutable : path.join(alasPath, PythonExecutable)); export const webuiUrl = `http://127.0.0.1:${WebuiPort}`; export const webuiPath = 'gui.py'; export const webuiArgs = ['--port', WebuiPort, '--electron']; -if (runConfig) { - webuiArgs.push('--run', runConfig); -} export const dpiScaling = Boolean(config.Deploy.Webui.DpiScaling) || (config.Deploy.Webui.DpiScaling === undefined) ; From e2d399e81f9c5f481bcd99128d2869c66d25aa3e Mon Sep 17 00:00:00 2001 From: AI Agent Date: Tue, 3 Mar 2026 10:43:21 -0600 Subject: [PATCH 03/10] chore(config): update PatrickCustom runtime profile --- alas_wrapped/config/PatrickCustom.json | 93 ++++---------------------- 1 file changed, 12 insertions(+), 81 deletions(-) diff --git a/alas_wrapped/config/PatrickCustom.json b/alas_wrapped/config/PatrickCustom.json index e80226f5fb..0ea639baba 100644 --- a/alas_wrapped/config/PatrickCustom.json +++ b/alas_wrapped/config/PatrickCustom.json @@ -1,78 +1,10 @@ { - "Dashboard": { - "Oil": { - "Value": 15792, - "Limit": 17650, - "Color": "^000000", - "Record": "2026-02-04 21:16:13" - }, - "Coin": { - "Value": 0, - "Limit": 0, - "Color": "^FFAA33", - "Record": "2020-01-01 00:00:00" - }, - "Gem": { - "Value": 0, - "Color": "^FF3333", - "Record": "2020-01-01 00:00:00" - }, - "Pt": { - "Value": 3365, - "Color": "^00BFFF", - "Record": "2026-02-01 21:58:44" - }, - "Cube": { - "Value": 0, - "Color": "^33FFFF", - "Record": "2020-01-01 00:00:00" - }, - "ActionPoint": { - "Value": 43, - "Total": 1223, - "Color": "^0000FF", - "Record": "2026-02-04 22:26:41" - }, - "YellowCoin": { - "Value": 0, - "Color": "^FF8800", - "Record": "2020-01-01 00:00:00" - }, - "PurpleCoin": { - "Value": 0, - "Color": "^7700BB", - "Record": "2020-01-01 00:00:00" - }, - "Core": { - "Value": 0, - "Color": "^AAAAAA", - "Record": "2020-01-01 00:00:00" - }, - "Medal": { - "Value": 0, - "Color": "^FFDD00", - "Record": "2020-01-01 00:00:00" - }, - "Merit": { - "Value": 0, - "Color": "^FFFF00", - "Record": "2020-01-01 00:00:00" - }, - "GuildCoin": { - "Value": 0, - "Color": "^AAAAAA", - "Record": "2020-01-01 00:00:00" - }, - "Storage": { - "Storage": {} - } - }, "Alas": { "Emulator": { "Serial": "127.0.0.1:21513", "PackageName": "com.YoStarEN.AzurLane", "ServerName": "disabled", - "ScreenshotMethod": "DroidCast", + "ScreenshotMethod": "uiautomator2", "ControlMethod": "MaaTouch", "ScreenshotDedithering": false, "AdbRestart": true @@ -86,8 +18,7 @@ "HandleError": true, "SaveError": true, "OnePushConfig": "provider: null", - "ScreenshotLength": 6, - "RestartOnUnknownPage": true + "ScreenshotLength": 6 }, "Optimization": { "ScreenshotInterval": 0.3, @@ -136,7 +67,7 @@ "Restart": { "Scheduler": { "Enable": true, - "NextRun": "2026-03-02 22:54:54", + "NextRun": "2026-03-04 01:00:00", "Command": "Restart", "SuccessInterval": 0, "FailureInterval": 0, @@ -441,7 +372,7 @@ }, "Campaign": { "Name": "D3", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -515,7 +446,7 @@ }, "Campaign": { "Name": "D3", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -593,7 +524,7 @@ }, "Campaign": { "Name": "dynamic", - "Event": "raid_20250116", + "Event": "raid_20260212", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -816,7 +747,7 @@ }, "Campaign": { "Name": "dynamic", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -894,7 +825,7 @@ }, "Campaign": { "Name": "dynamic", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -972,7 +903,7 @@ }, "Campaign": { "Name": "dynamic", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -1050,7 +981,7 @@ }, "Campaign": { "Name": "dynamic", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -1124,7 +1055,7 @@ }, "Campaign": { "Name": "sp", - "Event": "event_20231221_cn", + "Event": "event_20260226_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, @@ -1201,7 +1132,7 @@ }, "Campaign": { "Name": "dynamic", - "Event": "raid_20250116", + "Event": "raid_20260212", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, From fc15be07d0e7b9c99e43c2bc9224cf295612c1fd Mon Sep 17 00:00:00 2001 From: AI Agent Date: Tue, 3 Mar 2026 18:27:55 -0600 Subject: [PATCH 04/10] Add LLMGuide docs and update PatrickCustom scheduling --- alas_wrapped/config/PatrickCustom.json | 8 +- docs/LLMGuide/README.md | 34 + docs/LLMGuide/device_setup.md | 520 ++++++++++++++ docs/LLMGuide/llm_control_harness.md | 916 ++++++++++++++++++++++++ docs/LLMGuide/startup_and_operations.md | 235 ++++++ docs/LLMGuide/task_catalog.md | 539 ++++++++++++++ 6 files changed, 2248 insertions(+), 4 deletions(-) create mode 100644 docs/LLMGuide/README.md create mode 100644 docs/LLMGuide/device_setup.md create mode 100644 docs/LLMGuide/llm_control_harness.md create mode 100644 docs/LLMGuide/startup_and_operations.md create mode 100644 docs/LLMGuide/task_catalog.md diff --git a/alas_wrapped/config/PatrickCustom.json b/alas_wrapped/config/PatrickCustom.json index 0ea639baba..bfd54cc4f5 100644 --- a/alas_wrapped/config/PatrickCustom.json +++ b/alas_wrapped/config/PatrickCustom.json @@ -1216,7 +1216,7 @@ "Commission": { "Scheduler": { "Enable": true, - "NextRun": "2026-02-21 21:08:17", + "NextRun": "2026-03-03 14:02:30", "Command": "Commission", "SuccessInterval": "30-60", "FailureInterval": "30-60", @@ -1233,7 +1233,7 @@ }, "Tactical": { "Scheduler": { - "Enable": true, + "Enable": false, "NextRun": "2026-02-21 20:37:37", "Command": "Tactical", "SuccessInterval": "30-60", @@ -1895,8 +1895,8 @@ }, "OpsiCrossMonth": { "Scheduler": { - "Enable": true, - "NextRun": "2026-03-01 00:50:00", + "Enable": false, + "NextRun": "2026-04-01 00:50:00", "Command": "OpsiCrossMonth", "SuccessInterval": 0, "FailureInterval": 120, diff --git a/docs/LLMGuide/README.md b/docs/LLMGuide/README.md new file mode 100644 index 0000000000..5a679e1d81 --- /dev/null +++ b/docs/LLMGuide/README.md @@ -0,0 +1,34 @@ +# LLMGuide — Operator Documentation for LLM Agents + +This folder contains practical documentation written **for and by LLM agents** operating this codebase. Unlike the architecture docs, this is operational truth learned from actually running things. + +## Documents + +| File | What it covers | +|------|---------------| +| `startup_and_operations.md` | How to start ALAS, monitor it, and recover from crashes. **Start here.** | +| `task_catalog.md` | Every ALAS task: what it does, failure modes, when to disable | +| `device_setup.md` | MEmu + ADB + screenshot methods. Root cause of the black screen problem. | +| `llm_control_harness.md` | MCP tools, how to drive the emulator directly, recovery playbooks | + +## The One Thing That Matters Right Now + +**MEmu render mode causes intermittent black screenshots.** + +Until the user changes MEmu → Settings → Display → Render mode → **DirectX or Software** (NOT OpenGL — OpenGL is the GPU mode that causes black frames), the bot will crash every 5-10 minutes with `GameStuckError`. The bot still makes partial progress (commissions collected, etc.) but is unreliable. + +Everything else in this guide assumes you understand that constraint. + +## Guiding Principles (learned the hard way) + +1. **Don't change screenshot method mid-session.** DroidCast needs to be set up in advance. Switching to DroidCast without installing the APK first will kill the session. + +2. **GameStuckError ≠ task is broken.** Usually it means screenshots are unreliable. Fix screenshots before disabling tasks. + +3. **OpsiCrossMonth runs once a month.** Disable it after March 1 reset. Re-enable April 1. + +4. **The venv lives at `alas_wrapped/.venv/`.** It was missing until 2026-03-03. If it disappears, recreate: `uv venv --python=3.9 .venv && uv pip install -r requirements.txt --overrides overrides.txt` + +5. **ALAS writes to PatrickCustom.json while running.** Don't edit it concurrently — you'll corrupt the schedule. Edit when the process is stopped, or edit only `Enable` and `NextRun` fields (ConfigWatcher merges these safely). + +6. **The process exits with code 4 on RequestHumanTakeover.** This is the bot saying "I give up, please look at me." Check `log/error/` for the screenshots and stack trace. diff --git a/docs/LLMGuide/device_setup.md b/docs/LLMGuide/device_setup.md new file mode 100644 index 0000000000..48ddf01c3c --- /dev/null +++ b/docs/LLMGuide/device_setup.md @@ -0,0 +1,520 @@ +# Device Setup Guide for LLM Agents (MEmu + ALAS) + +This guide is written for an LLM agent that is operating ALAS on a Windows host with +MEmu (Microvirt) as the Android emulator. It covers every screenshot method available +in ALAS, explains exactly why `adb screencap` returns black frames on MEmu, and documents +every known fix. + +--- + +## 1. ADB Serial for This Setup + +The configured serial for this installation is: + +``` +127.0.0.1:21513 +``` + +This value lives in `alas_wrapped/config/PatrickCustom.json`: + +```json +"Alas": { + "Emulator": { + "Serial": "127.0.0.1:21513", + ... + }, + "EmulatorInfo": { + "Emulator": "MEmuPlayer", + "name": "MEmu", + "path": "C:/Program Files/Microvirt/MEmu/MEmu.exe" + } +} +``` + +MEmu's default ADB port is `21503` for the first instance. Port `21513` is the second +instance (MEmu uses offsets of 10 per instance). Verify the emulator is live before +starting ALAS: + +```bash +adb connect 127.0.0.1:21513 +adb -s 127.0.0.1:21513 shell echo ok +``` + +If `echo ok` hangs or returns an error, the emulator is not ready. ADB responding does +not guarantee the Android boot sequence is complete. Wait until the launcher is visible +on screen before attempting to start ALAS. + +--- + +## 2. How ALAS Selects a Screenshot Method + +The active method is read from `config.Emulator_ScreenshotMethod` on every call. +The full method map is defined in +`alas_wrapped/module/device/screenshot.py` (`Screenshot.screenshot_methods`): + +```python +{ + 'ADB': self.screenshot_adb, + 'ADB_nc': self.screenshot_adb_nc, + 'uiautomator2': self.screenshot_uiautomator2, + 'aScreenCap': self.screenshot_ascreencap, + 'aScreenCap_nc': self.screenshot_ascreencap_nc, + 'DroidCast': self.screenshot_droidcast, + 'DroidCast_raw': self.screenshot_droidcast_raw, + 'scrcpy': self.screenshot_scrcpy, + 'nemu_ipc': self.screenshot_nemu_ipc, + 'ldopengl': self.screenshot_ldopengl, +} +``` + +The current config uses `uiautomator2`. That is the source of the black screenshot +problem described in this guide. + +--- + +## 3. All Screenshot Methods: What Each Requires and MEmu Compatibility + +### 3.1 ADB (`ADB`) + +- **How it works:** Runs `adb shell screencap -p` on the device. The raw RGBA pixel + stream is received over the ADB transport and decoded with `cv2.imdecode`. +- **Requirements:** ADB connection only. No helper process on the device. +- **MEmu compatibility:** Depends on MEmu's GPU rendering mode. On MEmu with the default + OpenGL rendering mode, `adb screencap` returns a pure black image (all pixels zero). + When MEmu is switched to DirectX or software rendering, ADB screencap works correctly. + This is the same root cause as for `uiautomator2` (see section 4). +- **Speed:** Slow. Each call crosses the full ADB pipe. Expect 150-300 ms per frame. + +### 3.2 ADB via Netcat (`ADB_nc`) + +- **How it works:** Same as `ADB` but transfers the raw pixel buffer over a local TCP + socket (`adb_shell_nc`) instead of the ADB protocol. Avoids ADB's CRLF translation. +- **Requirements:** ADB connection. The `nc` (netcat) binary must be available inside + the Android image (it is present in MEmu). +- **MEmu compatibility:** Same as `ADB`. The GPU rendering mode limitation applies + identically because the pixel source is still `screencap`. +- **Speed:** Slightly faster than `ADB` for large buffers. + +### 3.3 uiautomator2 (`uiautomator2`) + +- **How it works:** Uses the `uiautomator2` Python library, which communicates with + the `atx-agent` daemon running inside the Android image. The agent calls Android's + `screenshot()` API internally (backed by `screencap`) and returns a PNG via HTTP. +- **Requirements:** `atx-agent` must be installed and running inside the Android image. + ALAS installs it automatically. MiniCap is explicitly uninstalled on emulators because + it cannot work correctly. +- **MEmu compatibility:** BROKEN with default MEmu settings. This is the configured + method in PatrickCustom.json and it is the direct cause of the persistent black + screenshots seen in the error logs. See section 4 for the full explanation. +- **Speed:** Moderate. HTTP round-trip to the agent adds overhead. + +### 3.4 aScreenCap (`aScreenCap`, `aScreenCap_nc`) + +- **How it works:** ALAS pushes a custom native binary (`bin/ascreencap`) to + `/data/local/tmp/ascreencap` on the device and runs it. The binary reads the + framebuffer directly. The `_nc` variant transfers the result over netcat. +- **Requirements:** ABI-compatible native binary. ALAS includes arm64 and x86_64 + variants. Requires initial push to device. +- **MEmu compatibility:** Also affected by the GPU rendering mode bug because it reads + the Android framebuffer layer, which is black when the GPU compositor is not writing + to it. Not a reliable fix for MEmu. +- **Speed:** Faster than ADB variants. Native binary, minimal encoding overhead. + +### 3.5 DroidCast (`DroidCast`) + +- **How it works:** ALAS pushes `DroidCast_raw-release-1.0.apk` to + `/data/local/tmp/DroidCast_raw.apk` on the device, then launches it as a Java class + with `app_process`: + ``` + CLASSPATH=/data/local/tmp/DroidCast_raw.apk app_process / ink.mol.droidcast_raw.Main + ``` + The process runs an HTTP server on port 53516 inside the Android image. ALAS forwards + that port to the host with `adb forward tcp:53516` and captures PNG frames from + `http://127.0.0.1:/preview`. +- **Requirements:** + - The APK file must exist at `./bin/DroidCast/DroidCast_raw-release-1.0.apk` relative + to the `alas_wrapped/` directory. It is present in this repo. + - `uiautomator2` (atx-agent) must be running inside Android for the process list query + used by `droidcast_stop()`. + - ADB forward port in range `20000-21000` (configured default). + - 10-second startup timeout: ALAS polls `http://127.0.0.1:/` and waits for a 404 + response (which means the server is up but the root route is unregistered). +- **MEmu compatibility:** Works. DroidCast uses Android's `MediaProjection` or display + surface API, which bypasses the framebuffer path that causes the black screenshot bug. + This is the recommended fix method. +- **Speed:** Good. HTTP GET per frame, local TCP. Faster than ADB, slower than IPC methods. + +### 3.6 DroidCast Raw (`DroidCast_raw`) + +- **How it works:** Same APK and launch process as `DroidCast`. The difference is the + API endpoint: `/screenshot` instead of `/preview`. This endpoint returns a raw RGB565 + bitmap (not PNG), which ALAS decodes to RGB888 using bitwise operations. The raw + format skips PNG encoding on the device side. +- **Requirements:** Same as `DroidCast`. +- **MEmu compatibility:** Works, same as `DroidCast`. +- **Speed:** Faster than `DroidCast` because there is no PNG encoding step on the + device side. + +### 3.7 scrcpy (`scrcpy`) + +- **How it works:** ALAS pushes a scrcpy server JAR to the device and starts it, then + reads a continuous H.264 video stream over ADB. The screenshot method returns the most + recent decoded frame from the stream buffer. +- **Requirements:** The scrcpy server JAR at `./bin/scrcpy/scrcpy-server-v1.20.jar`. + The video stream runs continuously regardless of polling frequency. +- **MEmu compatibility:** Works in principle. scrcpy uses `SurfaceControl.screenshot()` + or `DisplayCapture` APIs that can bypass the GPU rendering issue. Not the primary + recommendation because it is more complex to initialize and has higher latency during + startup. The screenshot interval setting is ignored (stream is always live). +- **Speed:** Once started, very fast. The frame is always ready in the buffer. + +### 3.8 nemu_ipc (`nemu_ipc`) + +- **How it works:** Loads `external_renderer_ipc.dll` from the MuMu12 installation + directory and calls `nemu_capture_display()` directly via ctypes. This is a shared + memory / IPC call between the host process and the MuMu12 emulator process. +- **Requirements:** MuMu12 (also called MuMuPlayer12, Netease emulator) version >= 3.8.13. + Requires the DLL at `/shell/sdk/external_renderer_ipc.dll`. +- **MEmu compatibility:** DOES NOT WORK. This method is hard-coded to the MuMu (Nemu/ + Netease) emulator family. The availability check is: + ```python + def nemu_ipc_available(self) -> bool: + if not IS_WINDOWS: + return False + if not self.is_mumu_family: + return False + ... + ``` + `is_mumu_family` returns True only for serials `127.0.0.1:7555` or ports in the range + `16384-17408`. MEmu uses port `21503`/`21513`, so `is_mumu_family` is False and this + method will raise `RequestHumanTakeover` immediately. Do not configure `nemu_ipc` for + MEmu. + +### 3.9 ldopengl (`ldopengl`) + +- **How it works:** Loads `ldopengl64.dll` from the LDPlayer9 installation directory + and calls `CreateScreenShotInstance()` / `IScreenShotClass::Cap()` via ctypes. This + captures the OpenGL framebuffer directly from the emulator's render thread. +- **Requirements:** LDPlayer9 with `ldopengl64.dll` present. +- **MEmu compatibility:** DOES NOT WORK. The availability check requires + `EmulatorInfo_Emulator == 'LDPlayer9'` and `is_ldplayer_bluestacks_family` (port range + `5555-5619`). MEmu does not satisfy either condition. + +--- + +## 4. The Black Screenshot Problem: Root Cause on MEmu + +### What is happening + +MEmu's default GPU rendering mode uses OpenGL hardware acceleration. In this mode, the +game renders to a hardware-accelerated surface that is composited by the GPU. The Android +framebuffer (`/dev/graphics/fb0`) and the surface accessible via `screencap` are not +reliably updated with the composited output. Instead, they return a buffer of all zeros +(pure black, RGB = 0,0,0). + +ALAS detects this in `check_screen_black()` in `screenshot.py`: + +```python +color = get_color(self.image, area=(0, 0, 1280, 720)) +if sum(color) < 1: + logger.warning(f'Received pure black screenshots from emulator, color: {color}') + logger.warning(f'Screenshot method `{self.config.Emulator_ScreenshotMethod}` ' + f'may not work on emulator `{self.serial}`, or the emulator is not fully started') +``` + +The error log in `alas_wrapped/log/error/1772565331621/log.txt` confirms this pattern: +ALAS connected (screen size check passed at `1280x720`), MaaTouch connected, but then +entered an infinite `Unknown ui page` loop. This happens when every screenshot is black +and no page template matches. The bot ran for approximately 10 seconds before giving up +with `Game page unknown`. + +### Why it is intermittent + +Real frames appear occasionally because MEmu sometimes falls back to software rendering +for specific operations, or because the GPU scheduler temporarily flushes the framebuffer. +This is not a reliable behavior. The only durable fix is to change the rendering mode or +use a screenshot method that does not depend on the framebuffer. + +--- + +## 5. Every Known Fix for Black Screenshots on MEmu + +### Fix 1 (Recommended): Switch MEmu to Software Rendering + +This permanently fixes `ADB`, `ADB_nc`, `uiautomator2`, and `aScreenCap` methods. + +**Where the setting is:** + +1. Open MEmu Multiple Instance Manager (`MEmuConsole.exe`). +2. For the target instance (e.g., `MEmu`), click the gear icon (Settings). +3. Navigate to the **Performance** tab (sometimes labeled **Engine** or **Graphics**). +4. Find the **Render Mode** setting. It will show one of: + - `OpenGL` (the default, causes black screenshots) + - `DirectX` (also hardware-accelerated, often works) + - `Software` (fully software-rendered, always works) +5. Change the mode to **DirectX** first. If still black, change to **Software**. +6. Click OK and restart the MEmu instance. + +After changing the render mode, verify the fix: +```bash +adb -s 127.0.0.1:21513 shell screencap -p > /tmp/test.png +``` +The PNG file should contain the actual screen contents, not a black image. + +**Trade-off:** Software rendering is significantly slower for GPU-heavy games. DirectX +rendering is the best balance: hardware-accelerated on the host GPU but writes to a +surface that `screencap` can read. + +### Fix 2 (No Setting Change Required): Switch to DroidCast + +DroidCast uses Android's `MediaProjection` API or display surface capture instead of +reading the framebuffer. This works regardless of MEmu's render mode. + +**Step 1: Verify the APK is present** + +```bash +ls alas_wrapped/bin/DroidCast/DroidCast_raw-release-1.0.apk +``` + +This file is already in the repository. + +**Step 2: Update the config** + +Edit `alas_wrapped/config/PatrickCustom.json`. Change: +```json +"ScreenshotMethod": "uiautomator2" +``` +to: +```json +"ScreenshotMethod": "DroidCast" +``` + +or use `DroidCast_raw` for slightly better performance: +```json +"ScreenshotMethod": "DroidCast_raw" +``` + +**Step 3: What happens at startup** + +When ALAS first runs with `DroidCast` configured, it calls `droidcast_init()`: + +1. Stops any existing DroidCast processes (kills by `ink.mol.droidcast_raw.Main` in + the process command line, via `uiautomator2` process list). +2. Pushes the APK: `adb push ./bin/DroidCast/DroidCast_raw-release-1.0.apk /data/local/tmp/DroidCast_raw.apk` +3. Starts the server: runs `app_process` with the APK as the CLASSPATH. +4. Forwards port 53516 from the device to the host. +5. Polls `http://127.0.0.1:/` for up to 10 seconds until a 404 is returned (indicating + the server is running). +6. Begins capturing frames from `/preview` (DroidCast) or `/screenshot` (DroidCast_raw). + +**Failure modes and their recovery:** + +| Exception | Meaning | Recovery | +|-----------|---------|----------| +| `requests.exceptions.ConnectionError` | DroidCast process died | `droidcast_init()` called automatically | +| `requests.exceptions.ReadTimeout` | Server not responding within 3s | `droidcast_init()` called automatically | +| `DroidCastVersionIncompatible` | Mismatch between expected and actual endpoint | `droidcast_init()` called automatically | +| `ImageTruncated` | Partial response | Retry with no re-init | + +**Note:** DroidCast requires `uiautomator2` (atx-agent) to be running for the +`droidcast_stop()` function to enumerate existing processes. If atx-agent is not running, +DroidCast initialization may fail to clean up stale processes. ALAS handles this by +initializing uiautomator2 before DroidCast. + +### Fix 3: Switch to scrcpy + +Change the screenshot method to `scrcpy` in `PatrickCustom.json`. This also bypasses +the framebuffer. However, scrcpy uses a continuous video stream, which means: + +- Startup is slower (stream negotiation). +- The `Optimization_ScreenshotInterval` setting is ignored; the interval is capped at + 0.1s. +- More resource-intensive than DroidCast. + +Prefer DroidCast over scrcpy for this setup. + +### What Will NOT Fix It + +- Changing `ControlMethod` (MaaTouch, uiautomator2, ADB) has no effect on screenshots. + Touch input and screenshot capture are completely separate subsystems. +- Increasing `ScreenshotInterval` does not help; the frames are black, not slow. +- Restarting ADB server (`adb kill-server && adb start-server`) does not help; the root + cause is in the GPU render path inside MEmu, not the ADB transport. +- Setting `ScreenshotDedithering: true` does not help; it applies noise reduction to + the image, not to the capture mechanism. + +--- + +## 6. Why nemu_ipc Does NOT Work for MEmu + +This is a frequent point of confusion because `nemu_ipc` sounds generic. + +`nemu_ipc` is exclusively for MuMu12 (MuMuPlayer12), which is made by Netease and also +called "Nemu" in Chinese documentation. The IPC mechanism uses a proprietary DLL +(`external_renderer_ipc.dll`) distributed with MuMu12 itself. + +MEmu is made by Microvirt. It is a completely separate emulator with a different +hypervisor, different GPU pipeline, and no `external_renderer_ipc.dll`. + +ALAS enforces this at three levels: + +1. **Port range check**: `is_mumu_family` only returns True for `127.0.0.1:7555` or + ports `16384-17408`. MEmu's port `21513` is outside this range, so `is_mumu_family` + is False. + +2. **Explicit guard in `nemu_ipc_available()`**: + ```python + if not self.is_mumu_family: + return False + ``` + +3. **DLL existence check**: `NemuIpcImpl.__init__()` looks for the DLL at: + - `/shell/sdk/external_renderer_ipc.dll` + - `/nx_device/12.0/shell/sdk/external_renderer_ipc.dll` + + Neither path exists in a Microvirt installation. If `nemu_ipc` is forced anyway, + `NemuIpcIncompatible` is raised and the bot calls `RequestHumanTakeover`. + +**Similarly, `ldopengl` does not work for MEmu.** It requires LDPlayer9's +`ldopengl64.dll`. MEmu has neither this DLL nor the `ldconsole.exe` interface that +LDOpenGL uses to discover the instance PID. + +--- + +## 7. MaaTouch vs uiautomator2 for Control + +The current config: +```json +"ControlMethod": "MaaTouch" +``` + +This is correct and not related to the screenshot problem. + +**MaaTouch** is a high-performance touch input daemon. ALAS pushes +`bin/MaaTouch/maatouchsync` to `/data/local/tmp/maatouchsync` on the device and opens +a persistent socket connection. Touch events are sent as protocol messages over this +socket. The error log confirms MaaTouch initialized successfully: + +``` +MaaTouch stream connected +max_contact: 10; max_x: 1280; max_y: 720; max_pressure: 255 +``` + +**uiautomator2** as a control method uses the atx-agent's `click` RPC call, which is +slower. MaaTouch is the better choice for performance. + +Keep `ControlMethod: MaaTouch` regardless of which screenshot method is chosen. +MaaTouch has no dependency on the GPU rendering path. + +--- + +## 8. Startup Sequence: Verifying Readiness Before Starting ALAS + +The error pattern in the logs (10 seconds of `Unknown ui page` followed by `Game page +unknown`) indicates ALAS started before the emulator or the game was fully ready. + +### Recommended Readiness Check Sequence + +**Step 1: Verify MEmu instance is running** + +```bash +"C:/Program Files/Microvirt/MEmu/memuc.exe" isvmrunning -n MEmu +``` + +Wait until this returns a running status. If the instance name differs (e.g., `MEmu_0`), +adjust accordingly: + +```bash +"C:/Program Files/Microvirt/MEmu/memuc.exe" listvms --running +``` + +**Step 2: Verify ADB can connect** + +```bash +adb connect 127.0.0.1:21513 +adb -s 127.0.0.1:21513 get-state +``` + +Expected output: `device`. If you get `offline` or connection refused, the emulator's +ADB daemon has not started yet. This can take 30-60 seconds after `memuc start`. + +**Step 3: Verify Android has booted** + +```bash +adb -s 127.0.0.1:21513 shell getprop sys.boot_completed +``` + +Expected output: `1`. Any other output (empty, `0`) means Android is still booting. +Poll this until it returns `1`. + +**Step 4: Verify the game is running and on screen** + +```bash +adb -s 127.0.0.1:21513 shell dumpsys window windows | grep mCurrentFocus +``` + +Confirm the focus is on `com.YoStarEN.AzurLane`. If the focus is on the launcher or +another app, the game has not started yet. + +**Step 5: Take a test screenshot to confirm non-black output** + +```bash +adb -s 127.0.0.1:21513 shell screencap -p > /tmp/verify.png +``` + +If the PNG is entirely black, the GPU rendering mode issue is present. Apply Fix 1 or +Fix 2 from section 5 before starting ALAS. + +### Minimum Recommended Wait + +After `memuc start -n MEmu`, allow a minimum of 60 seconds before attempting ADB +commands. After ADB is live (`adb get-state` returns `device`), allow 30 additional +seconds for `sys.boot_completed` to become `1`. After that, allow another 30 seconds +for the game to reach its main screen before ALAS is started. + +--- + +## 9. Summary: Recommended Configuration for This Setup + +The fastest path to a working setup without changing MEmu settings: + +```json +"Alas": { + "Emulator": { + "Serial": "127.0.0.1:21513", + "PackageName": "com.YoStarEN.AzurLane", + "ScreenshotMethod": "DroidCast", + "ControlMethod": "MaaTouch", + "ScreenshotDedithering": false, + "AdbRestart": true + } +} +``` + +The permanent fix that allows any screenshot method: + +1. Open MEmu Settings for the target instance. +2. Change Render Mode from `OpenGL` to `DirectX` or `Software`. +3. Restart the instance. +4. Restore `ScreenshotMethod` to `uiautomator2` or `ADB` if desired. + +Do not use: `nemu_ipc`, `ldopengl`. These will raise `RequestHumanTakeover` on MEmu. + +--- + +## 10. File Reference + +| File | Purpose | +|------|---------| +| `alas_wrapped/module/device/screenshot.py` | Screenshot dispatcher, `check_screen_black()` | +| `alas_wrapped/module/device/method/adb.py` | `ADB`, `ADB_nc` implementations | +| `alas_wrapped/module/device/method/uiautomator_2.py` | `uiautomator2` screenshot via atx-agent | +| `alas_wrapped/module/device/method/droidcast.py` | `DroidCast`, `DroidCast_raw` implementations | +| `alas_wrapped/module/device/method/ascreencap.py` | `aScreenCap` native binary method | +| `alas_wrapped/module/device/method/scrcpy/scrcpy.py` | `scrcpy` video stream method | +| `alas_wrapped/module/device/method/nemu_ipc.py` | `nemu_ipc` (MuMu12 only, NOT MEmu) | +| `alas_wrapped/module/device/method/ldopengl.py` | `ldopengl` (LDPlayer9 only, NOT MEmu) | +| `alas_wrapped/module/device/connection_attr.py` | `is_mumu_family`, `is_ldplayer_bluestacks_family` | +| `alas_wrapped/module/config/config_manual.py` | `DROIDCAST_FILEPATH_LOCAL`, `DROIDCAST_FILEPATH_REMOTE` | +| `alas_wrapped/config/PatrickCustom.json` | Active runtime configuration | +| `alas_wrapped/bin/DroidCast/DroidCast_raw-release-1.0.apk` | DroidCast APK (already present) | diff --git a/docs/LLMGuide/llm_control_harness.md b/docs/LLMGuide/llm_control_harness.md new file mode 100644 index 0000000000..30a0d255f4 --- /dev/null +++ b/docs/LLMGuide/llm_control_harness.md @@ -0,0 +1,916 @@ +# LLM Control Harness Guide + +This document is the definitive reference for an LLM (Claude or Gemini) that needs to take +control of the ALAS automation stack — either to diagnose a crash, perform a manual recovery, +or validate the bot's state before handing back to autonomous operation. + +It covers every MCP tool, every log file, all detection heuristics, and a worked example of +a complete crash-recovery cycle. + +--- + +## 1. System Overview + +ALAS is a Python bot that plays Azur Lane through ADB on a MEmu Android emulator. It runs +as a scheduler process, picking tasks from a queue (Commission, Tactical, Research, etc.) and +executing them one at a time. When a task finishes or fails, ALAS logs the event, saves an +error snapshot, and moves to the next task or requests human takeover. + +The LLM control harness is a thin layer on top: + +``` +[LLM] -- MCP (JSON-RPC) --> [alas_mcp_server.py] --> [ALAS runtime / ADB] +``` + +The MCP server exposes eight callable tools. The LLM uses those tools to observe, interact, +and recover. It never modifies ALAS source or config directly during a recovery session. + +Design principle (from NORTH_STAR.md): deterministic tools handle all normal operations; the +LLM and vision are reserved for recovery when those tools fail or the game reaches an +unexpected state. + +--- + +## 2. MCP Tool Surface + +The MCP server is `agent_orchestrator/alas_mcp_server.py`. It is launched as: + +```bash +cd agent_orchestrator +uv run alas_mcp_server.py --config PatrickCustom +``` + +All tools are registered with FastMCP and exposed over stdio JSON-RPC 2.0. Every call is +appended to `agent_orchestrator/mcp_actions.jsonl` with a sequence number, timestamp, tool +name, arguments, result summary, error string, and duration in milliseconds. + +### 2.1 `adb_screenshot` + +**Signature:** `adb_screenshot() -> dict` + +**What it does:** Captures a full-resolution screenshot from the emulator using the +uiautomator2 ATX agent HTTP path. Does not use `adb shell screencap` — that path reads the +Linux framebuffer, which MEmu's VirtualBox GPU passthrough never populates, producing a blank +3 KB PNG. The ATX agent is the only path that returns real pixel data. + +The call runs in a thread-pool executor. A hard 25-second ceiling is enforced via +`asyncio.wait_for`; if exceeded, the tool raises `RuntimeError("adb_screenshot timed out +after 25s")`. + +**Return value:** +```json +{ + "content": [ + { + "type": "image", + "mimeType": "image/png", + "data": "" + } + ] +} +``` + +The PNG is also saved to `agent_orchestrator/mcp_screenshots/_.png` for +audit purposes. + +**When to use:** As the first step of any diagnosis. Call it before touching anything else. +If it times out or returns a blank image, ADB is broken — proceed to the ADB health check +sequence in section 5. + +**Error signals:** +- `RuntimeError: adb_screenshot timed out after 25s` — ATX agent is unresponsive; the + emulator may have crashed or MEmu GPU rendering mode changed. +- The mcp_actions.jsonl entry shows `"error": "screencap failed: device offline"` — ADB + transport is dead. + +### 2.2 `adb_tap` + +**Signature:** `adb_tap(x: int, y: int) -> str` + +**What it does:** Sends `adb shell input tap X Y` to the emulator. Uses the raw ADB CLI, +not ALAS's MaaTouch daemon. Timeout: 5 seconds. Returns the string `"tapped X,Y"`. + +**When to use:** To dismiss dialogs, confirm prompts, or click known UI elements when the +LLM has taken control and identified a coordinate from a screenshot. The game resolution is +1280x720; all coordinates must be within those bounds. + +**Caveats:** `adb_tap` uses Android's input subsystem, not MaaTouch. It is slightly slower +and less precise than ALAS's normal touch path. Adequate for recovery clicks; do not use it +to drive high-frequency battle automation. + +### 2.3 `adb_swipe` + +**Signature:** `adb_swipe(x1: int, y1: int, x2: int, y2: int, duration_ms: int = 300) -> str` + +**What it does:** Sends `adb shell input swipe X1 Y1 X2 Y2 DURATION_MS`. Timeout: +`5.0 + duration_ms / 1000.0` seconds. Returns `"swiped X1,Y1->X2,Y2"`. + +**When to use:** To scroll through menus, close drawers, or dismiss full-screen overlays +that cannot be dismissed with a tap. + +### 2.4 `adb_get_focus` + +**Signature:** `adb_get_focus() -> dict` + +**What it does:** Runs `adb shell dumpsys window windows`, finds the `mCurrentFocus` line, +and parses the focused package and activity names. Timeout: 8 seconds. + +**Return value:** +```json +{ + "raw": "mCurrentFocus=Window{... u0 com.YoStarEN.AzurLane/com.manjuu.azurlane.MainActivity}", + "package": "com.YoStarEN.AzurLane", + "activity": "com.manjuu.azurlane.MainActivity" +} +``` + +If no window is focused or the line is missing, `package` and `activity` will be `null`. + +**When to use:** +1. Immediately after taking a screenshot, to confirm the game is in the foreground. +2. After calling `adb_launch_game`, to verify the launch succeeded before interacting. +3. When `adb_screenshot` shows a black or unexpected screen, to detect whether another + app (MEmu launcher, system dialog) has taken focus. + +**Healthy values:** +- `package`: `com.YoStarEN.AzurLane` +- `activity`: `com.manjuu.azurlane.MainActivity` or `com.manjuu.azurlane.PrePermissionActivity` + (the latter appears only during startup) + +**Unhealthy signals:** +- `package` is `null` — no app is focused; system or emulator UI is in the way. +- `package` is `com.microvirt.memu` or similar — the MEmu launcher grabbed focus (game + likely crashed to desktop). +- `package` is correct but `activity` contains `Crash` or `Dialog` — the game OS-level + crash reporter is showing. + +### 2.5 `adb_launch_game` + +**Signature:** `adb_launch_game() -> str` + +**What it does:** Sends an explicit Android intent: + +``` +adb shell am start \ + -a android.intent.action.MAIN \ + -c android.intent.category.LAUNCHER \ + -n com.YoStarEN.AzurLane/com.manjuu.azurlane.PrePermissionActivity +``` + +If the game is already running, Android brings the existing process to the foreground instead +of double-launching. Timeout: 10 seconds. Returns `"Azur Lane launch intent sent"`. + +**When to use:** When `adb_get_focus` shows the game is not in the foreground, or when you +have determined the game process has died and need to cold-start it. After calling this, wait +approximately 15-30 seconds and then call `adb_screenshot` to verify the game has loaded. + +### 2.6 `alas_get_current_state` + +**Signature:** `alas_get_current_state() -> str` + +**What it does:** Queries ALAS's internal state machine for the currently recognized UI page. +Returns the page name as a string, e.g. `"page_main"`, `"page_tactical"`, `"page_research"`. +Requires the ALAS context to be initialized. + +**When to use:** To confirm the game is at a known page before invoking `alas_goto` or +`alas_call_tool`. If this raises or returns `"page_unknown"`, the game is not at a recognized +screen. + +**Known page names** (from the ALAS state machine, 43 total): +`page_main`, `page_campaign_menu`, `page_campaign`, `page_fleet`, `page_main_white`, +`page_unknown`, `page_exercise`, `page_daily`, `page_event`, `page_sp`, `page_coalition`, +`page_os`, `page_archives`, `page_reward`, `page_mission`, `page_guild`, `page_commission`, +`page_tactical`, `page_battle_pass`, `page_event_list`, `page_raid`, `page_dock`, +`page_research`, `page_shipyard`, `page_meta`, `page_storage`, `page_reshmenu`, +`page_dormmenu`, `page_dorm`, `page_meowfficer`, `page_academy`, `page_private_quarters`, +`page_game_room`, `page_shop`, `page_munitions`, `page_supply_pack`, `page_build`, +`page_mail`, `page_channel`, `page_rpg_stage`, `page_rpg_story`, `page_rpg_city`, +`page_hospital`. + +### 2.7 `alas_goto` + +**Signature:** `alas_goto(page: str) -> str` + +**What it does:** Instructs ALAS's state machine to navigate from the current page to the +named target page. The state machine calculates the shortest path through the 122-link page +graph and executes the required taps automatically. Raises `ValueError` if the page name is +unknown. + +**When to use:** To return the game to a known starting point before handing back to the +autonomous scheduler. The canonical recovery target is `"page_main"`. + +**Preconditions:** The current page must be recognized. If `alas_get_current_state` returns +`"page_unknown"`, do not call `alas_goto` — the state machine cannot plan a route from an +unknown position. Use `adb_screenshot` plus vision instead to identify where the game is, +then use `adb_tap` to navigate manually to a recognized page first. + +### 2.8 `alas_list_tools` + +**Signature:** `alas_list_tools() -> list[dict]` + +**What it does:** Returns all deterministic ALAS tools registered in the state machine, +as a list of `{name, description, parameters}` dicts. + +**When to use:** To discover what automation tasks are available before calling +`alas_call_tool`. Useful for orientation at the start of a session. + +### 2.9 `alas_call_tool` + +**Signature:** `alas_call_tool(name: str, arguments: dict | None = None) -> Any` + +**What it does:** Invokes a named ALAS tool (from `alas_list_tools`) with the provided +arguments. This is the correct pattern for triggering a task; do not build one MCP tool +per workflow. If the tool raises, the exception propagates and is logged to +`mcp_actions.jsonl`. + +**When to use:** To run a specific ALAS task as part of a recovery or manual drive. For +example, after navigating to `page_main`, you could call `alas_call_tool("commission.run")` +to trigger commission collection. + +### 2.10 `alas_login_ensure_main` + +**Signature:** +``` +alas_login_ensure_main( + max_wait_s: float = 90.0, + poll_interval_s: float = 1.0, + dismiss_popups: bool = True, + get_ship: bool = True, +) -> dict +``` + +**What it does:** Wraps ALAS's deterministic login handler (`alas_wrapped/tools/login.py`). +Polls the current page until the game reaches `page_main`, dismissing popups along the way. +Returns a structured envelope: + +```json +{ + "success": true, + "data": null, + "error": null, + "observed_state": "page_main", + "expected_state": "page_main" +} +``` + +**Warning:** The underlying login handler contains a `while 1:` loop with heavy popup +detection logic (14+ popup types, scipy peak detection). Setting `max_wait_s` to a large +value risks blocking the MCP event loop for that entire duration. Keep `max_wait_s` at 90 +or below. This tool is categorized as a known risk area (see memory: issue #35). + +**When to use:** After cold-starting the game via `adb_launch_game`, when the game has not +yet reached the main lobby and you want ALAS's own popup-dismissal logic to handle the login +sequence automatically. + +--- + +## 3. How to Take a Screenshot and Diagnose Game State + +The LLM's primary observability tool is the screenshot. The game resolution is always 1280x720. + +### Step-by-step diagnosis sequence + +``` +1. Call adb_get_focus + - If package != "com.YoStarEN.AzurLane": + -> Game is not in foreground. Call adb_launch_game, wait 20s, repeat. + - If package is correct: proceed. + +2. Call adb_screenshot + - If it times out: ATX agent is down. See section 5 (crash detection). + - If the returned PNG is black (all pixels near 0): MEmu GPU rendering + issue or emulator freeze. See section 5. + - If the PNG shows a valid game screen: proceed. + +3. Visually interpret the screenshot: + - Main lobby (blue ocean, ship girls, resource bar at top): page_main + - Dark overlay with confirm/cancel buttons: popup dialog + - Loading spinner or progress bar: transitioning between pages + - Network error banner ("Server Unavailable", "Check Connection"): game network failure + - Black screen with Android system chrome: game crashed to desktop + - White screen: possible page_main_white (a variant of the main lobby) + +4. Call alas_get_current_state + - Cross-reference with visual interpretation. + - If state machine says "page_main" and visual confirms: system is healthy. + - If state machine says "page_unknown" but game looks valid: ALAS page detection + failed; use alas_goto("page_main") to attempt recovery. +``` + +### What a blank/black screenshot means + +A black PNG from `adb_screenshot` is almost always an MEmu GPU rendering issue. The +underlying screenshot method is uiautomator2's ATX agent, not `adb shell screencap`. If +the ATX agent returns a black frame, the emulator's GPU pipeline is stalled. Options: +- Use `adb_get_focus` to confirm Android itself is still running. +- If Android is responsive (focus returns a result), the emulator GPU may have glitched. + Consider restarting MEmu via `memuc reboot -i 0`. +- If Android is not responsive, the emulator process has died. Restart via `memuc start`. + +--- + +## 4. How to Detect ALAS Has Crashed + +There are three independent signals. Check them in order from fastest to most reliable. + +### Signal 1: Process check (fastest) + +ALAS runs as a child process of the GUI (`gui.py`). Check whether the process is alive: + +```python +import psutil + +def alas_is_running(config_name: str = "PatrickCustom") -> bool: + for proc in psutil.process_iter(attrs=["name", "cmdline"]): + cmdline = proc.info.get("cmdline") or [] + for arg in cmdline: + if config_name in str(arg): + return True + return False +``` + +If the process is not found, ALAS has exited. This does not tell you why — consult the logs. + +### Signal 2: Log staleness (reliable) + +ALAS writes to `alas_wrapped/log/_PatrickCustom.txt` continuously while running. A +healthy bot produces new log lines every few seconds during task execution, and at least +every 60 seconds during idle cooldown periods. + +Check the modification time of the log file: + +```python +import os, time + +def log_is_stale(threshold_s: int = 120) -> bool: + log_path = "alas_wrapped/log/2026-03-03_PatrickCustom.txt" # use today's date + try: + mtime = os.path.getmtime(log_path) + return (time.time() - mtime) > threshold_s + except FileNotFoundError: + return True # no log at all = definitely not running +``` + +If the log has not been written in 120 seconds during a period when a task should be running, +treat it as a crash indicator. + +### Signal 3: schedule_status.jsonl (most authoritative) + +File: `alas_wrapped/log/schedule_status.jsonl` + +ALAS appends a JSONL record here each time the scheduler loop evaluates the task queue. Each +record contains: + +```json +{ + "ts": "2026-03-03T04:52:09.497Z", + "config": "PatrickCustom", + "current_task": null, + "next_task": "Restart", + "pending": ["Restart", "OpsiCrossMonth", "Commission", "Tactical", ...], + "next_waiting": [], + "pending_count": 23, + "waiting_count": 0, + "source": "scheduler_loop" +} +``` + +**Healthy state:** The `ts` field should be recent (within the last 5 minutes), `pending` +should list tasks, and `current_task` should either be `null` (between tasks) or a task name +(actively running). + +**Stuck/dead state:** The last `ts` is more than 5 minutes old, or `current_task` has been +the same task for an unreasonably long time (Tactical tasks should complete in under 2 +minutes; Commission in under 5 minutes). + +**Parsing the last record:** + +```python +import json +from pathlib import Path + +def read_schedule_status() -> dict | None: + path = Path("alas_wrapped/log/schedule_status.jsonl") + if not path.exists(): + return None + lines = path.read_text(encoding="utf-8").strip().splitlines() + if not lines: + return None + return json.loads(lines[-1]) +``` + +### Signal 4: Error directory + +ALAS saves error snapshots to `alas_wrapped/log/error//` whenever it catches a +critical exception. Each subdirectory contains: +- Several PNG screenshots taken just before the error +- `log.txt` — the relevant log tail + +Check whether new error directories have appeared since the last healthy checkpoint: + +```python +import os +from pathlib import Path + +def recent_errors(since_ms: int) -> list[str]: + error_dir = Path("alas_wrapped/log/error") + if not error_dir.exists(): + return [] + dirs = [ + d.name for d in error_dir.iterdir() + if d.is_dir() and d.name.isdigit() and int(d.name) > since_ms + ] + return sorted(dirs) +``` + +The directory name is the Unix timestamp in milliseconds at the time of the error. The +`log.txt` inside will contain the Python traceback and the final log lines before the crash. + +--- + +## 5. Recovery Playbook + +When ALAS crashes or gets stuck, execute this playbook in order. Stop at the step that +resolves the situation. + +### Step 0: Establish baseline + +``` +1. Read the last line of schedule_status.jsonl — note current_task and ts. +2. Check the PatrickCustom log for recent CRITICAL or ERROR lines. +3. Call adb_get_focus — confirm the game is in foreground. +4. Call adb_screenshot — confirm the screen is visible and sensible. +``` + +### Step 1: Soft recovery (state machine can navigate) + +If `adb_get_focus` confirms the game is in the foreground and `adb_screenshot` shows a +recognizable game screen: + +``` +1. Call alas_get_current_state. + - If it returns a page name (not "page_unknown"): call alas_goto("page_main"). + - If it returns "page_unknown": proceed to Step 2. +2. After alas_goto returns, call alas_get_current_state again to confirm. +3. If confirmed at page_main: ALAS can be restarted (Step 4). +``` + +### Step 2: Manual navigation (state machine cannot navigate) + +If the game screen is visible but unrecognized: + +``` +1. Inspect the screenshot visually. +2. If a popup or overlay is present: + - Identify the dismiss button (usually bottom-right "OK" or "Close"). + - Call adb_tap with the button coordinates. + - Wait 1 second, take another screenshot, repeat until clear. +3. If the game is on a sub-screen (research queue, dorm, etc.): + - Look for a "HOME" button (upper-right, typically around x=1220, y=35 at 1280x720). + - Call adb_tap(1220, 35) to navigate home. + - Take a screenshot, confirm page_main appearance. +4. Once at page_main: call alas_get_current_state to verify, then proceed to Step 4. +``` + +### Step 3: Game process recovery (game has crashed or focus lost) + +If `adb_get_focus` shows the game is not in foreground, or screenshots are black: + +``` +1. Call adb_launch_game. +2. Wait 20 seconds. +3. Call adb_get_focus — confirm package is "com.YoStarEN.AzurLane". +4. Call adb_screenshot — confirm the game screen is visible. +5. If the game shows a login screen or server selection: + - Call alas_login_ensure_main(max_wait_s=90) to handle popups and reach page_main. +6. If the game shows a loading bar: wait up to 60 seconds and retry screenshot. +7. Once at page_main: proceed to Step 4. +``` + +### Step 4: Restart ALAS + +Once the game is confirmed at `page_main`, restart the ALAS process so it resumes autonomous +operation. Do this from a Python subprocess — never from within the MCP server itself. + +**Exact restart command:** + +```bash +cd alas_wrapped +PYTHONIOENCODING=utf-8 .venv/Scripts/python.exe gui.py --run PatrickCustom +``` + +Or using the convenience batch file: + +```bash +cd alas_wrapped +alas.bat +``` + +**From Python:** + +```python +import subprocess +from pathlib import Path + +alas_wrapped = Path("D:/_projects/ALAS/alas_wrapped") +subprocess.Popen( + [str(alas_wrapped / ".venv/Scripts/python.exe"), "gui.py", "--run", "PatrickCustom"], + cwd=str(alas_wrapped), + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, +) +``` + +After starting, watch `schedule_status.jsonl` for a new record with a recent `ts` and +`source: "scheduler_loop"`. This confirms ALAS is running and the scheduler is active. + +### Step 5: Escalate (cannot recover automatically) + +If the game process cannot be started, ADB is unresponsive, or three recovery attempts have +failed: + +``` +1. Write a structured incident summary to stdout for human review: + - Last known state from schedule_status.jsonl + - Last error from log/error//log.txt + - Screenshot from adb_screenshot (if available) + - Steps attempted and outcomes +2. Do not loop indefinitely. One final attempt max. +``` + +--- + +## 6. How to Restart ALAS from Python + +The canonical restart is through `gui.py`, which manages the child process lifecycle. Do not +start `alas.py` directly — the GUI's process manager writes the PID file and handles +log rotation. + +```python +import os +import subprocess +from pathlib import Path + +def restart_alas(config_name: str = "PatrickCustom") -> subprocess.Popen: + alas_wrapped = Path("D:/_projects/ALAS/alas_wrapped") + python = alas_wrapped / ".venv" / "Scripts" / "python.exe" + env = {**os.environ, "PYTHONIOENCODING": "utf-8"} + proc = subprocess.Popen( + [str(python), "gui.py", "--run", config_name], + cwd=str(alas_wrapped), + env=env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return proc +``` + +After calling this: +1. Wait 10 seconds. +2. Poll `schedule_status.jsonl` every 5 seconds until you see a record where `ts` is within + the last 30 seconds. +3. Confirm the process is alive with a `psutil` process check. + +--- + +## 7. Log Files + +All logs are in `alas_wrapped/log/`. They rotate daily; the filename prefix is the date. + +### `_PatrickCustom.txt` + +The primary bot log. ALAS writes every action here, structured as: + +``` +