a034e74c16
CI / Build, lint & test (push) Successful in 2m19s
Pure rustfmt reflow (import grouping, array/match-arm/call-arg wrapping, alphabetized module decls, comment realignment). No semantic change: full-diff and `git diff -w` both confirm logic byte-identical to 271c363; workspace builds and all 74 core tests pass. Retained pre-existing WIP brought forward after verification.
93 lines
2.5 KiB
Rust
93 lines
2.5 KiB
Rust
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<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl Profile {
|
|
pub fn new(username: impl Into<String>, game_id: impl Into<String>) -> 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<String>,
|
|
}
|
|
|
|
/// 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<String>,
|
|
}
|