Bloxel sandbox game similar to Minecraft "Classic" (2009) written in Rust with Bevy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

96 lines
3.0 KiB

use bevy::prelude::*;
use lightyear::prelude::*;
use bevy::ecs::system::SystemParam;
use bevy::platform::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::math::BlockPos;
pub(crate) fn plugin(app: &mut App) {
app.init_resource::<BlockMap>();
app.add_observer(block_added);
app.add_observer(block_removed);
}
#[derive(Component, Deserialize, Serialize, PartialEq)]
pub struct Block;
#[derive(Resource, Default)]
pub struct BlockMap(HashMap<BlockPos, Entity>);
#[derive(SystemParam)]
pub struct Blocks<'w, 's> {
map: Res<'w, BlockMap>,
blocks: Query<'w, 's, (&'static Block, &'static BlockPos)>,
commands: Commands<'w, 's>,
}
impl Blocks<'_, '_> {
/// Gets the block [`Entity`] at the given position, if any.
pub fn get(&self, pos: impl Into<BlockPos>) -> Option<Entity> {
self.map.0.get(&pos.into()).copied()
}
/// Gets an [`EntityCommands`] for the block at the given position, if any.
pub fn entity(&mut self, pos: impl Into<BlockPos>) -> Option<EntityCommands> {
self.get(pos).map(|block| self.commands.entity(block))
}
/// Spawns a block at the given position.
pub fn spawn(&mut self, pos: impl Into<BlockPos>) -> EntityCommands {
self.commands.spawn((Block, pos.into()))
}
/// Tries to spawn a block at the given position, as long as there isn't one already.
/// If there already is a block, its [`Entity`] is returned as the `Err` variant.
pub fn try_spawn(&mut self, pos: impl Into<BlockPos>) -> Result<EntityCommands, Entity> {
let pos = pos.into(); // required because we use it twice
if let Some(existing) = self.get(pos) {
Err(existing)
} else {
Ok(self.spawn(pos))
}
}
/// Despawns the block at the given position, returning if successful.
pub fn despawn(&mut self, pos: impl Into<BlockPos>) -> bool {
self.entity(pos).map(|mut block| block.despawn()).is_some()
}
/// Gets the position of the given block [`Entity`], if it exists and is a [`Block`].
pub fn position(&self, entity: Entity) -> Option<BlockPos> {
self.blocks.get(entity).ok().map(|block| *block.1)
}
}
fn block_added(
event: On<Add, Block>,
blocks: Query<(&Block, &BlockPos)>,
mut map: ResMut<BlockMap>,
server: Option<Single<&Server>>,
mut commands: Commands,
) {
let block = event.entity;
let (_, pos) = blocks.get(block).expect("Block is missing BlockPos");
if map.0.insert(*pos, block).is_some() {
// TODO: This IS going to happen occasionally.
warn!("Duplicate block at pos {pos}");
}
let mut entity = commands.entity(block);
entity.insert(Transform::from_translation(pos.center()));
if server.is_some() {
entity.insert(Replicate::to_clients(NetworkTarget::All));
}
}
fn block_removed(
event: On<Remove, Block>,
blocks: Query<(&Block, &BlockPos)>,
mut map: ResMut<BlockMap>,
) {
let block = event.entity;
let (_, pos) = blocks.get(block).expect("Block is missing BlockPos");
map.0.remove(pos);
}