#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[shape(api = "entity")]
pub struct Entity(pub u32);
impl Entity {
pub(crate) const NONE: Self = Self(0);
#[must_use]
pub(crate) const fn is_none(self) -> bool {
self.0 == 0
}
#[must_use]
pub(crate) const fn from_raw(raw: u32) -> Option<Self> {
if raw == 0 { None } else { Some(Self(raw)) }
}
}
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "item_id")]
pub struct ItemId(pub u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "player_key")]
pub struct PlayerKey(pub u64);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "recipe_id")]
pub struct RecipeId(pub u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "skill_node")]
pub struct SkillNode(pub u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "knowledge_id")]
pub struct KnowledgeId(pub u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "buff_type_id")]
pub struct BuffTypeId(pub u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "npc_id")]
pub struct NpcId(pub u32);
impl From<RecipeId> for KnowledgeId {
fn from(recipe: RecipeId) -> Self {
Self(recipe.0)
}
}
macro_rules! id_conversions {
($($id:ident($int:ty)),* $(,)?) => {$(
impl $id {
/// The id numbered `id`, in a `const` too.
///
/// # Examples
///
#[doc = concat!("See [`", stringify!($id), "`].")]
#[must_use]
pub const fn new(id: $int) -> Self {
Self(id)
}
#[doc = concat!("See [`", stringify!($id), "`].")]
#[must_use]
pub const fn get(self) -> $int {
self.0
}
}
impl From<$int> for $id {
fn from(id: $int) -> Self {
Self(id)
}
}
impl From<$id> for $int {
fn from(id: $id) -> Self {
id.0
}
}
impl core::fmt::Display for $id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
impl core::fmt::LowerHex for $id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerHex::fmt(&self.0, f)
}
}
impl core::fmt::UpperHex for $id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::UpperHex::fmt(&self.0, f)
}
}
)*};
}
pub(crate) use id_conversions;
macro_rules! guid_ids {
($($id:ident),* $(,)?) => {$(
impl $id {
#[doc = concat!("let id = enshwick::abi::", stringify!($id), "::from_guid(LOCKER.guid());")]
#[must_use]
pub const fn from_guid(guid: crate::content::Guid) -> Self {
Self(guid.fnv1a())
}
#[doc = concat!("const WARMTH: enshwick::abi::", stringify!($id), " = enshwick::abi::", stringify!($id), "::named(\"my-mod:warmth\");")]
#[doc = concat!("assert_eq!(WARMTH, enshwick::abi::", stringify!($id), "::from_guid(Id::new(\"my-mod:warmth\").guid()));")]
#[must_use]
pub const fn named(id: &str) -> Self {
Self::from_guid(crate::content::Guid::named(id))
}
}
)*};
}
id_conversions!(
Entity(u32),
ItemId(u32),
PlayerKey(u64),
RecipeId(u32),
SkillNode(u32),
KnowledgeId(u32),
BuffTypeId(u32),
NpcId(u32),
);
guid_ids!(ItemId, RecipeId, KnowledgeId, BuffTypeId, NpcId);
const _: () = {
assert!(size_of::<Entity>() == size_of::<u32>() && align_of::<Entity>() == align_of::<u32>());
assert!(size_of::<ItemId>() == size_of::<u32>() && align_of::<ItemId>() == align_of::<u32>());
assert!(
size_of::<PlayerKey>() == size_of::<u64>() && align_of::<PlayerKey>() == align_of::<u64>()
);
assert!(
size_of::<RecipeId>() == size_of::<u32>() && align_of::<RecipeId>() == align_of::<u32>()
);
assert!(
size_of::<SkillNode>() == size_of::<u32>() && align_of::<SkillNode>() == align_of::<u32>()
);
assert!(
size_of::<KnowledgeId>() == size_of::<u32>()
&& align_of::<KnowledgeId>() == align_of::<u32>()
);
assert!(
size_of::<BuffTypeId>() == size_of::<u32>()
&& align_of::<BuffTypeId>() == align_of::<u32>()
);
assert!(size_of::<NpcId>() == size_of::<u32>() && align_of::<NpcId>() == align_of::<u32>());
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_item_id_converts_to_and_from_its_number() {
let item = ItemId::from(1_042_u32);
assert_eq!(u32::from(item), 1_042);
assert_eq!(item, ItemId::new(1_042));
assert_eq!(item.get(), 1_042);
}
#[test]
fn a_player_key_converts_to_and_from_its_number() {
let key = PlayerKey::from(u64::MAX - 1);
assert_eq!(u64::from(key), u64::MAX - 1);
assert_eq!(key, PlayerKey::new(u64::MAX - 1));
assert_eq!(key.get(), u64::MAX - 1);
}
#[test]
fn a_recipe_id_converts_to_and_from_its_number() {
let recipe = RecipeId::from(77_u32);
assert_eq!(u32::from(recipe), 77);
assert_eq!(recipe, RecipeId::new(77));
assert_eq!(recipe.get(), 77);
}
#[test]
fn an_id_prints_in_hex_as_its_number_does() {
assert_eq!(format!("{:#x}", ItemId::new(0x5e01)), "0x5e01");
assert_eq!(format!("{:016X}", PlayerKey::new(0xab)), "00000000000000AB");
assert_eq!(format!("{:x}", RecipeId::new(255)), "ff");
}
#[test]
fn every_id_displays_as_its_number() {
assert_eq!(Entity(1210).to_string(), "1210");
assert_eq!(ItemId::new(1_042).to_string(), "1042");
assert_eq!(PlayerKey::new(u64::MAX).to_string(), u64::MAX.to_string());
assert_eq!(RecipeId::new(77).to_string(), "77");
assert_eq!(SkillNode::new(0x2a).to_string(), "42");
assert_eq!(KnowledgeId::new(29).to_string(), "29");
assert_eq!(format!("{:>6}", ItemId::new(7)), " 7");
}
#[test]
fn a_named_id_is_the_id_of_its_content_guid() {
let id = crate::content::Id::new("my-mod:warmth");
assert_eq!(
BuffTypeId::named("my-mod:warmth"),
BuffTypeId::from_guid(id.guid())
);
assert_eq!(
NpcId::named("my-mod:smith").get(),
crate::content::Guid::named("my-mod:smith").fnv1a()
);
assert_eq!(
ItemId::named("my-mod:warmth").get(),
BuffTypeId::named("my-mod:warmth").get()
);
assert_eq!(NpcId::new(7).to_string(), "7");
}
#[test]
fn a_recipe_s_knowledge_is_its_recipe_id() {
assert_eq!(KnowledgeId::from(RecipeId::new(77)), KnowledgeId::new(77));
}
#[test]
fn an_id_from_a_guid_is_the_fnv_hash_of_its_bytes() {
let chest = crate::content::Guid::parse("6943053a-32c1-4718-ae22-e61427ddf54d");
assert_eq!(ItemId::from_guid(chest).get(), 1_302_084_846);
assert_eq!(RecipeId::from_guid(chest).get(), 1_302_084_846);
assert_eq!(KnowledgeId::from_guid(chest).get(), 1_302_084_846);
}
#[test]
fn an_entity_of_zero_is_no_entity() {
assert_eq!(Entity::from_raw(0), None);
assert_eq!(Entity::from_raw(9), Some(Entity(9)));
assert!(Entity::NONE.is_none());
assert!(!Entity(9).is_none());
}
fn bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
postcard::to_allocvec(value).unwrap()
}
fn read<T: serde::de::DeserializeOwned, N: serde::Serialize>(number: N) -> T {
postcard::from_bytes(&bytes(&number)).unwrap()
}
#[test]
fn every_lasting_id_serializes_as_its_bare_number() {
assert_eq!(bytes(&ItemId::new(1_042)), bytes(&1_042_u32));
assert_eq!(bytes(&PlayerKey::new(u64::MAX)), bytes(&u64::MAX));
assert_eq!(bytes(&RecipeId::new(77)), bytes(&77_u32));
}
#[test]
fn every_lasting_id_deserializes_from_its_bare_number() {
assert_eq!(read::<ItemId, _>(9_u32), ItemId::new(9));
assert_eq!(read::<PlayerKey, _>(u64::MAX), PlayerKey::new(u64::MAX));
assert_eq!(read::<RecipeId, _>(77_u32), RecipeId::new(77));
}
}