artifacts-dashboard/backend/app/engine/strategies/base.py
Paweł Orzech f845647934
Some checks failed
Release / release (push) Has been cancelled
Initial release: Artifacts MMO Dashboard & Automation Platform
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)
2026-03-01 19:46:45 +01:00

99 lines
3.1 KiB
Python

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from app.engine.pathfinder import Pathfinder
from app.schemas.game import CharacterSchema
class ActionType(str, Enum):
"""All possible actions the automation runner can execute."""
MOVE = "move"
FIGHT = "fight"
GATHER = "gather"
REST = "rest"
EQUIP = "equip"
UNEQUIP = "unequip"
USE_ITEM = "use_item"
DEPOSIT_ITEM = "deposit_item"
WITHDRAW_ITEM = "withdraw_item"
CRAFT = "craft"
RECYCLE = "recycle"
GE_BUY = "ge_buy"
GE_SELL = "ge_sell"
GE_CANCEL = "ge_cancel"
TASK_NEW = "task_new"
TASK_TRADE = "task_trade"
TASK_COMPLETE = "task_complete"
TASK_EXCHANGE = "task_exchange"
IDLE = "idle"
COMPLETE = "complete"
@dataclass
class ActionPlan:
"""A single action to be executed by the runner."""
action_type: ActionType
params: dict = field(default_factory=dict)
reason: str = ""
class BaseStrategy(ABC):
"""Abstract base class for all automation strategies.
A strategy inspects the current character state and returns an
:class:`ActionPlan` describing the next action the runner should execute.
Subclasses must implement :meth:`next_action` and :meth:`get_state`.
"""
def __init__(self, config: dict, pathfinder: Pathfinder) -> None:
self.config = config
self.pathfinder = pathfinder
@abstractmethod
async def next_action(self, character: CharacterSchema) -> ActionPlan:
"""Determine the next action based on the current character state.
Returns an :class:`ActionPlan` for the runner to execute. Returning
``ActionType.COMPLETE`` signals the runner to stop the automation
loop gracefully. ``ActionType.IDLE`` causes the runner to skip
execution and re-evaluate after a short delay.
"""
...
@abstractmethod
def get_state(self) -> str:
"""Return a human-readable label describing the current strategy state.
Used for logging and status reporting.
"""
...
# ------------------------------------------------------------------
# Shared helpers available to all strategies
# ------------------------------------------------------------------
@staticmethod
def _inventory_used_slots(character: CharacterSchema) -> int:
"""Count how many inventory slots are currently occupied."""
return len(character.inventory)
@staticmethod
def _inventory_free_slots(character: CharacterSchema) -> int:
"""Count how many inventory slots are free."""
return character.inventory_max_items - len(character.inventory)
@staticmethod
def _hp_percent(character: CharacterSchema) -> float:
"""Return the character's HP as a percentage of max HP."""
if character.max_hp == 0:
return 100.0
return (character.hp / character.max_hp) * 100.0
@staticmethod
def _is_at(character: CharacterSchema, x: int, y: int) -> bool:
"""Check whether the character is standing at the given tile."""
return character.x == x and character.y == y