diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 6bfe625fea..80b541c1c7 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -11,6 +11,8 @@ use crate::comp::dialogue::MoodState; slotmap::new_key_type! { pub struct NpcId; } +slotmap::new_key_type! { pub struct SiteId; } + #[derive(Copy, Clone, Debug)] pub struct RtSimEntity(pub NpcId); diff --git a/rtsim/src/data/mod.rs b/rtsim/src/data/mod.rs index c68797db4f..e1a7d66adb 100644 --- a/rtsim/src/data/mod.rs +++ b/rtsim/src/data/mod.rs @@ -1,8 +1,10 @@ pub mod npc; +pub mod site; pub mod nature; pub use self::{ npc::{Npc, NpcId, Npcs}, + site::{Site, SiteId, Sites}, nature::Nature, }; @@ -25,6 +27,7 @@ pub enum Actor { pub struct Data { pub nature: Nature, pub npcs: Npcs, + pub sites: Sites, } pub type ReadError = rmp_serde::decode::Error; @@ -40,13 +43,6 @@ impl Data { } } -// fn rugged_ser_enum_map + Serialize, V: PartialEq + Default + Serialize, S: ser::Serializer>(map: &EnumMap, mut ser: S) -> Result { -// ser.collect_map(map -// .iter() -// .filter(|(k, v)| v != &&V::default()) -// .map(|(k, v)| (k, v))) -// } - fn rugged_ser_enum_map< K: EnumArray + Serialize, V: From + PartialEq + Serialize, diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index 6f2f0d6151..c01b3b0081 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -3,21 +3,47 @@ use serde::{Serialize, Deserialize}; use slotmap::HopSlotMap; use vek::*; use std::ops::{Deref, DerefMut}; -use common::uid::Uid; - +use common::{ + uid::Uid, + store::Id, + rtsim::SiteId, +}; pub use common::rtsim::NpcId; #[derive(Clone, Serialize, Deserialize)] pub struct Npc { - pub wpos: Vec3, + // Persisted state + + /// Represents the location of the NPC. + pub loc: NpcLoc, + + // Unpersisted state + + /// The position of the NPC in the world. Note that this is derived from [`Npc::loc`] and cannot be updated manually + #[serde(skip_serializing, skip_deserializing)] + wpos: Vec3, + /// Whether the NPC is in simulated or loaded mode (when rtsim is run on the server, loaded corresponds to being + /// within a loaded chunk). When in loaded mode, the interactions of the NPC should not be simulated but should + /// instead be derived from the game. #[serde(skip_serializing, skip_deserializing)] pub mode: NpcMode, } impl Npc { - pub fn at(wpos: Vec3) -> Self { - Self { wpos, mode: NpcMode::Simulated } + pub fn new(loc: NpcLoc) -> Self { + Self { + loc, + wpos: Vec3::zero(), + mode: NpcMode::Simulated, + } } + + pub fn wpos(&self) -> Vec3 { self.wpos } + + /// You almost certainly *DO NOT* want to use this method. + /// + /// Update the NPC's wpos as a result of routine NPC simulation derived from its location. + pub(crate) fn tick_wpos(&mut self, wpos: Vec3) { self.wpos = wpos; } } #[derive(Copy, Clone, Default)] @@ -29,13 +55,24 @@ pub enum NpcMode { Loaded, } +#[derive(Clone, Serialize, Deserialize)] +pub enum NpcLoc { + Wild { wpos: Vec3 }, + Site { site: SiteId, wpos: Vec3 }, + Travelling { + a: SiteId, + b: SiteId, + frac: f32, + }, +} + #[derive(Clone, Serialize, Deserialize)] pub struct Npcs { pub npcs: HopSlotMap, } impl Npcs { - pub fn spawn(&mut self, npc: Npc) -> NpcId { + pub fn create(&mut self, npc: Npc) -> NpcId { self.npcs.insert(npc) } } diff --git a/rtsim/src/data/site.rs b/rtsim/src/data/site.rs new file mode 100644 index 0000000000..6e34be73d0 --- /dev/null +++ b/rtsim/src/data/site.rs @@ -0,0 +1,47 @@ +use hashbrown::HashMap; +use serde::{Serialize, Deserialize}; +use slotmap::HopSlotMap; +use vek::*; +use std::ops::{Deref, DerefMut}; +use common::{ + uid::Uid, + store::Id, +}; +pub use common::rtsim::SiteId; +use world::site::Site as WorldSite; + +#[derive(Clone, Serialize, Deserialize)] +pub struct Site { + pub wpos: Vec2, + + /// The site generated during initial worldgen that this site corresponds to. + /// + /// Eventually, rtsim should replace initial worldgen's site system and this will not be necessary. + /// + /// When setting up rtsim state, we try to 'link' these two definitions of a site: but if initial worldgen has + /// changed, this might not be possible. We try to delete sites that no longer exist during setup, but this is an + /// inherent fallible process. If linking fails, we try to delete the site in rtsim2 in order to avoid an + /// 'orphaned' site. (TODO: create new sites for new initial worldgen sites that come into being too). + #[serde(skip_serializing, skip_deserializing)] + pub world_site: Option>, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Sites { + pub sites: HopSlotMap, +} + +impl Sites { + pub fn create(&mut self, site: Site) -> SiteId { + self.sites.insert(site) + } +} + +impl Deref for Sites { + type Target = HopSlotMap; + fn deref(&self) -> &Self::Target { &self.sites } +} + +impl DerefMut for Sites { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.sites } +} diff --git a/rtsim/src/event.rs b/rtsim/src/event.rs index deb35cc68c..298cebf8c8 100644 --- a/rtsim/src/event.rs +++ b/rtsim/src/event.rs @@ -1,8 +1,21 @@ use common::resources::Time; +use world::{World, IndexRef}; +use super::{Rule, RtState}; pub trait Event: Clone + 'static {} +pub struct EventCtx<'a, R: Rule, E: Event> { + pub state: &'a RtState, + pub rule: &'a mut R, + pub event: &'a E, + pub world: &'a World, + pub index: IndexRef<'a>, +} + +#[derive(Clone)] +pub struct OnSetup; +impl Event for OnSetup {} + #[derive(Clone)] pub struct OnTick { pub dt: f32 } - impl Event for OnTick {} diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index fb2d3829a8..4e0877c240 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -1,6 +1,14 @@ -use crate::data::{Npcs, Npc, Data, Nature}; +pub mod site; + +use crate::data::{ + npc::{Npcs, Npc, NpcLoc}, + site::{Sites, Site}, + Data, + Nature, +}; use hashbrown::HashMap; use rand::prelude::*; +use tracing::info; use world::{ site::SiteKind, IndexRef, @@ -8,7 +16,7 @@ use world::{ }; impl Data { - pub fn generate(index: IndexRef, world: &World) -> Self { + pub fn generate(world: &World, index: IndexRef) -> Self { let mut seed = [0; 32]; seed.iter_mut().zip(&mut index.seed.to_le_bytes()).for_each(|(dst, src)| *dst = *src); let mut rng = SmallRng::from_seed(seed); @@ -16,25 +24,25 @@ impl Data { let mut this = Self { nature: Nature::generate(world), npcs: Npcs { npcs: Default::default() }, + sites: Sites { sites: Default::default() }, }; - for (site_id, site) in world - .civs() + // Register sites with rtsim + for (world_site_id, _) in index .sites .iter() - .filter_map(|(site_id, site)| site.site_tmp.map(|id| (site_id, &index.sites[id]))) { - match &site.kind { - SiteKind::Refactor(site2) => { - let wpos = site.get_origin() - .map(|e| e as f32 + 0.5) - .with_z(world.sim().get_alt_approx(site.get_origin()).unwrap_or(0.0)); - // TODO: Better API - this.npcs.spawn(Npc::at(wpos)); - println!("Spawned rtsim NPC at {:?}", wpos); - } - _ => {}, - } + let site = Site::generate(world_site_id, world, index); + this.sites.create(site); + } + info!("Registering {} rtsim sites from world sites.", this.sites.len()); + + // Spawn some test entities at the sites + for (site_id, site) in this.sites.iter() { + let wpos = site.wpos.map(|e| e as f32) + .with_z(world.sim().get_alt_approx(site.wpos).unwrap_or(0.0)); + this.npcs.create(Npc::new(NpcLoc::Site { site: site_id, wpos })); + println!("Spawned rtsim NPC at {:?}", wpos); } this diff --git a/rtsim/src/gen/site.rs b/rtsim/src/gen/site.rs new file mode 100644 index 0000000000..75a0876611 --- /dev/null +++ b/rtsim/src/gen/site.rs @@ -0,0 +1,25 @@ +use crate::data::Site; +use common::store::Id; +use world::{ + site::Site as WorldSite, + World, + IndexRef, +}; + +impl Site { + pub fn generate(world_site: Id, world: &World, index: IndexRef) -> Self { + // match &world_site.kind { + // SiteKind::Refactor(site2) => { + // let site = Site::generate(world_site_id, world, index); + // println!("Registering rtsim site at {:?}...", site.wpos); + // this.sites.create(site); + // } + // _ => {}, + // } + + Self { + wpos: index.sites.get(world_site).get_origin(), + world_site: Some(world_site), + } + } +} diff --git a/rtsim/src/lib.rs b/rtsim/src/lib.rs index 1397d99dd8..84de682836 100644 --- a/rtsim/src/lib.rs +++ b/rtsim/src/lib.rs @@ -7,9 +7,10 @@ pub mod rule; pub use self::{ data::Data, - event::{Event, OnTick}, + event::{Event, EventCtx, OnTick}, rule::{Rule, RuleError}, }; +use world::{World, IndexRef}; use anymap2::SendSyncAnyMap; use tracing::{info, error}; use atomic_refcell::AtomicRefCell; @@ -25,7 +26,7 @@ pub struct RtState { } type RuleState = AtomicRefCell; -type EventHandlersOf = Vec>; +type EventHandlersOf = Vec>; impl RtState { pub fn new(data: Data) -> Self { @@ -48,7 +49,8 @@ impl RtState { fn start_default_rules(&mut self) { info!("Starting default rtsim rules..."); - self.start_rule::(); + self.start_rule::(); + self.start_rule::(); } pub fn start_rule(&mut self) { @@ -66,13 +68,19 @@ impl RtState { .borrow_mut() } - pub fn bind(&mut self, mut f: impl FnMut(&mut R, &RtState, E) + Send + Sync + 'static) { + pub fn bind(&mut self, mut f: impl FnMut(EventCtx) + Send + Sync + 'static) { let f = AtomicRefCell::new(f); self.event_handlers .entry::>() .or_default() - .push(Box::new(move |rtstate, event| { - (f.borrow_mut())(&mut rtstate.rule_mut(), rtstate, event) + .push(Box::new(move |state, world, index, event| { + (f.borrow_mut())(EventCtx { + state, + rule: &mut state.rule_mut(), + event, + world, + index, + }) })); } @@ -93,15 +101,15 @@ impl RtState { .borrow_mut() } - pub fn emit(&mut self, e: E) { + pub fn emit(&mut self, e: E, world: &World, index: IndexRef) { self.event_handlers .get::>() .map(|handlers| handlers .iter() - .for_each(|f| f(self, e.clone()))); + .for_each(|f| f(self, world, index, &e))); } - pub fn tick(&mut self, dt: f32) { - self.emit(OnTick { dt }); + pub fn tick(&mut self, world: &World, index: IndexRef, dt: f32) { + self.emit(OnTick { dt }, world, index); } } diff --git a/rtsim/src/rule.rs b/rtsim/src/rule.rs index 6f6b701740..0547d4d277 100644 --- a/rtsim/src/rule.rs +++ b/rtsim/src/rule.rs @@ -1,4 +1,5 @@ -pub mod example; +pub mod setup; +pub mod simulate_npcs; use std::fmt; use super::RtState; diff --git a/rtsim/src/rule/example.rs b/rtsim/src/rule/example.rs deleted file mode 100644 index d4cda69920..0000000000 --- a/rtsim/src/rule/example.rs +++ /dev/null @@ -1,19 +0,0 @@ -use tracing::info; -use crate::{ - event::OnTick, - RtState, Rule, RuleError, -}; - -pub struct RuleState; - -impl Rule for RuleState { - fn start(rtstate: &mut RtState) -> Result { - info!("Hello from example rule!"); - - rtstate.bind::(|this, rtstate, event| { - // println!("Tick!"); - }); - - Ok(Self) - } -} diff --git a/rtsim/src/rule/setup.rs b/rtsim/src/rule/setup.rs new file mode 100644 index 0000000000..5bdb00a373 --- /dev/null +++ b/rtsim/src/rule/setup.rs @@ -0,0 +1,48 @@ +use tracing::warn; +use crate::{ + data::Site, + event::OnSetup, + RtState, Rule, RuleError, +}; + +/// This rule runs at rtsim startup and broadly acts to perform some primitive migration/sanitisation in order to +/// ensure that the state of rtsim is mostly sensible. +pub struct Setup; + +impl Rule for Setup { + fn start(rtstate: &mut RtState) -> Result { + rtstate.bind::(|ctx| { + // Delete rtsim sites that don't correspond to a world site + ctx.state.data_mut().sites.retain(|site_id, site| { + if let Some((world_site_id, _)) = ctx.index.sites + .iter() + .find(|(_, world_site)| world_site.get_origin() == site.wpos) + { + site.world_site = Some(world_site_id); + true + } else { + warn!("{:?} is no longer valid because the site it was derived from no longer exists. It will now be deleted.", site_id); + false + } + }); + + // Generate rtsim sites for world sites that don't have a corresponding rtsim site yet + for (world_site_id, _) in ctx.index.sites.iter() { + if !ctx.state.data().sites + .values() + .any(|site| site.world_site.expect("Rtsim site not assigned to world site") == world_site_id) + { + warn!("{:?} is new and does not have a corresponding rtsim site. One will now be generated afresh.", world_site_id); + ctx.state + .data_mut() + .sites + .create(Site::generate(world_site_id, ctx.world, ctx.index)); + } + } + + // TODO: Reassign sites for NPCs if they don't have one + }); + + Ok(Self) + } +} diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs new file mode 100644 index 0000000000..dae9ed6502 --- /dev/null +++ b/rtsim/src/rule/simulate_npcs.rs @@ -0,0 +1,25 @@ +use tracing::info; +use crate::{ + data::npc::NpcLoc, + event::OnTick, + RtState, Rule, RuleError, +}; + +pub struct SimulateNpcs; + +impl Rule for SimulateNpcs { + fn start(rtstate: &mut RtState) -> Result { + + rtstate.bind::(|ctx| { + for (_, npc) in ctx.state.data_mut().npcs.iter_mut() { + npc.tick_wpos(match npc.loc { + NpcLoc::Wild { wpos } => wpos, + NpcLoc::Site { site, wpos } => wpos, + NpcLoc::Travelling { a, b, frac } => todo!(), + }); + } + }); + + Ok(Self) + } +} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 8046e23e9b..3307d80ed8 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -9,6 +9,7 @@ use crate::{ }, // rtsim::RtSim, sys::terrain::SAFE_ZONE_RADIUS, + rtsim2, Server, SpawnPoint, StateExt, }; use authc::Uuid; @@ -26,7 +27,7 @@ use common::{ event::{EventBus, ServerEvent}, outcome::{HealthChangeInfo, Outcome}, resources::{Secs, Time}, - // rtsim::RtSimEntity, + rtsim::RtSimEntity, states::utils::{AbilityInfo, StageSection}, terrain::{Block, BlockKind, TerrainGrid}, uid::{Uid, UidAllocator}, @@ -519,7 +520,6 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt } if should_delete { - /* if let Some(rtsim_entity) = state .ecs() .read_storage::() @@ -528,10 +528,9 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt { state .ecs() - .write_resource::() - .destroy_entity(rtsim_entity.0); + .write_resource::() + .hook_rtsim_entity_delete(rtsim_entity); } - */ if let Err(e) = state.delete_entity_recorded(entity) { error!(?e, ?entity, "Failed to delete destroyed entity"); diff --git a/server/src/lib.rs b/server/src/lib.rs index de19d3a7c7..e7baa3c55e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -707,7 +707,13 @@ impl Server { fn on_block_update(ecs: &specs::World, wpos: Vec3, old_block: Block, new_block: Block) { // When a resource block updates, inform rtsim if old_block.get_rtsim_resource().is_some() || new_block.get_rtsim_resource().is_some() { - ecs.write_resource::().hook_block_update(wpos, old_block, new_block); + ecs.write_resource::().hook_block_update( + &ecs.read_resource::>(), + ecs.read_resource::>().as_index_ref(), + wpos, + old_block, + new_block, + ); } } diff --git a/server/src/rtsim2/mod.rs b/server/src/rtsim2/mod.rs index fa73f1fe0c..a0f6cf6d33 100644 --- a/server/src/rtsim2/mod.rs +++ b/server/src/rtsim2/mod.rs @@ -17,6 +17,7 @@ use rtsim2::{ ReadError, }, rule::Rule, + event::OnSetup, RtState, }; use specs::{DispatcherBuilder, WorldExt}; @@ -80,7 +81,7 @@ impl RtSim { warn!("'RTSIM_NOLOAD' is set, skipping loading of rtsim state (old state will be overwritten)."); } - let data = Data::generate(index, &world); + let data = Data::generate(&world, index); info!("Rtsim data generated."); data }; @@ -94,6 +95,8 @@ impl RtSim { rule::start_rules(&mut this.state); + this.state.emit(OnSetup, world, index); + Ok(this) } @@ -120,8 +123,8 @@ impl RtSim { } } - pub fn hook_block_update(&mut self, wpos: Vec3, old: Block, new: Block) { - self.state.emit(event::OnBlockChange { wpos, old, new }); + pub fn hook_block_update(&mut self, world: &World, index: IndexRef, wpos: Vec3, old: Block, new: Block) { + self.state.emit(event::OnBlockChange { wpos, old, new }, world, index); } pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) { @@ -130,6 +133,11 @@ impl RtSim { } } + pub fn hook_rtsim_entity_delete(&mut self, entity: RtSimEntity) { + // TODO: Emit event on deletion to catch death? + self.state.data_mut().npcs.remove(entity.0); + } + pub fn save(&mut self, slowjob_pool: &SlowJobPool) { info!("Saving rtsim data..."); let file_path = self.file_path.clone(); diff --git a/server/src/rtsim2/rule.rs b/server/src/rtsim2/rule.rs index 41dd191507..2f349b5368 100644 --- a/server/src/rtsim2/rule.rs +++ b/server/src/rtsim2/rule.rs @@ -5,5 +5,5 @@ use rtsim2::RtState; pub fn start_rules(rtstate: &mut RtState) { info!("Starting server rtsim rules..."); - rtstate.start_rule::(); + rtstate.start_rule::(); } diff --git a/server/src/rtsim2/rule/deplete_resources.rs b/server/src/rtsim2/rule/deplete_resources.rs index fe13a2d190..1041576977 100644 --- a/server/src/rtsim2/rule/deplete_resources.rs +++ b/server/src/rtsim2/rule/deplete_resources.rs @@ -9,20 +9,20 @@ use common::{ vol::RectRasterableVol, }; -pub struct State; +pub struct DepleteResources; -impl Rule for State { +impl Rule for DepleteResources { fn start(rtstate: &mut RtState) -> Result { info!("Hello from the resource depletion rule!"); - rtstate.bind::(|this, rtstate, event| { - let key = event.wpos + rtstate.bind::(|ctx| { + let key = ctx.event.wpos .xy() .map2(TerrainChunk::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)); - if let Some(Some(chunk_state)) = rtstate.resource_mut::().0.get(key) { - let mut chunk_res = rtstate.data().nature.get_chunk_resources(key); + if let Some(Some(chunk_state)) = ctx.state.resource_mut::().0.get(key) { + let mut chunk_res = ctx.state.data().nature.get_chunk_resources(key); // Remove resources - if let Some(res) = event.old.get_rtsim_resource() { + if let Some(res) = ctx.event.old.get_rtsim_resource() { if chunk_state.max_res[res] > 0 { chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32 - 1.0) .round() @@ -30,7 +30,7 @@ impl Rule for State { } } // Add resources - if let Some(res) = event.new.get_rtsim_resource() { + if let Some(res) = ctx.event.new.get_rtsim_resource() { if chunk_state.max_res[res] > 0 { chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32 + 1.0) .round() @@ -38,7 +38,7 @@ impl Rule for State { } } println!("Chunk resources = {:?}", chunk_res); - rtstate.data_mut().nature.set_chunk_resources(key, chunk_res); + ctx.state.data_mut().nature.set_chunk_resources(key, chunk_res); } }); diff --git a/server/src/rtsim2/tick.rs b/server/src/rtsim2/tick.rs index 1f1fdc0d3d..bcd577594b 100644 --- a/server/src/rtsim2/tick.rs +++ b/server/src/rtsim2/tick.rs @@ -39,7 +39,7 @@ impl<'a> System<'a> for Sys { let mut emitter = server_event_bus.emitter(); let rtsim = &mut *rtsim; - rtsim.state.tick(dt.0); + rtsim.state.tick(&world, index.as_index_ref(), dt.0); if rtsim.last_saved.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) { rtsim.save(&slow_jobs); @@ -47,7 +47,7 @@ impl<'a> System<'a> for Sys { let chunk_states = rtsim.state.resource::(); for (npc_id, npc) in rtsim.state.data_mut().npcs.iter_mut() { - let chunk = npc.wpos + let chunk = npc.wpos() .xy() .map2(TerrainChunk::RECT_SIZE, |e, sz| (e as i32).div_euclid(sz as i32)); @@ -55,11 +55,9 @@ impl<'a> System<'a> for Sys { if matches!(npc.mode, NpcMode::Simulated) && chunk_states.0.get(chunk).map_or(false, |c| c.is_some()) { npc.mode = NpcMode::Loaded; - println!("Loading in rtsim NPC at {:?}", npc.wpos); - let body = comp::Body::Object(comp::object::Body::Scarecrow); emitter.emit(ServerEvent::CreateNpc { - pos: comp::Pos(npc.wpos), + pos: comp::Pos(npc.wpos()), stats: comp::Stats::new("Rtsim NPC".to_string()), skill_set: comp::SkillSet::default(), health: None, diff --git a/voxygen/src/scene/debug.rs b/voxygen/src/scene/debug.rs index b0fb2959f7..3ab11ba0d5 100644 --- a/voxygen/src/scene/debug.rs +++ b/voxygen/src/scene/debug.rs @@ -7,7 +7,7 @@ use hashbrown::{HashMap, HashSet}; use tracing::warn; use vek::*; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DebugShape { Line([Vec3; 2]), Cylinder { @@ -261,7 +261,8 @@ pub struct DebugShapeId(pub u64); pub struct Debug { next_shape_id: DebugShapeId, - pending_shapes: HashMap, + shapes: HashMap, + pending: HashSet, pending_locals: HashMap, pending_deletes: HashSet, models: HashMap, Bound>)>, @@ -272,7 +273,8 @@ impl Debug { pub fn new() -> Debug { Debug { next_shape_id: DebugShapeId(0), - pending_shapes: HashMap::new(), + shapes: HashMap::new(), + pending: HashSet::new(), pending_locals: HashMap::new(), pending_deletes: HashSet::new(), models: HashMap::new(), @@ -286,10 +288,15 @@ impl Debug { if matches!(shape, DebugShape::TrainTrack { .. }) { self.casts_shadow.insert(id); } - self.pending_shapes.insert(id, shape); + self.shapes.insert(id, shape); + self.pending.insert(id); id } + pub fn get_shape(&self, id: DebugShapeId) -> Option<&DebugShape> { + self.shapes.get(&id) + } + pub fn set_context(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4], ori: [f32; 4]) { self.pending_locals.insert(id, (pos, color, ori)); } @@ -297,19 +304,21 @@ impl Debug { pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); } pub fn maintain(&mut self, renderer: &mut Renderer) { - for (id, shape) in self.pending_shapes.drain() { - if let Some(model) = renderer.create_model(&shape.mesh()) { - let locals = renderer.create_debug_bound_locals(&[DebugLocals { - pos: [0.0; 4], - color: [1.0, 0.0, 0.0, 1.0], - ori: [0.0, 0.0, 0.0, 1.0], - }]); - self.models.insert(id, (model, locals)); - } else { - warn!( - "Failed to create model for debug shape {:?}: {:?}", - id, shape - ); + for id in self.pending.drain() { + if let Some(shape) = self.shapes.get(&id) { + if let Some(model) = renderer.create_model(&shape.mesh()) { + let locals = renderer.create_debug_bound_locals(&[DebugLocals { + pos: [0.0; 4], + color: [1.0, 0.0, 0.0, 1.0], + ori: [0.0, 0.0, 0.0, 1.0], + }]); + self.models.insert(id, (model, locals)); + } else { + warn!( + "Failed to create model for debug shape {:?}: {:?}", + id, shape + ); + } } } for (id, (pos, color, ori)) in self.pending_locals.drain() { @@ -330,6 +339,7 @@ impl Debug { } for id in self.pending_deletes.drain() { self.models.remove(&id); + self.shapes.remove(&id); } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index ab7cf11a62..08cccdf9bb 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1465,17 +1465,28 @@ impl Scene { z_min, z_max, } => { + let scale = scale.map_or(1.0, |s| s.0); current_entities.insert(entity); - let s = scale.map_or(1.0, |sc| sc.0); + + let shape = DebugShape::CapsulePrism { + p0: *p0 * scale, + p1: *p1 * scale, + radius: *radius * scale, + height: (*z_max - *z_min) * scale, + }; + + // If this shape no longer matches, remove the old one + if let Some(shape_id) = hitboxes.get(&entity) { + if self.debug.get_shape(*shape_id).map_or(false, |s| s != &shape) { + self.debug.remove_shape(*shape_id); + hitboxes.remove(&entity); + } + } + let shape_id = hitboxes.entry(entity).or_insert_with(|| { - self.debug.add_shape(DebugShape::CapsulePrism { - p0: *p0 * s, - p1: *p1 * s, - radius: *radius * s, - height: (*z_max - *z_min) * s, - }) + self.debug.add_shape(shape) }); - let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0]; + let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min * scale, 0.0]; let color = if group == Some(&comp::group::ENEMY) { [1.0, 0.0, 0.0, 0.5] } else if group == Some(&comp::group::NPC) { diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 3f7d6e7ea7..6aaef9b714 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1430,6 +1430,7 @@ fn find_site_loc( }); } } + debug!("Failed to place site {:?}.", site_kind); None }