Document most items

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

View file

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

View file

@ -400,16 +400,16 @@ pub fn build() -> anyhow::Result<()> {
#default_block_states
}
/// 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

View file

@ -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(),

View file

@ -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 {

View file

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

View file

@ -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()
}

View file

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

View file

@ -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>,

View file

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

View file

@ -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(),
},
}),
},
}
}

View file

@ -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,
}

View file

@ -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.

View file

@ -1,17 +1,18 @@
/// A handle to a particular [`Dimension`] on the server.
use crate::ident;
use crate::protocol_inner::packets::play::s2c::DimensionType;
/// Identifies a particular [`Dimension`] on the server.
///
/// Dimension IDs must only be used on servers from which they originate.
/// The default dimension ID refers to the first dimension added in the server's
/// [configuration](crate::config::Config).
///
/// To obtain dimension IDs for other dimensions, call
/// [`dimensions`](crate::server::SharedServer::dimensions).
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
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,

View file

@ -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],

View file

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

View file

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

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

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

View file

@ -7,15 +7,15 @@ use serde::de::Visitor;
use serde::{Deserialize, Serialize};
use 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)*) => {{

View file

@ -1,10 +1,111 @@
//! A Rust framework for building efficient Minecraft servers.
//!
//! Valence is a Rust library which provides the necessary abstractions over
//! Minecraft's protocol to build servers. Very few assumptions about the
//! desired server are made, which allows for greater flexibility in its design.
//!
//! At a high level, a Valence [`Server`] is a collection of [`Clients`],
//! [`Entities`], and [`Worlds`]. When a client connects to the server, they are
//! added to the server's [`Clients`]. After connecting, clients are assigned to
//! a [`World`] where they are able to interact with the entities and
//! [`Chunks`] that are a part of it.
//!
//! The Valence documentation assumes some familiarity with Minecraft and its
//! mechanics. See the [Minecraft Wiki] for general information and [wiki.vg]
//! for protocol documentation.
//!
//! [Minecraft Wiki]: https://minecraft.fandom.com/wiki/Minecraft_Wiki
//! [wiki.vg]: https://wiki.vg/Main_Page
//!
//! # Logging
//!
//! Valence uses the [log] crate to report errors and other information. You may
//! want to use a logging implementation such as [env_logger] to see these
//! messages.
//!
//! [log]: https://docs.rs/log/latest/log/
//! [env_logger]: https://docs.rs/env_logger/latest/env_logger/
//!
//! # An Important Note on [`mem::swap`]
//!
//! In Valence, many types are owned by the library but given out as mutable
//! references for the user to modify. Examples of such types include [`World`],
//! [`Chunk`], [`Entity`], and [`Client`].
//!
//! **You must not call [`mem::swap`] on these references (or any other
//! function that would move their location in memory).** Doing so breaks
//! invariants within the library and the resulting behavior is unspecified.
//! These types should be considered pinned in memory.
//!
//! Preventing this illegal behavior using Rust's type system was considered too
//! cumbersome, so a note has been left here instead.
//!
//! [`mem::swap`]: std::mem::swap
//!
//! # Examples
//!
//! The following is a minimal server implementation. You should be able to
//! connect to the server at `localhost`.
//!
//! ```
//! use valence::config::Config;
//! use valence::server::{Server, ShutdownResult};
//!
//! pub fn main() -> ShutdownResult {
//! valence::start_server(Game)
//! }
//!
//! struct Game;
//!
//! impl Config for Game {
//! fn max_connections(&self) -> usize {
//! 256
//! }
//!
//! fn update(&self, server: &mut Server) {
//! server.clients.retain(|_, client| {
//! if client.created_tick() == server.shared.current_tick() {
//! println!("{} joined!", client.username());
//! }
//!
//! if client.is_disconnected() {
//! println!("{} left!", client.username());
//! false
//! } else {
//! true
//! }
//! });
//! # server.shared.shutdown::<_, std::convert::Infallible>(Ok(()));
//! }
//! }
//! ```
//!
//! For more complete examples, see the [examples] in the source repository.
//!
//! [examples]: https://github.com/rj00a/valence/tree/main/examples
//!
//! # Feature Flags
//!
//! * `protocol`: Enables low-level access to the [`protocol`] module, which
//! could be used to build your own proxy or client. This feature is
//! considered experimental and is subject to change.
//!
//! [`Server`]: crate::server::Server
//! [`Clients`]: crate::client::Clients
//! [`Entities`]: crate::entity::Entities
//! [`Worlds`]: crate::world::Worlds
//! [`World`]: crate::world::World
//! [`Chunks`]: crate::chunk::Chunks
//! [`Chunk`]: crate::chunk::Chunk
//! [`Entity`]: crate::entity::Entity
//! [`Client`]: crate::client::Client
#![forbid(unsafe_code)]
#![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;

View file

@ -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 {

View file

@ -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")]

View file

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

View file

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

View file

@ -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() {

View file

@ -1,8 +1,6 @@
//! Contains packet definitions and some types contained within them.
//! Packet definitions and some types contained within them.
//!
//! See <https://wiki.vg/Protocol> for up to date protocol information.
#![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
}
}

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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 {}

View file

@ -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");

View file

@ -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
}