Document most items

This commit is contained in:
Ryan 2022-07-11 05:08:02 -07:00
parent 54e0d5cb90
commit 3f150b4c8a
32 changed files with 1230 additions and 599 deletions

View file

@ -1,4 +0,0 @@
# valence
Coming soon to a package manager near you!
(Valence is a WIP Rust library.)

View file

@ -400,16 +400,16 @@ pub fn build() -> anyhow::Result<()> {
#default_block_states #default_block_states
} }
/// An enumeration of all block types. /// An enumeration of all block kinds.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum BlockKind { pub enum BlockKind {
#(#block_kind_variants,)* #(#block_kind_variants,)*
} }
impl BlockKind { impl BlockKind {
/// Construct a block type from its snake_case name. /// Construct a block kind from its snake_case name.
/// ///
/// Returns `None` if the given name is not valid. /// Returns `None` if the name is invalid.
pub fn from_str(name: &str) -> Option<BlockKind> { pub fn from_str(name: &str) -> Option<BlockKind> {
match name { match name {
#block_kind_from_str_arms #block_kind_from_str_arms
@ -417,19 +417,19 @@ pub fn build() -> anyhow::Result<()> {
} }
} }
/// Get the snake_case name of this block type. /// Get the snake_case name of this block kind.
pub const fn to_str(self) -> &'static str { pub const fn to_str(self) -> &'static str {
match self { match self {
#block_kind_to_str_arms #block_kind_to_str_arms
} }
} }
/// Returns the default block state for a given block type. /// Returns the default block state for a given block kind.
pub const fn to_state(self) -> BlockState { pub const fn to_state(self) -> BlockState {
BlockState::from_kind(self) BlockState::from_kind(self)
} }
/// Returns a slice of all properties this block type has. /// Returns a slice of all properties this block kind has.
pub const fn props(self) -> &'static [PropName] { pub const fn props(self) -> &'static [PropName] {
match self { match self {
#block_kind_props_arms #block_kind_props_arms
@ -437,11 +437,11 @@ pub fn build() -> anyhow::Result<()> {
} }
} }
/// An array of all block types. /// An array of all block kinds.
pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*]; pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*];
} }
/// The default block type is `air`. /// The default block kind is `air`.
impl Default for BlockKind { impl Default for BlockKind {
fn default() -> Self { fn default() -> Self {
Self::Air Self::Air

View file

@ -9,10 +9,10 @@ use num::Integer;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence::biome::Biome; use valence::biome::Biome;
use valence::block::BlockState; use valence::block::BlockState;
use valence::client::{ClientEvent, ClientId, GameMode, Hand}; use valence::client::{Event, ClientId, GameMode, Hand};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId}; use valence::dimension::{Dimension, DimensionId};
use valence::entity::meta::Pose; use valence::entity::data::Pose;
use valence::entity::{EntityData, EntityId, EntityKind}; use valence::entity::{EntityData, EntityId, EntityKind};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
@ -175,17 +175,15 @@ impl Config for Game {
while let Some(event) = client.pop_event() { while let Some(event) = client.pop_event() {
match event { match event {
ClientEvent::Digging(e) => { Event::Digging { position, .. } => {
let pos = e.position; if (0..SIZE_X as i32).contains(&position.x)
&& (0..SIZE_Z as i32).contains(&position.z)
if (0..SIZE_X as i32).contains(&pos.x) && position.y == BOARD_Y
&& (0..SIZE_Z as i32).contains(&pos.z)
&& pos.y == BOARD_Y
{ {
board[pos.x as usize + pos.z as usize * SIZE_X] = true; board[position.x as usize + position.z as usize * SIZE_X] = true;
} }
} }
ClientEvent::Movement { .. } => { Event::Movement { .. } => {
if client.position().y <= 0.0 { if client.position().y <= 0.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch()); client.teleport(spawn_pos, client.yaw(), client.pitch());
} }
@ -197,29 +195,29 @@ impl Config for Game {
player.set_pitch(client.pitch()); player.set_pitch(client.pitch());
player.set_on_ground(client.on_ground()); player.set_on_ground(client.on_ground());
} }
ClientEvent::StartSneaking => { Event::StartSneaking => {
if let EntityData::Player(e) = player.data_mut() { if let EntityData::Player(e) = player.data_mut() {
e.set_crouching(true); e.set_crouching(true);
e.set_pose(Pose::Sneaking); e.set_pose(Pose::Sneaking);
} }
} }
ClientEvent::StopSneaking => { Event::StopSneaking => {
if let EntityData::Player(e) = player.data_mut() { if let EntityData::Player(e) = player.data_mut() {
e.set_pose(Pose::Standing); e.set_pose(Pose::Standing);
e.set_crouching(false); e.set_crouching(false);
} }
} }
ClientEvent::StartSprinting => { Event::StartSprinting => {
if let EntityData::Player(e) = player.data_mut() { if let EntityData::Player(e) = player.data_mut() {
e.set_sprinting(true); e.set_sprinting(true);
} }
} }
ClientEvent::StopSprinting => { Event::StopSprinting => {
if let EntityData::Player(e) = player.data_mut() { if let EntityData::Player(e) = player.data_mut() {
e.set_sprinting(false); e.set_sprinting(false);
} }
} }
ClientEvent::ArmSwing(hand) => { Event::ArmSwing(hand) => {
if let EntityData::Player(e) = player.data_mut() { if let EntityData::Player(e) = player.data_mut() {
match hand { match hand {
Hand::Main => e.trigger_swing_main_arm(), Hand::Main => e.trigger_swing_main_arm(),

View file

@ -1,19 +1,23 @@
//! Biome definitions.
use crate::ident; use crate::ident;
use crate::ident::Ident; use crate::ident::Ident;
use crate::protocol_inner::packets::play::s2c::Biome as BiomeRegistryBiome;
/// Identifies a particular [`Biome`]. /// Identifies a particular [`Biome`] on the server.
/// ///
/// Biome IDs are always valid and are cheap to copy and store. /// The default biome ID refers to the first biome added in the server's
/// [configuration](crate::config::Config).
///
/// To obtain biome IDs for other biomes, call
/// [`biomes`](crate::server::SharedServer::biomes).
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BiomeId(pub(crate) u16); pub struct BiomeId(pub(crate) u16);
impl BiomeId {
pub fn to_index(self) -> usize {
self.0 as usize
}
}
/// Contains the configuration for a biome. /// Contains the configuration for a biome.
///
/// Biomes are registered once at startup through
/// [`biomes`](crate::config::Config::biomes).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Biome { pub struct Biome {
/// The unique name for this biome. The name can be /// The unique name for this biome. The name can be
@ -42,6 +46,70 @@ pub struct Biome {
// * temperature_modifier // * temperature_modifier
} }
impl Biome {
pub(crate) fn to_biome_registry_item(&self, id: i32) -> BiomeRegistryBiome {
use crate::protocol_inner::packets::play::s2c::{
BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, BiomeParticle,
BiomeParticleOptions, BiomeProperty,
};
BiomeRegistryBiome {
name: self.name.clone(),
id,
element: BiomeProperty {
precipitation: match self.precipitation {
BiomePrecipitation::Rain => "rain",
BiomePrecipitation::Snow => "snow",
BiomePrecipitation::None => "none",
}
.into(),
depth: 0.125,
temperature: 0.8,
scale: 0.05,
downfall: 0.4,
category: "none".into(),
temperature_modifier: None,
effects: BiomeEffects {
sky_color: self.sky_color as i32,
water_fog_color: self.water_fog_color as i32,
fog_color: self.fog_color as i32,
water_color: self.water_color as i32,
foliage_color: self.foliage_color.map(|x| x as i32),
grass_color: self.grass_color.map(|x| x as i32),
grass_color_modifier: match self.grass_color_modifier {
BiomeGrassColorModifier::Swamp => Some("swamp".into()),
BiomeGrassColorModifier::DarkForest => Some("dark_forest".into()),
BiomeGrassColorModifier::None => None,
},
music: self.music.as_ref().map(|bm| BiomeMusic {
replace_current_music: bm.replace_current_music,
sound: bm.sound.clone(),
max_delay: bm.max_delay,
min_delay: bm.min_delay,
}),
ambient_sound: self.ambient_sound.clone(),
additions_sound: self.additions_sound.as_ref().map(|a| BiomeAdditionsSound {
sound: a.sound.clone(),
tick_chance: a.tick_chance,
}),
mood_sound: self.mood_sound.as_ref().map(|m| BiomeMoodSound {
sound: m.sound.clone(),
tick_delay: m.tick_delay,
offset: m.offset,
block_search_extent: m.block_search_extent,
}),
},
particle: self.particle.as_ref().map(|p| BiomeParticle {
probability: p.probability,
options: BiomeParticleOptions {
kind: p.kind.clone(),
},
}),
},
}
}
}
impl Default for Biome { impl Default for Biome {
fn default() -> Self { fn default() -> Self {
Self { Self {

View file

@ -6,7 +6,7 @@ use std::io::{Read, Write};
use anyhow::Context; use anyhow::Context;
pub use crate::block_pos::BlockPos; pub use crate::block_pos::BlockPos;
use crate::protocol::{Decode, Encode, VarInt}; use crate::protocol_inner::{Decode, Encode, VarInt};
include!(concat!(env!("OUT_DIR"), "/block.rs")); include!(concat!(env!("OUT_DIR"), "/block.rs"));
@ -17,7 +17,7 @@ impl fmt::Debug for BlockState {
} }
impl Display for BlockState { impl Display for BlockState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_block_state(*self, f) fmt_block_state(*self, f)
} }
} }
@ -35,7 +35,7 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result {
struct KeyVal<'a>(&'a str, &'a str); struct KeyVal<'a>(&'a str, &'a str);
impl<'a> fmt::Debug for KeyVal<'a> { impl<'a> fmt::Debug for KeyVal<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}={}", self.0, self.1) write!(f, "{}={}", self.0, self.1)
} }
} }

View file

@ -3,8 +3,9 @@ use std::io::{Read, Write};
use anyhow::bail; use anyhow::bail;
use vek::Vec3; use vek::Vec3;
use crate::protocol::{Decode, Encode}; use crate::protocol_inner::{Decode, Encode};
/// Represents an absolute block position in a world.
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
pub struct BlockPos { pub struct BlockPos {
pub x: i32, pub x: i32,
@ -13,10 +14,12 @@ pub struct BlockPos {
} }
impl BlockPos { impl BlockPos {
/// Constructs a new block position.
pub const fn new(x: i32, y: i32, z: i32) -> Self { pub const fn new(x: i32, y: i32, z: i32) -> Self {
Self { x, y, z } Self { x, y, z }
} }
/// Returns the block position a point is contained within.
pub fn at(pos: impl Into<Vec3<f64>>) -> Self { pub fn at(pos: impl Into<Vec3<f64>>) -> Self {
pos.into().floor().as_::<i32>().into() pos.into().floor().as_::<i32>().into()
} }

View file

@ -1,3 +1,8 @@
//! The [bounding volume hierarchy][bvh] contained in the [`SpatialIndex`]
//!
//! [bvh]: https://en.wikipedia.org/wiki/Bounding_volume_hierarchy
//! [`SpatialIndex`]: crate::spatial_index::SpatialIndex
use std::mem; use std::mem;
use approx::relative_eq; use approx::relative_eq;

View file

@ -14,13 +14,14 @@ use crate::block::BlockState;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
pub use crate::chunk_pos::ChunkPos; pub use crate::chunk_pos::ChunkPos;
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::protocol::packets::play::s2c::{ use crate::protocol_inner::packets::play::s2c::{
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate, BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
}; };
use crate::protocol::{Encode, Nbt, VarInt, VarLong}; use crate::protocol_inner::{Encode, Nbt, VarInt, VarLong};
use crate::server::SharedServer; use crate::server::SharedServer;
use crate::Ticks; use crate::Ticks;
/// A container for all [`Chunks`]s in a [`World`](crate::world::World).
pub struct Chunks { pub struct Chunks {
chunks: HashMap<ChunkPos, Chunk>, chunks: HashMap<ChunkPos, Chunk>,
server: SharedServer, server: SharedServer,
@ -36,6 +37,16 @@ impl Chunks {
} }
} }
/// Creates an empty chunk at the provided position and returns a mutable
/// refernce to it.
///
/// If a chunk at the position already exists, then the old chunk
/// is overwritten.
///
/// **Note**: For the vanilla Minecraft client to see a chunk, all chunks
/// adjacent to it must also be loaded. It is also important that clients
/// are not spawned within unloaded chunks via
/// [`spawn`](crate::client::Client::spawn).
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk { pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk {
let section_count = (self.server.dimension(self.dimension).height / 16) as u32; let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
let chunk = Chunk::new(section_count, self.server.current_tick()); let chunk = Chunk::new(section_count, self.server.current_tick());
@ -49,42 +60,69 @@ impl Chunks {
} }
} }
pub fn delete(&mut self, pos: ChunkPos) -> bool { /// Removes a chunk at the provided position.
self.chunks.remove(&pos).is_some() ///
/// If a chunk exists at the position, then it is deleted and `true` is
/// returned. Otherwise, `false` is returned.
pub fn delete(&mut self, pos: impl Into<ChunkPos>) -> bool {
self.chunks.remove(&pos.into()).is_some()
} }
/// Returns the number of loaded chunks.
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.chunks.len() self.chunks.len()
} }
/// Gets a shared reference to the chunk at the provided position.
///
/// If there is no chunk at the position, then `None` is returned.
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> { pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> {
self.chunks.get(&pos.into()) self.chunks.get(&pos.into())
} }
/// Gets an exclusive reference to the chunk at the provided position.
///
/// If there is no chunk at the position, then `None` is returned.
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> { pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> {
self.chunks.get_mut(&pos.into()) self.chunks.get_mut(&pos.into())
} }
/// Deletes all chunks.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.chunks.clear(); self.chunks.clear();
} }
/// Returns an immutable iterator over all chunks in the world in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk)) self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
} }
/// Returns a mutable iterator over all chunks in the world in an
/// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk)) self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
} }
/// Returns a parallel immutable iterator over all chunks in the world in an
/// unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk)) self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
} }
/// Returns a parallel mutable iterator over all chunks in the world in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk)) self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
} }
/// Gets the block state at a position.
///
/// If the position is not inside of a chunk, then `None` is returned.
///
/// Note: if you need to get a large number of blocks, it may be more
/// efficient to read from the chunks directly with
/// [`Chunk::get_block_state`].
pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> { pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> {
let pos = pos.into(); let pos = pos.into();
let chunk_pos = ChunkPos::from(pos); let chunk_pos = ChunkPos::from(pos);
@ -106,6 +144,14 @@ impl Chunks {
} }
} }
/// Sets the block state at a position.
///
/// If the position is inside of a chunk, then `true` is returned.
/// Otherwise, `false` is returned.
///
/// Note: if you need to set a large number of blocks, it may be more
/// efficient write to the chunks directly with
/// [`Chunk::set_block_state`].
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool { pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
let pos = pos.into(); let pos = pos.into();
let chunk_pos = ChunkPos::from(pos); let chunk_pos = ChunkPos::from(pos);
@ -130,6 +176,11 @@ impl Chunks {
} }
} }
/// A chunk is a 16x16-block segment of a world with a height determined by the
/// [`Dimension`](crate::dimension::Dimension) of the world.
///
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
pub struct Chunk { pub struct Chunk {
sections: Box<[ChunkSection]>, sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>, // TODO block_entities: HashMap<u32, BlockEntity>,

View file

@ -10,10 +10,13 @@ pub struct ChunkPos {
} }
impl ChunkPos { impl ChunkPos {
/// Constructs a new chunk position.
pub const fn new(x: i32, z: i32) -> Self { pub const fn new(x: i32, z: i32) -> Self {
Self { x, z } Self { x, z }
} }
/// Takes an absolute position and returns the chunk position
/// containing the point.
pub fn at(x: f64, z: f64) -> Self { pub fn at(x: f64, z: f64) -> Self {
Self::new((x / 16.0).floor() as i32, (z / 16.0).floor() as i32) Self::new((x / 16.0).floor() as i32, (z / 16.0).floor() as i32)
} }

View file

@ -11,29 +11,28 @@ use rayon::iter::ParallelIterator;
use uuid::Uuid; use uuid::Uuid;
use vek::Vec3; use vek::Vec3;
use crate::biome::{Biome, BiomeGrassColorModifier, BiomePrecipitation}; use crate::biome::Biome;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::chunk_pos::ChunkPos; use crate::chunk_pos::ChunkPos;
use crate::dimension::{Dimension, DimensionEffects, DimensionId}; use crate::dimension::DimensionId;
use crate::entity::data::Player; use crate::entity::types::Player;
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind}; use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol::packets::play::c2s::{ use crate::protocol_inner::packets::play::c2s::{
C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId, C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId,
}; };
pub use crate::protocol::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes; pub use crate::protocol_inner::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes;
use crate::protocol::packets::play::s2c::{ use crate::protocol_inner::packets::play::s2c::{
Animate, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, Animate, BiomeRegistry, BlockChangeAck, ChatType, ChatTypeChat, ChatTypeNarration,
BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, BlockChangeAck, ChatTypeRegistry, ChatTypeRegistryEntry, ClearTitles, DimensionTypeRegistry,
ChatType, ChatTypeChat, ChatTypeNarration, ChatTypeRegistry, ChatTypeRegistryEntry, DimensionTypeRegistryEntry, Disconnect, EntityEvent, ForgetLevelChunk, GameEvent,
ClearTitles, DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, GameEventReason, KeepAlive, Login, MoveEntityPosition, MoveEntityPositionAndRotation,
EntityEvent, ForgetLevelChunk, GameEvent, GameEventReason, KeepAlive, Login, MoveEntityRotation, PlayerPosition, PlayerPositionFlags, RegistryCodec, RemoveEntities,
MoveEntityPosition, MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition, Respawn, RotateHead, S2cPlayPacket, SetChunkCacheCenter, SetChunkCacheRadius,
PlayerPositionFlags, RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket, SetEntityMetadata, SetEntityMotion, SetSubtitleText, SetTitleText, SpawnPosition, SystemChat,
SetChunkCacheCenter, SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SetSubtitleText, TeleportEntity, ENTITY_EVENT_MAX_BOUND,
SetTitleText, SpawnPosition, SystemChat, TeleportEntity, ENTITY_EVENT_MAX_BOUND,
}; };
use crate::protocol::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt}; use crate::protocol_inner::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt};
use crate::server::{C2sPacketChannels, NewClientData, SharedServer}; use crate::server::{C2sPacketChannels, NewClientData, SharedServer};
use crate::slotmap::{Key, SlotMap}; use crate::slotmap::{Key, SlotMap};
use crate::text::Text; use crate::text::Text;
@ -41,6 +40,11 @@ use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
use crate::world::{WorldId, Worlds}; use crate::world::{WorldId, Worlds};
use crate::{ident, Ticks, LIBRARY_NAMESPACE}; use crate::{ident, Ticks, LIBRARY_NAMESPACE};
/// A container for all [`Client`]s on a [`Server`](crate::server::Server).
///
/// New clients are automatically inserted into this container but
/// are not automatically deleted. It is your responsibility to delete them once
/// they disconnect. This can be checked with [`Client::is_disconnected`].
pub struct Clients { pub struct Clients {
sm: SlotMap<Client>, sm: SlotMap<Client>,
} }
@ -55,51 +59,103 @@ impl Clients {
(ClientId(id), client) (ClientId(id), client)
} }
/// Removes a client from the server.
///
/// If the given client ID is valid, `true` is returned and the client is
/// deleted. Otherwise, `false` is returned and the function has no effect.
pub fn delete(&mut self, client: ClientId) -> bool { pub fn delete(&mut self, client: ClientId) -> bool {
self.sm.remove(client.0).is_some() self.sm.remove(client.0).is_some()
} }
/// Deletes all clients from the server (as if by [`Self::delete`]) for
/// which `f` returns `true`.
///
/// All clients are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) {
self.sm.retain(|k, v| f(ClientId(k), v)) self.sm.retain(|k, v| f(ClientId(k), v))
} }
/// Returns the number of clients on the server. This includes clients
/// which may be disconnected.
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.sm.len() self.sm.len()
} }
/// Returns a shared reference to the client with the given ID. If
/// the ID is invalid, then `None` is returned.
pub fn get(&self, client: ClientId) -> Option<&Client> { pub fn get(&self, client: ClientId) -> Option<&Client> {
self.sm.get(client.0) self.sm.get(client.0)
} }
/// Returns an exclusive reference to the client with the given ID. If the
/// ID is invalid, then `None` is returned.
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> { pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> {
self.sm.get_mut(client.0) self.sm.get_mut(client.0)
} }
/// Returns an immutable iterator over all clients on the server in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ClientId(k), v)) self.sm.iter().map(|(k, v)| (ClientId(k), v))
} }
/// Returns a mutable iterator over all clients on the server in an
/// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v)) self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
} }
/// Returns a parallel immutable iterator over all clients on the server in
/// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ClientId(k), v)) self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
} }
/// Returns a parallel mutable iterator over all clients on the server in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v)) self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
} }
} }
/// A key for a [`Client`] on the server.
///
/// Client IDs are either _valid_ or _invalid_. Valid client IDs point to
/// clients that have not been deleted, while invalid IDs point to those that
/// have. Once an ID becomes invalid, it will never become valid again.
///
/// The [`Ord`] instance on this type is correct but otherwise unspecified. This
/// is useful for storing IDs in containers such as
/// [`BTreeMap`](std::collections::BTreeMap).
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct ClientId(Key); pub struct ClientId(Key);
impl ClientId { impl ClientId {
/// The value of the default client ID which is always invalid.
pub const NULL: Self = Self(Key::NULL); pub const NULL: Self = Self(Key::NULL);
} }
/// Represents a client connected to the server after logging in. /// Represents a remote connection to a client after successfully logging in.
///
/// Much like an [`Entity`], clients posess a location, rotation, and UUID.
/// Clients are handled separately from entities and are partially
/// controlled by the library.
///
/// By default, clients have no influence over the worlds they reside in. They
/// cannot break blocks, hurt entities, or see other clients. Interactions with
/// the server must be handled explicitly with [`Self::pop_event`].
///
/// Additionally, clients posess [`Player`] entity data which is only visible to
/// themselves. This can be accessed with [`Self::player`] and
/// [`Self::player_mut`].
///
/// # The Difference Between a "Client" and a "Player"
///
/// Normally in Minecraft, players and clients are one and the same. Players are
/// simply a special type of entity which is backed by a remote connection.
///
/// In Valence however, clients and players have been decoupled. This separation
/// was done primarily to enable multithreaded client updates.
pub struct Client { pub struct Client {
/// Setting this to `None` disconnects the client. /// Setting this to `None` disconnects the client.
send: SendOpt, send: SendOpt,
@ -125,7 +181,7 @@ pub struct Client {
spawn_position: BlockPos, spawn_position: BlockPos,
spawn_position_yaw: f32, spawn_position_yaw: f32,
death_location: Option<(DimensionId, BlockPos)>, death_location: Option<(DimensionId, BlockPos)>,
events: VecDeque<ClientEvent>, events: VecDeque<Event>,
/// The ID of the last keepalive sent. /// The ID of the last keepalive sent.
last_keepalive_id: i64, last_keepalive_id: i64,
new_max_view_distance: u8, new_max_view_distance: u8,
@ -207,42 +263,58 @@ impl Client {
} }
} }
/// Gets the tick that this client was created.
pub fn created_tick(&self) -> Ticks { pub fn created_tick(&self) -> Ticks {
self.created_tick self.created_tick
} }
/// Gets the client's UUID.
pub fn uuid(&self) -> Uuid { pub fn uuid(&self) -> Uuid {
self.uuid self.uuid
} }
/// Gets the username of this client, which is always valid.
pub fn username(&self) -> &str { pub fn username(&self) -> &str {
&self.username &self.username
} }
/// Gets the player textures of this client. If the client does not have
/// a skin, then `None` is returned.
pub fn textures(&self) -> Option<&SignedPlayerTextures> { pub fn textures(&self) -> Option<&SignedPlayerTextures> {
self.textures.as_ref() self.textures.as_ref()
} }
/// Gets the world this client is located in.
pub fn world(&self) -> WorldId { pub fn world(&self) -> WorldId {
self.world self.world
} }
/// Changes the world this client is located in.
///
/// The given [`WorldId`] must be valid. Otherwise, the client is
/// disconnected.
pub fn spawn(&mut self, world: WorldId) { pub fn spawn(&mut self, world: WorldId) {
self.world = world; self.world = world;
self.flags.set_spawn(true); self.flags.set_spawn(true);
} }
/// Sends a system message to the player. /// Sends a system message to the player which is visible in the chat.
pub fn send_message(&mut self, msg: impl Into<Text>) { pub fn send_message(&mut self, msg: impl Into<Text>) {
// We buffer messages because weird things happen if we send them before the // We buffer messages because weird things happen if we send them before the
// login packet. // login packet.
self.msgs_to_send.push(msg.into()); self.msgs_to_send.push(msg.into());
} }
/// Gets the absolute position of this client in the world it is located
/// in.
pub fn position(&self) -> Vec3<f64> { pub fn position(&self) -> Vec3<f64> {
self.new_position self.new_position
} }
/// Changes the position and rotation of this client in the world it is
/// located in.
///
/// If you want to change the client's world, use [`Self::spawn`].
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) { pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
self.new_position = pos.into(); self.new_position = pos.into();
@ -265,22 +337,24 @@ impl Client {
} }
} }
/// Gets this client's yaw.
pub fn yaw(&self) -> f32 { pub fn yaw(&self) -> f32 {
self.yaw self.yaw
} }
/// Gets this client's pitch.
pub fn pitch(&self) -> f32 { pub fn pitch(&self) -> f32 {
self.pitch self.pitch
} }
/// Gets the spawn position. The client will see regular compasses point at /// Gets the spawn position. The client will see `minecraft:compass` items
/// the returned position. /// point at the returned position.
pub fn spawn_position(&self) -> BlockPos { pub fn spawn_position(&self) -> BlockPos {
self.spawn_position self.spawn_position
} }
/// Sets the spawn position. The client will see regular compasses point at /// Sets the spawn position. The client will see `minecraft:compass` items
/// the provided position. /// point at the provided position.
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) { pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
let pos = pos.into(); let pos = pos.into();
if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw { if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw {
@ -290,18 +364,20 @@ impl Client {
} }
} }
/// Gets the last death location. The client will see recovery compasses /// Gets the last death location of this client. The client will see
/// point at the returned position. If the client's current dimension /// `minecraft:recovery_compass` items point at the returned position.
/// differs from the returned dimension or the location is `None` then the /// If the client's current dimension differs from the returned
/// compass will spin randomly. /// dimension or the location is `None` then the compass will spin
/// randomly.
pub fn death_location(&self) -> Option<(DimensionId, BlockPos)> { pub fn death_location(&self) -> Option<(DimensionId, BlockPos)> {
self.death_location self.death_location
} }
/// Sets the last death location. The client will see recovery compasses /// Sets the last death location. The client will see
/// point at the provided position. If the client's current dimension /// `minecraft:recovery_compass` items point at the provided position.
/// differs from the provided dimension or the location is `None` then the /// If the client's current dimension differs from the provided
/// compass will spin randomly. /// dimension or the location is `None` then the compass will spin
/// randomly.
/// ///
/// Changes to the last death location take effect when the client /// Changes to the last death location take effect when the client
/// (re)spawns. /// (re)spawns.
@ -309,14 +385,22 @@ impl Client {
self.death_location = location; self.death_location = location;
} }
/// Gets the client's game mode.
pub fn game_mode(&self) -> GameMode { pub fn game_mode(&self) -> GameMode {
self.new_game_mode self.new_game_mode
} }
pub fn set_game_mode(&mut self, new_game_mode: GameMode) { /// Sets the client's game mode.
self.new_game_mode = new_game_mode; pub fn set_game_mode(&mut self, game_mode: GameMode) {
self.new_game_mode = game_mode;
} }
/// Sets the title this client sees.
///
/// A title is a large piece of text displayed in the center of the screen
/// which may also include a subtitle underneath it. The title
/// can be configured to fade in and out using the
/// [`TitleAnimationTimes`] struct.
pub fn set_title( pub fn set_title(
&mut self, &mut self,
title: impl Into<Text>, title: impl Into<Text>,
@ -339,19 +423,30 @@ impl Client {
} }
} }
/// Removes the current title from the client's screen.
pub fn clear_title(&mut self) { pub fn clear_title(&mut self) {
self.send_packet(ClearTitles { reset: true }); self.send_packet(ClearTitles { reset: true });
} }
/// Gets if the client is on the ground, as determined by the client.
pub fn on_ground(&self) -> bool { pub fn on_ground(&self) -> bool {
self.flags.on_ground() self.flags.on_ground()
} }
/// Gets whether or not the client is connected to the server.
///
/// A disconnected client object will never become reconnected. It is your
/// responsibility to remove disconnected clients from the [`Clients`]
/// container.
pub fn is_disconnected(&self) -> bool { pub fn is_disconnected(&self) -> bool {
self.send.is_none() self.send.is_none()
} }
pub fn pop_event(&mut self) -> Option<ClientEvent> { /// Removes an [`Event`] from the queue.
///
/// Any remaining client events not popped are deleted at the end of the
/// current tick.
pub fn pop_event(&mut self) -> Option<Event> {
self.events.pop_front() self.events.pop_front()
} }
@ -363,28 +458,44 @@ impl Client {
.min(self.max_view_distance()) .min(self.max_view_distance())
} }
/// Gets the maximum view distance. The client will not be able to see
/// chunks and entities past this distance.
///
/// The value returned is measured in chunks.
pub fn max_view_distance(&self) -> u8 { pub fn max_view_distance(&self) -> u8 {
self.new_max_view_distance self.new_max_view_distance
} }
/// The new view distance is clamped to `2..=32`. /// Sets the maximum view distance. The client will not be able to see
/// chunks and entities past this distance.
///
/// The new view distance is measured in chunks and is clamped to `2..=32`.
pub fn set_max_view_distance(&mut self, dist: u8) { pub fn set_max_view_distance(&mut self, dist: u8) {
self.new_max_view_distance = dist.clamp(2, 32); self.new_max_view_distance = dist.clamp(2, 32);
} }
/// Must be set on the same tick the client joins the game. /// Enables hardcore mode. This changes the design of the client's hearts.
///
/// To have any visible effect, this function must be called on the same
/// tick the client joins the server.
pub fn set_hardcore(&mut self, hardcore: bool) { pub fn set_hardcore(&mut self, hardcore: bool) {
self.flags.set_hardcore(hardcore); self.flags.set_hardcore(hardcore);
} }
/// Gets if hardcore mode is enabled.
pub fn is_hardcore(&mut self) -> bool { pub fn is_hardcore(&mut self) -> bool {
self.flags.hardcore() self.flags.hardcore()
} }
/// Gets the client's current settings.
pub fn settings(&self) -> Option<&Settings> { pub fn settings(&self) -> Option<&Settings> {
self.settings.as_ref() self.settings.as_ref()
} }
/// Disconnects this client from the server with the provided reason. This
/// has no effect if the client is already disconnected.
///
/// All future calls to [`Self::is_disconnected`] will return `true`.
pub fn disconnect(&mut self, reason: impl Into<Text>) { pub fn disconnect(&mut self, reason: impl Into<Text>) {
if self.send.is_some() { if self.send.is_some() {
let txt = reason.into(); let txt = reason.into();
@ -396,6 +507,8 @@ impl Client {
} }
} }
/// Like [`Self::disconnect`], but no reason for the disconnect is
/// displayed.
pub fn disconnect_no_reason(&mut self) { pub fn disconnect_no_reason(&mut self) {
if self.send.is_some() { if self.send.is_some() {
log::info!("disconnecting client '{}'", self.username); log::info!("disconnecting client '{}'", self.username);
@ -403,11 +516,15 @@ impl Client {
} }
} }
pub fn data(&self) -> &Player { /// Returns an immutable reference to the client's own [`Player`] data.
pub fn player(&self) -> &Player {
&self.player_data &self.player_data
} }
pub fn data_mut(&mut self) -> &mut Player { /// Returns a mutable reference to the client's own [`Player`] data.
///
/// Changes made to this data is only visible to this client.
pub fn player_mut(&mut self) -> &mut Player {
&mut self.player_data &mut self.player_data
} }
@ -442,7 +559,7 @@ impl Client {
if client.pending_teleports == 0 { if client.pending_teleports == 0 {
// TODO: validate movement using swept AABB collision with the blocks. // TODO: validate movement using swept AABB collision with the blocks.
// TODO: validate that the client is actually inside/outside the vehicle? // TODO: validate that the client is actually inside/outside the vehicle?
let event = ClientEvent::Movement { let event = Event::Movement {
position: client.new_position, position: client.new_position,
yaw: client.yaw, yaw: client.yaw,
pitch: client.pitch, pitch: client.pitch,
@ -485,7 +602,7 @@ impl Client {
C2sPlayPacket::BlockEntityTagQuery(_) => {} C2sPlayPacket::BlockEntityTagQuery(_) => {}
C2sPlayPacket::ChangeDifficulty(_) => {} C2sPlayPacket::ChangeDifficulty(_) => {}
C2sPlayPacket::ChatCommand(_) => {} C2sPlayPacket::ChatCommand(_) => {}
C2sPlayPacket::Chat(p) => self.events.push_back(ClientEvent::ChatMessage { C2sPlayPacket::Chat(p) => self.events.push_back(Event::ChatMessage {
message: p.message.0, message: p.message.0,
timestamp: Duration::from_millis(p.timestamp), timestamp: Duration::from_millis(p.timestamp),
}), }),
@ -502,7 +619,7 @@ impl Client {
allow_server_listings: p.allow_server_listings, allow_server_listings: p.allow_server_listings,
}); });
self.events.push_back(ClientEvent::SettingsChanged(old)); self.events.push_back(Event::SettingsChanged(old));
} }
C2sPlayPacket::CommandSuggestion(_) => {} C2sPlayPacket::CommandSuggestion(_) => {}
C2sPlayPacket::ContainerButtonClick(_) => {} C2sPlayPacket::ContainerButtonClick(_) => {}
@ -515,14 +632,14 @@ impl Client {
// TODO: verify that the client has line of sight to the targeted entity and // TODO: verify that the client has line of sight to the targeted entity and
// that the distance is <=4 blocks. // that the distance is <=4 blocks.
self.events.push_back(ClientEvent::InteractWithEntity { self.events.push_back(Event::InteractWithEntity {
id, id,
sneaking: p.sneaking, sneaking: p.sneaking,
kind: match p.kind { kind: match p.kind {
InteractKind::Interact(hand) => InteractWithEntity::Interact(hand), InteractKind::Interact(hand) => InteractWithEntityKind::Interact(hand),
InteractKind::Attack => InteractWithEntity::Attack, InteractKind::Attack => InteractWithEntityKind::Attack,
InteractKind::InteractAt((target, hand)) => { InteractKind::InteractAt((target, hand)) => {
InteractWithEntity::InteractAt { target, hand } InteractWithEntityKind::InteractAt { target, hand }
} }
}, },
}); });
@ -575,7 +692,7 @@ impl Client {
); );
} }
C2sPlayPacket::PaddleBoat(p) => { C2sPlayPacket::PaddleBoat(p) => {
self.events.push_back(ClientEvent::SteerBoat { self.events.push_back(Event::SteerBoat {
left_paddle_turning: p.left_paddle_turning, left_paddle_turning: p.left_paddle_turning,
right_paddle_turning: p.right_paddle_turning, right_paddle_turning: p.right_paddle_turning,
}); });
@ -592,21 +709,21 @@ impl Client {
} }
self.events.push_back(match p.status { self.events.push_back(match p.status {
DiggingStatus::StartedDigging => ClientEvent::Digging(Digging { DiggingStatus::StartedDigging => Event::Digging {
status: event::DiggingStatus::Start, status: event::DiggingStatus::Start,
position: p.location, position: p.location,
face: p.face, face: p.face,
}), },
DiggingStatus::CancelledDigging => ClientEvent::Digging(Digging { DiggingStatus::CancelledDigging => Event::Digging {
status: event::DiggingStatus::Cancel, status: event::DiggingStatus::Cancel,
position: p.location, position: p.location,
face: p.face, face: p.face,
}), },
DiggingStatus::FinishedDigging => ClientEvent::Digging(Digging { DiggingStatus::FinishedDigging => Event::Digging {
status: event::DiggingStatus::Finish, status: event::DiggingStatus::Finish,
position: p.location, position: p.location,
face: p.face, face: p.face,
}), },
DiggingStatus::DropItemStack => return, DiggingStatus::DropItemStack => return,
DiggingStatus::DropItem => return, DiggingStatus::DropItem => return,
DiggingStatus::ShootArrowOrFinishEating => return, DiggingStatus::ShootArrowOrFinishEating => return,
@ -623,31 +740,33 @@ impl Client {
self.events.push_back(match e.action_id { self.events.push_back(match e.action_id {
PlayerCommandId::StartSneaking => { PlayerCommandId::StartSneaking => {
self.flags.set_sneaking(true); self.flags.set_sneaking(true);
ClientEvent::StartSneaking Event::StartSneaking
} }
PlayerCommandId::StopSneaking => { PlayerCommandId::StopSneaking => {
self.flags.set_sneaking(false); self.flags.set_sneaking(false);
ClientEvent::StopSneaking Event::StopSneaking
} }
PlayerCommandId::LeaveBed => ClientEvent::LeaveBed, PlayerCommandId::LeaveBed => Event::LeaveBed,
PlayerCommandId::StartSprinting => { PlayerCommandId::StartSprinting => {
self.flags.set_sprinting(true); self.flags.set_sprinting(true);
ClientEvent::StartSprinting Event::StartSprinting
} }
PlayerCommandId::StopSprinting => { PlayerCommandId::StopSprinting => {
self.flags.set_sprinting(false); self.flags.set_sprinting(false);
ClientEvent::StopSprinting Event::StopSprinting
} }
PlayerCommandId::StartJumpWithHorse => { PlayerCommandId::StartJumpWithHorse => {
self.flags.set_jumping_with_horse(true); self.flags.set_jumping_with_horse(true);
ClientEvent::StartJumpWithHorse(e.jump_boost.0 .0 as u8) Event::StartJumpWithHorse {
jump_boost: e.jump_boost.0 .0 as u8,
}
} }
PlayerCommandId::StopJumpWithHorse => { PlayerCommandId::StopJumpWithHorse => {
self.flags.set_jumping_with_horse(false); self.flags.set_jumping_with_horse(false);
ClientEvent::StopJumpWithHorse Event::StopJumpWithHorse
} }
PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory, PlayerCommandId::OpenHorseInventory => Event::OpenHorseInventory,
PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra, PlayerCommandId::StartFlyingWithElytra => Event::StartFlyingWithElytra,
}); });
} }
C2sPlayPacket::PlayerInput(_) => {} C2sPlayPacket::PlayerInput(_) => {}
@ -666,7 +785,7 @@ impl Client {
C2sPlayPacket::SetJigsawBlock(_) => {} C2sPlayPacket::SetJigsawBlock(_) => {}
C2sPlayPacket::SetStructureBlock(_) => {} C2sPlayPacket::SetStructureBlock(_) => {}
C2sPlayPacket::SignUpdate(_) => {} C2sPlayPacket::SignUpdate(_) => {}
C2sPlayPacket::Swing(p) => self.events.push_back(ClientEvent::ArmSwing(p.hand)), C2sPlayPacket::Swing(p) => self.events.push_back(Event::ArmSwing(p.hand)),
C2sPlayPacket::TeleportToEntity(_) => {} C2sPlayPacket::TeleportToEntity(_) => {}
C2sPlayPacket::UseItemOn(_) => {} C2sPlayPacket::UseItemOn(_) => {}
C2sPlayPacket::UseItem(_) => {} C2sPlayPacket::UseItem(_) => {}
@ -715,7 +834,7 @@ impl Client {
gamemode: self.new_game_mode, gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode, previous_gamemode: self.old_game_mode,
dimension_names, dimension_names,
registry_codec: Nbt(make_dimension_codec(shared)), registry_codec: Nbt(make_registry_codec(shared)),
dimension_type_name: ident!( dimension_type_name: ident!(
"{LIBRARY_NAMESPACE}:dimension_type_{}", "{LIBRARY_NAMESPACE}:dimension_type_{}",
world.meta.dimension().0 world.meta.dimension().0
@ -1128,21 +1247,21 @@ fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
} }
} }
fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec { fn make_registry_codec(shared: &SharedServer) -> RegistryCodec {
let mut dims = Vec::new(); let mut dims = Vec::new();
for (id, dim) in shared.dimensions() { for (id, dim) in shared.dimensions() {
let id = id.0 as i32; let id = id.0 as i32;
dims.push(DimensionTypeRegistryEntry { dims.push(DimensionTypeRegistryEntry {
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"), name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
id, id,
element: to_dimension_registry_item(dim), element: dim.to_dimension_registry_item(),
}) })
} }
let mut biomes = Vec::new(); let mut biomes: Vec<_> = shared
for (id, biome) in shared.biomes() { .biomes()
biomes.push(to_biome_registry_item(biome, id.0 as i32)); .map(|(id, biome)| biome.to_biome_registry_item(id.0 as i32))
} .collect();
// The client needs a biome named "minecraft:plains" in the registry to // The client needs a biome named "minecraft:plains" in the registry to
// connect. This is probably a bug. // connect. This is probably a bug.
@ -1151,7 +1270,7 @@ fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
if !biomes.iter().any(|b| b.name == ident!("plains")) { if !biomes.iter().any(|b| b.name == ident!("plains")) {
let biome = Biome::default(); let biome = Biome::default();
assert_eq!(biome.name, ident!("plains")); assert_eq!(biome.name, ident!("plains"));
biomes.push(to_biome_registry_item(&biome, biomes.len() as i32)); biomes.push(biome.to_biome_registry_item(biomes.len() as i32));
} }
RegistryCodec { RegistryCodec {
@ -1171,94 +1290,10 @@ fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
element: ChatType { element: ChatType {
chat: ChatTypeChat {}, chat: ChatTypeChat {},
narration: ChatTypeNarration { narration: ChatTypeNarration {
priority: "system".to_owned(), priority: "system".into(),
}, },
}, },
}], }],
}, },
} }
} }
fn to_dimension_registry_item(dim: &Dimension) -> DimensionType {
DimensionType {
piglin_safe: true,
has_raids: true,
monster_spawn_light_level: 0,
monster_spawn_block_light_limit: 0,
natural: dim.natural,
ambient_light: dim.ambient_light,
fixed_time: dim.fixed_time.map(|t| t as i64),
infiniburn: "#minecraft:infiniburn_overworld".into(),
respawn_anchor_works: true,
has_skylight: true,
bed_works: true,
effects: match dim.effects {
DimensionEffects::Overworld => ident!("overworld"),
DimensionEffects::TheNether => ident!("the_nether"),
DimensionEffects::TheEnd => ident!("the_end"),
},
min_y: dim.min_y,
height: dim.height,
logical_height: dim.height,
coordinate_scale: 1.0,
ultrawarm: false,
has_ceiling: false,
}
}
fn to_biome_registry_item(biome: &Biome, id: i32) -> BiomeRegistryBiome {
BiomeRegistryBiome {
name: biome.name.clone(),
id,
element: BiomeProperty {
precipitation: match biome.precipitation {
BiomePrecipitation::Rain => "rain",
BiomePrecipitation::Snow => "snow",
BiomePrecipitation::None => "none",
}
.into(),
depth: 0.125,
temperature: 0.8,
scale: 0.05,
downfall: 0.4,
category: "none".into(),
temperature_modifier: None,
effects: BiomeEffects {
sky_color: biome.sky_color as i32,
water_fog_color: biome.water_fog_color as i32,
fog_color: biome.fog_color as i32,
water_color: biome.water_color as i32,
foliage_color: biome.foliage_color.map(|x| x as i32),
grass_color: biome.grass_color.map(|x| x as i32),
grass_color_modifier: match biome.grass_color_modifier {
BiomeGrassColorModifier::Swamp => Some("swamp".into()),
BiomeGrassColorModifier::DarkForest => Some("dark_forest".into()),
BiomeGrassColorModifier::None => None,
},
music: biome.music.as_ref().map(|bm| BiomeMusic {
replace_current_music: bm.replace_current_music,
sound: bm.sound.clone(),
max_delay: bm.max_delay,
min_delay: bm.min_delay,
}),
ambient_sound: biome.ambient_sound.clone(),
additions_sound: biome.additions_sound.as_ref().map(|a| BiomeAdditionsSound {
sound: a.sound.clone(),
tick_chance: a.tick_chance,
}),
mood_sound: biome.mood_sound.as_ref().map(|m| BiomeMoodSound {
sound: m.sound.clone(),
tick_delay: m.tick_delay,
offset: m.offset,
block_search_extent: m.block_search_extent,
}),
},
particle: biome.particle.as_ref().map(|p| BiomeParticle {
probability: p.probability,
options: BiomeParticleOptions {
kind: p.kind.clone(),
},
}),
},
}
}

