Initial commit with bbs command melding table
commit
e140f2a57e
|
@ -0,0 +1,2 @@
|
||||||
|
[env]
|
||||||
|
RUST_LOG = "khguide=debug"
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"plugins": ["prettier-plugin-jinja-template"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"options": {
|
||||||
|
"parser": "jinja-template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "khguide"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
askama = "0.12.1"
|
||||||
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
|
serde_json = "1.0.118"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
|
itertools = "0.13.0"
|
|
@ -0,0 +1,9 @@
|
||||||
|
# KHGuide
|
||||||
|
|
||||||
|
A small static page generator for command melding in Kingdom Hearts: Birth By Sleep. It generates a static html (index.html) file that lists all commands and how they're melded and using some vanilla javascript it allows for filtering and searching of these commands.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
- Probably the easiest way is to download the `index.html` file and open it with any browser.
|
||||||
|
- Alternatively hosting a web server and serving the `index.html` file also works.
|
||||||
|
- The third option requires an internet connection, using the hosted version [here](https://git.pixelatedw.xyz/pages/wynd/khguide/)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,200 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Treasure Magnet",
|
||||||
|
"from": "Hungry Crystal",
|
||||||
|
"category": "prize",
|
||||||
|
"max": 5,
|
||||||
|
"types": ["I", "J", "K", "L", "M", "N", "O", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HP Prize Plus",
|
||||||
|
"from": "Hungry Crystal",
|
||||||
|
"category": "prize",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Link Prize Plus",
|
||||||
|
"from": "Abounding Crystal",
|
||||||
|
"category": "prize",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["A", "C", "D", "I", "K"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lucky Strike",
|
||||||
|
"from": "Abounding Crystal",
|
||||||
|
"category": "prize",
|
||||||
|
"max": 5,
|
||||||
|
"types": ["E", "F", "G", "L", "M", "N", "O"]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "HP Boost",
|
||||||
|
"from": "Soothing Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["A", "C", "D", "I", "K", "L"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fire Boost",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["A", "B"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Blizzard Boost",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["E", "F"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Thunder Boost",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["I", "J"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cure Boost",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["M", "N"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Item Boost",
|
||||||
|
"from": "Soothing Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["E", "G", "H", "M", "O", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attack Haste",
|
||||||
|
"from": "Fleeting Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 5,
|
||||||
|
"types": ["C", "D", "G", "K", "L", "O"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Magic Haste",
|
||||||
|
"from": "Fleeting Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 5,
|
||||||
|
"types": ["A", "E", "H", "I", "M", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Combo F Boost",
|
||||||
|
"from": "Pulsing Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 2,
|
||||||
|
"types": ["H", "I", "J", "M", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Finish Boost",
|
||||||
|
"from": "Pulsing Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 2,
|
||||||
|
"types": ["B", "C", "K", "L", "O"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fire Screen",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 2,
|
||||||
|
"types": ["C", "D"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Blizzard Screen",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 2,
|
||||||
|
"types": ["G", "H"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Thunder Screen",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 2,
|
||||||
|
"types": ["K", "L"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dark Screen",
|
||||||
|
"from": "Shimmering Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 2,
|
||||||
|
"types": ["O", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Reload Boost",
|
||||||
|
"from": "Fleeting Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["B", "F", "J", "N"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Defender",
|
||||||
|
"from": "Soothing Crystal",
|
||||||
|
"category": "status",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["J", "N"]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Combo Plus",
|
||||||
|
"from": "Wellspring Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["C", "D", "E", "K", "L", "M", "N"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Air Combo Plus",
|
||||||
|
"from": "Wellspring Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 3,
|
||||||
|
"types": ["A", "F", "G", "H", "I", "O", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Exp Chance",
|
||||||
|
"from": "Abounding Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["B", "J"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Exp Walker",
|
||||||
|
"from": "Abounding Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["H", "P"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Damage Syphon",
|
||||||
|
"from": "Soothing Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["B", "F"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Second Chance",
|
||||||
|
"from": "Pulsing Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["F", "N"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Once More",
|
||||||
|
"from": "Wellspring Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["B", "J"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Leaf Bracer",
|
||||||
|
"from": "Pulsing Crystal",
|
||||||
|
"category": "support",
|
||||||
|
"max": 1,
|
||||||
|
"types": ["A", "D", "E", "G"]
|
||||||
|
}
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,225 @@
|
||||||
|
{
|
||||||
|
"finish": {
|
||||||
|
"name": "Finish",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 1,
|
||||||
|
"goal": "Unlocked from the start",
|
||||||
|
"color": "blue"
|
||||||
|
},
|
||||||
|
"heat-slash-1": {
|
||||||
|
"name": "Heat Slash 1",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 2,
|
||||||
|
"follows": ["finish"],
|
||||||
|
"goal": "Activate the Firestorm Command Style 8 times",
|
||||||
|
"color": "red"
|
||||||
|
},
|
||||||
|
"heat-slash-2": {
|
||||||
|
"name": "Heat Slash 2",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["heat-slash-1"],
|
||||||
|
"goal": "Activate the Firestorm Command Style 12 times",
|
||||||
|
"color": "red"
|
||||||
|
},
|
||||||
|
"rising-rock-1": {
|
||||||
|
"name": "Rising Rock 1",
|
||||||
|
"char": ["T"],
|
||||||
|
"level": 2,
|
||||||
|
"follows": ["finish"],
|
||||||
|
"goal": "Earn 2000 CP",
|
||||||
|
"color": "brown"
|
||||||
|
},
|
||||||
|
"rising-rock-2": {
|
||||||
|
"name": "Rising Rock 2",
|
||||||
|
"char": ["T"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["rising-rock-1"],
|
||||||
|
"goal": "Earn 4200 CP",
|
||||||
|
"color": "brown"
|
||||||
|
},
|
||||||
|
"dark-star-1": {
|
||||||
|
"name": "Dark Star 1",
|
||||||
|
"char": ["T"],
|
||||||
|
"level": 4,
|
||||||
|
"follows": ["rising-rock-2"],
|
||||||
|
"goal": "Defeat 420 enemies",
|
||||||
|
"color": "brown"
|
||||||
|
},
|
||||||
|
"dark-star-2": {
|
||||||
|
"name": "Dark Star 2",
|
||||||
|
"char": ["T"],
|
||||||
|
"level": 5,
|
||||||
|
"follows": ["dark-star-1"],
|
||||||
|
"goal": "Defeat 550 enemies",
|
||||||
|
"color": "brown"
|
||||||
|
},
|
||||||
|
"air-flair-1": {
|
||||||
|
"name": "Air Flair 1",
|
||||||
|
"char": ["V"],
|
||||||
|
"level": 2,
|
||||||
|
"follows": ["finish"],
|
||||||
|
"goal": "Earn 2000 CP",
|
||||||
|
"color": "blue"
|
||||||
|
},
|
||||||
|
"air-flair-2": {
|
||||||
|
"name": "Air Flair 2",
|
||||||
|
"char": ["V"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["air-flair-1"],
|
||||||
|
"goal": "Earn 4000 CP",
|
||||||
|
"color": "blue"
|
||||||
|
},
|
||||||
|
"air-flair-3": {
|
||||||
|
"name": "Air Flair 3",
|
||||||
|
"char": ["V"],
|
||||||
|
"level": 4,
|
||||||
|
"follows": ["air-flair-2"],
|
||||||
|
"goal": "Take 4500 steps",
|
||||||
|
"color": "blue"
|
||||||
|
},
|
||||||
|
"air-flair-4": {
|
||||||
|
"name": "Air Flair 4",
|
||||||
|
"char": ["V"],
|
||||||
|
"level": 5,
|
||||||
|
"follows": ["air-flair-3"],
|
||||||
|
"goal": "Take 7000 steps",
|
||||||
|
"color": "blue"
|
||||||
|
},
|
||||||
|
"magic-pulse-1": {
|
||||||
|
"name": "Magic Pulse 1",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 2,
|
||||||
|
"follows": ["finish"],
|
||||||
|
"goal": "Earn 2000 CP",
|
||||||
|
"color": "purple"
|
||||||
|
},
|
||||||
|
"magic-pulse-2": {
|
||||||
|
"name": "Magic Pulse 2",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["magic-pulse-1"],
|
||||||
|
"goal": "Earn 3800 CP",
|
||||||
|
"color": "purple"
|
||||||
|
},
|
||||||
|
"magic-pulse-3": {
|
||||||
|
"name": "Magic Pulse 3",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 4,
|
||||||
|
"follows": ["magic-pulse-2"],
|
||||||
|
"goal": "Defeat 350 enemies",
|
||||||
|
"color": "purple"
|
||||||
|
},
|
||||||
|
"magic-pulse-4": {
|
||||||
|
"name": "Magic Pulse 4",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 5,
|
||||||
|
"follows": ["magic-pulse-3"],
|
||||||
|
"goal": "Defeat 500 enemies",
|
||||||
|
"color": "purple"
|
||||||
|
},
|
||||||
|
"gold-rush": {
|
||||||
|
"name": "Gold Rush",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 2,
|
||||||
|
"follows": ["finish"],
|
||||||
|
"goal": "Collect 1000 munny",
|
||||||
|
"color": "yellow"
|
||||||
|
},
|
||||||
|
"ramuhs-judgement": {
|
||||||
|
"name": "Ramuh's Judgement",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["air-flair-1", "magic-pulse-1", "rising-rock-1"],
|
||||||
|
"goal": "Activate the Thunderbolt Command Style 12 times",
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
"twisted-hours": {
|
||||||
|
"name": "Twisted Hours",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["air-flair-1", "magic-pulse-1", "rising-rock-1", "gold-rush"],
|
||||||
|
"goal": "Take 7000 steps",
|
||||||
|
"color": "gray"
|
||||||
|
},
|
||||||
|
"surprise-1": {
|
||||||
|
"name": "Surprise! 1",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 3,
|
||||||
|
"follows": ["gold-rush"],
|
||||||
|
"goal": "Collect 1400 munny",
|
||||||
|
"color": "yellow"
|
||||||
|
},
|
||||||
|
"surprise-2": {
|
||||||
|
"name": "Surprise! 2",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 4,
|
||||||
|
"follows": ["twisted-hours", "surprise-1"],
|
||||||
|
"goal": "Collect 5200 munny",
|
||||||
|
"color": "yellow"
|
||||||
|
},
|
||||||
|
"heal-strike": {
|
||||||
|
"name": "Heal Strike",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 4,
|
||||||
|
"follows": ["rising-rock-2", "air-flair-2", "magic-pulse-2"],
|
||||||
|
"goal": "Use the effect of Second Chance or Once More to survive lethal damage 5 times",
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
"random-end": {
|
||||||
|
"name": "Random End",
|
||||||
|
"char": ["T"],
|
||||||
|
"level": 4,
|
||||||
|
"follows": ["twisted-hours"],
|
||||||
|
"goal": "Take 8000 steps",
|
||||||
|
"color": "gray"
|
||||||
|
},
|
||||||
|
"explosion": {
|
||||||
|
"name": "Explosion",
|
||||||
|
"char": ["A", "V", "T"],
|
||||||
|
"level": 5,
|
||||||
|
"follows": ["dark-star-1", "air-flair-3", "magic-pulse-3"],
|
||||||
|
"goal": "Earn 6400 CP",
|
||||||
|
"color": "orange"
|
||||||
|
},
|
||||||
|
"celebration": {
|
||||||
|
"name": "Celebration",
|
||||||
|
"char": ["V"],
|
||||||
|
"level": 5,
|
||||||
|
"follows": ["surprise-2"],
|
||||||
|
"goal": "Collect 7000 munny",
|
||||||
|
"color": "yellow"
|
||||||
|
},
|
||||||
|
"ice-burst": {
|
||||||
|
"name": "Ice Burst",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 5,
|
||||||
|
"follows": ["magic-pulse-3"],
|
||||||
|
"goal": "Activate the Diamond Dust Command Style 15 times",
|
||||||
|
"color": "cyan"
|
||||||
|
},
|
||||||
|
"demolition": {
|
||||||
|
"name": "Demolition",
|
||||||
|
"char": ["T"],
|
||||||
|
"level": 6,
|
||||||
|
"follows": ["dark-star-2"],
|
||||||
|
"goal": "Earn 10000 CP",
|
||||||
|
"color": "dark-blue"
|
||||||
|
},
|
||||||
|
"stratosphere": {
|
||||||
|
"name": "Stratosphere",
|
||||||
|
"char": ["V"],
|
||||||
|
"level": 6,
|
||||||
|
"follows": ["air-flair-4"],
|
||||||
|
"goal": "Defeat 800 enemies",
|
||||||
|
"color": "white"
|
||||||
|
},
|
||||||
|
"teleport-spike": {
|
||||||
|
"name": "Teleport Spike",
|
||||||
|
"char": ["A"],
|
||||||
|
"level": 6,
|
||||||
|
"follows": ["magic-pulse-4"],
|
||||||
|
"goal": "Defeat 800 enemies",
|
||||||
|
"color": "pink"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
|
@ -0,0 +1,8 @@
|
||||||
|
unstable_features = true
|
||||||
|
reorder_imports = true
|
||||||
|
hard_tabs = true
|
||||||
|
control_brace_style = "ClosingNextLine"
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
edition = "2021"
|
||||||
|
newline_style = "Unix"
|
|
@ -0,0 +1,173 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
enum Character {
|
||||||
|
#[serde(alias = "A")]
|
||||||
|
Aqua,
|
||||||
|
#[serde(alias = "V")]
|
||||||
|
Ventus,
|
||||||
|
#[serde(alias = "T")]
|
||||||
|
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)]
|
||||||
|
#[template(path = "pages/commands.html", whitespace = "suppress")]
|
||||||
|
struct CommandsTemplate {
|
||||||
|
pub commands: Vec<Command>,
|
||||||
|
pub crystals: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ABILITIES_PATH: &str = "./input/abilities.json";
|
||||||
|
const FINISHERS_PATH: &str = "./input/finish-commands.json";
|
||||||
|
const COMMANDS_PATH: &str = "./input/commands.json";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize tracing
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.event_format(tracing_subscriber::fmt::format().with_source_location(true))
|
||||||
|
.init();
|
||||||
|
|
||||||
|
tracing::info!("Loading abilities json data from {}", ABILITIES_PATH);
|
||||||
|
let abilities_str = std::fs::read_to_string(ABILITIES_PATH).unwrap();
|
||||||
|
let abilities = serde_json::from_str::<Vec<Ability>>(&abilities_str).unwrap();
|
||||||
|
|
||||||
|
tracing::info!("Loading finishers json data from {}", ABILITIES_PATH);
|
||||||
|
let finishers_str = std::fs::read_to_string(FINISHERS_PATH).unwrap();
|
||||||
|
let finishers = serde_json::from_str::<HashMap<String, Finisher>>(&finishers_str).unwrap();
|
||||||
|
|
||||||
|
tracing::info!("Loading commands json data from {}", ABILITIES_PATH);
|
||||||
|
let commands_str = std::fs::read_to_string(COMMANDS_PATH).unwrap();
|
||||||
|
let mut commands = serde_json::from_str::<Vec<Command>>(&commands_str).unwrap();
|
||||||
|
|
||||||
|
// Create a vec with all the crystal variants found in abilities
|
||||||
|
let crystals = abilities
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.from.clone())
|
||||||
|
.unique()
|
||||||
|
.sorted()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Create a vec of crystals and what ability they give for each recipe
|
||||||
|
for cmd in commands.iter_mut() {
|
||||||
|
for recipe in cmd.recipes.iter_mut() {
|
||||||
|
recipe.set_abilities(&abilities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Generating the commands table template");
|
||||||
|
let template = CommandsTemplate { commands, crystals };
|
||||||
|
|
||||||
|
std::fs::write("./index.html", template.render().unwrap()).unwrap();
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="hasAny"
|
||||||
|
name="charFilter"
|
||||||
|
autocomplete="off"
|
||||||
|
value=""
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label for="hasAny">Any</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="hasTerra"
|
||||||
|
autocomplete="off"
|
||||||
|
value="T"
|
||||||
|
name="charFilter"
|
||||||
|
/>
|
||||||
|
<label for="hasTerra">Terra</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="hasVentus"
|
||||||
|
autocomplete="off"
|
||||||
|
value="V"
|
||||||
|
name="charFilter"
|
||||||
|
/>
|
||||||
|
<label for="hasVentus">Ventus</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="hasAqua"
|
||||||
|
autocomplete="off"
|
||||||
|
value="A"
|
||||||
|
name="charFilter"
|
||||||
|
/>
|
||||||
|
<label for="hasAqua">Aqua</label>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<input type="text" id="filter" autocomplete="off" />
|
||||||
|
<br />
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="searchResult"
|
||||||
|
name="search"
|
||||||
|
autocomplete="off"
|
||||||
|
value="result"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label for="searchResult">Result</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="searchIngredients"
|
||||||
|
autocomplete="off"
|
||||||
|
value="ingredients"
|
||||||
|
name="search"
|
||||||
|
/>
|
||||||
|
<label for="searchIngredients">Ingredients</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="searchAbilities"
|
||||||
|
autocomplete="off"
|
||||||
|
value="abilities"
|
||||||
|
name="search"
|
||||||
|
/>
|
||||||
|
<label for="searchAbilities">Abilities</label>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="isAny"
|
||||||
|
name="typeFilter"
|
||||||
|
autocomplete="off"
|
||||||
|
value=""
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label for="isAny">Any</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="isAttack"
|
||||||
|
name="typeFilter"
|
||||||
|
autocomplete="off"
|
||||||
|
value="attack"
|
||||||
|
/>
|
||||||
|
<label for="isAttack">Attack</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="isMagic"
|
||||||
|
name="typeFilter"
|
||||||
|
autocomplete="off"
|
||||||
|
value="magic"
|
||||||
|
/>
|
||||||
|
<label for="isMagic">Magic</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="isAction"
|
||||||
|
name="typeFilter"
|
||||||
|
autocomplete="off"
|
||||||
|
value="action"
|
||||||
|
/>
|
||||||
|
<label for="isAction">Action</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="isShotlock"
|
||||||
|
name="typeFilter"
|
||||||
|
autocomplete="off"
|
||||||
|
value="shotlock"
|
||||||
|
/>
|
||||||
|
<label for="isShotlock">Shotlock</label>
|
|
@ -0,0 +1,57 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="content">{% block content %}{% endblock %}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,198 @@
|
||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Commands{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script>
|
||||||
|
let charFilter = "";
|
||||||
|
let typeFilter = "";
|
||||||
|
let searchType = "result";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
const searchFilter = document.getElementById("filter");
|
||||||
|
let filterHandler = debounce(() => filter());
|
||||||
|
searchFilter.addEventListener("keyup", filterHandler);
|
||||||
|
|
||||||
|
const searchInputs = document.querySelectorAll(
|
||||||
|
'input[type="radio"][name="search"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
searchInputs.forEach(function (item, index) {
|
||||||
|
item.addEventListener("input", function () {
|
||||||
|
searchType = this.checked ? this.value : "";
|
||||||
|
filter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const charFilters = document.querySelectorAll(
|
||||||
|
'input[type="radio"][name="charFilter"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
charFilters.forEach(function (item, index) {
|
||||||
|
item.addEventListener("input", function () {
|
||||||
|
charFilter = this.checked ? this.value : "";
|
||||||
|
filter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeFilters = document.querySelectorAll(
|
||||||
|
'input[type="radio"][name="typeFilter"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
typeFilters.forEach(function (item, index) {
|
||||||
|
item.addEventListener("input", function () {
|
||||||
|
typeFilter = this.checked ? this.value : "";
|
||||||
|
filter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function debounce(callback, wait = 300) {
|
||||||
|
let timeoutId = null;
|
||||||
|
return (...args) => {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
timeoutId = window.setTimeout(() => {
|
||||||
|
callback(...args);
|
||||||
|
}, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function filter() {
|
||||||
|
const table = document.querySelector("table tbody");
|
||||||
|
const search = document
|
||||||
|
.getElementById("filter")
|
||||||
|
.value.toLowerCase();
|
||||||
|
|
||||||
|
for (const child of table.children) {
|
||||||
|
const tds = child.children;
|
||||||
|
resetStyle(child, tds);
|
||||||
|
|
||||||
|
if (search.length > 0) {
|
||||||
|
if (searchType === "result") {
|
||||||
|
// Check for command name
|
||||||
|
if (!tds[1].innerText.toLowerCase().includes(search)) {
|
||||||
|
child.style.display = "none";
|
||||||
|
} else {
|
||||||
|
applyStyle(tds[1]);
|
||||||
|
}
|
||||||
|
} else if (searchType === "ingredients") {
|
||||||
|
// Ingredients
|
||||||
|
if (!tds[2].innerText.toLowerCase().includes(search)) {
|
||||||
|
if (
|
||||||
|
!tds[3].innerText.toLowerCase().includes(search)
|
||||||
|
) {
|
||||||
|
child.style.display = "none";
|
||||||
|
} else {
|
||||||
|
applyStyle(tds[3]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
applyStyle(tds[2]);
|
||||||
|
}
|
||||||
|
} else if (searchType === "abilities") {
|
||||||
|
// Abilities
|
||||||
|
hasLine = false;
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const id = i + 6;
|
||||||
|
const ablName = tds[id].innerText.toLowerCase();
|
||||||
|
if (ablName.includes(search)) {
|
||||||
|
applyStyle(tds[id]);
|
||||||
|
hasLine = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasLine) {
|
||||||
|
child.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeCheck =
|
||||||
|
typeFilter === "" || tds[1].className === typeFilter;
|
||||||
|
const charCheck =
|
||||||
|
charFilter === "" || child.className.includes(charFilter);
|
||||||
|
|
||||||
|
if (child.style.display == "" && (!typeCheck || !charCheck)) {
|
||||||
|
child.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetStyle(child, tds) {
|
||||||
|
child.style.display = "";
|
||||||
|
for (const td of tds) {
|
||||||
|
td.style.fontWeight = "inherit";
|
||||||
|
td.style.color = "#fff";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStyle(el) {
|
||||||
|
el.style.fontWeight = "bold";
|
||||||
|
el.style.color = "red";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include "components/search.html" %}
|
||||||
|
<br />
|
||||||
|
{% include "components/type-filters.html" %}
|
||||||
|
<br />
|
||||||
|
{% include "components/char-filters.html" %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowspan="2">Character</th>
|
||||||
|
<th rowspan="2">Command</th>
|
||||||
|
<th rowspan="2">Ingredient A</th>
|
||||||
|
<th rowspan="2">Ingredient B</th>
|
||||||
|
<th rowspan="2">Type</th>
|
||||||
|
<th rowspan="2">Chance</th>
|
||||||
|
<th colspan="7">Abilities</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for crystal in crystals %}
|
||||||
|
<th>{{ crystal }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for cmd in commands %}
|
||||||
|
{% for recipe in cmd.recipes %}
|
||||||
|
<tr class="{{ recipe.get_unlock_chars() }}">
|
||||||
|
<td>
|
||||||
|
<div class="charlist">
|
||||||
|
<!-- RGB moment -->
|
||||||
|
<span class="terra">
|
||||||
|
{% if recipe.can_unlock(Character::Terra) %}T{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="ventus">
|
||||||
|
{% if recipe.can_unlock(Character::Ventus) %}V{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="aqua">
|
||||||
|
{% if recipe.can_unlock(Character::Aqua) %}A{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="{{ cmd.category }}">{{ cmd.name }}</td>
|
||||||
|
<td>{{ recipe.ingredients.0 }}</td>
|
||||||
|
<td>{{ recipe.ingredients.1 }}</td>
|
||||||
|
<td>{{ recipe.type }}</td>
|
||||||
|
<td>{{ recipe.chance }}%</td>
|
||||||
|
{% for crystal in crystals %}
|
||||||
|
{% let ability = recipe.get_ability(crystal) %}
|
||||||
|
<td>
|
||||||
|
{% if ability.is_some() %}
|
||||||
|
{{ ability.unwrap().name }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue