use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use super::{stat_descriptions, ManifestIndex};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RarityTier {
pub tier: u8,
pub code: String,
pub name: String,
pub color: String,
}
pub fn rarity_tiers() -> Vec<RarityTier> {
bl4::reference::RARITY_TIERS
.iter()
.map(|r| RarityTier {
tier: r.tier,
code: r.code.to_string(),
name: r.name.to_string(),
color: r.color.to_string(),
})
.collect()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElementType {
pub code: String,
pub name: String,
pub description: String,
pub color: String,
}
pub fn element_types() -> Vec<ElementType> {
bl4::reference::ELEMENT_TYPES
.iter()
.map(|e| ElementType {
code: e.code.to_string(),
name: e.name.to_string(),
description: e.description.to_string(),
color: e.color.to_string(),
})
.collect()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LegendaryItem {
pub internal: String,
pub name: String,
pub weapon_type: String,
pub manufacturer: String,
}
pub fn known_legendaries() -> Vec<LegendaryItem> {
bl4::reference::KNOWN_LEGENDARIES
.iter()
.map(|l| LegendaryItem {
internal: l.internal.to_string(),
name: l.name.to_string(),
weapon_type: l.weapon_type.to_string(),
manufacturer: l.manufacturer.to_string(),
})
.collect()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeaponTypeInfo {
pub code: String,
pub name: String,
pub description: String,
}
pub fn weapon_type_info() -> Vec<WeaponTypeInfo> {
bl4::reference::WEAPON_TYPES
.iter()
.map(|w| WeaponTypeInfo {
code: w.code.to_string(),
name: w.name.to_string(),
description: w.description.to_string(),
})
.collect()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManufacturerInfo {
pub code: String,
pub name: String,
pub weapon_types: Vec<String>,
pub style: String,
}
pub fn manufacturer_info() -> Vec<ManufacturerInfo> {
bl4::reference::MANUFACTURERS
.iter()
.map(|m| ManufacturerInfo {
code: m.code.to_string(),
name: m.name.to_string(),
weapon_types: m.weapon_types.iter().map(|s| s.to_string()).collect(),
style: m.style.to_string(),
})
.collect()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GearTypeInfo {
pub code: String,
pub name: String,
pub description: String,
}
pub fn gear_type_info() -> Vec<GearTypeInfo> {
bl4::reference::GEAR_TYPES
.iter()
.map(|g| GearTypeInfo {
code: g.code.to_string(),
name: g.name.to_string(),
description: g.description.to_string(),
})
.collect()
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConsolidatedManifest {
pub version: String,
pub game: String,
pub description: String,
pub manufacturers: Vec<ManufacturerInfo>,
pub weapon_types: Vec<WeaponTypeInfo>,
pub gear_types: Vec<GearTypeInfo>,
pub rarities: Vec<RarityTier>,
pub elements: Vec<ElementType>,
pub stats: HashMap<String, String>,
pub legendaries: Vec<LegendaryItem>,
}
pub fn generate_reference_manifest(output_dir: &Path) -> Result<()> {
fs::create_dir_all(output_dir).context("Failed to create output directory")?;
println!("Generating consolidated reference manifest (HARDCODED - NOT AUTHORITATIVE)...");
let readme = r#"# Reference Data
WARNING: The files in this directory contain HARDCODED REFERENCE DATA.
They are NOT extracted from game files and should NOT be used in implementation.
These files exist to:
- Document known game data structures
- Provide a starting point for extraction work
- Allow quick prototyping before proper extraction
For authoritative data, use files in the parent directory (share/manifest/)
which are generated by extraction commands that read actual game files.
## Files
- manufacturers.json - Known manufacturer codes and names (HARDCODED)
- weapon_types.json - Weapon type codes (HARDCODED)
- gear_types.json - Gear type codes (HARDCODED)
- rarities.json - Rarity tiers with colors (HARDCODED)
- elements.json - Element types (HARDCODED)
- stats.json - Stat property names (HARDCODED)
- legendaries.json - Known legendary items (HARDCODED)
- reference.json - All above consolidated (HARDCODED)
- parts_database_spreadsheet.json - Community spreadsheet data (EXTERNAL)
"#;
fs::write(output_dir.join("README.md"), readme)?;
println!(" README.md - documentation");
let stats: HashMap<String, String> = stat_descriptions()
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let manifest = ConsolidatedManifest {
version: env!("CARGO_PKG_VERSION").to_string(),
game: "Borderlands 4".to_string(),
description: "REFERENCE DATA ONLY - Hardcoded, not extracted from game".to_string(),
manufacturers: manufacturer_info(),
weapon_types: weapon_type_info(),
gear_types: gear_type_info(),
rarities: rarity_tiers(),
elements: element_types(),
stats,
legendaries: known_legendaries(),
};
let mfr_path = output_dir.join("manufacturers.json");
fs::write(
&mfr_path,
serde_json::to_string_pretty(&manifest.manufacturers)?,
)?;
println!(
" manufacturers.json - {} entries",
manifest.manufacturers.len()
);
let wt_path = output_dir.join("weapon_types.json");
fs::write(
&wt_path,
serde_json::to_string_pretty(&manifest.weapon_types)?,
)?;
println!(
" weapon_types.json - {} entries",
manifest.weapon_types.len()
);
let gt_path = output_dir.join("gear_types.json");
fs::write(
>_path,
serde_json::to_string_pretty(&manifest.gear_types)?,
)?;
println!(" gear_types.json - {} entries", manifest.gear_types.len());
let rarity_path = output_dir.join("rarities.json");
fs::write(
&rarity_path,
serde_json::to_string_pretty(&manifest.rarities)?,
)?;
println!(" rarities.json - {} entries", manifest.rarities.len());
let elem_path = output_dir.join("elements.json");
fs::write(
&elem_path,
serde_json::to_string_pretty(&manifest.elements)?,
)?;
println!(" elements.json - {} entries", manifest.elements.len());
let stats_path = output_dir.join("stats.json");
fs::write(&stats_path, serde_json::to_string_pretty(&manifest.stats)?)?;
println!(" stats.json - {} entries", manifest.stats.len());
let leg_path = output_dir.join("legendaries.json");
fs::write(
&leg_path,
serde_json::to_string_pretty(&manifest.legendaries)?,
)?;
println!(
" legendaries.json - {} entries",
manifest.legendaries.len()
);
let consolidated_path = output_dir.join("reference.json");
fs::write(&consolidated_path, serde_json::to_string_pretty(&manifest)?)?;
println!(" reference.json - consolidated reference data");
let mut files = HashMap::new();
files.insert(
"manufacturers".to_string(),
"manufacturers.json".to_string(),
);
files.insert("weapon_types".to_string(), "weapon_types.json".to_string());
files.insert("gear_types".to_string(), "gear_types.json".to_string());
files.insert("rarities".to_string(), "rarities.json".to_string());
files.insert("elements".to_string(), "elements.json".to_string());
files.insert("stats".to_string(), "stats.json".to_string());
files.insert("legendaries".to_string(), "legendaries.json".to_string());
files.insert("reference".to_string(), "reference.json".to_string());
let index = ManifestIndex {
version: env!("CARGO_PKG_VERSION").to_string(),
source: "HARDCODED REFERENCE DATA - NOT EXTRACTED FROM GAME".to_string(),
extract_path: output_dir.to_string_lossy().to_string(),
files,
};
let index_path = output_dir.join("index.json");
fs::write(&index_path, serde_json::to_string_pretty(&index)?)?;
println!(" index.json");
println!("\nReference manifest saved to {:?}", output_dir);
println!("WARNING: This is REFERENCE DATA ONLY - do not use in implementation!");
Ok(())
}