Some checks failed
Release / release (push) Has been cancelled
Full-stack dashboard for controlling, automating, and analyzing Artifacts MMO characters via the game's HTTP API. Backend (FastAPI): - Async Artifacts API client with rate limiting and retry - 6 automation strategies (combat, gathering, crafting, trading, task, leveling) - Automation engine with runner, manager, cooldown tracker, pathfinder - WebSocket relay (game server -> frontend) - Game data cache, character snapshots, price history, analytics - 9 API routers, 7 database tables, 3 Alembic migrations - 108 unit tests Frontend (Next.js 15 + shadcn/ui): - Live character dashboard with HP/XP bars and cooldowns - Character detail with stats, equipment, inventory, skills, manual actions - Automation management with live log streaming - Interactive canvas map with content-type coloring and zoom/pan - Bank management, Grand Exchange with price charts - Events, logs, analytics pages with Recharts - WebSocket auto-reconnect with query cache invalidation - Settings page, error boundaries, dark theme Infrastructure: - Docker Compose (dev + prod) - GitHub Actions CI/CD - Documentation (Architecture, Automation, Deployment, API)
139 lines
4.4 KiB
Python
139 lines
4.4 KiB
Python
"""Coordinator for multi-character operations.
|
|
|
|
Provides simple sequential setup of automations across characters
|
|
for pipelines like: gatherer collects materials -> crafter processes them.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from app.engine.manager import AutomationManager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Coordinator:
|
|
"""Coordinates multi-character operations by sequentially setting up automations.
|
|
|
|
This is a lightweight orchestrator that configures multiple characters
|
|
to work in a pipeline. It does not manage real-time synchronization
|
|
between characters; each character runs its automation independently.
|
|
"""
|
|
|
|
def __init__(self, manager: AutomationManager) -> None:
|
|
self._manager = manager
|
|
|
|
async def resource_pipeline(
|
|
self,
|
|
gatherer_config_id: int,
|
|
crafter_config_id: int,
|
|
item_code: str,
|
|
) -> dict[str, Any]:
|
|
"""Set up a gather-then-craft pipeline across two characters.
|
|
|
|
The gatherer character will gather resources and deposit them.
|
|
The crafter character will withdraw materials and craft the item.
|
|
|
|
This is a simple sequential setup -- both automations run
|
|
independently after being started.
|
|
|
|
Parameters
|
|
----------
|
|
gatherer_config_id:
|
|
Automation config ID for the gathering character.
|
|
crafter_config_id:
|
|
Automation config ID for the crafting character.
|
|
item_code:
|
|
The item code that the crafter will produce.
|
|
|
|
Returns
|
|
-------
|
|
Dict with the run IDs and status of both automations.
|
|
"""
|
|
results: dict[str, Any] = {
|
|
"item_code": item_code,
|
|
"gatherer": None,
|
|
"crafter": None,
|
|
"errors": [],
|
|
}
|
|
|
|
# Start the gatherer first
|
|
try:
|
|
gatherer_run = await self._manager.start(gatherer_config_id)
|
|
results["gatherer"] = {
|
|
"config_id": gatherer_config_id,
|
|
"run_id": gatherer_run.id,
|
|
"status": gatherer_run.status,
|
|
}
|
|
logger.info(
|
|
"Pipeline: started gatherer config=%d run=%d",
|
|
gatherer_config_id,
|
|
gatherer_run.id,
|
|
)
|
|
except ValueError as exc:
|
|
error_msg = f"Failed to start gatherer: {exc}"
|
|
results["errors"].append(error_msg)
|
|
logger.warning("Pipeline: %s", error_msg)
|
|
|
|
# Start the crafter
|
|
try:
|
|
crafter_run = await self._manager.start(crafter_config_id)
|
|
results["crafter"] = {
|
|
"config_id": crafter_config_id,
|
|
"run_id": crafter_run.id,
|
|
"status": crafter_run.status,
|
|
}
|
|
logger.info(
|
|
"Pipeline: started crafter config=%d run=%d",
|
|
crafter_config_id,
|
|
crafter_run.id,
|
|
)
|
|
except ValueError as exc:
|
|
error_msg = f"Failed to start crafter: {exc}"
|
|
results["errors"].append(error_msg)
|
|
logger.warning("Pipeline: %s", error_msg)
|
|
|
|
return results
|
|
|
|
async def stop_pipeline(
|
|
self,
|
|
gatherer_config_id: int,
|
|
crafter_config_id: int,
|
|
) -> dict[str, Any]:
|
|
"""Stop both automations in a resource pipeline.
|
|
|
|
Parameters
|
|
----------
|
|
gatherer_config_id:
|
|
Automation config ID for the gathering character.
|
|
crafter_config_id:
|
|
Automation config ID for the crafting character.
|
|
|
|
Returns
|
|
-------
|
|
Dict with the stop results for both automations.
|
|
"""
|
|
results: dict[str, Any] = {
|
|
"gatherer_stopped": False,
|
|
"crafter_stopped": False,
|
|
"errors": [],
|
|
}
|
|
|
|
for label, config_id in [
|
|
("gatherer", gatherer_config_id),
|
|
("crafter", crafter_config_id),
|
|
]:
|
|
try:
|
|
await self._manager.stop(config_id)
|
|
results[f"{label}_stopped"] = True
|
|
logger.info("Pipeline: stopped %s config=%d", label, config_id)
|
|
except ValueError as exc:
|
|
results["errors"].append(f"Failed to stop {label}: {exc}")
|
|
logger.warning(
|
|
"Pipeline: failed to stop %s config=%d: %s",
|
|
label,
|
|
config_id,
|
|
exc,
|
|
)
|
|
|
|
return results
|