Compare commits

..

No commits in common. "85f8327f5e5227f7dce5a8b6a901acf1f484f6d7" and "4d8974bc6699089c879de7296b31b800a7d967a4" have entirely different histories.

9 changed files with 16 additions and 14374 deletions

View file

@ -33,7 +33,7 @@ async def get_dashboard(request: Request) -> DashboardData:
# Server status could be extended later (e.g., ping, event info) # Server status could be extended later (e.g., ping, event info)
server_status: dict | None = None server_status: dict | None = None
try: try:
events = await client.get_active_events() events = await client.get_events()
server_status = {"events": events} server_status = {"events": events}
except Exception: except Exception:
logger.warning("Failed to fetch server events for dashboard", exc_info=True) logger.warning("Failed to fetch server events for dashboard", exc_info=True)

View file

@ -22,7 +22,7 @@ async def get_active_events(request: Request) -> dict[str, Any]:
client = get_user_client(request) client = get_user_client(request)
try: try:
events = await client.get_active_events() events = await client.get_events()
except HTTPStatusError as exc: except HTTPStatusError as exc:
raise HTTPException( raise HTTPException(
status_code=exc.response.status_code, status_code=exc.response.status_code,

View file

@ -9,8 +9,8 @@ class Settings(BaseSettings):
# Artifacts API # Artifacts API
artifacts_api_url: str = "https://api.artifactsmmo.com" artifacts_api_url: str = "https://api.artifactsmmo.com"
# Rate limits (matching Artifacts API: 20 actions/2s, 20 data/1s) # Rate limits
action_rate_limit: int = 20 # actions per window action_rate_limit: int = 7 # actions per window
action_rate_window: float = 2.0 # seconds action_rate_window: float = 2.0 # seconds
data_rate_limit: int = 20 # data requests per window data_rate_limit: int = 20 # data requests per window
data_rate_window: float = 1.0 # seconds data_rate_window: float = 1.0 # seconds

View file

@ -136,10 +136,6 @@ class CharacterSchema(BaseModel):
haste: int = 0 haste: int = 0
critical_strike: int = 0 critical_strike: int = 0
stamina: int = 0 stamina: int = 0
wisdom: int = 0
prospecting: int = 0
initiative: int = 0
threat: int = 0
# Attack stats # Attack stats
attack_fire: int = 0 attack_fire: int = 0
@ -148,7 +144,6 @@ class CharacterSchema(BaseModel):
attack_air: int = 0 attack_air: int = 0
# Damage stats # Damage stats
dmg: int = 0
dmg_fire: int = 0 dmg_fire: int = 0
dmg_earth: int = 0 dmg_earth: int = 0
dmg_water: int = 0 dmg_water: int = 0
@ -163,19 +158,13 @@ class CharacterSchema(BaseModel):
# Position # Position
x: int = 0 x: int = 0
y: int = 0 y: int = 0
layer: str = "overworld"
map_id: int = 0
# Cooldown # Cooldown
cooldown: int = 0 cooldown: int = 0
cooldown_expiration: datetime | None = None cooldown_expiration: datetime | None = None
# Active effects
effects: list[EffectSchema] = Field(default_factory=list)
# Equipment slots # Equipment slots
weapon_slot: str = "" weapon_slot: str = ""
rune_slot: str = ""
shield_slot: str = "" shield_slot: str = ""
helmet_slot: str = "" helmet_slot: str = ""
body_armor_slot: str = "" body_armor_slot: str = ""
@ -191,7 +180,6 @@ class CharacterSchema(BaseModel):
utility1_slot_quantity: int = 0 utility1_slot_quantity: int = 0
utility2_slot: str = "" utility2_slot: str = ""
utility2_slot_quantity: int = 0 utility2_slot_quantity: int = 0
bag_slot: str = ""
# Inventory # Inventory
inventory_max_items: int = 0 inventory_max_items: int = 0
@ -203,31 +191,23 @@ class CharacterSchema(BaseModel):
task_progress: int = 0 task_progress: int = 0
task_total: int = 0 task_total: int = 0
# Skill levels and XP (includes max_xp for progress calculation) # Skill levels and XP
mining_level: int = 0 mining_level: int = 0
mining_xp: int = 0 mining_xp: int = 0
mining_max_xp: int = 0
woodcutting_level: int = 0 woodcutting_level: int = 0
woodcutting_xp: int = 0 woodcutting_xp: int = 0
woodcutting_max_xp: int = 0
fishing_level: int = 0 fishing_level: int = 0
fishing_xp: int = 0 fishing_xp: int = 0
fishing_max_xp: int = 0
weaponcrafting_level: int = 0 weaponcrafting_level: int = 0
weaponcrafting_xp: int = 0 weaponcrafting_xp: int = 0
weaponcrafting_max_xp: int = 0
gearcrafting_level: int = 0 gearcrafting_level: int = 0
gearcrafting_xp: int = 0 gearcrafting_xp: int = 0
gearcrafting_max_xp: int = 0
jewelrycrafting_level: int = 0 jewelrycrafting_level: int = 0
jewelrycrafting_xp: int = 0 jewelrycrafting_xp: int = 0
jewelrycrafting_max_xp: int = 0
cooking_level: int = 0 cooking_level: int = 0
cooking_xp: int = 0 cooking_xp: int = 0
cooking_max_xp: int = 0
alchemy_level: int = 0 alchemy_level: int = 0
alchemy_xp: int = 0 alchemy_xp: int = 0
alchemy_max_xp: int = 0
# --- Dashboard --- # --- Dashboard ---

View file

@ -156,12 +156,8 @@ class ArtifactsClient:
await asyncio.sleep(retry_after) await asyncio.sleep(retry_after)
continue continue
# 498 = character not found raise immediately # 498 = character in cooldown wait and retry
if response.status_code == 498: if response.status_code == 498:
response.raise_for_status()
# 499 = character in cooldown wait and retry
if response.status_code == 499:
try: try:
body = response.json() body = response.json()
cooldown = body.get("data", {}).get("cooldown", {}) cooldown = body.get("data", {}).get("cooldown", {})
@ -290,7 +286,7 @@ class ArtifactsClient:
return [ItemSchema.model_validate(i) for i in result.get("data", [])] return [ItemSchema.model_validate(i) for i in result.get("data", [])]
async def get_all_items(self) -> list[ItemSchema]: async def get_all_items(self) -> list[ItemSchema]:
raw = await self._get_paginated("/items", page_size=10000) raw = await self._get_paginated("/items")
return [ItemSchema.model_validate(i) for i in raw] return [ItemSchema.model_validate(i) for i in raw]
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@ -302,7 +298,7 @@ class ArtifactsClient:
return [MonsterSchema.model_validate(m) for m in result.get("data", [])] return [MonsterSchema.model_validate(m) for m in result.get("data", [])]
async def get_all_monsters(self) -> list[MonsterSchema]: async def get_all_monsters(self) -> list[MonsterSchema]:
raw = await self._get_paginated("/monsters", page_size=10000) raw = await self._get_paginated("/monsters")
return [MonsterSchema.model_validate(m) for m in raw] return [MonsterSchema.model_validate(m) for m in raw]
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@ -314,7 +310,7 @@ class ArtifactsClient:
return [ResourceSchema.model_validate(r) for r in result.get("data", [])] return [ResourceSchema.model_validate(r) for r in result.get("data", [])]
async def get_all_resources(self) -> list[ResourceSchema]: async def get_all_resources(self) -> list[ResourceSchema]:
raw = await self._get_paginated("/resources", page_size=10000) raw = await self._get_paginated("/resources")
return [ResourceSchema.model_validate(r) for r in raw] return [ResourceSchema.model_validate(r) for r in raw]
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@ -337,23 +333,17 @@ class ArtifactsClient:
return [MapSchema.model_validate(m) for m in result.get("data", [])] return [MapSchema.model_validate(m) for m in result.get("data", [])]
async def get_all_maps(self) -> list[MapSchema]: async def get_all_maps(self) -> list[MapSchema]:
raw = await self._get_paginated("/maps", page_size=10000) raw = await self._get_paginated("/maps")
return [MapSchema.model_validate(m) for m in raw] return [MapSchema.model_validate(m) for m in raw]
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Data endpoints - Events, Bank, GE # Data endpoints - Events, Bank, GE
# ------------------------------------------------------------------ # ------------------------------------------------------------------
async def get_all_events(self) -> list[dict[str, Any]]: async def get_events(self) -> list[dict[str, Any]]:
"""Get all event definitions."""
result = await self._get("/events") result = await self._get("/events")
return result.get("data", []) 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]]: 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}) result = await self._get("/my/bank/items", params={"page": page, "size": size})
return result.get("data", []) return result.get("data", [])
@ -438,14 +428,14 @@ class ArtifactsClient:
async def deposit_item(self, name: str, code: str, quantity: int) -> dict[str, Any]: async def deposit_item(self, name: str, code: str, quantity: int) -> dict[str, Any]:
result = await self._post_action( result = await self._post_action(
f"/my/{name}/action/bank/deposit/item", f"/my/{name}/action/bank/deposit",
json_body={"code": code, "quantity": quantity}, json_body={"code": code, "quantity": quantity},
) )
return result.get("data", {}) return result.get("data", {})
async def withdraw_item(self, name: str, code: str, quantity: int) -> dict[str, Any]: async def withdraw_item(self, name: str, code: str, quantity: int) -> dict[str, Any]:
result = await self._post_action( result = await self._post_action(
f"/my/{name}/action/bank/withdraw/item", f"/my/{name}/action/bank/withdraw",
json_body={"code": code, "quantity": quantity}, json_body={"code": code, "quantity": quantity},
) )
return result.get("data", {}) return result.get("data", {})

