mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-24 23:13:05 +00:00
Add crown cosmetic type (#4618)
## What Adds **crowns** as a new cosmetic category, wired end-to-end like flags/skins, plus a Crowns tab in the lobby cosmetics modal (#4617). Catalog shape in cosmetics.json: ```json "crowns": { "gold_crown": { "name": "gold_crown", "url": "...", "affiliateCode": null, "product": null, "priceHard": 5, "artist": "...", "rarity": "common" } } ``` ## Changes - **Core**: `CrownSchema` + optional `crowns` record in `CosmeticsSchema`; `crownName` in `PlayerCosmeticRefs`; resolved `crown: { name, url }` in `PlayerCosmetics` - **Server**: `isCrownAllowed` in the privilege checker — requires a `crown:*` or `crown:<name>` flare, resolves the ref to the catalog URL - **Client**: crown selection persisted under the `crown` localStorage key (stale/unowned selections self-clear), `crownRelationship` + resolve/refs wiring, `"crown"` purchase type, Crowns tab in the store, and a Crowns tab in the cosmetics modal (owned crowns + Default tile; selecting persists and keeps the modal open) - **i18n**: `store.crowns`, `store.no_crowns` - Tests for schema parsing, privilege checks, and cosmetic resolution ## Not in this PR - In-game rendering of `player.cosmetics.crown` (name pass / leaderboards / player panel) - API-side support (`/shop/purchase` accepting `cosmeticType: "crown"`, granting `crown:<name>` flares, serving `crowns` in cosmetics.json) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { html, LitElement, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import {
|
||||
Crown,
|
||||
Effect,
|
||||
Flag,
|
||||
isNukeExplosionEffect,
|
||||
@@ -91,6 +92,9 @@ export class CosmeticButton extends LitElement {
|
||||
if (this.activeResolved.type === "effect") {
|
||||
return translateCosmetic("effects", c.name);
|
||||
}
|
||||
if (this.activeResolved.type === "crown") {
|
||||
return translateCosmetic("crowns", c.name);
|
||||
}
|
||||
return translateCosmetic("flags", c.name);
|
||||
}
|
||||
|
||||
@@ -209,6 +213,25 @@ export class CosmeticButton extends LitElement {
|
||||
></trail-swatch>`;
|
||||
}
|
||||
|
||||
if (this.activeResolved.type === "crown") {
|
||||
const c = this.activeResolved.cosmetic as Crown | null;
|
||||
if (c === null) {
|
||||
// "Default" (none) tile — selecting it clears the crown.
|
||||
return html`<div
|
||||
class="w-full h-full flex items-center justify-center text-white/40 text-xs uppercase"
|
||||
>
|
||||
${translateText("territory_patterns.pattern.default")}
|
||||
</div>`;
|
||||
}
|
||||
return html`<img
|
||||
src=${c.url}
|
||||
alt=${c.name}
|
||||
class="w-full h-full object-contain pointer-events-none"
|
||||
draggable="false"
|
||||
loading="lazy"
|
||||
/>`;
|
||||
}
|
||||
|
||||
if (this.activeResolved.type === "pack") {
|
||||
const pack = this.activeResolved.cosmetic as Pack;
|
||||
const isHard = pack.currency === "hard";
|
||||
|
||||
Reference in New Issue
Block a user