Skip to content

Commit

Permalink
Give the player an inventory
Browse files Browse the repository at this point in the history
Breaking blocks adds them to the inventory, and placing blocks removes
them from the inventory, failing if no such item can be found.
  • Loading branch information
patowen committed Jun 23, 2024
1 parent 042336b commit 26cd7d1
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 18 deletions.
7 changes: 6 additions & 1 deletion client/src/graphics/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ impl GuiState {
align(Alignment::TOP_LEFT, || {
pad(Pad::all(8.0), || {
colored_box_container(Color::BLACK.with_alpha(0.7), || {
label(format!("Selected material: {:?}", sim.selected_material()));
label(format!(
"Selected material: {:?} (\u{00D7}{})", // \u{00D7} is the multiplication synbol
sim.selected_material(),
sim.inventory_contents_matching_material(sim.selected_material())
.len()
));
});
});
});
Expand Down
57 changes: 56 additions & 1 deletion client/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use common::{
graph_ray_casting,
node::{populate_fresh_nodes, ChunkId, VoxelData},
proto::{
self, BlockUpdate, Character, CharacterInput, CharacterState, Command, Component, Position,
self, BlockUpdate, Character, CharacterInput, CharacterState, Command, Component,
Inventory, Position,
},
sanitize_motion_input,
world::Material,
Expand Down Expand Up @@ -165,6 +166,29 @@ impl Sim {
self.selected_material
}

/// Return the list of EntityIds corresponding to all inventory items matching the given material
pub fn inventory_contents_matching_material(&self, material: Material) -> Vec<EntityId> {
let Some(local_character) = self.local_character else {
return vec![];
};
let Ok(inventory) = self.world.get::<&Inventory>(local_character) else {
return vec![];
};
inventory
.contents
.iter()
.copied()
.filter(|e| {
let Some(entity) = self.entity_ids.get(e) else {
return false;
};
self.world
.get::<&Material>(*entity)
.is_ok_and(|m| *m == material)
})
.collect()
}

pub fn set_break_block_pressed_true(&mut self) {
self.break_block_pressed = true;
}
Expand Down Expand Up @@ -346,6 +370,20 @@ impl Sim {
};
self.graph.populate_chunk(chunk_id, voxel_data);
}
for (subject, new_entity) in msg.inventory_additions {
self.world
.get::<&mut Inventory>(*self.entity_ids.get(&subject).unwrap())
.unwrap()
.contents
.push(new_entity);
}
for (subject, removed_entity) in msg.inventory_removals {
self.world
.get::<&mut Inventory>(*self.entity_ids.get(&subject).unwrap())
.unwrap()
.contents
.retain(|&id| id != removed_entity);
}
}

fn spawn(
Expand All @@ -367,6 +405,12 @@ impl Sim {
node = Some(x.node);
builder.add(x);
}
Inventory(x) => {
builder.add(x);
}
Material(x) => {
builder.add(x);
}
};
}
let entity = self.world.spawn(builder.build());
Expand Down Expand Up @@ -515,10 +559,21 @@ impl Sim {
Material::Void
};

let consumed_entity = if placing {
Some(
*self
.inventory_contents_matching_material(material)
.first()?,
)
} else {
None
};

Some(BlockUpdate {
chunk_id: block_pos.0,
coords: block_pos.1,
new_material: material,
consumed_entity,
})
}
}
10 changes: 10 additions & 0 deletions common/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ impl Graph {
};
}

/// Returns the material at the specified coordinates of the specified chunk, if the chunk is generated
pub fn get_material(&self, chunk_id: ChunkId, coords: Coords) -> Option<Material> {
let dimension = self.layout().dimension;

let Some(Chunk::Populated { voxels, .. }) = self.get_chunk(chunk_id) else {
return None;
};
Some(voxels.get(coords.to_index(dimension)))
}

