Compare commits
No commits in common. "85f8327f5e5227f7dce5a8b6a901acf1f484f6d7" and "4d8974bc6699089c879de7296b31b800a7d967a4" have entirely different histories.
85f8327f5e
...
4d8974bc66
9 changed files with 16 additions and 14374 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 ---
|
||||||
|
|
|
||||||
|
|
@ -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
14306
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"];
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue