use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct Profile { pub id: String, pub username: String, pub level: i64, pub xp: i64, /// The game this profile belongs to (e.g. "fifa17", "fifa23"). Scopes all of /// this profile's downstream state so multiple games share one core + DB. pub game_id: String, pub created_at: DateTime, pub updated_at: DateTime, } impl Profile { pub fn new(username: impl Into, game_id: impl Into) -> Self { let now = Utc::now(); Self { id: Uuid::new_v4().to_string(), username: username.into(), level: 1, xp: 0, game_id: game_id.into(), created_at: now, updated_at: now, } } } #[derive(Debug, Deserialize)] pub struct CreateProfileRequest { pub username: Option, } /// XP required to reach each level (cumulative total from level 1). /// Level 1 starts at 0 XP. Level 2 needs 500 total XP, etc. pub const XP_THRESHOLDS: &[i64] = &[ 0, // level 1 500, // level 2 1200, // level 3 2000, // level 4 3000, // level 5 4200, // level 6 5600, // level 7 7200, // level 8 9000, // level 9 11000, // level 10 ]; /// Compute the level for a given cumulative XP total. pub fn level_for_xp(total_xp: i64) -> i64 { let base = XP_THRESHOLDS .iter() .rposition(|&t| total_xp >= t) .map(|i| i as i64 + 1) .unwrap_or(1); // Beyond the table: every 2500 XP is another level. let overflow_xp = total_xp - XP_THRESHOLDS.last().copied().unwrap_or(0); if overflow_xp > 0 { let table_max = XP_THRESHOLDS.len() as i64; table_max + (overflow_xp / 2500) } else { base } } /// Coins awarded per new level gained. pub fn coins_for_level(new_level: i64) -> i64 { new_level * 500 } /// Pack granted at milestone levels (5, 10, 15, 20, …). pub fn pack_for_level(new_level: i64) -> Option<&'static str> { match new_level { 5 => Some("bronze_pack"), 10 => Some("silver_pack"), 15 => Some("gold_pack"), 20 => Some("rare_gold_pack"), l if l > 20 && l % 5 == 0 => Some("gold_pack"), _ => None, } } #[derive(Debug, Clone, Serialize)] pub struct LevelUpEvent { pub new_level: i64, pub coins_granted: i64, pub pack_granted: Option, }