Added rtsim sites

This commit is contained in:
Joshua Barretto 2022-08-11 15:44:57 +01:00
parent 1dc7518200
commit c856f2625c
21 changed files with 333 additions and 109 deletions

View File

@ -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);

View File

@ -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<K: EnumArray<V> + Serialize, V: PartialEq + Default + Serialize, S: ser::Serializer>(map: &EnumMap<K, V>, mut ser: S) -> Result<S::Ok, S::Error> {
// ser.collect_map(map
// .iter()
// .filter(|(k, v)| v != &&V::default())
// .map(|(k, v)| (k, v)))
// }
fn rugged_ser_enum_map<
K: EnumArray<V> + Serialize,
V: From<i16> + PartialEq + Serialize,

View File

@ -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<f32>,
// 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<f32>,
/// 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<f32>) -> 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<f32> { 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<f32>) { 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<f32> },
Site { site: SiteId, wpos: Vec3<f32> },
Travelling {
a: SiteId,
b: SiteId,
frac: f32,
},
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Npcs {
pub npcs: HopSlotMap<NpcId, Npc>,
}
impl Npcs {
pub fn spawn(&mut self, npc: Npc) -> NpcId {
pub fn create(&mut self, npc: Npc) -> NpcId {
self.npcs.insert(npc)
}
}

47
rtsim/src/data/site.rs Normal file
View File

@ -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<i32>,
/// 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<Id<WorldSite>>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Sites {
pub sites: HopSlotMap<SiteId, Site>,
}
impl Sites {
pub fn create(&mut self, site: Site) -> SiteId {
self.sites.insert(site)
}
}
impl Deref for Sites {
type Target = HopSlotMap<SiteId, Site>;
fn deref(&self) -> &Self::Target { &self.sites }
}
impl DerefMut for Sites {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.sites }
}

View File

@ -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 {}

View File

@ -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,26 +24,26 @@ 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));
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
}

25
rtsim/src/gen/site.rs Normal file
View File

@ -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<WorldSite>, 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),
}
}
}

View File

@ -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<R> = AtomicRefCell<R>;
type EventHandlersOf<E> = Vec<Box<dyn Fn(&RtState, E) + Send + Sync + 'static>>;
type EventHandlersOf<E> = Vec<Box<dyn Fn(&RtState, &World, IndexRef, &E) + Send + Sync + 'static>>;
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::<rule::example::RuleState>();
self.start_rule::<rule::simulate_npcs::SimulateNpcs>();
self.start_rule::<rule::setup::Setup>();
}
pub fn start_rule<R: Rule>(&mut self) {
@ -66,13 +68,19 @@ impl RtState {
.borrow_mut()
}
pub fn bind<R: Rule, E: Event>(&mut self, mut f: impl FnMut(&mut R, &RtState, E) + Send + Sync + 'static) {
pub fn bind<R: Rule, E: Event>(&mut self, mut f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static) {
let f = AtomicRefCell::new(f);
self.event_handlers
.entry::<EventHandlersOf<E>>()
.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<E: Event>(&mut self, e: E) {
pub fn emit<E: Event>(&mut self, e: E, world: &World, index: IndexRef) {
self.event_handlers
.get::<EventHandlersOf<E>>()
.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);
}
}

View File

@ -1,4 +1,5 @@
pub mod example;
pub mod setup;
pub mod simulate_npcs;
use std::fmt;
use super::RtState;

View File

@ -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<Self, RuleError> {
info!("Hello from example rule!");
rtstate.bind::<Self, OnTick>(|this, rtstate, event| {
// println!("Tick!");
});
Ok(Self)
}
}

48
rtsim/src/rule/setup.rs Normal file
View File

@ -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<Self, RuleError> {
rtstate.bind::<Self, OnSetup>(|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)
}
}

View File

@ -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<Self, RuleError> {
rtstate.bind::<Self, OnTick>(|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)
}
}

View File

@ -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::<RtSimEntity>()
@ -528,10 +528,9 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
{
state
.ecs()
.write_resource::<RtSim>()
.destroy_entity(rtsim_entity.0);
.write_resource::<rtsim2::RtSim>()
.hook_rtsim_entity_delete(rtsim_entity);
}
*/
if let Err(e) = state.delete_entity_recorded(entity) {
error!(?e, ?entity, "Failed to delete destroyed entity");

View File

@ -707,7 +707,13 @@ impl Server {
fn on_block_update(ecs: &specs::World, wpos: Vec3<i32>, 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::<rtsim2::RtSim>().hook_block_update(wpos, old_block, new_block);
ecs.write_resource::<rtsim2::RtSim>().hook_block_update(
&ecs.read_resource::<Arc<world::World>>(),
ecs.read_resource::<Arc<world::IndexOwned>>().as_index_ref(),
wpos,
old_block,
new_block,
);
}
}

View File

@ -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<i32>, 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<i32>, 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();

View File

@ -5,5 +5,5 @@ use rtsim2::RtState;
pub fn start_rules(rtstate: &mut RtState) {
info!("Starting server rtsim rules...");
rtstate.start_rule::<deplete_resources::State>();
rtstate.start_rule::<deplete_resources::DepleteResources>();
}

View File

@ -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<Self, RuleError> {
info!("Hello from the resource depletion rule!");
rtstate.bind::<Self, OnBlockChange>(|this, rtstate, event| {
let key = event.wpos
rtstate.bind::<Self, OnBlockChange>(|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::<ChunkStates>().0.get(key) {
let mut chunk_res = rtstate.data().nature.get_chunk_resources(key);
if let Some(Some(chunk_state)) = ctx.state.resource_mut::<ChunkStates>().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);
}
});

View File

@ -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::<ChunkStates>();
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,

View File

@ -7,7 +7,7 @@ use hashbrown::{HashMap, HashSet};
use tracing::warn;
use vek::*;
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum DebugShape {
Line([Vec3<f32>; 2]),
Cylinder {
@ -261,7 +261,8 @@ pub struct DebugShapeId(pub u64);
pub struct Debug {
next_shape_id: DebugShapeId,
pending_shapes: HashMap<DebugShapeId, DebugShape>,
shapes: HashMap<DebugShapeId, DebugShape>,
pending: HashSet<DebugShapeId>,
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4], [f32; 4])>,
pending_deletes: HashSet<DebugShapeId>,
models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
@ -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,7 +304,8 @@ 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() {
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],
@ -312,6 +320,7 @@ impl Debug {
);
}
}
}
for (id, (pos, color, ori)) in self.pending_locals.drain() {
if let Some((_, locals)) = self.models.get_mut(&id) {
let lc = srgba_to_linear(color.into());
@ -330,6 +339,7 @@ impl Debug {
}
for id in self.pending_deletes.drain() {
self.models.remove(&id);
self.shapes.remove(&id);
}
}

View File

@ -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) {

View File

@ -1430,6 +1430,7 @@ fn find_site_loc(
});
}
}
debug!("Failed to place site {:?}.", site_kind);
None
}