diff --git a/common/src/world.rs b/common/src/world.rs index 7633395c..508c5e91 100644 --- a/common/src/world.rs +++ b/common/src/world.rs @@ -95,11 +95,25 @@ impl Material { ]; } +#[derive(Debug, Clone, Copy)] +pub struct MaterialOutOfBounds; + +impl std::fmt::Display for MaterialOutOfBounds { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Integer input does not represent a valid material") + } +} + +impl std::error::Error for MaterialOutOfBounds {} + impl TryFrom for Material { - type Error = (); + type Error = MaterialOutOfBounds; fn try_from(value: u16) -> Result { - Material::VALUES.get(value as usize).ok_or(()).copied() + Material::VALUES + .get(value as usize) + .ok_or(MaterialOutOfBounds) + .copied() } } diff --git a/save/gen-protos/Cargo.toml b/save/gen-protos/Cargo.toml index 2bbb099f..c26ce914 100644 --- a/save/gen-protos/Cargo.toml +++ b/save/gen-protos/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" publish = false [dependencies] -prost-build = "0.13.1" +prost-build = "0.13.3" diff --git a/save/src/protos.proto b/save/src/protos.proto index 8fec3839..c16e574f 100644 --- a/save/src/protos.proto +++ b/save/src/protos.proto @@ -36,4 +36,8 @@ enum ComponentType { POSITION = 0; // UTF-8 text NAME = 1; + // u16 + MATERIAL = 2; + // List of u64 + INVENTORY = 3; } diff --git a/save/src/protos.rs b/save/src/protos.rs index fa03540a..64c764ab 100644 --- a/save/src/protos.rs +++ b/save/src/protos.rs @@ -40,6 +40,10 @@ pub enum ComponentType { Position = 0, /// UTF-8 text Name = 1, + /// u16 + Material = 2, + /// List of u64 + Inventory = 3, } impl ComponentType { /// String value of the enum field names used in the ProtoBuf definition. @@ -48,8 +52,10 @@ impl ComponentType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ComponentType::Position => "POSITION", - ComponentType::Name => "NAME", + Self::Position => "POSITION", + Self::Name => "NAME", + Self::Material => "MATERIAL", + Self::Inventory => "INVENTORY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -57,6 +63,8 @@ impl ComponentType { match value { "POSITION" => Some(Self::Position), "NAME" => Some(Self::Name), + "MATERIAL" => Some(Self::Material), + "INVENTORY" => Some(Self::Inventory), _ => None, } } diff --git a/server/src/sim.rs b/server/src/sim.rs index 08ece64e..6064ddf8 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -210,7 +210,19 @@ impl Sim { orientation: na::UnitQuaternion::identity(), }, })); - entity_builder.add(Inventory { contents: vec![] }); + } + ComponentType::Material => { + let material: u16 = u16::from_le_bytes(component_bytes.try_into().unwrap()); + entity_builder.add(Material::try_from(material)?); + } + ComponentType::Inventory => { + let mut contents = vec![]; + for chunk in component_bytes.chunks(8) { + contents.push(EntityId::from_bits(u64::from_le_bytes( + chunk.try_into().unwrap(), + ))); + } + entity_builder.add(Inventory { contents }); } } Ok(()) @@ -242,42 +254,69 @@ impl Sim { fn snapshot_node(&self, node: NodeId) -> save::EntityNode { let mut entities = Vec::new(); for &entity in self.graph_entities.get(node) { - let Ok(entity) = self.world.entity(entity) else { - error!("stale graph entity {:?}", entity); - continue; - }; - let Some(id) = entity.get::<&EntityId>() else { - continue; - }; - let mut components = Vec::new(); - if let Some(pos) = entity.get::<&Position>() { - components.push(( - ComponentType::Position as u64, - postcard::to_stdvec(&pos.local.as_ref()).unwrap(), - )); - } - if let Some(ch) = entity.get::<&Character>().or_else(|| { - entity - .get::<&InactiveCharacter>() - .map(|ich| hecs::Ref::map(ich, |ich| &ich.0)) // Extract Ref from Ref - }) { - components.push((ComponentType::Name as u64, ch.name.as_bytes().into())); - } - let mut repr = Vec::new(); - postcard_helpers::serialize( - &SaveEntity { - entity: id.to_bits().to_le_bytes(), - components, - }, - &mut repr, - ) - .unwrap(); - entities.push(repr); + let reprs = self.snapshot_entity_and_children(entity); + entities.extend_from_slice(&reprs); } save::EntityNode { entities } } + fn snapshot_entity_and_children(&self, entity: Entity) -> Vec> { + let mut reprs = vec![]; + let Ok(entity) = self.world.entity(entity) else { + error!("stale graph entity {:?}", entity); + return reprs; + }; + let Some(id) = entity.get::<&EntityId>() else { + return reprs; + }; + let mut components = Vec::new(); + if let Some(pos) = entity.get::<&Position>() { + components.push(( + ComponentType::Position as u64, + postcard::to_stdvec(&pos.local.as_ref()).unwrap(), + )); + } + if let Some(ch) = entity.get::<&Character>().or_else(|| { + entity + .get::<&InactiveCharacter>() + .map(|ich| hecs::Ref::map(ich, |ich| &ich.0)) // Extract Ref from Ref + }) { + components.push((ComponentType::Name as u64, ch.name.as_bytes().into())); + } + if let Some(material) = entity.get::<&Material>() { + components.push(( + ComponentType::Material as u64, + (*material as u16).to_le_bytes().into(), + )); + } + if let Some(inventory) = entity.get::<&Inventory>() { + let mut serialized_inventory_contents = vec![]; + for entity_id in &inventory.contents { + reprs.extend_from_slice( + &self.snapshot_entity_and_children(self.entity_ids[entity_id]), + ); + serialized_inventory_contents.extend_from_slice(&entity_id.to_bits().to_le_bytes()); + } + components.push(( + ComponentType::Inventory as u64, + serialized_inventory_contents, + )); + } + let mut repr = Vec::new(); + postcard_helpers::serialize( + &SaveEntity { + entity: id.to_bits().to_le_bytes(), + components, + }, + &mut repr, + ) + .unwrap(); + + reprs.push(repr); + reprs + } + fn snapshot_voxel_node(&self, node: NodeId) -> save::VoxelNode { let mut chunks = vec![]; let node_data = self.graph.get(node).as_ref().unwrap();