//! The generic content registry: one `Registry<K>` implementation shared by every "id ->
//! factory closure" content table (weapons, enemies, damage types, and future kinds like
//! status/animation/tower/drone). Each concrete content kind is a zero-sized marker
//! implementing `ContentKind`, which just declares the factory's argument and output types —
//! the interning, storage, and lookup logic is written exactly once here.
use std::{collections::HashMap, hash::Hash, marker::PhantomData};
use crate::{
behavior::{
damage::DamageBehavior,
projectile::{BehaviorPriority, ProjectileBehavior},
status::{PeriodDamage, StatusBehavior},
},
enemies::{EnemyKind as EnemyKindLifecycle, blob::Blob},
enemy::Enemy,
weapons::impact::TowerBaseWeapon,
};
/// Declares what a content kind's factory closures look like: the arguments they take
/// (`u32` tier for weapons, `()` for enemies, ...) and what they produce
/// (`Box<dyn ProjectileBehavior>`, `Enemy`, ...). One zero-sized marker per content kind.
pub trait ContentKind {
type Args;
type Output;
}
/// A dense, type-distinct id into a `Registry<K>`. Generic over the content kind so
/// `Id<WeaponContent>` and `Id<EnemyContent>` can never be mixed up, without hand-writing a
/// newtype per registry.
pub struct Id<K> {
idx: u32,
_marker: PhantomData<fn() -> K>,
}
impl<K> Id<K> {
const fn new(idx: u32) -> Self {
Self {
idx,
_marker: PhantomData,
}
}
}
// Manual impls instead of `#[derive]`: derive would add a `K: Trait` bound from the
// `PhantomData<K>` field, which is wrong here — `K` is a marker type, never actually stored.
impl<K> Copy for Id<K> {}
impl<K> Clone for Id<K> {
fn clone(&self) -> Self {
*self
}
}
impl<K> PartialEq for Id<K> {
fn eq(&self, other: &Self) -> bool {
self.idx == other.idx
}
}
impl<K> Eq for Id<K> {}
impl<K> Hash for Id<K> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.idx.hash(state);
}
}
impl<K> std::fmt::Debug for Id<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Id({})", self.idx)
}
}
struct Def<K: ContentKind> {
display_name: String,
priority: i32,
make: Box<dyn Fn(K::Args) -> K::Output + Send + Sync>,
}
/// String <-> dense u32 id table, shared by every content registry.
#[derive(Default)]
struct Interner {
to_id: HashMap<String, u32>,
names: Vec<String>,
}
impl Interner {
fn intern(&mut self, s: &str) -> u32 {
if let Some(&id) = self.to_id.get(s) {
return id;
}
let id = self.names.len() as u32;
self.names.push(s.to_owned());
self.to_id.insert(s.to_owned(), id);
id
}
fn get(&self, s: &str) -> Option<u32> {
self.to_id.get(s).copied()
}
#[allow(dead_code)]
fn name(&self, id: u32) -> &str {
&self.names[id as usize]
}
}
/// One generic content table: intern a name, store its factory closure, hand back a
/// type-distinct `Id<K>`. `WeaponRegistry`, `EnemyRegistry`, `DamageRegistry`, and any future
/// registry are all just `Registry<SomeMarker>` — see the aliases below.
pub struct Registry<K: ContentKind> {
interner: Interner,
defs: HashMap<Id<K>, Def<K>>,
all: Vec<Id<K>>,
}
impl<K: ContentKind> Default for Registry<K> {
fn default() -> Self {
Self {
interner: Interner::default(),
defs: HashMap::new(),
all: Vec::new(),
}
}
}
impl<K: ContentKind> Registry<K> {
pub fn register(
&mut self,
name: &str,
display: &str,
priority: i32,
make: impl Fn(K::Args) -> K::Output + Send + Sync + 'static,
) -> Id<K> {
let id = Id::new(self.interner.intern(name));
self.defs.insert(
id,
Def {
display_name: display.into(),
priority,
make: Box::new(make),
},
);
self.all.push(id);
id
}
pub fn make(&self, id: Id<K>, args: K::Args) -> K::Output {
(self.defs[&id].make)(args)
}
pub fn display_name(&self, id: Id<K>) -> &str {
&self.defs[&id].display_name
}
pub fn priority(&self, id: Id<K>) -> i32 {
self.defs[&id].priority
}
pub fn id_of(&self, name: &str) -> Option<Id<K>> {
self.interner.get(name).map(Id::new)
}
pub fn all(&self) -> &[Id<K>] {
&self.all
}
}
// ---------------------------------------------------------------------------------------
// Content kinds. Adding a new registry (status, animation, tower loadout, drone, ...) is
// just one marker + two aliases below, plus a `new()` that seeds its own content — no new
// storage/lookup code required.
// ---------------------------------------------------------------------------------------
pub struct WeaponContent;
impl ContentKind for WeaponContent {
type Args = u32; // tier
type Output = Box<dyn ProjectileBehavior>;
}
pub type WeaponID = Id<WeaponContent>;
pub type WeaponRegistry = Registry<WeaponContent>;
impl WeaponRegistry {
pub fn new(damage: &DamageRegistry) -> Self {
let physical = damage
.id_of("physical")
.expect("DamageRegistry must seed \"physical\" before WeaponRegistry is built");
let poison = damage
.id_of("poison")
.expect("DamageRegistry must seed \"poison\" before WeaponRegistry is built");
let mut reg = Self::default();
reg.register(
"impact",
"Impact",
crate::behavior::projectile::BehaviorPriority::DEFAULT,
move |tier| TowerBaseWeapon::create(tier, physical),
);
reg.register(
"poison_coating",
"Poison Coating",
crate::behavior::projectile::BehaviorPriority::DEFAULT,
move |tier| crate::weapons::poison::PoisonCoating::create(tier, poison),
);
reg
}
}
pub struct EnemyContent;
impl ContentKind for EnemyContent {
type Args = u32;
type Output = Enemy;
}
pub type EnemyID = Id<EnemyContent>;
pub type EnemyRegistry = Registry<EnemyContent>;
impl EnemyID {
pub const PLACEHOLDER: EnemyID = Id::new(0);
}
impl EnemyRegistry {
pub fn new() -> Self {
let mut reg = Self::default();
reg.register("blob", "Blob", 0, |tier| Blob::create(tier));
reg
}
pub fn make_kind(&self, id: EnemyID, tier: u32) -> Enemy {
let mut e = self.make(id, tier);
e.kind = id;
e
}
}
pub struct DamageContent;
impl ContentKind for DamageContent {
type Args = u32; // potency / scaling input
type Output = Box<dyn DamageBehavior>;
}
pub type DamageID = Id<DamageContent>;
pub type DamageRegistry = Registry<DamageContent>;
impl DamageRegistry {
pub fn new() -> Self {
let mut reg = Self::default();
reg.register(
"physical",
"Physical",
crate::behavior::projectile::BehaviorPriority::DEFAULT,
|_| Box::new(crate::behavior::damage::Physical) as Box<dyn DamageBehavior>,
);
reg.register(
"poison",
"Poison",
crate::behavior::projectile::BehaviorPriority::DEFAULT,
|_| Box::new(crate::behavior::damage::Poison) as Box<dyn DamageBehavior>,
);
reg
}
}
pub struct StatusContent;
impl ContentKind for StatusContent {
type Args = u32;
type Output = Box<dyn StatusBehavior>;
}
pub type StatusID = Id<StatusContent>;
pub type StatusRegistry = Registry<StatusContent>;
impl StatusRegistry {
pub fn new(damage: &DamageRegistry) -> Self {
let poison = damage.id_of("poison").unwrap();
let mut reg = Self::default();
reg.register("poison", "Poison", BehaviorPriority::MODIFY, move |_| {
PeriodDamage::make(poison, 3.0, 1.0, 5.0, crate::types::Source::None)
});
reg
}
}