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::(); 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); #[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) -> Option { 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) -> Option { self.get(pos).map(|block| self.commands.entity(block)) } /// Spawns a block at the given position. pub fn spawn(&mut self, pos: impl Into) -> 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) -> Result { 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) -> 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 { self.blocks.get(entity).ok().map(|block| *block.1) } } fn block_added( event: On, blocks: Query<(&Block, &BlockPos)>, mut map: ResMut, server: Option>, 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, blocks: Query<(&Block, &BlockPos)>, mut map: ResMut, ) { let block = event.entity; let (_, pos) = blocks.get(block).expect("Block is missing BlockPos"); map.0.remove(pos); }