use crate::engine_ids as ids;
macro_rules! mask {
(
$(#[$doc:meta])*
$name:ident($bits:ty) {
$($(#[$cdoc:meta])* $constant:ident = $bit:literal, $id:path;)*
}
) => {
$(#[$doc])*
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct $name($bits);
impl $name {
$($(#[$cdoc])* pub const $constant: Self = Self(1 << $bit);)*
#[doc = concat!("See [`", stringify!($name), "`].")]
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[doc = concat!("See [`", stringify!($name), "`].")]
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub(super) const IDS: [(Self, &'static str); [$(stringify!($constant)),*].len()] =
[$((Self::$constant, $id)),*];
#[cfg(test)]
const NAMES: [(&'static str, &'static str); [$(stringify!($constant)),*].len()] =
[$((stringify!($constant), $id)),*];
pub(super) const fn from_bits(bits: $bits) -> Self {
Self(bits)
}
pub(super) const fn bits(self) -> $bits {
self.0
}
}
impl core::ops::BitOr for $name {
type Output = Self;
fn bitor(self, other: Self) -> Self {
self.union(other)
}
}
impl crate::script::Typed for $name {
const TY: &'static crate::script::Ty = &crate::script::Ty::List(&crate::script::Ty::String);
}
impl crate::script::IntoScript for $name {
fn into_value(self) -> crate::script::Value {
let set = [$((stringify!($constant), Self::$constant)),*]
.into_iter()
.filter(|(_, bit)| self.contains(*bit))
.map(|(name, _)| crate::script::Value::String(name.to_owned()))
.collect();
crate::script::Value::List(set)
}
}
impl crate::script::FromScript for $name {
fn from_value(value: crate::script::Value) -> Result<Self, crate::script::Error> {
let names = <Vec<String> as crate::script::FromScript>::from_value(value)?;
names.iter().try_fold(Self::default(), |set, name| {
[$((stringify!($constant), Self::$constant)),*]
.into_iter()
.find(|(known, _)| known == name)
.map(|(_, bit)| set.union(bit))
.ok_or_else(|| crate::script::Error::new(format!("{name:?} is no bit of {}", stringify!($name))))
})
}
}
};
}
macro_rules! named_values {
($($name:ident),*) => {$(
impl crate::script::Typed for $name {
const TY: &'static crate::script::Ty = &crate::script::Ty::String;
}
impl crate::script::IntoScript for $name {
fn into_value(self) -> crate::script::Value {
crate::script::Value::String(format!("{self:?}"))
}
}
impl crate::script::FromScript for $name {
fn from_value(value: crate::script::Value) -> Result<Self, crate::script::Error> {
let text = <String as crate::script::FromScript>::from_value(value)?;
if let Some(raw) = text.strip_prefix("Other(").and_then(|t| t.strip_suffix(')')) {
return raw.parse().map(Self::Other).map_err(|_| {
crate::script::Error::new(format!("{text:?} is no {}", stringify!($name)))
});
}
Self::IDS
.iter()
.map(|(value, _)| *value)
.find(|value| format!("{value:?}") == text)
.ok_or_else(|| crate::script::Error::new(format!("{text:?} is no {}", stringify!($name))))
}
}
)*};
}
mask! {
DamageTypeMask(u16) {
PHYSICAL_CUT = 0, ids::DAMAGE_TYPE_PHYSICAL_CUT;
PHYSICAL_PIERCE = 1, ids::DAMAGE_TYPE_PHYSICAL_PIERCE;
PHYSICAL_BLUNT = 2, ids::DAMAGE_TYPE_PHYSICAL_BLUNT;
MAGICAL_FIRE = 3, ids::DAMAGE_TYPE_MAGICAL_FIRE;
MAGICAL_ICE = 4, ids::DAMAGE_TYPE_MAGICAL_ICE;
MAGICAL_FOG = 5, ids::DAMAGE_TYPE_MAGICAL_FOG;
MAGICAL_LIGHTNING = 6, ids::DAMAGE_TYPE_MAGICAL_LIGHTNING;
HEALING = 7, ids::DAMAGE_TYPE_HEALING;
POISON = 8, ids::DAMAGE_TYPE_POISON;
WOOD = 9, ids::DAMAGE_TYPE_WOOD;
STONE = 10, ids::DAMAGE_TYPE_STONE;
METAL = 11, ids::DAMAGE_TYPE_METAL;
EXPLOSION = 12, ids::DAMAGE_TYPE_EXPLOSION;
}
}
mask! {
AttackCategoryMask(u8) {
MELEE = 0, ids::ATTACK_CATEGORY_MELEE;
RANGED = 1, ids::ATTACK_CATEGORY_RANGED;
MAGIC = 2, ids::ATTACK_CATEGORY_MAGIC;
TOOL = 3, ids::ATTACK_CATEGORY_TOOL;
OTHER = 4, ids::ATTACK_CATEGORY_OTHER;
AOE = 5, ids::ATTACK_CATEGORY_AO_E;
SKILL_SHOT = 6, ids::ATTACK_CATEGORY_SKILL_SHOT;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ExperienceSourceType {
None,
Exploration,
Lore,
Quest,
Combat,
Mining,
ItemSetCompleted,
Other(u8),
}
impl ExperienceSourceType {
pub(crate) const IDS: [(Self, &'static str); 7] = [
(Self::None, ids::EXPERIENCE_SOURCE_NONE),
(Self::Exploration, ids::EXPERIENCE_SOURCE_EXPLORATION),
(Self::Lore, ids::EXPERIENCE_SOURCE_LORE),
(Self::Quest, ids::EXPERIENCE_SOURCE_QUEST),
(Self::Combat, ids::EXPERIENCE_SOURCE_COMBAT),
(Self::Mining, ids::EXPERIENCE_SOURCE_MINING),
(
Self::ItemSetCompleted,
ids::EXPERIENCE_SOURCE_ITEM_SET_COMPLETED,
),
];
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FlameAltarActionType {
Abandon,
Reclaim,
RemoveWater,
SetAsPreferredSpawnAltar,
Other(u8),
}
impl FlameAltarActionType {
pub(super) const IDS: [(Self, &'static str); 4] = [
(Self::Abandon, ids::ALTAR_ACTION_ABANDON),
(Self::Reclaim, ids::ALTAR_ACTION_RECLAIM),
(Self::RemoveWater, ids::ALTAR_ACTION_REMOVE_WATER),
(
Self::SetAsPreferredSpawnAltar,
ids::ALTAR_ACTION_SET_AS_PREFERRED_SPAWN_ALTAR,
),
];
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EquipmentSlot {
VanityHead,
VanityUpperBody,
VanityArms,
VanityLowerBody,
VanityFeet,
ArmourHead,
ArmourUpperBody,
ArmourArms,
ArmourLowerBody,
ArmourFeet,
Ring0,
Ring1,
RangedWeapon,
Shield,
Arrow,
Spell,
FishingBait,
TerrainMaterial,
BlueprintMaterialDefault,
BlueprintMaterialRoof,
OvergrowthMaterial,
HookShot,
Glider,
Backpack,
MainHand,
Other(u8),
}
impl EquipmentSlot {
pub(super) const IDS: [(Self, &'static str); 25] = [
(Self::VanityHead, ids::EQUIPMENT_SLOT_VANITY_HEAD),
(Self::VanityUpperBody, ids::EQUIPMENT_SLOT_VANITY_UPPER_BODY),
(Self::VanityArms, ids::EQUIPMENT_SLOT_VANITY_ARMS),
(Self::VanityLowerBody, ids::EQUIPMENT_SLOT_VANITY_LOWER_BODY),
(Self::VanityFeet, ids::EQUIPMENT_SLOT_VANITY_FEET),
(Self::ArmourHead, ids::EQUIPMENT_SLOT_ARMOUR_HEAD),
(Self::ArmourUpperBody, ids::EQUIPMENT_SLOT_ARMOUR_UPPER_BODY),
(Self::ArmourArms, ids::EQUIPMENT_SLOT_ARMOUR_ARMS),
(Self::ArmourLowerBody, ids::EQUIPMENT_SLOT_ARMOUR_LOWER_BODY),
(Self::ArmourFeet, ids::EQUIPMENT_SLOT_ARMOUR_FEET),
(Self::Ring0, ids::EQUIPMENT_SLOT_RING0),
(Self::Ring1, ids::EQUIPMENT_SLOT_RING1),
(Self::RangedWeapon, ids::EQUIPMENT_SLOT_RANGED_WEAPON),
(Self::Shield, ids::EQUIPMENT_SLOT_SHIELD),
(Self::Arrow, ids::EQUIPMENT_SLOT_ARROW),
(Self::Spell, ids::EQUIPMENT_SLOT_SPELL),
(Self::FishingBait, ids::EQUIPMENT_SLOT_FISHING_BAIT),
(Self::TerrainMaterial, ids::EQUIPMENT_SLOT_TERRAIN_MATERIAL),
(
Self::BlueprintMaterialDefault,
ids::EQUIPMENT_SLOT_BLUEPRINT_MATERIAL_DEFAULT,
),
(
Self::BlueprintMaterialRoof,
ids::EQUIPMENT_SLOT_BLUEPRINT_MATERIAL_ROOF,
),
(
Self::OvergrowthMaterial,
ids::EQUIPMENT_SLOT_OVERGROWTH_MATERIAL,
),
(Self::HookShot, ids::EQUIPMENT_SLOT_HOOK_SHOT),
(Self::Glider, ids::EQUIPMENT_SLOT_GLIDER),
(Self::Backpack, ids::EQUIPMENT_SLOT_BACKPACK),
(Self::MainHand, ids::EQUIPMENT_SLOT_MAIN_HAND),
];
}
named_values!(ExperienceSourceType, FlameAltarActionType, EquipmentSlot);
#[cfg(test)]
mod tests {
use super::*;
use crate::engine_enum::assert_named_after_engine;
#[test]
fn every_value_and_bit_takes_the_engine_s_name() {
assert_named_after_engine(&EquipmentSlot::IDS);
assert_named_after_engine(&ExperienceSourceType::IDS);
assert_named_after_engine(&FlameAltarActionType::IDS);
for names in [
DamageTypeMask::NAMES.as_slice(),
AttackCategoryMask::NAMES.as_slice(),
] {
let named: Vec<_> = names
.iter()
.map(|&(name, id)| (Constant(name), id))
.collect();
assert_named_after_engine(&named);
}
}
struct Constant(&'static str);
impl core::fmt::Debug for Constant {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.0)
}
}
}