use crate::tf2::attributes::AttributeError;
use sdk_raw::tf2::attributes::INVALID_ATTRIB_DEF_INDEX;
use std::ffi::CStr;
use std::fmt::{self, Debug, Display, Formatter};
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Amount(f32);
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnyAttributeDef {
Amount(AttributeDef<Amount>),
Flag(AttributeDef<Flag>),
Multiplier(AttributeDef<Multiplier>),
Seconds(AttributeDef<Seconds>),
}
impl AnyAttributeDef {
pub const fn class(&self) -> &'static CStr {
self.raw().class
}
pub const fn format(&self) -> DescriptionFormat {
self.raw().format
}
pub const fn index(&self) -> AttributeIndex {
self.raw().index
}
pub const fn max(&self) -> f32 {
self.raw().max
}
pub const fn min(&self) -> f32 {
self.raw().min
}
pub const fn name(&self) -> &'static CStr {
self.raw().name
}
pub(super) const fn raw(&self) -> RawDef {
match self {
Self::Amount(def) => def.raw(),
Self::Flag(def) => def.raw(),
Self::Multiplier(def) => def.raw(),
Self::Seconds(def) => def.raw(),
}
}
pub fn stored(&self, value: f32) -> Result<f32, AttributeError> {
fn typed<V: AttributeValue>(
def: &AttributeDef<V>,
value: f32,
) -> Result<f32, AttributeError> {
def.stored(V::from_stored(value).ok_or(AttributeError::InvalidValue)?)
}
match self {
Self::Amount(def) => typed(def, value),
Self::Flag(def) => typed(def, value),
Self::Multiplier(def) => typed(def, value),
Self::Seconds(def) => typed(def, value),
}
}
pub const fn value_kind(&self) -> &'static str {
match self {
Self::Amount(_) => "amount",
Self::Flag(_) => "flag",
Self::Multiplier(_) => "multiplier",
Self::Seconds(_) => "seconds",
}
}
}
impl Amount {
pub const ZERO: Self = Self(0.0);
pub const fn new(amount: f32) -> Option<Self> {
if amount.is_finite() {
Some(Self(amount + 0.0))
} else {
None
}
}
pub const fn get(self) -> f32 {
self.0
}
}
impl AttributeValue for Amount {
fn from_stored(stored: f32) -> Option<Self> {
Self::new(stored)
}
fn to_stored(self) -> f32 {
self.0
}
}
impl sealed::Sealed for Amount {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AttributeDef<V: AttributeValue> {
raw: RawDef,
_value: PhantomData<fn() -> V>,
}
impl<V: AttributeValue> AttributeDef<V> {
pub(super) const fn new(
index: u16,
name: &'static CStr,
class: &'static CStr,
format: DescriptionFormat,
min: f32,
max: f32,
) -> Self {
let Some(index) = AttributeIndex::new(index) else {
panic!("catalog attributes need a valid definition index");
};
assert!(min.is_finite() && max.is_finite() && min < max);
Self {
raw: RawDef {
name,
index,
class,
format,
min,
max,
},
_value: PhantomData,
}
}
#[doc(alias("attribute_class"))]
pub const fn class(&self) -> &'static CStr {
self.raw.class
}
pub fn contains(&self, value: V) -> bool {
self.raw.contains(value.to_stored())
}
#[doc(alias("description_format"))]
pub const fn format(&self) -> DescriptionFormat {
self.raw.format
}
#[doc(alias("defindex"))]
pub const fn index(&self) -> AttributeIndex {
self.raw.index
}
pub const fn max(&self) -> f32 {
self.raw.max
}
pub const fn min(&self) -> f32 {
self.raw.min
}
pub const fn name(&self) -> &'static CStr {
self.raw.name
}
pub(super) const fn raw(&self) -> RawDef {
self.raw
}
pub fn stored(&self, value: V) -> Result<f32, AttributeError> {
let stored = value.to_stored();
if self.raw.contains(stored) {
Ok(stored)
} else {
Err(AttributeError::OutOfDomain)
}
}
}
#[doc(alias("attrib_definition_index_t"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AttributeIndex(u16);
impl AttributeIndex {
pub const fn new(index: u16) -> Option<Self> {
if index == INVALID_ATTRIB_DEF_INDEX {
None
} else {
Some(Self(index))
}
}
pub const fn get(self) -> u16 {
self.0
}
}
impl Display for AttributeIndex {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
pub trait AttributeValue: sealed::Sealed + Copy + Debug {
fn from_stored(stored: f32) -> Option<Self>;
fn to_stored(self) -> f32;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Combine {
Add,
BitOr,
Multiply,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DescriptionFormat {
#[doc(alias("value_is_additive"))]
Additive,
#[doc(alias("value_is_additive_percentage"))]
AdditivePercentage,
#[doc(alias("value_is_inverted_percentage"))]
InvertedPercentage,
#[doc(alias("value_is_or"))]
Or,
#[doc(alias("value_is_percentage"))]
Percentage,
}
impl DescriptionFormat {
pub const fn combine(self) -> Combine {
match self {
Self::Additive | Self::AdditivePercentage => Combine::Add,
Self::InvertedPercentage | Self::Percentage => Combine::Multiply,
Self::Or => Combine::BitOr,
}
}
pub const fn keyword(self) -> &'static str {
match self {
Self::Additive => "value_is_additive",
Self::AdditivePercentage => "value_is_additive_percentage",
Self::InvertedPercentage => "value_is_inverted_percentage",
Self::Or => "value_is_or",
Self::Percentage => "value_is_percentage",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Flag(pub bool);
impl AttributeValue for Flag {
fn from_stored(stored: f32) -> Option<Self> {
if stored == 1.0 {
Some(Self(true))
} else if stored == 0.0 {
Some(Self(false))
} else {
None
}
}
fn to_stored(self) -> f32 {
if self.0 { 1.0 } else { 0.0 }
}
}
impl sealed::Sealed for Flag {}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Multiplier(f32);
impl Multiplier {
pub const ONE: Self = Self(1.0);
pub const fn new(factor: f32) -> Option<Self> {
if factor.is_finite() && factor >= 0.0 {
Some(Self(factor + 0.0))
} else {
None
}
}
pub const fn get(self) -> f32 {
self.0
}
}
impl AttributeValue for Multiplier {
fn from_stored(stored: f32) -> Option<Self> {
Self::new(stored)
}
fn to_stored(self) -> f32 {
self.0
}
}
impl sealed::Sealed for Multiplier {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct RawDef {
pub(super) name: &'static CStr,
pub(super) index: AttributeIndex,
pub(super) class: &'static CStr,
pub(super) format: DescriptionFormat,
pub(super) min: f32,
pub(super) max: f32,
}
impl RawDef {
pub(super) fn contains(self, stored: f32) -> bool {
stored >= self.min && stored <= self.max
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Seconds(f32);
impl Seconds {
pub const ZERO: Self = Self(0.0);
pub const fn new(seconds: f32) -> Option<Self> {
if seconds.is_finite() && seconds >= 0.0 {
Some(Self(seconds + 0.0))
} else {
None
}
}
pub const fn get(self) -> f32 {
self.0
}
}
impl AttributeValue for Seconds {
fn from_stored(stored: f32) -> Option<Self> {
Self::new(stored)
}
fn to_stored(self) -> f32 {
self.0
}
}
impl sealed::Sealed for Seconds {}
mod sealed {
pub trait Sealed {}
}