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
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); |
|
}
|
|
|