diff --git a/input/ddd/abilities.json b/input/ddd/abilities.json new file mode 100644 index 0000000..ac777a2 --- /dev/null +++ b/input/ddd/abilities.json @@ -0,0 +1,335 @@ +[ + { + "spirit": "Meow Wow", + "routes": [ + { + "id": 0, + "name": "Stray", + "color": "#17CBD8" + }, + { + "id": 1, + "name": "Smart Cookie", + "color": "#992A9B" + } + ], + "abilities": [ + { + "name": "Start", + "pos": "A2", + "type": "Start", + "price": "", + "path": ["E"] + }, + { + "name": "Link Critical", + "pos": "B2", + "type": "Spirit", + "price": "10 LP", + "path": ["W", "E"] + }, + { + "name": "Magic Haste", + "pos": "C1", + "type": "Stat", + "price": "30 LP", + "path": ["S", "E"] + }, + { + "name": "Cure", + "pos": "C2", + "type": "Magic", + "price": "50 LP", + "path": ["N", "W", "E", "S"] + }, + { + "name": "Item Boost", + "pos": "C3", + "type": "Stat", + "price": "30 LP", + "path": ["E", "N"] + }, + { + "name": "Light Screen", + "pos": "D1", + "type": "Stat", + "price": "20 LP", + "path": ["E", "W"] + }, + { + "name": "Checkpoint", + "pos": "D2", + "type": "Checkpoint", + "price": "Level 10", + "path": ["E", "W"] + }, + { + "name": "Slow", + "pos": "D3", + "type": "Magic", + "price": "50 LP", + "path": ["E", "W"] + }, + { + "name": "Defense Boost", + "pos": "E1", + "type": "Stat", + "price": "100 LP", + "path": ["E", "W"] + }, + { + "name": "Cura", + "pos": "E2", + "type": "Magic", + "price": "100 LP", + "path": ["E", "W"] + }, + { + "name": "Poison Block", + "pos": "E3", + "type": "Stat", + "price": "30 LP", + "path": ["E", "S", "W"] + }, + { + "name": "Spark", + "pos": "E4", + "type": "Magic", + "price": "50 LP", + "path": ["N"] + }, + { + "name": "Confusion Block", + "pos": "F1", + "type": "Stat", + "price": "30 LP", + "route": 0, + "path": ["E", "W"] + }, + { + "name": "Leaf Bracer", + "pos": "F2", + "type": "Support", + "price": "300 LP", + "path": ["E", "W"] + }, + { + "name": "Attack Haste", + "pos": "F3", + "type": "Stat", + "price": "30 LP", + "route": 1, + "path": ["E", "W"] + }, + { + "name": "HP Boost", + "pos": "G1", + "type": "Stat", + "price": "30 LP", + "route": 0, + "path": ["W"] + }, + { + "name": "Checkpoint", + "pos": "G2", + "type": "Checkpoint", + "price": "Level 25", + "path": ["E", "W"] + }, + { + "name": "Magic Boost", + "pos": "G3", + "type": "Stat", + "price": "100 LP", + "route": 1, + "path": ["W"] + }, + { + "name": "Curaga", + "pos": "H2", + "type": "Magic", + "price": "150 LP", + "path": ["W"] + } + ] + }, + { + "spirit": "Komory Bat", + "routes": [ + { + "id": 0, + "name": "Rescuer", + "color": "#3FE4D1" + }, + { + "id": 1, + "name": "Aggro", + "color": "#992A9B" + } + ], + "abilities": [ + { + "name": "Start", + "pos": "A3", + "type": "Start", + "price": "", + "path": ["E"] + }, + { + "name": "Waking Dream", + "pos": "B3", + "type": "Spirit", + "price": "10 LP", + "path": ["E"] + }, + { + "name": "Checkpoint", + "pos": "C1", + "type": "Checkpoint", + "price": "Link x2", + "path": ["E", "S"] + }, + { + "name": "Zero Gravity", + "pos": "C2", + "type": "Magic", + "price": "50 LP", + "path": ["N", "E", "S"] + }, + { + "name": "Confuse", + "pos": "C3", + "type": "Magic", + "price": "50 LP", + "path": ["S", "N", "W", "E"] + }, + { + "name": "Dark Screen", + "pos": "C4", + "type": "Stat", + "price": "20 LP", + "path": ["N", "E", "S"] + }, + { + "name": "Magic Haste", + "pos": "C5", + "type": "Stat", + "price": "50 LP", + "route": 0, + "path": ["N", "E"] + }, + { + "name": "Zero Gravira", + "pos": "D1", + "type": "Magic", + "price": "100 LP", + "path": ["W"] + }, + { + "name": "Confusion Block", + "pos": "D2", + "type": "Stat", + "price": "30 LP", + "path": ["W", "S"] + }, + { + "name": "Drain Dive", + "pos": "D3", + "type": "Attack", + "price": "50 LP", + "path": ["N", "S", "W", "E"] + }, + { + "name": "Magic Boost", + "pos": "D4", + "type": "Stat", + "price": "100 LP", + "path": ["W", "N", "E"] + }, + { + "name": "Magic Haste", + "pos": "D5", + "type": "Stat", + "price": "100 LP", + "route": 0, + "path": ["W"] + }, + { + "name": "Attack Boost", + "pos": "E2", + "type": "Stat", + "price": "100 LP", + "path": ["S"] + }, + { + "name": "Magic Haste", + "pos": "E3", + "type": "Stat", + "price": "30 LP", + "path": ["W", "E", "N"] + }, + { + "name": "Attack Haste", + "pos": "E4", + "type": "Stat", + "price": "30 LP", + "route": 1, + "path": ["W"] + }, + { + "name": "Dark Screen", + "pos": "F3", + "type": "Stat", + "price": "40 LP", + "path": ["W", "E"] + }, + { + "name": "Attack Haste", + "pos": "F4", + "type": "Stat", + "price": "50 LP", + "route": 1, + "path": ["W"] + }, + { + "name": "Checkpoint", + "pos": "G3", + "type": "Checkpoint", + "price": "Link x2", + "path": ["W", "E"] + } + ] + }, + { + "spirit": "Necho Cat", + "routes": [ + { + "id": 0, + "name": "Diva", + "color": "#3FE4D1" + }, + { + "id": 1, + "name": "Artist", + "color": "#7AE43F" + } + ], + "abilities": [ + { + "name": "Magic Boost", + "pos": "A4", + "type": "Stat", + "price": "200 LP", + "route": 0, + "path": ["E"] + }, + { + "name": "Support Boost", + "pos": "B1", + "type": "Spirit", + "price": "200 LP", + "path": ["E"] + } + ] + } +] diff --git a/src/bbs.rs b/src/bbs.rs index 5b5cc23..ab7bc74 100644 --- a/src/bbs.rs +++ b/src/bbs.rs @@ -157,8 +157,8 @@ pub fn init() { } } - tracing::info!("Generating the commands table template"); + tracing::info!("Generating the BBS commands table template"); let template = CommandsTemplate { commands, crystals }; - std::fs::write("./out/index.html", template.render().unwrap()).unwrap(); + std::fs::write("./out/bbs-commands.html", template.render().unwrap()).unwrap(); } diff --git a/src/ddd.rs b/src/ddd.rs new file mode 100644 index 0000000..c8996f9 --- /dev/null +++ b/src/ddd.rs @@ -0,0 +1,212 @@ +use std::panic; + +use askama::Template; +use itertools::Itertools; +use serde::{Deserialize, Deserializer}; + +#[derive(Debug, Deserialize, PartialEq, Eq)] +struct Board { + spirit: String, + routes: Vec, + abilities: Vec, + + #[serde(skip_deserializing)] + total_lp: u32, + #[serde(skip_deserializing)] + max_level: u32, + + #[serde(skip_deserializing)] + stats: Vec<(usize, String)>, +} + +impl Board { + pub fn init_stats(&mut self) { + let v = self + .abilities + .iter() + .filter(|&ability| ability.r#type == AbilityType::Stat) + .sorted_by(|a, b| Ord::cmp(&a.name, &b.name)) + .collect_vec(); + + let mut stats: Vec<(usize, String)> = vec![]; + for (key, chunk) in &v.into_iter().chunk_by(|k| k.name.clone()) { + let grouped = chunk.collect_vec(); + stats.push((grouped.len(), key)); + } + + self.stats = stats; + } + + pub fn init_total_lp(&mut self) { + self.total_lp = self + .abilities + .iter() + .filter(|&ability| ability.price.contains("LP")) + .map(|ability| { + let mut split = ability.price.split_whitespace(); + + split.next().unwrap_or("0").parse::().unwrap_or(0) + }) + .sum::(); + } + + pub fn init_max_level(&mut self) { + self.max_level = self + .abilities + .iter() + .filter(|&ability| ability.price.starts_with("Level")) + .map(|ability| { + let mut split = ability.price.split_whitespace(); + + split.nth(1).unwrap_or("0").parse::().unwrap_or(0) + }) + .sorted() + .last() + .unwrap_or(0); + } + + pub fn get_size(&self) -> BoardPosition { + let mut x = 1; + let mut y = 1; + for ability in &self.abilities { + if ability.pos.0 > x { + x = ability.pos.0; + } + + if ability.pos.1 > y { + y = ability.pos.1; + } + } + + (x, y) + } + + pub fn get_ability_at(&self, x: &u32, y: &u32) -> Option<&Ability> { + self.abilities + .iter() + .find(|&ability| ability.pos == (*x, *y)) + } + + pub fn get_char(&self, i: &u32) -> char { + char::from_u32(64 + *i).unwrap_or('0') + } + + pub fn get_supports(&self) -> Vec<&Ability> { + self.abilities + .iter() + .filter(|&ability| ability.r#type == AbilityType::Support) + .collect_vec() + } + + pub fn get_magics(&self) -> Vec<&Ability> { + self.abilities + .iter() + .filter(|&ability| ability.r#type == AbilityType::Magic) + .collect_vec() + } + + pub fn get_spirit(&self) -> Vec<&Ability> { + self.abilities + .iter() + .filter(|&ability| ability.r#type == AbilityType::Spirit) + .collect_vec() + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +struct Route { + id: u32, + name: String, + color: String, +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +enum AbilityType { + Start, + Checkpoint, + Stat, + Spirit, + Support, + Attack, + Magic, +} + +type BoardPosition = (u32, u32); + +#[derive(Debug, Deserialize, PartialEq, Eq)] +struct ParseBoardPositionError; + +fn deserialize_position<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let str = match String::deserialize(deserializer) { + Ok(v) => v, + Err(e) => { + panic!("Tried deserializing a non-string type\nerror: {}", e); + } + }; + let str = str.to_uppercase(); + + let mut chars = str.chars(); + + let a = chars.next().unwrap_or('A'); + let b = chars.next().unwrap_or('1'); + + let a = (a.to_ascii_uppercase() as u32).saturating_sub(64); + if a == 0 { + panic!("Second position parameter {} is 0 or lower!", str); + } + + let b = b.to_digit(10).unwrap_or(0); + + Ok((a, b)) +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +struct Ability { + name: String, + #[serde(deserialize_with = "deserialize_position")] + pos: BoardPosition, + r#type: AbilityType, + price: String, + route: Option, + path: Vec, +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +enum Direction { + #[serde(alias = "N")] + North, + #[serde(alias = "S")] + South, + #[serde(alias = "E")] + East, + #[serde(alias = "W")] + West, +} + +#[derive(Template)] +#[template(path = "pages/ddd-abilities.html", whitespace = "suppress")] +struct AbilitiesTemplate { + pub boards: Vec, +} + +const ABILITIES_PATH: &str = "./input/ddd/abilities.json"; + +pub fn init() { + tracing::info!("Loading ability links json data from {}", ABILITIES_PATH); + let boards_str = std::fs::read_to_string(ABILITIES_PATH).unwrap(); + let mut boards = serde_json::from_str::>(&boards_str).unwrap(); + + for board in &mut boards { + board.init_total_lp(); + board.init_max_level(); + board.init_stats(); + } + + tracing::info!("Generating the DDD ability boards template"); + let template = AbilitiesTemplate { boards }; + + std::fs::write("./out/ddd-abilities.html", template.render().unwrap()).unwrap(); +} diff --git a/src/main.rs b/src/main.rs index ced6d0e..db01977 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use tracing_subscriber::EnvFilter; mod bbs; +mod ddd; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -14,4 +15,5 @@ fn main() { .init(); bbs::init(); + ddd::init(); } diff --git a/templates/layouts/base.html b/templates/layouts/base.html index bb44b1a..edf1ca2 100644 --- a/templates/layouts/base.html +++ b/templates/layouts/base.html @@ -35,35 +35,6 @@ thead th { background-color: #252525; } - - tbody tr:hover { - background-color: #4f4f4f; - } - - & tr, - th, - td { - border: 1px solid #fff; - padding: 7px; - } - - .charlist { - display: inline-grid; - grid-template-columns: repeat(3, 1fr); - gap: 15px; - - .aqua { - color: #97c8ff; - } - - .ventus { - color: #26ff62; - } - - .terra { - color: #ff7400; - } - } } diff --git a/templates/pages/bbs-commands.html b/templates/pages/bbs-commands.html index 5bb1fb5..356b890 100644 --- a/templates/pages/bbs-commands.html +++ b/templates/pages/bbs-commands.html @@ -3,6 +3,38 @@ {% block title %}Commands{% endblock %} {% block head %} + -{% endblock %} - -{% block content %} - {% include "components/search.html" %} -
- {% include "components/type-filters.html" %} -
- {% include "components/char-filters.html" %} - - - - - - - - - - - - - - {% for crystal in crystals %} - - {% endfor %} - - - - {% for cmd in commands %} - {% for recipe in cmd.recipes %} - - - - - - - - {% for crystal in crystals %} - {% let ability = recipe.get_ability(crystal) %} - - {% endfor %} - - {% endfor %} - {% endfor %} - -
CharacterCommandIngredient AIngredient BTypeChanceAbilities
{{ crystal }}
-
- - - {% if recipe.can_unlock(Character::Terra) %}T{% endif %} - - - {% if recipe.can_unlock(Character::Ventus) %}V{% endif %} - - - {% if recipe.can_unlock(Character::Aqua) %}A{% endif %} - -
-
{{ cmd.name }}{{ recipe.ingredients.0 }}{{ recipe.ingredients.1 }}{{ recipe.type }}{{ recipe.chance }}% - {% if ability.is_some() %} - {{ ability.unwrap().name }} - {% else %} - - - {% endif %} -
-{% endblock %} diff --git a/templates/pages/ddd-abilities.html b/templates/pages/ddd-abilities.html new file mode 100644 index 0000000..94c14f5 --- /dev/null +++ b/templates/pages/ddd-abilities.html @@ -0,0 +1,82 @@ +{% extends "layouts/base.html" %} + +{% block title %}Abilities{% endblock %} + +{% block head %} + + +{% endblock %} + +{% block content %} + {% for board in boards %} + Total LP Needed: {{+ board.total_lp +}} +
+ Max Level Needed: {{+ board.max_level +}} +

+ Stats: +
    + {% for val in board.stats %} +
  • x{{+ val.0 +}} {{+ val.1 +}}
  • + {% endfor %} +
+ + Support: +
    + {% for support in board.get_supports() %} +
  • {{ support.name }}
  • + {% endfor %} +
+ + + + + + {% for x in 1..board.get_size().0 + 1 %} + + {% endfor %} + + {% for y in 1..board.get_size().1 + 1 %} + + + {% for x in 1..board.get_size().0 + 1 %} + {% let ability = board.get_ability_at(x, y) %} + {% match ability %} + {% when Some with (val) %} + + {% when None %} + + {% endmatch %} + {% endfor %} + + {% endfor %} + +
{{ board.get_char(x) }}
{{ y }}{{ val.name }}
+ {% endfor %} +{% endblock %}