Skip to content

Commit

Permalink
Serialize the player's inventory in save files
Browse files Browse the repository at this point in the history
  • Loading branch information
patowen committed Nov 27, 2024
1 parent 8755237 commit c1b6e2a
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 24 deletions.
18 changes: 16 additions & 2 deletions common/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16> for Material {
type Error = ();
type Error = MaterialOutOfBounds;

fn try_from(value: u16) -> Result<Self, Self::Error> {
Material::VALUES.get(value as usize).ok_or(()).copied()
Material::VALUES
.get(value as usize)
.ok_or(MaterialOutOfBounds)
.copied()
}
}

Expand Down
2 changes: 1 addition & 1 deletion save/gen-protos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ edition = "2021"
publish = false

[dependencies]
prost-build = "0.13.1"
prost-build = "0.13.3"
4 changes: 4 additions & 0 deletions save/src/protos.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ enum ComponentType {
POSITION = 0;
// UTF-8 text
NAME = 1;
// u16
MATERIAL = 2;
// List of u64
INVENTORY = 3;
}
12 changes: 10 additions & 2 deletions save/src/protos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -48,15 +52,19 @@ 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.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"POSITION" => Some(Self::Position),
"NAME" => Some(Self::Name),
"MATERIAL" => Some(Self::Material),
"INVENTORY" => Some(Self::Inventory),
_ => None,
}
}
Expand Down
80 changes: 61 additions & 19 deletions server/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -399,14 +432,13 @@ 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);
}
self.world.despawn(entity).unwrap();

if !self.world.satisfies::<&InactiveCharacter>(entity).unwrap() {
self.accumulated_changes.despawns.push(id);
}
self.world.despawn(entity).unwrap();
}

/// Collect information about all entities, for transmission to new clients
Expand Down Expand Up @@ -490,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,
Expand All @@ -508,19 +539,26 @@ 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 {
let id = *self.world.get::<&EntityId>(entity).unwrap();
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,
Expand Down Expand Up @@ -613,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)
Expand Down Expand Up @@ -645,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);
}
}
Expand Down

0 comments on commit c1b6e2a

Please sign in to comment.