127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
/**
|
|
* Domain Entity: Achievement
|
|
*
|
|
* Represents an achievement that can be earned by users.
|
|
* Achievements are categorized by role (driver, steward, admin) and type.
|
|
*/
|
|
|
|
import { Entity } from '@core/shared/domain/Entity';
|
|
|
|
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 extends Entity<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) {
|
|
super(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<AchievementProps, 'createdAt'> & { 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<string, number>): 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<AchievementRarity, string> = {
|
|
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;
|
|
}
|
|
}
|