Updated dependencies + a major split and cleanup of the code
parent
b57fa29fae
commit
9180d330d6
21
Cargo.toml
21
Cargo.toml
|
@ -1,19 +1,20 @@
|
||||||
[package]
|
[package]
|
||||||
name = "khguide"
|
name = "khguide"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rinja = "0.3.5"
|
askama = "0.14"
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.118"
|
serde_json = "1.0"
|
||||||
toml = "0.8.19"
|
toml = "0.8"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
itertools = "0.13.0"
|
itertools = "0.14"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bbs", "ddd", "kh3", "kh2"]
|
default = ["bbs", "ddd", "kh3", "kh2", "kh1"]
|
||||||
|
kh1 = []
|
||||||
kh2 = []
|
kh2 = []
|
||||||
kh3 = []
|
kh3 = []
|
||||||
bbs = []
|
bbs = []
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "stable"
|
channel = "1.85"
|
||||||
|
|
122
src/bbs.rs
122
src/bbs.rs
|
@ -1,11 +1,23 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use ability::Ability;
|
||||||
|
use askama::Template;
|
||||||
|
use command::Command;
|
||||||
|
use finisher::Finisher;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rinja::Template;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::create_file;
|
use crate::create_file;
|
||||||
|
|
||||||
|
mod ability;
|
||||||
|
mod command;
|
||||||
|
mod finisher;
|
||||||
|
mod melding;
|
||||||
|
|
||||||
|
const ABILITIES_PATH: &str = "./input/bbs/abilities.json";
|
||||||
|
const FINISHERS_PATH: &str = "./input/bbs/finish-commands.json";
|
||||||
|
const COMMANDS_PATH: &str = "./input/bbs/commands.json";
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
enum Character {
|
enum Character {
|
||||||
#[serde(alias = "A")]
|
#[serde(alias = "A")]
|
||||||
|
@ -16,110 +28,6 @@ enum Character {
|
||||||
Terra,
|
Terra,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct Ability {
|
|
||||||
name: String,
|
|
||||||
from: String,
|
|
||||||
category: String,
|
|
||||||
max: u8,
|
|
||||||
types: Vec<char>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Command {
|
|
||||||
name: String,
|
|
||||||
category: String,
|
|
||||||
char: Vec<char>,
|
|
||||||
#[serde(default)]
|
|
||||||
starting: Option<bool>,
|
|
||||||
#[serde(default)]
|
|
||||||
info: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
recipes: Vec<CommandRecipe>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command {}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct CommandRecipe {
|
|
||||||
char: Vec<Character>,
|
|
||||||
r#type: char,
|
|
||||||
ingredients: (String, String),
|
|
||||||
chance: u8,
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
abilities: Vec<(String, Ability)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandRecipe {
|
|
||||||
pub fn get_ability(&self, crystal: &str) -> Option<Ability> {
|
|
||||||
if self.r#type == '-' {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ability in self.abilities.iter() {
|
|
||||||
if ability.0 == crystal {
|
|
||||||
return Some(ability.1.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should never happen unless the json files are wrong
|
|
||||||
panic!(
|
|
||||||
"No ability found for {} + {} and {}",
|
|
||||||
self.ingredients.0, self.ingredients.1, crystal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_unlock(&self, char: Character) -> bool {
|
|
||||||
self.char.contains(&char)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_unlock_chars(&self) -> String {
|
|
||||||
let mut id = String::new();
|
|
||||||
if self.can_unlock(Character::Terra) {
|
|
||||||
id += "T"
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.can_unlock(Character::Ventus) {
|
|
||||||
if !id.is_empty() {
|
|
||||||
id += " ";
|
|
||||||
}
|
|
||||||
id += "V";
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.can_unlock(Character::Aqua) {
|
|
||||||
if !id.is_empty() {
|
|
||||||
id += " ";
|
|
||||||
}
|
|
||||||
id += "A";
|
|
||||||
}
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_abilities(&mut self, abilities: &[Ability]) {
|
|
||||||
let mut vec: Vec<(String, Ability)> = vec![];
|
|
||||||
|
|
||||||
for ability in abilities.iter() {
|
|
||||||
if ability.types.contains(&self.r#type) {
|
|
||||||
vec.push((ability.from.clone(), ability.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vec.sort();
|
|
||||||
self.abilities = vec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Finisher {
|
|
||||||
name: String,
|
|
||||||
char: Vec<Character>,
|
|
||||||
level: u8,
|
|
||||||
#[serde(default)]
|
|
||||||
follows: Vec<String>,
|
|
||||||
goal: String,
|
|
||||||
color: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "pages/bbs/melding.html")]
|
#[template(path = "pages/bbs/melding.html")]
|
||||||
struct CommandsTemplate {
|
struct CommandsTemplate {
|
||||||
|
@ -127,10 +35,6 @@ struct CommandsTemplate {
|
||||||
pub crystals: Vec<String>,
|
pub crystals: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABILITIES_PATH: &str = "./input/bbs/abilities.json";
|
|
||||||
const FINISHERS_PATH: &str = "./input/bbs/finish-commands.json";
|
|
||||||
const COMMANDS_PATH: &str = "./input/bbs/commands.json";
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
tracing::info!("Loading abilities data from {}", ABILITIES_PATH);
|
tracing::info!("Loading abilities data from {}", ABILITIES_PATH);
|
||||||
let abilities_str = std::fs::read_to_string(ABILITIES_PATH).unwrap();
|
let abilities_str = std::fs::read_to_string(ABILITIES_PATH).unwrap();
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Ability {
|
||||||
|
pub name: String,
|
||||||
|
pub from: String,
|
||||||
|
pub category: String,
|
||||||
|
pub max: u8,
|
||||||
|
pub types: Vec<char>,
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::melding::CommandRecipe;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Command {
|
||||||
|
pub name: String,
|
||||||
|
pub category: String,
|
||||||
|
pub char: Vec<char>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub starting: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub info: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub recipes: Vec<CommandRecipe>,
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::Character;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Finisher {
|
||||||
|
pub name: String,
|
||||||
|
pub char: Vec<Character>,
|
||||||
|
pub level: u8,
|
||||||
|
#[serde(default)]
|
||||||
|
pub follows: Vec<String>,
|
||||||
|
pub goal: String,
|
||||||
|
pub color: String,
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::{ability::Ability, Character};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct CommandRecipe {
|
||||||
|
pub char: Vec<Character>,
|
||||||
|
pub r#type: char,
|
||||||
|
pub ingredients: (String, String),
|
||||||
|
pub chance: u8,
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub abilities: Vec<(String, Ability)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandRecipe {
|
||||||
|
pub fn get_ability(&self, crystal: &str) -> Option<Ability> {
|
||||||
|
if self.r#type == '-' {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ability in self.abilities.iter() {
|
||||||
|
if ability.0 == crystal {
|
||||||
|
return Some(ability.1.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should never happen unless the json files are wrong
|
||||||
|
panic!(
|
||||||
|
"No ability found for {} + {} and {}",
|
||||||
|
self.ingredients.0, self.ingredients.1, crystal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_unlock(&self, char: Character) -> bool {
|
||||||
|
self.char.contains(&char)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_unlock_chars(&self) -> String {
|
||||||
|
let mut id = String::new();
|
||||||
|
if self.can_unlock(Character::Terra) {
|
||||||
|
id += "T"
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.can_unlock(Character::Ventus) {
|
||||||
|
if !id.is_empty() {
|
||||||
|
id += " ";
|
||||||
|
}
|
||||||
|
id += "V";
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.can_unlock(Character::Aqua) {
|
||||||
|
if !id.is_empty() {
|
||||||
|
id += " ";
|
||||||
|
}
|
||||||
|
id += "A";
|
||||||
|
}
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_abilities(&mut self, abilities: &[Ability]) {
|
||||||
|
let mut vec: Vec<(String, Ability)> = vec![];
|
||||||
|
|
||||||
|
for ability in abilities.iter() {
|
||||||
|
if ability.types.contains(&self.r#type) {
|
||||||
|
vec.push((ability.from.clone(), ability.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec.sort();
|
||||||
|
self.abilities = vec;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod direction;
|
||||||
|
pub mod materials;
|
|
@ -0,0 +1,13 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum Direction {
|
||||||
|
#[serde(alias = "N")]
|
||||||
|
North,
|
||||||
|
#[serde(alias = "S")]
|
||||||
|
South,
|
||||||
|
#[serde(alias = "E")]
|
||||||
|
East,
|
||||||
|
#[serde(alias = "W")]
|
||||||
|
West,
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct EnemyDrop {
|
||||||
|
pub from: String,
|
||||||
|
pub chance: u8,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub info: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnemyDrop {
|
||||||
|
pub fn texture(&self) -> String {
|
||||||
|
self.from.replace(" ", "_").to_lowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct MaterialDrops {
|
||||||
|
pub kind: String,
|
||||||
|
pub shard: Vec<EnemyDrop>,
|
||||||
|
pub stone: Vec<EnemyDrop>,
|
||||||
|
pub gem: Vec<EnemyDrop>,
|
||||||
|
pub crystal: Vec<EnemyDrop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaterialDrops {
|
||||||
|
pub fn drops(&self, kind: &str) -> &[EnemyDrop] {
|
||||||
|
match kind {
|
||||||
|
"shard" => &self.shard,
|
||||||
|
"stone" => &self.stone,
|
||||||
|
"gem" => &self.gem,
|
||||||
|
"crystal" => &self.crystal,
|
||||||
|
_ => &self.shard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
359
src/ddd.rs
359
src/ddd.rs
|
@ -1,353 +1,16 @@
|
||||||
use std::{fmt::Display, panic, path::PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use rinja::Template;
|
|
||||||
use serde::{Deserialize, Deserializer};
|
|
||||||
|
|
||||||
use crate::create_file;
|
use crate::create_file;
|
||||||
|
use crate::ddd::ability::AbilityType;
|
||||||
|
use askama::Template;
|
||||||
|
use board::Board;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
mod ability;
|
||||||
struct Board {
|
mod board;
|
||||||
order: u32,
|
mod board_position;
|
||||||
spirit: String,
|
mod route;
|
||||||
routes: Vec<Route>,
|
|
||||||
abilities: Vec<Ability>,
|
|
||||||
|
|
||||||
#[serde(skip_deserializing)]
|
const ABILITIES_PATH: &str = "./input/ddd/abilities";
|
||||||
total_lp: u32,
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
max_level: u32,
|
|
||||||
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
stats: Vec<(usize, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Board {
|
|
||||||
pub fn init_ability_help(&mut self) {
|
|
||||||
for abl in &mut self.abilities {
|
|
||||||
if abl.tip.is_none() && abl.route.is_some() {
|
|
||||||
let route_id = abl.route.unwrap();
|
|
||||||
|
|
||||||
let mut route = None;
|
|
||||||
for r in &self.routes {
|
|
||||||
if r.id == route_id {
|
|
||||||
route = Some(r);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if route_id < 10 && route.is_some() {
|
|
||||||
abl.tip = Some(format!("Unlocks with disposition {}", route.unwrap().name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_routes(&mut self) {
|
|
||||||
self.routes.iter_mut().for_each(|r| {
|
|
||||||
r.interaction = match r.color {
|
|
||||||
Color::Blue => Interaction::Poke,
|
|
||||||
Color::Purple => Interaction::Poke,
|
|
||||||
Color::Yellow => Interaction::Rub,
|
|
||||||
Color::Green => Interaction::Rub,
|
|
||||||
|
|
||||||
_ => Interaction::None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let routes = self
|
|
||||||
.routes
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|r| r.id < 100)
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
self.routes.iter_mut().filter(|r| r.id < 100).for_each(|r| {
|
|
||||||
r.tips.iter_mut().for_each(|t| {
|
|
||||||
let route = routes.iter().find(|r| r.name == t.to);
|
|
||||||
if let Some(route) = route {
|
|
||||||
t.to_color = route.color.clone();
|
|
||||||
t.tip = format!("{} {}", route.interaction, t.tip);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<u32>().unwrap_or(0)
|
|
||||||
})
|
|
||||||
.sum::<u32>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<u32>().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_dispositions(&self) -> Vec<&Route> {
|
|
||||||
self.routes.iter().filter(|r| r.id < 100).collect_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_supports(&self) -> Vec<&Ability> {
|
|
||||||
self.abilities
|
|
||||||
.iter()
|
|
||||||
.filter(|&ability| ability.r#type == AbilityType::Support)
|
|
||||||
.collect_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_commands(&self) -> Vec<&Ability> {
|
|
||||||
self.abilities
|
|
||||||
.iter()
|
|
||||||
.filter(|&ability| {
|
|
||||||
ability.r#type == AbilityType::Magic
|
|
||||||
|| ability.r#type == AbilityType::Attack
|
|
||||||
|| ability.r#type == AbilityType::Reprisal
|
|
||||||
|| ability.r#type == AbilityType::Defense
|
|
||||||
})
|
|
||||||
.collect_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_spirits(&self) -> Vec<&Ability> {
|
|
||||||
self.abilities
|
|
||||||
.iter()
|
|
||||||
.filter(|&ability| ability.r#type == AbilityType::Spirit)
|
|
||||||
.collect_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
|
||||||
struct Route {
|
|
||||||
id: u32,
|
|
||||||
name: String,
|
|
||||||
color: Color,
|
|
||||||
tips: Vec<Tip>,
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
interaction: Interaction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
|
||||||
enum Color {
|
|
||||||
#[serde(alias = "blue")]
|
|
||||||
Blue,
|
|
||||||
#[serde(alias = "purple")]
|
|
||||||
Purple,
|
|
||||||
#[serde(alias = "yellow")]
|
|
||||||
Yellow,
|
|
||||||
#[serde(alias = "green")]
|
|
||||||
Green,
|
|
||||||
|
|
||||||
#[serde(alias = "secret1")]
|
|
||||||
SecretGreen,
|
|
||||||
#[serde(alias = "secret2")]
|
|
||||||
SecretRed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Color {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Color::Blue => f.write_str("blue"),
|
|
||||||
Color::Purple => f.write_str("purple"),
|
|
||||||
Color::Yellow => f.write_str("yellow"),
|
|
||||||
Color::Green => f.write_str("green"),
|
|
||||||
|
|
||||||
Color::SecretGreen => f.write_str("secret1"),
|
|
||||||
Color::SecretRed => f.write_str("secret2"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
|
||||||
struct Tip {
|
|
||||||
to: String,
|
|
||||||
#[serde(skip_deserializing, default = "default_disposition_color")]
|
|
||||||
to_color: Color,
|
|
||||||
tip: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_disposition_color() -> Color {
|
|
||||||
Color::Blue
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
|
||||||
enum AbilityType {
|
|
||||||
Start,
|
|
||||||
Checkpoint,
|
|
||||||
Secret,
|
|
||||||
Stat,
|
|
||||||
Spirit,
|
|
||||||
Support,
|
|
||||||
Attack,
|
|
||||||
Magic,
|
|
||||||
Reprisal,
|
|
||||||
Defense,
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoardPosition = (u32, u32);
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
|
||||||
struct ParseBoardPositionError;
|
|
||||||
|
|
||||||
fn deserialize_position<'de, D>(deserializer: D) -> Result<BoardPosition, D::Error>
|
|
||||||
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<u32>,
|
|
||||||
path: Vec<Direction>,
|
|
||||||
tip: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ability {
|
|
||||||
pub fn tip(&self) -> String {
|
|
||||||
self.tip.clone().unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_slot_details(&self, board: &Board) -> String {
|
|
||||||
let mut details = String::new();
|
|
||||||
if let Some(route) = self.route {
|
|
||||||
for broute in &board.routes {
|
|
||||||
if broute.id == route {
|
|
||||||
details += &format!("{} ", broute.color);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for path in &self.path {
|
|
||||||
match path {
|
|
||||||
Direction::North => details += "north ",
|
|
||||||
Direction::South => details += "south ",
|
|
||||||
Direction::East => details += "east ",
|
|
||||||
Direction::West => details += "west ",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.r#type == AbilityType::Start {
|
|
||||||
details += "start ";
|
|
||||||
}
|
|
||||||
|
|
||||||
details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
|
||||||
enum Direction {
|
|
||||||
#[serde(alias = "N")]
|
|
||||||
North,
|
|
||||||
#[serde(alias = "S")]
|
|
||||||
South,
|
|
||||||
#[serde(alias = "E")]
|
|
||||||
East,
|
|
||||||
#[serde(alias = "W")]
|
|
||||||
West,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Clone)]
|
|
||||||
enum Interaction {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Poke,
|
|
||||||
Rub,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Interaction {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Interaction::Poke => f.write_str("Poke"),
|
|
||||||
Interaction::Rub => f.write_str("Rub"),
|
|
||||||
_ => f.write_str(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "pages/ddd/boards.html")]
|
#[template(path = "pages/ddd/boards.html")]
|
||||||
|
@ -355,8 +18,6 @@ struct AbilitiesTemplate {
|
||||||
pub boards: Vec<Board>,
|
pub boards: Vec<Board>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABILITIES_PATH: &str = "./input/ddd/abilities";
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
tracing::info!("Loading ability boards data from {}", ABILITIES_PATH);
|
tracing::info!("Loading ability boards data from {}", ABILITIES_PATH);
|
||||||
let mut boards: Vec<Board> = vec![];
|
let mut boards: Vec<Board> = vec![];
|
||||||
|
@ -365,7 +26,7 @@ pub fn init() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filter_map(|f| f.ok())
|
.filter_map(|f| f.ok())
|
||||||
.map(|f| f.path())
|
.map(|f| f.path())
|
||||||
.filter_map(|p| match p.extension().map_or(false, |e| e == "toml") {
|
.filter_map(|p| match p.extension().is_some_and(|e| e == "toml") {
|
||||||
true => Some(p),
|
true => Some(p),
|
||||||
false => None,
|
false => None,
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::common::direction::Direction;
|
||||||
|
|
||||||
|
use super::{board::Board, board_position::BoardPosition};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum AbilityType {
|
||||||
|
Start,
|
||||||
|
Checkpoint,
|
||||||
|
Secret,
|
||||||
|
Stat,
|
||||||
|
Spirit,
|
||||||
|
Support,
|
||||||
|
Attack,
|
||||||
|
Magic,
|
||||||
|
Reprisal,
|
||||||
|
Defense,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Ability {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(deserialize_with = "crate::ddd::board_position::deserialize_position")]
|
||||||
|
pub pos: BoardPosition,
|
||||||
|
pub r#type: AbilityType,
|
||||||
|
pub price: String,
|
||||||
|
pub route: Option<u32>,
|
||||||
|
pub path: Vec<Direction>,
|
||||||
|
pub tip: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ability {
|
||||||
|
pub fn tip(&self) -> String {
|
||||||
|
self.tip.clone().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_slot_details(&self, board: &Board) -> String {
|
||||||
|
let mut details = String::new();
|
||||||
|
if let Some(route) = self.route {
|
||||||
|
for broute in &board.routes {
|
||||||
|
if broute.id == route {
|
||||||
|
details += &format!("{} ", broute.color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in &self.path {
|
||||||
|
match path {
|
||||||
|
Direction::North => details += "north ",
|
||||||
|
Direction::South => details += "south ",
|
||||||
|
Direction::East => details += "east ",
|
||||||
|
Direction::West => details += "west ",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.r#type == AbilityType::Start {
|
||||||
|
details += "start ";
|
||||||
|
}
|
||||||
|
|
||||||
|
details
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
ability::{Ability, AbilityType},
|
||||||
|
board_position::BoardPosition,
|
||||||
|
route::{Interaction, Route, RouteColor},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Board {
|
||||||
|
pub order: u32,
|
||||||
|
pub spirit: String,
|
||||||
|
pub routes: Vec<Route>,
|
||||||
|
pub abilities: Vec<Ability>,
|
||||||
|
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub total_lp: u32,
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub max_level: u32,
|
||||||
|
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub stats: Vec<(usize, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
pub fn init_ability_help(&mut self) {
|
||||||
|
for abl in &mut self.abilities {
|
||||||
|
if abl.tip.is_none() && abl.route.is_some() {
|
||||||
|
let route_id = abl.route.unwrap();
|
||||||
|
|
||||||
|
let mut route = None;
|
||||||
|
for r in &self.routes {
|
||||||
|
if r.id == route_id {
|
||||||
|
route = Some(r);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if route_id < 10 && route.is_some() {
|
||||||
|
abl.tip = Some(format!("Unlocks with disposition {}", route.unwrap().name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_routes(&mut self) {
|
||||||
|
self.routes.iter_mut().for_each(|r| {
|
||||||
|
r.interaction = match r.color {
|
||||||
|
RouteColor::Blue => Interaction::Poke,
|
||||||
|
RouteColor::Purple => Interaction::Poke,
|
||||||
|
RouteColor::Yellow => Interaction::Rub,
|
||||||
|
RouteColor::Green => Interaction::Rub,
|
||||||
|
|
||||||
|
_ => Interaction::None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let routes = self
|
||||||
|
.routes
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|r| r.id < 100)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
self.routes.iter_mut().filter(|r| r.id < 100).for_each(|r| {
|
||||||
|
r.tips.iter_mut().for_each(|t| {
|
||||||
|
let route = routes.iter().find(|r| r.name == t.to);
|
||||||
|
if let Some(route) = route {
|
||||||
|
t.to_color = route.color.clone();
|
||||||
|
t.tip = format!("{} {}", route.interaction, t.tip);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<u32>().unwrap_or(0)
|
||||||
|
})
|
||||||
|
.sum::<u32>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<u32>().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_dispositions(&self) -> Vec<&Route> {
|
||||||
|
self.routes.iter().filter(|r| r.id < 100).collect_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_supports(&self) -> Vec<&Ability> {
|
||||||
|
self.abilities
|
||||||
|
.iter()
|
||||||
|
.filter(|&ability| ability.r#type == AbilityType::Support)
|
||||||
|
.collect_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_commands(&self) -> Vec<&Ability> {
|
||||||
|
self.abilities
|
||||||
|
.iter()
|
||||||
|
.filter(|&ability| {
|
||||||
|
ability.r#type == AbilityType::Magic
|
||||||
|
|| ability.r#type == AbilityType::Attack
|
||||||
|
|| ability.r#type == AbilityType::Reprisal
|
||||||
|
|| ability.r#type == AbilityType::Defense
|
||||||
|
})
|
||||||
|
.collect_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_spirits(&self) -> Vec<&Ability> {
|
||||||
|
self.abilities
|
||||||
|
.iter()
|
||||||
|
.filter(|&ability| ability.r#type == AbilityType::Spirit)
|
||||||
|
.collect_vec()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
|
pub type BoardPosition = (u32, u32);
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct ParseBoardPositionError;
|
||||||
|
|
||||||
|
pub fn deserialize_position<'de, D>(deserializer: D) -> Result<BoardPosition, D::Error>
|
||||||
|
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))
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Route {
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub color: RouteColor,
|
||||||
|
pub tips: Vec<Tip>,
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub interaction: Interaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
pub enum Interaction {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Poke,
|
||||||
|
Rub,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Interaction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Interaction::Poke => f.write_str("Poke"),
|
||||||
|
Interaction::Rub => f.write_str("Rub"),
|
||||||
|
_ => f.write_str(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
pub enum RouteColor {
|
||||||
|
#[default]
|
||||||
|
#[serde(alias = "blue")]
|
||||||
|
Blue,
|
||||||
|
#[serde(alias = "purple")]
|
||||||
|
Purple,
|
||||||
|
#[serde(alias = "yellow")]
|
||||||
|
Yellow,
|
||||||
|
#[serde(alias = "green")]
|
||||||
|
Green,
|
||||||
|
|
||||||
|
#[serde(alias = "secret1")]
|
||||||
|
SecretGreen,
|
||||||
|
#[serde(alias = "secret2")]
|
||||||
|
SecretRed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RouteColor {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
RouteColor::Blue => f.write_str("blue"),
|
||||||
|
RouteColor::Purple => f.write_str("purple"),
|
||||||
|
RouteColor::Yellow => f.write_str("yellow"),
|
||||||
|
RouteColor::Green => f.write_str("green"),
|
||||||
|
|
||||||
|
RouteColor::SecretGreen => f.write_str("secret1"),
|
||||||
|
RouteColor::SecretRed => f.write_str("secret2"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Tip {
|
||||||
|
pub to: String,
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub to_color: RouteColor,
|
||||||
|
pub tip: String,
|
||||||
|
}
|
45
src/kh2.rs
45
src/kh2.rs
|
@ -1,11 +1,10 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rinja::Template;
|
use askama::Template;
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::create_file;
|
use crate::{common::materials::MaterialDrops, create_file};
|
||||||
|
|
||||||
pub const MATERIAL_KINDS: &[&str] = &[
|
const MATERIAL_KINDS: &[&str] = &[
|
||||||
"blazing",
|
"blazing",
|
||||||
"bright",
|
"bright",
|
||||||
"dark",
|
"dark",
|
||||||
|
@ -19,39 +18,7 @@ pub const MATERIAL_KINDS: &[&str] = &[
|
||||||
"serenity",
|
"serenity",
|
||||||
"twilight",
|
"twilight",
|
||||||
];
|
];
|
||||||
|
const DROPS_PATH: &str = "./input/kh2/drops";
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct MaterialDrops {
|
|
||||||
kind: String,
|
|
||||||
shard: Vec<EnemyDrop>,
|
|
||||||
stone: Vec<EnemyDrop>,
|
|
||||||
gem: Vec<EnemyDrop>,
|
|
||||||
crystal: Vec<EnemyDrop>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MaterialDrops {
|
|
||||||
fn drops(&self, kind: &str) -> &[EnemyDrop] {
|
|
||||||
match kind {
|
|
||||||
"shard" => &self.shard,
|
|
||||||
"stone" => &self.stone,
|
|
||||||
"gem" => &self.gem,
|
|
||||||
"crystal" => &self.crystal,
|
|
||||||
_ => &self.shard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct EnemyDrop {
|
|
||||||
from: String,
|
|
||||||
chance: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EnemyDrop {
|
|
||||||
fn texture(&self) -> String {
|
|
||||||
self.from.replace(" ", "_").to_lowercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "pages/kh2/drops.html")]
|
#[template(path = "pages/kh2/drops.html")]
|
||||||
|
@ -59,8 +26,6 @@ struct DropsTemplate {
|
||||||
pub drops: Vec<MaterialDrops>,
|
pub drops: Vec<MaterialDrops>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DROPS_PATH: &str = "./input/kh2/drops";
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
tracing::info!("Loading enemy drops data from {}", DROPS_PATH);
|
tracing::info!("Loading enemy drops data from {}", DROPS_PATH);
|
||||||
let mut drops: Vec<MaterialDrops> = vec![];
|
let mut drops: Vec<MaterialDrops> = vec![];
|
||||||
|
@ -69,7 +34,7 @@ pub fn init() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filter_map(|f| f.ok())
|
.filter_map(|f| f.ok())
|
||||||
.map(|f| f.path())
|
.map(|f| f.path())
|
||||||
.filter_map(|p| match p.extension().map_or(false, |e| e == "toml") {
|
.filter_map(|p| match p.extension().is_some_and(|e| e == "toml") {
|
||||||
true => Some(p),
|
true => Some(p),
|
||||||
false => None,
|
false => None,
|
||||||
})
|
})
|
||||||
|
|
59
src/kh3.rs
59
src/kh3.rs
|
@ -1,60 +1,11 @@
|
||||||
use std::fmt::Display;
|
use askama::Template;
|
||||||
|
use food::Recipes;
|
||||||
use rinja::Template;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::create_file;
|
use crate::create_file;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
mod food;
|
||||||
struct Recipes {
|
|
||||||
starters: Vec<Recipe>,
|
|
||||||
soups: Vec<Recipe>,
|
|
||||||
fish: Vec<Recipe>,
|
|
||||||
meat: Vec<Recipe>,
|
|
||||||
deserts: Vec<Recipe>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
const RECIPES_PATH: &str = "./input/kh3/recipes.toml";
|
||||||
struct Recipe {
|
|
||||||
name: String,
|
|
||||||
is_special: bool,
|
|
||||||
ingredients: Vec<String>,
|
|
||||||
normal: FoodStats,
|
|
||||||
plus: FoodStats,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Recipe {
|
|
||||||
fn stats(&self, is_plus: &bool) -> &FoodStats {
|
|
||||||
if *is_plus {
|
|
||||||
&self.plus
|
|
||||||
} else {
|
|
||||||
&self.normal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
|
||||||
struct FoodStats {
|
|
||||||
str: u8,
|
|
||||||
mag: u8,
|
|
||||||
def: u8,
|
|
||||||
hp: u8,
|
|
||||||
mp: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FoodStats {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let val = serde_json::json!({
|
|
||||||
"str": self.str,
|
|
||||||
"mag": self.mag,
|
|
||||||
"def": self.def,
|
|
||||||
"hp": self.hp,
|
|
||||||
"mp": self.mp
|
|
||||||
});
|
|
||||||
f.write_str(&val.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "pages/kh3/food-sim.html")]
|
#[template(path = "pages/kh3/food-sim.html")]
|
||||||
|
@ -62,8 +13,6 @@ struct RecipesTemplate {
|
||||||
pub recipes: Recipes,
|
pub recipes: Recipes,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RECIPES_PATH: &str = "./input/kh3/recipes.toml";
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
tracing::info!("Loading recipes data from {}", RECIPES_PATH);
|
tracing::info!("Loading recipes data from {}", RECIPES_PATH);
|
||||||
let recipes_str = std::fs::read_to_string(RECIPES_PATH).unwrap();
|
let recipes_str = std::fs::read_to_string(RECIPES_PATH).unwrap();
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Recipes {
|
||||||
|
pub starters: Vec<Recipe>,
|
||||||
|
pub soups: Vec<Recipe>,
|
||||||
|
pub fish: Vec<Recipe>,
|
||||||
|
pub meat: Vec<Recipe>,
|
||||||
|
pub deserts: Vec<Recipe>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Recipe {
|
||||||
|
pub name: String,
|
||||||
|
pub is_special: bool,
|
||||||
|
pub ingredients: Vec<String>,
|
||||||
|
pub normal: FoodStats,
|
||||||
|
pub plus: FoodStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recipe {
|
||||||
|
pub fn stats(&self, is_plus: bool) -> &FoodStats {
|
||||||
|
if is_plus { &self.plus } else { &self.normal }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct FoodStats {
|
||||||
|
pub str: u8,
|
||||||
|
pub mag: u8,
|
||||||
|
pub def: u8,
|
||||||
|
pub hp: u8,
|
||||||
|
pub mp: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FoodStats {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let val = serde_json::json!({
|
||||||
|
"str": self.str,
|
||||||
|
"mag": self.mag,
|
||||||
|
"def": self.def,
|
||||||
|
"hp": self.hp,
|
||||||
|
"mp": self.mp
|
||||||
|
});
|
||||||
|
f.write_str(&val.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use rinja::Template;
|
use askama::Template;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
#[cfg(feature = "bbs")]
|
#[cfg(feature = "bbs")]
|
||||||
mod bbs;
|
mod bbs;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue