Compare commits

..

No commits in common. "d8fd3d71115b3676be2078a91b1ae12283c73285" and "fd334262193a71fe21fa2dfbe1daef4a4427e771" have entirely different histories.

39 changed files with 64 additions and 895 deletions

View File

@ -12,7 +12,6 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
itertools = "0.14"
blake3 = "1.8"
ordered-float = { version = "5.0", features = ["serde"] }
[features]
default = ["bbs", "ddd", "kh3", "kh2", "kh1"]

View File

@ -1,29 +0,0 @@
kind = "blaze"
aliases = ["blazing"]
[[shard]]
from = "Red Nocturne"
chance = 6
[[shard]]
from = "White Mushroom"
chance = 100
info = "When Fire is the last spell used"
[[gem]]
from = "Bandit"
chance = 4
[[gem]]
from = "Fat Bandit"
chance = 8
[[gem]]
from = "White Mushroom"
chance = 10
info = "When Fire is used three times"
[[stone]]
from = "Chimera"
chance = 35
info = "Additional drops based on how many heads are reflected without getting hit"

View File

@ -1,23 +0,0 @@
kind = "bright"
[[shard]]
from = "Green Requiem"
chance = 10
[[shard]]
from = "White Mushroom"
chance = 100
info = "When Cure is the last spell used"
[[gem]]
from = "Search Ghost"
chance = 4
[[gem]]
from = "White Mushroom"
chance = 10
info = "When Cure is used three times"
[[crystal]]
from = "Defender"
chance = 2

View File

@ -1,5 +0,0 @@
kind = "energy"
[[stone]]
from = "Stealth Soldier"
chance = 35

View File

@ -1,28 +0,0 @@
kind = "frost"
[[shard]]
from = "Blue Rhapsody"
chance = 12
[[shard]]
from = "White Mushroom"
chance = 100
info = "When Blizzard is the last spell used"
[[gem]]
from = "Sea Neon"
chance = 2
[[gem]]
from = "Sheltering Zone"
chance = 8
[[gem]]
from = "White Mushroom"
chance = 10
info = "When Blizzard is used three times"
[[stone]]
from = "Grand Ghost"
chance = 100
info = "Additional drops based on what items are used to defeat it with Megalixirs having an additional 100% drop"

View File

@ -1,23 +0,0 @@
kind = "mythril"
[[shard]]
from = "Pot Spider"
chance = 1
[[shard]]
from = "Barrel Spider"
chance = 1
[[shard]]
from = "Pot Scorpion"
chance = 20
info = "Only if all 11 pots are broken before engaging"
[[shard]]
from = "Arch Behemoth"
chance = 20
[[stone]]
from = "Pot Scorpion"
chance = "10-35"
info = "20% when less than 11 pots got destroyed\n35% when all 11 pots were destroyed\nadditional 10% chance when breaking the 11th pot"

View File

@ -1,40 +0,0 @@
kind = "power"
[[shard]]
from = "Powerwild"
chance = 4
[[shard]]
from = "Bouncywild"
chance = 8
[[shard]]
from = "White Mushroom"
chance = 100
info = "When Stop is the last spell used"
[[gem]]
from = "Pirate"
chance = 4
[[gem]]
from = "Air Pirate"
chance = 4
[[gem]]
from = "Battleeship"
chance = 4
[[gem]]
from = "White Mushroom"
chance = 10
info = "When Stop is used three times"
[[stone]]
from = "Sniperwild"
chance = "?"
info = "% based on how many sets of 6 are defeated"
[[crystal]]
from = "Wyvern"
chance = 2

View File

@ -1,21 +0,0 @@
kind = "serenity"
aliases = ["hungry", "mystery"]
[[crystal]]
from = "White Mushroom"
chance = "10-20"
info = "10% when defeated and 20% when the same spell is used three times"
[[crystal]]
from = "Black Fungus"
chance = 6
[[crystal]]
from = "Rare Truffle"
chance = "20-100"
info = "% depends on how many times it got juggles ranging from 10 to 100"
[[stone]]
from = "Pink Agaricus"
chance = "10-100"
info = "% depends on how many times it got hit ranging from 40 to 100"

View File

@ -1,11 +0,0 @@
kind = "shiny"
aliases = ["shimmering"]
[[crystal]]
from = "Wizard"
chance = 2
[[stone]]
from = "Jet Balloon"
chance = 100
info = "Additional 20% drop chance"

View File

@ -1,14 +0,0 @@
kind = "stormy"
aliases = ["gale"]
[[crystal]]
from = "Angel Star"
chance = 4
[[crystal]]
from = "Invisible"
chance = 4
[[stone]]
from = "Neoshadow"
chance = 35

