use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
const CATEGORY_NAMES_TSV: &str = include_str!(concat!(env!("OUT_DIR"), "/category_names.tsv"));
const PARTS_DATABASE_TSV: &str = include_str!(concat!(env!("OUT_DIR"), "/parts_database.tsv"));
const MANUFACTURERS_JSON: &str = include_str!(concat!(env!("OUT_DIR"), "/manufacturers.json"));
const WEAPON_TYPES_JSON: &str = include_str!(concat!(env!("OUT_DIR"), "/weapon_types.json"));
const DROP_POOLS_TSV: &str = include_str!(concat!(env!("OUT_DIR"), "/drop_pools.tsv"));
const PART_POOLS_TSV: &str = include_str!(concat!(env!("OUT_DIR"), "/part_pools.tsv"));
const BOSS_REPLAY_COSTS_TSV: &str =
include_str!(concat!(env!("OUT_DIR"), "/table_bossreplay_costs.tsv"));
const ITEM_NAMES_TSV: &str = include_str!(concat!(env!("OUT_DIR"), "/item_names.tsv"));
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct Manufacturer {
code: String,
name: String,
#[serde(default)]
path: Option<String>,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct WeaponType {
name: String,
#[serde(default)]
manufacturers: Vec<Manufacturer>,
}
static CATEGORY_NAMES: Lazy<HashMap<i64, String>> =
Lazy::new(|| parse_tsv_pairs(CATEGORY_NAMES_TSV));
static PARTS_BY_ID: Lazy<HashMap<(i64, i64), (String, String)>> =
Lazy::new(|| parse_tsv_parts(PARTS_DATABASE_TSV));
static PARTS_BY_NAME: Lazy<HashMap<(i64, String), i64>> = Lazy::new(|| {
PARTS_BY_ID
.iter()
.map(|(&(cat, idx), (name, _))| {
let bare = normalize_part_name(name).to_string();
((cat, bare), idx)
})
.collect()
});
fn parse_tsv_pairs(tsv: &str) -> HashMap<i64, String> {
tsv.lines()
.skip(1)
.filter_map(|line| {
let mut cols = line.splitn(2, '\t');
let id = cols.next()?.parse::<i64>().ok()?;
let name = cols.next()?.to_string();
Some((id, name))
})
.collect()
}
fn parse_tsv_parts(tsv: &str) -> HashMap<(i64, i64), (String, String)> {
tsv.lines()
.skip(1)
.filter_map(|line| {
let mut cols = line.splitn(4, '\t');
let category = cols.next()?.parse::<i64>().ok()?;
let index = cols.next()?.parse::<i64>().ok()?;
let name = cols.next()?.to_string();
let slot = cols.next().unwrap_or("unknown").to_string();
Some(((category, index), (name, slot)))
})
.collect()
}
#[derive(Debug, Clone)]
pub struct DropPool {
pub manufacturer_code: String,
pub gear_type_code: String,
pub legendary_count: u32,
pub boss_source_count: u32,
pub world_pool_name: String,
}
static DROP_POOLS: Lazy<HashMap<(String, String), DropPool>> = Lazy::new(|| {
DROP_POOLS_TSV
.lines()
.skip(1)
.filter_map(|line| {
let cols: Vec<&str> = line.splitn(5, '\t').collect();
if cols.len() < 5 {
return None;
}
let pool = DropPool {
manufacturer_code: cols[0].to_string(),
gear_type_code: cols[1].to_string(),
legendary_count: cols[2].parse().ok()?,
boss_source_count: cols[3].parse().ok()?,
world_pool_name: cols[4].to_string(),
};
Some(((cols[0].to_string(), cols[1].to_string()), pool))
})
.collect()
});
static BOSS_NAMES: Lazy<HashMap<String, String>> = Lazy::new(|| {
let mut names = HashMap::new();
for line in BOSS_REPLAY_COSTS_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];
if let Some(display_name) = parse_boss_comment(comment) {
names.insert(row_name.to_string(), display_name.to_string());
}
}
names
});
fn parse_boss_comment(comment: &str) -> Option<&str> {
if comment.is_empty() {
return None;
}
let mut parts = comment.splitn(3, ", ");
let _table = parts.next()?;
let uuid = parts.next()?;
if uuid.len() != 32 || !uuid.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
parts.next()
}
fn normalize_part_name(name: &str) -> &str {
name.split('.').next_back().unwrap_or(name)
}
static PART_POOL_MEMBERS: Lazy<HashMap<i64, HashSet<String>>> = Lazy::new(|| {
let mut pools: HashMap<i64, HashSet<String>> = HashMap::new();
for line in PART_POOLS_TSV.lines().skip(1) {
let mut cols = line.splitn(2, '\t');
let Some(cat) = cols.next().and_then(|s| s.parse::<i64>().ok()) else {
continue;
};
let Some(name) = cols.next() else { continue };
pools
.entry(cat)
.or_default()
.insert(normalize_part_name(name).to_string());
}
pools
});
static MANUFACTURERS: Lazy<HashMap<String, String>> = Lazy::new(|| {
let mfrs: HashMap<String, Manufacturer> =
serde_json::from_str(MANUFACTURERS_JSON).expect("Failed to parse manufacturers.json");
mfrs.into_iter().map(|(code, m)| (code, m.name)).collect()
});
static WEAPON_TYPES: Lazy<HashMap<String, Vec<String>>> = Lazy::new(|| {
let types: HashMap<String, WeaponType> =
serde_json::from_str(WEAPON_TYPES_JSON).expect("Failed to parse weapon_types.json");
types
.into_iter()
.map(|(name, wt)| {
let codes: Vec<String> = wt.manufacturers.into_iter().map(|m| m.code).collect();
(name, codes)
})
.collect()
});
static ITEM_NAMES: Lazy<HashMap<String, String>> = Lazy::new(|| {
ITEM_NAMES_TSV
.lines()
.filter_map(|line| {
let mut cols = line.splitn(2, '\t');
let key = cols.next()?.to_string();
let name = cols.next()?.to_string();
Some((key, name))
})
.collect()
});
pub fn category_name(category_id: i64) -> Option<&'static str> {
CATEGORY_NAMES.get(&category_id).map(|s| s.as_str())
}
pub fn part_name(category: i64, index: i64) -> Option<&'static str> {
PARTS_BY_ID
.get(&(category, index))
.map(|(name, _)| name.as_str())
}
pub fn part_slot(category: i64, index: i64) -> Option<&'static str> {
PARTS_BY_ID
.get(&(category, index))
.map(|(_, slot)| slot.as_str())
}
pub fn manufacturer_name(code: &str) -> Option<&'static str> {
MANUFACTURERS.get(code).map(|s| s.as_str())
}
pub fn drop_pool(manufacturer_code: &str, gear_type_code: &str) -> Option<&'static DropPool> {
DROP_POOLS.get(&(manufacturer_code.to_string(), gear_type_code.to_string()))
}
pub fn is_part_in_pool(category: i64, name: &str) -> Option<bool> {
let pool = PART_POOL_MEMBERS.get(&category)?;
Some(pool.contains(normalize_part_name(name)))
}
pub fn world_pool_legendary_count(world_pool_name: &str) -> u32 {
DROP_POOLS
.values()
.filter(|p| p.world_pool_name == world_pool_name)
.map(|p| p.legendary_count)
.sum()
}
pub fn boss_display_name(internal_name: &str) -> Option<&'static str> {
BOSS_NAMES.get(internal_name).map(|s| s.as_str())
}
pub fn all_boss_names() -> &'static HashMap<String, String> {
&BOSS_NAMES
}
pub fn item_display_name(np_key: &str) -> Option<&'static str> {
ITEM_NAMES.get(np_key).map(|s| s.as_str())
}
pub fn item_name_from_part(part_name: &str, category: Option<i64>) -> Option<&'static str> {
if let Some(suffix) = part_name.strip_prefix("comp_05_legendary_") {
let key = format!("np_{}", suffix.to_lowercase());
if let Some(name) = item_display_name(&key) {
return Some(name);
}
}
if let Some(after_barrel) = part_name.strip_prefix("part_barrel_") {
if let Some(pos) = after_barrel.find('_') {
let suffix = &after_barrel[pos + 1..];
if suffix.len() > 1 && !suffix.chars().all(|c| c.is_ascii_digit()) {
let key = format!("np_{}", suffix.to_lowercase());
if let Some(name) = item_display_name(&key) {
return Some(name);
}
}
}
if let Some(cat) = category {
if let Some(ncs_prefix) = category_ncs_prefix(cat) {
let barrel_num = if after_barrel == "01" || after_barrel == "02" {
after_barrel
} else {
let num_end = after_barrel.find('_').unwrap_or(after_barrel.len());
&after_barrel[..num_end]
};
if !barrel_num.is_empty() && barrel_num.chars().all(|c| c.is_ascii_digit()) {
let key = format!("np_weap_{}_b{}", ncs_prefix, barrel_num);
if let Some(name) = item_display_name(&key) {
return Some(name);
}
}
}
}
}
None
}
fn category_ncs_prefix(category: i64) -> Option<&'static str> {
match category {
7 => Some("bor_sg"),
8 => Some("dad_sg"),
9 => Some("jak_sg"),
10 => Some("mal_sg"),
11 => Some("ted_sg"),
12 => Some("tor_sg"),
2 => Some("dad_ps"),
3 => Some("jak_ps"),
4 => Some("ord_ps"),
5 => Some("ted_ps"),
6 => Some("tor_ps"),
13 => Some("dad_ar"),
14 => Some("ted_ar"),
15 => Some("ord_ar"),
17 => Some("tor_ar"),
18 => Some("vla_ar"),
27 => Some("jak_ar"),
16 => Some("vla_sr"),
23 => Some("bor_sr"),
24 => Some("jak_sr"),
25 => Some("mal_sr"),
26 => Some("ord_sr"),
19 => Some("bor_sm"),
20 => Some("dad_sm"),
21 => Some("mal_sm"),
22 => Some("vla_sm"),
_ => None,
}
}
pub fn all_item_names() -> &'static HashMap<String, String> {
&ITEM_NAMES
}
pub fn weapon_type_manufacturers(weapon_type: &str) -> Option<&'static [String]> {
WEAPON_TYPES.get(weapon_type).map(|v| v.as_slice())
}
pub fn all_categories() -> impl Iterator<Item = (i64, &'static str)> {
CATEGORY_NAMES.iter().map(|(&id, name)| (id, name.as_str()))
}
pub fn all_manufacturers() -> impl Iterator<Item = (&'static str, &'static str)> {
MANUFACTURERS
.iter()
.map(|(code, name)| (code.as_str(), name.as_str()))
}
const SLOT_PREFIXES: &[&str] = &[
"secondary_elem",
"secondary_ammo",
"body_element",
"body_armor",
"body_bolt",
"body_energy",
"body_mag",
"barrel",
"body",
"firmware",
"foregrip",
"grip",
"mag",
"multi",
"passive",
"scope",
"secondary",
"shield",
"stat2",
"stat3",
"stat",
"underbarrel",
"unique",
];
pub fn slot_from_part_name(name: &str) -> &'static str {
let segment = name.split('.').next_back().unwrap_or(name);
if segment.starts_with("comp_") || segment.starts_with("base_comp_") {
return "rarity";
}
match segment {
"fire" | "cryo" | "shock" | "corrosive" | "radiation" | "sonic" => return "element",
_ => {}
}
if segment.starts_with("exosoldier_") {
return "class_mod";
}
let stripped = match segment.strip_prefix("part_") {
Some(rest) => rest,
None => return "unknown",
};
for prefix in SLOT_PREFIXES {
if let Some(rest) = stripped.strip_prefix(prefix) {
if rest.is_empty() || rest.starts_with('_') {
return prefix;
}
}
}
"unknown"
}
static MAX_PART_INDEX: Lazy<HashMap<i64, i64>> = Lazy::new(|| {
let mut max_by_cat: HashMap<i64, i64> = HashMap::new();
for &(cat, idx) in PARTS_BY_ID.keys() {
let entry = max_by_cat.entry(cat).or_insert(0);
if idx > *entry {
*entry = idx;
}
}
max_by_cat
});
pub fn max_part_index(category: i64) -> Option<i64> {
MAX_PART_INDEX.get(&category).copied()
}
pub const SHARED_VERTICAL_CATEGORIES: &[i64] = &[
10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008, 10009, 1, ];
pub fn part_index(category: i64, name: &str) -> Option<i64> {
let bare = normalize_part_name(name).to_string();
if let Some(&idx) = PARTS_BY_NAME.get(&(category, bare.clone())) {
return Some(idx);
}
for &shared_cat in SHARED_VERTICAL_CATEGORIES {
if shared_cat == category {
continue;
}
if let Some(&idx) = PARTS_BY_NAME.get(&(shared_cat, bare.clone())) {
return Some(idx);
}
}
None
}
pub fn category_part_count(category: i64) -> usize {
PARTS_BY_ID
.keys()
.filter(|(cat, _)| *cat == category)
.count()
}
pub fn legendary_barrel_alias(category: i64, barrel_base: &str) -> Option<&'static str> {
let target_prefix = format!("part_{}_", barrel_base);
PARTS_BY_ID
.iter()
.filter(|(&(cat, _), _)| cat == category)
.find_map(|(&(_, _), (name, _))| {
if name.starts_with(&target_prefix) && name.len() > target_prefix.len() {
let suffix = &name[target_prefix.len()..];
if suffix.len() == 1 && suffix.chars().all(|c| c.is_ascii_lowercase()) {
None
} else {
Some(name.as_str())
}
} else {
None
}
})
}
pub fn is_loaded() -> bool {
let _ = CATEGORY_NAMES.len();
let _ = PARTS_BY_ID.len();
let _ = MANUFACTURERS.len();
true
}
pub fn stats() -> ManifestStats {
ManifestStats {
categories: CATEGORY_NAMES.len(),
parts: PARTS_BY_ID.len(),
manufacturers: MANUFACTURERS.len(),
weapon_types: WEAPON_TYPES.len(),
}
}
#[derive(Debug, Clone)]
pub struct ManifestStats {
pub categories: usize,
pub parts: usize,
pub manufacturers: usize,
pub weapon_types: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_category_name() {
assert!(category_name(2).is_some()); assert!(category_name(9).is_some()); }
#[test]
fn test_part_name() {
let _ = part_name(2, 1);
}
#[test]
fn test_manufacturer_name() {
assert_eq!(manufacturer_name("JAK"), Some("Jakobs"));
assert_eq!(manufacturer_name("TOR"), Some("Torgue"));
assert_eq!(manufacturer_name("BOR"), Some("Ripper")); assert_eq!(manufacturer_name("XXX"), None);
}
#[test]
fn test_stats() {
let s = stats();
assert!(s.categories > 0);
assert!(s.manufacturers > 0);
}
#[test]
fn test_weapon_type_manufacturers() {
let pistol_mfrs = weapon_type_manufacturers("Pistols");
assert!(pistol_mfrs.is_some());
assert!(!pistol_mfrs.unwrap().is_empty());
let smg_mfrs = weapon_type_manufacturers("SMG");
assert!(smg_mfrs.is_some());
let shotgun_mfrs = weapon_type_manufacturers("Shotguns");
assert!(shotgun_mfrs.is_some());
assert!(weapon_type_manufacturers("LaserBlaster3000").is_none());
}
#[test]
fn test_all_categories() {
let categories: Vec<_> = all_categories().collect();
assert!(!categories.is_empty());
for (id, name) in &categories {
assert!(*id >= 0);
assert!(!name.is_empty());
}
}
#[test]
fn test_all_manufacturers() {
let manufacturers: Vec<_> = all_manufacturers().collect();
assert!(!manufacturers.is_empty());
let codes: Vec<&str> = manufacturers.iter().map(|(c, _)| *c).collect();
assert!(codes.contains(&"JAK")); assert!(codes.contains(&"TOR"));
for (code, name) in &manufacturers {
assert!(!code.is_empty());
assert!(!name.is_empty());
}
}
#[test]
fn test_is_loaded() {
assert!(is_loaded());
assert!(is_loaded());
}
#[test]
fn test_drop_pool() {
let pool = drop_pool("JAK", "PS");
assert!(pool.is_some());
let pool = pool.unwrap();
assert_eq!(pool.manufacturer_code, "JAK");
assert_eq!(pool.gear_type_code, "PS");
assert!(pool.legendary_count > 0);
assert_eq!(pool.world_pool_name, "Pistols");
}
#[test]
fn test_drop_pool_unknown() {
assert!(drop_pool("ZZZ", "XX").is_none());
}
#[test]
fn test_world_pool_legendary_count() {
let pistol_count = world_pool_legendary_count("Pistols");
assert!(pistol_count > 0);
assert!(world_pool_legendary_count("Nonexistent") == 0);
}
#[test]
fn test_max_part_index() {
let max = max_part_index(2);
assert!(max.is_some());
assert!(max.unwrap() > 0);
assert!(max_part_index(99999).is_none());
}
#[test]
fn test_category_part_count() {
let count = category_part_count(2);
assert!(count > 0);
assert_eq!(category_part_count(99999), 0);
}
#[test]
fn test_part_slot() {
if let Some(slot) = part_slot(2, 1) {
assert!(!slot.is_empty());
}
}
#[test]
fn test_slot_from_part_name() {
assert_eq!(slot_from_part_name("DAD_PS.part_barrel_01"), "barrel");
assert_eq!(slot_from_part_name("part_barrel_02_finnty"), "barrel");
assert_eq!(
slot_from_part_name("part_barrel_licensed_ted_shooting"),
"barrel"
);
assert_eq!(slot_from_part_name("part_scope_02"), "scope");
assert_eq!(slot_from_part_name("part_body"), "body");
assert_eq!(slot_from_part_name("part_body_b"), "body");
assert_eq!(slot_from_part_name("part_body_mag_sg"), "body_mag");
assert_eq!(slot_from_part_name("JAK_SG.part_foregrip_03"), "foregrip");
assert_eq!(slot_from_part_name("part_mag_1"), "mag");
assert_eq!(slot_from_part_name("part_barrel"), "barrel");
assert_eq!(slot_from_part_name("part_grip_04_hyp"), "grip");
assert_eq!(slot_from_part_name("part_stat2_wt_ps_equipspeed"), "stat2");
assert_eq!(
slot_from_part_name("part_stat3_statuseffect_chance"),
"stat3"
);
assert_eq!(slot_from_part_name("comp_05_legendary_stopgap"), "rarity");
assert_eq!(slot_from_part_name("base_comp_02_uncommon"), "rarity");
assert_eq!(slot_from_part_name("comp_03_rare"), "rarity");
assert_eq!(slot_from_part_name("radiation"), "element");
assert_eq!(slot_from_part_name("cryo"), "element");
assert_eq!(slot_from_part_name("part_firmware_baker"), "firmware");
assert_eq!(
slot_from_part_name("part_passive_blue_3_1_tier_1"),
"passive"
);
assert_eq!(
slot_from_part_name("part_secondary_ammo_sg"),
"secondary_ammo"
);
assert_eq!(
slot_from_part_name("part_secondary_elem_cryo_fire"),
"secondary_elem"
);
assert_eq!(slot_from_part_name("part_shield_ammo"), "shield");
assert_eq!(
slot_from_part_name("part_underbarrel_04_atlas_ball"),
"underbarrel"
);
}
#[test]
fn test_normalize_part_name() {
assert_eq!(
normalize_part_name("DAD_PS.part_barrel_01"),
"part_barrel_01"
);
assert_eq!(normalize_part_name("part_body"), "part_body");
assert_eq!(normalize_part_name("comp_01_common"), "comp_01_common");
assert_eq!(normalize_part_name("BOR_REPAIR_KIT.part_borg"), "part_borg");
}
#[test]
fn test_is_part_in_pool_known_category() {
let result = is_part_in_pool(2, "part_barrel_01");
assert!(result.is_some(), "Category 2 should have pool data");
}
#[test]
fn test_is_part_in_pool_unknown_category() {
assert!(is_part_in_pool(99999, "part_body").is_none());
}
#[test]
fn test_is_part_in_pool_normalizes_prefix() {
let result = is_part_in_pool(2, "DAD_PS.part_barrel_01");
assert!(result.is_some());
if let Some(found) = result {
assert!(found, "DAD_PS.part_barrel_01 should be in category 2 pool");
}
}
#[test]
fn test_part_index_reverse_lookup() {
if let Some(name) = part_name(2, 7) {
let idx = part_index(2, name);
assert_eq!(idx, Some(7), "Reverse lookup for '{}' in category 2", name);
}
}
#[test]
fn test_part_index_unknown() {
assert!(part_index(2, "nonexistent_part_xyz").is_none());
}
#[test]
fn test_part_index_normalizes_prefix() {
if let Some(name) = part_name(2, 7) {
let prefixed = format!("MFR_PS.{}", name);
let idx = part_index(2, &prefixed);
assert_eq!(idx, Some(7), "Should normalize prefix for '{}'", prefixed);
}
}
#[test]
fn test_part_pool_stats() {
let total_categories = PART_POOL_MEMBERS.len();
assert!(
total_categories > 50,
"Expected 50+ categories, got {}",
total_categories
);
}
}