Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Player inventory (initial version) #408

Merged
merged 4 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion client/src/graphics/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,17 @@ 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()));
let material_count_string = if sim.cfg.gameplay_enabled {
sim.count_inventory_entities_matching_material(sim.selected_material())
.to_string()
} else {
"∞".to_string()
};
label(format!(
"Selected material: {:?} (×{})",
sim.selected_material(),
material_count_string
));
});
});
});
Expand Down
72 changes: 71 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,48 @@ impl Sim {
self.selected_material
}

/// Returns an EntityId in the inventory with the given material
pub fn get_any_inventory_entity_matching_material(
&self,
material: Material,
) -> Option<EntityId> {
self.world
.get::<&Inventory>(self.local_character?)
.ok()?
.contents
.iter()
.copied()
.find(|e| {
self.entity_ids.get(e).is_some_and(|&entity| {
self.world
.get::<&Material>(entity)
.is_ok_and(|m| *m == material)
})
})
}

/// Returns the number of entities in the inventory with the given material
pub fn count_inventory_entities_matching_material(&self, material: Material) -> usize {
let Some(local_character) = self.local_character else {
return 0;
};
let Ok(inventory) = self.world.get::<&Inventory>(local_character) else {
return 0;
};
inventory
.contents
.iter()
.copied()
.filter(|e| {
self.entity_ids.get(e).is_some_and(|&entity| {
self.world
.get::<&Material>(entity)
.is_ok_and(|m| *m == material)
})
})
.count()
}

pub fn set_break_block_pressed_true(&mut self) {
self.break_block_pressed = true;
}
Expand Down Expand Up @@ -346,6 +389,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 +424,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 +578,17 @@ impl Sim {
Material::Void
};

let consumed_entity = if placing && self.cfg.gameplay_enabled {
Some(self.get_any_inventory_entity_matching_material(material)?)
} 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;
};
patowen marked this conversation as resolved.
Show resolved Hide resolved
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>,
}
27 changes: 24 additions & 3 deletions common/src/sim_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub struct SimConfigRaw {
/// Maximum distance at which anything can be seen in meters
pub view_distance: Option<f32>,
pub input_queue_size_ms: Option<u16>,
/// Whether gameplay-like restrictions exist, such as limited inventory
pub gameplay_enabled: Option<bool>,
/// Number of voxels along the edge of a chunk
pub chunk_size: Option<u8>,
/// Approximate length of the edge of a voxel in meters
Expand All @@ -34,9 +36,14 @@ pub struct SimConfigRaw {
pub struct SimConfig {
/// Amount of time between each step. Inverse of the rate
pub step_interval: Duration,
/// Maximum distance at which anything can be seen in absolute units
pub view_distance: f32,
pub input_queue_size: Duration,
/// Whether gameplay-like restrictions exist, such as limited inventory
pub gameplay_enabled: bool,
patowen marked this conversation as resolved.
Show resolved Hide resolved
/// Number of voxels along the edge of a chunk
pub chunk_size: u8,
/// Static configuration information relevant to character physics
pub character: CharacterConfig,
/// Scaling factor converting meters to absolute units
pub meters_to_absolute: f32,
Expand All @@ -51,6 +58,7 @@ impl SimConfig {
step_interval: Duration::from_secs(1) / x.rate.unwrap_or(30) as u32,
view_distance: x.view_distance.unwrap_or(90.0) * meters_to_absolute,
input_queue_size: Duration::from_millis(x.input_queue_size_ms.unwrap_or(50).into()),
gameplay_enabled: x.gameplay_enabled.unwrap_or(false),
chunk_size,
character: CharacterConfig::from_raw(&x.character, meters_to_absolute),
meters_to_absolute,
Expand Down Expand Up @@ -89,28 +97,41 @@ pub struct CharacterConfigRaw {
pub air_resistance: Option<f32>,
/// How fast the player jumps off the ground in m/s
pub jump_speed: Option<f32>,
/// How far away the player needs to be from the ground in meters to be considered in the air
/// How far away the player needs to be from the ground in meters to be considered in the air in meters
pub ground_distance_tolerance: Option<f32>,
/// Radius of the character in meters
pub character_radius: Option<f32>,
/// How far a character can reach when placing blocks
/// How far a character can reach when placing blocks in meters
pub block_reach: Option<f32>,
}

/// Static configuration information relevant to character physics
/// Static configuration information relevant to character physics. Most fields are based on
/// absolute units and seconds.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CharacterConfig {
/// Character movement speed in units/s during no-clip
pub no_clip_movement_speed: f32,
/// Character maximumum movement speed while on the ground in units/s
pub max_ground_speed: f32,
/// Character artificial speed cap to avoid overloading the server in units/s
pub speed_cap: f32,
/// Maximum ground slope (0=horizontal, 1=45 degrees)
pub max_ground_slope: f32,
/// Character acceleration while on the ground in units/s^2
pub ground_acceleration: f32,
/// Character acceleration while in the air in units/s^2
pub air_acceleration: f32,
/// Acceleration of gravity in units/s^2
pub gravity_acceleration: f32,
/// Air resistance in (units/s^2) per (units/s); scales linearly with respect to speed
pub air_resistance: f32,
/// How fast the player jumps off the ground in units/s
pub jump_speed: f32,
/// How far away the player needs to be from the ground in meters to be considered in the air in absolute units
pub ground_distance_tolerance: f32,
/// Radius of the character in absolute units
pub character_radius: f32,
/// How far a character can reach when placing blocks in absolute units
pub block_reach: f32,
}

Expand Down
11 changes: 3 additions & 8 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,15 @@ impl Server {

// Step the simulation
let (spawns, delta) = self.sim.step();
let spawns = Arc::new(spawns);
let spawns = spawns.map(Arc::new);
let mut overran = Vec::new();
for (client_id, client) in &mut self.clients {
if let Some(ref mut handles) = client.handles {
let mut delta = delta.clone();
delta.latest_input = client.latest_input_processed;
let r1 = handles.unordered.try_send(delta);
let r2 = if !spawns.spawns.is_empty()
|| !spawns.despawns.is_empty()
|| !spawns.nodes.is_empty()
|| !spawns.block_updates.is_empty()
|| !spawns.voxel_data.is_empty()
{
handles.ordered.try_send(spawns.clone())
let r2 = if let Some(spawns) = spawns.as_ref() {
handles.ordered.try_send(Arc::clone(spawns))
} else {
Ok(())
};
Expand Down
Loading
Loading