View File

@ -1,29 +0,0 @@
kind = "thunder"
aliases = ["lightning"]
[[shard]]
from = "Yellow Opera"
chance = 8
[[shard]]
from = "White Mushroom"
chance = 100
info = "When Thunder is the last spell used"
[[gem]]
from = "Aquatank"
chance = 8
[[gem]]
from = "Screwdiver"
chance = 4
[[gem]]
from = "White Mushroom"
chance = 10
info = "When Thunder is used three times"
[[stone]]
from = "Black Ballade"
chance = 100
info = "With additional 10% drops each time the correct target is hit"

View File

@ -1,24 +0,0 @@
name = "Air Pirate"
[[world]]
name = "Neverland"
room = ""
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 2
[[drops]]
name = "Mega-Potion"
kind = "item"
chance = 1
[[drops]]
name = "Power Gem"
kind = "material"
chance = 4
[drops.material]
category = "power"
kind = "gem"

View File

@ -1,24 +0,0 @@
name = "Air Soldier"
[[world]]
name = ""
room = ""
[[drops]]
name = "Potion"
kind = "item"
chance = 2
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 1
[[drops]]
name = "Spirit Gem"
kind = "material"
chance = 4
[drops.material]
category = "spirit"
kind = "gem"

View File

@ -1,18 +0,0 @@
name = "Angel Star"
[[world]]
name = "End of the World"
[[drops]]
name = "Ether"
kind = "item"
chance = 2
[[drops]]
name = "Gale"
kind = "material"
chance = 4
[drops.material]
category = "stormy"
kind = "crystal"

View File

@ -1,18 +0,0 @@
name = "Aquatank"
[[world]]
name = "Atlantica"
[[drops]]
name = "Mega-Potion"
kind = "item"
chance = 4
[[drops]]
name = "Thunder Gem"
kind = "material"
chance = 8
[drops.material]
category = "thunder"
kind = "gem"

View File

@ -1,24 +0,0 @@
name = "Bandit"
[[world]]
name = "Agrabah"
[[world]]
name = "Monstro"
[[world]]
name = "End of the World"
[[drops]]
name = "Potion"
kind = "item"
chance = 2
[[drops]]
name = "Blaze Gem"
kind = "material"
chance = 4
[drops.material]
category = "blaze"
kind = "gem"

View File

@ -1,20 +0,0 @@
name = "Barrel Spider"
[[world]]
name = "Monstro"
[[world]]
name = "Neverland"
[[world]]
name = "End of the World"
[[drops]]
name = "Camping Set"
kind = "item"
chance = 1
[[drops]]
name = "Cottage"
kind = "item"
chance = 0.5

View File

@ -1,34 +0,0 @@
name = "Battleship"
[[world]]
name = "Neverland"
[[drops]]
name = "Elixir"
kind = "item"
chance = 0.5
[[drops]]
name = "Elixir"
kind = "item"
chance = 1
info = "Destroy the Stern, Cannons or Mast"
[[drops]]
name = "Power Gem"
kind = "material"
chance = 4
[drops.material]
category = "power"
kind = "gem"
[[drops]]
name = "Power Gem"
kind = "material"
chance = 8
info = "Destroy the Stern, Cannons or Mast"
[drops.material]
category = "power"
kind = "gem"

View File

@ -1,30 +0,0 @@
name = "Blue Rhapsody"
[[world]]
name = "Traverse Town"
[[world]]
name = "Wonderland"
[[world]]
name = "Monstro"
[[world]]
name = "Hollow Bastion"
[[world]]
name = "End of the World"
[[drops]]
name = "Ether"
kind = "item"
chance = 1
[[drops]]
name = "Frost Shard"
kind = "material"
chance = 12
[drops.material]
category = "frost"
kind = "shard"

View File

@ -1,43 +0,0 @@
name = "Bouncywild"
[[world]]
name = "Deep Jungle"
[[world]]
name = "End of the World"
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 2
[[drops]]
name = "Power Shard"
kind = "material"
chance = 8
[drops.material]
category = "power"
kind = "shard"
[[drops]]
name = "Ether"
kind = "item"
chance = 20
info = "When it slips on the banana peel"
[[drops]]
name = "Mega-Ether"
kind = "item"
chance = 4
info = "When it slips on the banana peel"
[[drops]]
name = "Power Shard"
kind = "material"
chance = 8
info = "When it slips on the banana peel"
[drops.material]
category = "power"
kind = "shard"

View File

