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/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 d05816b8..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. @@ -50,6 +54,8 @@ impl ComponentType { match self { 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 b048b520..e552853e 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -147,6 +147,7 @@ impl Sim { let entity_id = EntityId::from_bits(u64::from_le_bytes(save_entity.entity)); let mut entity_builder = EntityBuilder::new(); entity_builder.add(entity_id); + entity_builder.add(node); for (component_type, component_bytes) in save_entity.components { self.load_component( read, @@ -210,7 +211,22 @@ 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().map_err(|_| { + anyhow::anyhow!("Expected Material component in save file to be 2 bytes") + })?); + 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(()) @@ -263,6 +279,23 @@ impl Sim { }) { 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 { + 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 { @@ -344,7 +377,7 @@ impl Sim { }; let inventory = Inventory { contents: vec![] }; let initial_input = CharacterInput::default(); - Some(self.spawn((position, character, inventory, initial_input))) + Some(self.spawn((position.node, position, character, inventory, initial_input))) } pub fn deactivate_character(&mut self, entity: Entity) { @@ -366,9 +399,9 @@ impl Sim { entity_builder.add_bundle(bundle); let entity = self.world.spawn(entity_builder.build()); - if let Ok(position) = self.world.get::<&Position>(entity) { - self.graph_entities.insert(position.node, entity); - self.dirty_nodes.insert(position.node); + if let Ok(node) = self.world.get::<&NodeId>(entity) { + self.graph_entities.insert(*node, entity); + self.dirty_nodes.insert(*node); } if let Ok(character) = self.world.get::<&Character>(entity) { @@ -399,8 +432,8 @@ impl Sim { pub fn destroy(&mut self, entity: Entity) { let id = *self.world.get::<&EntityId>(entity).unwrap(); self.entity_ids.remove(&id); - if let Ok(position) = self.world.get::<&Position>(entity) { - self.graph_entities.remove(position.node, entity); + if let Ok(node) = self.world.get::<&NodeId>(entity) { + self.graph_entities.remove(*node, entity); } if !self.world.satisfies::<&InactiveCharacter>(entity).unwrap() { self.accumulated_changes.despawns.push(id); @@ -489,12 +522,11 @@ impl Sim { let mut pending_block_updates: Vec<(Entity, BlockUpdate)> = vec![]; // Simulate - for (entity, (position, character, input)) in self + for (entity, (node, position, character, input)) in self .world - .query::<(&mut Position, &mut Character, &CharacterInput)>() + .query::<(&NodeId, &mut Position, &mut Character, &CharacterInput)>() .iter() { - let prev_node = position.node; character_controller::run_character_step( &self.cfg, &self.graph, @@ -507,12 +539,7 @@ impl Sim { if let Some(block_update) = input.block_update.clone() { pending_block_updates.push((entity, block_update)); } - if prev_node != position.node { - self.dirty_nodes.insert(prev_node); - self.graph_entities.remove(prev_node, entity); - self.graph_entities.insert(position.node, entity); - } - self.dirty_nodes.insert(position.node); + self.dirty_nodes.insert(*node); } for (entity, block_update) in pending_block_updates { @@ -520,6 +547,18 @@ impl Sim { self.attempt_block_update(id, block_update); } + // Synchronize NodeId and Position + for (entity, (node_id, position)) in self.world.query::<(&mut NodeId, &Position)>().iter() { + if *node_id != position.node { + self.dirty_nodes.insert(*node_id); + self.graph_entities.remove(*node_id, entity); + + *node_id = position.node; + self.dirty_nodes.insert(*node_id); + self.graph_entities.insert(*node_id, entity); + } + } + let spawns = std::mem::take(&mut self.accumulated_changes).into_spawns( self.step, &self.world, @@ -612,6 +651,10 @@ impl Sim { /// Executes the requested block update if the subject is able to do so and /// leaves the state of the world unchanged otherwise fn attempt_block_update(&mut self, subject: EntityId, block_update: BlockUpdate) { + let subject_node = *self + .world + .get::<&NodeId>(*self.entity_ids.get(&subject).unwrap()) + .unwrap(); let Some(old_material) = self .graph .get_material(block_update.chunk_id, block_update.coords) @@ -644,7 +687,7 @@ impl Sim { self.destroy(consumed_entity); } if old_material != Material::Void { - let (produced_entity, _) = self.spawn((old_material,)); + let (produced_entity, _) = self.spawn((subject_node, old_material)); self.add_to_inventory(subject, produced_entity); } }