use std::{fmt, mem};
use bitfield::bitfield;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ItemIdError {
#[error("Invalid item category {0}")]
InvalidCategory(u8),
#[error("Invalid param ID {0}")]
InvalidParamId(u32),
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ItemCategory {
Weapon = 0,
Protector = 1,
Accessory = 2,
Goods = 4,
}
impl TryFrom<u8> for ItemCategory {
type Error = ItemIdError;
fn try_from(value: u8) -> Result<ItemCategory, Self::Error> {
Ok(match value {
0 => ItemCategory::Weapon,
1 => ItemCategory::Protector,
2 => ItemCategory::Accessory,
4 => ItemCategory::Goods,
_ => return Err(ItemIdError::InvalidCategory(value)),
})
}
}
#[repr(u32)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ItemCategoryHigh {
Weapon = 0,
Protector = 0x10000000,
Accessory = 0x20000000,
Goods = 0x40000000,
}
impl TryFrom<u32> for ItemCategoryHigh {
type Error = ItemIdError;
fn try_from(value: u32) -> Result<ItemCategoryHigh, Self::Error> {
match value {
0 => Ok(ItemCategoryHigh::Weapon),
0x10000000 => Ok(ItemCategoryHigh::Protector),
0x20000000 => Ok(ItemCategoryHigh::Accessory),
0x40000000 => Ok(ItemCategoryHigh::Goods),
_ => Err(ItemIdError::InvalidCategory((value >> 28) as u8)),
}
}
}
impl From<ItemCategory> for ItemCategoryHigh {
fn from(value: ItemCategory) -> ItemCategoryHigh {
unsafe { mem::transmute((value as u32) << 28) }
}
}
impl From<ItemCategoryHigh> for ItemCategory {
fn from(value: ItemCategoryHigh) -> ItemCategory {
unsafe { mem::transmute((value as u32 >> 28) as u8) }
}
}
impl PartialEq<ItemCategory> for ItemCategoryHigh {
fn eq(&self, other: &ItemCategory) -> bool {
(*self as u32) == (*other as u32) << 28
}
}
impl PartialEq<ItemCategoryHigh> for ItemCategory {
fn eq(&self, other: &ItemCategoryHigh) -> bool {
other == self
}
}
bitfield! {
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct OptionalItemId(u32);
u32;
param_id_raw, set_param_id_raw: 27, 0;
u8;
category_raw, set_category_raw: 31, 28;
}
impl OptionalItemId {
pub const NONE: OptionalItemId = OptionalItemId(u32::MAX);
pub fn as_valid(&self) -> Option<ItemId> {
if self.is_valid() {
Some(ItemId(*self))
} else {
None
}
}
pub fn is_valid(&self) -> bool {
self.category().is_some()
}
pub fn param_id(&self) -> Option<u32> {
if self.is_valid() {
Some(self.param_id_raw())
} else {
None
}
}
pub fn category(&self) -> Option<ItemCategory> {
ItemCategory::try_from(self.category_raw()).ok()
}
pub fn into_inner(self) -> u32 {
self.0
}
}
impl From<u32> for OptionalItemId {
fn from(value: u32) -> Self {
let id = Self(value);
if id.is_valid() { id } else { Self::NONE }
}
}
impl From<ItemId> for OptionalItemId {
fn from(value: ItemId) -> OptionalItemId {
value.0
}
}
impl fmt::Debug for OptionalItemId {
fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
match ItemCategory::try_from(self.category_raw()) {
Ok(category) => {
write!(f, "ItemId({}, {:?})", self.param_id_raw(), category)
}
Err(err) => write!(f, "ItemId(0x{:x}, {:?})", self.0, err),
}
}
}
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ItemId(OptionalItemId);
impl ItemId {
pub const fn new(category: ItemCategory, param_id: u32) -> Result<Self, ItemIdError> {
if param_id > 0xFFFFFFF {
Err(ItemIdError::InvalidParamId(param_id))
} else {
Ok(Self(OptionalItemId(((category as u32) << 28) | param_id)))
}
}
pub fn category(&self) -> ItemCategory {
unsafe { mem::transmute(self.0.category_raw()) }
}
pub fn param_id(&self) -> u32 {
self.0.param_id_raw()
}
pub fn into_inner(self) -> u32 {
self.0.into_inner()
}
}
impl TryFrom<u32> for ItemId {
type Error = ItemIdError;
fn try_from(value: u32) -> Result<ItemId, Self::Error> {
ItemId::try_from(OptionalItemId(value))
}
}
impl TryFrom<OptionalItemId> for ItemId {
type Error = ItemIdError;
fn try_from(value: OptionalItemId) -> Result<ItemId, Self::Error> {
if value.is_valid() {
Ok(Self(value))
} else {
Err(ItemIdError::InvalidCategory(value.category_raw()))
}
}
}
impl fmt::Debug for ItemId {
fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use crate::sprj::{ItemCategory, OptionalItemId};
#[test]
fn test_bitfield() {
let mut item = OptionalItemId(0);
item.set_param_id_raw(123);
item.set_category_raw(ItemCategory::Goods as u8);
assert_eq!(item.param_id(), Some(123));
assert_eq!(item.category(), Some(ItemCategory::Goods));
assert_eq!(item.0, 123 | (ItemCategory::Goods as u32) << 28);
item = OptionalItemId(u32::MAX);
assert_eq!(item.param_id(), None);
assert_eq!(item.category(), None);
}
}