use super::errors::{AuthoringError, WaymarkError};
use super::topology::TopologyLayer;
use crate::physics::properties::PropertyValue;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneTemplate {
pub id: String,
pub name: String,
pub layer: TopologyLayer,
pub tags: Vec<String>,
pub properties: HashMap<String, PropertyValue>,
pub uses: Vec<String>,
pub description: String,
}
#[derive(Debug, Clone, Default)]
pub struct TemplateRegistry {
templates: Vec<SceneTemplate>,
id_index: HashMap<String, usize>,
}
impl TemplateRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_builtins() -> Self {
let mut reg = Self::new();
for (id, name, desc) in [
("universe_cosmology_map", "Cosmology Map", "Top-level cosmology view"),
("universe_timeline_root", "Timeline Root", "Universal timeline root"),
(
"universe_faction_registry",
"Faction Registry",
"Global faction registry",
),
] {
let _ = reg.register(tmpl(id, name, TopologyLayer::Universe, desc));
}
for (id, name, desc) in [
("galaxy_strategy_map", "Strategy Map", "Galaxy-scale strategy view"),
("galaxy_trade_route", "Trade Route", "Interstellar trade route network"),
(
"galaxy_warp_lane_network",
"Warp Lane Network",
"FTL jump corridor network",
),
(
"galaxy_hostile_void_corridor",
"Hostile Void Corridor",
"Dangerous void passage",
),
] {
let _ = reg.register(tmpl(id, name, TopologyLayer::Galaxy, desc));
}
for (id, name, desc) in [
("sector_campaign", "Campaign Sector", "Campaign event zone"),
("sector_patrol", "Patrol Sector", "Patrol route zone"),
("sector_frontier", "Frontier Sector", "Frontier exploration zone"),
("sector_contested", "Contested Sector", "Contested territory zone"),
] {
let _ = reg.register(tmpl(id, name, TopologyLayer::Sector, desc));
}
for (id, name, desc, grav) in [
(
"world_habitable_planet",
"Habitable Planet",
"Standard habitable world",
9.81,
),
(
"world_hostile_wasteland",
"Hostile Wasteland",
"Dangerous wasteland world",
9.81,
),
("world_ocean", "Ocean World", "Water-dominated world", 9.81),
("world_city", "City World", "Urban megacity world", 9.81),
("world_mining", "Mining World", "Resource extraction world", 7.5),
] {
let mut t = tmpl(id, name, TopologyLayer::World, desc);
t.tags.push("isHabitableWorld".into());
t.properties
.insert("gravity.planetary".into(), PropertyValue::Float(grav));
t.properties
.insert("atmosphere.density".into(), PropertyValue::Float(1.0));
let _ = reg.register(t);
}
for (id, name, desc) in [
("realm_dream_overlay", "Dream Overlay", "Dream reality overlay"),
("realm_spirit_layer", "Spirit Layer", "Spirit world layer"),
(
"realm_corrupted_mirror",
"Corrupted Mirror",
"Corrupted mirror dimension",
),
("realm_tactical_combat", "Tactical Combat", "Combat pocket realm"),
(
"realm_simulation_pocket",
"Simulation Pocket",
"Simulation pocket realm",
),
] {
let mut t = tmpl(id, name, TopologyLayer::Realm, desc);
t.tags.push("isDreamLayer".into());
let _ = reg.register(t);
}
for (id, name, desc) in [
("region_desert_basin", "Desert Basin", "Arid desert region"),
("region_snowy_ridge", "Snowy Ridge", "Cold mountain region"),
(
"region_industrial_zone",
"Industrial Zone",
"Industrial production region",
),
("region_jungle_basin", "Jungle Basin", "Dense jungle region"),
("region_ash_crater", "Ash Crater", "Volcanic ash region"),
] {
let _ = reg.register(tmpl(id, name, TopologyLayer::Region, desc));
}
for (id, name, desc) in [
("area_boss_arena", "Boss Arena", "Boss encounter area"),
("area_harvest_field", "Harvest Field", "Resource gathering area"),
(
"area_settlement_perimeter",
"Settlement Perimeter",
"Settlement outer zone",
),
("area_siege_lane", "Siege Lane", "Siege combat corridor"),
("area_build_zone", "Build Zone", "Construction zone"),
] {
let _ = reg.register(tmpl(id, name, TopologyLayer::Area, desc));
}
for (id, name, desc) in [
("location_town", "Town", "Settlement with services"),
("location_camp", "Camp", "Temporary camp site"),
("location_station", "Station", "Service station"),
("location_dungeon_entrance", "Dungeon Entrance", "Dungeon entry point"),
("location_foundry", "Foundry", "Industrial foundry"),
("location_temple", "Temple", "Religious or mystical temple"),
] {
let _ = reg.register(tmpl(id, name, TopologyLayer::Location, desc));
}
for (id, name, desc) in [
("room_hallway", "Hallway", "Corridor passage"),
("room_reactor_chamber", "Reactor Chamber", "Power reactor room"),
("room_vault", "Vault", "Secure vault"),
("room_bedroom", "Bedroom", "Sleeping quarters"),
("room_throne_room", "Throne Room", "Throne chamber"),
("room_workshop", "Workshop", "Crafting workshop"),
] {
let mut t = tmpl(id, name, TopologyLayer::Room, desc);
t.tags.push("isInterior".into());
let _ = reg.register(t);
}
for (id, name, desc) in [
("point_loot", "Loot Point", "Item pickup point"),
("point_power_socket", "Power Socket", "Power connection socket"),
("point_door_trigger", "Door Trigger", "Door activation trigger"),
("point_spawn_marker", "Spawn Marker", "Entity spawn point"),
("point_crafting_slot", "Crafting Slot", "Crafting station slot"),
("point_dialogue_anchor", "Dialogue Anchor", "NPC dialogue anchor"),
] {
let mut t = tmpl(id, name, TopologyLayer::Point, desc);
t.tags.push("isPointSocket".into());
let _ = reg.register(t);
}
reg
}
pub fn register(&mut self, template: SceneTemplate) -> Result<(), WaymarkError> {
if self.id_index.contains_key(&template.id) {
return Err(WaymarkError::Authoring(AuthoringError::InvalidTemplateReference(
format!("duplicate template: {}", template.id),
)));
}
let idx = self.templates.len();
self.id_index.insert(template.id.clone(), idx);
self.templates.push(template);
Ok(())
}
pub fn find(&self, id: &str) -> Option<&SceneTemplate> {
self.id_index.get(id).map(|&i| &self.templates[i])
}
pub fn by_layer(&self, layer: TopologyLayer) -> Vec<&SceneTemplate> {
self.templates.iter().filter(|t| t.layer == layer).collect()
}
pub fn len(&self) -> usize {
self.templates.len()
}
pub fn is_empty(&self) -> bool {
self.templates.is_empty()
}
}
fn tmpl(id: &str, name: &str, layer: TopologyLayer, desc: &str) -> SceneTemplate {
SceneTemplate {
id: id.into(),
name: name.into(),
layer,
tags: vec![],
properties: HashMap::new(),
uses: vec![],
description: desc.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtins_count() {
let reg = TemplateRegistry::with_builtins();
assert_eq!(reg.len(), 49); }
#[test]
fn find_template() {
let reg = TemplateRegistry::with_builtins();
assert!(reg.find("world_habitable_planet").is_some());
}
#[test]
fn by_layer_universe() {
let reg = TemplateRegistry::with_builtins();
assert_eq!(reg.by_layer(TopologyLayer::Universe).len(), 3);
}
#[test]
fn by_layer_all_covered() {
let reg = TemplateRegistry::with_builtins();
for &layer in TopologyLayer::ALL {
assert!(!reg.by_layer(layer).is_empty(), "no templates for {:?}", layer);
}
}
#[test]
fn no_duplicate_ids() {
let reg = TemplateRegistry::with_builtins();
let mut ids: Vec<&str> = reg.templates.iter().map(|t| t.id.as_str()).collect();
let before = ids.len();
ids.sort();
ids.dedup();
assert_eq!(before, ids.len());
}
#[test]
fn world_template_has_gravity() {
let reg = TemplateRegistry::with_builtins();
let t = reg.find("world_habitable_planet").unwrap();
assert!(t.properties.contains_key("gravity.planetary"));
}
}