namespace Campus_live_rewrite.api.server.Classes { /// /// Which currency a game mode pays out in. /// /// The client asks /api/storefronts/v2/balance to add currency and NAMES the currency /// itself. Trusting that is how a completed Golden Trophy match paid out 1000 Dracula /// Silver - Crescendo's currency. The room the player is actually in decides now; the /// client's CurrencyType is only a fallback when the room is unknown. /// /// Room names come from the live server (Room_server/rooms/search). /// public static class GameCurrency { /// /// The one balance bucket this server keeps per currency, on the wire as "Platform" /// (BalancePlatform.NonPurchasedNotUsableInP2P = -2, the reference's ALL_PLATFORMS). The /// client holds a balance per (CurrencyType, Platform) and SUMS them, so every balance /// frame - the GET, a purchase response, a balance add, the 61 push - has to name this /// same bucket; one that says 0 (SteamPurchased) makes a phantom bucket that is added to /// the real one for the rest of the session. /// public const int AllPlatformsBucket = -2; /// Currency for a room name, or null when the room has no special currency. public static CurrencyType? ForRoomName(string? roomName) { if (string.IsNullOrWhiteSpace(roomName)) return null; // Room instance names arrive as "^RoomName". string n = roomName.TrimStart('^').Trim(); if (n.Equals("Crescendo", StringComparison.OrdinalIgnoreCase)) return CurrencyType.DraculaSilver; if (n.Equals("IsleOfLostSkulls", StringComparison.OrdinalIgnoreCase)) return CurrencyType.LostSkullsGold; if (n.StartsWith("RecRoyale", StringComparison.OrdinalIgnoreCase)) return CurrencyType.RecRoyale_Season1; if (n.StartsWith("Paintball", StringComparison.OrdinalIgnoreCase)) return CurrencyType.PaintballPaint; if (n.Equals("LaserTag", StringComparison.OrdinalIgnoreCase)) return CurrencyType.LaserTagTickets; // GoldenTrophy and everything else pay in Rec Center Tokens - which is what the // Golden Trophy storefront (StorefrontType 102) actually prices its items in. return CurrencyType.RecCenterTokens; } /// /// STRICT balance-room whitelist: the ONLY rooms in which POST v2/balance may mint /// currency, and which currency each mints. Anything not listed returns null and mints /// nothing. Rec Center Tokens are deliberately absent - they are server/admin only. /// /// Crescendo -> DraculaSilver (101) /// IsleOfLostSkulls -> LostSkullsGold (100) /// LaserTag -> LaserTagTickets (1) /// public static CurrencyType? ForBalanceRoom(string? roomName) { if (string.IsNullOrWhiteSpace(roomName)) return null; // Room instance names arrive as "^RoomName". string n = roomName.TrimStart('^').Trim(); if (n.Equals("Crescendo", StringComparison.OrdinalIgnoreCase)) return CurrencyType.DraculaSilver; if (n.Equals("IsleOfLostSkulls", StringComparison.OrdinalIgnoreCase)) return CurrencyType.LostSkullsGold; if (n.Equals("LaserTag", StringComparison.OrdinalIgnoreCase)) return CurrencyType.LaserTagTickets; return null; } /// /// Whether a currency may be minted by a PLAYER-facing endpoint (v2/balance, game rewards). /// Only the three room currencies qualify. Rec Center Tokens (and every other bucket) are /// NOT player-grantable - they can only be granted server-side / from the admin panel. /// public static bool IsPlayerGrantable(int currencyType) => currencyType == (int)CurrencyType.DraculaSilver || currencyType == (int)CurrencyType.LostSkullsGold || currencyType == (int)CurrencyType.LaserTagTickets; /// /// Most a single award may grant, per currency. 0 = no cap. /// /// Crescendo pays per CHECKPOINT, and a checkpoint is worth 0..30,000 Silver - /// five checkpoints, so 150,000 is a full game. The cap is enforced per request so /// a bad (or forged) amount cannot mint a fortune in one call. /// /// /// Whether a currency is an account balance a purchase can debit. Invalid (0) never is; /// everything else the enum defines is a real balance bucket. Used by bulkpurchase to /// reject a bag posted against a currency there is no wallet to spend from (400). /// public static bool IsSpendable(int currencyType) => currencyType != (int)CurrencyType.Invalid && Enum.IsDefined(typeof(CurrencyType), currencyType); public static long MaxPerAward(CurrencyType currency) { switch (currency) { case CurrencyType.DraculaSilver: return 30000; // per Crescendo checkpoint default: return 0; // uncapped } } /// Clamps an award to 0..MaxPerAward for that currency. public static long ClampAward(CurrencyType currency, long amount) { if (amount < 0) return 0; long max = MaxPerAward(currency); return (max > 0 && amount > max) ? max : amount; } } }