use crate::abi::{AffixDefData, AffixModifierData, Header, Str};
use crate::content::categories::ItemCategoryMask;
use crate::stats::{Op, Stat};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ItemRarity {
#[default]
Common,
Uncommon,
Rare,
Epic,
Legendary,
}
impl ItemRarity {
#[must_use]
pub const fn to_raw(self) -> u8 {
self as u8
}
#[must_use]
pub const fn from_raw(raw: u8) -> Option<Self> {
match raw {
0 => Some(Self::Common),
1 => Some(Self::Uncommon),
2 => Some(Self::Rare),
3 => Some(Self::Epic),
4 => Some(Self::Legendary),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Range {
pub stat: Stat,
pub op: Op,
pub min: f32,
pub max: f32,
pub step: f32,
}
impl Range {
#[must_use]
pub const fn new(stat: Stat, op: Op, min: f32, max: f32) -> Self {
Self {
stat,
op,
min,
max,
step: 0.0,
}
}
#[must_use]
pub const fn step(mut self, step: f32) -> Self {
self.step = step;
self
}
pub(super) fn at(&self, unit: f32) -> f32 {
let raw = self.min + unit * (self.max - self.min);
let value = if self.step > 0.0 {
self.min + ((raw - self.min) / self.step).round() * self.step
} else {
raw
};
value.clamp(self.min.min(self.max), self.max.max(self.min))
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct AffixDef {
pub text: String,
pub ranges: Vec<Range>,
pub weight: u32,
pub rarity: ItemRarity,
pub categories: ItemCategoryMask,
pub group: Option<String>,
}
impl AffixDef {
#[must_use]
pub fn new(text: &str, categories: ItemCategoryMask) -> Self {
Self {
text: text.to_owned(),
ranges: Vec::new(),
weight: 100,
rarity: ItemRarity::Common,
categories,
group: None,
}
}
#[must_use]
pub fn with(mut self, range: Range) -> Self {
self.ranges.push(range);
self
}
#[must_use]
pub const fn weight(mut self, weight: u32) -> Self {
self.weight = weight;
self
}
#[must_use]
pub const fn rarity(mut self, rarity: ItemRarity) -> Self {
self.rarity = rarity;
self
}
#[must_use]
pub fn group(mut self, group: &str) -> Self {
self.group = Some(group.to_owned());
self
}
pub(super) fn check(&self) -> Result<(), String> {
if self.ranges.is_empty() || self.weight == 0 || self.categories.0 == 0 {
return Err("needs a modifier, a weight and a category".to_owned());
}
let bad = self.ranges.iter().any(|r| {
!(r.min.is_finite() && r.max.is_finite() && r.step.is_finite())
|| r.min > r.max
|| r.step < 0.0
});
if bad {
return Err("a range is empty or not finite".to_owned());
}
Ok(())
}
#[must_use]
pub fn to_data(&self, ranges: &[AffixModifierData]) -> AffixDefData {
AffixDefData {
header: Header::of::<AffixDefData>(),
text: Str::new(&self.text),
group: self.group.as_deref().map_or(Str::EMPTY, Str::new),
modifiers: ranges.as_ptr(),
modifier_count: u32::try_from(ranges.len()).unwrap_or(u32::MAX),
weight: self.weight,
categories: self.categories.0,
rarity: self.rarity.to_raw(),
reserved: [0; 3],
}
}
#[must_use]
pub unsafe fn from_data(
data: &AffixDefData,
stat: impl Fn(u32) -> Option<Stat>,
) -> Option<Self> {
let text = unsafe { data.text.as_str() }?.to_owned();
let group = unsafe { data.group.as_str() }?;
let ranges = if data.modifier_count == 0 {
&[][..]
} else if data.modifiers.is_null() {
return None;
} else {
unsafe { core::slice::from_raw_parts(data.modifiers, data.modifier_count as usize) }
};
let def = Self {
text,
ranges: ranges
.iter()
.map(|r| {
Some(Range {
stat: stat(r.stat)?,
op: Op::from_raw(r.op)?,
min: r.min,
max: r.max,
step: r.step,
})
})
.collect::<Option<_>>()?,
weight: data.weight,
rarity: ItemRarity::from_raw(data.rarity)?,
categories: ItemCategoryMask(data.categories),
group: (!group.is_empty()).then(|| group.to_owned()),
};
def.check().is_ok().then_some(def)
}
#[must_use]
pub fn to_ranges(&self) -> Vec<AffixModifierData> {
self.ranges
.iter()
.map(|r| AffixModifierData {
stat: r.stat.index(),
op: r.op.to_raw(),
reserved: [0; 3],
min: r.min,
max: r.max,
step: r.step,
})
.collect()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AffixId(u32);
crate::abi::id_conversions!(AffixId(u32));
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
)]
pub struct AffixKey(pub u32);
impl AffixKey {
#[must_use]
pub const fn of(id: &str) -> Self {
Self(crate::stats::key_of(id).0)
}
}