14306
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,6 @@ export function SkillBars({ character }: SkillBarsProps) {
...skill, ...skill,
level: character[`${skill.key}_level` as keyof Character] as number, level: character[`${skill.key}_level` as keyof Character] as number,
xp: character[`${skill.key}_xp` 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); })).sort((a, b) => b.level - a.level);
return ( return (
@ -45,12 +44,12 @@ export function SkillBars({ character }: SkillBarsProps) {
<div <div
className={`h-full rounded-full ${SKILL_COLOR_MAP[skill.color]} opacity-80 transition-all`} className={`h-full rounded-full ${SKILL_COLOR_MAP[skill.color]} opacity-80 transition-all`}
style={{ style={{
width: `${Math.min(100, skill.maxXp > 0 ? Math.max(2, (skill.xp / skill.maxXp) * 100) : 0)}%`, width: `${Math.min(100, skill.xp > 0 ? Math.max(2, (skill.xp % 100)) : 0)}%`,
}} }}
/> />
</div> </div>
<span className="text-[10px] text-muted-foreground w-24 text-right shrink-0"> <span className="text-[10px] text-muted-foreground w-16 text-right shrink-0">
{skill.xp.toLocaleString()}{skill.maxXp > 0 ? ` / ${skill.maxXp.toLocaleString()}` : ""} XP {skill.xp.toLocaleString()} XP
</span> </span>
</div> </div>
</div> </div>

View file

@ -13,7 +13,6 @@ export type SkillKey = (typeof SKILLS)[number]["key"];
export const EQUIPMENT_SLOTS = [ export const EQUIPMENT_SLOTS = [
{ key: "weapon_slot", label: "Weapon" }, { key: "weapon_slot", label: "Weapon" },
{ key: "rune_slot", label: "Rune" },
{ key: "shield_slot", label: "Shield" }, { key: "shield_slot", label: "Shield" },
{ key: "helmet_slot", label: "Helmet" }, { key: "helmet_slot", label: "Helmet" },
{ key: "body_armor_slot", label: "Body Armor" }, { key: "body_armor_slot", label: "Body Armor" },
@ -27,7 +26,6 @@ export const EQUIPMENT_SLOTS = [
{ key: "artifact3_slot", label: "Artifact 3" }, { key: "artifact3_slot", label: "Artifact 3" },
{ key: "utility1_slot", label: "Utility 1" }, { key: "utility1_slot", label: "Utility 1" },
{ key: "utility2_slot", label: "Utility 2" }, { key: "utility2_slot", label: "Utility 2" },
{ key: "bag_slot", label: "Bag" },
] as const; ] as const;
export type EquipmentSlotKey = (typeof EQUIPMENT_SLOTS)[number]["key"]; export type EquipmentSlotKey = (typeof EQUIPMENT_SLOTS)[number]["key"];

View file

@ -18,17 +18,12 @@ export interface Character {
haste: number; haste: number;
critical_strike: number; critical_strike: number;
stamina: number; stamina: number;
wisdom: number;
prospecting: number;
initiative: number;
threat: number;
attack_fire: number; attack_fire: number;
attack_earth: number; attack_earth: number;
attack_water: number; attack_water: number;
attack_air: number; attack_air: number;
dmg: number;
dmg_fire: number; dmg_fire: number;
dmg_earth: number; dmg_earth: number;
dmg_water: number; dmg_water: number;
@ -41,15 +36,10 @@ export interface Character {
x: number; x: number;
y: number; y: number;
layer: string;
map_id: number;
cooldown: number; cooldown: number;
cooldown_expiration: string | null; cooldown_expiration: string | null;
effects: Effect[];
weapon_slot: string; weapon_slot: string;
rune_slot: string;
shield_slot: string; shield_slot: string;
helmet_slot: string; helmet_slot: string;
body_armor_slot: string; body_armor_slot: string;
@ -65,7 +55,6 @@ export interface Character {
utility1_slot_quantity: number; utility1_slot_quantity: number;
utility2_slot: string; utility2_slot: string;
utility2_slot_quantity: number; utility2_slot_quantity: number;
bag_slot: string;
inventory_max_items: number; inventory_max_items: number;
inventory: InventorySlot[]; inventory: InventorySlot[];
@ -77,28 +66,20 @@ export interface Character {
mining_level: number; mining_level: number;
mining_xp: number; mining_xp: number;
mining_max_xp: number;
woodcutting_level: number; woodcutting_level: number;
woodcutting_xp: number; woodcutting_xp: number;
woodcutting_max_xp: number;
fishing_level: number; fishing_level: number;
fishing_xp: number; fishing_xp: number;
fishing_max_xp: number;
weaponcrafting_level: number; weaponcrafting_level: number;
weaponcrafting_xp: number; weaponcrafting_xp: number;
weaponcrafting_max_xp: number;
gearcrafting_level: number; gearcrafting_level: number;
gearcrafting_xp: number; gearcrafting_xp: number;
gearcrafting_max_xp: number;
jewelrycrafting_level: number; jewelrycrafting_level: number;
jewelrycrafting_xp: number; jewelrycrafting_xp: number;
jewelrycrafting_max_xp: number;
cooking_level: number; cooking_level: number;
cooking_xp: number; cooking_xp: number;
cooking_max_xp: number;
alchemy_level: number; alchemy_level: number;
alchemy_xp: number; alchemy_xp: number;
alchemy_max_xp: number;
} }
export interface Effect { export interface Effect {