View file

@ -4,50 +4,76 @@ use vek::Vec3;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::entity::EntityId; use crate::entity::EntityId;
use crate::protocol::packets::play::c2s::BlockFace; use crate::protocol_inner::packets::play::c2s::BlockFace;
pub use crate::protocol::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand}; pub use crate::protocol_inner::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand};
pub use crate::protocol::packets::play::s2c::GameMode; pub use crate::protocol_inner::packets::play::s2c::GameMode;
/// Represents an action performed by a client.
///
/// Client events can be obtained from
/// [`pop_event`](crate::client::Client::pop_event).
#[derive(Debug)] #[derive(Debug)]
pub enum ClientEvent { pub enum Event {
/// A regular message was sent to the chat.
ChatMessage { ChatMessage {
/// The content of the message
message: String, message: String,
/// The time the message was sent.
timestamp: Duration, timestamp: Duration,
}, },
/// Settings were changed. The value in this variant is the previous client /// Settings were changed. The value in this variant is the _previous_
/// settings. /// client settings.
SettingsChanged(Option<Settings>), SettingsChanged(Option<Settings>),
/// The client has moved. The values in this /// The client moved. The values in this
/// variant are the _previous_ position and look. /// variant are the _previous_ position and look.
Movement { Movement {
/// Absolute coordinates of the previous position.
position: Vec3<f64>, position: Vec3<f64>,
/// The previous yaw (in degrees).
yaw: f32, yaw: f32,
/// The previous pitch (in degrees).
pitch: f32, pitch: f32,
/// If the client was previously on the ground.
on_ground: bool, on_ground: bool,
}, },
StartSneaking, StartSneaking,
StopSneaking, StopSneaking,
StartSprinting, StartSprinting,
StopSprinting, StopSprinting,
StartJumpWithHorse(u8), /// A jump while on a horse started.
StartJumpWithHorse {
/// The power of the horse jump.
jump_boost: u8,
},
/// A jump while on a horse stopped.
StopJumpWithHorse, StopJumpWithHorse,
/// The client left a bed.
LeaveBed, LeaveBed,
/// The inventory was opened while on a horse.
OpenHorseInventory, OpenHorseInventory,
StartFlyingWithElytra, StartFlyingWithElytra,
ArmSwing(Hand), ArmSwing(Hand),
/// Left or right click interaction with an entity's hitbox.
InteractWithEntity { InteractWithEntity {
/// The ID of the entity being interacted with. /// The ID of the entity being interacted with.
id: EntityId, id: EntityId,
/// If the client was sneaking during the interaction. /// If the client was sneaking during the interaction.
sneaking: bool, sneaking: bool,
/// The type of interaction that occurred. /// The kind of interaction that occurred.
kind: InteractWithEntity, kind: InteractWithEntityKind,
}, },
SteerBoat { SteerBoat {
left_paddle_turning: bool, left_paddle_turning: bool,
right_paddle_turning: bool, right_paddle_turning: bool,
}, },
Digging(Digging), Digging {
/// The kind of digging event this is.
status: DiggingStatus,
/// The position of the block being broken.
position: BlockPos,
/// The face of the block being broken.
face: BlockFace,
},
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
@ -67,22 +93,18 @@ pub struct Settings {
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum InteractWithEntity { pub enum InteractWithEntityKind {
Interact(Hand), Interact(Hand),
InteractAt { target: Vec3<f32>, hand: Hand }, InteractAt { target: Vec3<f32>, hand: Hand },
Attack, Attack,
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Digging {
pub status: DiggingStatus,
pub position: BlockPos,
pub face: BlockFace,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum DiggingStatus { pub enum DiggingStatus {
/// The client started digging a block.
Start, Start,
/// The client stopped digging a block before it was fully broken.
Cancel, Cancel,
/// The client finished digging a block successfully.
Finish, Finish,
} }

View file

@ -15,14 +15,15 @@ use crate::Ticks;
/// server. /// server.
/// ///
/// The config is used from multiple threads and must therefore implement /// The config is used from multiple threads and must therefore implement
/// `Send` and `Sync`. From within a single thread, methods are never invoked /// [`Send`] and [`Sync`]. From within a single thread, methods are never
/// recursively by the library. In other words, a mutex can be aquired at /// invoked recursively by the library. In other words, a mutex can be aquired
/// the beginning of a method and released at the end without risk of /// at the beginning of a method and released at the end without risk of
/// deadlocking. /// deadlocking.
/// ///
/// This trait uses the [async_trait](https://docs.rs/async-trait/latest/async_trait/) attribute macro. /// This trait uses the [async_trait] attribute macro. It is exported at the
/// This will be removed once `impl Trait` in return position in traits is /// root of this crate.
/// available in stable rust. ///
/// [async_trait]: https://docs.rs/async-trait/latest/async_trait/
#[async_trait] #[async_trait]
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe { pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
@ -39,6 +40,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// be bound to. /// be bound to.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// Returns `127.0.0.1:25565`. /// Returns `127.0.0.1:25565`.
fn address(&self) -> SocketAddr { fn address(&self) -> SocketAddr {
SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 25565).into() SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 25565).into()
@ -57,6 +59,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// so there is little benefit to a tick rate higher than 20. /// so there is little benefit to a tick rate higher than 20.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// Returns `20`, which is the same as Minecraft's official server. /// Returns `20`, which is the same as Minecraft's official server.
fn tick_rate(&self) -> Ticks { fn tick_rate(&self) -> Ticks {
20 20
@ -73,6 +76,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// internet. /// internet.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// Returns `true`. /// Returns `true`.
fn online_mode(&self) -> bool { fn online_mode(&self) -> bool {
true true
@ -85,6 +89,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// potential memory usage. /// potential memory usage.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// An unspecified value is returned that should be adequate in most /// An unspecified value is returned that should be adequate in most
/// situations. /// situations.
fn incoming_packet_capacity(&self) -> usize { fn incoming_packet_capacity(&self) -> usize {
@ -98,6 +103,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// but increases potential memory usage. /// but increases potential memory usage.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// An unspecified value is returned that should be adequate in most /// An unspecified value is returned that should be adequate in most
/// situations. /// situations.
fn outgoing_packet_capacity(&self) -> usize { fn outgoing_packet_capacity(&self) -> usize {
@ -111,6 +117,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// runtime. /// runtime.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// Returns `None`. /// Returns `None`.
fn tokio_handle(&self) -> Option<TokioHandle> { fn tokio_handle(&self) -> Option<TokioHandle> {
None None
@ -119,14 +126,15 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Called once at startup to get the list of [`Dimension`]s usable on the /// Called once at startup to get the list of [`Dimension`]s usable on the
/// server. /// server.
/// ///
/// The dimensions traversed by [`Server::dimensions`] will be in the same /// The dimensions returned by [`SharedServer::dimensions`] will be in the
/// order as the `Vec` returned by this function. /// same order as the `Vec` returned by this function.
/// ///
/// The number of elements in the returned `Vec` must be in \[1, u16::MAX]. /// The number of elements in the returned `Vec` must be in `1..=u16::MAX`.
/// Additionally, the documented requirements on the fields of [`Dimension`] /// Additionally, the documented requirements on the fields of [`Dimension`]
/// must be met. /// must be met.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// Returns `vec![Dimension::default()]`. /// Returns `vec![Dimension::default()]`.
fn dimensions(&self) -> Vec<Dimension> { fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension::default()] vec![Dimension::default()]
@ -135,14 +143,15 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Called once at startup to get the list of [`Biome`]s usable on the /// Called once at startup to get the list of [`Biome`]s usable on the
/// server. /// server.
/// ///
/// The biomes traversed by [`Server::biomes`] will be in the same /// The biomes returned by [`SharedServer::biomes`] will be in the same
/// order as the `Vec` returned by this function. /// order as the `Vec` returned by this function.
/// ///
/// The number of elements in the returned `Vec` must be in \[1, u16::MAX]. /// The number of elements in the returned `Vec` must be in `1..=u16::MAX`.
/// Additionally, the documented requirements on the fields of [`Biome`] /// Additionally, the documented requirements on the fields of [`Biome`]
/// must be met. /// must be met.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// Returns `vec![Dimension::default()]`. /// Returns `vec![Dimension::default()]`.
fn biomes(&self) -> Vec<Biome> { fn biomes(&self) -> Vec<Biome> {
vec![Biome::default()] vec![Biome::default()]
@ -154,6 +163,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// This method is called from within a tokio runtime. /// This method is called from within a tokio runtime.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// The query is ignored. /// The query is ignored.
async fn server_list_ping( async fn server_list_ping(
&self, &self,
@ -164,10 +174,10 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
} }
/// Called asynchronously for each client after successful authentication /// Called asynchronously for each client after successful authentication
/// (if online mode is enabled) to determine if they can continue to join /// (if online mode is enabled) to determine if they can join
/// the server. On success, [`Config::join`] is called with the new /// the server. On success, the new client is added to the server's
/// client. If this method returns with `Err(reason)`, then the client is /// [`Clients`]. If this method returns with `Err(reason)`, then the
/// immediately disconnected with the given reason. /// client is immediately disconnected with the given reason.
/// ///
/// This method is the appropriate place to perform asynchronous /// This method is the appropriate place to perform asynchronous
/// operations such as database queries which may take some time to /// operations such as database queries which may take some time to
@ -176,7 +186,10 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// This method is called from within a tokio runtime. /// This method is called from within a tokio runtime.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// The client is allowed to join unconditionally. /// The client is allowed to join unconditionally.
///
/// [`Clients`]: crate::client::Clients
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> { async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
Ok(()) Ok(())
} }
@ -191,25 +204,29 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
fn init(&self, server: &mut Server) {} fn init(&self, server: &mut Server) {}
/// Called once at the beginning of every server update (also known as /// Called once at the beginning of every server update (also known as
/// a "tick"). /// "tick"). This is likely where the majority of your code will be.
/// ///
/// The frequency of server updates can be configured by `update_duration` /// The frequency of ticks can be configured by [`Self::tick_rate`].
/// in [`ServerConfig`].
/// ///
/// This method is called from within a tokio runtime. /// This method is called from within a tokio runtime.
/// ///
/// # Default Implementation /// # Default Implementation
///
/// The default implementation does nothing. /// The default implementation does nothing.
fn update(&self, server: &mut Server); fn update(&self, server: &mut Server);
} }
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback. /// The result of the [`server_list_ping`](Config::server_list_ping) callback.
#[derive(Debug)] #[derive(Debug)]
pub enum ServerListPing<'a> { pub enum ServerListPing<'a> {
/// Responds to the server list ping with the given information. /// Responds to the server list ping with the given information.
Respond { Respond {
/// Displayed as the number of players on the server.
online_players: i32, online_players: i32,
/// Displayed as the maximum number of players allowed on the server at
/// a time.
max_players: i32, max_players: i32,
/// A description of the server.
description: Text, description: Text,
/// The server's icon as the bytes of a PNG image. /// The server's icon as the bytes of a PNG image.
/// The image must be 64x64 pixels. /// The image must be 64x64 pixels.

View file

@ -1,17 +1,18 @@
/// A handle to a particular [`Dimension`] on the server. use crate::ident;
use crate::protocol_inner::packets::play::s2c::DimensionType;
/// Identifies a particular [`Dimension`] on the server.
/// ///
/// Dimension IDs must only be used on servers from which they originate. /// The default dimension ID refers to the first dimension added in the server's
/// [configuration](crate::config::Config).
///
/// To obtain dimension IDs for other dimensions, call
/// [`dimensions`](crate::server::SharedServer::dimensions).
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct DimensionId(pub(crate) u16); pub struct DimensionId(pub(crate) u16);
impl DimensionId {
pub fn to_index(self) -> usize {
self.0 as usize
}
}
/// The default dimension ID corresponds to the first element in the `Vec` /// The default dimension ID corresponds to the first element in the `Vec`
/// returned by [`Config::dimensions`]. /// returned by [`crate::config::Config::dimensions`].
impl Default for DimensionId { impl Default for DimensionId {
fn default() -> Self { fn default() -> Self {
Self(0) Self(0)
@ -20,14 +21,19 @@ impl Default for DimensionId {
/// Contains the configuration for a dimension type. /// Contains the configuration for a dimension type.
/// ///
/// In Minecraft, "dimension" and "dimension type" are two different concepts. /// On creation, each [`World`] in Valence is assigned a dimension. The
/// dimension determines certain properties of the world such as its height and
/// ambient lighting.
///
/// In Minecraft, "dimension" and "dimension type" are two distinct concepts.
/// For instance, the Overworld and Nether are dimensions, each with /// For instance, the Overworld and Nether are dimensions, each with
/// their own dimension type. A dimension in this library is analogous to a /// their own dimension type. A dimension in this library is analogous to a
/// [`World`](crate::World) while [`Dimension`] represents a /// [`World`] while [`Dimension`] represents a dimension type.
/// dimension type. ///
/// [`World`]: crate::world::World
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Dimension { pub struct Dimension {
/// When false, compases will spin randomly. /// When false, compasses will spin randomly.
pub natural: bool, pub natural: bool,
/// Must be between 0.0 and 1.0. /// Must be between 0.0 and 1.0.
pub ambient_light: f32, pub ambient_light: f32,
@ -63,21 +69,52 @@ pub struct Dimension {
// * has_ceiling // * has_ceiling
} }
impl Dimension {
pub(crate) fn to_dimension_registry_item(&self) -> DimensionType {
DimensionType {
piglin_safe: true,
has_raids: true,
monster_spawn_light_level: 0,
monster_spawn_block_light_limit: 0,
natural: self.natural,
ambient_light: self.ambient_light,
fixed_time: self.fixed_time.map(|t| t as i64),
infiniburn: "#minecraft:infiniburn_overworld".into(),
respawn_anchor_works: true,
has_skylight: true,
bed_works: true,
effects: match self.effects {
DimensionEffects::Overworld => ident!("overworld"),
DimensionEffects::TheNether => ident!("the_nether"),
DimensionEffects::TheEnd => ident!("the_end"),
},
min_y: self.min_y,
height: self.height,
logical_height: self.height,
coordinate_scale: 1.0,
ultrawarm: false,
has_ceiling: false,
}
}
}
impl Default for Dimension { impl Default for Dimension {
fn default() -> Self { fn default() -> Self {
Self { Self {
natural: true, natural: true,
ambient_light: 1.0, ambient_light: 1.0,
fixed_time: None, fixed_time: None,
effects: DimensionEffects::Overworld, effects: DimensionEffects::default(),
min_y: -64, min_y: -64,
height: 384, height: 384,
} }
} }
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Determines what skybox/fog effects to use in dimensions.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum DimensionEffects { pub enum DimensionEffects {
#[default]
Overworld, Overworld,
TheNether, TheNether,
TheEnd, TheEnd,

View file

@ -1,5 +1,5 @@
pub mod data; pub mod data;
pub mod meta; pub mod types;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
@ -7,19 +7,29 @@ use std::iter::FusedIterator;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use bitfield_struct::bitfield; use bitfield_struct::bitfield;
pub use data::{EntityData, EntityKind};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
pub use types::{EntityData, EntityKind};
use uuid::Uuid; use uuid::Uuid;
use vek::{Aabb, Vec3}; use vek::{Aabb, Vec3};
use crate::protocol::packets::play::s2c::{ use crate::protocol_inner::packets::play::s2c::{
AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata, AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata,
}; };
use crate::protocol::{ByteAngle, RawBytes, VarInt}; use crate::protocol_inner::{ByteAngle, RawBytes, VarInt};
use crate::slotmap::{Key, SlotMap}; use crate::slotmap::{Key, SlotMap};
use crate::util::aabb_from_bottom_and_size; use crate::util::aabb_from_bottom_and_size;
use crate::world::WorldId; use crate::world::WorldId;
/// A container for all [`Entity`]s on a [`Server`](crate::server::Server).
///
/// # Spawning Player Entities
///
/// [`Player`] entities are treated specially by the client. For the player
/// entity to be visible to clients, the player's UUID must be added to the
/// [`PlayerList`] _before_ being loaded by the client.
///
/// [`Player`]: crate::entity::types::Player
/// [`PlayerList`]: crate::player_list::PlayerList
pub struct Entities { pub struct Entities {
sm: SlotMap<Entity>, sm: SlotMap<Entity>,
uuid_to_entity: HashMap<Uuid, EntityId>, uuid_to_entity: HashMap<Uuid, EntityId>,
@ -35,18 +45,18 @@ impl Entities {
} }
} }
/// Spawns a new entity with the default data. The new entity's [`EntityId`] /// Spawns a new entity with a random UUID. A reference to the entity along
/// is returned. /// with its ID is returned.
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) { pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) {
self.create_with_uuid(kind, Uuid::from_bytes(rand::random())) self.create_with_uuid(kind, Uuid::from_bytes(rand::random()))
.expect("UUID collision") .expect("UUID collision")
} }
/// Like [`create`](Entities::create), but requires specifying the new /// Like [`Self::create`], but requires specifying the new
/// entity's UUID. /// entity's UUID.
/// ///
/// The provided UUID must not conflict with an existing entity UUID in this /// The provided UUID must not conflict with an existing entity UUID. If it
/// world. If it does, `None` is returned and the entity is not spawned. /// does, `None` is returned and the entity is not spawned.
pub fn create_with_uuid( pub fn create_with_uuid(
&mut self, &mut self,
kind: EntityKind, kind: EntityKind,
@ -58,7 +68,7 @@ impl Entities {
let (k, e) = self.sm.insert(Entity { let (k, e) = self.sm.insert(Entity {
flags: EntityFlags(0), flags: EntityFlags(0),
data: EntityData::new(kind), data: EntityData::new(kind),
world: None, world: WorldId::NULL,
new_position: Vec3::default(), new_position: Vec3::default(),
old_position: Vec3::default(), old_position: Vec3::default(),
yaw: 0.0, yaw: 0.0,
@ -78,6 +88,10 @@ impl Entities {
} }
} }
/// Removes an entity from the server.
///
/// If the given entity ID is valid, `true` is returned and the entity is
/// deleted. Otherwise, `false` is returned and the function has no effect.
pub fn delete(&mut self, entity: EntityId) -> bool { pub fn delete(&mut self, entity: EntityId) -> bool {
if let Some(e) = self.sm.remove(entity.0) { if let Some(e) = self.sm.remove(entity.0) {
self.uuid_to_entity self.uuid_to_entity
@ -94,6 +108,9 @@ impl Entities {
} }
} }
/// Removes all entities from the server for which `f` returns `true`.
///
/// All entities are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
self.sm.retain(|k, v| { self.sm.retain(|k, v| {
if f(EntityId(k), v) { if f(EntityId(k), v) {
@ -112,7 +129,7 @@ impl Entities {
}); });
} }
/// Returns the number of live entities. /// Returns the number of entities in this container.
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.sm.len() self.sm.len()
} }
@ -120,16 +137,21 @@ impl Entities {
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient /// Gets the [`EntityId`] of the entity with the given UUID in an efficient
/// manner. /// manner.
/// ///
/// Returns `None` if there is no entity with the provided UUID. Returns /// If there is no entity with the UUID, `None` is returned.
/// `Some` otherwise.
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> { pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
self.uuid_to_entity.get(&uuid).cloned() self.uuid_to_entity.get(&uuid).cloned()
} }
/// Gets a shared reference to the entity with the given [`EntityId`].
///
/// If the ID is invalid, `None` is returned.
pub fn get(&self, entity: EntityId) -> Option<&Entity> { pub fn get(&self, entity: EntityId) -> Option<&Entity> {
self.sm.get(entity.0) self.sm.get(entity.0)
} }
/// Gets an exclusive reference to the entity with the given [`EntityId`].
///
/// If the ID is invalid, `None` is returned.
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> { pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
self.sm.get_mut(entity.0) self.sm.get_mut(entity.0)
} }
@ -140,18 +162,26 @@ impl Entities {
Some(EntityId(Key::new(index, version))) Some(EntityId(Key::new(index, version)))
} }
/// Returns an immutable iterator over all entities on the server in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v)) self.sm.iter().map(|(k, v)| (EntityId(k), v))
} }
/// Returns a mutable iterator over all entities on the server in an
/// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v)) self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
} }
/// Returns a parallel immutable iterator over all entities on the server in
/// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (EntityId(k), v)) self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
} }
/// Returns a parallel mutable iterator over all clients on the server in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v)) self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
} }
@ -168,10 +198,20 @@ impl Entities {
} }
} }
/// A key for an [`Entity`] on the server.
///
/// Entity IDs are either _valid_ or _invalid_. Valid entity IDs point to
/// entities that have not been deleted, while invalid IDs point to those that
/// have. Once an ID becomes invalid, it will never become valid again.
///
/// The [`Ord`] instance on this type is correct but otherwise unspecified. This
/// is useful for storing IDs in containers such as
/// [`BTreeMap`](std::collections::BTreeMap).
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct EntityId(Key); pub struct EntityId(Key);
impl EntityId { impl EntityId {
/// The value of the default entity ID which is always invalid.
pub const NULL: Self = Self(Key::NULL); pub const NULL: Self = Self(Key::NULL);
pub(crate) fn to_network_id(self) -> i32 { pub(crate) fn to_network_id(self) -> i32 {
@ -179,10 +219,19 @@ impl EntityId {
} }
} }
/// Represents an entity on the server.
///
/// In essence, an entity is anything in a world that isn't a block or client.
/// Entities include paintings, falling blocks, zombies, fireballs, and more.
///
/// Every entity has common state which is accessible directly from
/// this struct. This includes position, rotation, velocity, UUID, and hitbox.
/// To access data that is not common to every kind of entity, see
/// [`Self::data`].
pub struct Entity { pub struct Entity {
flags: EntityFlags, flags: EntityFlags,
data: EntityData, data: EntityData,
world: Option<WorldId>, world: WorldId,
new_position: Vec3<f64>, new_position: Vec3<f64>,
old_position: Vec3<f64>, old_position: Vec3<f64>,
yaw: f32, yaw: f32,
@ -192,8 +241,6 @@ pub struct Entity {
uuid: Uuid, uuid: Uuid,
} }
/// Contains a bit for certain fields in [`Entity`] to track if they have been
/// modified.
#[bitfield(u8)] #[bitfield(u8)]
pub(crate) struct EntityFlags { pub(crate) struct EntityFlags {
pub yaw_or_pitch_modified: bool, pub yaw_or_pitch_modified: bool,
@ -209,51 +256,64 @@ impl Entity {
self.flags self.flags
} }
/// Returns a reference to this entity's [`EntityData`]. /// Gets a reference to this entity's [`EntityData`].
pub fn data(&self) -> &EntityData { pub fn data(&self) -> &EntityData {
&self.data &self.data
} }
/// Returns a mutable reference to this entity's [`EntityData`]. /// Gets a mutable reference to this entity's
/// [`EntityData`].
pub fn data_mut(&mut self) -> &mut EntityData { pub fn data_mut(&mut self) -> &mut EntityData {
&mut self.data &mut self.data
} }
/// Returns the [`EntityKind`] of this entity. /// Gets the [`EntityKind`] of this entity.
pub fn kind(&self) -> EntityKind { pub fn kind(&self) -> EntityKind {
self.data.kind() self.data.kind()
} }
pub fn world(&self) -> Option<WorldId> { /// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is
/// located in.
///
/// By default, entities are located in
/// [`WorldId::NULL`](crate::world::WorldId::NULL).
pub fn world(&self) -> WorldId {
self.world self.world
} }
pub fn set_world(&mut self, world: impl Into<Option<WorldId>>) { /// Sets the world this entity is located in.
self.world = world.into(); pub fn set_world(&mut self, world: WorldId) {
self.world = world;
} }
/// Returns the position of this entity in the world it inhabits. /// Gets the position of this entity in the world it inhabits.
///
/// The position of an entity is located on the botton of its
/// hitbox and not the center.
pub fn position(&self) -> Vec3<f64> { pub fn position(&self) -> Vec3<f64> {
self.new_position self.new_position
} }
/// Sets the position of this entity in the world it inhabits. /// Sets the position of this entity in the world it inhabits.
///
/// The position of an entity is located on the botton of its
/// hitbox and not the center.
pub fn set_position(&mut self, pos: impl Into<Vec3<f64>>) { pub fn set_position(&mut self, pos: impl Into<Vec3<f64>>) {
self.new_position = pos.into(); self.new_position = pos.into();
} }
/// Returns the position of this entity as it existed at the end of the /// Returns the position of this entity as it existed at the end of the
/// previous tick. /// previous tick.
pub fn old_position(&self) -> Vec3<f64> { pub(crate) fn old_position(&self) -> Vec3<f64> {
self.old_position self.old_position
} }
/// Gets the yaw of this entity (in degrees). /// Gets the yaw of this entity in degrees.
pub fn yaw(&self) -> f32 { pub fn yaw(&self) -> f32 {
self.yaw self.yaw
} }
/// Sets the yaw of this entity (in degrees). /// Sets the yaw of this entity in degrees.
pub fn set_yaw(&mut self, yaw: f32) { pub fn set_yaw(&mut self, yaw: f32) {
if self.yaw != yaw { if self.yaw != yaw {
self.yaw = yaw; self.yaw = yaw;
@ -261,12 +321,12 @@ impl Entity {
} }
} }
/// Gets the pitch of this entity (in degrees). /// Gets the pitch of this entity in degrees.
pub fn pitch(&self) -> f32 { pub fn pitch(&self) -> f32 {
self.pitch self.pitch
} }
/// Sets the pitch of this entity (in degrees). /// Sets the pitch of this entity in degrees.
pub fn set_pitch(&mut self, pitch: f32) { pub fn set_pitch(&mut self, pitch: f32) {
if self.pitch != pitch { if self.pitch != pitch {
self.pitch = pitch; self.pitch = pitch;
@ -274,12 +334,12 @@ impl Entity {
} }
} }
/// Gets the head yaw of this entity (in degrees). /// Gets the head yaw of this entity in degrees.
pub fn head_yaw(&self) -> f32 { pub fn head_yaw(&self) -> f32 {
self.head_yaw self.head_yaw
} }
/// Sets the head yaw of this entity (in degrees). /// Sets the head yaw of this entity in degrees.
pub fn set_head_yaw(&mut self, head_yaw: f32) { pub fn set_head_yaw(&mut self, head_yaw: f32) {
if self.head_yaw != head_yaw { if self.head_yaw != head_yaw {
self.head_yaw = head_yaw; self.head_yaw = head_yaw;
@ -292,6 +352,7 @@ impl Entity {
self.velocity self.velocity
} }
/// Sets the velocity of this entity in meters per second.
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) { pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
let new_vel = velocity.into(); let new_vel = velocity.into();
@ -301,18 +362,30 @@ impl Entity {
} }
} }
/// Gets the value of the "on ground" flag.
pub fn on_ground(&self) -> bool { pub fn on_ground(&self) -> bool {
self.flags.on_ground() self.flags.on_ground()
} }
/// Sets the value of the "on ground" flag.
pub fn set_on_ground(&mut self, on_ground: bool) { pub fn set_on_ground(&mut self, on_ground: bool) {
self.flags.set_on_ground(on_ground); self.flags.set_on_ground(on_ground);
} }
/// Gets the UUID of this entity.
pub fn uuid(&self) -> Uuid { pub fn uuid(&self) -> Uuid {
self.uuid self.uuid
} }
/// Returns the hitbox of this entity.
///
/// The hitbox describes the space that an entity occupies. Clients interact
/// with this space to create an [interact event].
///
/// The hitbox of an entity is determined by its position, entity type, and
/// other state specific to that type.
///
/// [interact event]: crate::client::Event::InteractWithEntity
pub fn hitbox(&self) -> Aabb<f64> { pub fn hitbox(&self) -> Aabb<f64> {
let dims = match &self.data { let dims = match &self.data {
EntityData::Allay(_) => [0.6, 0.35, 0.6], EntityData::Allay(_) => [0.6, 0.35, 0.6],

View file

@ -1,10 +1,216 @@
#![allow(clippy::all, missing_docs)] //! Types used in [`EntityData`](crate::entity::EntityData).
use crate::block::{BlockPos, BlockState}; use std::io::Write;
use crate::entity::meta::*;
use crate::entity::EntityId;
use crate::protocol::{Encode, VarInt};
use crate::text::Text;
use crate::uuid::Uuid;
include!(concat!(env!("OUT_DIR"), "/entity.rs")); use crate::protocol_inner::{Encode, VarInt};
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
pub struct ArmorStandRotations {
/// Rotation on the X axis in degrees.
pub x: f32,
/// Rotation on the Y axis in degrees.
pub y: f32,
/// Rotation on the Z axis in degrees.
pub z: f32,
}
impl ArmorStandRotations {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
}
impl Encode for ArmorStandRotations {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.x.encode(w)?;
self.y.encode(w)?;
self.z.encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Direction {
Down,
Up,
North,
South,
West,
East,
}
impl Encode for Direction {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VillagerData {
pub kind: VillagerKind,
pub profession: VillagerProfession,
pub level: i32,
}
impl VillagerData {
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
Self {
kind,
profession,
level,
}
}
}
impl Default for VillagerData {
fn default() -> Self {
Self {
kind: Default::default(),
profession: Default::default(),
level: 1,
}
}
}
impl Encode for VillagerData {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.kind as i32).encode(w)?;
VarInt(self.profession as i32).encode(w)?;
VarInt(self.level).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerKind {
Desert,
Jungle,
#[default]
Plains,
Savanna,
Snow,
Swamp,
Taiga,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerProfession {
#[default]
None,
Armorer,
Butcher,
Cartographer,
Cleric,
Farmer,
Fisherman,
Fletcher,
Leatherworker,
Librarian,
Mason,
Nitwit,
Shepherd,
Toolsmith,
Weaponsmith,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum Pose {
#[default]
Standing,
FallFlying,
Sleeping,
Swimming,
SpinAttack,
Sneaking,
LongJumping,
Dying,
Croaking,
UsingTongue,
Roaring,
Sniffing,
Emerging,
Digging,
}
impl Encode for Pose {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
/// The main hand of a player.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum MainHand {
Left,
#[default]
Right,
}
impl Encode for MainHand {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
(*self as u8).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum BoatKind {
#[default]
Oak,
Spruce,
Birch,
Jungle,
Acacia,
DarkOak,
}
impl Encode for BoatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum CatKind {
Tabby,
#[default]
Black,
Red,
Siamese,
BritishShorthair,
Calico,
Persian,
Ragdoll,
White,
Jellie,
AllBlack,
}
impl Encode for CatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum FrogKind {
#[default]
Temperate,
Warm,
Cold,
}
impl Encode for FrogKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum PaintingKind {
#[default]
Default, // TODO
}
impl Encode for PaintingKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}

View file

@ -1,216 +0,0 @@
#![allow(missing_docs)]
use std::io::Write;
use crate::protocol::{Encode, VarInt};
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
pub struct ArmorStandRotations {
/// Rotation on the X axis in degrees.
pub x: f32,
/// Rotation on the Y axis in degrees.
pub y: f32,
/// Rotation on the Z axis in degrees.
pub z: f32,
}
impl ArmorStandRotations {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
}
impl Encode for ArmorStandRotations {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.x.encode(w)?;
self.y.encode(w)?;
self.z.encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Direction {
Down,
Up,
North,
South,
West,
East,
}
impl Encode for Direction {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VillagerData {
pub kind: VillagerKind,
pub profession: VillagerProfession,
pub level: i32,
}
impl VillagerData {
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
Self {
kind,
profession,
level,
}
}
}
impl Default for VillagerData {
fn default() -> Self {
Self {
kind: Default::default(),
profession: Default::default(),
level: 1,
}
}
}
impl Encode for VillagerData {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.kind as i32).encode(w)?;
VarInt(self.profession as i32).encode(w)?;
VarInt(self.level).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerKind {
Desert,
Jungle,
#[default]
Plains,
Savanna,
Snow,
Swamp,
Taiga,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerProfession {
#[default]
None,
Armorer,
Butcher,
Cartographer,
Cleric,
Farmer,
Fisherman,
Fletcher,
Leatherworker,
Librarian,
Mason,
Nitwit,
Shepherd,
Toolsmith,
Weaponsmith,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum Pose {
#[default]
Standing,
FallFlying,
Sleeping,
Swimming,
SpinAttack,
Sneaking,
LongJumping,
Dying,
Croaking,
UsingTongue,
Roaring,
Sniffing,
Emerging,
Digging,
}
impl Encode for Pose {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
/// The main hand of a player.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum MainHand {
Left,
#[default]
Right,
}
impl Encode for MainHand {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
(*self as u8).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum BoatKind {
#[default]
Oak,
Spruce,
Birch,
Jungle,
Acacia,
DarkOak,
}
impl Encode for BoatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum CatKind {
Tabby,
#[default]
Black,
Red,
Siamese,
BritishShorthair,
Calico,
Persian,
Ragdoll,
White,
Jellie,
AllBlack,
}
impl Encode for CatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum FrogKind {
#[default]
Temperate,
Warm,
Cold,
}
impl Encode for FrogKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum PaintingKind {
#[default]
Default, // TODO
}
impl Encode for PaintingKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}

12
src/entity/types.rs Normal file
View file

@ -0,0 +1,12 @@
//! Contains a struct for each variant in [`EntityKind`].
#![allow(clippy::all, missing_docs)]
use crate::block::{BlockPos, BlockState};
use crate::entity::data::*;
use crate::entity::EntityId;
use crate::protocol_inner::{Encode, VarInt};
use crate::text::Text;
use crate::uuid::Uuid;
include!(concat!(env!("OUT_DIR"), "/entity.rs"));

View file

@ -7,15 +7,15 @@ use serde::de::Visitor;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::protocol::{encode_string_bounded, BoundedString, Decode, Encode}; use crate::protocol_inner::{encode_string_bounded, BoundedString, Decode, Encode};
/// An identifier is a string split into a "namespace" part and a "name" part. /// An identifier is a string split into a "namespace" part and a "name" part.
/// For instance `minecraft:apple` and `apple` are both valid identifiers. /// For instance `minecraft:apple` and `apple` are both valid identifiers.
/// ///
/// If the namespace part is left off (the part before and including the colon) /// If the namespace part is left off (the part before and including the colon)
/// the namespace is considered to be "minecraft". /// the namespace is considered to be "minecraft" for the purposes of equality.
/// ///
/// The entire identifier must match the regex `([a-z0-9_-]+:)?[a-z0-9_\/.-]+`. /// The identifier must match the regex `^([a-z0-9_-]+:)?[a-z0-9_\/.-]+$`.
#[derive(Clone, Eq)] #[derive(Clone, Eq)]
pub struct Ident { pub struct Ident {
ident: Cow<'static, AsciiStr>, ident: Cow<'static, AsciiStr>,
@ -239,10 +239,26 @@ impl<'de> Visitor<'de> for IdentifierVisitor {
} }
} }
/// Convenience macro for constructing an identifier from a format string. /// Convenience macro for constructing an [`Ident`] from a format string.
/// ///
/// The macro will panic if the formatted string is not a valid /// The arguments to this macro are forwarded to [`std::format_args`].
///
/// # Panics
///
/// The macro will cause a panic if the formatted string is not a valid
/// identifier. /// identifier.
///
/// # Examples
///
/// ```
/// use valence::ident;
///
/// let namespace = "my_namespace";
/// let apple = ident!("{namespace}:apple");
///
/// assert_eq!(apple.namespace(), Some("my_namespace"));
/// assert_eq!(apple.name(), "apple");
/// ```
#[macro_export] #[macro_export]
macro_rules! ident { macro_rules! ident {
($($arg:tt)*) => {{ ($($arg:tt)*) => {{

View file

@ -1,10 +1,111 @@
//! A Rust framework for building efficient Minecraft servers.
//!
//! Valence is a Rust library which provides the necessary abstractions over
//! Minecraft's protocol to build servers. Very few assumptions about the
//! desired server are made, which allows for greater flexibility in its design.
//!
//! At a high level, a Valence [`Server`] is a collection of [`Clients`],
//! [`Entities`], and [`Worlds`]. When a client connects to the server, they are
//! added to the server's [`Clients`]. After connecting, clients are assigned to
//! a [`World`] where they are able to interact with the entities and
//! [`Chunks`] that are a part of it.
//!
//! The Valence documentation assumes some familiarity with Minecraft and its
//! mechanics. See the [Minecraft Wiki] for general information and [wiki.vg]
//! for protocol documentation.
//!
//! [Minecraft Wiki]: https://minecraft.fandom.com/wiki/Minecraft_Wiki
//! [wiki.vg]: https://wiki.vg/Main_Page
//!
//! # Logging
//!
//! Valence uses the [log] crate to report errors and other information. You may
//! want to use a logging implementation such as [env_logger] to see these
//! messages.
//!
//! [log]: https://docs.rs/log/latest/log/
//! [env_logger]: https://docs.rs/env_logger/latest/env_logger/
//!
//! # An Important Note on [`mem::swap`]
//!
//! In Valence, many types are owned by the library but given out as mutable
//! references for the user to modify. Examples of such types include [`World`],
//! [`Chunk`], [`Entity`], and [`Client`].
//!
//! **You must not call [`mem::swap`] on these references (or any other
//! function that would move their location in memory).** Doing so breaks
//! invariants within the library and the resulting behavior is unspecified.
//! These types should be considered pinned in memory.
//!
//! Preventing this illegal behavior using Rust's type system was considered too
//! cumbersome, so a note has been left here instead.
//!
//! [`mem::swap`]: std::mem::swap
//!
//! # Examples
//!
//! The following is a minimal server implementation. You should be able to
//! connect to the server at `localhost`.
//!
//! ```
//! use valence::config::Config;
//! use valence::server::{Server, ShutdownResult};
//!
//! pub fn main() -> ShutdownResult {
//! valence::start_server(Game)
//! }
//!
//! struct Game;
//!
//! impl Config for Game {
//! fn max_connections(&self) -> usize {
//! 256
//! }
//!
//! fn update(&self, server: &mut Server) {
//! server.clients.retain(|_, client| {
//! if client.created_tick() == server.shared.current_tick() {
//! println!("{} joined!", client.username());
//! }
//!
//! if client.is_disconnected() {
//! println!("{} left!", client.username());
//! false
//! } else {
//! true
//! }
//! });
//! # server.shared.shutdown::<_, std::convert::Infallible>(Ok(()));
//! }
//! }
//! ```
//!
//! For more complete examples, see the [examples] in the source repository.
//!
//! [examples]: https://github.com/rj00a/valence/tree/main/examples
//!
//! # Feature Flags
//!
//! * `protocol`: Enables low-level access to the [`protocol`] module, which
//! could be used to build your own proxy or client. This feature is
//! considered experimental and is subject to change.
//!
//! [`Server`]: crate::server::Server
//! [`Clients`]: crate::client::Clients
//! [`Entities`]: crate::entity::Entities
//! [`Worlds`]: crate::world::Worlds
//! [`World`]: crate::world::World
//! [`Chunks`]: crate::chunk::Chunks
//! [`Chunk`]: crate::chunk::Chunk
//! [`Entity`]: crate::entity::Entity
//! [`Client`]: crate::client::Client
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn( #![warn(
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,
unused_lifetimes, unused_lifetimes,
unused_import_braces, unused_import_braces
// missing_docs
)] )]
pub mod biome; pub mod biome;
@ -18,31 +119,36 @@ pub mod config;
pub mod dimension; pub mod dimension;
pub mod entity; pub mod entity;
pub mod ident; pub mod ident;
mod player_list; pub mod player_list;
pub mod player_textures; pub mod player_textures;
#[cfg(not(feature = "protocol"))] #[allow(dead_code)]
#[allow(unused)] mod protocol_inner;
mod protocol;
#[cfg(feature = "protocol")]
pub mod protocol;
pub mod server; pub mod server;
mod slotmap; mod slotmap;
mod spatial_index; pub mod spatial_index;
pub mod text; pub mod text;
pub mod util; pub mod util;
pub mod world; pub mod world;
#[cfg(feature = "protocol")]
pub mod protocol {
pub use crate::protocol_inner::*;
}
pub use async_trait::async_trait; pub use async_trait::async_trait;
pub use server::start_server; pub use server::start_server;
pub use spatial_index::SpatialIndex;
pub use {nbt, uuid, vek}; pub use {nbt, uuid, vek};
/// The Minecraft protocol version that this library targets. /// The Minecraft protocol version this library currently targets.
pub const PROTOCOL_VERSION: i32 = 759; pub const PROTOCOL_VERSION: i32 = 759;
/// The name of the Minecraft version that this library targets. /// The name of the Minecraft version this library currently targets, e.g.
/// "1.8.2"
pub const VERSION_NAME: &str = "1.19"; pub const VERSION_NAME: &str = "1.19";
/// The namespace for this library used internally for namespaced identifiers. /// The namespace for this library used internally for
/// [identifiers](crate::ident::Ident).
///
/// You should avoid using this namespace in your own identifiers.
const LIBRARY_NAMESPACE: &str = "valence"; const LIBRARY_NAMESPACE: &str = "valence";
/// A discrete unit of time where 1 tick is the duration of a /// A discrete unit of time where 1 tick is the duration of a
@ -51,3 +157,7 @@ const LIBRARY_NAMESPACE: &str = "valence";
/// The duration of a game update depends on the current configuration, which /// The duration of a game update depends on the current configuration, which
/// may or may not be the same as Minecraft's standard 20 ticks/second. /// may or may not be the same as Minecraft's standard 20 ticks/second.
pub type Ticks = i64; pub type Ticks = i64;
/// Whatever dude
#[cfg(feature = "whatever")]
pub type Whatever = i64;

View file

@ -6,13 +6,20 @@ use uuid::Uuid;
use crate::client::GameMode; use crate::client::GameMode;
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol::packets::play::s2c::{ use crate::protocol_inner::packets::play::s2c::{
PlayerInfo, PlayerInfoAddPlayer, S2cPlayPacket, TabList, PlayerInfo, PlayerInfoAddPlayer, S2cPlayPacket, TabList,
}; };
use crate::protocol::packets::Property; use crate::protocol_inner::packets::Property;
use crate::protocol::VarInt; use crate::protocol_inner::VarInt;
use crate::text::Text; use crate::text::Text;
/// The list of players on a server visible by pressing the tab key by default.
///
/// Each entry in the player list is intended to represent a connected client to
/// the server.
///
/// In addition to a list of players, the player list has a header and a footer
/// which can contain arbitrary text.
pub struct PlayerList { pub struct PlayerList {
entries: HashMap<Uuid, PlayerListEntry>, entries: HashMap<Uuid, PlayerListEntry>,
removed: HashSet<Uuid>, removed: HashSet<Uuid>,
@ -32,6 +39,10 @@ impl PlayerList {
} }
} }
/// Inserts a player into the player list.
///
/// If the given UUID conflicts with an existing entry, the entry is
/// overwritten and `false` is returned. Otherwise, `true` is returned.
pub fn insert( pub fn insert(
&mut self, &mut self,
uuid: Uuid, uuid: Uuid,
@ -40,7 +51,7 @@ impl PlayerList {
game_mode: GameMode, game_mode: GameMode,
ping: i32, ping: i32,
display_name: impl Into<Option<Text>>, display_name: impl Into<Option<Text>>,
) { ) -> bool {
match self.entries.entry(uuid) { match self.entries.entry(uuid) {
Entry::Occupied(mut oe) => { Entry::Occupied(mut oe) => {
let e = oe.get_mut(); let e = oe.get_mut();
@ -62,6 +73,7 @@ impl PlayerList {
e.set_ping(ping); e.set_ping(ping);
e.set_display_name(display_name); e.set_display_name(display_name);
} }
false
} }
Entry::Vacant(ve) => { Entry::Vacant(ve) => {
ve.insert(PlayerListEntry { ve.insert(PlayerListEntry {
@ -72,10 +84,13 @@ impl PlayerList {
display_name: display_name.into(), display_name: display_name.into(),
flags: EntryFlags::new().with_created_this_tick(true), flags: EntryFlags::new().with_created_this_tick(true),
}); });
true
} }
} }
} }
/// Removes an entry from the player list with the given UUID. Returns
/// whether the entry was present in the list.
pub fn remove(&mut self, uuid: Uuid) -> bool { pub fn remove(&mut self, uuid: Uuid) -> bool {
if self.entries.remove(&uuid).is_some() { if self.entries.remove(&uuid).is_some() {
self.removed.insert(uuid); self.removed.insert(uuid);
@ -85,10 +100,31 @@ impl PlayerList {
} }
} }
/// Removes all entries from the player list for which `f` returns `true`.
///
/// All entries are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(Uuid, &mut PlayerListEntry) -> bool) {
self.entries.retain(|&uuid, entry| {
if !f(uuid, entry) {
self.removed.insert(uuid);
false
} else {
true
}
})
}
/// Removes all entries from the player list.
pub fn clear(&mut self) {
self.removed.extend(self.entries.drain().map(|p| p.0))
}
/// Gets the header part of the player list.
pub fn header(&self) -> &Text { pub fn header(&self) -> &Text {
&self.header &self.header
} }
/// Sets the header part of the player list.
pub fn set_header(&mut self, header: impl Into<Text>) { pub fn set_header(&mut self, header: impl Into<Text>) {
let header = header.into(); let header = header.into();
if self.header != header { if self.header != header {
@ -97,10 +133,12 @@ impl PlayerList {
} }
} }
/// Gets the footer part of the player list.
pub fn footer(&self) -> &Text { pub fn footer(&self) -> &Text {
&self.footer &self.footer
} }
/// Sets the footer part of the player list.
pub fn set_footer(&mut self, footer: impl Into<Text>) { pub fn set_footer(&mut self, footer: impl Into<Text>) {
let footer = footer.into(); let footer = footer.into();
if self.footer != footer { if self.footer != footer {
@ -109,10 +147,13 @@ impl PlayerList {
} }
} }
/// Returns an iterator over all entries in an unspecified order.
pub fn entries(&self) -> impl Iterator<Item = (Uuid, &PlayerListEntry)> + '_ { pub fn entries(&self) -> impl Iterator<Item = (Uuid, &PlayerListEntry)> + '_ {
self.entries.iter().map(|(k, v)| (*k, v)) self.entries.iter().map(|(k, v)| (*k, v))
} }
/// Returns an iterator which allows modifications over all entries. The
/// entries are visited in an unspecified order.
pub fn entries_mut(&mut self) -> impl Iterator<Item = (Uuid, &mut PlayerListEntry)> + '_ { pub fn entries_mut(&mut self) -> impl Iterator<Item = (Uuid, &mut PlayerListEntry)> + '_ {
self.entries.iter_mut().map(|(k, v)| (*k, v)) self.entries.iter_mut().map(|(k, v)| (*k, v))
} }
@ -240,6 +281,7 @@ impl PlayerList {
} }
} }
/// Represents a player entry in the [`PlayerList`].
pub struct PlayerListEntry { pub struct PlayerListEntry {
username: String, username: String,
textures: Option<SignedPlayerTextures>, textures: Option<SignedPlayerTextures>,
@ -250,6 +292,7 @@ pub struct PlayerListEntry {
} }
impl PlayerListEntry { impl PlayerListEntry {
/// Gets the username of this entry.
pub fn username(&self) -> &str { pub fn username(&self) -> &str {
&self.username &self.username
} }
@ -258,10 +301,12 @@ impl PlayerListEntry {
self.textures.as_ref() self.textures.as_ref()
} }
/// Gets the game mode of this entry.
pub fn game_mode(&self) -> GameMode { pub fn game_mode(&self) -> GameMode {
self.game_mode self.game_mode
} }
/// Sets the game mode of this entry.
pub fn set_game_mode(&mut self, game_mode: GameMode) { pub fn set_game_mode(&mut self, game_mode: GameMode) {
if self.game_mode != game_mode { if self.game_mode != game_mode {
self.game_mode = game_mode; self.game_mode = game_mode;
@ -269,10 +314,12 @@ impl PlayerListEntry {
} }
} }
/// Gets the ping (latency) of this entry measured in milliseconds.
pub fn ping(&self) -> i32 { pub fn ping(&self) -> i32 {
self.ping self.ping
} }
/// Sets the ping (latency) of this entry measured in milliseconds.
pub fn set_ping(&mut self, ping: i32) { pub fn set_ping(&mut self, ping: i32) {
if self.ping != ping { if self.ping != ping {
self.ping = ping; self.ping = ping;
@ -280,10 +327,12 @@ impl PlayerListEntry {
} }
} }
/// Gets the display name of this entry.
pub fn display_name(&self) -> Option<&Text> { pub fn display_name(&self) -> Option<&Text> {
self.display_name.as_ref() self.display_name.as_ref()
} }
/// Sets the display name of this entry.
pub fn set_display_name(&mut self, display_name: impl Into<Option<Text>>) { pub fn set_display_name(&mut self, display_name: impl Into<Option<Text>>) {
let display_name = display_name.into(); let display_name = display_name.into();
if self.display_name != display_name { if self.display_name != display_name {

View file

@ -1,7 +1,13 @@
//! Player skins and capes.
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
/// Contains URLs to the skin and cape of a player.
///
/// This data has been cryptographically signed to ensure it will not be altered
/// by the server.
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct SignedPlayerTextures { pub struct SignedPlayerTextures {
payload: Box<[u8]>, payload: Box<[u8]>,
@ -9,14 +15,15 @@ pub struct SignedPlayerTextures {
} }
impl SignedPlayerTextures { impl SignedPlayerTextures {
pub fn payload(&self) -> &[u8] { pub(crate) fn payload(&self) -> &[u8] {
&self.payload &self.payload
} }
pub fn signature(&self) -> &[u8] { pub(crate) fn signature(&self) -> &[u8] {
&self.signature &self.signature
} }
/// Gets the unsigned texture URLs.
pub fn to_textures(&self) -> PlayerTextures { pub fn to_textures(&self) -> PlayerTextures {
self.to_textures_fallible() self.to_textures_fallible()
.expect("payload should have been validated earlier") .expect("payload should have been validated earlier")
@ -49,9 +56,14 @@ impl SignedPlayerTextures {
} }
} }
/// Contains URLs to the skin and cape of a player.
#[derive(Clone, PartialEq, Default, Debug)] #[derive(Clone, PartialEq, Default, Debug)]
pub struct PlayerTextures { pub struct PlayerTextures {
/// A URL to the skin of a player. Is `None` if the player does not have a
/// skin.
pub skin: Option<Url>, pub skin: Option<Url>,
/// A URL to the cape of a player. Is `None` if the player does not have a
/// cape.
pub cape: Option<Url>, pub cape: Option<Url>,
} }
@ -63,7 +75,7 @@ impl From<SignedPlayerTextures> for PlayerTextures {
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")] #[serde(rename_all = "UPPERCASE")]
pub struct PlayerTexturesPayload { struct PlayerTexturesPayload {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
skin: Option<TextureUrl>, skin: Option<TextureUrl>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]

View file

@ -21,12 +21,12 @@ use vek::{Vec2, Vec3, Vec4};
use crate::entity::EntityId; use crate::entity::EntityId;
/// Trait for types that can be written to the Minecraft protocol. /// Types that can be written to the Minecraft protocol.
pub trait Encode { pub trait Encode {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()>; fn encode(&self, w: &mut impl Write) -> anyhow::Result<()>;
} }
/// Trait for types that can be read from the Minecraft protocol. /// Types that can be read from the Minecraft protocol.
pub trait Decode: Sized { pub trait Decode: Sized {
fn decode(r: &mut impl Read) -> anyhow::Result<Self>; fn decode(r: &mut impl Read) -> anyhow::Result<Self>;
} }

View file

@ -1,6 +1,6 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use crate::protocol::{Decode, Encode}; use crate::protocol_inner::{Decode, Encode};
/// Represents an angle in steps of 1/256 of a full turn. /// Represents an angle in steps of 1/256 of a full turn.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
@ -11,9 +11,9 @@ impl ByteAngle {
ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8) ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8)
} }
// pub fn to_degrees(self) -> f32 { pub fn to_degrees(self) -> f32 {
// self.0 as f32 / 256.0 * 360.0 self.0 as f32 / 256.0 * 360.0
// } }
} }
impl Encode for ByteAngle { impl Encode for ByteAngle {
@ -24,6 +24,6 @@ impl Encode for ByteAngle {
impl Decode for ByteAngle { impl Decode for ByteAngle {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> { fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(ByteAngle(u8::decode(r)?)) u8::decode(r).map(ByteAngle)
} }
} }

View file

@ -12,7 +12,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::time::timeout; use tokio::time::timeout;
use super::packets::{DecodePacket, EncodePacket}; use super::packets::{DecodePacket, EncodePacket};
use crate::protocol::{Decode, Encode, VarInt, MAX_PACKET_SIZE}; use crate::protocol_inner::{Decode, Encode, VarInt, MAX_PACKET_SIZE};
pub struct Encoder<W> { pub struct Encoder<W> {
write: W, write: W,
@ -249,7 +249,7 @@ mod tests {
use tokio::sync::oneshot; use tokio::sync::oneshot;
use super::*; use super::*;
use crate::protocol::packets::test::TestPacket; use crate::protocol_inner::packets::test::TestPacket;
#[tokio::test] #[tokio::test]
async fn encode_decode() { async fn encode_decode() {

View file

@ -1,8 +1,6 @@
//! Contains packet definitions and some types contained within them. //! Packet definitions and some types contained within them.
//! //!
//! See <https://wiki.vg/Protocol> for up to date protocol information. //! See <https://wiki.vg/Protocol> for more packet documentation.
#![allow(dead_code)]
use std::fmt; use std::fmt;
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -17,7 +15,7 @@ use vek::Vec3;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::ident::Ident; use crate::ident::Ident;
use crate::protocol::{ use crate::protocol_inner::{
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, Nbt, RawBytes, VarInt, BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, Nbt, RawBytes, VarInt,
VarLong, VarLong,
}; };
@ -319,13 +317,13 @@ macro_rules! def_bitfield {
} }
} }
impl $crate::protocol::Encode for $name { impl Encode for $name {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.0.encode(w) self.0.encode(w)
} }
} }
impl $crate::protocol::Decode for $name { impl Decode for $name {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> { fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
<$inner_ty>::decode(r).map(Self) <$inner_ty>::decode(r).map(Self)
} }
@ -457,7 +455,7 @@ pub mod status {
def_struct! { def_struct! {
PongResponse 0x01 { PongResponse 0x01 {
/// Should be the same as the payload from [`Ping`]. /// Should be the same as the payload from ping.
payload: u64 payload: u64
} }
} }

View file

@ -1,12 +1,11 @@
// TODO: use derive_more?
use std::io::{Read, Write}; use std::io::{Read, Write};
use anyhow::bail; use anyhow::bail;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::protocol::{Decode, Encode}; use crate::protocol_inner::{Decode, Encode};
/// An `i32` encoded with variable length.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VarInt(pub i32); pub struct VarInt(pub i32);

View file

@ -3,8 +3,9 @@ use std::io::{Read, Write};
use anyhow::bail; use anyhow::bail;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::protocol::{Decode, Encode}; use crate::protocol_inner::{Decode, Encode};
/// An `i64` encoded with variable length.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VarLong(pub(crate) i64); pub struct VarLong(pub(crate) i64);

View file

@ -31,30 +31,42 @@ use crate::config::{Config, ServerListPing};
use crate::dimension::{Dimension, DimensionId}; use crate::dimension::{Dimension, DimensionId};
use crate::entity::Entities; use crate::entity::Entities;
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol::codec::{Decoder, Encoder}; use crate::protocol_inner::codec::{Decoder, Encoder};
use crate::protocol::packets::handshake::{Handshake, HandshakeNextState}; use crate::protocol_inner::packets::handshake::{Handshake, HandshakeNextState};
use crate::protocol::packets::login::c2s::{EncryptionResponse, LoginStart, VerifyTokenOrMsgSig}; use crate::protocol_inner::packets::login::c2s::{
use crate::protocol::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression}; EncryptionResponse, LoginStart, VerifyTokenOrMsgSig,
use crate::protocol::packets::play::c2s::C2sPlayPacket; };
use crate::protocol::packets::play::s2c::S2cPlayPacket; use crate::protocol_inner::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression};
use crate::protocol::packets::status::c2s::{PingRequest, StatusRequest}; use crate::protocol_inner::packets::play::c2s::C2sPlayPacket;
use crate::protocol::packets::status::s2c::{PongResponse, StatusResponse}; use crate::protocol_inner::packets::play::s2c::S2cPlayPacket;
use crate::protocol::packets::{login, Property}; use crate::protocol_inner::packets::status::c2s::{PingRequest, StatusRequest};
use crate::protocol::{BoundedArray, BoundedString, VarInt}; use crate::protocol_inner::packets::status::s2c::{PongResponse, StatusResponse};
use crate::protocol_inner::packets::{login, Property};
use crate::protocol_inner::{BoundedArray, BoundedString, VarInt};
use crate::util::valid_username; use crate::util::valid_username;
use crate::world::Worlds; use crate::world::Worlds;
use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME}; use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME};
/// Contains the entire state of a running Minecraft server, accessible from
/// within the [update](crate::config::Config::update) loop.
pub struct Server { pub struct Server {
/// A handle to this server's [`SharedServer`].
pub shared: SharedServer, pub shared: SharedServer,
/// All of the clients in the server.
pub clients: Clients, pub clients: Clients,
/// All of entities in the server.
pub entities: Entities, pub entities: Entities,
/// All of the worlds in the server.
pub worlds: Worlds, pub worlds: Worlds,
} }
/// A handle to a running Minecraft server containing state which is accessible /// A handle to a Minecraft server containing the subset of functionality which
/// outside the update loop. Servers are internally refcounted and can be shared /// is accessible outside the [update][update] loop.
/// between threads. ///
/// `SharedServer`s are internally refcounted and can
/// be shared between threads.
///
/// [update]: crate::config::Config::update
#[derive(Clone)] #[derive(Clone)]
pub struct SharedServer(Arc<SharedServerInner>); pub struct SharedServer(Arc<SharedServerInner>);
@ -105,43 +117,49 @@ struct NewClientMessage {
reply: oneshot::Sender<S2cPacketChannels>, reply: oneshot::Sender<S2cPacketChannels>,
} }
/// The result type returned from [`ServerConfig::start`] after the server is /// The result type returned from [`start_server`].
/// shut down. pub type ShutdownResult = Result<(), Box<dyn Error + Send + Sync + 'static>>;
pub type ShutdownResult = Result<(), ShutdownError>;
pub type ShutdownError = Box<dyn Error + Send + Sync + 'static>;
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>); pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>); pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
impl SharedServer { impl SharedServer {
/// Gets a reference to the config object used to start the server.
pub fn config(&self) -> &(impl Config + ?Sized) { pub fn config(&self) -> &(impl Config + ?Sized) {
self.0.cfg.as_ref() self.0.cfg.as_ref()
} }
/// Gets the socket address this server is bound to.
pub fn address(&self) -> SocketAddr { pub fn address(&self) -> SocketAddr {
self.0.address self.0.address
} }
/// Gets the configured tick rate of this server.
pub fn tick_rate(&self) -> Ticks { pub fn tick_rate(&self) -> Ticks {
self.0.tick_rate self.0.tick_rate
} }
/// Gets whether online mode is enabled on this server.
pub fn online_mode(&self) -> bool { pub fn online_mode(&self) -> bool {
self.0.online_mode self.0.online_mode
} }
/// Gets the maximum number of connections allowed to the server at once.
pub fn max_connections(&self) -> usize { pub fn max_connections(&self) -> usize {
self.0.max_connections self.0.max_connections
} }
/// Gets the configured incoming packet capacity.
pub fn incoming_packet_capacity(&self) -> usize { pub fn incoming_packet_capacity(&self) -> usize {
self.0.incoming_packet_capacity self.0.incoming_packet_capacity
} }
/// Gets the configured outgoing incoming packet capacity.
pub fn outgoing_packet_capacity(&self) -> usize { pub fn outgoing_packet_capacity(&self) -> usize {
self.0.outgoing_packet_capacity self.0.outgoing_packet_capacity
} }
/// Gets a handle to the tokio instance this server is using.
pub fn tokio_handle(&self) -> &Handle { pub fn tokio_handle(&self) -> &Handle {
&self.0.tokio_handle &self.0.tokio_handle
} }
@ -197,7 +215,7 @@ impl SharedServer {
} }
/// Immediately stops new connections to the server and initiates server /// Immediately stops new connections to the server and initiates server
/// shutdown. The given result is returned through [`ServerConfig::start`]. /// shutdown. The given result is returned through [`start_server`].
/// ///
/// You may want to disconnect all players with a message prior to calling /// You may want to disconnect all players with a message prior to calling
/// this function. /// this function.
@ -213,10 +231,10 @@ impl SharedServer {
/// Consumes the configuration and starts the server. /// Consumes the configuration and starts the server.
/// ///
/// The function returns when the server has shut down, a runtime error /// The function returns once the server has shut down, a runtime error
/// occurs, or the configuration is invalid. /// occurs, or the configuration is invalid.
pub fn start_server(config: impl Config) -> ShutdownResult { pub fn start_server(config: impl Config) -> ShutdownResult {
let shared = setup_server(config).map_err(ShutdownError::from)?; let shared = setup_server(config).map_err(Box::<dyn Error + Send + Sync + 'static>::from)?;
let _guard = shared.tokio_handle().enter(); let _guard = shared.tokio_handle().enter();
@ -462,7 +480,7 @@ async fn do_accept_loop(server: SharedServer) {
return; return;
} }
} }
log::debug!("connection to {remote_addr} ended: {e:#}"); log::error!("connection to {remote_addr} ended: {e:#}");
} }
drop(permit); drop(permit);
}); });

View file

@ -1,3 +1,5 @@
//! Efficient spatial entity queries.
use vek::{Aabb, Vec3}; use vek::{Aabb, Vec3};
use crate::bvh::Bvh; use crate::bvh::Bvh;
@ -5,6 +7,14 @@ pub use crate::bvh::TraverseStep;
use crate::entity::{Entities, EntityId}; use crate::entity::{Entities, EntityId};
use crate::world::WorldId; use crate::world::WorldId;
/// A data structure for fast spatial queries on entity [hitboxes]. This is used
/// to accelerate tasks such as collision detection and ray tracing.
///
/// The spatial index is only updated at the end of each tick. Any modification
/// to an entity that would change its hitbox is not reflected in the spatial
/// index until the end of the tick.
///
/// [hitboxes]: crate::entity::Entity::hitbox
pub struct SpatialIndex { pub struct SpatialIndex {
bvh: Bvh<EntityId>, bvh: Bvh<EntityId>,
} }
@ -14,13 +24,46 @@ impl SpatialIndex {
Self { bvh: Bvh::new() } Self { bvh: Bvh::new() }
} }
pub fn traverse<F, T>(&self, mut f: F) -> Option<T> #[doc(hidden)]
where #[deprecated = "This is for documentation tests only"]
F: FnMut(Option<EntityId>, Aabb<f64>) -> TraverseStep<T>, pub fn example_new() -> Self {
{ println!("Don't call me!");
self.bvh.traverse(|e, bb| f(e.cloned(), bb)) Self::new()
} }
/// Invokes `f` with every entity in the spatial index considered
/// colliding according to `collides`.
///
/// `collides` takes an AABB and returns whether or not a collision
/// occurred with the given AABB.
///
/// `f` is called with the entity ID and hitbox of all colliding entities.
/// If `f` returns with `Some(x)`, then `query` exits early with
/// `Some(x)`. If `f` never returns with `Some`, then query returns `None`.
///
/// # Examples
///
/// Visit all entities intersecting a 10x10x10 cube centered at the origin.
///
/// ```
/// # #[allow(deprecated)]
/// # let si = valence::spatial_index::SpatialIndex::example_new();
/// use valence::vek::*;
///
/// let cube = Aabb {
/// min: Vec3::new(-5.0, -5.0, -5.0),
/// max: Vec3::new(5.0, 5.0, 5.0),
/// };
///
/// let collides = |aabb: Aabb<f64>| aabb.collides_with_aabb(cube);
/// let f = |id, _| -> Option<()> {
/// println!("Found entity: {id:?}");
/// None
/// };
///
/// // Assume `si` is the spatial index
/// si.query(collides, f);
/// ```
pub fn query<C, F, T>(&self, mut collides: C, mut f: F) -> Option<T> pub fn query<C, F, T>(&self, mut collides: C, mut f: F) -> Option<T>
where where
C: FnMut(Aabb<f64>) -> bool, C: FnMut(Aabb<f64>) -> bool,
@ -97,11 +140,22 @@ impl SpatialIndex {
) )
} }
/// Explores the spatial index according to `f`.
///
/// This is a low-level function that should only be used if the other
/// methods on this type are too restrictive.
pub fn traverse<F, T>(&self, mut f: F) -> Option<T>
where
F: FnMut(Option<EntityId>, Aabb<f64>) -> TraverseStep<T>,
{
self.bvh.traverse(|e, bb| f(e.cloned(), bb))
}
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) { pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) {
self.bvh.build( self.bvh.build(
entities entities
.iter() .iter()
.filter(|(_, e)| e.world() == Some(id)) .filter(|(_, e)| e.world() == id)
.map(|(id, e)| (id, e.hitbox())), .map(|(id, e)| (id, e.hitbox())),
) )
} }
@ -113,7 +167,7 @@ impl SpatialIndex {
pub struct RaycastHit { pub struct RaycastHit {
/// The [`EntityId`] of the entity that was hit by the ray. /// The [`EntityId`] of the entity that was hit by the ray.
pub entity: EntityId, pub entity: EntityId,
/// The bounding box of the entity that was hit. /// The bounding box (hitbox) of the entity that was hit.
pub bb: Aabb<f64>, pub bb: Aabb<f64>,
/// The distance from the ray origin to the closest intersection point. /// The distance from the ray origin to the closest intersection point.
/// If the origin of the ray is inside the bounding box, then this will be /// If the origin of the ray is inside the bounding box, then this will be

View file

@ -1,6 +1,3 @@
// TODO: Documentation.
// TODO: make fields of Text public?
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -9,19 +6,22 @@ use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::ident::Ident; use crate::ident::Ident;
use crate::protocol::{BoundedString, Decode, Encode}; use crate::protocol_inner::{BoundedString, Decode, Encode};
/// Represents formatted text in Minecraft's JSON text format. /// Represents formatted text in Minecraft's JSON text format.
/// ///
/// Text is used in various places such as chat, window titles, /// Text is used in various places such as chat, window titles,
/// disconnect messages, written books, signs, and more. /// disconnect messages, written books, signs, and more.
/// ///
/// For more information, see the relevant [Minecraft wiki article](https://minecraft.fandom.com/wiki/Raw_JSON_text_format). /// For more information, see the relevant [Minecraft Wiki article].
/// ///
/// Note that the current `Deserialize` implementation on this type recognizes /// Note that the current `Deserialize` implementation on this type recognizes
/// only a subset of the full JSON chat component format. /// only a subset of the full JSON chat component format.
/// ///
/// ## Example /// [Minecraft Wiki article]: https://minecraft.fandom.com/wiki/Raw_JSON_text_format
///
/// # Examples
///
/// With [`TextFormat`] in scope, you can write the following: /// With [`TextFormat`] in scope, you can write the following:
/// ``` /// ```
/// use valence::text::{Color, Text, TextFormat}; /// use valence::text::{Color, Text, TextFormat};
@ -82,6 +82,8 @@ pub struct Text {
} }
impl Text { impl Text {
/// Returns `true` if the text contains no characters. Returns `false`
/// otherwise.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
for extra in &self.extra { for extra in &self.extra {
if !extra.is_empty() { if !extra.is_empty() {
@ -99,7 +101,7 @@ impl Text {
/// Provides the methods necessary for working with [`Text`] objects. /// Provides the methods necessary for working with [`Text`] objects.
/// ///
/// This trait exists to allow using `Into<Text>` types without having to first /// This trait exists to allow using `Into<Text>` types without having to first
/// convert the type into [`Text`]. It is automatically implemented for all /// convert the type into [`Text`]. A blanket implementation exists for all
/// `Into<Text>` types, including [`Text`] itself. /// `Into<Text>` types, including [`Text`] itself.
pub trait TextFormat: Into<Text> { pub trait TextFormat: Into<Text> {
fn into_text(self) -> Text { fn into_text(self) -> Text {
@ -366,8 +368,9 @@ impl Text {
} }
pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result { pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result {
if let TextContent::Text { text } = &self.content { match &self.content {
w.write_str(text.as_ref())?; TextContent::Text { text } => w.write_str(text.as_ref())?,
TextContent::Translate { translate } => w.write_str(translate.as_ref())?,
} }
for child in &self.extra { for child in &self.extra {
@ -376,8 +379,6 @@ impl Text {
Ok(()) Ok(())
} }
// TODO: getters
} }
impl<T: Into<Text>> TextFormat for T {} impl<T: Into<Text>> TextFormat for T {}

View file

@ -1,3 +1,5 @@
//! Miscellaneous utilities.
use std::iter::FusedIterator; use std::iter::FusedIterator;
use num::cast::AsPrimitive; use num::cast::AsPrimitive;
@ -8,6 +10,20 @@ use crate::chunk_pos::ChunkPos;
/// Returns true if the given string meets the criteria for a valid Minecraft /// Returns true if the given string meets the criteria for a valid Minecraft
/// username. /// username.
///
/// Usernames are valid if they match the regex `^[a-zA-Z0-9_]{3,16}$`.
///
/// # Examples
///
/// ```
/// use valence::util::valid_username;
///
/// assert!(valid_username("00a"));
/// assert!(valid_username("jeb_"));
///
/// assert!(!valid_username("notavalidusername"));
/// assert!(!valid_username("NotValid!"))
/// ```
pub fn valid_username(s: &str) -> bool { pub fn valid_username(s: &str) -> bool {
(3..=16).contains(&s.len()) (3..=16).contains(&s.len())
&& s.chars() && s.chars()
@ -16,6 +32,8 @@ pub fn valid_username(s: &str) -> bool {
const EXTRA_RADIUS: i32 = 3; const EXTRA_RADIUS: i32 = 3;
/// Returns an iterator over all chunk positions within a view distance,
/// centered on a particular chunk position.
pub fn chunks_in_view_distance( pub fn chunks_in_view_distance(
center: ChunkPos, center: ChunkPos,
distance: u8, distance: u8,
@ -26,8 +44,8 @@ pub fn chunks_in_view_distance(
.filter(move |&p| is_chunk_in_view_distance(center, p, distance)) .filter(move |&p| is_chunk_in_view_distance(center, p, distance))
} }
pub fn is_chunk_in_view_distance(center: ChunkPos, other: ChunkPos, distance: u8) -> bool { pub fn is_chunk_in_view_distance(p0: ChunkPos, p1: ChunkPos, distance: u8) -> bool {
(center.x as f64 - other.x as f64).powi(2) + (center.z as f64 - other.z as f64).powi(2) (p0.x as f64 - p1.x as f64).powi(2) + (p0.z as f64 - p1.z as f64).powi(2)
<= (distance as f64 + EXTRA_RADIUS as f64).powi(2) <= (distance as f64 + EXTRA_RADIUS as f64).powi(2)
} }
@ -54,10 +72,10 @@ where
aabb aabb
} }
/// Takes a normalized direction vector and returns a (yaw, pitch) tuple in /// Takes a normalized direction vector and returns a `(yaw, pitch)` tuple in
/// degrees. /// degrees.
/// ///
/// This function is the inverse of [`from_yaw_and_pitch`]. // /// This function is the inverse of [`from_yaw_and_pitch`].
pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) { pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) {
debug_assert!(d.is_normalized(), "the given vector should be normalized"); debug_assert!(d.is_normalized(), "the given vector should be normalized");

View file

@ -7,17 +7,27 @@ use crate::dimension::DimensionId;
use crate::player_list::PlayerList; use crate::player_list::PlayerList;
use crate::server::SharedServer; use crate::server::SharedServer;
use crate::slotmap::{Key, SlotMap}; use crate::slotmap::{Key, SlotMap};
use crate::SpatialIndex; use crate::spatial_index::SpatialIndex;
pub struct Worlds { pub struct Worlds {
sm: SlotMap<World>, sm: SlotMap<World>,
server: SharedServer, server: SharedServer,
} }
/// A key for a [`World`] on the server.
///
/// World IDs are either _valid_ or _invalid_. Valid world IDs point to
/// worlds that have not been deleted, while invalid IDs point to those that
/// have. Once an ID becomes invalid, it will never become valid again.
///
/// The [`Ord`] instance on this type is correct but otherwise unspecified. This
/// is useful for storing IDs in containers such as
/// [`BTreeMap`](std::collections::BTreeMap).
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct WorldId(Key); pub struct WorldId(Key);
impl WorldId { impl WorldId {
/// The value of the default world ID which is always invalid.
pub const NULL: Self = Self(Key::NULL); pub const NULL: Self = Self(Key::NULL);
} }
@ -29,6 +39,8 @@ impl Worlds {
} }
} }
/// Creates a new world on the server with the provided dimension. A
/// reference to the world along with its ID is returned.
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) { pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
let (id, world) = self.sm.insert(World { let (id, world) = self.sm.insert(World {
spatial_index: SpatialIndex::new(), spatial_index: SpatialIndex::new(),
@ -43,54 +55,76 @@ impl Worlds {
(WorldId(id), world) (WorldId(id), world)
} }
/// Deletes a world from the server. Any [`WorldId`] referring to the /// Deletes a world from the server.
/// deleted world will be invalidated.
/// ///
/// Note that any entities with positions inside the deleted world will not /// Note that entities located in the world are not deleted themselves.
/// be deleted themselves. /// Additionally, any clients that are still in the deleted world at the end
/// of the tick are disconnected.
pub fn delete(&mut self, world: WorldId) -> bool { pub fn delete(&mut self, world: WorldId) -> bool {
self.sm.remove(world.0).is_some() self.sm.remove(world.0).is_some()
} }
/// Deletes all worlds from the server (as if by [`Self::delete`]) for which
/// `f` returns `true`.
///
/// All worlds are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) {
self.sm.retain(|k, v| f(WorldId(k), v)) self.sm.retain(|k, v| f(WorldId(k), v))
} }
/// Returns the number of worlds on the server.
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.sm.len() self.sm.len()
} }
/// Returns a shared reference to the world with the given ID. If
/// the ID is invalid, then `None` is returned.
pub fn get(&self, world: WorldId) -> Option<&World> { pub fn get(&self, world: WorldId) -> Option<&World> {
self.sm.get(world.0) self.sm.get(world.0)
} }
/// Returns an exclusive reference to the world with the given ID. If the
/// ID is invalid, then `None` is returned.
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> { pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
self.sm.get_mut(world.0) self.sm.get_mut(world.0)
} }
/// Returns an immutable iterator over all worlds on the server in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), v)) self.sm.iter().map(|(k, v)| (WorldId(k), v))
} }
/// Returns a mutable iterator over all worlds on the server in an
/// unspecified ordder.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v)) self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
} }
/// Returns a parallel immutable iterator over all worlds on the server in
/// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (WorldId(k), v)) self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
} }
/// Returns a parallel mutable iterator over all worlds on the server in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v)) self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
} }
} }
/// A space for chunks, entities, and clients to occupy.
pub struct World { pub struct World {
/// Contains all of the entities in this world.
pub spatial_index: SpatialIndex, pub spatial_index: SpatialIndex,
/// All of the chunks in this world.
pub chunks: Chunks, pub chunks: Chunks,
/// This world's metadata.
pub meta: WorldMeta, pub meta: WorldMeta,
} }
/// Contains miscellaneous world state.
pub struct WorldMeta { pub struct WorldMeta {
dimension: DimensionId, dimension: DimensionId,
is_flat: bool, is_flat: bool,
@ -99,22 +133,33 @@ pub struct WorldMeta {
} }
impl WorldMeta { impl WorldMeta {
/// Gets the dimension the world was created with.
pub fn dimension(&self) -> DimensionId { pub fn dimension(&self) -> DimensionId {
self.dimension self.dimension
} }
/// Gets if this world is considered a superflat world. Superflat worlds
/// have a horizon line at y=0.
pub fn is_flat(&self) -> bool { pub fn is_flat(&self) -> bool {
self.is_flat self.is_flat
} }
/// Sets if this world is considered a superflat world. Superflat worlds
/// have a horizon line at y=0.
///
/// Clients already in the world must be respawned to see any changes.
pub fn set_flat(&mut self, flat: bool) { pub fn set_flat(&mut self, flat: bool) {
self.is_flat = flat; self.is_flat = flat;
} }
/// Returns a shared reference to the world's
/// [`PlayerList`](crate::player_list::PlayerList).
pub fn player_list(&self) -> &PlayerList { pub fn player_list(&self) -> &PlayerList {
&self.player_list &self.player_list
} }
/// Returns an exclusive reference to the world's
/// [`PlayerList`](crate::player_list::PlayerList).
pub fn player_list_mut(&mut self) -> &mut PlayerList { pub fn player_list_mut(&mut self) -> &mut PlayerList {
&mut self.player_list &mut self.player_list
} }