RandomSpells / DifferentSpells / RareChance / EpicChance / MinGold / MaxGold all come off the chest row now, gold is scaled by the player's arena. magic chest is 30 cards over 8 cards with 1 epic and 6 rares, like the client says it is. arena chest rows inherit everything through BaseChest, so Free_Arena1 reads Free.
110 lines
3.6 KiB
Rust
110 lines
3.6 KiB
Rust
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<LogicDataRef>,
|
|
}
|
|
impl RewardRoller {
|
|
pub fn new(catalog: &Catalog, fallback: Vec<LogicDataRef>) -> 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<LogicDataRef> {
|
|
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<LogicDataRef> = 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)
|
|
}
|
|
}
|