diff --git a/backend/app/api/dashboard.py b/backend/app/api/dashboard.py index a4c711a..44dec8e 100644 --- a/backend/app/api/dashboard.py +++ b/backend/app/api/dashboard.py @@ -33,7 +33,7 @@ async def get_dashboard(request: Request) -> DashboardData: # Server status could be extended later (e.g., ping, event info) server_status: dict | None = None try: - events = await client.get_events() + events = await client.get_active_events() server_status = {"events": events} except Exception: logger.warning("Failed to fetch server events for dashboard", exc_info=True) diff --git a/backend/app/api/events.py b/backend/app/api/events.py index afe429f..4b25243 100644 --- a/backend/app/api/events.py +++ b/backend/app/api/events.py @@ -22,7 +22,7 @@ async def get_active_events(request: Request) -> dict[str, Any]: client = get_user_client(request) try: - events = await client.get_events() + events = await client.get_active_events() except HTTPStatusError as exc: raise HTTPException( status_code=exc.response.status_code, diff --git a/backend/app/config.py b/backend/app/config.py index 6e7844f..9eabab8 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -9,8 +9,8 @@ class Settings(BaseSettings): # Artifacts API artifacts_api_url: str = "https://api.artifactsmmo.com" - # Rate limits - action_rate_limit: int = 7 # actions per window + # Rate limits (matching Artifacts API: 20 actions/2s, 20 data/1s) + action_rate_limit: int = 20 # actions per window action_rate_window: float = 2.0 # seconds data_rate_limit: int = 20 # data requests per window data_rate_window: float = 1.0 # seconds diff --git a/backend/app/schemas/game.py b/backend/app/schemas/game.py index 2fdfdec..f0b9101 100644 --- a/backend/app/schemas/game.py +++ b/backend/app/schemas/game.py @@ -136,6 +136,10 @@ class CharacterSchema(BaseModel): haste: int = 0 critical_strike: int = 0 stamina: int = 0 + wisdom: int = 0 + prospecting: int = 0 + initiative: int = 0 + threat: int = 0 # Attack stats attack_fire: int = 0 @@ -144,6 +148,7 @@ class CharacterSchema(BaseModel): attack_air: int = 0 # Damage stats + dmg: int = 0 dmg_fire: int = 0 dmg_earth: int = 0 dmg_water: int = 0 @@ -158,13 +163,19 @@ class CharacterSchema(BaseModel): # Position x: int = 0 y: int = 0 + layer: str = "overworld" + map_id: int = 0 # Cooldown cooldown: int = 0 cooldown_expiration: datetime | None = None + # Active effects + effects: list[EffectSchema] = Field(default_factory=list) + # Equipment slots weapon_slot: str = "" + rune_slot: str = "" shield_slot: str = "" helmet_slot: str = "" body_armor_slot: str = "" @@ -180,6 +191,7 @@ class CharacterSchema(BaseModel): utility1_slot_quantity: int = 0 utility2_slot: str = "" utility2_slot_quantity: int = 0 + bag_slot: str = "" # Inventory inventory_max_items: int = 0 @@ -191,23 +203,31 @@ class CharacterSchema(BaseModel): task_progress: int = 0 task_total: int = 0 - # Skill levels and XP + # Skill levels and XP (includes max_xp for progress calculation) mining_level: int = 0 mining_xp: int = 0 + mining_max_xp: int = 0 woodcutting_level: int = 0 woodcutting_xp: int = 0 + woodcutting_max_xp: int = 0 fishing_level: int = 0 fishing_xp: int = 0 + fishing_max_xp: int = 0 weaponcrafting_level: int = 0 weaponcrafting_xp: int = 0 + weaponcrafting_max_xp: int = 0 gearcrafting_level: int = 0 gearcrafting_xp: int = 0 + gearcrafting_max_xp: int = 0 jewelrycrafting_level: int = 0 jewelrycrafting_xp: int = 0 + jewelrycrafting_max_xp: int = 0 cooking_level: int = 0 cooking_xp: int = 0 + cooking_max_xp: int = 0 alchemy_level: int = 0 alchemy_xp: int = 0 + alchemy_max_xp: int = 0 # --- Dashboard --- diff --git a/backend/app/services/artifacts_client.py b/backend/app/services/artifacts_client.py index facc6a6..929e2a6 100644 --- a/backend/app/services/artifacts_client.py +++ b/backend/app/services/artifacts_client.py @@ -156,8 +156,12 @@ class ArtifactsClient: await asyncio.sleep(retry_after) continue - # 498 = character in cooldown – wait and retry + # 498 = character not found – raise immediately if response.status_code == 498: + response.raise_for_status() + + # 499 = character in cooldown – wait and retry + if response.status_code == 499: try: body = response.json() cooldown = body.get("data", {}).get("cooldown", {}) @@ -286,7 +290,7 @@ class ArtifactsClient: return [ItemSchema.model_validate(i) for i in result.get("data", [])] async def get_all_items(self) -> list[ItemSchema]: - raw = await self._get_paginated("/items") + raw = await self._get_paginated("/items", page_size=10000) return [ItemSchema.model_validate(i) for i in raw] # ------------------------------------------------------------------ @@ -298,7 +302,7 @@ class ArtifactsClient: return [MonsterSchema.model_validate(m) for m in result.get("data", [])] async def get_all_monsters(self) -> list[MonsterSchema]: - raw = await self._get_paginated("/monsters") + raw = await self._get_paginated("/monsters", page_size=10000) return [MonsterSchema.model_validate(m) for m in raw] # ------------------------------------------------------------------ @@ -310,7 +314,7 @@ class ArtifactsClient: return [ResourceSchema.model_validate(r) for r in result.get("data", [])] async def get_all_resources(self) -> list[ResourceSchema]: - raw = await self._get_paginated("/resources") + raw = await self._get_paginated("/resources", page_size=10000) return [ResourceSchema.model_validate(r) for r in raw] # ------------------------------------------------------------------ @@ -333,17 +337,23 @@ class ArtifactsClient: return [MapSchema.model_validate(m) for m in result.get("data", [])] async def get_all_maps(self) -> list[MapSchema]: - raw = await self._get_paginated("/maps") + raw = await self._get_paginated("/maps", page_size=10000) return [MapSchema.model_validate(m) for m in raw] # ------------------------------------------------------------------ # Data endpoints - Events, Bank, GE # ------------------------------------------------------------------ - async def get_events(self) -> list[dict[str, Any]]: + async def get_all_events(self) -> list[dict[str, Any]]: + """Get all event definitions.""" result = await self._get("/events") return result.get("data", []) + async def get_active_events(self) -> list[dict[str, Any]]: + """Get currently active game events.""" + result = await self._get("/events/active") + return result.get("data", []) + async def get_bank_items(self, page: int = 1, size: int = 100) -> list[dict[str, Any]]: result = await self._get("/my/bank/items", params={"page": page, "size": size}) return result.get("data", []) @@ -428,14 +438,14 @@ class ArtifactsClient: async def deposit_item(self, name: str, code: str, quantity: int) -> dict[str, Any]: result = await self._post_action( - f"/my/{name}/action/bank/deposit", + f"/my/{name}/action/bank/deposit/item", json_body={"code": code, "quantity": quantity}, ) return result.get("data", {}) async def withdraw_item(self, name: str, code: str, quantity: int) -> dict[str, Any]: result = await self._post_action( - f"/my/{name}/action/bank/withdraw", + f"/my/{name}/action/bank/withdraw/item", json_body={"code": code, "quantity": quantity}, ) return result.get("data", {}) diff --git a/frontend/src/components/character/skill-bars.tsx b/frontend/src/components/character/skill-bars.tsx index 640c53f..f2d47a0 100644 --- a/frontend/src/components/character/skill-bars.tsx +++ b/frontend/src/components/character/skill-bars.tsx @@ -19,6 +19,7 @@ export function SkillBars({ character }: SkillBarsProps) { ...skill, level: character[`${skill.key}_level` as keyof Character] as number, xp: character[`${skill.key}_xp` as keyof Character] as number, + maxXp: (character[`${skill.key}_max_xp` as keyof Character] as number) || 0, })).sort((a, b) => b.level - a.level); return ( @@ -44,12 +45,12 @@ export function SkillBars({ character }: SkillBarsProps) {
0 ? Math.max(2, (skill.xp % 100)) : 0)}%`, + width: `${Math.min(100, skill.maxXp > 0 ? Math.max(2, (skill.xp / skill.maxXp) * 100) : 0)}%`, }} />
- - {skill.xp.toLocaleString()} XP + + {skill.xp.toLocaleString()}{skill.maxXp > 0 ? ` / ${skill.maxXp.toLocaleString()}` : ""} XP diff --git a/frontend/src/lib/constants.ts b/frontend/src/lib/constants.ts index 29b5b13..101e8c1 100644 --- a/frontend/src/lib/constants.ts +++ b/frontend/src/lib/constants.ts @@ -13,6 +13,7 @@ export type SkillKey = (typeof SKILLS)[number]["key"]; export const EQUIPMENT_SLOTS = [ { key: "weapon_slot", label: "Weapon" }, + { key: "rune_slot", label: "Rune" }, { key: "shield_slot", label: "Shield" }, { key: "helmet_slot", label: "Helmet" }, { key: "body_armor_slot", label: "Body Armor" }, @@ -26,6 +27,7 @@ export const EQUIPMENT_SLOTS = [ { key: "artifact3_slot", label: "Artifact 3" }, { key: "utility1_slot", label: "Utility 1" }, { key: "utility2_slot", label: "Utility 2" }, + { key: "bag_slot", label: "Bag" }, ] as const; export type EquipmentSlotKey = (typeof EQUIPMENT_SLOTS)[number]["key"]; diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 876e7b3..31abfc5 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -18,12 +18,17 @@ export interface Character { haste: number; critical_strike: number; stamina: number; + wisdom: number; + prospecting: number; + initiative: number; + threat: number; attack_fire: number; attack_earth: number; attack_water: number; attack_air: number; + dmg: number; dmg_fire: number; dmg_earth: number; dmg_water: number; @@ -36,10 +41,15 @@ export interface Character { x: number; y: number; + layer: string; + map_id: number; cooldown: number; cooldown_expiration: string | null; + effects: Effect[]; + weapon_slot: string; + rune_slot: string; shield_slot: string; helmet_slot: string; body_armor_slot: string; @@ -55,6 +65,7 @@ export interface Character { utility1_slot_quantity: number; utility2_slot: string; utility2_slot_quantity: number; + bag_slot: string; inventory_max_items: number; inventory: InventorySlot[]; @@ -66,20 +77,28 @@ export interface Character { mining_level: number; mining_xp: number; + mining_max_xp: number; woodcutting_level: number; woodcutting_xp: number; + woodcutting_max_xp: number; fishing_level: number; fishing_xp: number; + fishing_max_xp: number; weaponcrafting_level: number; weaponcrafting_xp: number; + weaponcrafting_max_xp: number; gearcrafting_level: number; gearcrafting_xp: number; + gearcrafting_max_xp: number; jewelrycrafting_level: number; jewelrycrafting_xp: number; + jewelrycrafting_max_xp: number; cooking_level: number; cooking_xp: number; + cooking_max_xp: number; alchemy_level: number; alchemy_xp: number; + alchemy_max_xp: number; } export interface Effect {