2020-07-03 20:23:24 +00:00
|
|
|
use crate::assets::{self, Asset};
|
2020-07-03 13:49:17 +00:00
|
|
|
use rand::prelude::*;
|
2020-08-12 14:10:12 +00:00
|
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
2020-07-03 13:49:17 +00:00
|
|
|
use std::{fs::File, io::BufReader};
|
|
|
|
|
2020-07-03 20:23:24 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2020-07-03 13:49:17 +00:00
|
|
|
pub struct Lottery<T> {
|
2020-07-03 20:23:24 +00:00
|
|
|
items: Vec<(f32, T)>,
|
2020-07-03 13:49:17 +00:00
|
|
|
total: f32,
|
|
|
|
}
|
|
|
|
|
2020-07-25 11:19:13 +00:00
|
|
|
impl<T: DeserializeOwned + Send + Sync> Asset for Lottery<T> {
|
2020-07-03 13:49:17 +00:00
|
|
|
const ENDINGS: &'static [&'static str] = &["ron"];
|
|
|
|
|
|
|
|
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
|
2020-07-25 11:19:13 +00:00
|
|
|
ron::de::from_reader::<BufReader<File>, Vec<(f32, T)>>(buf_reader)
|
2020-07-03 20:23:24 +00:00
|
|
|
.map(|items| Lottery::from_rates(items.into_iter()))
|
|
|
|
.map_err(assets::Error::parse_error)
|
2020-07-03 13:49:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Lottery<T> {
|
2020-07-03 20:23:24 +00:00
|
|
|
pub fn from_rates(items: impl Iterator<Item = (f32, T)>) -> Self {
|
2020-07-03 13:49:17 +00:00
|
|
|
let mut total = 0.0;
|
|
|
|
let items = items
|
2020-07-03 20:23:24 +00:00
|
|
|
.map(|(rate, item)| {
|
2020-07-03 13:49:17 +00:00
|
|
|
total += rate;
|
|
|
|
(total - rate, item)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
Self { items, total }
|
|
|
|
}
|
|
|
|
|
2020-07-25 11:19:13 +00:00
|
|
|
pub fn choose_seeded(&self, seed: u32) -> &T {
|
|
|
|
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
|
2020-07-03 20:23:24 +00:00
|
|
|
&self.items[self
|
|
|
|
.items
|
|
|
|
.binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap())
|
|
|
|
.unwrap_or_else(|i| i.saturating_sub(1))]
|
|
|
|
.1
|
2020-07-03 13:49:17 +00:00
|
|
|
}
|
2020-07-05 16:49:15 +00:00
|
|
|
|
2020-08-12 14:10:12 +00:00
|
|
|
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
|
2020-07-25 11:19:13 +00:00
|
|
|
|
2020-07-05 16:49:15 +00:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-08-12 17:29:51 +00:00
|
|
|
use super::*;
|
|
|
|
use crate::{assets, comp::Item};
|
2020-07-05 16:49:15 +00:00
|
|
|
#[test]
|
|
|
|
fn test_loot_table() {
|
2020-08-18 21:32:34 +00:00
|
|
|
let test = assets::load_expect::<Lottery<String>>("common.loot_tables.loot_table");
|
2020-07-05 16:49:15 +00:00
|
|
|
|
|
|
|
for (_, item) in test.iter() {
|
|
|
|
assert!(
|
|
|
|
assets::load::<Item>(item).is_ok(),
|
|
|
|
"Invalid loot table item '{}'",
|
|
|
|
item
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-07-03 20:23:24 +00:00
|
|
|
}
|