use crate::dice;
use crate::rules::classes::CLASSES;
use include_dir::{include_dir, Dir};
use lazy_static::lazy_static;
use serde::Deserialize;
use serde_yaml;
use std::collections::HashMap;
use std::error::Error;
use std::string::String;
#[derive(Debug, Deserialize)]
struct RandomTable {
formula: String,
rows: Vec<TableRow>,
}
#[derive(Debug, Deserialize)]
struct TableRow {
roll: String,
steps: Vec<TableRowStep>,
}
#[derive(Debug, Deserialize)]
struct TableRowStep {
class: Option<String>,
table: Option<String>,
text: Option<String>,
}
lazy_static! {
pub static ref RANDOM_TABLES: RandomTables =
RandomTables::new().expect("Failed to load random tables.");
}
const YAML_DIR: Dir = include_dir!("src/data/random_tables/");
pub struct RandomTables {
tables: HashMap<String, RandomTable>,
}
impl RandomTables {
pub fn new() -> Result<Self, Box<dyn Error>> {
let mut tables_yaml = String::new();
for entry in YAML_DIR.files() {
tables_yaml.push_str(entry.contents_utf8().unwrap());
}
let tables: HashMap<String, RandomTable> = serde_yaml::from_str(tables_yaml.as_str())?;
Ok(RandomTables { tables })
}
pub fn roll_table(&self, table_name: &str) -> String {
let random_table = self.tables.get(table_name);
if let Some(table) = random_table {
let roll_result = dice::roll_formula(&table.formula).unwrap();
for table_row in &table.rows {
if let Some((start, end)) = RandomTables::parse_roll(&table_row.roll) {
if start <= roll_result.total() && roll_result.total() <= end {
let mut output_text = String::new();
for step in &table_row.steps {
if let Some(text) = &step.text {
output_text.push_str(text);
}
if let Some(table) = &step.table {
let inner_output = self.roll_table(table);
output_text.push_str(&inner_output);
}
if let Some(class_string) = &step.class {
let class =
CLASSES.get(class_string).expect("Failed to load class.");
output_text.push_str(&*class.name);
}
output_text.push_str("\n");
}
return output_text.trim().replace("\n", ", ").to_string();
}
} else {
panic!("Invalid roll format in yaml")
}
}
}
String::new()
}
fn parse_roll(roll: &str) -> Option<(u32, u32)> {
let parts: Vec<&str> = roll.split('-').collect();
match parts.len() {
1 => {
if let Ok(num) = parts[0].parse::<u32>() {
Some((num, num))
} else {
None
}
}
2 => {
if let (Ok(start), Ok(end)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
Some((start, end))
} else {
None
}
}
_ => None,
}
}
}