use logic::data::{RARITY_COMMON, RARITY_EPIC, RARITY_RARE}; use logic::model::LogicSpell; use logic::{LogicDataRef, LogicReward}; use rand::seq::SliceRandom; use rand::Rng; use crate::catalog::Catalog; pub struct RewardRoller { pool: Vec, } impl RewardRoller { pub fn new(catalog: &Catalog, fallback: Vec) -> Self { let pool = match catalog.card_pool() { pool if pool.is_empty() => fallback, pool => pool, }; Self { pool } } pub fn is_empty(&self) -> bool { self.pool.is_empty() } fn by_rarity(&self, rarity: &str) -> Vec { self.pool .iter() .filter(|card| { card.as_spell() .map(|spell| spell.rarity() == rarity) .unwrap_or(false) }) .cloned() .collect() } pub fn roll(&self, chest: &LogicDataRef, arena: &LogicDataRef) -> LogicReward { let Some(data) = chest.as_treasure_chest() else { return LogicReward::default(); }; let mut rng = rand::thread_rng(); let scale = |value: i32| match arena.as_arena() { Some(arena) => arena.scaled_chest_reward(value), None => value, }; let gold = { let low = scale(data.min_gold()); let high = scale(data.max_gold()); if high > low { rng.gen_range(low..=high) } else { low } }; let diamonds = { let low = data.min_diamonds(); let high = data.max_diamonds(); if high > low { rng.gen_range(low..=high) } else { low } }; let copies = data.random_spells().max(0); let distinct = data.different_spells().max(1) as usize; let mut picked: Vec = Vec::new(); let guaranteed = data.guaranteed_spell(); if !guaranteed.is_none() { picked.push(guaranteed); } for (rarity, count) in [ (RARITY_EPIC, data.guaranteed_epics()), (RARITY_RARE, data.guaranteed_rares()), ] { let mut candidates = self.by_rarity(rarity); candidates.retain(|card| !picked.contains(card)); candidates.shuffle(&mut rng); for card in candidates.into_iter().take(count.max(0) as usize) { if picked.len() >= distinct { break; } picked.push(card); } } if picked.len() < distinct { let mut candidates = self.by_rarity(RARITY_COMMON); candidates.retain(|card| !picked.contains(card)); candidates.shuffle(&mut rng); for card in candidates { if picked.len() >= distinct { break; } picked.push(card); } } if picked.is_empty() { return LogicReward::new(Vec::new(), gold, diamonds); } picked.sort_by_key(|card| card.global_id().map(|id| (id.class_id, id.instance_id))); let share = copies / picked.len() as i32; let mut remainder = copies % picked.len() as i32; let spells = picked .into_iter() .map(|card| { let mut count = share; if remainder > 0 { count += 1; remainder -= 1; } LogicSpell::card(card, 0, count.max(1)) }) .collect(); LogicReward::new(spells, gold, diamonds) } }