@ -1,24 +0,0 @@
name = "Darkball"
[[world]]
name = "Traverse Town"
[[world]]
name = "Hollow Bastion"
[[world]]
name = "End of the World"
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 1
[[drops]]
name = "Lucid Crystal"
kind = "material"
chance = 1
[drops.material]
category = "lucid"
kind = "crystal"

View File

@ -1,29 +0,0 @@
name = "Defender"
[[world]]
name = "Traverse Town"
[[world]]
name = "Hollow Bastion"
[[world]]
name = "End of the World"
[[drops]]
name = "Elixir"
kind = "item"
chance = 1
[[drops]]
name = "Bright Crystal"
kind = "material"
chance = 2
[drops.material]
category = "bright"
kind = "crystal"
[[drops]]
name = "Defender"
kind = "equipment"
chance = 0.2

View File

@ -1,24 +0,0 @@
name = "Fat Bandit"
[[world]]
name = "Agrabah"
[[world]]
name = "Monstro"
[[world]]
name = "End of the World"
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 4
[[drops]]
name = "Blaze Gem"
kind = "material"
chance = 8
[drops.material]
category = "blaze"
kind = "gem"

View File

@ -1,26 +0,0 @@
name = "Gargoyle"
[[world]]
name = "Holloween Town"
[[world]]
name = "End of the World"
[[drops]]
name = "Ether"
kind = "item"
chance = 1
[[drops]]
name = "Mega-Ether"
kind = "item"
chance = 0.5
[[drops]]
name = "Lucid Gem"
kind = "material"
chance = 2
[drops.material]
category = "lucid"
kind = "gem"

View File

@ -1,33 +0,0 @@
name = "Green Requiem"
[[world]]
name = "Traverse Town"
[[world]]
name = "Agrabah"
[[world]]
name = "Monstro"
[[world]]
name = "Deep Jungle"
[[world]]
name = "Hollow Bastion"
[[world]]
name = "End of the World"
[[drops]]
name = "Ether"
kind = "item"
chance = 4
[[drops]]
name = "Bright Shard"
kind = "material"
chance = 10
[drops.material]
category = "bright"
kind = "shard"

View File

@ -1,18 +0,0 @@
name = "Invisible"
[[world]]
name = "End of the World"
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 2
[[drops]]
name = "Gale"
kind = "material"
chance = 4
[drops.material]
category = "stormy"
kind = "crystal"

View File

@ -1,34 +0,0 @@
name = "Large Body"
[[world]]
name = "Traverse Town"
[[world]]
name = "Wonderland"
[[world]]
name = "Agrabah"
marked = true
[[world]]
name = "Monstro"
[[world]]
name = "Hollow Bastion"
[[world]]
name = "End of the World"
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 4
[[drops]]
name = "Spirit Shard"
kind = "material"
chance = 10
[drops.material]
category = "spirit"
kind = "shard"

View File

@ -1,29 +0,0 @@
name = "Pirate"
[[world]]
name = "Neverland"
[[world]]
name = "Monstro"
[[world]]
name = "End of the World"
[[drops]]
name = "Hi-Potion"
kind = "item"
chance = 2
[[drops]]
name = "Mega-Potion"
kind = "item"
chance = 1
[[drops]]
name = "Power Gem"
kind = "material"
chance = 4
[drops.material]
category = "power"
kind = "gem"

View File

@ -1,19 +0,0 @@
name = "Shadow"
[[world]]
name = "Traverse Town"
room = ""
[[drops]]
name = "Potion"
kind = "item"
chance = 6
[[drops]]
name = "Lucid Shard"
kind = "material"
chance = 3
[drops.material]
category = "lucid"
kind = "shard"

View File

@ -1,14 +0,0 @@
name = "Soldier"
[[world]]
name = "Traverse Town"
room = ""
[[drops]]
name = "Spirit Shard"
kind = "material"
chance = 6
[drops.material]
category = "spirit"
kind = "shard"

View File

@ -1,3 +1,2 @@
pub mod direction;
pub mod enemy;
pub mod materials;

View File

@ -1,90 +0,0 @@
use std::{fmt::Display, path::PathBuf};
use serde::Deserialize;
use super::materials::MaterialDetails;
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Enemy {
pub name: String,
pub icon: Option<String>,
pub world: Vec<SpawnLocation>,
pub drops: Vec<EnemyDrop>,
}
impl Enemy {
pub fn import(path: &str) -> Vec<Enemy> {
let mut enemies: Vec<Enemy> = vec![];
// Loading multiple files into one vector due to the size of each board
let paths = std::fs::read_dir(path)
.unwrap()
.filter_map(|f| f.ok())
.map(|f| f.path())
.filter_map(|p| match p.extension().is_some_and(|e| e == "toml") {
true => Some(p),
false => None,
})
.collect::<Vec<PathBuf>>();
for path in paths {
let enemy_str = std::fs::read_to_string(path).unwrap();
let mut enemy = toml::from_str::<Enemy>(&enemy_str).unwrap();
enemy
.drops
.iter_mut()
.for_each(|d| d.from = enemy.name.clone());
enemies.push(enemy);
}
enemies
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct EnemyDrop {
pub name: String,
#[serde(skip)]
pub from: String,
pub chance: EnemyDropChance,
pub kind: EnemyDropKind,
pub icon: Option<String>,
pub info: Option<String>,
pub material: Option<MaterialDetails>,
}
impl EnemyDrop {
pub fn texture(&self) -> String {
self.from.replace(" ", "_").to_lowercase()
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum EnemyDropKind {
Item,
Material,
Equipment,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged)]
pub enum EnemyDropChance {
Fixed(ordered_float::OrderedFloat<f32>),
Variable(String),
}
impl Display for EnemyDropChance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EnemyDropChance::Fixed(val) => f.write_str(&format!("{val}%")),
EnemyDropChance::Variable(val) => f.write_str(val),
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct SpawnLocation {
pub name: String,
pub room: Option<String>,
}

View File

@ -1,100 +1,80 @@
use std::{collections::HashMap, fmt::Display};
use std::{fmt::Display, path::PathBuf};
use serde::Deserialize;
use super::enemy::{Enemy, EnemyDrop};
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct EnemyDrop {
pub from: String,
pub chance: EnemyDropChance,
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct MaterialDetails {
pub category: String,
pub kind: MaterialKind,
#[serde(default)]
pub info: Option<String>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum MaterialKind {
Shard,
Stone,
Gem,
Crystal,
impl EnemyDrop {
pub fn texture(&self) -> String {
self.from.replace(" ", "_").to_lowercase()
}
}
impl Display for MaterialKind {
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum EnemyDropChance {
Fixed(u8),
Variable(String),
}
impl Display for EnemyDropChance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MaterialKind::Shard => f.write_str("shard"),
MaterialKind::Stone => f.write_str("stone"),
MaterialKind::Gem => f.write_str("gem"),
MaterialKind::Crystal => f.write_str("crystal"),
EnemyDropChance::Fixed(val) => f.write_str(&format!("{val}%")),
EnemyDropChance::Variable(val) => f.write_str(val),
}
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct MaterialDrops {
pub name: String,
pub icon: String,
pub category: String,
pub kind: MaterialKind,
pub drops: Vec<EnemyDrop>,
}
impl PartialOrd for MaterialDrops {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.category.cmp(&other.category) == std::cmp::Ordering::Equal {
return Some(self.kind.cmp(&other.kind));
}
Some(self.category.cmp(&other.category))
}
pub kind: String,
pub shard: Vec<EnemyDrop>,
pub stone: Vec<EnemyDrop>,
pub gem: Vec<EnemyDrop>,
pub crystal: Vec<EnemyDrop>,
}
impl MaterialDrops {
pub fn new(enemies: Vec<Enemy>) -> Vec<MaterialDrops> {
let mut mat_map = HashMap::<(String, MaterialKind), MaterialDrops>::new();
pub fn import(path: &str) -> Vec<MaterialDrops> {
let mut drops: Vec<MaterialDrops> = vec![];
for enemy in enemies {
for drop in &enemy.drops {
let Some(material) = &drop.material else {
continue;
};
// Loading multiple files into one vector due to the size of each board
let paths = std::fs::read_dir(path)
.unwrap()
.filter_map(|f| f.ok())
.map(|f| f.path())
.filter_map(|p| match p.extension().is_some_and(|e| e == "toml") {
true => Some(p),
false => None,
})
.collect::<Vec<PathBuf>>();
let key = (material.category.clone(), material.kind.clone());
mat_map
.entry(key)
.and_modify(|d| d.drops.push(drop.clone()))
.or_insert(MaterialDrops {
name: drop.name.to_string(),
icon: "".to_string(),
category: material.category.clone(),
kind: material.kind.clone(),
drops: vec![drop.clone()],
});
for path in paths {
let drops_str = std::fs::read_to_string(path).unwrap();
let enemy_drops = toml::from_str::<MaterialDrops>(&drops_str).unwrap();
drops.push(enemy_drops);
}
drops.sort_by(|a, b| a.kind.cmp(&b.kind));
drops
}
let mut values: Vec<MaterialDrops> = mat_map.into_values().collect();
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
values
}
pub fn drops(&self, kind: &str) -> Vec<&EnemyDrop> {
pub fn drops(&self, kind: &str) -> &[EnemyDrop] {
match kind {
"shard" => self.get_drop_kind(MaterialKind::Shard),
"stone" => self.get_drop_kind(MaterialKind::Stone),
"gem" => self.get_drop_kind(MaterialKind::Gem),
"crystal" => self.get_drop_kind(MaterialKind::Crystal),
_ => vec![],
"shard" => &self.shard,
"stone" => &self.stone,
"gem" => &self.gem,
"crystal" => &self.crystal,
_ => &self.shard,
}
}
fn get_drop_kind(&self, kind: MaterialKind) -> Vec<&EnemyDrop> {
self.drops
.iter()
.filter(|d| d.material.as_ref().map(|m| m.kind == kind).unwrap_or(false))
.collect::<Vec<&EnemyDrop>>()
}
}

View File

@ -3,18 +3,13 @@ use std::sync::OnceLock;
use askama::Template;
use blake3::Hash;
use crate::{
RuntimeModule,
common::{enemy::Enemy, materials::MaterialDrops},
create_file, create_hashes,
};
use crate::{RuntimeModule, common::materials::MaterialDrops, create_file, create_hashes};
const MATERIAL_KINDS: &[&str] = &[
"lucid", "spirit", "power", "blaze", "frost", "thunder", "shiny", "bright", "mystery", "gale",
"mythril",
];
const DROPS_PATH: &str = "./input/kh1/drops";
const ENEMIES_PATH: &str = "./input/kh1/enemies";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)]
@ -27,11 +22,8 @@ pub struct Module;
impl RuntimeModule for Module {
fn start_module() {
tracing::info!("Loading enemy data from {}", ENEMIES_PATH);
let enemies = Enemy::import(ENEMIES_PATH);
tracing::info!("Loading enemy drops data from {}", DROPS_PATH);
let drops = MaterialDrops::new(enemies);
let drops = MaterialDrops::import(DROPS_PATH);
tracing::info!("Generating the KH1 drops template");
let drops_template = DropsTemplate { drops };

View File

@ -33,12 +33,12 @@ pub struct Module;
impl RuntimeModule for Module {
fn start_module() {
tracing::info!("Loading enemy drops data from {}", DROPS_PATH);
// let drops = MaterialDrops::import(DROPS_PATH);
let drops = MaterialDrops::import(DROPS_PATH);
tracing::info!("Generating the KH2 drops template");
// let drops_template = DropsTemplate { drops };
let drops_template = DropsTemplate { drops };
// create_file("./out/kh2", "drops", drops_template.render().unwrap()).unwrap();
create_file("./out/kh2", "drops", drops_template.render().unwrap()).unwrap();
}
fn get_js_hash() -> String {

View File

@ -72,7 +72,7 @@ fn start_module<M: RuntimeModule>() {
}
fn create_hashes(module: &str) -> Hash {
let mut hasher = Hasher::new();
let mut hasher = blake3::Hasher::new();
hash_file(format!("./public/scripts/{module}.js").into(), &mut hasher);
hash_files_in_dir("./public/scripts/modules/common".into(), &mut hasher);
hash_files_in_dir(

View File

@ -1,21 +1,18 @@
{% macro drop(label) %}
{% let drops = drop.drops(label) %}
{% let drops = category.drops(label) %}
{% if drops.len() > 0 %}
<div
class="category-wrapper"
data-mat-kind="{{ drop.category }}"
data-mat-kind="{{ category.kind }}"
data-mat-type="{{ label }}"
>
<div class="category">
<img
src="../public/assets/materials/{{ drop.category }}/{{ label }}.webp"
src="../public/assets/materials/{{ category.kind }}/{{ label }}.webp"
width="64"
height="64"
/>
<!-- <h1> -->
<!-- {{ drop.category|capitalize +}} {{+ label|capitalize }} -->
<!-- </h1> -->
<h1>{{ drop.name }}</h1>
<h1>{{ category.kind|capitalize +}} {{+ label|capitalize }}</h1>
<button onclick="track(this)">Start tracking</button>
</div>
<div class="enemies">

View File

@ -14,7 +14,7 @@
{% include "components/common/kind-filters.html" %}
<br />
{% for drop in drops %}
{% for category in drops %}
{% call macros::drop("shard") %}
{% call macros::drop("stone") %}
{% call macros::drop("gem") %}

View File

@ -14,7 +14,7 @@
{% include "components/common/kind-filters.html" %}
<br />
{% for drop in drops %}
{% for category in drops %}
{% call macros::drop("shard") %}
{% call macros::drop("stone") %}
{% call macros::drop("gem") %}