/// Tries to update the block at the given position to the given material.
/// Fails and returns false if the chunk is not populated yet.
#[must_use]
Expand Down
10 changes: 10 additions & 0 deletions common/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub struct Spawns {
pub nodes: Vec<FreshNode>,
pub block_updates: Vec<BlockUpdate>,
pub voxel_data: Vec<(ChunkId, SerializedVoxelData)>,
pub inventory_additions: Vec<(EntityId, EntityId)>,
pub inventory_removals: Vec<(EntityId, EntityId)>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -78,6 +80,7 @@ pub struct BlockUpdate {
pub chunk_id: ChunkId,
pub coords: Coords,
pub new_material: Material,
pub consumed_entity: Option<EntityId>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -90,6 +93,8 @@ pub struct SerializedVoxelData {
pub enum Component {
Character(Character),
Position(Position),
Material(Material),
Inventory(Inventory),
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -104,3 +109,8 @@ pub struct Character {
pub name: String,
pub state: CharacterState,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Inventory {
pub contents: Vec<EntityId>,
}
2 changes: 2 additions & 0 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ impl Server {
|| !spawns.nodes.is_empty()
|| !spawns.block_updates.is_empty()
|| !spawns.voxel_data.is_empty()
|| !spawns.inventory_additions.is_empty()
|| !spawns.inventory_removals.is_empty()
{
handles.ordered.try_send(spawns.clone())
} else {
Expand Down
139 changes: 123 additions & 16 deletions server/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ use std::sync::Arc;
use anyhow::Context;
use common::dodeca::Vertex;
use common::node::VoxelData;
use common::proto::{BlockUpdate, SerializedVoxelData};
use common::proto::{BlockUpdate, Inventory, SerializedVoxelData};
use common::world::Material;
use common::{node::ChunkId, GraphEntities};
use fxhash::{FxHashMap, FxHashSet};
use hecs::Entity;
use hecs::{DynamicBundle, Entity, EntityBuilder};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use save::ComponentType;
Expand Down Expand Up @@ -185,8 +186,6 @@ impl Sim {
}

pub fn spawn_character(&mut self, hello: ClientHello) -> (EntityId, Entity) {
let id = self.new_id();
info!(%id, name = %hello.name, "spawning character");
let position = Position {
node: NodeId::ROOT,
local: math::translate_along(&(na::Vector3::y() * 1.4)),
Expand All @@ -199,17 +198,35 @@ impl Sim {
on_ground: false,
},
};
let inventory = Inventory { contents: vec![] };
let initial_input = CharacterInput {
movement: na::Vector3::zeros(),
jump: false,
no_clip: true,
block_update: None,
};
let entity = self.world.spawn((id, position, character, initial_input));
self.graph_entities.insert(position.node, entity);
self.spawn((position, character, inventory, initial_input))
}

fn spawn(&mut self, bundle: impl DynamicBundle) -> (EntityId, Entity) {
let id = self.new_id();
let mut entity_builder = EntityBuilder::new();
entity_builder.add(id);
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(character) = self.world.get::<&Character>(entity) {
info!(%id, name = %character.name, "spawning character");
}

self.entity_ids.insert(id, entity);
self.accumulated_changes.spawns.push(entity);
self.dirty_nodes.insert(position.node);

(id, entity)
}

Expand Down Expand Up @@ -248,6 +265,8 @@ impl Sim {
.collect(),
block_updates: Vec::new(),
voxel_data: Vec::new(),
inventory_additions: Vec::new(),
inventory_removals: Vec::new(),
};
for (entity, &id) in &mut self.world.query::<&EntityId>() {
spawns.spawns.push((id, dump_entity(&self.world, entity)));
Expand Down Expand Up @@ -322,7 +341,7 @@ impl Sim {
}
}

let mut pending_block_updates: Vec<BlockUpdate> = vec![];
let mut pending_block_updates: Vec<(Entity, BlockUpdate)> = vec![];

// Simulate
for (entity, (position, character, input)) in self
Expand All @@ -340,7 +359,9 @@ impl Sim {
input,
self.cfg.step_interval.as_secs_f32(),
);
pending_block_updates.extend(input.block_update.iter().cloned());
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);
Expand All @@ -349,13 +370,9 @@ impl Sim {
self.dirty_nodes.insert(position.node);
}

for block_update in pending_block_updates.into_iter() {
if !self.graph.update_block(&block_update) {
tracing::warn!("Block update received from ungenerated chunk");
}
self.modified_chunks.insert(block_update.chunk_id);
self.dirty_voxel_nodes.insert(block_update.chunk_id.node);
self.accumulated_changes.block_updates.push(block_update);
for (entity, block_update) in pending_block_updates {
let id = *self.world.get::<&EntityId>(entity).unwrap();
self.attempt_block_update(id, block_update);
}

let accumulated_changes = std::mem::take(&mut self.accumulated_changes);
Expand Down Expand Up @@ -415,6 +432,8 @@ impl Sim {
.collect(),
block_updates: accumulated_changes.block_updates,
voxel_data: accumulated_changes.fresh_voxel_data,
inventory_additions: accumulated_changes.inventory_additions,
inventory_removals: accumulated_changes.inventory_removals,
}
}

Expand All @@ -426,6 +445,80 @@ impl Sim {
}
}
}

/// Add the given entity to the given inventory
fn add_to_inventory(&mut self, inventory_id: EntityId, entity_id: EntityId) {
let mut inventory = self
.world
.get::<&mut Inventory>(*self.entity_ids.get(&inventory_id).unwrap())
.unwrap();
inventory.contents.push(entity_id);
self.accumulated_changes
.inventory_additions
.push((inventory_id, entity_id));
}

/// Remove the given entity from the given inventory. Note that this does not destroy the entity.
fn remove_from_inventory(&mut self, inventory_id: EntityId, entity_id: EntityId) {
let mut inventory = self
.world
.get::<&mut Inventory>(*self.entity_ids.get(&inventory_id).unwrap())
.unwrap();
inventory.contents.retain_mut(|e| *e != entity_id);
self.accumulated_changes
.inventory_removals
.push((inventory_id, entity_id));
}

/// Check if the given entity is in the given inventory
fn is_in_inventory(&self, inventory_id: EntityId, entity_id: EntityId) -> bool {
let inventory = self
.world
.get::<&Inventory>(*self.entity_ids.get(&inventory_id).unwrap())
.unwrap();
inventory.contents.contains(&entity_id)
}

/// 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 Some(old_material) = self
.graph
.get_material(block_update.chunk_id, block_update.coords)
else {
tracing::warn!("Block update received from ungenerated chunk");
return;
};
if block_update.new_material != Material::Void {
let Some(consumed_entity_id) = block_update.consumed_entity else {
tracing::warn!("Tried to place block without consuming any entities");
return;
};
if !self.is_in_inventory(subject, consumed_entity_id) {
tracing::warn!("Tried to consume entity not in player inventory");
return;
};
let consumed_entity = *self.entity_ids.get(&consumed_entity_id).unwrap();
if !self
.world
.get::<&Material>(consumed_entity)
.is_ok_and(|m| *m == block_update.new_material)
{
tracing::warn!("Tried to consume wrong material");
return;
}
self.remove_from_inventory(subject, consumed_entity_id);
self.destroy(consumed_entity);
}
if old_material != Material::Void {
let (produced_entity, _) = self.spawn((old_material,));
self.add_to_inventory(subject, produced_entity);
}
assert!(self.graph.update_block(&block_update));
self.modified_chunks.insert(block_update.chunk_id);
self.dirty_voxel_nodes.insert(block_update.chunk_id.node);
self.accumulated_changes.block_updates.push(block_update);
}
}

fn dump_entity(world: &hecs::World, entity: Entity) -> Vec<Component> {
Expand All @@ -436,6 +529,12 @@ fn dump_entity(world: &hecs::World, entity: Entity) -> Vec<Component> {
if let Ok(x) = world.get::<&Character>(entity) {
components.push(Component::Character((*x).clone()));
}
if let Ok(x) = world.get::<&Inventory>(entity) {
components.push(Component::Inventory((*x).clone()));
}
if let Ok(x) = world.get::<&Material>(entity) {
components.push(Component::Material(*x));
}
components
}

Expand All @@ -451,6 +550,14 @@ struct AccumulatedChanges {
/// Block updates that have been applied to the world since the last broadcast
block_updates: Vec<BlockUpdate>,

/// Entities that have been added to an inventory since the last broadcast, where `(a, b)`` represents
/// entity `b`` being added to inventory `a``
inventory_additions: Vec<(EntityId, EntityId)>,

/// Entities that have been removed from an inventory since the last broadcast, where `(a, b)`` represents
/// entity `b`` being removed from inventory `a``
inventory_removals: Vec<(EntityId, EntityId)>,

/// Nodes that have been added to the graph since the last broadcast
fresh_nodes: Vec<NodeId>,

Expand Down

0 comments on commit 26cd7d1

Please sign in to comment.