Shop: every item is coin-priced; level/rank/achievement only gate the purchase
No more earned-only (rank/wins) cosmetics — every avatar, card back/front, reaction & sticker pack now has a coin price. Rank/wins/achievement become purchase requirements (coin · coin+rank · coin+rank+achievement), enforced client (mock + ShopScreen lock label) and server (ProfileService.ItemGate, keyed by kind:id). Ownership = default + purchased only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -139,6 +139,47 @@ public class ProfileService
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// Per-item purchase gates for the named (non-tier-encoded) cosmetics, keyed by
|
||||
// "kind:id" since ids repeat across kinds (e.g. "taunt" is both a reaction &
|
||||
// sticker pack). Every one is still coin-priced — this only gates the purchase.
|
||||
// ⚠️ Mirror of req* in src/lib/online/types.ts (AVATARS) + gamification.ts
|
||||
// (CARD_BACKS/FRONTS, REACTION_PACKS, STICKER_PACKS). Keep both in sync.
|
||||
private static readonly Dictionary<string, (int Level, int Rating, string? Ach)> ItemGate = new()
|
||||
{
|
||||
// avatars
|
||||
["avatar:a-robot"] = (0, 0, "wins_50"),
|
||||
["avatar:a-wizard"] = (0, 1300, null),
|
||||
["avatar:a-ninja"] = (0, 0, "wins_100"),
|
||||
["avatar:a-king"] = (0, 1500, null),
|
||||
["avatar:a-genie"] = (0, 1700, null),
|
||||
["avatar:a-crown"] = (0, 1900, "hakem_7"),
|
||||
["avatar:a-gem"] = (0, 2100, "shutout_10"),
|
||||
// card backs
|
||||
["cardback:crimson"] = (0, 0, "wins_25"),
|
||||
["cardback:ruby"] = (0, 1300, null),
|
||||
["cardback:royal"] = (0, 0, "wins_50"),
|
||||
["cardback:aurora"] = (0, 1500, null),
|
||||
["cardback:obsidian"] = (0, 1700, null),
|
||||
["cardback:imperial"] = (0, 1900, "hakem_7"),
|
||||
// card fronts
|
||||
["cardfront:parchment"] = (0, 1300, null),
|
||||
["cardfront:mint"] = (0, 0, "wins_50"),
|
||||
["cardfront:goldleaf"] = (0, 1500, null),
|
||||
["cardfront:crystal"] = (0, 1700, null),
|
||||
["cardfront:imperial-face"] = (0, 0, "wins_100"),
|
||||
// reaction packs
|
||||
["reactionpack:champion"] = (0, 1300, null),
|
||||
["reactionpack:legend"] = (0, 0, "wins_100"),
|
||||
// sticker packs
|
||||
["stickerpack:hokm"] = (0, 0, "shutout_1"),
|
||||
["stickerpack:persian"] = (0, 0, "wins_100"),
|
||||
["stickerpack:taunt"] = (0, 0, "kot_25"),
|
||||
["stickerpack:rulership"] = (0, 0, "hakem_7"),
|
||||
["stickerpack:firestorm"] = (0, 0, "streak_10"),
|
||||
["stickerpack:victory"] = (0, 1500, null),
|
||||
["stickerpack:raghib"] = (0, 0, "kot_10"),
|
||||
};
|
||||
|
||||
public async Task<(bool ok, ProfileDto? profile, string error)> ShopBuy(string uid, string kind, string id, int price)
|
||||
{
|
||||
var p = await GetOrCreate(uid, null);
|
||||
@@ -147,6 +188,13 @@ public class ProfileService
|
||||
var gate = GiftGateFor(id);
|
||||
if (p.Level < gate.Level || p.Rating < gate.Rating) return (false, p, "locked");
|
||||
|
||||
// Named-item gate (avatars/backs/fronts/reactions/stickers): coin-priced but
|
||||
// locked until the level / rating / achievement requirement is met.
|
||||
if (ItemGate.TryGetValue($"{kind}:{id}", out var ig) &&
|
||||
(p.Level < ig.Level || p.Rating < ig.Rating ||
|
||||
(ig.Ach != null && !p.Unlocked.Contains(ig.Ach))))
|
||||
return (false, p, "locked");
|
||||
|
||||
// XP packs are consumable (grant XP, may level up) — not added to an owned list.
|
||||
if (kind == "xp")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user