use std::collections::HashMap;
use std::fmt;
use crate::common::utils::csv;
#[derive(Debug)]
pub enum UnitBuyError {
EmptyFile,
}
impl fmt::Display for UnitBuyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyFile => write!(f, "The provided file bytes contained no valid unit buy data."),
}
}
}
impl std::error::Error for UnitBuyError {}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct UnitBuy {
pub stage_unlock_requirement: i32,
pub purchase_cost: i32,
pub currency_type: i32,
pub rarity: i32,
pub guide_order: i32,
pub chapter_unlock_requirement: i32,
pub sell_xp_yield: i32,
pub unknown_17: i32,
pub level_cap_ch2: i32,
pub base_max_plus_level: i32,
pub evolve_level_xp: i32,
pub unknown_21: i32,
pub level_cap_ch1: i32,
pub true_form_id: i32,
pub ultra_form_id: i32,
pub true_form_unlock_level: i32,
pub ultra_form_unlock_level: i32,
pub true_form_xp_cost: i32,
pub ultra_form_xp_cost: i32,
pub level_cap_standard: i32,
pub level_cap_catseye: i32,
pub level_cap_plus: i32,
pub normal_evolution_y_offset: i32,
pub evolved_evolution_y_offset: i32,
pub true_evolution_y_offset: i32,
pub ultra_evolution_y_offset: i32,
pub unknown_56: i32,
pub version_added: i64,
pub sell_np_yield: i32,
pub unknown_59: i32,
pub unknown_60: i32,
pub egg_id_normal: i32,
pub egg_id_evolved: i32,
pub rest: Vec<i32>,
pub upgrade_costs: Vec<i32>,
pub true_form_materials: Vec<(i32, i32)>,
pub ultra_form_materials: Vec<(i32, i32)>,
}
impl UnitBuy {
fn from_csv_line(csv_line: &str, delimiter: char) -> Option<Self> {
let parts: Vec<&str> = csv_line.split(delimiter).map(|s| s.trim()).collect();
let get_integer = |idx: usize| -> i32 {
parts.get(idx).and_then(|s| s.parse::<i32>().ok()).unwrap_or(-1)
};
let get_long = |idx: usize| -> i64 {
parts.get(idx).and_then(|s| s.parse::<i64>().ok()).unwrap_or(-1)
};
let parse_materials = |start_idx: usize| -> Vec<(i32, i32)> {
let mut material_list = Vec::new();
for i in 0..5 {
let base_idx = start_idx + (i * 2);
let item_id = get_integer(base_idx);
let item_cost = get_integer(base_idx + 1);
if item_id != -1 && item_cost > 0 {
material_list.push((item_id, item_cost));
}
}
material_list
};
let parse_upgrades = |start_idx: usize| -> Vec<i32> {
(0..10).map(|i| get_integer(start_idx + i)).collect()
};
let mut rest_vector = Vec::new();
if parts.len() > 63 {
for i in 63..parts.len() {
let Ok(parsed_value) = parts[i].parse::<i32>() else { continue; };
rest_vector.push(parsed_value);
}
}
Some(Self {
stage_unlock_requirement: get_integer(0),
purchase_cost: get_integer(1),
upgrade_costs: parse_upgrades(2),
currency_type: get_integer(12),
rarity: get_integer(13),
guide_order: get_integer(14),
chapter_unlock_requirement: get_integer(15),
sell_xp_yield: get_integer(16),
unknown_17: get_integer(17),
level_cap_ch2: get_integer(18),
base_max_plus_level: get_integer(19),
evolve_level_xp: get_integer(20),
unknown_21: get_integer(21),
level_cap_ch1: get_integer(22),
true_form_id: get_integer(23),
ultra_form_id: get_integer(24),
true_form_unlock_level: get_integer(25),
ultra_form_unlock_level: get_integer(26),
true_form_xp_cost: get_integer(27),
true_form_materials: parse_materials(28),
ultra_form_xp_cost: get_integer(38),
ultra_form_materials: parse_materials(39),
level_cap_standard: get_integer(49),
level_cap_catseye: get_integer(50),
level_cap_plus: get_integer(51),
normal_evolution_y_offset: get_integer(52),
evolved_evolution_y_offset: get_integer(53),
true_evolution_y_offset: get_integer(54),
ultra_evolution_y_offset: get_integer(55),
unknown_56: get_integer(56),
version_added: get_long(57),
sell_np_yield: get_integer(58),
unknown_59: get_integer(59),
unknown_60: get_integer(60),
egg_id_normal: get_integer(61),
egg_id_evolved: get_integer(62),
rest: rest_vector,
})
}
pub fn parse<B: AsRef<[u8]>>(bytes: B) -> Result<HashMap<u32, Self>, UnitBuyError> {
parse_inner(bytes.as_ref())
}
}
fn parse_inner(bytes: &[u8]) -> Result<HashMap<u32, UnitBuy>, UnitBuyError> {
let file_content = csv::scrub(bytes);
let delimiter = csv::detect_separator(&file_content);
let mut map = HashMap::new();
for (line_index, line) in file_content.lines().enumerate() {
if line.trim().is_empty() {
continue;
}
if let Some(row_data) = UnitBuy::from_csv_line(line, delimiter) {
map.insert(line_index as u32, row_data);
}
}
if map.is_empty() {
return Err(UnitBuyError::EmptyFile);
}
Ok(map)
}