use anyhow::{Context, Result};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use crate::manifest::PakManifest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedWeaponType {
pub internal_name: String,
pub code: String,
pub manufacturers: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub example_paths: Vec<String>,
}
pub fn extract_weapon_types_from_pak(
pak_manifest_path: &Path,
) -> Result<HashMap<String, ExtractedWeaponType>> {
let content =
fs::read_to_string(pak_manifest_path).context("Failed to read pak_manifest.json")?;
let manifest: PakManifest =
serde_json::from_str(&content).context("Failed to parse pak_manifest.json")?;
let mut weapon_types: HashMap<String, ExtractedWeaponType> = HashMap::new();
let weapon_path_pattern = Regex::new(r"/Gear/Weapons/([^_/][^/]*)/([A-Z]{3})/").unwrap();
let heavy_weapon_pattern = Regex::new(r"/Gear/Gadgets/HeavyWeapons/([A-Z]{3})/").unwrap();
let type_to_code: HashMap<&str, &str> = [
("AssaultRifles", "AR"),
("Pistols", "PS"),
("Shotguns", "SG"),
("SMG", "SM"),
("Sniper", "SR"),
("HeavyWeapons", "HW"),
]
.iter()
.cloned()
.collect();
for item in &manifest.items {
let path = &item.path;
if let Some(cap) = weapon_path_pattern.captures(path) {
let weapon_type = cap[1].to_string();
let mfr_code = cap[2].to_string();
if weapon_type.starts_with('_')
|| weapon_type == "Materials"
|| weapon_type == "Textures"
|| weapon_type == "Systems"
|| weapon_type == "Uniques"
{
continue;
}
let code = type_to_code
.get(weapon_type.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| {
weapon_type
.chars()
.take(2)
.collect::<String>()
.to_uppercase()
});
let wt =
weapon_types
.entry(weapon_type.clone())
.or_insert_with(|| ExtractedWeaponType {
internal_name: weapon_type.clone(),
code,
manufacturers: Vec::new(),
example_paths: Vec::new(),
});
if !wt.manufacturers.contains(&mfr_code) {
wt.manufacturers.push(mfr_code);
}
if wt.example_paths.len() < 3 {
wt.example_paths.push(path.clone());
}
}
if let Some(cap) = heavy_weapon_pattern.captures(path) {
let mfr_code = cap[1].to_string();
let wt = weapon_types
.entry("HeavyWeapons".to_string())
.or_insert_with(|| ExtractedWeaponType {
internal_name: "HeavyWeapons".to_string(),
code: "HW".to_string(),
manufacturers: Vec::new(),
example_paths: Vec::new(),
});
if !wt.manufacturers.contains(&mfr_code) {
wt.manufacturers.push(mfr_code);
}
if wt.example_paths.len() < 3 {
wt.example_paths.push(path.clone());
}
}
}
for wt in weapon_types.values_mut() {
wt.manufacturers.sort();
}
Ok(weapon_types)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedGearType {
pub internal_name: String,
pub manufacturers: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub subcategories: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub example_paths: Vec<String>,
}
pub fn extract_gear_types_from_pak(
pak_manifest_path: &Path,
) -> Result<HashMap<String, ExtractedGearType>> {
let content =
fs::read_to_string(pak_manifest_path).context("Failed to read pak_manifest.json")?;
let manifest: PakManifest =
serde_json::from_str(&content).context("Failed to parse pak_manifest.json")?;
let mut gear_types: HashMap<String, ExtractedGearType> = HashMap::new();
let gear_mfr_pattern = Regex::new(r"/Gear/([^_/][^/]*)/Manufacturer/([A-Z]{3})/").unwrap();
let gadget_pattern = Regex::new(r"/Gear/Gadgets/([^_/][^/]*)/([A-Z]{3})/").unwrap();
let gear_path_pattern = Regex::new(r"/Gear/([^_/][^/]*)/").unwrap();
for item in &manifest.items {
let path = &item.path;
if let Some(cap) = gear_mfr_pattern.captures(path) {
let gear_type = cap[1].to_string();
let mfr_code = cap[2].to_string();
let gear_type_normalized = if gear_type.to_lowercase() == "shields" {
"Shields".to_string()
} else {
gear_type
};
let gt = gear_types
.entry(gear_type_normalized.clone())
.or_insert_with(|| ExtractedGearType {
internal_name: gear_type_normalized.clone(),
manufacturers: Vec::new(),
subcategories: Vec::new(),
example_paths: Vec::new(),
});
if !gt.manufacturers.contains(&mfr_code) {
gt.manufacturers.push(mfr_code);
}
if gt.example_paths.len() < 3 {
gt.example_paths.push(path.clone());
}
}
if let Some(cap) = gadget_pattern.captures(path) {
let subcategory = cap[1].to_string();
let mfr_code = cap[2].to_string();
if subcategory == "HeavyWeapons" {
continue;
}
let gt = gear_types
.entry("Gadgets".to_string())
.or_insert_with(|| ExtractedGearType {
internal_name: "Gadgets".to_string(),
manufacturers: Vec::new(),
subcategories: Vec::new(),
example_paths: Vec::new(),
});
if !gt.subcategories.contains(&subcategory) {
gt.subcategories.push(subcategory);
}
if !gt.manufacturers.contains(&mfr_code) {
gt.manufacturers.push(mfr_code);
}
if gt.example_paths.len() < 3 {
gt.example_paths.push(path.clone());
}
}
if let Some(cap) = gear_path_pattern.captures(path) {
let gear_type = cap[1].to_string();
if gear_type.starts_with('_')
|| gear_type == "Weapons"
|| gear_type.to_lowercase() == "shields"
|| gear_type == "GrenadeGadgets"
|| gear_type == "Gadgets"
|| gear_type == "Effects"
{
continue;
}
let gt = gear_types
.entry(gear_type.clone())
.or_insert_with(|| ExtractedGearType {
internal_name: gear_type.clone(),
manufacturers: Vec::new(),
subcategories: Vec::new(),
example_paths: Vec::new(),
});
if gt.example_paths.len() < 3 && !gt.example_paths.contains(&path.clone()) {
gt.example_paths.push(path.clone());
}
}
}
for gt in gear_types.values_mut() {
gt.manufacturers.sort();
gt.subcategories.sort();
}
Ok(gear_types)
}