Phase 11: FUT Champions mode and Division Rivals weekly rewards
CI / Build, lint & test (push) Failing after 1m33s
CI / Build, lint & test (push) Failing after 1m33s
FUT Champions (Weekend League): - POST /fut-champs/start — open a new 30-match week (one active session at a time) - GET /fut-champs — current session status with matches_remaining - POST /fut-champs/:id/result — record a match; auto-completes and computes tier at 30 - POST /fut-champs/:id/claim — claim coins + pack reward (idempotent guard) - GET /fut-champs/history — past sessions newest-first - 11 reward tiers: Elite (27+ wins, 50k coins + icon pack) down to Bronze 1 (0 wins, 250 coins) Division Rivals: - POST /rivals/claim-weekly — claim weekly reward scaled by current division - Week counter is monotonic (offline-safe, no real-time calendar dependency) - Division 1-3 get a bonus pack alongside coins Migration 0008 adds fut_champs_sessions table and two columns to seasons. 12 new integration tests — all 67 pass, clippy clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const MAX_MATCHES: i64 = 30;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct FutChampsSession {
|
||||
pub id: String,
|
||||
pub profile_id: String,
|
||||
pub week_number: i64,
|
||||
pub matches_played: i64,
|
||||
pub wins: i64,
|
||||
pub draws: i64,
|
||||
pub losses: i64,
|
||||
pub status: String,
|
||||
pub reward_tier: Option<String>,
|
||||
pub reward_coins: Option<i64>,
|
||||
pub reward_pack_id: Option<String>,
|
||||
pub reward_claimed: bool,
|
||||
pub started_at: String,
|
||||
pub completed_at: Option<String>,
|
||||
}
|
||||
|
||||
impl FutChampsSession {
|
||||
pub fn matches_remaining(&self) -> i64 {
|
||||
(MAX_MATCHES - self.matches_played).max(0)
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.status == "completed"
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward tier based on wins out of 30 matches — mirrors FUT 23 structure.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum ChampsTier {
|
||||
Elite,
|
||||
WorldClass,
|
||||
Gold3,
|
||||
Gold2,
|
||||
Gold1,
|
||||
Silver3,
|
||||
Silver2,
|
||||
Silver1,
|
||||
Bronze3,
|
||||
Bronze2,
|
||||
Bronze1,
|
||||
}
|
||||
|
||||
impl ChampsTier {
|
||||
pub fn from_wins(wins: i64) -> Self {
|
||||
match wins {
|
||||
27.. => Self::Elite,
|
||||
23..=26 => Self::WorldClass,
|
||||
20..=22 => Self::Gold3,
|
||||
17..=19 => Self::Gold2,
|
||||
14..=16 => Self::Gold1,
|
||||
11..=13 => Self::Silver3,
|
||||
8..=10 => Self::Silver2,
|
||||
5..=7 => Self::Silver1,
|
||||
2..=4 => Self::Bronze3,
|
||||
1 => Self::Bronze2,
|
||||
_ => Self::Bronze1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Elite => "Elite",
|
||||
Self::WorldClass => "World Class",
|
||||
Self::Gold3 => "Gold 3",
|
||||
Self::Gold2 => "Gold 2",
|
||||
Self::Gold1 => "Gold 1",
|
||||
Self::Silver3 => "Silver 3",
|
||||
Self::Silver2 => "Silver 2",
|
||||
Self::Silver1 => "Silver 1",
|
||||
Self::Bronze3 => "Bronze 3",
|
||||
Self::Bronze2 => "Bronze 2",
|
||||
Self::Bronze1 => "Bronze 1",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reward_coins(&self) -> i64 {
|
||||
match self {
|
||||
Self::Elite => 50_000,
|
||||
Self::WorldClass => 30_000,
|
||||
Self::Gold3 => 15_000,
|
||||
Self::Gold2 => 10_000,
|
||||
Self::Gold1 => 7_500,
|
||||
Self::Silver3 => 5_000,
|
||||
Self::Silver2 => 3_500,
|
||||
Self::Silver1 => 2_000,
|
||||
Self::Bronze3 => 1_000,
|
||||
Self::Bronze2 => 500,
|
||||
Self::Bronze1 => 250,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reward_pack(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::Elite => Some("icon_pack"),
|
||||
Self::WorldClass => Some("rare_gold_pack"),
|
||||
Self::Gold3 | Self::Gold2 | Self::Gold1 => Some("gold_pack"),
|
||||
Self::Silver3 | Self::Silver2 | Self::Silver1 => Some("silver_pack"),
|
||||
Self::Bronze3 => Some("bronze_pack"),
|
||||
Self::Bronze2 | Self::Bronze1 => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user