mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-11 07:11:30 +11:00
Document most items
This commit is contained in:
parent
54e0d5cb90
commit
3f150b4c8a
|
@ -1,4 +0,0 @@
|
|||
# valence
|
||||
Coming soon to a package manager near you!
|
||||
|
||||
(Valence is a WIP Rust library.)
|
|
@ -400,16 +400,16 @@ pub fn build() -> anyhow::Result<()> {
|
|||
#default_block_states
|
||||
}
|
||||
|
||||
/// An enumeration of all block types.
|
||||
/// An enumeration of all block kinds.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum BlockKind {
|
||||
#(#block_kind_variants,)*
|
||||
}
|
||||
|
||||
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> {
|
||||
match name {
|
||||
#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 {
|
||||
match self {
|
||||
#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 {
|
||||
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] {
|
||||
match self {
|
||||
#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,)*];
|
||||
}
|
||||
|
||||
/// The default block type is `air`.
|
||||
/// The default block kind is `air`.
|
||||
impl Default for BlockKind {
|
||||
fn default() -> Self {
|
||||
Self::Air
|
||||
|
|
|
@ -9,10 +9,10 @@ use num::Integer;
|
|||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use valence::biome::Biome;
|
||||
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::dimension::{Dimension, DimensionId};
|
||||
use valence::entity::meta::Pose;
|
||||
use valence::entity::data::Pose;
|
||||
use valence::entity::{EntityData, EntityId, EntityKind};
|
||||
use valence::server::{Server, SharedServer, ShutdownResult};
|
||||
use valence::text::{Color, TextFormat};
|
||||
|
@ -175,17 +175,15 @@ impl Config for Game {
|
|||
|
||||
while let Some(event) = client.pop_event() {
|
||||
match event {
|
||||
ClientEvent::Digging(e) => {
|
||||
let pos = e.position;
|
||||
|
||||
if (0..SIZE_X as i32).contains(&pos.x)
|
||||
&& (0..SIZE_Z as i32).contains(&pos.z)
|
||||
&& pos.y == BOARD_Y
|
||||
Event::Digging { position, .. } => {
|
||||
if (0..SIZE_X as i32).contains(&position.x)
|
||||
&& (0..SIZE_Z as i32).contains(&position.z)
|
||||
&& position.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 {
|
||||
client.teleport(spawn_pos, client.yaw(), client.pitch());
|
||||
}
|
||||
|
@ -197,29 +195,29 @@ impl Config for Game {
|
|||
player.set_pitch(client.pitch());
|
||||
player.set_on_ground(client.on_ground());
|
||||
}
|
||||
ClientEvent::StartSneaking => {
|
||||
Event::StartSneaking => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
e.set_crouching(true);
|
||||
e.set_pose(Pose::Sneaking);
|
||||
}
|
||||
}
|
||||
ClientEvent::StopSneaking => {
|
||||
Event::StopSneaking => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
e.set_pose(Pose::Standing);
|
||||
e.set_crouching(false);
|
||||
}
|
||||
}
|
||||
ClientEvent::StartSprinting => {
|
||||
Event::StartSprinting => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
e.set_sprinting(true);
|
||||
}
|
||||
}
|
||||
ClientEvent::StopSprinting => {
|
||||
Event::StopSprinting => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
e.set_sprinting(false);
|
||||
}
|
||||
}
|
||||
ClientEvent::ArmSwing(hand) => {
|
||||
Event::ArmSwing(hand) => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
match hand {
|
||||
Hand::Main => e.trigger_swing_main_arm(),
|
||||
|
|
84
src/biome.rs
84
src/biome.rs
|
@ -1,19 +1,23 @@
|
|||
//! Biome definitions.
|
||||
|
||||
use crate::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)]
|
||||
pub struct BiomeId(pub(crate) u16);
|
||||
|
||||
impl BiomeId {
|
||||
pub fn to_index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the configuration for a biome.
|
||||
///
|
||||
/// Biomes are registered once at startup through
|
||||
/// [`biomes`](crate::config::Config::biomes).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Biome {
|
||||
/// The unique name for this biome. The name can be
|
||||
|
@ -42,6 +46,70 @@ pub struct Biome {
|
|||
// * 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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::io::{Read, Write};
|
|||
use anyhow::Context;
|
||||
|
||||
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"));
|
||||
|
||||
|
@ -17,7 +17,7 @@ impl fmt::Debug 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)
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
struct KeyVal<'a>(&'a str, &'a str);
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ use std::io::{Read, Write};
|
|||
use anyhow::bail;
|
||||
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)]
|
||||
pub struct BlockPos {
|
||||
pub x: i32,
|
||||
|
@ -13,10 +14,12 @@ pub struct BlockPos {
|
|||
}
|
||||
|
||||
impl BlockPos {
|
||||
/// Constructs a new block position.
|
||||
pub const fn new(x: i32, y: i32, z: i32) -> Self {
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
/// Returns the block position a point is contained within.
|
||||
pub fn at(pos: impl Into<Vec3<f64>>) -> Self {
|
||||
pos.into().floor().as_::<i32>().into()
|
||||
}
|
||||
|
|
|
@ -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 approx::relative_eq;
|
||||
|
|
59
src/chunk.rs
59
src/chunk.rs
|
@ -14,13 +14,14 @@ use crate::block::BlockState;
|
|||
use crate::block_pos::BlockPos;
|
||||
pub use crate::chunk_pos::ChunkPos;
|
||||
use crate::dimension::DimensionId;
|
||||
use crate::protocol::packets::play::s2c::{
|
||||
use crate::protocol_inner::packets::play::s2c::{
|
||||
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::Ticks;
|
||||
|
||||
/// A container for all [`Chunks`]s in a [`World`](crate::world::World).
|
||||
pub struct Chunks {
|
||||
chunks: HashMap<ChunkPos, Chunk>,
|
||||
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 {
|
||||
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
|
||||
let chunk = Chunk::new(section_count, self.server.current_tick());
|
||||
|
@ -49,42 +60,69 @@ impl Chunks {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, pos: ChunkPos) -> bool {
|
||||
self.chunks.remove(&pos).is_some()
|
||||
/// Removes a chunk at the provided position.
|
||||
///
|
||||
/// 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 {
|
||||
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> {
|
||||
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> {
|
||||
self.chunks.get_mut(&pos.into())
|
||||
}
|
||||
|
||||
/// Deletes all chunks.
|
||||
pub fn clear(&mut self) {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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> {
|
||||
let pos = pos.into();
|
||||
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 {
|
||||
let pos = pos.into();
|
||||
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 {
|
||||
sections: Box<[ChunkSection]>,
|
||||
// TODO block_entities: HashMap<u32, BlockEntity>,
|
||||
|
|
|
@ -10,10 +10,13 @@ pub struct ChunkPos {
|
|||
}
|
||||
|
||||
impl ChunkPos {
|
||||
/// Constructs a new chunk position.
|
||||
pub const fn new(x: i32, z: i32) -> Self {
|
||||
Self { x, z }
|
||||
}
|
||||
|
||||
/// Takes an absolute position and returns the chunk position
|
||||
/// containing the point.
|
||||
pub fn at(x: f64, z: f64) -> Self {
|
||||
Self::new((x / 16.0).floor() as i32, (z / 16.0).floor() as i32)
|
||||
}
|
||||
|
|
345
src/client.rs
345
src/client.rs
|
@ -11,29 +11,28 @@ use rayon::iter::ParallelIterator;
|
|||
use uuid::Uuid;
|
||||
use vek::Vec3;
|
||||
|
||||
use crate::biome::{Biome, BiomeGrassColorModifier, BiomePrecipitation};
|
||||
use crate::biome::Biome;
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::chunk_pos::ChunkPos;
|
||||
use crate::dimension::{Dimension, DimensionEffects, DimensionId};
|
||||
use crate::entity::data::Player;
|
||||
use crate::dimension::DimensionId;
|
||||
use crate::entity::types::Player;
|
||||
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::packets::play::c2s::{
|
||||
use crate::protocol_inner::packets::play::c2s::{
|
||||
C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId,
|
||||
};
|
||||
pub use crate::protocol::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes;
|
||||
use crate::protocol::packets::play::s2c::{
|
||||
Animate, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound,
|
||||
BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, BlockChangeAck,
|
||||
ChatType, ChatTypeChat, ChatTypeNarration, ChatTypeRegistry, ChatTypeRegistryEntry,
|
||||
ClearTitles, DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect,
|
||||
EntityEvent, ForgetLevelChunk, GameEvent, GameEventReason, KeepAlive, Login,
|
||||
MoveEntityPosition, MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition,
|
||||
PlayerPositionFlags, RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket,
|
||||
SetChunkCacheCenter, SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SetSubtitleText,
|
||||
SetTitleText, SpawnPosition, SystemChat, TeleportEntity, ENTITY_EVENT_MAX_BOUND,
|
||||
pub use crate::protocol_inner::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes;
|
||||
use crate::protocol_inner::packets::play::s2c::{
|
||||
Animate, BiomeRegistry, BlockChangeAck, ChatType, ChatTypeChat, ChatTypeNarration,
|
||||
ChatTypeRegistry, ChatTypeRegistryEntry, ClearTitles, DimensionTypeRegistry,
|
||||
DimensionTypeRegistryEntry, Disconnect, EntityEvent, ForgetLevelChunk, GameEvent,
|
||||
GameEventReason, KeepAlive, Login, MoveEntityPosition, MoveEntityPositionAndRotation,
|
||||
MoveEntityRotation, PlayerPosition, PlayerPositionFlags, RegistryCodec, RemoveEntities,
|
||||
Respawn, RotateHead, S2cPlayPacket, SetChunkCacheCenter, SetChunkCacheRadius,
|
||||
SetEntityMetadata, SetEntityMotion, SetSubtitleText, 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::slotmap::{Key, SlotMap};
|
||||
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::{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 {
|
||||
sm: SlotMap<Client>,
|
||||
}
|
||||
|
@ -55,51 +59,103 @@ impl Clients {
|
|||
(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 {
|
||||
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) {
|
||||
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 {
|
||||
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> {
|
||||
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> {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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)]
|
||||
pub struct ClientId(Key);
|
||||
|
||||
impl ClientId {
|
||||
/// The value of the default client ID which is always invalid.
|
||||
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 {
|
||||
/// Setting this to `None` disconnects the client.
|
||||
send: SendOpt,
|
||||
|
@ -125,7 +181,7 @@ pub struct Client {
|
|||
spawn_position: BlockPos,
|
||||
spawn_position_yaw: f32,
|
||||
death_location: Option<(DimensionId, BlockPos)>,
|
||||
events: VecDeque<ClientEvent>,
|
||||
events: VecDeque<Event>,
|
||||
/// The ID of the last keepalive sent.
|
||||
last_keepalive_id: i64,
|
||||
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 {
|
||||
self.created_tick
|
||||
}
|
||||
|
||||
/// Gets the client's UUID.
|
||||
pub fn uuid(&self) -> Uuid {
|
||||
self.uuid
|
||||
}
|
||||
|
||||
/// Gets the username of this client, which is always valid.
|
||||
pub fn username(&self) -> &str {
|
||||
&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> {
|
||||
self.textures.as_ref()
|
||||
}
|
||||
|
||||
/// Gets the world this client is located in.
|
||||
pub fn world(&self) -> WorldId {
|
||||
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) {
|
||||
self.world = world;
|
||||
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>) {
|
||||
// We buffer messages because weird things happen if we send them before the
|
||||
// login packet.
|
||||
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> {
|
||||
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) {
|
||||
self.new_position = pos.into();
|
||||
|
||||
|
@ -265,22 +337,24 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets this client's yaw.
|
||||
pub fn yaw(&self) -> f32 {
|
||||
self.yaw
|
||||
}
|
||||
|
||||
/// Gets this client's pitch.
|
||||
pub fn pitch(&self) -> f32 {
|
||||
self.pitch
|
||||
}
|
||||
|
||||
/// Gets the spawn position. The client will see regular compasses point at
|
||||
/// the returned position.
|
||||
/// Gets the spawn position. The client will see `minecraft:compass` items
|
||||
/// point at the returned position.
|
||||
pub fn spawn_position(&self) -> BlockPos {
|
||||
self.spawn_position
|
||||
}
|
||||
|
||||
/// Sets the spawn position. The client will see regular compasses point at
|
||||
/// the provided position.
|
||||
/// Sets the spawn position. The client will see `minecraft:compass` items
|
||||
/// point at the provided position.
|
||||
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
|
||||
let pos = pos.into();
|
||||
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
|
||||
/// point at the returned position. If the client's current dimension
|
||||
/// differs from the returned dimension or the location is `None` then the
|
||||
/// compass will spin randomly.
|
||||
/// Gets the last death location of this client. The client will see
|
||||
/// `minecraft:recovery_compass` items point at the returned position.
|
||||
/// If the client's current dimension differs from the returned
|
||||
/// dimension or the location is `None` then the compass will spin
|
||||
/// randomly.
|
||||
pub fn death_location(&self) -> Option<(DimensionId, BlockPos)> {
|
||||
self.death_location
|
||||
}
|
||||
|
||||
/// Sets the last death location. The client will see recovery compasses
|
||||
/// point at the provided position. If the client's current dimension
|
||||
/// differs from the provided dimension or the location is `None` then the
|
||||
/// compass will spin randomly.
|
||||
/// Sets the last death location. The client will see
|
||||
/// `minecraft:recovery_compass` items point at the provided position.
|
||||
/// If the client's current dimension differs from the provided
|
||||
/// dimension or the location is `None` then the compass will spin
|
||||
/// randomly.
|
||||
///
|
||||
/// Changes to the last death location take effect when the client
|
||||
/// (re)spawns.
|
||||
|
@ -309,14 +385,22 @@ impl Client {
|
|||
self.death_location = location;
|
||||
}
|
||||
|
||||
/// Gets the client's game mode.
|
||||
pub fn game_mode(&self) -> GameMode {
|
||||
self.new_game_mode
|
||||
}
|
||||
|
||||
pub fn set_game_mode(&mut self, new_game_mode: GameMode) {
|
||||
self.new_game_mode = new_game_mode;
|
||||
/// Sets the client's 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(
|
||||
&mut self,
|
||||
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) {
|
||||
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 {
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -363,28 +458,44 @@ impl Client {
|
|||
.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 {
|
||||
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) {
|
||||
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) {
|
||||
self.flags.set_hardcore(hardcore);
|
||||
}
|
||||
|
||||
/// Gets if hardcore mode is enabled.
|
||||
pub fn is_hardcore(&mut self) -> bool {
|
||||
self.flags.hardcore()
|
||||
}
|
||||
|
||||
/// Gets the client's current settings.
|
||||
pub fn settings(&self) -> Option<&Settings> {
|
||||
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>) {
|
||||
if self.send.is_some() {
|
||||
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) {
|
||||
if self.send.is_some() {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -442,7 +559,7 @@ impl Client {
|
|||
if client.pending_teleports == 0 {
|
||||
// TODO: validate movement using swept AABB collision with the blocks.
|
||||
// TODO: validate that the client is actually inside/outside the vehicle?
|
||||
let event = ClientEvent::Movement {
|
||||
let event = Event::Movement {
|
||||
position: client.new_position,
|
||||
yaw: client.yaw,
|
||||
pitch: client.pitch,
|
||||
|
@ -485,7 +602,7 @@ impl Client {
|
|||
C2sPlayPacket::BlockEntityTagQuery(_) => {}
|
||||
C2sPlayPacket::ChangeDifficulty(_) => {}
|
||||
C2sPlayPacket::ChatCommand(_) => {}
|
||||
C2sPlayPacket::Chat(p) => self.events.push_back(ClientEvent::ChatMessage {
|
||||
C2sPlayPacket::Chat(p) => self.events.push_back(Event::ChatMessage {
|
||||
message: p.message.0,
|
||||
timestamp: Duration::from_millis(p.timestamp),
|
||||
}),
|
||||
|
@ -502,7 +619,7 @@ impl Client {
|
|||
allow_server_listings: p.allow_server_listings,
|
||||
});
|
||||
|
||||
self.events.push_back(ClientEvent::SettingsChanged(old));
|
||||
self.events.push_back(Event::SettingsChanged(old));
|
||||
}
|
||||
C2sPlayPacket::CommandSuggestion(_) => {}
|
||||
C2sPlayPacket::ContainerButtonClick(_) => {}
|
||||
|
@ -515,14 +632,14 @@ impl Client {
|
|||
// TODO: verify that the client has line of sight to the targeted entity and
|
||||
// that the distance is <=4 blocks.
|
||||
|
||||
self.events.push_back(ClientEvent::InteractWithEntity {
|
||||
self.events.push_back(Event::InteractWithEntity {
|
||||
id,
|
||||
sneaking: p.sneaking,
|
||||
kind: match p.kind {
|
||||
InteractKind::Interact(hand) => InteractWithEntity::Interact(hand),
|
||||
InteractKind::Attack => InteractWithEntity::Attack,
|
||||
InteractKind::Interact(hand) => InteractWithEntityKind::Interact(hand),
|
||||
InteractKind::Attack => InteractWithEntityKind::Attack,
|
||||
InteractKind::InteractAt((target, hand)) => {
|
||||
InteractWithEntity::InteractAt { target, hand }
|
||||
InteractWithEntityKind::InteractAt { target, hand }
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -575,7 +692,7 @@ impl Client {
|
|||
);
|
||||
}
|
||||
C2sPlayPacket::PaddleBoat(p) => {
|
||||
self.events.push_back(ClientEvent::SteerBoat {
|
||||
self.events.push_back(Event::SteerBoat {
|
||||
left_paddle_turning: p.left_paddle_turning,
|
||||
right_paddle_turning: p.right_paddle_turning,
|
||||
});
|
||||
|
@ -592,21 +709,21 @@ impl Client {
|
|||
}
|
||||
|
||||
self.events.push_back(match p.status {
|
||||
DiggingStatus::StartedDigging => ClientEvent::Digging(Digging {
|
||||
DiggingStatus::StartedDigging => Event::Digging {
|
||||
status: event::DiggingStatus::Start,
|
||||
position: p.location,
|
||||
face: p.face,
|
||||
}),
|
||||
DiggingStatus::CancelledDigging => ClientEvent::Digging(Digging {
|
||||
},
|
||||
DiggingStatus::CancelledDigging => Event::Digging {
|
||||
status: event::DiggingStatus::Cancel,
|
||||
position: p.location,
|
||||
face: p.face,
|
||||
}),
|
||||
DiggingStatus::FinishedDigging => ClientEvent::Digging(Digging {
|
||||
},
|
||||
DiggingStatus::FinishedDigging => Event::Digging {
|
||||
status: event::DiggingStatus::Finish,
|
||||
position: p.location,
|
||||
face: p.face,
|
||||
}),
|
||||
},
|
||||
DiggingStatus::DropItemStack => return,
|
||||
DiggingStatus::DropItem => return,
|
||||
DiggingStatus::ShootArrowOrFinishEating => return,
|
||||
|
@ -623,31 +740,33 @@ impl Client {
|
|||
self.events.push_back(match e.action_id {
|
||||
PlayerCommandId::StartSneaking => {
|
||||
self.flags.set_sneaking(true);
|
||||
ClientEvent::StartSneaking
|
||||
Event::StartSneaking
|
||||
}
|
||||
PlayerCommandId::StopSneaking => {
|
||||
self.flags.set_sneaking(false);
|
||||
ClientEvent::StopSneaking
|
||||
Event::StopSneaking
|
||||
}
|
||||
PlayerCommandId::LeaveBed => ClientEvent::LeaveBed,
|
||||
PlayerCommandId::LeaveBed => Event::LeaveBed,
|
||||
PlayerCommandId::StartSprinting => {
|
||||
self.flags.set_sprinting(true);
|
||||
ClientEvent::StartSprinting
|
||||
Event::StartSprinting
|
||||
}
|
||||
PlayerCommandId::StopSprinting => {
|
||||
self.flags.set_sprinting(false);
|
||||
ClientEvent::StopSprinting
|
||||
Event::StopSprinting
|
||||
}
|
||||
PlayerCommandId::StartJumpWithHorse => {
|
||||
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 => {
|
||||
self.flags.set_jumping_with_horse(false);
|
||||
ClientEvent::StopJumpWithHorse
|
||||
Event::StopJumpWithHorse
|
||||
}
|
||||
PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory,
|
||||
PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra,
|
||||
PlayerCommandId::OpenHorseInventory => Event::OpenHorseInventory,
|
||||
PlayerCommandId::StartFlyingWithElytra => Event::StartFlyingWithElytra,
|
||||
});
|
||||
}
|
||||
C2sPlayPacket::PlayerInput(_) => {}
|
||||
|
@ -666,7 +785,7 @@ impl Client {
|
|||
C2sPlayPacket::SetJigsawBlock(_) => {}
|
||||
C2sPlayPacket::SetStructureBlock(_) => {}
|
||||
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::UseItemOn(_) => {}
|
||||
C2sPlayPacket::UseItem(_) => {}
|
||||
|
@ -715,7 +834,7 @@ impl Client {
|
|||
gamemode: self.new_game_mode,
|
||||
previous_gamemode: self.old_game_mode,
|
||||
dimension_names,
|
||||
registry_codec: Nbt(make_dimension_codec(shared)),
|
||||
registry_codec: Nbt(make_registry_codec(shared)),
|
||||
dimension_type_name: ident!(
|
||||
"{LIBRARY_NAMESPACE}:dimension_type_{}",
|
||||
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();
|
||||
for (id, dim) in shared.dimensions() {
|
||||
let id = id.0 as i32;
|
||||
dims.push(DimensionTypeRegistryEntry {
|
||||
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
||||
id,
|
||||
element: to_dimension_registry_item(dim),
|
||||
element: dim.to_dimension_registry_item(),
|
||||
})
|
||||
}
|
||||
|
||||
let mut biomes = Vec::new();
|
||||
for (id, biome) in shared.biomes() {
|
||||
biomes.push(to_biome_registry_item(biome, id.0 as i32));
|
||||
}
|
||||
let mut biomes: Vec<_> = shared
|
||||
.biomes()
|
||||
.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
|
||||
// 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")) {
|
||||
let biome = Biome::default();
|
||||
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 {
|
||||
|
@ -1171,94 +1290,10 @@ fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
|
|||
element: ChatType {
|
||||
chat: ChatTypeChat {},
|
||||
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(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,50 +4,76 @@ use vek::Vec3;
|
|||
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::entity::EntityId;
|
||||
use crate::protocol::packets::play::c2s::BlockFace;
|
||||
pub use crate::protocol::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand};
|
||||
pub use crate::protocol::packets::play::s2c::GameMode;
|
||||
use crate::protocol_inner::packets::play::c2s::BlockFace;
|
||||
pub use crate::protocol_inner::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand};
|
||||
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)]
|
||||
pub enum ClientEvent {
|
||||
pub enum Event {
|
||||
/// A regular message was sent to the chat.
|
||||
ChatMessage {
|
||||
/// The content of the message
|
||||
message: String,
|
||||
/// The time the message was sent.
|
||||
timestamp: Duration,
|
||||
},
|
||||
/// Settings were changed. The value in this variant is the previous client
|
||||
/// settings.
|
||||
/// Settings were changed. The value in this variant is the _previous_
|
||||
/// client 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.
|
||||
Movement {
|
||||
/// Absolute coordinates of the previous position.
|
||||
position: Vec3<f64>,
|
||||
/// The previous yaw (in degrees).
|
||||
yaw: f32,
|
||||
/// The previous pitch (in degrees).
|
||||
pitch: f32,
|
||||
/// If the client was previously on the ground.
|
||||
on_ground: bool,
|
||||
},
|
||||
StartSneaking,
|
||||
StopSneaking,
|
||||
StartSprinting,
|
||||
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,
|
||||
/// The client left a bed.
|
||||
LeaveBed,
|
||||
/// The inventory was opened while on a horse.
|
||||
OpenHorseInventory,
|
||||
StartFlyingWithElytra,
|
||||
ArmSwing(Hand),
|
||||
/// Left or right click interaction with an entity's hitbox.
|
||||
InteractWithEntity {
|
||||
/// The ID of the entity being interacted with.
|
||||
id: EntityId,
|
||||
/// If the client was sneaking during the interaction.
|
||||
sneaking: bool,
|
||||
/// The type of interaction that occurred.
|
||||
kind: InteractWithEntity,
|
||||
/// The kind of interaction that occurred.
|
||||
kind: InteractWithEntityKind,
|
||||
},
|
||||
SteerBoat {
|
||||
left_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)]
|
||||
|
@ -67,22 +93,18 @@ pub struct Settings {
|
|||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum InteractWithEntity {
|
||||
pub enum InteractWithEntityKind {
|
||||
Interact(Hand),
|
||||
InteractAt { target: Vec3<f32>, hand: Hand },
|
||||
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)]
|
||||
pub enum DiggingStatus {
|
||||
/// The client started digging a block.
|
||||
Start,
|
||||
/// The client stopped digging a block before it was fully broken.
|
||||
Cancel,
|
||||
/// The client finished digging a block successfully.
|
||||
Finish,
|
||||
}
|
||||
|
|
|
@ -15,14 +15,15 @@ use crate::Ticks;
|
|||
/// server.
|
||||
///
|
||||
/// The config is used from multiple threads and must therefore implement
|
||||
/// `Send` and `Sync`. From within a single thread, methods are never invoked
|
||||
/// recursively by the library. In other words, a mutex can be aquired at
|
||||
/// the beginning of a method and released at the end without risk of
|
||||
/// [`Send`] and [`Sync`]. From within a single thread, methods are never
|
||||
/// invoked recursively by the library. In other words, a mutex can be aquired
|
||||
/// at the beginning of a method and released at the end without risk of
|
||||
/// deadlocking.
|
||||
///
|
||||
/// This trait uses the [async_trait](https://docs.rs/async-trait/latest/async_trait/) attribute macro.
|
||||
/// This will be removed once `impl Trait` in return position in traits is
|
||||
/// available in stable rust.
|
||||
/// This trait uses the [async_trait] attribute macro. It is exported at the
|
||||
/// root of this crate.
|
||||
///
|
||||
/// [async_trait]: https://docs.rs/async-trait/latest/async_trait/
|
||||
#[async_trait]
|
||||
#[allow(unused_variables)]
|
||||
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||
|
@ -39,6 +40,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// be bound to.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `127.0.0.1:25565`.
|
||||
fn address(&self) -> SocketAddr {
|
||||
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.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `20`, which is the same as Minecraft's official server.
|
||||
fn tick_rate(&self) -> Ticks {
|
||||
20
|
||||
|
@ -73,6 +76,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// internet.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `true`.
|
||||
fn online_mode(&self) -> bool {
|
||||
true
|
||||
|
@ -85,6 +89,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// potential memory usage.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// An unspecified value is returned that should be adequate in most
|
||||
/// situations.
|
||||
fn incoming_packet_capacity(&self) -> usize {
|
||||
|
@ -98,6 +103,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// but increases potential memory usage.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// An unspecified value is returned that should be adequate in most
|
||||
/// situations.
|
||||
fn outgoing_packet_capacity(&self) -> usize {
|
||||
|
@ -111,6 +117,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// runtime.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `None`.
|
||||
fn tokio_handle(&self) -> Option<TokioHandle> {
|
||||
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
|
||||
/// server.
|
||||
///
|
||||
/// The dimensions traversed by [`Server::dimensions`] will be in the same
|
||||
/// order as the `Vec` returned by this function.
|
||||
/// The dimensions returned by [`SharedServer::dimensions`] will be in the
|
||||
/// 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`]
|
||||
/// must be met.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `vec![Dimension::default()]`.
|
||||
fn dimensions(&self) -> Vec<Dimension> {
|
||||
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
|
||||
/// 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.
|
||||
///
|
||||
/// 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`]
|
||||
/// must be met.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `vec![Dimension::default()]`.
|
||||
fn biomes(&self) -> Vec<Biome> {
|
||||
vec![Biome::default()]
|
||||
|
@ -154,6 +163,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// This method is called from within a tokio runtime.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// The query is ignored.
|
||||
async fn server_list_ping(
|
||||
&self,
|
||||
|
@ -164,10 +174,10 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
}
|
||||
|
||||
/// Called asynchronously for each client after successful authentication
|
||||
/// (if online mode is enabled) to determine if they can continue to join
|
||||
/// the server. On success, [`Config::join`] is called with the new
|
||||
/// client. If this method returns with `Err(reason)`, then the client is
|
||||
/// immediately disconnected with the given reason.
|
||||
/// (if online mode is enabled) to determine if they can join
|
||||
/// the server. On success, the new client is added to the server's
|
||||
/// [`Clients`]. If this method returns with `Err(reason)`, then the
|
||||
/// client is immediately disconnected with the given reason.
|
||||
///
|
||||
/// This method is the appropriate place to perform asynchronous
|
||||
/// 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.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// The client is allowed to join unconditionally.
|
||||
///
|
||||
/// [`Clients`]: crate::client::Clients
|
||||
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -191,25 +204,29 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
fn init(&self, server: &mut Server) {}
|
||||
|
||||
/// 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`
|
||||
/// in [`ServerConfig`].
|
||||
/// The frequency of ticks can be configured by [`Self::tick_rate`].
|
||||
///
|
||||
/// This method is called from within a tokio runtime.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// The default implementation does nothing.
|
||||
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)]
|
||||
pub enum ServerListPing<'a> {
|
||||
/// Responds to the server list ping with the given information.
|
||||
Respond {
|
||||
/// Displayed as the number of players on the server.
|
||||
online_players: i32,
|
||||
/// Displayed as the maximum number of players allowed on the server at
|
||||
/// a time.
|
||||
max_players: i32,
|
||||
/// A description of the server.
|
||||
description: Text,
|
||||
/// The server's icon as the bytes of a PNG image.
|
||||
/// The image must be 64x64 pixels.
|
||||
|
|
|
@ -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)]
|
||||
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`
|
||||
/// returned by [`Config::dimensions`].
|
||||
/// returned by [`crate::config::Config::dimensions`].
|
||||
impl Default for DimensionId {
|
||||
fn default() -> Self {
|
||||
Self(0)
|
||||
|
@ -20,14 +21,19 @@ impl Default for DimensionId {
|
|||
|
||||
/// 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
|
||||
/// their own dimension type. A dimension in this library is analogous to a
|
||||
/// [`World`](crate::World) while [`Dimension`] represents a
|
||||
/// dimension type.
|
||||
/// [`World`] while [`Dimension`] represents a dimension type.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dimension {
|
||||
/// When false, compases will spin randomly.
|
||||
/// When false, compasses will spin randomly.
|
||||
pub natural: bool,
|
||||
/// Must be between 0.0 and 1.0.
|
||||
pub ambient_light: f32,
|
||||
|
@ -63,21 +69,52 @@ pub struct Dimension {
|
|||
// * 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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
natural: true,
|
||||
ambient_light: 1.0,
|
||||
fixed_time: None,
|
||||
effects: DimensionEffects::Overworld,
|
||||
effects: DimensionEffects::default(),
|
||||
min_y: -64,
|
||||
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 {
|
||||
#[default]
|
||||
Overworld,
|
||||
TheNether,
|
||||
TheEnd,
|
||||
|
|
133
src/entity.rs
133
src/entity.rs
|
@ -1,5 +1,5 @@
|
|||
pub mod data;
|
||||
pub mod meta;
|
||||
pub mod types;
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
|
@ -7,19 +7,29 @@ use std::iter::FusedIterator;
|
|||
use std::num::NonZeroU32;
|
||||
|
||||
use bitfield_struct::bitfield;
|
||||
pub use data::{EntityData, EntityKind};
|
||||
use rayon::iter::ParallelIterator;
|
||||
pub use types::{EntityData, EntityKind};
|
||||
use uuid::Uuid;
|
||||
use vek::{Aabb, Vec3};
|
||||
|
||||
use crate::protocol::packets::play::s2c::{
|
||||
use crate::protocol_inner::packets::play::s2c::{
|
||||
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::util::aabb_from_bottom_and_size;
|
||||
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 {
|
||||
sm: SlotMap<Entity>,
|
||||
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`]
|
||||
/// is returned.
|
||||
/// Spawns a new entity with a random UUID. A reference to the entity along
|
||||
/// with its ID is returned.
|
||||
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) {
|
||||
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()))
|
||||
.expect("UUID collision")
|
||||
}
|
||||
|
||||
/// Like [`create`](Entities::create), but requires specifying the new
|
||||
/// Like [`Self::create`], but requires specifying the new
|
||||
/// entity's UUID.
|
||||
///
|
||||
/// The provided UUID must not conflict with an existing entity UUID in this
|
||||
/// world. If it does, `None` is returned and the entity is not spawned.
|
||||
/// The provided UUID must not conflict with an existing entity UUID. If it
|
||||
/// does, `None` is returned and the entity is not spawned.
|
||||
pub fn create_with_uuid(
|
||||
&mut self,
|
||||
kind: EntityKind,
|
||||
|
@ -58,7 +68,7 @@ impl Entities {
|
|||
let (k, e) = self.sm.insert(Entity {
|
||||
flags: EntityFlags(0),
|
||||
data: EntityData::new(kind),
|
||||
world: None,
|
||||
world: WorldId::NULL,
|
||||
new_position: Vec3::default(),
|
||||
old_position: Vec3::default(),
|
||||
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 {
|
||||
if let Some(e) = self.sm.remove(entity.0) {
|
||||
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) {
|
||||
self.sm.retain(|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 {
|
||||
self.sm.len()
|
||||
}
|
||||
|
@ -120,16 +137,21 @@ impl Entities {
|
|||
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
|
||||
/// manner.
|
||||
///
|
||||
/// Returns `None` if there is no entity with the provided UUID. Returns
|
||||
/// `Some` otherwise.
|
||||
/// If there is no entity with the UUID, `None` is returned.
|
||||
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
|
||||
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> {
|
||||
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> {
|
||||
self.sm.get_mut(entity.0)
|
||||
}
|
||||
|
@ -140,18 +162,26 @@ impl Entities {
|
|||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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)]
|
||||
pub struct EntityId(Key);
|
||||
|
||||
impl EntityId {
|
||||
/// The value of the default entity ID which is always invalid.
|
||||
pub const NULL: Self = Self(Key::NULL);
|
||||
|
||||
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 {
|
||||
flags: EntityFlags,
|
||||
data: EntityData,
|
||||
world: Option<WorldId>,
|
||||
world: WorldId,
|
||||
new_position: Vec3<f64>,
|
||||
old_position: Vec3<f64>,
|
||||
yaw: f32,
|
||||
|
@ -192,8 +241,6 @@ pub struct Entity {
|
|||
uuid: Uuid,
|
||||
}
|
||||
|
||||
/// Contains a bit for certain fields in [`Entity`] to track if they have been
|
||||
/// modified.
|
||||
#[bitfield(u8)]
|
||||
pub(crate) struct EntityFlags {
|
||||
pub yaw_or_pitch_modified: bool,
|
||||
|
@ -209,51 +256,64 @@ impl Entity {
|
|||
self.flags
|
||||
}
|
||||
|
||||
/// Returns a reference to this entity's [`EntityData`].
|
||||
/// Gets a reference to this entity's [`EntityData`].
|
||||
pub fn data(&self) -> &EntityData {
|
||||
&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 {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
/// Returns the [`EntityKind`] of this entity.
|
||||
/// Gets the [`EntityKind`] of this entity.
|
||||
pub fn kind(&self) -> EntityKind {
|
||||
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
|
||||
}
|
||||
|
||||
pub fn set_world(&mut self, world: impl Into<Option<WorldId>>) {
|
||||
self.world = world.into();
|
||||
/// Sets the world this entity is located in.
|
||||
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> {
|
||||
self.new_position
|
||||
}
|
||||
|
||||
/// 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>>) {
|
||||
self.new_position = pos.into();
|
||||
}
|
||||
|
||||
/// Returns the position of this entity as it existed at the end of the
|
||||
/// previous tick.
|
||||
pub fn old_position(&self) -> Vec3<f64> {
|
||||
pub(crate) fn old_position(&self) -> Vec3<f64> {
|
||||
self.old_position
|
||||
}
|
||||
|
||||
/// Gets the yaw of this entity (in degrees).
|
||||
/// Gets the yaw of this entity in degrees.
|
||||
pub fn yaw(&self) -> f32 {
|
||||
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) {
|
||||
if 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 {
|
||||
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) {
|
||||
if 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 {
|
||||
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) {
|
||||
if self.head_yaw != head_yaw {
|
||||
self.head_yaw = head_yaw;
|
||||
|
@ -292,6 +352,7 @@ impl Entity {
|
|||
self.velocity
|
||||
}
|
||||
|
||||
/// Sets the velocity of this entity in meters per second.
|
||||
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
||||
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 {
|
||||
self.flags.on_ground()
|
||||
}
|
||||
|
||||
/// Sets the value of the "on ground" flag.
|
||||
pub fn set_on_ground(&mut self, on_ground: bool) {
|
||||
self.flags.set_on_ground(on_ground);
|
||||
}
|
||||
|
||||
/// Gets the UUID of this entity.
|
||||
pub fn 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> {
|
||||
let dims = match &self.data {
|
||||
EntityData::Allay(_) => [0.6, 0.35, 0.6],
|
||||
|
|
|
@ -1,10 +1,216 @@
|
|||
#![allow(clippy::all, missing_docs)]
|
||||
//! Types used in [`EntityData`](crate::entity::EntityData).
|
||||
|
||||
use crate::block::{BlockPos, BlockState};
|
||||
use crate::entity::meta::*;
|
||||
use crate::entity::EntityId;
|
||||
use crate::protocol::{Encode, VarInt};
|
||||
use crate::text::Text;
|
||||
use crate::uuid::Uuid;
|
||||
use std::io::Write;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
12
src/entity/types.rs
Normal 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"));
|
26
src/ident.rs
26
src/ident.rs
|
@ -7,15 +7,15 @@ use serde::de::Visitor;
|
|||
use serde::{Deserialize, Serialize};
|
||||
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.
|
||||
/// For instance `minecraft:apple` and `apple` are both valid identifiers.
|
||||
///
|
||||
/// 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)]
|
||||
pub struct Ident {
|
||||
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.
|
||||
///
|
||||
/// # 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_rules! ident {
|
||||
($($arg:tt)*) => {{
|
||||
|
|
136
src/lib.rs
136
src/lib.rs
|
@ -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)]
|
||||
#![warn(
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_lifetimes,
|
||||
unused_import_braces,
|
||||
// missing_docs
|
||||
unused_import_braces
|
||||
)]
|
||||
|
||||
pub mod biome;
|
||||
|
@ -18,31 +119,36 @@ pub mod config;
|
|||
pub mod dimension;
|
||||
pub mod entity;
|
||||
pub mod ident;
|
||||
mod player_list;
|
||||
pub mod player_list;
|
||||
pub mod player_textures;
|
||||
#[cfg(not(feature = "protocol"))]
|
||||
#[allow(unused)]
|
||||
mod protocol;
|
||||
#[cfg(feature = "protocol")]
|
||||
pub mod protocol;
|
||||
#[allow(dead_code)]
|
||||
mod protocol_inner;
|
||||
pub mod server;
|
||||
mod slotmap;
|
||||
mod spatial_index;
|
||||
pub mod spatial_index;
|
||||
pub mod text;
|
||||
pub mod util;
|
||||
pub mod world;
|
||||
|
||||
#[cfg(feature = "protocol")]
|
||||
pub mod protocol {
|
||||
pub use crate::protocol_inner::*;
|
||||
}
|
||||
|
||||
pub use async_trait::async_trait;
|
||||
pub use server::start_server;
|
||||
pub use spatial_index::SpatialIndex;
|
||||
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;
|
||||
/// 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";
|
||||
|
||||
/// 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";
|
||||
|
||||
/// 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
|
||||
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
||||
pub type Ticks = i64;
|
||||
|
||||
/// Whatever dude
|
||||
#[cfg(feature = "whatever")]
|
||||
pub type Whatever = i64;
|
||||
|
|
|
@ -6,13 +6,20 @@ use uuid::Uuid;
|
|||
|
||||
use crate::client::GameMode;
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::packets::play::s2c::{
|
||||
use crate::protocol_inner::packets::play::s2c::{
|
||||
PlayerInfo, PlayerInfoAddPlayer, S2cPlayPacket, TabList,
|
||||
};
|
||||
use crate::protocol::packets::Property;
|
||||
use crate::protocol::VarInt;
|
||||
use crate::protocol_inner::packets::Property;
|
||||
use crate::protocol_inner::VarInt;
|
||||
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 {
|
||||
entries: HashMap<Uuid, PlayerListEntry>,
|
||||
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(
|
||||
&mut self,
|
||||
uuid: Uuid,
|
||||
|
@ -40,7 +51,7 @@ impl PlayerList {
|
|||
game_mode: GameMode,
|
||||
ping: i32,
|
||||
display_name: impl Into<Option<Text>>,
|
||||
) {
|
||||
) -> bool {
|
||||
match self.entries.entry(uuid) {
|
||||
Entry::Occupied(mut oe) => {
|
||||
let e = oe.get_mut();
|
||||
|
@ -62,6 +73,7 @@ impl PlayerList {
|
|||
e.set_ping(ping);
|
||||
e.set_display_name(display_name);
|
||||
}
|
||||
false
|
||||
}
|
||||
Entry::Vacant(ve) => {
|
||||
ve.insert(PlayerListEntry {
|
||||
|
@ -72,10 +84,13 @@ impl PlayerList {
|
|||
display_name: display_name.into(),
|
||||
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 {
|
||||
if self.entries.remove(&uuid).is_some() {
|
||||
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 {
|
||||
&self.header
|
||||
}
|
||||
|
||||
/// Sets the header part of the player list.
|
||||
pub fn set_header(&mut self, header: impl Into<Text>) {
|
||||
let header = header.into();
|
||||
if self.header != header {
|
||||
|
@ -97,10 +133,12 @@ impl PlayerList {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the footer part of the player list.
|
||||
pub fn footer(&self) -> &Text {
|
||||
&self.footer
|
||||
}
|
||||
|
||||
/// Sets the footer part of the player list.
|
||||
pub fn set_footer(&mut self, footer: impl Into<Text>) {
|
||||
let footer = footer.into();
|
||||
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)> + '_ {
|
||||
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)> + '_ {
|
||||
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 {
|
||||
username: String,
|
||||
textures: Option<SignedPlayerTextures>,
|
||||
|
@ -250,6 +292,7 @@ pub struct PlayerListEntry {
|
|||
}
|
||||
|
||||
impl PlayerListEntry {
|
||||
/// Gets the username of this entry.
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
@ -258,10 +301,12 @@ impl PlayerListEntry {
|
|||
self.textures.as_ref()
|
||||
}
|
||||
|
||||
/// Gets the game mode of this entry.
|
||||
pub fn game_mode(&self) -> GameMode {
|
||||
self.game_mode
|
||||
}
|
||||
|
||||
/// Sets the game mode of this entry.
|
||||
pub fn set_game_mode(&mut self, game_mode: GameMode) {
|
||||
if 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 {
|
||||
self.ping
|
||||
}
|
||||
|
||||
/// Sets the ping (latency) of this entry measured in milliseconds.
|
||||
pub fn set_ping(&mut self, ping: i32) {
|
||||
if 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> {
|
||||
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>>) {
|
||||
let display_name = display_name.into();
|
||||
if self.display_name != display_name {
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
//! Player skins and capes.
|
||||
|
||||
use anyhow::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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)]
|
||||
pub struct SignedPlayerTextures {
|
||||
payload: Box<[u8]>,
|
||||
|
@ -9,14 +15,15 @@ pub struct SignedPlayerTextures {
|
|||
}
|
||||
|
||||
impl SignedPlayerTextures {
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
pub(crate) fn payload(&self) -> &[u8] {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &[u8] {
|
||||
pub(crate) fn signature(&self) -> &[u8] {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
/// Gets the unsigned texture URLs.
|
||||
pub fn to_textures(&self) -> PlayerTextures {
|
||||
self.to_textures_fallible()
|
||||
.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)]
|
||||
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>,
|
||||
/// A URL to the cape of a player. Is `None` if the player does not have a
|
||||
/// cape.
|
||||
pub cape: Option<Url>,
|
||||
}
|
||||
|
||||
|
@ -63,7 +75,7 @@ impl From<SignedPlayerTextures> for PlayerTextures {
|
|||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct PlayerTexturesPayload {
|
||||
struct PlayerTexturesPayload {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
skin: Option<TextureUrl>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
|
|
|
@ -21,12 +21,12 @@ use vek::{Vec2, Vec3, Vec4};
|
|||
|
||||
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 {
|
||||
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 {
|
||||
fn decode(r: &mut impl Read) -> anyhow::Result<Self>;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
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.
|
||||
#[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)
|
||||
}
|
||||
|
||||
// pub fn to_degrees(self) -> f32 {
|
||||
// self.0 as f32 / 256.0 * 360.0
|
||||
// }
|
||||
pub fn to_degrees(self) -> f32 {
|
||||
self.0 as f32 / 256.0 * 360.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for ByteAngle {
|
||||
|
@ -24,6 +24,6 @@ impl Encode for ByteAngle {
|
|||
|
||||
impl Decode for ByteAngle {
|
||||
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
|
||||
Ok(ByteAngle(u8::decode(r)?))
|
||||
u8::decode(r).map(ByteAngle)
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
|||
use tokio::time::timeout;
|
||||
|
||||
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> {
|
||||
write: W,
|
||||
|
@ -249,7 +249,7 @@ mod tests {
|
|||
use tokio::sync::oneshot;
|
||||
|
||||
use super::*;
|
||||
use crate::protocol::packets::test::TestPacket;
|
||||
use crate::protocol_inner::packets::test::TestPacket;
|
||||
|
||||
#[tokio::test]
|
||||
async fn encode_decode() {
|
|
@ -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.
|
||||
|
||||
#![allow(dead_code)]
|
||||
//! See <https://wiki.vg/Protocol> for more packet documentation.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
|
@ -17,7 +15,7 @@ use vek::Vec3;
|
|||
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::ident::Ident;
|
||||
use crate::protocol::{
|
||||
use crate::protocol_inner::{
|
||||
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, Nbt, RawBytes, VarInt,
|
||||
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<()> {
|
||||
self.0.encode(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::protocol::Decode for $name {
|
||||
impl Decode for $name {
|
||||
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
|
||||
<$inner_ty>::decode(r).map(Self)
|
||||
}
|
||||
|
@ -457,7 +455,7 @@ pub mod status {
|
|||
|
||||
def_struct! {
|
||||
PongResponse 0x01 {
|
||||
/// Should be the same as the payload from [`Ping`].
|
||||
/// Should be the same as the payload from ping.
|
||||
payload: u64
|
||||
}
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
// TODO: use derive_more?
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::bail;
|
||||
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)]
|
||||
pub struct VarInt(pub i32);
|
||||
|
|
@ -3,8 +3,9 @@ use std::io::{Read, Write};
|
|||
use anyhow::bail;
|
||||
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)]
|
||||
pub struct VarLong(pub(crate) i64);
|
||||
|
|
@ -31,30 +31,42 @@ use crate::config::{Config, ServerListPing};
|
|||
use crate::dimension::{Dimension, DimensionId};
|
||||
use crate::entity::Entities;
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::codec::{Decoder, Encoder};
|
||||
use crate::protocol::packets::handshake::{Handshake, HandshakeNextState};
|
||||
use crate::protocol::packets::login::c2s::{EncryptionResponse, LoginStart, VerifyTokenOrMsgSig};
|
||||
use crate::protocol::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression};
|
||||
use crate::protocol::packets::play::c2s::C2sPlayPacket;
|
||||
use crate::protocol::packets::play::s2c::S2cPlayPacket;
|
||||
use crate::protocol::packets::status::c2s::{PingRequest, StatusRequest};
|
||||
use crate::protocol::packets::status::s2c::{PongResponse, StatusResponse};
|
||||
use crate::protocol::packets::{login, Property};
|
||||
use crate::protocol::{BoundedArray, BoundedString, VarInt};
|
||||
use crate::protocol_inner::codec::{Decoder, Encoder};
|
||||
use crate::protocol_inner::packets::handshake::{Handshake, HandshakeNextState};
|
||||
use crate::protocol_inner::packets::login::c2s::{
|
||||
EncryptionResponse, LoginStart, VerifyTokenOrMsgSig,
|
||||
};
|
||||
use crate::protocol_inner::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression};
|
||||
use crate::protocol_inner::packets::play::c2s::C2sPlayPacket;
|
||||
use crate::protocol_inner::packets::play::s2c::S2cPlayPacket;
|
||||
use crate::protocol_inner::packets::status::c2s::{PingRequest, StatusRequest};
|
||||
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::world::Worlds;
|
||||
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 {
|
||||
/// A handle to this server's [`SharedServer`].
|
||||
pub shared: SharedServer,
|
||||
/// All of the clients in the server.
|
||||
pub clients: Clients,
|
||||
/// All of entities in the server.
|
||||
pub entities: Entities,
|
||||
/// All of the worlds in the server.
|
||||
pub worlds: Worlds,
|
||||
}
|
||||
|
||||
/// A handle to a running Minecraft server containing state which is accessible
|
||||
/// outside the update loop. Servers are internally refcounted and can be shared
|
||||
/// between threads.
|
||||
/// A handle to a Minecraft server containing the subset of functionality which
|
||||
/// is accessible outside the [update][update] loop.
|
||||
///
|
||||
/// `SharedServer`s are internally refcounted and can
|
||||
/// be shared between threads.
|
||||
///
|
||||
/// [update]: crate::config::Config::update
|
||||
#[derive(Clone)]
|
||||
pub struct SharedServer(Arc<SharedServerInner>);
|
||||
|
||||
|
@ -105,43 +117,49 @@ struct NewClientMessage {
|
|||
reply: oneshot::Sender<S2cPacketChannels>,
|
||||
}
|
||||
|
||||
/// The result type returned from [`ServerConfig::start`] after the server is
|
||||
/// shut down.
|
||||
pub type ShutdownResult = Result<(), ShutdownError>;
|
||||
pub type ShutdownError = Box<dyn Error + Send + Sync + 'static>;
|
||||
/// The result type returned from [`start_server`].
|
||||
pub type ShutdownResult = Result<(), Box<dyn Error + Send + Sync + 'static>>;
|
||||
|
||||
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
|
||||
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
|
||||
|
||||
impl SharedServer {
|
||||
/// Gets a reference to the config object used to start the server.
|
||||
pub fn config(&self) -> &(impl Config + ?Sized) {
|
||||
self.0.cfg.as_ref()
|
||||
}
|
||||
|
||||
/// Gets the socket address this server is bound to.
|
||||
pub fn address(&self) -> SocketAddr {
|
||||
self.0.address
|
||||
}
|
||||
|
||||
/// Gets the configured tick rate of this server.
|
||||
pub fn tick_rate(&self) -> Ticks {
|
||||
self.0.tick_rate
|
||||
}
|
||||
|
||||
/// Gets whether online mode is enabled on this server.
|
||||
pub fn online_mode(&self) -> bool {
|
||||
self.0.online_mode
|
||||
}
|
||||
|
||||
/// Gets the maximum number of connections allowed to the server at once.
|
||||
pub fn max_connections(&self) -> usize {
|
||||
self.0.max_connections
|
||||
}
|
||||
|
||||
/// Gets the configured incoming packet capacity.
|
||||
pub fn incoming_packet_capacity(&self) -> usize {
|
||||
self.0.incoming_packet_capacity
|
||||
}
|
||||
|
||||
/// Gets the configured outgoing incoming packet capacity.
|
||||
pub fn outgoing_packet_capacity(&self) -> usize {
|
||||
self.0.outgoing_packet_capacity
|
||||
}
|
||||
|
||||
/// Gets a handle to the tokio instance this server is using.
|
||||
pub fn tokio_handle(&self) -> &Handle {
|
||||
&self.0.tokio_handle
|
||||
}
|
||||
|
@ -197,7 +215,7 @@ impl SharedServer {
|
|||
}
|
||||
|
||||
/// 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
|
||||
/// this function.
|
||||
|
@ -213,10 +231,10 @@ impl SharedServer {
|
|||
|
||||
/// 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.
|
||||
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();
|
||||
|
||||
|
@ -462,7 +480,7 @@ async fn do_accept_loop(server: SharedServer) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
log::debug!("connection to {remote_addr} ended: {e:#}");
|
||||
log::error!("connection to {remote_addr} ended: {e:#}");
|
||||
}
|
||||
drop(permit);
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Efficient spatial entity queries.
|
||||
|
||||
use vek::{Aabb, Vec3};
|
||||
|
||||
use crate::bvh::Bvh;
|
||||
|
@ -5,6 +7,14 @@ pub use crate::bvh::TraverseStep;
|
|||
use crate::entity::{Entities, EntityId};
|
||||
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 {
|
||||
bvh: Bvh<EntityId>,
|
||||
}
|
||||
|
@ -14,13 +24,46 @@ impl SpatialIndex {
|
|||
Self { bvh: Bvh::new() }
|
||||
}
|
||||
|
||||
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))
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "This is for documentation tests only"]
|
||||
pub fn example_new() -> Self {
|
||||
println!("Don't call me!");
|
||||
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>
|
||||
where
|
||||
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) {
|
||||
self.bvh.build(
|
||||
entities
|
||||
.iter()
|
||||
.filter(|(_, e)| e.world() == Some(id))
|
||||
.filter(|(_, e)| e.world() == id)
|
||||
.map(|(id, e)| (id, e.hitbox())),
|
||||
)
|
||||
}
|
||||
|
@ -113,7 +167,7 @@ impl SpatialIndex {
|
|||
pub struct RaycastHit {
|
||||
/// The [`EntityId`] of the entity that was hit by the ray.
|
||||
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>,
|
||||
/// 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
|
||||
|
|
23
src/text.rs
23
src/text.rs
|
@ -1,6 +1,3 @@
|
|||
// TODO: Documentation.
|
||||
// TODO: make fields of Text public?
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
|
@ -9,19 +6,22 @@ use serde::de::Visitor;
|
|||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
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.
|
||||
///
|
||||
/// Text is used in various places such as chat, window titles,
|
||||
/// 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
|
||||
/// 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:
|
||||
/// ```
|
||||
/// use valence::text::{Color, Text, TextFormat};
|
||||
|
@ -82,6 +82,8 @@ pub struct Text {
|
|||
}
|
||||
|
||||
impl Text {
|
||||
/// Returns `true` if the text contains no characters. Returns `false`
|
||||
/// otherwise.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
for extra in &self.extra {
|
||||
if !extra.is_empty() {
|
||||
|
@ -99,7 +101,7 @@ impl Text {
|
|||
/// Provides the methods necessary for working with [`Text`] objects.
|
||||
///
|
||||
/// 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.
|
||||
pub trait TextFormat: Into<Text> {
|
||||
fn into_text(self) -> Text {
|
||||
|
@ -366,8 +368,9 @@ impl Text {
|
|||
}
|
||||
|
||||
pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result {
|
||||
if let TextContent::Text { text } = &self.content {
|
||||
w.write_str(text.as_ref())?;
|
||||
match &self.content {
|
||||
TextContent::Text { text } => w.write_str(text.as_ref())?,
|
||||
TextContent::Translate { translate } => w.write_str(translate.as_ref())?,
|
||||
}
|
||||
|
||||
for child in &self.extra {
|
||||
|
@ -376,8 +379,6 @@ impl Text {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: getters
|
||||
}
|
||||
|
||||
impl<T: Into<Text>> TextFormat for T {}
|
||||
|
|
26
src/util.rs
26
src/util.rs
|
@ -1,3 +1,5 @@
|
|||
//! Miscellaneous utilities.
|
||||
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
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
|
||||
/// 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 {
|
||||
(3..=16).contains(&s.len())
|
||||
&& s.chars()
|
||||
|
@ -16,6 +32,8 @@ pub fn valid_username(s: &str) -> bool {
|
|||
|
||||
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(
|
||||
center: ChunkPos,
|
||||
distance: u8,
|
||||
|
@ -26,8 +44,8 @@ pub fn chunks_in_view_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 {
|
||||
(center.x as f64 - other.x as f64).powi(2) + (center.z as f64 - other.z as f64).powi(2)
|
||||
pub fn is_chunk_in_view_distance(p0: ChunkPos, p1: ChunkPos, distance: u8) -> bool {
|
||||
(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)
|
||||
}
|
||||
|
||||
|
@ -54,10 +72,10 @@ where
|
|||
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.
|
||||
///
|
||||
/// 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) {
|
||||
debug_assert!(d.is_normalized(), "the given vector should be normalized");
|
||||
|
||||
|
|
55
src/world.rs
55
src/world.rs
|
@ -7,17 +7,27 @@ use crate::dimension::DimensionId;
|
|||
use crate::player_list::PlayerList;
|
||||
use crate::server::SharedServer;
|
||||
use crate::slotmap::{Key, SlotMap};
|
||||
use crate::SpatialIndex;
|
||||
use crate::spatial_index::SpatialIndex;
|
||||
|
||||
pub struct Worlds {
|
||||
sm: SlotMap<World>,
|
||||
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)]
|
||||
pub struct WorldId(Key);
|
||||
|
||||
impl WorldId {
|
||||
/// The value of the default world ID which is always invalid.
|
||||
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) {
|
||||
let (id, world) = self.sm.insert(World {
|
||||
spatial_index: SpatialIndex::new(),
|
||||
|
@ -43,54 +55,76 @@ impl Worlds {
|
|||
(WorldId(id), world)
|
||||
}
|
||||
|
||||
/// Deletes a world from the server. Any [`WorldId`] referring to the
|
||||
/// deleted world will be invalidated.
|
||||
/// Deletes a world from the server.
|
||||
///
|
||||
/// Note that any entities with positions inside the deleted world will not
|
||||
/// be deleted themselves.
|
||||
/// Note that entities located in the world are not 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 {
|
||||
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) {
|
||||
self.sm.retain(|k, v| f(WorldId(k), v))
|
||||
}
|
||||
|
||||
/// Returns the number of worlds on the server.
|
||||
pub fn count(&self) -> usize {
|
||||
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> {
|
||||
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> {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
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 + '_ {
|
||||
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)> + '_ {
|
||||
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||
}
|
||||
}
|
||||
|
||||
/// A space for chunks, entities, and clients to occupy.
|
||||
pub struct World {
|
||||
/// Contains all of the entities in this world.
|
||||
pub spatial_index: SpatialIndex,
|
||||
/// All of the chunks in this world.
|
||||
pub chunks: Chunks,
|
||||
/// This world's metadata.
|
||||
pub meta: WorldMeta,
|
||||
}
|
||||
|
||||
/// Contains miscellaneous world state.
|
||||
pub struct WorldMeta {
|
||||
dimension: DimensionId,
|
||||
is_flat: bool,
|
||||
|
@ -99,22 +133,33 @@ pub struct WorldMeta {
|
|||
}
|
||||
|
||||
impl WorldMeta {
|
||||
/// Gets the dimension the world was created with.
|
||||
pub fn dimension(&self) -> DimensionId {
|
||||
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 {
|
||||
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) {
|
||||
self.is_flat = flat;
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the world's
|
||||
/// [`PlayerList`](crate::player_list::PlayerList).
|
||||
pub fn player_list(&self) -> &PlayerList {
|
||||
&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 {
|
||||
&mut self.player_list
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue