use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct BossNameMapping {
pub boss_names: HashMap<String, String>,
#[serde(default)]
pub aliases: HashMap<String, String>,
}
impl BossNameMapping {
pub fn load() -> Self {
Self::default_mapping()
}
pub fn from_data_table(table: &crate::data_table::DataTable) -> Self {
let mut boss_names = HashMap::new();
let mut aliases = HashMap::new();
for row in &table.rows {
let comment = match row.fields.get("comment") {
Some(c) => c.as_str(),
None => continue,
};
let display_name = match crate::data_table::parse_boss_replay_comment(comment) {
Some((_, name)) => name.to_string(),
None => continue,
};
boss_names.insert(row.row_name.clone(), display_name.clone());
let row_lower = row.row_name.to_lowercase();
if row_lower == "foundryfreaks" {
aliases.insert("FoundryFreak".into(), display_name);
} else if row_lower == "meatheadriders" {
aliases.insert("MeatheadRider".into(), display_name);
} else if row_lower == "hovercarts" {
aliases.insert("Hovercart".into(), display_name);
} else if row_lower == "pangobango" {
aliases.insert("Pango".into(), display_name.clone());
aliases.insert("Bango".into(), display_name);
}
}
Self {
boss_names,
aliases,
}
}
pub fn merge_missing(&mut self, other: &BossNameMapping) {
for (key, value) in &other.boss_names {
if !self.boss_names.contains_key(key) {
self.boss_names.insert(key.clone(), value.clone());
}
}
for (key, value) in &other.aliases {
if !self.aliases.contains_key(key) {
self.aliases.insert(key.clone(), value.clone());
}
}
}
pub fn default_mapping() -> Self {
let mut mapping = Self::from_compiled_tsv();
mapping.merge_missing(&Self::hardcoded_fallbacks());
mapping
}
fn from_compiled_tsv() -> Self {
const TSV: &str = include_str!(concat!(env!("OUT_DIR"), "/table_bossreplay_costs.tsv"));
let mut boss_names = HashMap::new();
let mut aliases = HashMap::new();
for line in TSV.lines().skip(1) {
let cols: Vec<&str> = line.splitn(5, '\t').collect();
if cols.len() < 2 {
continue;
}
let row_name = cols[0];
let comment = cols[1];
let display_name = match crate::data_table::parse_boss_replay_comment(comment) {
Some((_, name)) => name.to_string(),
None => continue,
};
boss_names.insert(row_name.to_string(), display_name.clone());
let row_lower = row_name.to_lowercase();
if row_lower == "foundryfreaks" {
aliases.insert("FoundryFreak".into(), display_name);
} else if row_lower == "meatheadriders" {
aliases.insert("MeatheadRider".into(), display_name);
} else if row_lower == "hovercarts" {
aliases.insert("Hovercart".into(), display_name);
} else if row_lower == "pangobango" {
aliases.insert("Pango".into(), display_name.clone());
aliases.insert("Bango".into(), display_name);
}
}
Self {
boss_names,
aliases,
}
}
fn hardcoded_fallbacks() -> Self {
let names: &[(&str, &str)] = &[
("Grasslands_Commander", "Primordial Guardian Inceptus"),
("MountainCommander", "Primordial Guardian Radix"),
("ShatterlandsCommanderElpis", "Primordial Guardian Origo"),
("ShatterlandsCommanderFortress", "Primordial Guardian Origo"),
("Timekeeper_TKBoss", "The Timekeeper"),
("Grasslands_Guardian", "Grasslands Guardian"),
("MountainGuardian", "Mountain Guardian"),
("ShatterlandsGuardian", "Shatterlands Guardian"),
("Timekeeper_Guardian", "Timekeeper Guardian"),
("GlidePackPsycho", "Splashzone"),
("KOTOMotherbaseBrute", "Bio-Bulkhead"),
("KotoLieutenant", "Horace"),
("FoundryFreak_MeatheadFrackingBoss", "Foundry Freaks"),
("Thresher_BioArmoredBig", "Bio-Thresher Omega"),
("MeatheadRider_Jockey", "Jockey"),
("Redguard", "Directive-0"),
("Donk", "Donk"),
("MinisterScrew", "Minister Screw"),
("Bloomreaper", "Bloomreaper"),
("SideCity_Psycho", "Side City Psycho"),
("FoundryFreak_Psycho", "Foundry Freak Psycho"),
("FoundryFreak_Splice", "Foundry Freak Splice"),
];
let boss_names = names
.iter()
.map(|(k, v)| ((*k).into(), (*v).into()))
.collect();
let aliases = HashMap::new();
Self {
boss_names,
aliases,
}
}
pub fn get_display_name(&self, internal_name: &str) -> Option<&str> {
if let Some(name) = self.boss_names.get(internal_name) {
return Some(name);
}
let name_lower = internal_name.to_lowercase();
for (key, value) in &self.boss_names {
if key.to_lowercase() == name_lower {
return Some(value);
}
}
let normalized = name_lower.replace('_', "");
for (key, value) in &self.boss_names {
if key.to_lowercase().replace('_', "") == normalized {
return Some(value);
}
}
for (alias, name) in &self.aliases {
if name_lower.contains(&alias.to_lowercase()) {
return Some(name);
}
}
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DropSource {
Boss,
WorldDrop,
BlackMarket,
Mission,
Special,
}
impl std::fmt::Display for DropSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DropSource::Boss => write!(f, "Boss"),
DropSource::WorldDrop => write!(f, "World Drop"),
DropSource::BlackMarket => write!(f, "Black Market"),
DropSource::Mission => write!(f, "Mission"),
DropSource::Special => write!(f, "Special"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropEntry {
pub source: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_display: Option<String>,
pub source_type: DropSource,
pub manufacturer: String,
pub gear_type: String,
pub item_name: String,
pub item_id: String,
pub pool: String,
pub drop_tier: String,
pub drop_chance: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropProbabilities {
#[serde(rename = "Primary")]
pub primary: f64,
#[serde(rename = "Secondary")]
pub secondary: f64,
#[serde(rename = "Tertiary")]
pub tertiary: f64,
#[serde(rename = "Shiny")]
pub shiny: f64,
#[serde(rename = "TrueBoss")]
pub true_boss: f64,
#[serde(rename = "TrueBossShiny")]
pub true_boss_shiny: f64,
}
impl Default for DropProbabilities {
fn default() -> Self {
Self {
primary: 0.06,
secondary: 0.045,
tertiary: 0.03,
shiny: 0.01,
true_boss: 0.25,
true_boss_shiny: 0.03,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropsManifest {
pub version: u32,
pub drops: Vec<DropEntry>,
pub probabilities: DropProbabilities,
}
#[derive(Debug, Clone)]
pub struct DropLocation {
pub item_name: String,
pub source: String,
pub source_display: Option<String>,
pub source_type: DropSource,
pub tier: String,
pub chance: f64,
pub chance_display: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorldDropPool {
pub name: String,
pub item_count: u32,
pub per_item_chance: f64,
}