Match intro "players joining" loading screen + i18n fix; checkpoint
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 7m38s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m9s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 1s

- MatchIntroOverlay: UNO-style pre-game reveal — the 4 seats animate into the
  table (with "?" placeholders until each player's data streams in for live
  matches), a 3-2-1-GO countdown, then the table shows. Wired via game-store
  matchIntroPending/consumeIntro, rendered online-only in GameScreen.
- Fix: intro.found / intro.getReady / intro.go existed only in the Persian dict;
  added the English strings (would have shown raw keys to EN users).
- Checkpoint of the in-progress UI/social batch (CoinsPill, shop titles section,
  friend-request rate limit, etc.) — all green.

Verified: tsc + next build + scripts/sim.ts + dotnet build server/Hokm.slnx all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-06 21:58:54 +03:30
parent cb27a16dc1
commit 03dfbe1e67
16 changed files with 319 additions and 79 deletions
@@ -56,7 +56,7 @@ public static class Gamification
// metric: wins|kotsFor|bestWinStreak|shutoutWins|games|tricks|level ; ratingFloor>0 = rank ach.
private record AchDef(string Id, string? Metric, int RatingFloor, int Goal, int Coin, string NameFa, string NameEn, string Icon);
// Mirrors src/lib/online/gamification.ts (same ids/goals/coins/metrics).
private static int Coin(int g) => Math.Max(100, (int)Math.Floor((80.0 + g * 12) / 50.0 + 0.5) * 50);
private static int Coin(int g) => Math.Min(1500, Math.Max(50, (int)Math.Floor((40.0 + g * 6) / 50.0 + 0.5) * 50));
private static string Fa(int n) =>
new string(n.ToString().Select(c => c is >= '0' and <= '9' ? "۰۱۲۳۴۵۶۷۸۹"[c - '0'] : c).ToArray());
@@ -77,11 +77,11 @@ public static class Gamification
l.AddRange(Tier("roundsWon", "rounds", "🎴", new[] { 25, 100, 250, 500, 1000, 2000, 5000 }, g => $"{Fa(g)} دست برده", g => $"{g} Rounds Won"));
l.AddRange(Tier("tricks", "tricks", "🗂️", new[] { 50, 100, 250, 500, 1000, 2500, 5000, 10000 }, g => $"{Fa(g)} دست‌برد", g => $"{g} Tricks"));
l.AddRange(Tier("losses", "grit", "🛡️", new[] { 10, 50, 100 }, g => $"{Fa(g)} باخت", g => $"{g} Losses"));
l.Add(new AchDef("reach_silver", null, 1100, 1, 200, "لیگ نقره", "Reach Silver", "🥈"));
l.Add(new AchDef("reach_gold", null, 1300, 1, 500, "لیگ طلا", "Reach Gold", "🥇"));
l.Add(new AchDef("reach_platinum", null, 1500, 1, 1000, "لیگ پلاتین", "Reach Platinum", "🛡️"));
l.Add(new AchDef("reach_diamond", null, 1700, 1, 2000, "لیگ الماس", "Reach Diamond", "💠"));
l.Add(new AchDef("reach_master", null, 1900, 1, 4000, "لیگ استاد", "Reach Master", "👑"));
l.Add(new AchDef("reach_silver", null, 1100, 1, 150, "لیگ نقره", "Reach Silver", "🥈"));
l.Add(new AchDef("reach_gold", null, 1300, 1, 300, "لیگ طلا", "Reach Gold", "🥇"));
l.Add(new AchDef("reach_platinum", null, 1500, 1, 500, "لیگ پلاتین", "Reach Platinum", "🛡️"));
l.Add(new AchDef("reach_diamond", null, 1700, 1, 900, "لیگ الماس", "Reach Diamond", "💠"));
l.Add(new AchDef("reach_master", null, 1900, 1, 1500, "لیگ استاد", "Reach Master", "👑"));
return l.ToArray();
}
@@ -11,10 +11,10 @@ public class ProfileService
public static readonly CoinPackDto[] Packs =
{
new() { Id = "p1", Coins = 50000, Bonus = 0, PriceToman = 95000, Tag = "starter" },
new() { Id = "p2", Coins = 120000, Bonus = 15000, PriceToman = 189000, Tag = "popular" },
new() { Id = "p3", Coins = 300000, Bonus = 50000, PriceToman = 389000, Tag = "best" },
new() { Id = "p4", Coins = 700000, Bonus = 150000, PriceToman = 790000 },
new() { Id = "p1", Coins = 5000, Bonus = 0, PriceToman = 99000, Tag = "starter" },
new() { Id = "p2", Coins = 11000, Bonus = 1000, PriceToman = 199000, Tag = "popular" },
new() { Id = "p3", Coins = 24000, Bonus = 4000, PriceToman = 399000, Tag = "best" },
new() { Id = "p4", Coins = 50000, Bonus = 15000, PriceToman = 799000 },
};
private static ProfileDto Default(string userId, string? name) => new()
@@ -120,9 +120,9 @@ public class ProfileService
// Coin-priced XP packs (XP is intentionally expensive). Server-authoritative.
public static readonly Dictionary<string, (int Price, int Xp)> XpPacks = new()
{
["xp1"] = (5000, 200),
["xp2"] = (12000, 600),
["xp3"] = (25000, 1500),
["xp1"] = (1500, 200),
["xp2"] = (4000, 600),
["xp3"] = (8000, 1500),
};
public async Task<(bool ok, ProfileDto? profile, string error)> ShopBuy(string uid, string kind, string id, int price)
@@ -165,7 +165,7 @@ public class ProfileService
/* ----------------------------- daily ------------------------------ */
// Mirror the client DAILY_REWARDS (src/lib/online/gamification.ts) exactly.
private static readonly int[] DailyRewards = { 300, 500, 750, 1000, 1500, 2500, 7500 };
private static readonly int[] DailyRewards = { 100, 150, 200, 300, 400, 600, 1500 };
private static string Today => DateTime.UtcNow.ToString("yyyy-MM-dd");
public async Task<(int day, string? lastClaimed, bool available)> GetDaily(string uid)