use anyhow::Result; use engine::prelude::*; use std::str::FromStr; use std::sync::Arc; use crate::{ components::{ attributes::Attributes, inventory::Storable, statistic_types::{ AgilityStatisticTypes, IntelligenceStatisticTypes, StatisticType, StrengthStatisticTypes, }, }, config::items::ItemSettings, }; use super::{ItemSlots, ItemSystem, Jewel, Rarities, ToolTipBuilder, Tooltip}; #[derive(Debug, PartialEq, Clone)] pub enum ItemAffix { Socket(Option), Stat(StatisticType), } impl ItemAffix { pub fn from_persistent<'a>( affix_type: &str, split: &mut impl Iterator, ) -> Result { Ok(match affix_type { "socket" => { let socket_content = split.next().unwrap(); match socket_content { "empty" => ItemAffix::Socket(None), "some" => ItemAffix::Socket(Some(Jewel::from_persistent(split)?)), _ => unreachable!(), } } "stat" => ItemAffix::Stat(StatisticType::from_str(split.next().unwrap())?), _ => unreachable!(), }) } fn squash<'a>(v: impl Iterator) -> (Vec, Vec>) { let mut jewels = Vec::new(); let mut stats = Vec::new(); for affix in v { match affix { ItemAffix::Socket(j) => jewels.push(j.clone()), ItemAffix::Stat(s) => Self::insert_stat(s, &mut stats), } } (stats, jewels) } fn insert_stat(val: &StatisticType, vec: &mut Vec) { match vec .iter_mut() .find(|stat| StatisticType::same_type(stat, val)) { Some(stat) => { *stat += val.clone(); } None => vec.push(val.clone()), } } } impl std::fmt::Display for ItemAffix { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ItemAffix::Socket(jewel) => match jewel { Some(jewel) => write!(f, "socket|some|{}", jewel.into_persistent()), None => write!(f, "socket|empty"), }, ItemAffix::Stat(stat) => write!(f, "stat|{}", stat), } } } impl FromStr for ItemAffix { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut split = s.split('|'); let affix_type = split.next().unwrap(); Self::from_persistent(affix_type, &mut split) } } #[derive(Debug, Clone)] pub struct Item { pub rarity: Rarities, pub slot: ItemSlots, pub level: u32, pub attributes: Attributes, pub affixes: Vec, pub icon: Arc, } impl Item { const INSPECTOR_OFFSET: usize = 3; pub fn empty(rarity: Rarities, slot: ItemSlots, level: u32, icon: Arc) -> Self { Self { rarity, slot, level, attributes: Attributes::zero(), affixes: Vec::new(), icon, } } pub fn into_persistent(&self) -> String { let mut base = format!( "{}|{}|{}|{}|{}|{}", self.slot, self.rarity, self.level, self.attributes.strength().raw(), self.attributes.agility().raw(), self.attributes.intelligence().raw(), ); for affix in self.affixes.iter() { base = format!("{base}|{affix}"); } base } pub fn from_persistent<'a>( mut split: impl Iterator, item_system: &ItemSystem, ) -> Result { let slot = ItemSlots::from_str(split.next().unwrap())?; let rarity = Rarities::from_str(split.next().unwrap())?; let level = split.next().unwrap().parse::()?; let strength = split.next().unwrap().parse::()?; let agility = split.next().unwrap().parse::()?; let intelligence = split.next().unwrap().parse::()?; let mut affixes = Vec::new(); while let Some(affix_type) = split.next() { affixes.push({ let mut affix = ItemAffix::from_persistent(affix_type, &mut split)?; if let ItemAffix::Socket(Some(jewel)) = &mut affix { jewel.icon = Some(item_system.jewel_icon(jewel.rarity, jewel.level, jewel.attribute)); } affix }); } let attributes = Attributes::load(strength, agility, intelligence); Ok(item_system.item(rarity, slot, level, attributes, affixes)) } pub fn randomize_attributes(&mut self, item_settings: &ItemSettings) { // sum of stats is the level self.attributes .randomize((self.level as f32 * item_settings.general.attribute_multiplier) as u32); } fn stat_type(&self, attribute_number: u32) -> Option { if attribute_number <= self.attributes.strength().raw() && self.attributes.strength().raw() != 0 { return Some(StrengthStatisticTypes::random().into()); } else if attribute_number <= (self.attributes.strength().raw() + self.attributes.agility().raw()) && self.attributes.agility().raw() != 0 { return Some(AgilityStatisticTypes::random().into()); } else if self.attributes.intelligence().raw() != 0 { return Some(IntelligenceStatisticTypes::random().into()); } None } pub fn randomize_stats(&mut self, item_settings: &ItemSettings) { self.affixes = (0..item_settings.rarity_slot_settings.from_rarity(self.rarity)) .map(|_| { if Random::range_f32(0.0, 1.0) < 0.05 { ItemAffix::Socket(None) } else { let mut stat_type = None; while stat_type.is_none() { stat_type = self.stat_type(Random::range(0, self.attributes.sum())); } let mut stat_type = stat_type.unwrap(); stat_type.apply_for_item(&self.attributes, item_settings); ItemAffix::Stat(stat_type) } }) .collect(); } pub fn create_tooltip( &self, tooltip_builder: &impl ToolTipBuilder, attributes: &Attributes, position: (i32, i32), ) -> Result { let (stats, jewels) = ItemAffix::squash(self.affixes.iter()); let count = stats.len() + jewels.len(); let inspector_snippet = GuiBuilder::new( tooltip_builder.gui_handler(), &tooltip_builder.asset_path(&format!("gui/xml/ingame/loot/items/slots_{}.xml", count)), )?; let item_icon: Arc = inspector_snippet.element("item_icon")?; let slot_label: Arc