/** * Domain Entity: Achievement * * Represents an achievement that can be earned by users. * Achievements are categorized by role (driver, steward, admin) and type. */ import type { IEntity } from '@gridpilot/shared/domain'; export type AchievementCategory = 'driver' | 'steward' | 'admin' | 'community'; export type AchievementRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'; export interface AchievementProps { id: string; name: string; description: string; category: AchievementCategory; rarity: AchievementRarity; iconUrl?: string; points: number; requirements: AchievementRequirement[]; isSecret: boolean; createdAt: Date; } export interface AchievementRequirement { type: 'races_completed' | 'wins' | 'podiums' | 'clean_races' | 'protests_handled' | 'leagues_managed' | 'seasons_completed' | 'consecutive_clean' | 'rating_threshold' | 'trust_threshold' | 'events_stewarded' | 'members_managed' | 'championships_won'; value: number; operator: '>=' | '>' | '=' | '<' | '<='; } export class Achievement implements IEntity { readonly id: string; readonly name: string; readonly description: string; readonly category: AchievementCategory; readonly rarity: AchievementRarity; readonly iconUrl: string; readonly points: number; readonly requirements: AchievementRequirement[]; readonly isSecret: boolean; readonly createdAt: Date; private constructor(props: AchievementProps) { this.id = props.id; this.name = props.name; this.description = props.description; this.category = props.category; this.rarity = props.rarity; this.iconUrl = props.iconUrl ?? ''; this.points = props.points; this.requirements = props.requirements; this.isSecret = props.isSecret; this.createdAt = props.createdAt; } static create(props: Omit & { createdAt?: Date }): Achievement { if (!props.id || props.id.trim().length === 0) { throw new Error('Achievement ID is required'); } if (!props.name || props.name.trim().length === 0) { throw new Error('Achievement name is required'); } if (props.requirements.length === 0) { throw new Error('Achievement must have at least one requirement'); } return new Achievement({ ...props, createdAt: props.createdAt ?? new Date(), }); } /** * Check if user stats meet all requirements */ checkRequirements(stats: Record): boolean { return this.requirements.every(req => { const value = stats[req.type] ?? 0; switch (req.operator) { case '>=': return value >= req.value; case '>': return value > req.value; case '=': return value === req.value; case '<': return value < req.value; case '<=': return value <= req.value; default: return false; } }); } /** * Get rarity color for display */ getRarityColor(): string { const colors: Record = { common: '#9CA3AF', uncommon: '#22C55E', rare: '#3B82F6', epic: '#A855F7', legendary: '#F59E0B', }; return colors[this.rarity]; } /** * Get display text for hidden achievements */ getDisplayName(): string { if (this.isSecret) { return '???'; } return this.name; } getDisplayDescription(): string { if (this.isSecret) { return 'This achievement is secret. Keep playing to unlock it!'; } return this.description; } } // Predefined achievements for drivers export const DRIVER_ACHIEVEMENTS: Omit[] = [ { id: 'first-race', name: 'First Steps', description: 'Complete your first race', category: 'driver', rarity: 'common', points: 10, requirements: [{ type: 'races_completed', value: 1, operator: '>=' }], isSecret: false, }, { id: 'ten-races', name: 'Getting Started', description: 'Complete 10 races', category: 'driver', rarity: 'common', points: 25, requirements: [{ type: 'races_completed', value: 10, operator: '>=' }], isSecret: false, }, { id: 'fifty-races', name: 'Regular Racer', description: 'Complete 50 races', category: 'driver', rarity: 'uncommon', points: 50, requirements: [{ type: 'races_completed', value: 50, operator: '>=' }], isSecret: false, }, { id: 'hundred-races', name: 'Veteran', description: 'Complete 100 races', category: 'driver', rarity: 'rare', points: 100, requirements: [{ type: 'races_completed', value: 100, operator: '>=' }], isSecret: false, }, { id: 'first-win', name: 'Victory Lane', description: 'Win your first race', category: 'driver', rarity: 'uncommon', points: 50, requirements: [{ type: 'wins', value: 1, operator: '>=' }], isSecret: false, }, { id: 'ten-wins', name: 'Serial Winner', description: 'Win 10 races', category: 'driver', rarity: 'rare', points: 100, requirements: [{ type: 'wins', value: 10, operator: '>=' }], isSecret: false, }, { id: 'first-podium', name: 'Podium Finisher', description: 'Finish on the podium', category: 'driver', rarity: 'common', points: 25, requirements: [{ type: 'podiums', value: 1, operator: '>=' }], isSecret: false, }, { id: 'clean-streak-5', name: 'Clean Racer', description: 'Complete 5 consecutive races without incidents', category: 'driver', rarity: 'uncommon', points: 50, requirements: [{ type: 'consecutive_clean', value: 5, operator: '>=' }], isSecret: false, }, { id: 'clean-streak-10', name: 'Safety First', description: 'Complete 10 consecutive races without incidents', category: 'driver', rarity: 'rare', points: 100, requirements: [{ type: 'consecutive_clean', value: 10, operator: '>=' }], isSecret: false, }, { id: 'championship-win', name: 'Champion', description: 'Win a championship', category: 'driver', rarity: 'epic', points: 200, requirements: [{ type: 'championships_won', value: 1, operator: '>=' }], isSecret: false, }, { id: 'triple-crown', name: 'Triple Crown', description: 'Win 3 championships', category: 'driver', rarity: 'legendary', points: 500, requirements: [{ type: 'championships_won', value: 3, operator: '>=' }], isSecret: false, }, { id: 'elite-driver', name: 'Elite Driver', description: 'Reach Elite driver rating', category: 'driver', rarity: 'epic', points: 250, requirements: [{ type: 'rating_threshold', value: 90, operator: '>=' }], isSecret: false, }, ]; // Predefined achievements for stewards export const STEWARD_ACHIEVEMENTS: Omit[] = [ { id: 'first-protest', name: 'Justice Served', description: 'Handle your first protest', category: 'steward', rarity: 'common', points: 15, requirements: [{ type: 'protests_handled', value: 1, operator: '>=' }], isSecret: false, }, { id: 'ten-protests', name: 'Fair Judge', description: 'Handle 10 protests', category: 'steward', rarity: 'uncommon', points: 50, requirements: [{ type: 'protests_handled', value: 10, operator: '>=' }], isSecret: false, }, { id: 'fifty-protests', name: 'Senior Steward', description: 'Handle 50 protests', category: 'steward', rarity: 'rare', points: 100, requirements: [{ type: 'protests_handled', value: 50, operator: '>=' }], isSecret: false, }, { id: 'hundred-protests', name: 'Chief Steward', description: 'Handle 100 protests', category: 'steward', rarity: 'epic', points: 200, requirements: [{ type: 'protests_handled', value: 100, operator: '>=' }], isSecret: false, }, { id: 'event-steward-10', name: 'Event Official', description: 'Steward 10 race events', category: 'steward', rarity: 'uncommon', points: 50, requirements: [{ type: 'events_stewarded', value: 10, operator: '>=' }], isSecret: false, }, { id: 'trusted-steward', name: 'Trusted Steward', description: 'Achieve highly-trusted status', category: 'steward', rarity: 'rare', points: 150, requirements: [{ type: 'trust_threshold', value: 75, operator: '>=' }], isSecret: false, }, ]; // Predefined achievements for admins export const ADMIN_ACHIEVEMENTS: Omit[] = [ { id: 'first-league', name: 'League Founder', description: 'Create your first league', category: 'admin', rarity: 'common', points: 25, requirements: [{ type: 'leagues_managed', value: 1, operator: '>=' }], isSecret: false, }, { id: 'first-season', name: 'Season Organizer', description: 'Complete your first full season', category: 'admin', rarity: 'uncommon', points: 50, requirements: [{ type: 'seasons_completed', value: 1, operator: '>=' }], isSecret: false, }, { id: 'five-seasons', name: 'Experienced Organizer', description: 'Complete 5 seasons', category: 'admin', rarity: 'rare', points: 100, requirements: [{ type: 'seasons_completed', value: 5, operator: '>=' }], isSecret: false, }, { id: 'ten-seasons', name: 'Veteran Organizer', description: 'Complete 10 seasons', category: 'admin', rarity: 'epic', points: 200, requirements: [{ type: 'seasons_completed', value: 10, operator: '>=' }], isSecret: false, }, { id: 'large-league', name: 'Community Builder', description: 'Manage a league with 50+ members', category: 'admin', rarity: 'rare', points: 150, requirements: [{ type: 'members_managed', value: 50, operator: '>=' }], isSecret: false, }, { id: 'huge-league', name: 'Empire Builder', description: 'Manage a league with 100+ members', category: 'admin', rarity: 'epic', points: 300, requirements: [{ type: 'members_managed', value: 100, operator: '>=' }], isSecret: false, }, ]; // Community achievements (for all roles) export const COMMUNITY_ACHIEVEMENTS: Omit[] = [ { id: 'community-leader', name: 'Community Leader', description: 'Achieve community leader trust level', category: 'community', rarity: 'legendary', points: 500, requirements: [{ type: 'trust_threshold', value: 90, operator: '>=' }], isSecret: false, }, ];