diff --git a/Cargo.lock b/Cargo.lock index 66b5c65f2..6ea72e55a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -753,6 +753,7 @@ dependencies = [ "arrayvec 0.7.1", "bitflags", "bitvec", + "bytemuck", "byteorder", "feather-blocks", "feather-generated", diff --git a/feather/base/Cargo.toml b/feather/base/Cargo.toml index f225273f0..57cb61253 100644 --- a/feather/base/Cargo.toml +++ b/feather/base/Cargo.toml @@ -32,6 +32,7 @@ smallvec = "1" thiserror = "1" uuid = { version = "0.8", features = [ "serde" ] } vek = "0.14" +bytemuck = { version = "1", features = ["derive"] } [dev-dependencies] rand = "0.8" diff --git a/feather/base/src/block.rs b/feather/base/src/block.rs new file mode 100644 index 000000000..be84a2049 --- /dev/null +++ b/feather/base/src/block.rs @@ -0,0 +1,163 @@ +use bytemuck::{Pod, Zeroable}; +use serde::{Deserialize, Serialize}; + +use thiserror::Error; + +use libcraft_core::{BlockPosition, ChunkPosition, Position}; +use std::convert::TryFrom; + +/// Validated position of a block. +/// +/// This structure is immutable. +/// All operations that change a [`ValidBlockPosition`] must be done by +/// turning it into a [`BlockPosition`], performing said operations, +/// then using [`ValidBlockPosition`]'s [`TryFrom`] impl to get a [`ValidBlockPosition`]. +/// +/// The definition of a valid block position is defined by [`BlockPosition::valid`]. +/// +/// # Examples +/// +/// Converting a [`BlockPosition`] to a [`ValidBlockPosition`], unwrapping any errors that +/// occur. +/// ``` +/// # use feather_base::BlockPosition; +/// # use feather_base::ValidBlockPosition; +/// # use std::convert::TryInto; +/// // Create an unvalidated block position +/// let block_position = BlockPosition::new(727, 32, 727); +/// +/// // Validate the block position and unwrap any errors +/// let valid_block_position: ValidBlockPosition = block_position.try_into().unwrap(); +/// ``` +/// +/// Performing operations on a [`ValidBlockPosition`], then re-validating it. +/// ``` +/// # use feather_base::BlockPosition; +/// # use feather_base::ValidBlockPosition; +/// # use std::convert::TryInto; +/// # let mut valid_block_position: ValidBlockPosition = BlockPosition::new(727, 32, 727).try_into().unwrap(); +/// // Convert the ValidBlockPosition into an unvalidated one to perform math +/// let mut block_position: BlockPosition = valid_block_position.into(); +/// +/// block_position.x = 821; +/// block_position.z += 32; +/// +/// assert!(block_position.valid()); +/// +/// valid_block_position = block_position.try_into().unwrap(); +/// ``` +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Default, + Serialize, + Deserialize, + Zeroable, + Pod, +)] +#[repr(C)] +pub struct ValidBlockPosition { + x: i32, + y: i32, + z: i32, +} + +impl ValidBlockPosition { + pub fn x(&self) -> i32 { + self.x + } + + pub fn y(&self) -> i32 { + self.y + } + + pub fn z(&self) -> i32 { + self.z + } + + pub fn chunk(self) -> ChunkPosition { + self.into() + } + + pub fn position(self) -> Position { + self.into() + } +} + +impl TryFrom for ValidBlockPosition { + type Error = BlockPositionValidationError; + + fn try_from(value: BlockPosition) -> Result { + if value.valid() { + Ok(ValidBlockPosition { + x: value.x, + y: value.y, + z: value.z, + }) + } else { + Err(BlockPositionValidationError::OutOfRange(value)) + } + } +} + +impl From for BlockPosition { + fn from(position: ValidBlockPosition) -> Self { + BlockPosition { + x: position.x, + y: position.y, + z: position.z, + } + } +} + +impl From for ChunkPosition { + fn from(position: ValidBlockPosition) -> Self { + let position: BlockPosition = position.into(); + position.into() + } +} + +impl From for Position { + fn from(position: ValidBlockPosition) -> Self { + let position: BlockPosition = position.into(); + position.into() + } +} + +#[derive(Error, Debug)] +pub enum BlockPositionValidationError { + #[error("coordinate {0:?} out of range")] + OutOfRange(BlockPosition), +} + +#[cfg(test)] +mod tests { + + use std::convert::TryInto; + + use libcraft_core::BlockPosition; + + use crate::ValidBlockPosition; + + #[test] + #[should_panic] + fn check_out_of_bounds_up() { + let block_position = BlockPosition::new(0, 39483298, 0); + + >::try_into(block_position).unwrap(); + } + + #[test] + #[should_panic] + fn check_out_of_bounds_down() { + let block_position = BlockPosition::new(0, -39483298, 0); + + >::try_into(block_position).unwrap(); + } +} diff --git a/feather/base/src/lib.rs b/feather/base/src/lib.rs index b4fc81c4d..b48cd0cca 100644 --- a/feather/base/src/lib.rs +++ b/feather/base/src/lib.rs @@ -10,11 +10,13 @@ use num_derive::{FromPrimitive, ToPrimitive}; use serde::{Deserialize, Serialize}; pub mod anvil; +mod block; pub mod chunk; pub mod chunk_lock; pub mod inventory; pub mod metadata; +pub use block::{BlockPositionValidationError, ValidBlockPosition}; pub use blocks::*; pub use chunk::{Chunk, ChunkSection, CHUNK_HEIGHT, CHUNK_WIDTH}; pub use chunk_lock::*; diff --git a/feather/base/src/metadata.rs b/feather/base/src/metadata.rs index 664e13ac5..bf5d513a5 100644 --- a/feather/base/src/metadata.rs +++ b/feather/base/src/metadata.rs @@ -2,7 +2,7 @@ //! metadata format. See //! for the specification. -use crate::{BlockPosition, Direction}; +use crate::{Direction, ValidBlockPosition}; use bitflags::bitflags; use generated::ItemStack; use std::collections::BTreeMap; @@ -47,8 +47,8 @@ pub enum MetaEntry { Slot(Option), Boolean(bool), Rotation(f32, f32, f32), - Position(BlockPosition), - OptPosition(Option), + Position(ValidBlockPosition), + OptPosition(Option), Direction(Direction), OptUuid(OptUuid), OptBlockId(Option), @@ -131,7 +131,7 @@ impl ToMetaEntry for bool { } } -impl ToMetaEntry for BlockPosition { +impl ToMetaEntry for ValidBlockPosition { fn to_meta_entry(&self) -> MetaEntry { MetaEntry::Position(*self) } diff --git a/feather/common/src/events/block_change.rs b/feather/common/src/events/block_change.rs index c6041a831..bb92ba52d 100644 --- a/feather/common/src/events/block_change.rs +++ b/feather/common/src/events/block_change.rs @@ -1,8 +1,8 @@ -use std::iter; +use std::{convert::TryInto, iter}; use base::{ chunk::{SECTION_HEIGHT, SECTION_VOLUME}, - BlockPosition, ChunkPosition, + BlockPosition, ChunkPosition, ValidBlockPosition, }; use itertools::Either; @@ -18,7 +18,7 @@ pub struct BlockChangeEvent { impl BlockChangeEvent { /// Creates an event affecting a single block. - pub fn single(pos: BlockPosition) -> Self { + pub fn single(pos: ValidBlockPosition) -> Self { Self { changes: BlockChanges::Single { pos }, } @@ -42,7 +42,7 @@ impl BlockChangeEvent { } /// Returns an iterator over block positions affected by this block change. - pub fn iter_changed_blocks(&self) -> impl Iterator + '_ { + pub fn iter_changed_blocks(&self) -> impl Iterator + '_ { match &self.changes { BlockChanges::Single { pos } => Either::Left(iter::once(*pos)), BlockChanges::FillChunkSection { chunk, section } => { @@ -60,7 +60,7 @@ impl BlockChangeEvent { ) -> impl Iterator + '_ { match &self.changes { BlockChanges::Single { pos } => { - iter::once((pos.chunk(), pos.y as usize / SECTION_HEIGHT, 1)) + iter::once((pos.chunk(), pos.y() as usize / SECTION_HEIGHT, 1)) } BlockChanges::FillChunkSection { chunk, section } => { iter::once((*chunk, *section as usize, SECTION_VOLUME)) @@ -69,7 +69,10 @@ impl BlockChangeEvent { } } -fn iter_section_blocks(chunk: ChunkPosition, section: u32) -> impl Iterator { +fn iter_section_blocks( + chunk: ChunkPosition, + section: u32, +) -> impl Iterator { (0..16) .flat_map(|x| (0..16).map(move |y| (x, y))) .flat_map(|(x, y)| (0..16).map(move |z| (x, y, z))) @@ -77,14 +80,16 @@ fn iter_section_blocks(chunk: ChunkPosition, section: u32) -> impl Iterator>(), vec![pos]); @@ -123,9 +128,9 @@ mod tests { #[test] fn test_iter_section_blocks() { - let blocks: Vec = + let blocks: Vec = iter_section_blocks(ChunkPosition::new(-1, -2), 5).collect(); - let unique_blocks: AHashSet = blocks.iter().copied().collect(); + let unique_blocks: AHashSet = blocks.iter().copied().collect(); assert_eq!(blocks.len(), unique_blocks.len()); assert_eq!(blocks.len(), SECTION_VOLUME); @@ -134,7 +139,7 @@ mod tests { for y in 80..96 { for z in -32..-16 { assert!( - unique_blocks.contains(&BlockPosition::new(x, y, z)), + unique_blocks.contains(&BlockPosition::new(x, y, z).try_into().unwrap()), "{}, {}, {}", x, y, diff --git a/feather/common/src/game.rs b/feather/common/src/game.rs index 3d23442dc..35a44c3ec 100644 --- a/feather/common/src/game.rs +++ b/feather/common/src/game.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, mem, rc::Rc, sync::Arc}; -use base::{BlockId, BlockPosition, ChunkPosition, Position, Text, Title}; +use base::{BlockId, ChunkPosition, Position, Text, Title, ValidBlockPosition}; use ecs::{ Ecs, Entity, EntityBuilder, HasEcs, HasResources, NoSuchEntity, Resources, SysResult, SystemExecutor, @@ -181,14 +181,14 @@ impl Game { } /// Gets the block at the given position. - pub fn block(&self, pos: BlockPosition) -> Option { + pub fn block(&self, pos: ValidBlockPosition) -> Option { self.world.block_at(pos) } /// Sets the block at the given position. /// /// Triggers necessary `BlockChangeEvent`s. - pub fn set_block(&mut self, pos: BlockPosition, block: BlockId) -> bool { + pub fn set_block(&mut self, pos: ValidBlockPosition, block: BlockId) -> bool { let was_successful = self.world.set_block_at(pos, block); if was_successful { self.ecs.insert_event(BlockChangeEvent::single(pos)); @@ -226,7 +226,7 @@ impl Game { /// Breaks the block at the given position, propagating any /// necessary block updates. - pub fn break_block(&mut self, pos: BlockPosition) -> bool { + pub fn break_block(&mut self, pos: ValidBlockPosition) -> bool { self.set_block(pos, BlockId::air()) } } diff --git a/feather/common/src/world.rs b/feather/common/src/world.rs index 2380c399b..201eeb13e 100644 --- a/feather/common/src/world.rs +++ b/feather/common/src/world.rs @@ -5,7 +5,9 @@ use parking_lot::{RwLockReadGuard, RwLockWriteGuard}; use uuid::Uuid; use base::anvil::player::PlayerData; -use base::{BlockPosition, Chunk, ChunkHandle, ChunkLock, ChunkPosition, CHUNK_HEIGHT}; +use base::{ + BlockPosition, Chunk, ChunkHandle, ChunkLock, ChunkPosition, ValidBlockPosition, CHUNK_HEIGHT, +}; use blocks::BlockId; use ecs::{Ecs, SysResult}; use worldgen::{ComposableGenerator, WorldGenerator}; @@ -135,7 +137,7 @@ impl World { /// if its chunk was not loaded or the coordinates /// are out of bounds and thus no operation /// was performed. - pub fn set_block_at(&self, pos: BlockPosition, block: BlockId) -> bool { + pub fn set_block_at(&self, pos: ValidBlockPosition, block: BlockId) -> bool { self.chunk_map.set_block_at(pos, block) } @@ -143,7 +145,7 @@ impl World { /// location. If the chunk in which the block /// exists is not loaded or the coordinates /// are out of bounds, `None` is returned. - pub fn block_at(&self, pos: BlockPosition) -> Option { + pub fn block_at(&self, pos: ValidBlockPosition) -> Option { self.chunk_map.block_at(pos) } @@ -205,21 +207,23 @@ impl ChunkMap { self.0.get(&pos).map(Arc::clone) } - pub fn block_at(&self, pos: BlockPosition) -> Option { + pub fn block_at(&self, pos: ValidBlockPosition) -> Option { check_coords(pos)?; - let (x, y, z) = chunk_relative_pos(pos); - self.chunk_at(pos.into()) + + let (x, y, z) = chunk_relative_pos(pos.into()); + self.chunk_at(pos.chunk()) .map(|chunk| chunk.block_at(x, y, z)) .flatten() } - pub fn set_block_at(&self, pos: BlockPosition, block: BlockId) -> bool { + pub fn set_block_at(&self, pos: ValidBlockPosition, block: BlockId) -> bool { if check_coords(pos).is_none() { return false; } - let (x, y, z) = chunk_relative_pos(pos); - self.chunk_at_mut(pos.into()) + let (x, y, z) = chunk_relative_pos(pos.into()); + + self.chunk_at_mut(pos.chunk()) .map(|mut chunk| chunk.set_block_at(x, y, z, block)) .is_some() } @@ -241,8 +245,8 @@ impl ChunkMap { } } -fn check_coords(pos: BlockPosition) -> Option<()> { - if pos.y >= 0 && pos.y < CHUNK_HEIGHT as i32 { +fn check_coords(pos: ValidBlockPosition) -> Option<()> { + if pos.y() >= 0 && pos.y() < CHUNK_HEIGHT as i32 { Some(()) } else { None @@ -259,6 +263,8 @@ fn chunk_relative_pos(block_pos: BlockPosition) -> (usize, usize, usize) { #[cfg(test)] mod tests { + use std::convert::TryInto; + use super::*; #[test] @@ -268,7 +274,11 @@ mod tests { .chunk_map_mut() .insert_chunk(Chunk::new(ChunkPosition::new(0, 0))); - assert!(world.block_at(BlockPosition::new(0, -1, 0)).is_none()); - assert!(world.block_at(BlockPosition::new(0, 0, 0)).is_some()); + assert!(world + .block_at(BlockPosition::new(0, -1, 0).try_into().unwrap()) + .is_none()); + assert!(world + .block_at(BlockPosition::new(0, 0, 0).try_into().unwrap()) + .is_some()); } } diff --git a/feather/plugin-host/src/host_calls/block.rs b/feather/plugin-host/src/host_calls/block.rs index eb11dfca7..cc1f14b8b 100644 --- a/feather/plugin-host/src/host_calls/block.rs +++ b/feather/plugin-host/src/host_calls/block.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto; + use feather_base::{BlockId, BlockPosition, ChunkPosition}; use feather_plugin_host_macros::host_function; use quill_common::block::BlockGetResult; @@ -7,7 +9,7 @@ use crate::context::PluginContext; /// NB: `u32` has the same layout as `BlockGetResult`. #[host_function] pub fn block_get(cx: &PluginContext, x: i32, y: i32, z: i32) -> anyhow::Result { - let pos = BlockPosition::new(x, y, z); + let pos = BlockPosition::new(x, y, z).try_into()?; let block = cx.game_mut().block(pos); let result = BlockGetResult::new(block.map(BlockId::vanilla_id)); @@ -16,7 +18,7 @@ pub fn block_get(cx: &PluginContext, x: i32, y: i32, z: i32) -> anyhow::Result anyhow::Result { - let pos = BlockPosition::new(x, y, z); + let pos = BlockPosition::new(x, y, z).try_into()?; let block = BlockId::from_vanilla_id(block_id); let was_successful = cx.game_mut().set_block(pos, block); diff --git a/feather/protocol/src/io.rs b/feather/protocol/src/io.rs index 7b20e404a..5abd522f9 100644 --- a/feather/protocol/src/io.rs +++ b/feather/protocol/src/io.rs @@ -4,7 +4,7 @@ use crate::{ProtocolVersion, Slot}; use anyhow::{anyhow, bail, Context}; use base::{ anvil::entity::ItemNbt, metadata::MetaEntry, BlockId, BlockPosition, Direction, EntityMetadata, - Gamemode, Item, ItemStack, + Gamemode, Item, ItemStack, ValidBlockPosition, }; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use num_traits::{FromPrimitive, ToPrimitive}; @@ -632,9 +632,9 @@ fn read_meta_entry( f32::read(buffer, version)?, f32::read(buffer, version)?, ), - 9 => MetaEntry::Position(BlockPosition::read(buffer, version)?), + 9 => MetaEntry::Position(ValidBlockPosition::read(buffer, version)?), 10 => MetaEntry::OptPosition(if bool::read(buffer, version)? { - Some(BlockPosition::read(buffer, version)?) + Some(ValidBlockPosition::read(buffer, version)?) } else { None }), @@ -782,7 +782,7 @@ impl Writeable for Uuid { } } -impl Readable for BlockPosition { +impl Readable for ValidBlockPosition { fn read(buffer: &mut Cursor<&[u8]>, version: ProtocolVersion) -> anyhow::Result where Self: Sized, @@ -793,15 +793,15 @@ impl Readable for BlockPosition { let y = (val & 0xFFF) as i32; let z = (val << 26 >> 38) as i32; - Ok(BlockPosition { x, y, z }) + Ok(BlockPosition { x, y, z }.try_into()?) } } -impl Writeable for BlockPosition { +impl Writeable for ValidBlockPosition { fn write(&self, buffer: &mut Vec, version: ProtocolVersion) -> anyhow::Result<()> { - let val = ((self.x as u64 & 0x3FFFFFF) << 38) - | ((self.z as u64 & 0x3FFFFFF) << 12) - | (self.y as u64 & 0xFFF); + let val = ((self.x() as u64 & 0x3FFFFFF) << 38) + | ((self.z() as u64 & 0x3FFFFFF) << 12) + | (self.y() as u64 & 0xFFF); val.write(buffer, version)?; Ok(()) diff --git a/feather/protocol/src/packets.rs b/feather/protocol/src/packets.rs index acd786f58..445051ef0 100644 --- a/feather/protocol/src/packets.rs +++ b/feather/protocol/src/packets.rs @@ -286,7 +286,7 @@ pub trait VariantOf { use crate::io::{Angle, LengthInferredVecU8, Nbt, ShortPrefixedVec, VarInt, VarIntPrefixedVec}; use crate::Slot; -use base::{BlockId, BlockPosition}; +use base::BlockId; use nbt::Blob; use uuid::Uuid; diff --git a/feather/protocol/src/packets/client/play.rs b/feather/protocol/src/packets/client/play.rs index 7f10754bd..5b4b4311d 100644 --- a/feather/protocol/src/packets/client/play.rs +++ b/feather/protocol/src/packets/client/play.rs @@ -1,3 +1,5 @@ +use base::ValidBlockPosition; + use super::*; use crate::packets::server::Hand; @@ -8,7 +10,7 @@ packets! { QueryBlockNbt { transaction_id VarInt; - position BlockPosition; + position ValidBlockPosition; } QueryEntityNbt { @@ -114,7 +116,7 @@ def_enum! { packets! { GenerateStructure { - position BlockPosition; + position ValidBlockPosition; levels VarInt; keep_jigsaws bool; } @@ -192,7 +194,7 @@ packets! { PlayerDigging { status PlayerDiggingStatus; - position BlockPosition; + position ValidBlockPosition; face BlockFace; } } @@ -277,7 +279,7 @@ packets! { } UpdateCommandBlock { - position BlockPosition; + position ValidBlockPosition; command String; mode VarInt; flags u8; @@ -295,7 +297,7 @@ packets! { } UpdateJigsawBlock { - position BlockPosition; + position ValidBlockPosition; name String; target String; pool String; @@ -304,7 +306,7 @@ packets! { } UpdateStructureBlock { - position BlockPosition; + position ValidBlockPosition; action VarInt; mode VarInt; name String; @@ -323,7 +325,7 @@ packets! { } UpdateSign { - position BlockPosition; + position ValidBlockPosition; line_1 String; line_2 String; line_3 String; @@ -340,7 +342,7 @@ packets! { PlayerBlockPlacement { hand VarInt; - position BlockPosition; + position ValidBlockPosition; face BlockFace; cursor_position_x f32; cursor_position_y f32; diff --git a/feather/protocol/src/packets/server/play.rs b/feather/protocol/src/packets/server/play.rs index 57b9b0aaf..01c83b03f 100644 --- a/feather/protocol/src/packets/server/play.rs +++ b/feather/protocol/src/packets/server/play.rs @@ -1,6 +1,8 @@ use anyhow::bail; -use base::{BlockState, EntityMetadata, Gamemode, ParticleKind, ProfileProperty}; +use base::{ + BlockState, EntityMetadata, Gamemode, ParticleKind, ProfileProperty, ValidBlockPosition, +}; pub use chunk_data::{ChunkData, ChunkDataKind}; use quill_common::components::PreviousGamemode; pub use update_light::UpdateLight; @@ -54,7 +56,7 @@ packets! { entity_id VarInt; entity_uuid Uuid; motive VarInt; - location BlockPosition; + location ValidBlockPosition; direction PaintingDirection; } } @@ -108,7 +110,7 @@ packets! { } AcknowledgePlayerDigging { - position BlockPosition; + position ValidBlockPosition; block BlockId; status PlayerDiggingStatus; successful bool; @@ -126,25 +128,25 @@ def_enum! { packets! { BlockBreakAnimation { entity_id VarInt; - position BlockPosition; + position ValidBlockPosition; destroy_stage u8; } BlockEntityData { - position BlockPosition; + position ValidBlockPosition; action u8; data Nbt; } BlockAction { - position BlockPosition; + position ValidBlockPosition; action_id u8; action_param u8; block_type VarInt; } BlockChange { - position BlockPosition; + position ValidBlockPosition; block BlockId; } @@ -348,7 +350,7 @@ packets! { Effect { effect_id i32; - position BlockPosition; + position ValidBlockPosition; data i32; disable_relative_volume bool; } @@ -473,7 +475,7 @@ packets! { } OpenSignEditor { - position BlockPosition; + position ValidBlockPosition; } CraftRecipeResponse { @@ -1098,7 +1100,7 @@ packets! { } SpawnPosition { - position BlockPosition; + position ValidBlockPosition; } TimeUpdate { diff --git a/feather/server/src/client.rs b/feather/server/src/client.rs index deefaf673..bfb3dd74c 100644 --- a/feather/server/src/client.rs +++ b/feather/server/src/client.rs @@ -10,8 +10,8 @@ use flume::{Receiver, Sender}; use uuid::Uuid; use base::{ - BlockId, BlockPosition, ChunkHandle, ChunkPosition, EntityKind, EntityMetadata, Gamemode, - ItemStack, Position, ProfileProperty, Text, + BlockId, ChunkHandle, ChunkPosition, EntityKind, EntityMetadata, Gamemode, ItemStack, Position, + ProfileProperty, Text, ValidBlockPosition, }; use common::{ chat::{ChatKind, ChatMessage}, @@ -283,7 +283,7 @@ impl Client { }); } - pub fn send_block_change(&self, position: BlockPosition, new_block: BlockId) { + pub fn send_block_change(&self, position: ValidBlockPosition, new_block: BlockId) { self.send_packet(BlockChange { position, block: new_block, diff --git a/feather/server/src/packet_handlers/interaction.rs b/feather/server/src/packet_handlers/interaction.rs index 369fce91c..9202e6b95 100644 --- a/feather/server/src/packet_handlers/interaction.rs +++ b/feather/server/src/packet_handlers/interaction.rs @@ -80,7 +80,7 @@ pub fn handle_player_block_placement( // Handle this as a block interaction let event = BlockInteractEvent { hand, - location: packet.position, + location: packet.position.into(), face, cursor_position, inside_block: packet.inside_block, @@ -91,7 +91,7 @@ pub fn handle_player_block_placement( // Handle this as a block placement let event = BlockPlacementEvent { hand, - location: packet.position, + location: packet.position.into(), face, cursor_position, inside_block: packet.inside_block, diff --git a/libcraft/core/src/positions.rs b/libcraft/core/src/positions.rs index 91c9cf48d..81606b6f6 100644 --- a/libcraft/core/src/positions.rs +++ b/libcraft/core/src/positions.rs @@ -350,6 +350,18 @@ impl BlockPosition { z: self.z, } } + + /// Returns `true` if the [`BlockPosition`] is valid. + /// + /// Minecraft defines a valid block position with the following limits: + /// - X (-33554432 to 33554431) + /// - Y (-2048 to 2047) + /// - Z (-33554432 to 33554431) + pub fn valid(self) -> bool { + (-33554432 <= self.x && self.x <= 33554431) + && (-2048 <= self.y && self.y <= 2047) + && (-33554432 <= self.z && self.z <= 33554431) + } } impl Add for BlockPosition {