Turn timer + auto-play, disconnect/reconnect, cosmetics, queue & paid plan
- Turn timer (20s) for play/trump; system auto-plays a smart move on timeout - Disconnect handling (mock): wait-for-return countdown, system covers turns - Cosmetics: titles, card-back styles, custom profile-image upload, badges; pickers in Profile; shop sells card styles; reward modal shows new titles - Paid plan (pro): free players queue when server busy, pro skips; upgrade flow - OnlineService extended (upgradePlan, richer profile patch); mock implements queue + plans; gamification adds TITLES + CARD_STYLES Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,15 @@
|
||||
import {
|
||||
AchievementDef,
|
||||
AchievementUnlock,
|
||||
CardStyleDef,
|
||||
LeagueInfo,
|
||||
MatchSummary,
|
||||
PlayerStats,
|
||||
RankTier,
|
||||
RankTierId,
|
||||
RewardResult,
|
||||
TitleDef,
|
||||
TitleUnlock,
|
||||
UserProfile,
|
||||
} from "./types";
|
||||
|
||||
@@ -176,6 +179,55 @@ export function achievementProgress(
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------ Titles ------------------------------- */
|
||||
|
||||
export const TITLES: TitleDef[] = [
|
||||
{ id: "novice", nameFa: "تازهکار", nameEn: "Novice", hintFa: "پیشفرض", hintEn: "Default" },
|
||||
{ id: "winner", nameFa: "برنده", nameEn: "Winner", hintFa: "۱۰ برد", hintEn: "10 wins" },
|
||||
{ id: "kot_master", nameFa: "استاد کُت", nameEn: "Kot Master", hintFa: "۱۰ کُت", hintEn: "10 kots" },
|
||||
{ id: "veteran", nameFa: "کهنهکار", nameEn: "Veteran", hintFa: "سطح ۲۰", hintEn: "Level 20" },
|
||||
{ id: "champion", nameFa: "قهرمان", nameEn: "Champion", hintFa: "لیگ طلا", hintEn: "Gold league" },
|
||||
{ id: "legend", nameFa: "اسطوره", nameEn: "Legend", hintFa: "لیگ استاد", hintEn: "Master league" },
|
||||
];
|
||||
|
||||
export function titleUnlocked(
|
||||
id: string,
|
||||
stats: PlayerStats,
|
||||
rating: number,
|
||||
level: number
|
||||
): boolean {
|
||||
switch (id) {
|
||||
case "novice":
|
||||
return true;
|
||||
case "winner":
|
||||
return stats.wins >= 10;
|
||||
case "kot_master":
|
||||
return stats.kotsFor >= 10;
|
||||
case "veteran":
|
||||
return level >= 20;
|
||||
case "champion":
|
||||
return rating >= tierById("gold").floor;
|
||||
case "legend":
|
||||
return rating >= tierById("master").floor;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- Card styles ---------------------------- */
|
||||
|
||||
export const CARD_STYLES: CardStyleDef[] = [
|
||||
{ id: "classic", nameFa: "کلاسیک", nameEn: "Classic", c1: "#14274f", c2: "#0a142e", accent: "#d4af37", price: 0 },
|
||||
{ id: "sapphire", nameFa: "یاقوت کبود", nameEn: "Sapphire", c1: "#0b3a82", c2: "#06173a", accent: "#6aa6ff", price: 800 },
|
||||
{ id: "ruby", nameFa: "یاقوت", nameEn: "Ruby", c1: "#7f1d2e", c2: "#2b0a12", accent: "#ff7a90", price: 800 },
|
||||
{ id: "emerald", nameFa: "زمرد", nameEn: "Emerald", c1: "#0d6b5e", c2: "#062420", accent: "#2dd4bf", price: 1000 },
|
||||
{ id: "royal", nameFa: "سلطنتی", nameEn: "Royal", c1: "#4a1d7f", c2: "#1a0a2e", accent: "#c77dff", price: 1500 },
|
||||
];
|
||||
|
||||
export function cardStyleById(id: string): CardStyleDef {
|
||||
return CARD_STYLES.find((c) => c.id === id) ?? CARD_STYLES[0];
|
||||
}
|
||||
|
||||
/* ---------------------- Apply a match result ------------------------- */
|
||||
|
||||
function applyStats(stats: PlayerStats, summary: MatchSummary): PlayerStats {
|
||||
@@ -239,6 +291,19 @@ export function applyMatchResult(
|
||||
|
||||
const coinsAfter = Math.max(0, coinsBefore + cDelta + achievementCoins);
|
||||
|
||||
// Titles unlocked by the new state.
|
||||
const ownedTitles = [...(profile.ownedTitles ?? [])];
|
||||
const newTitles: TitleUnlock[] = [];
|
||||
for (const tdef of TITLES) {
|
||||
if (
|
||||
titleUnlocked(tdef.id, stats, ratingAfter, lvl.level) &&
|
||||
!ownedTitles.includes(tdef.id)
|
||||
) {
|
||||
ownedTitles.push(tdef.id);
|
||||
newTitles.push({ id: tdef.id, nameFa: tdef.nameFa, nameEn: tdef.nameEn });
|
||||
}
|
||||
}
|
||||
|
||||
const leagueBefore = getLeagueInfo(ratingBefore);
|
||||
const leagueAfter = getLeagueInfo(ratingAfter);
|
||||
const tierIndex = (id: RankTierId) => RANK_TIERS.findIndex((t) => t.id === id);
|
||||
@@ -256,6 +321,7 @@ export function applyMatchResult(
|
||||
stats,
|
||||
achievements,
|
||||
unlocked,
|
||||
ownedTitles,
|
||||
};
|
||||
|
||||
const reward: RewardResult = {
|
||||
@@ -270,6 +336,7 @@ export function applyMatchResult(
|
||||
levelAfter: lvl.level,
|
||||
leveledUp: lvl.level > levelBefore,
|
||||
newAchievements,
|
||||
newTitles,
|
||||
promoted,
|
||||
demoted,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user