2024-08-23 11:22:09 +00:00
|
|
|
use anyhow::Result;
|
2024-08-24 07:01:49 +00:00
|
|
|
use engine::prelude::*;
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2024-08-24 07:01:49 +00:00
|
|
|
use crate::{
|
|
|
|
components::{
|
|
|
|
attributes::Attributes,
|
|
|
|
inventory::Storable,
|
|
|
|
statistic_types::{
|
|
|
|
AgilityStatisticTypes, IntelligenceStatisticTypes, StatisticType,
|
|
|
|
StrengthStatisticTypes,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
config::items::ItemSettings,
|
|
|
|
};
|
2024-08-23 11:22:09 +00:00
|
|
|
|
2024-08-24 07:01:49 +00:00
|
|
|
use super::{ItemSlots, ItemSystem, Jewel, Rarities, ToolTipBuilder, Tooltip};
|
2024-08-23 11:22:09 +00:00
|
|
|
|
2024-08-25 11:48:16 +00:00
|
|
|
const ITEM_SNIPPETS: [&'static str; 8] = [
|
|
|
|
include_str!("../../resources/items/slots_0.xml"),
|
|
|
|
include_str!("../../resources/items/slots_1.xml"),
|
|
|
|
include_str!("../../resources/items/slots_2.xml"),
|
|
|
|
include_str!("../../resources/items/slots_3.xml"),
|
|
|
|
include_str!("../../resources/items/slots_4.xml"),
|
|
|
|
include_str!("../../resources/items/slots_5.xml"),
|
|
|
|
include_str!("../../resources/items/slots_6.xml"),
|
|
|
|
include_str!("../../resources/items/slots_7.xml"),
|
|
|
|
];
|
|
|
|
|
2024-08-23 11:22:09 +00:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
|
|
pub enum ItemAffix {
|
|
|
|
Socket(Option<Jewel>),
|
|
|
|
Stat(StatisticType),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ItemAffix {
|
|
|
|
pub fn from_persistent<'a>(
|
|
|
|
affix_type: &str,
|
|
|
|
split: &mut impl Iterator<Item = &'a str>,
|
|
|
|
) -> Result<Self> {
|
|
|
|
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<Item = &'a Self>) -> (Vec<StatisticType>, Vec<Option<Jewel>>) {
|
|
|
|
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<StatisticType>) {
|
|
|
|
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<Self> {
|
|
|
|
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<ItemAffix>,
|
|
|
|
|
|
|
|
pub icon: Arc<Image>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Item {
|
|
|
|
const INSPECTOR_OFFSET: usize = 3;
|
|
|
|
|
|
|
|
pub fn empty(rarity: Rarities, slot: ItemSlots, level: u32, icon: Arc<Image>) -> 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 = &'a str>,
|
|
|
|
item_system: &ItemSystem,
|
|
|
|
) -> Result<Self> {
|
|
|
|
let slot = ItemSlots::from_str(split.next().unwrap())?;
|
|
|
|
let rarity = Rarities::from_str(split.next().unwrap())?;
|
|
|
|
let level = split.next().unwrap().parse::<u32>()?;
|
|
|
|
let strength = split.next().unwrap().parse::<u32>()?;
|
|
|
|
let agility = split.next().unwrap().parse::<u32>()?;
|
|
|
|
let intelligence = split.next().unwrap().parse::<u32>()?;
|
|
|
|
|
|
|
|
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<StatisticType> {
|
|
|
|
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,
|
2024-08-24 07:01:49 +00:00
|
|
|
tooltip_builder: &impl ToolTipBuilder,
|
2024-08-23 11:22:09 +00:00
|
|
|
attributes: &Attributes,
|
|
|
|
position: (i32, i32),
|
|
|
|
) -> Result<Tooltip> {
|
|
|
|
let (stats, jewels) = ItemAffix::squash(self.affixes.iter());
|
|
|
|
let count = stats.len() + jewels.len();
|
|
|
|
|
2024-08-25 11:48:16 +00:00
|
|
|
let inspector_snippet =
|
|
|
|
GuiBuilder::from_str(&tooltip_builder.gui_handler(), &ITEM_SNIPPETS[count])?;
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
let item_icon: Arc<Icon> = inspector_snippet.element("item_icon")?;
|
|
|
|
let slot_label: Arc<Label> = inspector_snippet.element("slot_label")?;
|
|
|
|
let level_label: Arc<Label> = inspector_snippet.element("level_label")?;
|
|
|
|
let rarity_label: Arc<Label> = inspector_snippet.element("rarity_label")?;
|
|
|
|
let strength_field: Arc<Label> = inspector_snippet.element("strength_field")?;
|
|
|
|
let agility_field: Arc<Label> = inspector_snippet.element("agility_field")?;
|
|
|
|
let intelligence_field: Arc<Label> = inspector_snippet.element("intelligence_field")?;
|
|
|
|
let inspector_grid: Arc<Grid> = inspector_snippet.element("item_grid")?;
|
|
|
|
|
|
|
|
inspector_grid.change_position_unscaled(position.0, position.1)?;
|
|
|
|
|
|
|
|
item_icon.set_icon(&self.icon)?;
|
|
|
|
slot_label.set_text(&format!("{}", self.slot))?;
|
|
|
|
level_label.set_text(&format!("Lvl {}", self.level))?;
|
|
|
|
rarity_label.set_text(&format!("{}", self.rarity))?;
|
|
|
|
strength_field.set_text(&format!("{}", self.attributes.strength().raw()))?;
|
|
|
|
agility_field.set_text(&format!("{}", self.attributes.agility().raw()))?;
|
|
|
|
intelligence_field.set_text(&format!("{}", self.attributes.intelligence().raw()))?;
|
|
|
|
|
|
|
|
if attributes.strength().raw() < self.attributes.strength().raw() {
|
|
|
|
strength_field.set_background(Color::try_from("#AB7474")?)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if attributes.agility().raw() < self.attributes.agility().raw() {
|
|
|
|
agility_field.set_background(Color::try_from("#AB7474")?)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if attributes.intelligence().raw() < self.attributes.intelligence().raw() {
|
|
|
|
intelligence_field.set_background(Color::try_from("#AB7474")?)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut index = Self::INSPECTOR_OFFSET;
|
|
|
|
|
|
|
|
for stat in stats {
|
2024-08-25 11:48:16 +00:00
|
|
|
let stat_type_snippet = GuiSnippet::from_str(
|
2024-08-25 09:44:44 +00:00
|
|
|
&tooltip_builder.gui_handler(),
|
2024-08-25 11:48:16 +00:00
|
|
|
include_str!("../../resources/stat_type_snippet.xml"),
|
2024-08-24 07:01:49 +00:00
|
|
|
)?;
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
let stat_type_label: Arc<Label> = stat_type_snippet.element("stat_type")?;
|
|
|
|
let stat_value_label: Arc<Label> = stat_type_snippet.element("stat_value")?;
|
|
|
|
|
|
|
|
stat_type_label.set_text(&format!("{}", stat))?;
|
|
|
|
stat_value_label.set_text(&stat.display_value())?;
|
|
|
|
|
|
|
|
inspector_grid.attach(stat_type_snippet, 0, index, 1, 1)?;
|
|
|
|
|
|
|
|
index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for jewel in jewels {
|
2024-08-25 11:48:16 +00:00
|
|
|
let socket_snippet = GuiSnippet::from_str(
|
2024-08-25 09:44:44 +00:00
|
|
|
&tooltip_builder.gui_handler(),
|
2024-08-25 11:48:16 +00:00
|
|
|
include_str!("../../resources/item_socket_snippet.xml"),
|
2024-08-24 07:01:49 +00:00
|
|
|
)?;
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
let socket_icon: Arc<Icon> = socket_snippet.element("socket_icon")?;
|
|
|
|
let stat_type: Arc<Label> = socket_snippet.element("stat_type")?;
|
|
|
|
|
|
|
|
match jewel {
|
|
|
|
Some(jewel) => {
|
|
|
|
socket_icon.set_icon(&jewel.icon())?;
|
|
|
|
stat_type.set_text(format!("{}", jewel.stat))?;
|
|
|
|
}
|
|
|
|
None => stat_type.set_text("Empty Socket")?,
|
|
|
|
}
|
|
|
|
|
|
|
|
inspector_grid.attach(socket_snippet, 0, index, 1, 1)?;
|
|
|
|
|
|
|
|
index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Tooltip::new(
|
|
|
|
inspector_grid,
|
|
|
|
inspector_snippet,
|
2024-08-24 07:01:49 +00:00
|
|
|
tooltip_builder.gui_handler().clone(),
|
2024-08-23 11:22:09 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Item {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.rarity == other.rarity
|
|
|
|
&& self.slot == other.slot
|
|
|
|
&& self.level == other.level
|
|
|
|
&& self.attributes == other.attributes
|
|
|
|
&& self.affixes == other.affixes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Storable for Item {
|
|
|
|
fn rarity(&self) -> Rarities {
|
|
|
|
self.rarity
|
|
|
|
}
|
|
|
|
|
|
|
|
fn icon(&self) -> Arc<Image> {
|
|
|
|
self.icon.clone()
|
|
|
|
}
|
|
|
|
}
|