mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-26 05:26:34 +11:00
Document most items
This commit is contained in:
parent
54e0d5cb90
commit
3f150b4c8a
32 changed files with 1230 additions and 599 deletions
|
@ -1,4 +0,0 @@
|
||||||
# valence
|
|
||||||
Coming soon to a package manager near you!
|
|
||||||
|
|
||||||
(Valence is a WIP Rust library.)
|
|
|
@ -400,16 +400,16 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
#default_block_states
|
#default_block_states
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An enumeration of all block types.
|
/// An enumeration of all block kinds.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub enum BlockKind {
|
pub enum BlockKind {
|
||||||
#(#block_kind_variants,)*
|
#(#block_kind_variants,)*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockKind {
|
impl BlockKind {
|
||||||
/// Construct a block type from its snake_case name.
|
/// Construct a block kind from its snake_case name.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the given name is not valid.
|
/// Returns `None` if the name is invalid.
|
||||||
pub fn from_str(name: &str) -> Option<BlockKind> {
|
pub fn from_str(name: &str) -> Option<BlockKind> {
|
||||||
match name {
|
match name {
|
||||||
#block_kind_from_str_arms
|
#block_kind_from_str_arms
|
||||||
|
@ -417,19 +417,19 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the snake_case name of this block type.
|
/// Get the snake_case name of this block kind.
|
||||||
pub const fn to_str(self) -> &'static str {
|
pub const fn to_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
#block_kind_to_str_arms
|
#block_kind_to_str_arms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default block state for a given block type.
|
/// Returns the default block state for a given block kind.
|
||||||
pub const fn to_state(self) -> BlockState {
|
pub const fn to_state(self) -> BlockState {
|
||||||
BlockState::from_kind(self)
|
BlockState::from_kind(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a slice of all properties this block type has.
|
/// Returns a slice of all properties this block kind has.
|
||||||
pub const fn props(self) -> &'static [PropName] {
|
pub const fn props(self) -> &'static [PropName] {
|
||||||
match self {
|
match self {
|
||||||
#block_kind_props_arms
|
#block_kind_props_arms
|
||||||
|
@ -437,11 +437,11 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An array of all block types.
|
/// An array of all block kinds.
|
||||||
pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*];
|
pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default block type is `air`.
|
/// The default block kind is `air`.
|
||||||
impl Default for BlockKind {
|
impl Default for BlockKind {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Air
|
Self::Air
|
||||||
|
|
|
@ -9,10 +9,10 @@ use num::Integer;
|
||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||||
use valence::biome::Biome;
|
use valence::biome::Biome;
|
||||||
use valence::block::BlockState;
|
use valence::block::BlockState;
|
||||||
use valence::client::{ClientEvent, ClientId, GameMode, Hand};
|
use valence::client::{Event, ClientId, GameMode, Hand};
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::dimension::{Dimension, DimensionId};
|
use valence::dimension::{Dimension, DimensionId};
|
||||||
use valence::entity::meta::Pose;
|
use valence::entity::data::Pose;
|
||||||
use valence::entity::{EntityData, EntityId, EntityKind};
|
use valence::entity::{EntityData, EntityId, EntityKind};
|
||||||
use valence::server::{Server, SharedServer, ShutdownResult};
|
use valence::server::{Server, SharedServer, ShutdownResult};
|
||||||
use valence::text::{Color, TextFormat};
|
use valence::text::{Color, TextFormat};
|
||||||
|
@ -175,17 +175,15 @@ impl Config for Game {
|
||||||
|
|
||||||
while let Some(event) = client.pop_event() {
|
while let Some(event) = client.pop_event() {
|
||||||
match event {
|
match event {
|
||||||
ClientEvent::Digging(e) => {
|
Event::Digging { position, .. } => {
|
||||||
let pos = e.position;
|
if (0..SIZE_X as i32).contains(&position.x)
|
||||||
|
&& (0..SIZE_Z as i32).contains(&position.z)
|
||||||
if (0..SIZE_X as i32).contains(&pos.x)
|
&& position.y == BOARD_Y
|
||||||
&& (0..SIZE_Z as i32).contains(&pos.z)
|
|
||||||
&& pos.y == BOARD_Y
|
|
||||||
{
|
{
|
||||||
board[pos.x as usize + pos.z as usize * SIZE_X] = true;
|
board[position.x as usize + position.z as usize * SIZE_X] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientEvent::Movement { .. } => {
|
Event::Movement { .. } => {
|
||||||
if client.position().y <= 0.0 {
|
if client.position().y <= 0.0 {
|
||||||
client.teleport(spawn_pos, client.yaw(), client.pitch());
|
client.teleport(spawn_pos, client.yaw(), client.pitch());
|
||||||
}
|
}
|
||||||
|
@ -197,29 +195,29 @@ impl Config for Game {
|
||||||
player.set_pitch(client.pitch());
|
player.set_pitch(client.pitch());
|
||||||
player.set_on_ground(client.on_ground());
|
player.set_on_ground(client.on_ground());
|
||||||
}
|
}
|
||||||
ClientEvent::StartSneaking => {
|
Event::StartSneaking => {
|
||||||
if let EntityData::Player(e) = player.data_mut() {
|
if let EntityData::Player(e) = player.data_mut() {
|
||||||
e.set_crouching(true);
|
e.set_crouching(true);
|
||||||
e.set_pose(Pose::Sneaking);
|
e.set_pose(Pose::Sneaking);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientEvent::StopSneaking => {
|
Event::StopSneaking => {
|
||||||
if let EntityData::Player(e) = player.data_mut() {
|
if let EntityData::Player(e) = player.data_mut() {
|
||||||
e.set_pose(Pose::Standing);
|
e.set_pose(Pose::Standing);
|
||||||
e.set_crouching(false);
|
e.set_crouching(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientEvent::StartSprinting => {
|
Event::StartSprinting => {
|
||||||
if let EntityData::Player(e) = player.data_mut() {
|
if let EntityData::Player(e) = player.data_mut() {
|
||||||
e.set_sprinting(true);
|
e.set_sprinting(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientEvent::StopSprinting => {
|
Event::StopSprinting => {
|
||||||
if let EntityData::Player(e) = player.data_mut() {
|
if let EntityData::Player(e) = player.data_mut() {
|
||||||
e.set_sprinting(false);
|
e.set_sprinting(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientEvent::ArmSwing(hand) => {
|
Event::ArmSwing(hand) => {
|
||||||
if let EntityData::Player(e) = player.data_mut() {
|
if let EntityData::Player(e) = player.data_mut() {
|
||||||
match hand {
|
match hand {
|
||||||
Hand::Main => e.trigger_swing_main_arm(),
|
Hand::Main => e.trigger_swing_main_arm(),
|
||||||
|
|
84
src/biome.rs
84
src/biome.rs
|
@ -1,19 +1,23 @@
|
||||||
|
//! Biome definitions.
|
||||||
|
|
||||||
use crate::ident;
|
use crate::ident;
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
|
use crate::protocol_inner::packets::play::s2c::Biome as BiomeRegistryBiome;
|
||||||
|
|
||||||
/// Identifies a particular [`Biome`].
|
/// Identifies a particular [`Biome`] on the server.
|
||||||
///
|
///
|
||||||
/// Biome IDs are always valid and are cheap to copy and store.
|
/// The default biome ID refers to the first biome added in the server's
|
||||||
|
/// [configuration](crate::config::Config).
|
||||||
|
///
|
||||||
|
/// To obtain biome IDs for other biomes, call
|
||||||
|
/// [`biomes`](crate::server::SharedServer::biomes).
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct BiomeId(pub(crate) u16);
|
pub struct BiomeId(pub(crate) u16);
|
||||||
|
|
||||||
impl BiomeId {
|
|
||||||
pub fn to_index(self) -> usize {
|
|
||||||
self.0 as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains the configuration for a biome.
|
/// Contains the configuration for a biome.
|
||||||
|
///
|
||||||
|
/// Biomes are registered once at startup through
|
||||||
|
/// [`biomes`](crate::config::Config::biomes).
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Biome {
|
pub struct Biome {
|
||||||
/// The unique name for this biome. The name can be
|
/// The unique name for this biome. The name can be
|
||||||
|
@ -42,6 +46,70 @@ pub struct Biome {
|
||||||
// * temperature_modifier
|
// * temperature_modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Biome {
|
||||||
|
pub(crate) fn to_biome_registry_item(&self, id: i32) -> BiomeRegistryBiome {
|
||||||
|
use crate::protocol_inner::packets::play::s2c::{
|
||||||
|
BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, BiomeParticle,
|
||||||
|
BiomeParticleOptions, BiomeProperty,
|
||||||
|
};
|
||||||
|
|
||||||
|
BiomeRegistryBiome {
|
||||||
|
name: self.name.clone(),
|
||||||
|
id,
|
||||||
|
element: BiomeProperty {
|
||||||
|
precipitation: match self.precipitation {
|
||||||
|
BiomePrecipitation::Rain => "rain",
|
||||||
|
BiomePrecipitation::Snow => "snow",
|
||||||
|
BiomePrecipitation::None => "none",
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
depth: 0.125,
|
||||||
|
temperature: 0.8,
|
||||||
|
scale: 0.05,
|
||||||
|
downfall: 0.4,
|
||||||
|
category: "none".into(),
|
||||||
|
temperature_modifier: None,
|
||||||
|
effects: BiomeEffects {
|
||||||
|
sky_color: self.sky_color as i32,
|
||||||
|
water_fog_color: self.water_fog_color as i32,
|
||||||
|
fog_color: self.fog_color as i32,
|
||||||
|
water_color: self.water_color as i32,
|
||||||
|
foliage_color: self.foliage_color.map(|x| x as i32),
|
||||||
|
grass_color: self.grass_color.map(|x| x as i32),
|
||||||
|
grass_color_modifier: match self.grass_color_modifier {
|
||||||
|
BiomeGrassColorModifier::Swamp => Some("swamp".into()),
|
||||||
|
BiomeGrassColorModifier::DarkForest => Some("dark_forest".into()),
|
||||||
|
BiomeGrassColorModifier::None => None,
|
||||||
|
},
|
||||||
|
music: self.music.as_ref().map(|bm| BiomeMusic {
|
||||||
|
replace_current_music: bm.replace_current_music,
|
||||||
|
sound: bm.sound.clone(),
|
||||||
|
max_delay: bm.max_delay,
|
||||||
|
min_delay: bm.min_delay,
|
||||||
|
}),
|
||||||
|
ambient_sound: self.ambient_sound.clone(),
|
||||||
|
additions_sound: self.additions_sound.as_ref().map(|a| BiomeAdditionsSound {
|
||||||
|
sound: a.sound.clone(),
|
||||||
|
tick_chance: a.tick_chance,
|
||||||
|
}),
|
||||||
|
mood_sound: self.mood_sound.as_ref().map(|m| BiomeMoodSound {
|
||||||
|
sound: m.sound.clone(),
|
||||||
|
tick_delay: m.tick_delay,
|
||||||
|
offset: m.offset,
|
||||||
|
block_search_extent: m.block_search_extent,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
particle: self.particle.as_ref().map(|p| BiomeParticle {
|
||||||
|
probability: p.probability,
|
||||||
|
options: BiomeParticleOptions {
|
||||||
|
kind: p.kind.clone(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Biome {
|
impl Default for Biome {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::io::{Read, Write};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
pub use crate::block_pos::BlockPos;
|
pub use crate::block_pos::BlockPos;
|
||||||
use crate::protocol::{Decode, Encode, VarInt};
|
use crate::protocol_inner::{Decode, Encode, VarInt};
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/block.rs"));
|
include!(concat!(env!("OUT_DIR"), "/block.rs"));
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ impl fmt::Debug for BlockState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for BlockState {
|
impl Display for BlockState {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt_block_state(*self, f)
|
fmt_block_state(*self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
struct KeyVal<'a>(&'a str, &'a str);
|
struct KeyVal<'a>(&'a str, &'a str);
|
||||||
|
|
||||||
impl<'a> fmt::Debug for KeyVal<'a> {
|
impl<'a> fmt::Debug for KeyVal<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}={}", self.0, self.1)
|
write!(f, "{}={}", self.0, self.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ use std::io::{Read, Write};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use vek::Vec3;
|
use vek::Vec3;
|
||||||
|
|
||||||
use crate::protocol::{Decode, Encode};
|
use crate::protocol_inner::{Decode, Encode};
|
||||||
|
|
||||||
|
/// Represents an absolute block position in a world.
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct BlockPos {
|
pub struct BlockPos {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
|
@ -13,10 +14,12 @@ pub struct BlockPos {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockPos {
|
impl BlockPos {
|
||||||
|
/// Constructs a new block position.
|
||||||
pub const fn new(x: i32, y: i32, z: i32) -> Self {
|
pub const fn new(x: i32, y: i32, z: i32) -> Self {
|
||||||
Self { x, y, z }
|
Self { x, y, z }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the block position a point is contained within.
|
||||||
pub fn at(pos: impl Into<Vec3<f64>>) -> Self {
|
pub fn at(pos: impl Into<Vec3<f64>>) -> Self {
|
||||||
pos.into().floor().as_::<i32>().into()
|
pos.into().floor().as_::<i32>().into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
//! The [bounding volume hierarchy][bvh] contained in the [`SpatialIndex`]
|
||||||
|
//!
|
||||||
|
//! [bvh]: https://en.wikipedia.org/wiki/Bounding_volume_hierarchy
|
||||||
|
//! [`SpatialIndex`]: crate::spatial_index::SpatialIndex
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use approx::relative_eq;
|
use approx::relative_eq;
|
||||||
|
|
59
src/chunk.rs
59
src/chunk.rs
|
@ -14,13 +14,14 @@ use crate::block::BlockState;
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
pub use crate::chunk_pos::ChunkPos;
|
pub use crate::chunk_pos::ChunkPos;
|
||||||
use crate::dimension::DimensionId;
|
use crate::dimension::DimensionId;
|
||||||
use crate::protocol::packets::play::s2c::{
|
use crate::protocol_inner::packets::play::s2c::{
|
||||||
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
|
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
|
||||||
};
|
};
|
||||||
use crate::protocol::{Encode, Nbt, VarInt, VarLong};
|
use crate::protocol_inner::{Encode, Nbt, VarInt, VarLong};
|
||||||
use crate::server::SharedServer;
|
use crate::server::SharedServer;
|
||||||
use crate::Ticks;
|
use crate::Ticks;
|
||||||
|
|
||||||
|
/// A container for all [`Chunks`]s in a [`World`](crate::world::World).
|
||||||
pub struct Chunks {
|
pub struct Chunks {
|
||||||
chunks: HashMap<ChunkPos, Chunk>,
|
chunks: HashMap<ChunkPos, Chunk>,
|
||||||
server: SharedServer,
|
server: SharedServer,
|
||||||
|
@ -36,6 +37,16 @@ impl Chunks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an empty chunk at the provided position and returns a mutable
|
||||||
|
/// refernce to it.
|
||||||
|
///
|
||||||
|
/// If a chunk at the position already exists, then the old chunk
|
||||||
|
/// is overwritten.
|
||||||
|
///
|
||||||
|
/// **Note**: For the vanilla Minecraft client to see a chunk, all chunks
|
||||||
|
/// adjacent to it must also be loaded. It is also important that clients
|
||||||
|
/// are not spawned within unloaded chunks via
|
||||||
|
/// [`spawn`](crate::client::Client::spawn).
|
||||||
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk {
|
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk {
|
||||||
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
|
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
|
||||||
let chunk = Chunk::new(section_count, self.server.current_tick());
|
let chunk = Chunk::new(section_count, self.server.current_tick());
|
||||||
|
@ -49,42 +60,69 @@ impl Chunks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, pos: ChunkPos) -> bool {
|
/// Removes a chunk at the provided position.
|
||||||
self.chunks.remove(&pos).is_some()
|
///
|
||||||
|
/// If a chunk exists at the position, then it is deleted and `true` is
|
||||||
|
/// returned. Otherwise, `false` is returned.
|
||||||
|
pub fn delete(&mut self, pos: impl Into<ChunkPos>) -> bool {
|
||||||
|
self.chunks.remove(&pos.into()).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of loaded chunks.
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.chunks.len()
|
self.chunks.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a shared reference to the chunk at the provided position.
|
||||||
|
///
|
||||||
|
/// If there is no chunk at the position, then `None` is returned.
|
||||||
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> {
|
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> {
|
||||||
self.chunks.get(&pos.into())
|
self.chunks.get(&pos.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets an exclusive reference to the chunk at the provided position.
|
||||||
|
///
|
||||||
|
/// If there is no chunk at the position, then `None` is returned.
|
||||||
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> {
|
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> {
|
||||||
self.chunks.get_mut(&pos.into())
|
self.chunks.get_mut(&pos.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes all chunks.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.chunks.clear();
|
self.chunks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an immutable iterator over all chunks in the world in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
|
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
|
||||||
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable iterator over all chunks in the world in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
|
||||||
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel immutable iterator over all chunks in the world in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
|
||||||
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel mutable iterator over all chunks in the world in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
|
||||||
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the block state at a position.
|
||||||
|
///
|
||||||
|
/// If the position is not inside of a chunk, then `None` is returned.
|
||||||
|
///
|
||||||
|
/// Note: if you need to get a large number of blocks, it may be more
|
||||||
|
/// efficient to read from the chunks directly with
|
||||||
|
/// [`Chunk::get_block_state`].
|
||||||
pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> {
|
pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> {
|
||||||
let pos = pos.into();
|
let pos = pos.into();
|
||||||
let chunk_pos = ChunkPos::from(pos);
|
let chunk_pos = ChunkPos::from(pos);
|
||||||
|
@ -106,6 +144,14 @@ impl Chunks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the block state at a position.
|
||||||
|
///
|
||||||
|
/// If the position is inside of a chunk, then `true` is returned.
|
||||||
|
/// Otherwise, `false` is returned.
|
||||||
|
///
|
||||||
|
/// Note: if you need to set a large number of blocks, it may be more
|
||||||
|
/// efficient write to the chunks directly with
|
||||||
|
/// [`Chunk::set_block_state`].
|
||||||
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
|
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
|
||||||
let pos = pos.into();
|
let pos = pos.into();
|
||||||
let chunk_pos = ChunkPos::from(pos);
|
let chunk_pos = ChunkPos::from(pos);
|
||||||
|
@ -130,6 +176,11 @@ impl Chunks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A chunk is a 16x16-block segment of a world with a height determined by the
|
||||||
|
/// [`Dimension`](crate::dimension::Dimension) of the world.
|
||||||
|
///
|
||||||
|
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
|
||||||
|
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
sections: Box<[ChunkSection]>,
|
sections: Box<[ChunkSection]>,
|
||||||
// TODO block_entities: HashMap<u32, BlockEntity>,
|
// TODO block_entities: HashMap<u32, BlockEntity>,
|
||||||
|
|
|
@ -10,10 +10,13 @@ pub struct ChunkPos {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkPos {
|
impl ChunkPos {
|
||||||
|
/// Constructs a new chunk position.
|
||||||
pub const fn new(x: i32, z: i32) -> Self {
|
pub const fn new(x: i32, z: i32) -> Self {
|
||||||
Self { x, z }
|
Self { x, z }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes an absolute position and returns the chunk position
|
||||||
|
/// containing the point.
|
||||||
pub fn at(x: f64, z: f64) -> Self {
|
pub fn at(x: f64, z: f64) -> Self {
|
||||||
Self::new((x / 16.0).floor() as i32, (z / 16.0).floor() as i32)
|
Self::new((x / 16.0).floor() as i32, (z / 16.0).floor() as i32)
|
||||||
}
|
}
|
||||||
|
|
345
src/client.rs
345
src/client.rs
|
@ -11,29 +11,28 @@ use rayon::iter::ParallelIterator;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vek::Vec3;
|
use vek::Vec3;
|
||||||
|
|
||||||
use crate::biome::{Biome, BiomeGrassColorModifier, BiomePrecipitation};
|
use crate::biome::Biome;
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::chunk_pos::ChunkPos;
|
use crate::chunk_pos::ChunkPos;
|
||||||
use crate::dimension::{Dimension, DimensionEffects, DimensionId};
|
use crate::dimension::DimensionId;
|
||||||
use crate::entity::data::Player;
|
use crate::entity::types::Player;
|
||||||
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
|
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
|
||||||
use crate::player_textures::SignedPlayerTextures;
|
use crate::player_textures::SignedPlayerTextures;
|
||||||
use crate::protocol::packets::play::c2s::{
|
use crate::protocol_inner::packets::play::c2s::{
|
||||||
C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId,
|
C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId,
|
||||||
};
|
};
|
||||||
pub use crate::protocol::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes;
|
pub use crate::protocol_inner::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes;
|
||||||
use crate::protocol::packets::play::s2c::{
|
use crate::protocol_inner::packets::play::s2c::{
|
||||||
Animate, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound,
|
Animate, BiomeRegistry, BlockChangeAck, ChatType, ChatTypeChat, ChatTypeNarration,
|
||||||
BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, BlockChangeAck,
|
ChatTypeRegistry, ChatTypeRegistryEntry, ClearTitles, DimensionTypeRegistry,
|
||||||
ChatType, ChatTypeChat, ChatTypeNarration, ChatTypeRegistry, ChatTypeRegistryEntry,
|
DimensionTypeRegistryEntry, Disconnect, EntityEvent, ForgetLevelChunk, GameEvent,
|
||||||
ClearTitles, DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect,
|
GameEventReason, KeepAlive, Login, MoveEntityPosition, MoveEntityPositionAndRotation,
|
||||||
EntityEvent, ForgetLevelChunk, GameEvent, GameEventReason, KeepAlive, Login,
|
MoveEntityRotation, PlayerPosition, PlayerPositionFlags, RegistryCodec, RemoveEntities,
|
||||||
MoveEntityPosition, MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition,
|
Respawn, RotateHead, S2cPlayPacket, SetChunkCacheCenter, SetChunkCacheRadius,
|
||||||
PlayerPositionFlags, RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket,
|
SetEntityMetadata, SetEntityMotion, SetSubtitleText, SetTitleText, SpawnPosition, SystemChat,
|
||||||
SetChunkCacheCenter, SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SetSubtitleText,
|
TeleportEntity, ENTITY_EVENT_MAX_BOUND,
|
||||||
SetTitleText, SpawnPosition, SystemChat, TeleportEntity, ENTITY_EVENT_MAX_BOUND,
|
|
||||||
};
|
};
|
||||||
use crate::protocol::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt};
|
use crate::protocol_inner::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt};
|
||||||
use crate::server::{C2sPacketChannels, NewClientData, SharedServer};
|
use crate::server::{C2sPacketChannels, NewClientData, SharedServer};
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
|
@ -41,6 +40,11 @@ use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||||
use crate::world::{WorldId, Worlds};
|
use crate::world::{WorldId, Worlds};
|
||||||
use crate::{ident, Ticks, LIBRARY_NAMESPACE};
|
use crate::{ident, Ticks, LIBRARY_NAMESPACE};
|
||||||
|
|
||||||
|
/// A container for all [`Client`]s on a [`Server`](crate::server::Server).
|
||||||
|
///
|
||||||
|
/// New clients are automatically inserted into this container but
|
||||||
|
/// are not automatically deleted. It is your responsibility to delete them once
|
||||||
|
/// they disconnect. This can be checked with [`Client::is_disconnected`].
|
||||||
pub struct Clients {
|
pub struct Clients {
|
||||||
sm: SlotMap<Client>,
|
sm: SlotMap<Client>,
|
||||||
}
|
}
|
||||||
|
@ -55,51 +59,103 @@ impl Clients {
|
||||||
(ClientId(id), client)
|
(ClientId(id), client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes a client from the server.
|
||||||
|
///
|
||||||
|
/// If the given client ID is valid, `true` is returned and the client is
|
||||||
|
/// deleted. Otherwise, `false` is returned and the function has no effect.
|
||||||
pub fn delete(&mut self, client: ClientId) -> bool {
|
pub fn delete(&mut self, client: ClientId) -> bool {
|
||||||
self.sm.remove(client.0).is_some()
|
self.sm.remove(client.0).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes all clients from the server (as if by [`Self::delete`]) for
|
||||||
|
/// which `f` returns `true`.
|
||||||
|
///
|
||||||
|
/// All clients are visited in an unspecified order.
|
||||||
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) {
|
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) {
|
||||||
self.sm.retain(|k, v| f(ClientId(k), v))
|
self.sm.retain(|k, v| f(ClientId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of clients on the server. This includes clients
|
||||||
|
/// which may be disconnected.
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.sm.len()
|
self.sm.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a shared reference to the client with the given ID. If
|
||||||
|
/// the ID is invalid, then `None` is returned.
|
||||||
pub fn get(&self, client: ClientId) -> Option<&Client> {
|
pub fn get(&self, client: ClientId) -> Option<&Client> {
|
||||||
self.sm.get(client.0)
|
self.sm.get(client.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an exclusive reference to the client with the given ID. If the
|
||||||
|
/// ID is invalid, then `None` is returned.
|
||||||
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> {
|
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> {
|
||||||
self.sm.get_mut(client.0)
|
self.sm.get_mut(client.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an immutable iterator over all clients on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ {
|
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ {
|
||||||
self.sm.iter().map(|(k, v)| (ClientId(k), v))
|
self.sm.iter().map(|(k, v)| (ClientId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable iterator over all clients on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
|
||||||
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
|
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel immutable iterator over all clients on the server in
|
||||||
|
/// an unspecified order.
|
||||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
|
||||||
self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
|
self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel mutable iterator over all clients on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
|
||||||
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
|
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A key for a [`Client`] on the server.
|
||||||
|
///
|
||||||
|
/// Client IDs are either _valid_ or _invalid_. Valid client IDs point to
|
||||||
|
/// clients that have not been deleted, while invalid IDs point to those that
|
||||||
|
/// have. Once an ID becomes invalid, it will never become valid again.
|
||||||
|
///
|
||||||
|
/// The [`Ord`] instance on this type is correct but otherwise unspecified. This
|
||||||
|
/// is useful for storing IDs in containers such as
|
||||||
|
/// [`BTreeMap`](std::collections::BTreeMap).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
pub struct ClientId(Key);
|
pub struct ClientId(Key);
|
||||||
|
|
||||||
impl ClientId {
|
impl ClientId {
|
||||||
|
/// The value of the default client ID which is always invalid.
|
||||||
pub const NULL: Self = Self(Key::NULL);
|
pub const NULL: Self = Self(Key::NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a client connected to the server after logging in.
|
/// Represents a remote connection to a client after successfully logging in.
|
||||||
|
///
|
||||||
|
/// Much like an [`Entity`], clients posess a location, rotation, and UUID.
|
||||||
|
/// Clients are handled separately from entities and are partially
|
||||||
|
/// controlled by the library.
|
||||||
|
///
|
||||||
|
/// By default, clients have no influence over the worlds they reside in. They
|
||||||
|
/// cannot break blocks, hurt entities, or see other clients. Interactions with
|
||||||
|
/// the server must be handled explicitly with [`Self::pop_event`].
|
||||||
|
///
|
||||||
|
/// Additionally, clients posess [`Player`] entity data which is only visible to
|
||||||
|
/// themselves. This can be accessed with [`Self::player`] and
|
||||||
|
/// [`Self::player_mut`].
|
||||||
|
///
|
||||||
|
/// # The Difference Between a "Client" and a "Player"
|
||||||
|
///
|
||||||
|
/// Normally in Minecraft, players and clients are one and the same. Players are
|
||||||
|
/// simply a special type of entity which is backed by a remote connection.
|
||||||
|
///
|
||||||
|
/// In Valence however, clients and players have been decoupled. This separation
|
||||||
|
/// was done primarily to enable multithreaded client updates.
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
/// Setting this to `None` disconnects the client.
|
/// Setting this to `None` disconnects the client.
|
||||||
send: SendOpt,
|
send: SendOpt,
|
||||||
|
@ -125,7 +181,7 @@ pub struct Client {
|
||||||
spawn_position: BlockPos,
|
spawn_position: BlockPos,
|
||||||
spawn_position_yaw: f32,
|
spawn_position_yaw: f32,
|
||||||
death_location: Option<(DimensionId, BlockPos)>,
|
death_location: Option<(DimensionId, BlockPos)>,
|
||||||
events: VecDeque<ClientEvent>,
|
events: VecDeque<Event>,
|
||||||
/// The ID of the last keepalive sent.
|
/// The ID of the last keepalive sent.
|
||||||
last_keepalive_id: i64,
|
last_keepalive_id: i64,
|
||||||
new_max_view_distance: u8,
|
new_max_view_distance: u8,
|
||||||
|
@ -207,42 +263,58 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the tick that this client was created.
|
||||||
pub fn created_tick(&self) -> Ticks {
|
pub fn created_tick(&self) -> Ticks {
|
||||||
self.created_tick
|
self.created_tick
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the client's UUID.
|
||||||
pub fn uuid(&self) -> Uuid {
|
pub fn uuid(&self) -> Uuid {
|
||||||
self.uuid
|
self.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the username of this client, which is always valid.
|
||||||
pub fn username(&self) -> &str {
|
pub fn username(&self) -> &str {
|
||||||
&self.username
|
&self.username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the player textures of this client. If the client does not have
|
||||||
|
/// a skin, then `None` is returned.
|
||||||
pub fn textures(&self) -> Option<&SignedPlayerTextures> {
|
pub fn textures(&self) -> Option<&SignedPlayerTextures> {
|
||||||
self.textures.as_ref()
|
self.textures.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the world this client is located in.
|
||||||
pub fn world(&self) -> WorldId {
|
pub fn world(&self) -> WorldId {
|
||||||
self.world
|
self.world
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes the world this client is located in.
|
||||||
|
///
|
||||||
|
/// The given [`WorldId`] must be valid. Otherwise, the client is
|
||||||
|
/// disconnected.
|
||||||
pub fn spawn(&mut self, world: WorldId) {
|
pub fn spawn(&mut self, world: WorldId) {
|
||||||
self.world = world;
|
self.world = world;
|
||||||
self.flags.set_spawn(true);
|
self.flags.set_spawn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a system message to the player.
|
/// Sends a system message to the player which is visible in the chat.
|
||||||
pub fn send_message(&mut self, msg: impl Into<Text>) {
|
pub fn send_message(&mut self, msg: impl Into<Text>) {
|
||||||
// We buffer messages because weird things happen if we send them before the
|
// We buffer messages because weird things happen if we send them before the
|
||||||
// login packet.
|
// login packet.
|
||||||
self.msgs_to_send.push(msg.into());
|
self.msgs_to_send.push(msg.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the absolute position of this client in the world it is located
|
||||||
|
/// in.
|
||||||
pub fn position(&self) -> Vec3<f64> {
|
pub fn position(&self) -> Vec3<f64> {
|
||||||
self.new_position
|
self.new_position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes the position and rotation of this client in the world it is
|
||||||
|
/// located in.
|
||||||
|
///
|
||||||
|
/// If you want to change the client's world, use [`Self::spawn`].
|
||||||
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
|
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
|
||||||
self.new_position = pos.into();
|
self.new_position = pos.into();
|
||||||
|
|
||||||
|
@ -265,22 +337,24 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets this client's yaw.
|
||||||
pub fn yaw(&self) -> f32 {
|
pub fn yaw(&self) -> f32 {
|
||||||
self.yaw
|
self.yaw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets this client's pitch.
|
||||||
pub fn pitch(&self) -> f32 {
|
pub fn pitch(&self) -> f32 {
|
||||||
self.pitch
|
self.pitch
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the spawn position. The client will see regular compasses point at
|
/// Gets the spawn position. The client will see `minecraft:compass` items
|
||||||
/// the returned position.
|
/// point at the returned position.
|
||||||
pub fn spawn_position(&self) -> BlockPos {
|
pub fn spawn_position(&self) -> BlockPos {
|
||||||
self.spawn_position
|
self.spawn_position
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the spawn position. The client will see regular compasses point at
|
/// Sets the spawn position. The client will see `minecraft:compass` items
|
||||||
/// the provided position.
|
/// point at the provided position.
|
||||||
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
|
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
|
||||||
let pos = pos.into();
|
let pos = pos.into();
|
||||||
if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw {
|
if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw {
|
||||||
|
@ -290,18 +364,20 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the last death location. The client will see recovery compasses
|
/// Gets the last death location of this client. The client will see
|
||||||
/// point at the returned position. If the client's current dimension
|
/// `minecraft:recovery_compass` items point at the returned position.
|
||||||
/// differs from the returned dimension or the location is `None` then the
|
/// If the client's current dimension differs from the returned
|
||||||
/// compass will spin randomly.
|
/// dimension or the location is `None` then the compass will spin
|
||||||
|
/// randomly.
|
||||||
pub fn death_location(&self) -> Option<(DimensionId, BlockPos)> {
|
pub fn death_location(&self) -> Option<(DimensionId, BlockPos)> {
|
||||||
self.death_location
|
self.death_location
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the last death location. The client will see recovery compasses
|
/// Sets the last death location. The client will see
|
||||||
/// point at the provided position. If the client's current dimension
|
/// `minecraft:recovery_compass` items point at the provided position.
|
||||||
/// differs from the provided dimension or the location is `None` then the
|
/// If the client's current dimension differs from the provided
|
||||||
/// compass will spin randomly.
|
/// dimension or the location is `None` then the compass will spin
|
||||||
|
/// randomly.
|
||||||
///
|
///
|
||||||
/// Changes to the last death location take effect when the client
|
/// Changes to the last death location take effect when the client
|
||||||
/// (re)spawns.
|
/// (re)spawns.
|
||||||
|
@ -309,14 +385,22 @@ impl Client {
|
||||||
self.death_location = location;
|
self.death_location = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the client's game mode.
|
||||||
pub fn game_mode(&self) -> GameMode {
|
pub fn game_mode(&self) -> GameMode {
|
||||||
self.new_game_mode
|
self.new_game_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_game_mode(&mut self, new_game_mode: GameMode) {
|
/// Sets the client's game mode.
|
||||||
self.new_game_mode = new_game_mode;
|
pub fn set_game_mode(&mut self, game_mode: GameMode) {
|
||||||
|
self.new_game_mode = game_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the title this client sees.
|
||||||
|
///
|
||||||
|
/// A title is a large piece of text displayed in the center of the screen
|
||||||
|
/// which may also include a subtitle underneath it. The title
|
||||||
|
/// can be configured to fade in and out using the
|
||||||
|
/// [`TitleAnimationTimes`] struct.
|
||||||
pub fn set_title(
|
pub fn set_title(
|
||||||
&mut self,
|
&mut self,
|
||||||
title: impl Into<Text>,
|
title: impl Into<Text>,
|
||||||
|
@ -339,19 +423,30 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the current title from the client's screen.
|
||||||
pub fn clear_title(&mut self) {
|
pub fn clear_title(&mut self) {
|
||||||
self.send_packet(ClearTitles { reset: true });
|
self.send_packet(ClearTitles { reset: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets if the client is on the ground, as determined by the client.
|
||||||
pub fn on_ground(&self) -> bool {
|
pub fn on_ground(&self) -> bool {
|
||||||
self.flags.on_ground()
|
self.flags.on_ground()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether or not the client is connected to the server.
|
||||||
|
///
|
||||||
|
/// A disconnected client object will never become reconnected. It is your
|
||||||
|
/// responsibility to remove disconnected clients from the [`Clients`]
|
||||||
|
/// container.
|
||||||
pub fn is_disconnected(&self) -> bool {
|
pub fn is_disconnected(&self) -> bool {
|
||||||
self.send.is_none()
|
self.send.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_event(&mut self) -> Option<ClientEvent> {
|
/// Removes an [`Event`] from the queue.
|
||||||
|
///
|
||||||
|
/// Any remaining client events not popped are deleted at the end of the
|
||||||
|
/// current tick.
|
||||||
|
pub fn pop_event(&mut self) -> Option<Event> {
|
||||||
self.events.pop_front()
|
self.events.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,28 +458,44 @@ impl Client {
|
||||||
.min(self.max_view_distance())
|
.min(self.max_view_distance())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the maximum view distance. The client will not be able to see
|
||||||
|
/// chunks and entities past this distance.
|
||||||
|
///
|
||||||
|
/// The value returned is measured in chunks.
|
||||||
pub fn max_view_distance(&self) -> u8 {
|
pub fn max_view_distance(&self) -> u8 {
|
||||||
self.new_max_view_distance
|
self.new_max_view_distance
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The new view distance is clamped to `2..=32`.
|
/// Sets the maximum view distance. The client will not be able to see
|
||||||
|
/// chunks and entities past this distance.
|
||||||
|
///
|
||||||
|
/// The new view distance is measured in chunks and is clamped to `2..=32`.
|
||||||
pub fn set_max_view_distance(&mut self, dist: u8) {
|
pub fn set_max_view_distance(&mut self, dist: u8) {
|
||||||
self.new_max_view_distance = dist.clamp(2, 32);
|
self.new_max_view_distance = dist.clamp(2, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Must be set on the same tick the client joins the game.
|
/// Enables hardcore mode. This changes the design of the client's hearts.
|
||||||
|
///
|
||||||
|
/// To have any visible effect, this function must be called on the same
|
||||||
|
/// tick the client joins the server.
|
||||||
pub fn set_hardcore(&mut self, hardcore: bool) {
|
pub fn set_hardcore(&mut self, hardcore: bool) {
|
||||||
self.flags.set_hardcore(hardcore);
|
self.flags.set_hardcore(hardcore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets if hardcore mode is enabled.
|
||||||
pub fn is_hardcore(&mut self) -> bool {
|
pub fn is_hardcore(&mut self) -> bool {
|
||||||
self.flags.hardcore()
|
self.flags.hardcore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the client's current settings.
|
||||||
pub fn settings(&self) -> Option<&Settings> {
|
pub fn settings(&self) -> Option<&Settings> {
|
||||||
self.settings.as_ref()
|
self.settings.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disconnects this client from the server with the provided reason. This
|
||||||
|
/// has no effect if the client is already disconnected.
|
||||||
|
///
|
||||||
|
/// All future calls to [`Self::is_disconnected`] will return `true`.
|
||||||
pub fn disconnect(&mut self, reason: impl Into<Text>) {
|
pub fn disconnect(&mut self, reason: impl Into<Text>) {
|
||||||
if self.send.is_some() {
|
if self.send.is_some() {
|
||||||
let txt = reason.into();
|
let txt = reason.into();
|
||||||
|
@ -396,6 +507,8 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like [`Self::disconnect`], but no reason for the disconnect is
|
||||||
|
/// displayed.
|
||||||
pub fn disconnect_no_reason(&mut self) {
|
pub fn disconnect_no_reason(&mut self) {
|
||||||
if self.send.is_some() {
|
if self.send.is_some() {
|
||||||
log::info!("disconnecting client '{}'", self.username);
|
log::info!("disconnecting client '{}'", self.username);
|
||||||
|
@ -403,11 +516,15 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data(&self) -> &Player {
|
/// Returns an immutable reference to the client's own [`Player`] data.
|
||||||
|
pub fn player(&self) -> &Player {
|
||||||
&self.player_data
|
&self.player_data
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_mut(&mut self) -> &mut Player {
|
/// Returns a mutable reference to the client's own [`Player`] data.
|
||||||
|
///
|
||||||
|
/// Changes made to this data is only visible to this client.
|
||||||
|
pub fn player_mut(&mut self) -> &mut Player {
|
||||||
&mut self.player_data
|
&mut self.player_data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +559,7 @@ impl Client {
|
||||||
if client.pending_teleports == 0 {
|
if client.pending_teleports == 0 {
|
||||||
// TODO: validate movement using swept AABB collision with the blocks.
|
// TODO: validate movement using swept AABB collision with the blocks.
|
||||||
// TODO: validate that the client is actually inside/outside the vehicle?
|
// TODO: validate that the client is actually inside/outside the vehicle?
|
||||||
let event = ClientEvent::Movement {
|
let event = Event::Movement {
|
||||||
position: client.new_position,
|
position: client.new_position,
|
||||||
yaw: client.yaw,
|
yaw: client.yaw,
|
||||||
pitch: client.pitch,
|
pitch: client.pitch,
|
||||||
|
@ -485,7 +602,7 @@ impl Client {
|
||||||
C2sPlayPacket::BlockEntityTagQuery(_) => {}
|
C2sPlayPacket::BlockEntityTagQuery(_) => {}
|
||||||
C2sPlayPacket::ChangeDifficulty(_) => {}
|
C2sPlayPacket::ChangeDifficulty(_) => {}
|
||||||
C2sPlayPacket::ChatCommand(_) => {}
|
C2sPlayPacket::ChatCommand(_) => {}
|
||||||
C2sPlayPacket::Chat(p) => self.events.push_back(ClientEvent::ChatMessage {
|
C2sPlayPacket::Chat(p) => self.events.push_back(Event::ChatMessage {
|
||||||
message: p.message.0,
|
message: p.message.0,
|
||||||
timestamp: Duration::from_millis(p.timestamp),
|
timestamp: Duration::from_millis(p.timestamp),
|
||||||
}),
|
}),
|
||||||
|
@ -502,7 +619,7 @@ impl Client {
|
||||||
allow_server_listings: p.allow_server_listings,
|
allow_server_listings: p.allow_server_listings,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.events.push_back(ClientEvent::SettingsChanged(old));
|
self.events.push_back(Event::SettingsChanged(old));
|
||||||
}
|
}
|
||||||
C2sPlayPacket::CommandSuggestion(_) => {}
|
C2sPlayPacket::CommandSuggestion(_) => {}
|
||||||
C2sPlayPacket::ContainerButtonClick(_) => {}
|
C2sPlayPacket::ContainerButtonClick(_) => {}
|
||||||
|
@ -515,14 +632,14 @@ impl Client {
|
||||||
// TODO: verify that the client has line of sight to the targeted entity and
|
// TODO: verify that the client has line of sight to the targeted entity and
|
||||||
// that the distance is <=4 blocks.
|
// that the distance is <=4 blocks.
|
||||||
|
|
||||||
self.events.push_back(ClientEvent::InteractWithEntity {
|
self.events.push_back(Event::InteractWithEntity {
|
||||||
id,
|
id,
|
||||||
sneaking: p.sneaking,
|
sneaking: p.sneaking,
|
||||||
kind: match p.kind {
|
kind: match p.kind {
|
||||||
InteractKind::Interact(hand) => InteractWithEntity::Interact(hand),
|
InteractKind::Interact(hand) => InteractWithEntityKind::Interact(hand),
|
||||||
InteractKind::Attack => InteractWithEntity::Attack,
|
InteractKind::Attack => InteractWithEntityKind::Attack,
|
||||||
InteractKind::InteractAt((target, hand)) => {
|
InteractKind::InteractAt((target, hand)) => {
|
||||||
InteractWithEntity::InteractAt { target, hand }
|
InteractWithEntityKind::InteractAt { target, hand }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -575,7 +692,7 @@ impl Client {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
C2sPlayPacket::PaddleBoat(p) => {
|
C2sPlayPacket::PaddleBoat(p) => {
|
||||||
self.events.push_back(ClientEvent::SteerBoat {
|
self.events.push_back(Event::SteerBoat {
|
||||||
left_paddle_turning: p.left_paddle_turning,
|
left_paddle_turning: p.left_paddle_turning,
|
||||||
right_paddle_turning: p.right_paddle_turning,
|
right_paddle_turning: p.right_paddle_turning,
|
||||||
});
|
});
|
||||||
|
@ -592,21 +709,21 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.events.push_back(match p.status {
|
self.events.push_back(match p.status {
|
||||||
DiggingStatus::StartedDigging => ClientEvent::Digging(Digging {
|
DiggingStatus::StartedDigging => Event::Digging {
|
||||||
status: event::DiggingStatus::Start,
|
status: event::DiggingStatus::Start,
|
||||||
position: p.location,
|
position: p.location,
|
||||||
face: p.face,
|
face: p.face,
|
||||||
}),
|
},
|
||||||
DiggingStatus::CancelledDigging => ClientEvent::Digging(Digging {
|
DiggingStatus::CancelledDigging => Event::Digging {
|
||||||
status: event::DiggingStatus::Cancel,
|
status: event::DiggingStatus::Cancel,
|
||||||
position: p.location,
|
position: p.location,
|
||||||
face: p.face,
|
face: p.face,
|
||||||
}),
|
},
|
||||||
DiggingStatus::FinishedDigging => ClientEvent::Digging(Digging {
|
DiggingStatus::FinishedDigging => Event::Digging {
|
||||||
status: event::DiggingStatus::Finish,
|
status: event::DiggingStatus::Finish,
|
||||||
position: p.location,
|
position: p.location,
|
||||||
face: p.face,
|
face: p.face,
|
||||||
}),
|
},
|
||||||
DiggingStatus::DropItemStack => return,
|
DiggingStatus::DropItemStack => return,
|
||||||
DiggingStatus::DropItem => return,
|
DiggingStatus::DropItem => return,
|
||||||
DiggingStatus::ShootArrowOrFinishEating => return,
|
DiggingStatus::ShootArrowOrFinishEating => return,
|
||||||
|
@ -623,31 +740,33 @@ impl Client {
|
||||||
self.events.push_back(match e.action_id {
|
self.events.push_back(match e.action_id {
|
||||||
PlayerCommandId::StartSneaking => {
|
PlayerCommandId::StartSneaking => {
|
||||||
self.flags.set_sneaking(true);
|
self.flags.set_sneaking(true);
|
||||||
ClientEvent::StartSneaking
|
Event::StartSneaking
|
||||||
}
|
}
|
||||||
PlayerCommandId::StopSneaking => {
|
PlayerCommandId::StopSneaking => {
|
||||||
self.flags.set_sneaking(false);
|
self.flags.set_sneaking(false);
|
||||||
ClientEvent::StopSneaking
|
Event::StopSneaking
|
||||||
}
|
}
|
||||||
PlayerCommandId::LeaveBed => ClientEvent::LeaveBed,
|
PlayerCommandId::LeaveBed => Event::LeaveBed,
|
||||||
PlayerCommandId::StartSprinting => {
|
PlayerCommandId::StartSprinting => {
|
||||||
self.flags.set_sprinting(true);
|
self.flags.set_sprinting(true);
|
||||||
ClientEvent::StartSprinting
|
Event::StartSprinting
|
||||||
}
|
}
|
||||||
PlayerCommandId::StopSprinting => {
|
PlayerCommandId::StopSprinting => {
|
||||||
self.flags.set_sprinting(false);
|
self.flags.set_sprinting(false);
|
||||||
ClientEvent::StopSprinting
|
Event::StopSprinting
|
||||||
}
|
}
|
||||||
PlayerCommandId::StartJumpWithHorse => {
|
PlayerCommandId::StartJumpWithHorse => {
|
||||||
self.flags.set_jumping_with_horse(true);
|
self.flags.set_jumping_with_horse(true);
|
||||||
ClientEvent::StartJumpWithHorse(e.jump_boost.0 .0 as u8)
|
Event::StartJumpWithHorse {
|
||||||
|
jump_boost: e.jump_boost.0 .0 as u8,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PlayerCommandId::StopJumpWithHorse => {
|
PlayerCommandId::StopJumpWithHorse => {
|
||||||
self.flags.set_jumping_with_horse(false);
|
self.flags.set_jumping_with_horse(false);
|
||||||
ClientEvent::StopJumpWithHorse
|
Event::StopJumpWithHorse
|
||||||
}
|
}
|
||||||
PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory,
|
PlayerCommandId::OpenHorseInventory => Event::OpenHorseInventory,
|
||||||
PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra,
|
PlayerCommandId::StartFlyingWithElytra => Event::StartFlyingWithElytra,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
C2sPlayPacket::PlayerInput(_) => {}
|
C2sPlayPacket::PlayerInput(_) => {}
|
||||||
|
@ -666,7 +785,7 @@ impl Client {
|
||||||
C2sPlayPacket::SetJigsawBlock(_) => {}
|
C2sPlayPacket::SetJigsawBlock(_) => {}
|
||||||
C2sPlayPacket::SetStructureBlock(_) => {}
|
C2sPlayPacket::SetStructureBlock(_) => {}
|
||||||
C2sPlayPacket::SignUpdate(_) => {}
|
C2sPlayPacket::SignUpdate(_) => {}
|
||||||
C2sPlayPacket::Swing(p) => self.events.push_back(ClientEvent::ArmSwing(p.hand)),
|
C2sPlayPacket::Swing(p) => self.events.push_back(Event::ArmSwing(p.hand)),
|
||||||
C2sPlayPacket::TeleportToEntity(_) => {}
|
C2sPlayPacket::TeleportToEntity(_) => {}
|
||||||
C2sPlayPacket::UseItemOn(_) => {}
|
C2sPlayPacket::UseItemOn(_) => {}
|
||||||
C2sPlayPacket::UseItem(_) => {}
|
C2sPlayPacket::UseItem(_) => {}
|
||||||
|
@ -715,7 +834,7 @@ impl Client {
|
||||||
gamemode: self.new_game_mode,
|
gamemode: self.new_game_mode,
|
||||||
previous_gamemode: self.old_game_mode,
|
previous_gamemode: self.old_game_mode,
|
||||||
dimension_names,
|
dimension_names,
|
||||||
registry_codec: Nbt(make_dimension_codec(shared)),
|
registry_codec: Nbt(make_registry_codec(shared)),
|
||||||
dimension_type_name: ident!(
|
dimension_type_name: ident!(
|
||||||
"{LIBRARY_NAMESPACE}:dimension_type_{}",
|
"{LIBRARY_NAMESPACE}:dimension_type_{}",
|
||||||
world.meta.dimension().0
|
world.meta.dimension().0
|
||||||
|
@ -1128,21 +1247,21 @@ fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
|
fn make_registry_codec(shared: &SharedServer) -> RegistryCodec {
|
||||||
let mut dims = Vec::new();
|
let mut dims = Vec::new();
|
||||||
for (id, dim) in shared.dimensions() {
|
for (id, dim) in shared.dimensions() {
|
||||||
let id = id.0 as i32;
|
let id = id.0 as i32;
|
||||||
dims.push(DimensionTypeRegistryEntry {
|
dims.push(DimensionTypeRegistryEntry {
|
||||||
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
||||||
id,
|
id,
|
||||||
element: to_dimension_registry_item(dim),
|
element: dim.to_dimension_registry_item(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut biomes = Vec::new();
|
let mut biomes: Vec<_> = shared
|
||||||
for (id, biome) in shared.biomes() {
|
.biomes()
|
||||||
biomes.push(to_biome_registry_item(biome, id.0 as i32));
|
.map(|(id, biome)| biome.to_biome_registry_item(id.0 as i32))
|
||||||
}
|
.collect();
|
||||||
|
|
||||||
// The client needs a biome named "minecraft:plains" in the registry to
|
// The client needs a biome named "minecraft:plains" in the registry to
|
||||||
// connect. This is probably a bug.
|
// connect. This is probably a bug.
|
||||||
|
@ -1151,7 +1270,7 @@ fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
|
||||||
if !biomes.iter().any(|b| b.name == ident!("plains")) {
|
if !biomes.iter().any(|b| b.name == ident!("plains")) {
|
||||||
let biome = Biome::default();
|
let biome = Biome::default();
|
||||||
assert_eq!(biome.name, ident!("plains"));
|
assert_eq!(biome.name, ident!("plains"));
|
||||||
biomes.push(to_biome_registry_item(&biome, biomes.len() as i32));
|
biomes.push(biome.to_biome_registry_item(biomes.len() as i32));
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistryCodec {
|
RegistryCodec {
|
||||||
|
@ -1171,94 +1290,10 @@ fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
|
||||||
element: ChatType {
|
element: ChatType {
|
||||||
chat: ChatTypeChat {},
|
chat: ChatTypeChat {},
|
||||||
narration: ChatTypeNarration {
|
narration: ChatTypeNarration {
|
||||||
priority: "system".to_owned(),
|
priority: "system".into(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_dimension_registry_item(dim: &Dimension) -> DimensionType {
|
|
||||||
DimensionType {
|
|
||||||
piglin_safe: true,
|
|
||||||
has_raids: true,
|
|
||||||
monster_spawn_light_level: 0,
|
|
||||||
monster_spawn_block_light_limit: 0,
|
|
||||||
natural: dim.natural,
|
|
||||||
ambient_light: dim.ambient_light,
|
|
||||||
fixed_time: dim.fixed_time.map(|t| t as i64),
|
|
||||||
infiniburn: "#minecraft:infiniburn_overworld".into(),
|
|
||||||
respawn_anchor_works: true,
|
|
||||||
has_skylight: true,
|
|
||||||
bed_works: true,
|
|
||||||
effects: match dim.effects {
|
|
||||||
DimensionEffects::Overworld => ident!("overworld"),
|
|
||||||
DimensionEffects::TheNether => ident!("the_nether"),
|
|
||||||
DimensionEffects::TheEnd => ident!("the_end"),
|
|
||||||
},
|
|
||||||
min_y: dim.min_y,
|
|
||||||
height: dim.height,
|
|
||||||
logical_height: dim.height,
|
|
||||||
coordinate_scale: 1.0,
|
|
||||||
ultrawarm: false,
|
|
||||||
has_ceiling: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_biome_registry_item(biome: &Biome, id: i32) -> BiomeRegistryBiome {
|
|
||||||
BiomeRegistryBiome {
|
|
||||||
name: biome.name.clone(),
|
|
||||||
id,
|
|
||||||
element: BiomeProperty {
|
|
||||||
precipitation: match biome.precipitation {
|
|
||||||
BiomePrecipitation::Rain => "rain",
|
|
||||||
BiomePrecipitation::Snow => "snow",
|
|
||||||
BiomePrecipitation::None => "none",
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
depth: 0.125,
|
|
||||||
temperature: 0.8,
|
|
||||||
scale: 0.05,
|
|
||||||
downfall: 0.4,
|
|
||||||
category: "none".into(),
|
|
||||||
temperature_modifier: None,
|
|
||||||
effects: BiomeEffects {
|
|
||||||
sky_color: biome.sky_color as i32,
|
|
||||||
water_fog_color: biome.water_fog_color as i32,
|
|
||||||
fog_color: biome.fog_color as i32,
|
|
||||||
water_color: biome.water_color as i32,
|
|
||||||
foliage_color: biome.foliage_color.map(|x| x as i32),
|
|
||||||
grass_color: biome.grass_color.map(|x| x as i32),
|
|
||||||
grass_color_modifier: match biome.grass_color_modifier {
|
|
||||||
BiomeGrassColorModifier::Swamp => Some("swamp".into()),
|
|
||||||
BiomeGrassColorModifier::DarkForest => Some("dark_forest".into()),
|
|
||||||
BiomeGrassColorModifier::None => None,
|
|
||||||
},
|
|
||||||
music: biome.music.as_ref().map(|bm| BiomeMusic {
|
|
||||||
replace_current_music: bm.replace_current_music,
|
|
||||||
sound: bm.sound.clone(),
|
|
||||||
max_delay: bm.max_delay,
|
|
||||||
min_delay: bm.min_delay,
|
|
||||||
}),
|
|
||||||
ambient_sound: biome.ambient_sound.clone(),
|
|
||||||
additions_sound: biome.additions_sound.as_ref().map(|a| BiomeAdditionsSound {
|
|
||||||
sound: a.sound.clone(),
|
|
||||||
tick_chance: a.tick_chance,
|
|
||||||
}),
|
|
||||||
mood_sound: biome.mood_sound.as_ref().map(|m| BiomeMoodSound {
|
|
||||||
sound: m.sound.clone(),
|
|
||||||
tick_delay: m.tick_delay,
|
|
||||||
offset: m.offset,
|
|
||||||
block_search_extent: m.block_search_extent,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
particle: biome.particle.as_ref().map(|p| BiomeParticle {
|
|
||||||
probability: p.probability,
|
|
||||||
options: BiomeParticleOptions {
|
|
||||||
kind: p.kind.clone(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,50 +4,76 @@ use vek::Vec3;
|
||||||
|
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::entity::EntityId;
|
use crate::entity::EntityId;
|
||||||
use crate::protocol::packets::play::c2s::BlockFace;
|
use crate::protocol_inner::packets::play::c2s::BlockFace;
|
||||||
pub use crate::protocol::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand};
|
pub use crate::protocol_inner::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand};
|
||||||
pub use crate::protocol::packets::play::s2c::GameMode;
|
pub use crate::protocol_inner::packets::play::s2c::GameMode;
|
||||||
|
|
||||||
|
/// Represents an action performed by a client.
|
||||||
|
///
|
||||||
|
/// Client events can be obtained from
|
||||||
|
/// [`pop_event`](crate::client::Client::pop_event).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientEvent {
|
pub enum Event {
|
||||||
|
/// A regular message was sent to the chat.
|
||||||
ChatMessage {
|
ChatMessage {
|
||||||
|
/// The content of the message
|
||||||
message: String,
|
message: String,
|
||||||
|
/// The time the message was sent.
|
||||||
timestamp: Duration,
|
timestamp: Duration,
|
||||||
},
|
},
|
||||||
/// Settings were changed. The value in this variant is the previous client
|
/// Settings were changed. The value in this variant is the _previous_
|
||||||
/// settings.
|
/// client settings.
|
||||||
SettingsChanged(Option<Settings>),
|
SettingsChanged(Option<Settings>),
|
||||||
/// The client has moved. The values in this
|
/// The client moved. The values in this
|
||||||
/// variant are the _previous_ position and look.
|
/// variant are the _previous_ position and look.
|
||||||
Movement {
|
Movement {
|
||||||
|
/// Absolute coordinates of the previous position.
|
||||||
position: Vec3<f64>,
|
position: Vec3<f64>,
|
||||||
|
/// The previous yaw (in degrees).
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
|
/// The previous pitch (in degrees).
|
||||||
pitch: f32,
|
pitch: f32,
|
||||||
|
/// If the client was previously on the ground.
|
||||||
on_ground: bool,
|
on_ground: bool,
|
||||||
},
|
},
|
||||||
StartSneaking,
|
StartSneaking,
|
||||||
StopSneaking,
|
StopSneaking,
|
||||||
StartSprinting,
|
StartSprinting,
|
||||||
StopSprinting,
|
StopSprinting,
|
||||||
StartJumpWithHorse(u8),
|
/// A jump while on a horse started.
|
||||||
|
StartJumpWithHorse {
|
||||||
|
/// The power of the horse jump.
|
||||||
|
jump_boost: u8,
|
||||||
|
},
|
||||||
|
/// A jump while on a horse stopped.
|
||||||
StopJumpWithHorse,
|
StopJumpWithHorse,
|
||||||
|
/// The client left a bed.
|
||||||
LeaveBed,
|
LeaveBed,
|
||||||
|
/// The inventory was opened while on a horse.
|
||||||
OpenHorseInventory,
|
OpenHorseInventory,
|
||||||
StartFlyingWithElytra,
|
StartFlyingWithElytra,
|
||||||
ArmSwing(Hand),
|
ArmSwing(Hand),
|
||||||
|
/// Left or right click interaction with an entity's hitbox.
|
||||||
InteractWithEntity {
|
InteractWithEntity {
|
||||||
/// The ID of the entity being interacted with.
|
/// The ID of the entity being interacted with.
|
||||||
id: EntityId,
|
id: EntityId,
|
||||||
/// If the client was sneaking during the interaction.
|
/// If the client was sneaking during the interaction.
|
||||||
sneaking: bool,
|
sneaking: bool,
|
||||||
/// The type of interaction that occurred.
|
/// The kind of interaction that occurred.
|
||||||
kind: InteractWithEntity,
|
kind: InteractWithEntityKind,
|
||||||
},
|
},
|
||||||
SteerBoat {
|
SteerBoat {
|
||||||
left_paddle_turning: bool,
|
left_paddle_turning: bool,
|
||||||
right_paddle_turning: bool,
|
right_paddle_turning: bool,
|
||||||
},
|
},
|
||||||
Digging(Digging),
|
Digging {
|
||||||
|
/// The kind of digging event this is.
|
||||||
|
status: DiggingStatus,
|
||||||
|
/// The position of the block being broken.
|
||||||
|
position: BlockPos,
|
||||||
|
/// The face of the block being broken.
|
||||||
|
face: BlockFace,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
@ -67,22 +93,18 @@ pub struct Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum InteractWithEntity {
|
pub enum InteractWithEntityKind {
|
||||||
Interact(Hand),
|
Interact(Hand),
|
||||||
InteractAt { target: Vec3<f32>, hand: Hand },
|
InteractAt { target: Vec3<f32>, hand: Hand },
|
||||||
Attack,
|
Attack,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct Digging {
|
|
||||||
pub status: DiggingStatus,
|
|
||||||
pub position: BlockPos,
|
|
||||||
pub face: BlockFace,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum DiggingStatus {
|
pub enum DiggingStatus {
|
||||||
|
/// The client started digging a block.
|
||||||
Start,
|
Start,
|
||||||
|
/// The client stopped digging a block before it was fully broken.
|
||||||
Cancel,
|
Cancel,
|
||||||
|
/// The client finished digging a block successfully.
|
||||||
Finish,
|
Finish,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,14 +15,15 @@ use crate::Ticks;
|
||||||
/// server.
|
/// server.
|
||||||
///
|
///
|
||||||
/// The config is used from multiple threads and must therefore implement
|
/// The config is used from multiple threads and must therefore implement
|
||||||
/// `Send` and `Sync`. From within a single thread, methods are never invoked
|
/// [`Send`] and [`Sync`]. From within a single thread, methods are never
|
||||||
/// recursively by the library. In other words, a mutex can be aquired at
|
/// invoked recursively by the library. In other words, a mutex can be aquired
|
||||||
/// the beginning of a method and released at the end without risk of
|
/// at the beginning of a method and released at the end without risk of
|
||||||
/// deadlocking.
|
/// deadlocking.
|
||||||
///
|
///
|
||||||
/// This trait uses the [async_trait](https://docs.rs/async-trait/latest/async_trait/) attribute macro.
|
/// This trait uses the [async_trait] attribute macro. It is exported at the
|
||||||
/// This will be removed once `impl Trait` in return position in traits is
|
/// root of this crate.
|
||||||
/// available in stable rust.
|
///
|
||||||
|
/// [async_trait]: https://docs.rs/async-trait/latest/async_trait/
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
|
@ -39,6 +40,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// be bound to.
|
/// be bound to.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// Returns `127.0.0.1:25565`.
|
/// Returns `127.0.0.1:25565`.
|
||||||
fn address(&self) -> SocketAddr {
|
fn address(&self) -> SocketAddr {
|
||||||
SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 25565).into()
|
SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 25565).into()
|
||||||
|
@ -57,6 +59,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// so there is little benefit to a tick rate higher than 20.
|
/// so there is little benefit to a tick rate higher than 20.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// Returns `20`, which is the same as Minecraft's official server.
|
/// Returns `20`, which is the same as Minecraft's official server.
|
||||||
fn tick_rate(&self) -> Ticks {
|
fn tick_rate(&self) -> Ticks {
|
||||||
20
|
20
|
||||||
|
@ -73,6 +76,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// internet.
|
/// internet.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// Returns `true`.
|
/// Returns `true`.
|
||||||
fn online_mode(&self) -> bool {
|
fn online_mode(&self) -> bool {
|
||||||
true
|
true
|
||||||
|
@ -85,6 +89,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// potential memory usage.
|
/// potential memory usage.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// An unspecified value is returned that should be adequate in most
|
/// An unspecified value is returned that should be adequate in most
|
||||||
/// situations.
|
/// situations.
|
||||||
fn incoming_packet_capacity(&self) -> usize {
|
fn incoming_packet_capacity(&self) -> usize {
|
||||||
|
@ -98,6 +103,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// but increases potential memory usage.
|
/// but increases potential memory usage.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// An unspecified value is returned that should be adequate in most
|
/// An unspecified value is returned that should be adequate in most
|
||||||
/// situations.
|
/// situations.
|
||||||
fn outgoing_packet_capacity(&self) -> usize {
|
fn outgoing_packet_capacity(&self) -> usize {
|
||||||
|
@ -111,6 +117,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// runtime.
|
/// runtime.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// Returns `None`.
|
/// Returns `None`.
|
||||||
fn tokio_handle(&self) -> Option<TokioHandle> {
|
fn tokio_handle(&self) -> Option<TokioHandle> {
|
||||||
None
|
None
|
||||||
|
@ -119,14 +126,15 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// Called once at startup to get the list of [`Dimension`]s usable on the
|
/// Called once at startup to get the list of [`Dimension`]s usable on the
|
||||||
/// server.
|
/// server.
|
||||||
///
|
///
|
||||||
/// The dimensions traversed by [`Server::dimensions`] will be in the same
|
/// The dimensions returned by [`SharedServer::dimensions`] will be in the
|
||||||
/// order as the `Vec` returned by this function.
|
/// same order as the `Vec` returned by this function.
|
||||||
///
|
///
|
||||||
/// The number of elements in the returned `Vec` must be in \[1, u16::MAX].
|
/// The number of elements in the returned `Vec` must be in `1..=u16::MAX`.
|
||||||
/// Additionally, the documented requirements on the fields of [`Dimension`]
|
/// Additionally, the documented requirements on the fields of [`Dimension`]
|
||||||
/// must be met.
|
/// must be met.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// Returns `vec![Dimension::default()]`.
|
/// Returns `vec![Dimension::default()]`.
|
||||||
fn dimensions(&self) -> Vec<Dimension> {
|
fn dimensions(&self) -> Vec<Dimension> {
|
||||||
vec![Dimension::default()]
|
vec![Dimension::default()]
|
||||||
|
@ -135,14 +143,15 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// Called once at startup to get the list of [`Biome`]s usable on the
|
/// Called once at startup to get the list of [`Biome`]s usable on the
|
||||||
/// server.
|
/// server.
|
||||||
///
|
///
|
||||||
/// The biomes traversed by [`Server::biomes`] will be in the same
|
/// The biomes returned by [`SharedServer::biomes`] will be in the same
|
||||||
/// order as the `Vec` returned by this function.
|
/// order as the `Vec` returned by this function.
|
||||||
///
|
///
|
||||||
/// The number of elements in the returned `Vec` must be in \[1, u16::MAX].
|
/// The number of elements in the returned `Vec` must be in `1..=u16::MAX`.
|
||||||
/// Additionally, the documented requirements on the fields of [`Biome`]
|
/// Additionally, the documented requirements on the fields of [`Biome`]
|
||||||
/// must be met.
|
/// must be met.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// Returns `vec![Dimension::default()]`.
|
/// Returns `vec![Dimension::default()]`.
|
||||||
fn biomes(&self) -> Vec<Biome> {
|
fn biomes(&self) -> Vec<Biome> {
|
||||||
vec![Biome::default()]
|
vec![Biome::default()]
|
||||||
|
@ -154,6 +163,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// This method is called from within a tokio runtime.
|
/// This method is called from within a tokio runtime.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// The query is ignored.
|
/// The query is ignored.
|
||||||
async fn server_list_ping(
|
async fn server_list_ping(
|
||||||
&self,
|
&self,
|
||||||
|
@ -164,10 +174,10 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called asynchronously for each client after successful authentication
|
/// Called asynchronously for each client after successful authentication
|
||||||
/// (if online mode is enabled) to determine if they can continue to join
|
/// (if online mode is enabled) to determine if they can join
|
||||||
/// the server. On success, [`Config::join`] is called with the new
|
/// the server. On success, the new client is added to the server's
|
||||||
/// client. If this method returns with `Err(reason)`, then the client is
|
/// [`Clients`]. If this method returns with `Err(reason)`, then the
|
||||||
/// immediately disconnected with the given reason.
|
/// client is immediately disconnected with the given reason.
|
||||||
///
|
///
|
||||||
/// This method is the appropriate place to perform asynchronous
|
/// This method is the appropriate place to perform asynchronous
|
||||||
/// operations such as database queries which may take some time to
|
/// operations such as database queries which may take some time to
|
||||||
|
@ -176,7 +186,10 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// This method is called from within a tokio runtime.
|
/// This method is called from within a tokio runtime.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// The client is allowed to join unconditionally.
|
/// The client is allowed to join unconditionally.
|
||||||
|
///
|
||||||
|
/// [`Clients`]: crate::client::Clients
|
||||||
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
|
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -191,25 +204,29 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
fn init(&self, server: &mut Server) {}
|
fn init(&self, server: &mut Server) {}
|
||||||
|
|
||||||
/// Called once at the beginning of every server update (also known as
|
/// Called once at the beginning of every server update (also known as
|
||||||
/// a "tick").
|
/// "tick"). This is likely where the majority of your code will be.
|
||||||
///
|
///
|
||||||
/// The frequency of server updates can be configured by `update_duration`
|
/// The frequency of ticks can be configured by [`Self::tick_rate`].
|
||||||
/// in [`ServerConfig`].
|
|
||||||
///
|
///
|
||||||
/// This method is called from within a tokio runtime.
|
/// This method is called from within a tokio runtime.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
|
///
|
||||||
/// The default implementation does nothing.
|
/// The default implementation does nothing.
|
||||||
fn update(&self, server: &mut Server);
|
fn update(&self, server: &mut Server);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
|
/// The result of the [`server_list_ping`](Config::server_list_ping) callback.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ServerListPing<'a> {
|
pub enum ServerListPing<'a> {
|
||||||
/// Responds to the server list ping with the given information.
|
/// Responds to the server list ping with the given information.
|
||||||
Respond {
|
Respond {
|
||||||
|
/// Displayed as the number of players on the server.
|
||||||
online_players: i32,
|
online_players: i32,
|
||||||
|
/// Displayed as the maximum number of players allowed on the server at
|
||||||
|
/// a time.
|
||||||
max_players: i32,
|
max_players: i32,
|
||||||
|
/// A description of the server.
|
||||||
description: Text,
|
description: Text,
|
||||||
/// The server's icon as the bytes of a PNG image.
|
/// The server's icon as the bytes of a PNG image.
|
||||||
/// The image must be 64x64 pixels.
|
/// The image must be 64x64 pixels.
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
/// A handle to a particular [`Dimension`] on the server.
|
use crate::ident;
|
||||||
|
use crate::protocol_inner::packets::play::s2c::DimensionType;
|
||||||
|
|
||||||
|
/// Identifies a particular [`Dimension`] on the server.
|
||||||
///
|
///
|
||||||
/// Dimension IDs must only be used on servers from which they originate.
|
/// The default dimension ID refers to the first dimension added in the server's
|
||||||
|
/// [configuration](crate::config::Config).
|
||||||
|
///
|
||||||
|
/// To obtain dimension IDs for other dimensions, call
|
||||||
|
/// [`dimensions`](crate::server::SharedServer::dimensions).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct DimensionId(pub(crate) u16);
|
pub struct DimensionId(pub(crate) u16);
|
||||||
|
|
||||||
impl DimensionId {
|
|
||||||
pub fn to_index(self) -> usize {
|
|
||||||
self.0 as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default dimension ID corresponds to the first element in the `Vec`
|
/// The default dimension ID corresponds to the first element in the `Vec`
|
||||||
/// returned by [`Config::dimensions`].
|
/// returned by [`crate::config::Config::dimensions`].
|
||||||
impl Default for DimensionId {
|
impl Default for DimensionId {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(0)
|
Self(0)
|
||||||
|
@ -20,14 +21,19 @@ impl Default for DimensionId {
|
||||||
|
|
||||||
/// Contains the configuration for a dimension type.
|
/// Contains the configuration for a dimension type.
|
||||||
///
|
///
|
||||||
/// In Minecraft, "dimension" and "dimension type" are two different concepts.
|
/// On creation, each [`World`] in Valence is assigned a dimension. The
|
||||||
|
/// dimension determines certain properties of the world such as its height and
|
||||||
|
/// ambient lighting.
|
||||||
|
///
|
||||||
|
/// In Minecraft, "dimension" and "dimension type" are two distinct concepts.
|
||||||
/// For instance, the Overworld and Nether are dimensions, each with
|
/// For instance, the Overworld and Nether are dimensions, each with
|
||||||
/// their own dimension type. A dimension in this library is analogous to a
|
/// their own dimension type. A dimension in this library is analogous to a
|
||||||
/// [`World`](crate::World) while [`Dimension`] represents a
|
/// [`World`] while [`Dimension`] represents a dimension type.
|
||||||
/// dimension type.
|
///
|
||||||
|
/// [`World`]: crate::world::World
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Dimension {
|
pub struct Dimension {
|
||||||
/// When false, compases will spin randomly.
|
/// When false, compasses will spin randomly.
|
||||||
pub natural: bool,
|
pub natural: bool,
|
||||||
/// Must be between 0.0 and 1.0.
|
/// Must be between 0.0 and 1.0.
|
||||||
pub ambient_light: f32,
|
pub ambient_light: f32,
|
||||||
|
@ -63,21 +69,52 @@ pub struct Dimension {
|
||||||
// * has_ceiling
|
// * has_ceiling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dimension {
|
||||||
|
pub(crate) fn to_dimension_registry_item(&self) -> DimensionType {
|
||||||
|
DimensionType {
|
||||||
|
piglin_safe: true,
|
||||||
|
has_raids: true,
|
||||||
|
monster_spawn_light_level: 0,
|
||||||
|
monster_spawn_block_light_limit: 0,
|
||||||
|
natural: self.natural,
|
||||||
|
ambient_light: self.ambient_light,
|
||||||
|
fixed_time: self.fixed_time.map(|t| t as i64),
|
||||||
|
infiniburn: "#minecraft:infiniburn_overworld".into(),
|
||||||
|
respawn_anchor_works: true,
|
||||||
|
has_skylight: true,
|
||||||
|
bed_works: true,
|
||||||
|
effects: match self.effects {
|
||||||
|
DimensionEffects::Overworld => ident!("overworld"),
|
||||||
|
DimensionEffects::TheNether => ident!("the_nether"),
|
||||||
|
DimensionEffects::TheEnd => ident!("the_end"),
|
||||||
|
},
|
||||||
|
min_y: self.min_y,
|
||||||
|
height: self.height,
|
||||||
|
logical_height: self.height,
|
||||||
|
coordinate_scale: 1.0,
|
||||||
|
ultrawarm: false,
|
||||||
|
has_ceiling: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Dimension {
|
impl Default for Dimension {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
natural: true,
|
natural: true,
|
||||||
ambient_light: 1.0,
|
ambient_light: 1.0,
|
||||||
fixed_time: None,
|
fixed_time: None,
|
||||||
effects: DimensionEffects::Overworld,
|
effects: DimensionEffects::default(),
|
||||||
min_y: -64,
|
min_y: -64,
|
||||||
height: 384,
|
height: 384,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
/// Determines what skybox/fog effects to use in dimensions.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
|
||||||
pub enum DimensionEffects {
|
pub enum DimensionEffects {
|
||||||
|
#[default]
|
||||||
Overworld,
|
Overworld,
|
||||||
TheNether,
|
TheNether,
|
||||||
TheEnd,
|
TheEnd,
|
||||||
|
|
133
src/entity.rs
133
src/entity.rs
|
@ -1,5 +1,5 @@
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod meta;
|
pub mod types;
|
||||||
|
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -7,19 +7,29 @@ use std::iter::FusedIterator;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use bitfield_struct::bitfield;
|
use bitfield_struct::bitfield;
|
||||||
pub use data::{EntityData, EntityKind};
|
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
|
pub use types::{EntityData, EntityKind};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vek::{Aabb, Vec3};
|
use vek::{Aabb, Vec3};
|
||||||
|
|
||||||
use crate::protocol::packets::play::s2c::{
|
use crate::protocol_inner::packets::play::s2c::{
|
||||||
AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata,
|
AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata,
|
||||||
};
|
};
|
||||||
use crate::protocol::{ByteAngle, RawBytes, VarInt};
|
use crate::protocol_inner::{ByteAngle, RawBytes, VarInt};
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::util::aabb_from_bottom_and_size;
|
use crate::util::aabb_from_bottom_and_size;
|
||||||
use crate::world::WorldId;
|
use crate::world::WorldId;
|
||||||
|
|
||||||
|
/// A container for all [`Entity`]s on a [`Server`](crate::server::Server).
|
||||||
|
///
|
||||||
|
/// # Spawning Player Entities
|
||||||
|
///
|
||||||
|
/// [`Player`] entities are treated specially by the client. For the player
|
||||||
|
/// entity to be visible to clients, the player's UUID must be added to the
|
||||||
|
/// [`PlayerList`] _before_ being loaded by the client.
|
||||||
|
///
|
||||||
|
/// [`Player`]: crate::entity::types::Player
|
||||||
|
/// [`PlayerList`]: crate::player_list::PlayerList
|
||||||
pub struct Entities {
|
pub struct Entities {
|
||||||
sm: SlotMap<Entity>,
|
sm: SlotMap<Entity>,
|
||||||
uuid_to_entity: HashMap<Uuid, EntityId>,
|
uuid_to_entity: HashMap<Uuid, EntityId>,
|
||||||
|
@ -35,18 +45,18 @@ impl Entities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a new entity with the default data. The new entity's [`EntityId`]
|
/// Spawns a new entity with a random UUID. A reference to the entity along
|
||||||
/// is returned.
|
/// with its ID is returned.
|
||||||
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) {
|
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) {
|
||||||
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()))
|
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()))
|
||||||
.expect("UUID collision")
|
.expect("UUID collision")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`create`](Entities::create), but requires specifying the new
|
/// Like [`Self::create`], but requires specifying the new
|
||||||
/// entity's UUID.
|
/// entity's UUID.
|
||||||
///
|
///
|
||||||
/// The provided UUID must not conflict with an existing entity UUID in this
|
/// The provided UUID must not conflict with an existing entity UUID. If it
|
||||||
/// world. If it does, `None` is returned and the entity is not spawned.
|
/// does, `None` is returned and the entity is not spawned.
|
||||||
pub fn create_with_uuid(
|
pub fn create_with_uuid(
|
||||||
&mut self,
|
&mut self,
|
||||||
kind: EntityKind,
|
kind: EntityKind,
|
||||||
|
@ -58,7 +68,7 @@ impl Entities {
|
||||||
let (k, e) = self.sm.insert(Entity {
|
let (k, e) = self.sm.insert(Entity {
|
||||||
flags: EntityFlags(0),
|
flags: EntityFlags(0),
|
||||||
data: EntityData::new(kind),
|
data: EntityData::new(kind),
|
||||||
world: None,
|
world: WorldId::NULL,
|
||||||
new_position: Vec3::default(),
|
new_position: Vec3::default(),
|
||||||
old_position: Vec3::default(),
|
old_position: Vec3::default(),
|
||||||
yaw: 0.0,
|
yaw: 0.0,
|
||||||
|
@ -78,6 +88,10 @@ impl Entities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes an entity from the server.
|
||||||
|
///
|
||||||
|
/// If the given entity ID is valid, `true` is returned and the entity is
|
||||||
|
/// deleted. Otherwise, `false` is returned and the function has no effect.
|
||||||
pub fn delete(&mut self, entity: EntityId) -> bool {
|
pub fn delete(&mut self, entity: EntityId) -> bool {
|
||||||
if let Some(e) = self.sm.remove(entity.0) {
|
if let Some(e) = self.sm.remove(entity.0) {
|
||||||
self.uuid_to_entity
|
self.uuid_to_entity
|
||||||
|
@ -94,6 +108,9 @@ impl Entities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes all entities from the server for which `f` returns `true`.
|
||||||
|
///
|
||||||
|
/// All entities are visited in an unspecified order.
|
||||||
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
|
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
|
||||||
self.sm.retain(|k, v| {
|
self.sm.retain(|k, v| {
|
||||||
if f(EntityId(k), v) {
|
if f(EntityId(k), v) {
|
||||||
|
@ -112,7 +129,7 @@ impl Entities {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of live entities.
|
/// Returns the number of entities in this container.
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.sm.len()
|
self.sm.len()
|
||||||
}
|
}
|
||||||
|
@ -120,16 +137,21 @@ impl Entities {
|
||||||
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
|
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
|
||||||
/// manner.
|
/// manner.
|
||||||
///
|
///
|
||||||
/// Returns `None` if there is no entity with the provided UUID. Returns
|
/// If there is no entity with the UUID, `None` is returned.
|
||||||
/// `Some` otherwise.
|
|
||||||
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
|
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
|
||||||
self.uuid_to_entity.get(&uuid).cloned()
|
self.uuid_to_entity.get(&uuid).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a shared reference to the entity with the given [`EntityId`].
|
||||||
|
///
|
||||||
|
/// If the ID is invalid, `None` is returned.
|
||||||
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
|
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
|
||||||
self.sm.get(entity.0)
|
self.sm.get(entity.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets an exclusive reference to the entity with the given [`EntityId`].
|
||||||
|
///
|
||||||
|
/// If the ID is invalid, `None` is returned.
|
||||||
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
|
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
|
||||||
self.sm.get_mut(entity.0)
|
self.sm.get_mut(entity.0)
|
||||||
}
|
}
|
||||||
|
@ -140,18 +162,26 @@ impl Entities {
|
||||||
Some(EntityId(Key::new(index, version)))
|
Some(EntityId(Key::new(index, version)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an immutable iterator over all entities on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
||||||
self.sm.iter().map(|(k, v)| (EntityId(k), v))
|
self.sm.iter().map(|(k, v)| (EntityId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable iterator over all entities on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
|
||||||
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
|
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel immutable iterator over all entities on the server in
|
||||||
|
/// an unspecified order.
|
||||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
||||||
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
|
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel mutable iterator over all clients on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
|
||||||
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
|
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
|
||||||
}
|
}
|
||||||
|
@ -168,10 +198,20 @@ impl Entities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A key for an [`Entity`] on the server.
|
||||||
|
///
|
||||||
|
/// Entity IDs are either _valid_ or _invalid_. Valid entity IDs point to
|
||||||
|
/// entities that have not been deleted, while invalid IDs point to those that
|
||||||
|
/// have. Once an ID becomes invalid, it will never become valid again.
|
||||||
|
///
|
||||||
|
/// The [`Ord`] instance on this type is correct but otherwise unspecified. This
|
||||||
|
/// is useful for storing IDs in containers such as
|
||||||
|
/// [`BTreeMap`](std::collections::BTreeMap).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
pub struct EntityId(Key);
|
pub struct EntityId(Key);
|
||||||
|
|
||||||
impl EntityId {
|
impl EntityId {
|
||||||
|
/// The value of the default entity ID which is always invalid.
|
||||||
pub const NULL: Self = Self(Key::NULL);
|
pub const NULL: Self = Self(Key::NULL);
|
||||||
|
|
||||||
pub(crate) fn to_network_id(self) -> i32 {
|
pub(crate) fn to_network_id(self) -> i32 {
|
||||||
|
@ -179,10 +219,19 @@ impl EntityId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an entity on the server.
|
||||||
|
///
|
||||||
|
/// In essence, an entity is anything in a world that isn't a block or client.
|
||||||
|
/// Entities include paintings, falling blocks, zombies, fireballs, and more.
|
||||||
|
///
|
||||||
|
/// Every entity has common state which is accessible directly from
|
||||||
|
/// this struct. This includes position, rotation, velocity, UUID, and hitbox.
|
||||||
|
/// To access data that is not common to every kind of entity, see
|
||||||
|
/// [`Self::data`].
|
||||||
pub struct Entity {
|
pub struct Entity {
|
||||||
flags: EntityFlags,
|
flags: EntityFlags,
|
||||||
data: EntityData,
|
data: EntityData,
|
||||||
world: Option<WorldId>,
|
world: WorldId,
|
||||||
new_position: Vec3<f64>,
|
new_position: Vec3<f64>,
|
||||||
old_position: Vec3<f64>,
|
old_position: Vec3<f64>,
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
|
@ -192,8 +241,6 @@ pub struct Entity {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains a bit for certain fields in [`Entity`] to track if they have been
|
|
||||||
/// modified.
|
|
||||||
#[bitfield(u8)]
|
#[bitfield(u8)]
|
||||||
pub(crate) struct EntityFlags {
|
pub(crate) struct EntityFlags {
|
||||||
pub yaw_or_pitch_modified: bool,
|
pub yaw_or_pitch_modified: bool,
|
||||||
|
@ -209,51 +256,64 @@ impl Entity {
|
||||||
self.flags
|
self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to this entity's [`EntityData`].
|
/// Gets a reference to this entity's [`EntityData`].
|
||||||
pub fn data(&self) -> &EntityData {
|
pub fn data(&self) -> &EntityData {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to this entity's [`EntityData`].
|
/// Gets a mutable reference to this entity's
|
||||||
|
/// [`EntityData`].
|
||||||
pub fn data_mut(&mut self) -> &mut EntityData {
|
pub fn data_mut(&mut self) -> &mut EntityData {
|
||||||
&mut self.data
|
&mut self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`EntityKind`] of this entity.
|
/// Gets the [`EntityKind`] of this entity.
|
||||||
pub fn kind(&self) -> EntityKind {
|
pub fn kind(&self) -> EntityKind {
|
||||||
self.data.kind()
|
self.data.kind()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn world(&self) -> Option<WorldId> {
|
/// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is
|
||||||
|
/// located in.
|
||||||
|
///
|
||||||
|
/// By default, entities are located in
|
||||||
|
/// [`WorldId::NULL`](crate::world::WorldId::NULL).
|
||||||
|
pub fn world(&self) -> WorldId {
|
||||||
self.world
|
self.world
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_world(&mut self, world: impl Into<Option<WorldId>>) {
|
/// Sets the world this entity is located in.
|
||||||
self.world = world.into();
|
pub fn set_world(&mut self, world: WorldId) {
|
||||||
|
self.world = world;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position of this entity in the world it inhabits.
|
/// Gets the position of this entity in the world it inhabits.
|
||||||
|
///
|
||||||
|
/// The position of an entity is located on the botton of its
|
||||||
|
/// hitbox and not the center.
|
||||||
pub fn position(&self) -> Vec3<f64> {
|
pub fn position(&self) -> Vec3<f64> {
|
||||||
self.new_position
|
self.new_position
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the position of this entity in the world it inhabits.
|
/// Sets the position of this entity in the world it inhabits.
|
||||||
|
///
|
||||||
|
/// The position of an entity is located on the botton of its
|
||||||
|
/// hitbox and not the center.
|
||||||
pub fn set_position(&mut self, pos: impl Into<Vec3<f64>>) {
|
pub fn set_position(&mut self, pos: impl Into<Vec3<f64>>) {
|
||||||
self.new_position = pos.into();
|
self.new_position = pos.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position of this entity as it existed at the end of the
|
/// Returns the position of this entity as it existed at the end of the
|
||||||
/// previous tick.
|
/// previous tick.
|
||||||
pub fn old_position(&self) -> Vec3<f64> {
|
pub(crate) fn old_position(&self) -> Vec3<f64> {
|
||||||
self.old_position
|
self.old_position
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the yaw of this entity (in degrees).
|
/// Gets the yaw of this entity in degrees.
|
||||||
pub fn yaw(&self) -> f32 {
|
pub fn yaw(&self) -> f32 {
|
||||||
self.yaw
|
self.yaw
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the yaw of this entity (in degrees).
|
/// Sets the yaw of this entity in degrees.
|
||||||
pub fn set_yaw(&mut self, yaw: f32) {
|
pub fn set_yaw(&mut self, yaw: f32) {
|
||||||
if self.yaw != yaw {
|
if self.yaw != yaw {
|
||||||
self.yaw = yaw;
|
self.yaw = yaw;
|
||||||
|
@ -261,12 +321,12 @@ impl Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the pitch of this entity (in degrees).
|
/// Gets the pitch of this entity in degrees.
|
||||||
pub fn pitch(&self) -> f32 {
|
pub fn pitch(&self) -> f32 {
|
||||||
self.pitch
|
self.pitch
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the pitch of this entity (in degrees).
|
/// Sets the pitch of this entity in degrees.
|
||||||
pub fn set_pitch(&mut self, pitch: f32) {
|
pub fn set_pitch(&mut self, pitch: f32) {
|
||||||
if self.pitch != pitch {
|
if self.pitch != pitch {
|
||||||
self.pitch = pitch;
|
self.pitch = pitch;
|
||||||
|
@ -274,12 +334,12 @@ impl Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the head yaw of this entity (in degrees).
|
/// Gets the head yaw of this entity in degrees.
|
||||||
pub fn head_yaw(&self) -> f32 {
|
pub fn head_yaw(&self) -> f32 {
|
||||||
self.head_yaw
|
self.head_yaw
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the head yaw of this entity (in degrees).
|
/// Sets the head yaw of this entity in degrees.
|
||||||
pub fn set_head_yaw(&mut self, head_yaw: f32) {
|
pub fn set_head_yaw(&mut self, head_yaw: f32) {
|
||||||
if self.head_yaw != head_yaw {
|
if self.head_yaw != head_yaw {
|
||||||
self.head_yaw = head_yaw;
|
self.head_yaw = head_yaw;
|
||||||
|
@ -292,6 +352,7 @@ impl Entity {
|
||||||
self.velocity
|
self.velocity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the velocity of this entity in meters per second.
|
||||||
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
||||||
let new_vel = velocity.into();
|
let new_vel = velocity.into();
|
||||||
|
|
||||||
|
@ -301,18 +362,30 @@ impl Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the "on ground" flag.
|
||||||
pub fn on_ground(&self) -> bool {
|
pub fn on_ground(&self) -> bool {
|
||||||
self.flags.on_ground()
|
self.flags.on_ground()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the "on ground" flag.
|
||||||
pub fn set_on_ground(&mut self, on_ground: bool) {
|
pub fn set_on_ground(&mut self, on_ground: bool) {
|
||||||
self.flags.set_on_ground(on_ground);
|
self.flags.set_on_ground(on_ground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the UUID of this entity.
|
||||||
pub fn uuid(&self) -> Uuid {
|
pub fn uuid(&self) -> Uuid {
|
||||||
self.uuid
|
self.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the hitbox of this entity.
|
||||||
|
///
|
||||||
|
/// The hitbox describes the space that an entity occupies. Clients interact
|
||||||
|
/// with this space to create an [interact event].
|
||||||
|
///
|
||||||
|
/// The hitbox of an entity is determined by its position, entity type, and
|
||||||
|
/// other state specific to that type.
|
||||||
|
///
|
||||||
|
/// [interact event]: crate::client::Event::InteractWithEntity
|
||||||
pub fn hitbox(&self) -> Aabb<f64> {
|
pub fn hitbox(&self) -> Aabb<f64> {
|
||||||
let dims = match &self.data {
|
let dims = match &self.data {
|
||||||
EntityData::Allay(_) => [0.6, 0.35, 0.6],
|
EntityData::Allay(_) => [0.6, 0.35, 0.6],
|
||||||
|
|
|
@ -1,10 +1,216 @@
|
||||||
#![allow(clippy::all, missing_docs)]
|
//! Types used in [`EntityData`](crate::entity::EntityData).
|
||||||
|
|
||||||
use crate::block::{BlockPos, BlockState};
|
use std::io::Write;
|
||||||
use crate::entity::meta::*;
|
|
||||||
use crate::entity::EntityId;
|
|
||||||
use crate::protocol::{Encode, VarInt};
|
|
||||||
use crate::text::Text;
|
|
||||||
use crate::uuid::Uuid;
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/entity.rs"));
|
use crate::protocol_inner::{Encode, VarInt};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
|
||||||
|
pub struct ArmorStandRotations {
|
||||||
|
/// Rotation on the X axis in degrees.
|
||||||
|
pub x: f32,
|
||||||
|
/// Rotation on the Y axis in degrees.
|
||||||
|
pub y: f32,
|
||||||
|
/// Rotation on the Z axis in degrees.
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArmorStandRotations {
|
||||||
|
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||||
|
Self { x, y, z }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for ArmorStandRotations {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
self.x.encode(w)?;
|
||||||
|
self.y.encode(w)?;
|
||||||
|
self.z.encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub enum Direction {
|
||||||
|
Down,
|
||||||
|
Up,
|
||||||
|
North,
|
||||||
|
South,
|
||||||
|
West,
|
||||||
|
East,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Direction {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(*self as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct VillagerData {
|
||||||
|
pub kind: VillagerKind,
|
||||||
|
pub profession: VillagerProfession,
|
||||||
|
pub level: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VillagerData {
|
||||||
|
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
profession,
|
||||||
|
level,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VillagerData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Default::default(),
|
||||||
|
profession: Default::default(),
|
||||||
|
level: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for VillagerData {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(self.kind as i32).encode(w)?;
|
||||||
|
VarInt(self.profession as i32).encode(w)?;
|
||||||
|
VarInt(self.level).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum VillagerKind {
|
||||||
|
Desert,
|
||||||
|
Jungle,
|
||||||
|
#[default]
|
||||||
|
Plains,
|
||||||
|
Savanna,
|
||||||
|
Snow,
|
||||||
|
Swamp,
|
||||||
|
Taiga,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum VillagerProfession {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Armorer,
|
||||||
|
Butcher,
|
||||||
|
Cartographer,
|
||||||
|
Cleric,
|
||||||
|
Farmer,
|
||||||
|
Fisherman,
|
||||||
|
Fletcher,
|
||||||
|
Leatherworker,
|
||||||
|
Librarian,
|
||||||
|
Mason,
|
||||||
|
Nitwit,
|
||||||
|
Shepherd,
|
||||||
|
Toolsmith,
|
||||||
|
Weaponsmith,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum Pose {
|
||||||
|
#[default]
|
||||||
|
Standing,
|
||||||
|
FallFlying,
|
||||||
|
Sleeping,
|
||||||
|
Swimming,
|
||||||
|
SpinAttack,
|
||||||
|
Sneaking,
|
||||||
|
LongJumping,
|
||||||
|
Dying,
|
||||||
|
Croaking,
|
||||||
|
UsingTongue,
|
||||||
|
Roaring,
|
||||||
|
Sniffing,
|
||||||
|
Emerging,
|
||||||
|
Digging,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Pose {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(*self as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main hand of a player.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum MainHand {
|
||||||
|
Left,
|
||||||
|
#[default]
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for MainHand {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
(*self as u8).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum BoatKind {
|
||||||
|
#[default]
|
||||||
|
Oak,
|
||||||
|
Spruce,
|
||||||
|
Birch,
|
||||||
|
Jungle,
|
||||||
|
Acacia,
|
||||||
|
DarkOak,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for BoatKind {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(*self as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum CatKind {
|
||||||
|
Tabby,
|
||||||
|
#[default]
|
||||||
|
Black,
|
||||||
|
Red,
|
||||||
|
Siamese,
|
||||||
|
BritishShorthair,
|
||||||
|
Calico,
|
||||||
|
Persian,
|
||||||
|
Ragdoll,
|
||||||
|
White,
|
||||||
|
Jellie,
|
||||||
|
AllBlack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for CatKind {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(*self as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum FrogKind {
|
||||||
|
#[default]
|
||||||
|
Temperate,
|
||||||
|
Warm,
|
||||||
|
Cold,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for FrogKind {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(*self as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub enum PaintingKind {
|
||||||
|
#[default]
|
||||||
|
Default, // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for PaintingKind {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(*self as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,216 +0,0 @@
|
||||||
#![allow(missing_docs)]
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use crate::protocol::{Encode, VarInt};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
|
|
||||||
pub struct ArmorStandRotations {
|
|
||||||
/// Rotation on the X axis in degrees.
|
|
||||||
pub x: f32,
|
|
||||||
/// Rotation on the Y axis in degrees.
|
|
||||||
pub y: f32,
|
|
||||||
/// Rotation on the Z axis in degrees.
|
|
||||||
pub z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArmorStandRotations {
|
|
||||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
|
||||||
Self { x, y, z }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for ArmorStandRotations {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
self.x.encode(w)?;
|
|
||||||
self.y.encode(w)?;
|
|
||||||
self.z.encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
||||||
pub enum Direction {
|
|
||||||
Down,
|
|
||||||
Up,
|
|
||||||
North,
|
|
||||||
South,
|
|
||||||
West,
|
|
||||||
East,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for Direction {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(*self as i32).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
||||||
pub struct VillagerData {
|
|
||||||
pub kind: VillagerKind,
|
|
||||||
pub profession: VillagerProfession,
|
|
||||||
pub level: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VillagerData {
|
|
||||||
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
|
|
||||||
Self {
|
|
||||||
kind,
|
|
||||||
profession,
|
|
||||||
level,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VillagerData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
kind: Default::default(),
|
|
||||||
profession: Default::default(),
|
|
||||||
level: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for VillagerData {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(self.kind as i32).encode(w)?;
|
|
||||||
VarInt(self.profession as i32).encode(w)?;
|
|
||||||
VarInt(self.level).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum VillagerKind {
|
|
||||||
Desert,
|
|
||||||
Jungle,
|
|
||||||
#[default]
|
|
||||||
Plains,
|
|
||||||
Savanna,
|
|
||||||
Snow,
|
|
||||||
Swamp,
|
|
||||||
Taiga,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum VillagerProfession {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Armorer,
|
|
||||||
Butcher,
|
|
||||||
Cartographer,
|
|
||||||
Cleric,
|
|
||||||
Farmer,
|
|
||||||
Fisherman,
|
|
||||||
Fletcher,
|
|
||||||
Leatherworker,
|
|
||||||
Librarian,
|
|
||||||
Mason,
|
|
||||||
Nitwit,
|
|
||||||
Shepherd,
|
|
||||||
Toolsmith,
|
|
||||||
Weaponsmith,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum Pose {
|
|
||||||
#[default]
|
|
||||||
Standing,
|
|
||||||
FallFlying,
|
|
||||||
Sleeping,
|
|
||||||
Swimming,
|
|
||||||
SpinAttack,
|
|
||||||
Sneaking,
|
|
||||||
LongJumping,
|
|
||||||
Dying,
|
|
||||||
Croaking,
|
|
||||||
UsingTongue,
|
|
||||||
Roaring,
|
|
||||||
Sniffing,
|
|
||||||
Emerging,
|
|
||||||
Digging,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for Pose {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(*self as i32).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main hand of a player.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum MainHand {
|
|
||||||
Left,
|
|
||||||
#[default]
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for MainHand {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
(*self as u8).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum BoatKind {
|
|
||||||
#[default]
|
|
||||||
Oak,
|
|
||||||
Spruce,
|
|
||||||
Birch,
|
|
||||||
Jungle,
|
|
||||||
Acacia,
|
|
||||||
DarkOak,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for BoatKind {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(*self as i32).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum CatKind {
|
|
||||||
Tabby,
|
|
||||||
#[default]
|
|
||||||
Black,
|
|
||||||
Red,
|
|
||||||
Siamese,
|
|
||||||
BritishShorthair,
|
|
||||||
Calico,
|
|
||||||
Persian,
|
|
||||||
Ragdoll,
|
|
||||||
White,
|
|
||||||
Jellie,
|
|
||||||
AllBlack,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for CatKind {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(*self as i32).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum FrogKind {
|
|
||||||
#[default]
|
|
||||||
Temperate,
|
|
||||||
Warm,
|
|
||||||
Cold,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for FrogKind {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(*self as i32).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
|
||||||
pub enum PaintingKind {
|
|
||||||
#[default]
|
|
||||||
Default, // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for PaintingKind {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
VarInt(*self as i32).encode(w)
|
|
||||||
}
|
|
||||||
}
|
|
12
src/entity/types.rs
Normal file
12
src/entity/types.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//! Contains a struct for each variant in [`EntityKind`].
|
||||||
|
|
||||||
|
#![allow(clippy::all, missing_docs)]
|
||||||
|
|
||||||
|
use crate::block::{BlockPos, BlockState};
|
||||||
|
use crate::entity::data::*;
|
||||||
|
use crate::entity::EntityId;
|
||||||
|
use crate::protocol_inner::{Encode, VarInt};
|
||||||
|
use crate::text::Text;
|
||||||
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/entity.rs"));
|
26
src/ident.rs
26
src/ident.rs
|
@ -7,15 +7,15 @@ use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::protocol::{encode_string_bounded, BoundedString, Decode, Encode};
|
use crate::protocol_inner::{encode_string_bounded, BoundedString, Decode, Encode};
|
||||||
|
|
||||||
/// An identifier is a string split into a "namespace" part and a "name" part.
|
/// An identifier is a string split into a "namespace" part and a "name" part.
|
||||||
/// For instance `minecraft:apple` and `apple` are both valid identifiers.
|
/// For instance `minecraft:apple` and `apple` are both valid identifiers.
|
||||||
///
|
///
|
||||||
/// If the namespace part is left off (the part before and including the colon)
|
/// If the namespace part is left off (the part before and including the colon)
|
||||||
/// the namespace is considered to be "minecraft".
|
/// the namespace is considered to be "minecraft" for the purposes of equality.
|
||||||
///
|
///
|
||||||
/// The entire identifier must match the regex `([a-z0-9_-]+:)?[a-z0-9_\/.-]+`.
|
/// The identifier must match the regex `^([a-z0-9_-]+:)?[a-z0-9_\/.-]+$`.
|
||||||
#[derive(Clone, Eq)]
|
#[derive(Clone, Eq)]
|
||||||
pub struct Ident {
|
pub struct Ident {
|
||||||
ident: Cow<'static, AsciiStr>,
|
ident: Cow<'static, AsciiStr>,
|
||||||
|
@ -239,10 +239,26 @@ impl<'de> Visitor<'de> for IdentifierVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience macro for constructing an identifier from a format string.
|
/// Convenience macro for constructing an [`Ident`] from a format string.
|
||||||
///
|
///
|
||||||
/// The macro will panic if the formatted string is not a valid
|
/// The arguments to this macro are forwarded to [`std::format_args`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The macro will cause a panic if the formatted string is not a valid
|
||||||
/// identifier.
|
/// identifier.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use valence::ident;
|
||||||
|
///
|
||||||
|
/// let namespace = "my_namespace";
|
||||||
|
/// let apple = ident!("{namespace}:apple");
|
||||||
|
///
|
||||||
|
/// assert_eq!(apple.namespace(), Some("my_namespace"));
|
||||||
|
/// assert_eq!(apple.name(), "apple");
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ident {
|
macro_rules! ident {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
|
|
136
src/lib.rs
136
src/lib.rs
|
@ -1,10 +1,111 @@
|
||||||
|
//! A Rust framework for building efficient Minecraft servers.
|
||||||
|
//!
|
||||||
|
//! Valence is a Rust library which provides the necessary abstractions over
|
||||||
|
//! Minecraft's protocol to build servers. Very few assumptions about the
|
||||||
|
//! desired server are made, which allows for greater flexibility in its design.
|
||||||
|
//!
|
||||||
|
//! At a high level, a Valence [`Server`] is a collection of [`Clients`],
|
||||||
|
//! [`Entities`], and [`Worlds`]. When a client connects to the server, they are
|
||||||
|
//! added to the server's [`Clients`]. After connecting, clients are assigned to
|
||||||
|
//! a [`World`] where they are able to interact with the entities and
|
||||||
|
//! [`Chunks`] that are a part of it.
|
||||||
|
//!
|
||||||
|
//! The Valence documentation assumes some familiarity with Minecraft and its
|
||||||
|
//! mechanics. See the [Minecraft Wiki] for general information and [wiki.vg]
|
||||||
|
//! for protocol documentation.
|
||||||
|
//!
|
||||||
|
//! [Minecraft Wiki]: https://minecraft.fandom.com/wiki/Minecraft_Wiki
|
||||||
|
//! [wiki.vg]: https://wiki.vg/Main_Page
|
||||||
|
//!
|
||||||
|
//! # Logging
|
||||||
|
//!
|
||||||
|
//! Valence uses the [log] crate to report errors and other information. You may
|
||||||
|
//! want to use a logging implementation such as [env_logger] to see these
|
||||||
|
//! messages.
|
||||||
|
//!
|
||||||
|
//! [log]: https://docs.rs/log/latest/log/
|
||||||
|
//! [env_logger]: https://docs.rs/env_logger/latest/env_logger/
|
||||||
|
//!
|
||||||
|
//! # An Important Note on [`mem::swap`]
|
||||||
|
//!
|
||||||
|
//! In Valence, many types are owned by the library but given out as mutable
|
||||||
|
//! references for the user to modify. Examples of such types include [`World`],
|
||||||
|
//! [`Chunk`], [`Entity`], and [`Client`].
|
||||||
|
//!
|
||||||
|
//! **You must not call [`mem::swap`] on these references (or any other
|
||||||
|
//! function that would move their location in memory).** Doing so breaks
|
||||||
|
//! invariants within the library and the resulting behavior is unspecified.
|
||||||
|
//! These types should be considered pinned in memory.
|
||||||
|
//!
|
||||||
|
//! Preventing this illegal behavior using Rust's type system was considered too
|
||||||
|
//! cumbersome, so a note has been left here instead.
|
||||||
|
//!
|
||||||
|
//! [`mem::swap`]: std::mem::swap
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! The following is a minimal server implementation. You should be able to
|
||||||
|
//! connect to the server at `localhost`.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use valence::config::Config;
|
||||||
|
//! use valence::server::{Server, ShutdownResult};
|
||||||
|
//!
|
||||||
|
//! pub fn main() -> ShutdownResult {
|
||||||
|
//! valence::start_server(Game)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! struct Game;
|
||||||
|
//!
|
||||||
|
//! impl Config for Game {
|
||||||
|
//! fn max_connections(&self) -> usize {
|
||||||
|
//! 256
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn update(&self, server: &mut Server) {
|
||||||
|
//! server.clients.retain(|_, client| {
|
||||||
|
//! if client.created_tick() == server.shared.current_tick() {
|
||||||
|
//! println!("{} joined!", client.username());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! if client.is_disconnected() {
|
||||||
|
//! println!("{} left!", client.username());
|
||||||
|
//! false
|
||||||
|
//! } else {
|
||||||
|
//! true
|
||||||
|
//! }
|
||||||
|
//! });
|
||||||
|
//! # server.shared.shutdown::<_, std::convert::Infallible>(Ok(()));
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! For more complete examples, see the [examples] in the source repository.
|
||||||
|
//!
|
||||||
|
//! [examples]: https://github.com/rj00a/valence/tree/main/examples
|
||||||
|
//!
|
||||||
|
//! # Feature Flags
|
||||||
|
//!
|
||||||
|
//! * `protocol`: Enables low-level access to the [`protocol`] module, which
|
||||||
|
//! could be used to build your own proxy or client. This feature is
|
||||||
|
//! considered experimental and is subject to change.
|
||||||
|
//!
|
||||||
|
//! [`Server`]: crate::server::Server
|
||||||
|
//! [`Clients`]: crate::client::Clients
|
||||||
|
//! [`Entities`]: crate::entity::Entities
|
||||||
|
//! [`Worlds`]: crate::world::Worlds
|
||||||
|
//! [`World`]: crate::world::World
|
||||||
|
//! [`Chunks`]: crate::chunk::Chunks
|
||||||
|
//! [`Chunk`]: crate::chunk::Chunk
|
||||||
|
//! [`Entity`]: crate::entity::Entity
|
||||||
|
//! [`Client`]: crate::client::Client
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![warn(
|
#![warn(
|
||||||
trivial_casts,
|
trivial_casts,
|
||||||
trivial_numeric_casts,
|
trivial_numeric_casts,
|
||||||
unused_lifetimes,
|
unused_lifetimes,
|
||||||
unused_import_braces,
|
unused_import_braces
|
||||||
// missing_docs
|
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod biome;
|
pub mod biome;
|
||||||
|
@ -18,31 +119,36 @@ pub mod config;
|
||||||
pub mod dimension;
|
pub mod dimension;
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
pub mod ident;
|
pub mod ident;
|
||||||
mod player_list;
|
pub mod player_list;
|
||||||
pub mod player_textures;
|
pub mod player_textures;
|
||||||
#[cfg(not(feature = "protocol"))]
|
#[allow(dead_code)]
|
||||||
#[allow(unused)]
|
mod protocol_inner;
|
||||||
mod protocol;
|
|
||||||
#[cfg(feature = "protocol")]
|
|
||||||
pub mod protocol;
|
|
||||||
pub mod server;
|
pub mod server;
|
||||||
mod slotmap;
|
mod slotmap;
|
||||||
mod spatial_index;
|
pub mod spatial_index;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
|
|
||||||
|
#[cfg(feature = "protocol")]
|
||||||
|
pub mod protocol {
|
||||||
|
pub use crate::protocol_inner::*;
|
||||||
|
}
|
||||||
|
|
||||||
pub use async_trait::async_trait;
|
pub use async_trait::async_trait;
|
||||||
pub use server::start_server;
|
pub use server::start_server;
|
||||||
pub use spatial_index::SpatialIndex;
|
|
||||||
pub use {nbt, uuid, vek};
|
pub use {nbt, uuid, vek};
|
||||||
|
|
||||||
/// The Minecraft protocol version that this library targets.
|
/// The Minecraft protocol version this library currently targets.
|
||||||
pub const PROTOCOL_VERSION: i32 = 759;
|
pub const PROTOCOL_VERSION: i32 = 759;
|
||||||
/// The name of the Minecraft version that this library targets.
|
/// The name of the Minecraft version this library currently targets, e.g.
|
||||||
|
/// "1.8.2"
|
||||||
pub const VERSION_NAME: &str = "1.19";
|
pub const VERSION_NAME: &str = "1.19";
|
||||||
|
|
||||||
/// The namespace for this library used internally for namespaced identifiers.
|
/// The namespace for this library used internally for
|
||||||
|
/// [identifiers](crate::ident::Ident).
|
||||||
|
///
|
||||||
|
/// You should avoid using this namespace in your own identifiers.
|
||||||
const LIBRARY_NAMESPACE: &str = "valence";
|
const LIBRARY_NAMESPACE: &str = "valence";
|
||||||
|
|
||||||
/// A discrete unit of time where 1 tick is the duration of a
|
/// A discrete unit of time where 1 tick is the duration of a
|
||||||
|
@ -51,3 +157,7 @@ const LIBRARY_NAMESPACE: &str = "valence";
|
||||||
/// The duration of a game update depends on the current configuration, which
|
/// The duration of a game update depends on the current configuration, which
|
||||||
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
||||||
pub type Ticks = i64;
|
pub type Ticks = i64;
|
||||||
|
|
||||||
|
/// Whatever dude
|
||||||
|
#[cfg(feature = "whatever")]
|
||||||
|
pub type Whatever = i64;
|
||||||
|
|
|
@ -6,13 +6,20 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::client::GameMode;
|
use crate::client::GameMode;
|
||||||
use crate::player_textures::SignedPlayerTextures;
|
use crate::player_textures::SignedPlayerTextures;
|
||||||
use crate::protocol::packets::play::s2c::{
|
use crate::protocol_inner::packets::play::s2c::{
|
||||||
PlayerInfo, PlayerInfoAddPlayer, S2cPlayPacket, TabList,
|
PlayerInfo, PlayerInfoAddPlayer, S2cPlayPacket, TabList,
|
||||||
};
|
};
|
||||||
use crate::protocol::packets::Property;
|
use crate::protocol_inner::packets::Property;
|
||||||
use crate::protocol::VarInt;
|
use crate::protocol_inner::VarInt;
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
|
|
||||||
|
/// The list of players on a server visible by pressing the tab key by default.
|
||||||
|
///
|
||||||
|
/// Each entry in the player list is intended to represent a connected client to
|
||||||
|
/// the server.
|
||||||
|
///
|
||||||
|
/// In addition to a list of players, the player list has a header and a footer
|
||||||
|
/// which can contain arbitrary text.
|
||||||
pub struct PlayerList {
|
pub struct PlayerList {
|
||||||
entries: HashMap<Uuid, PlayerListEntry>,
|
entries: HashMap<Uuid, PlayerListEntry>,
|
||||||
removed: HashSet<Uuid>,
|
removed: HashSet<Uuid>,
|
||||||
|
@ -32,6 +39,10 @@ impl PlayerList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts a player into the player list.
|
||||||
|
///
|
||||||
|
/// If the given UUID conflicts with an existing entry, the entry is
|
||||||
|
/// overwritten and `false` is returned. Otherwise, `true` is returned.
|
||||||
pub fn insert(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
|
@ -40,7 +51,7 @@ impl PlayerList {
|
||||||
game_mode: GameMode,
|
game_mode: GameMode,
|
||||||
ping: i32,
|
ping: i32,
|
||||||
display_name: impl Into<Option<Text>>,
|
display_name: impl Into<Option<Text>>,
|
||||||
) {
|
) -> bool {
|
||||||
match self.entries.entry(uuid) {
|
match self.entries.entry(uuid) {
|
||||||
Entry::Occupied(mut oe) => {
|
Entry::Occupied(mut oe) => {
|
||||||
let e = oe.get_mut();
|
let e = oe.get_mut();
|
||||||
|
@ -62,6 +73,7 @@ impl PlayerList {
|
||||||
e.set_ping(ping);
|
e.set_ping(ping);
|
||||||
e.set_display_name(display_name);
|
e.set_display_name(display_name);
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
Entry::Vacant(ve) => {
|
Entry::Vacant(ve) => {
|
||||||
ve.insert(PlayerListEntry {
|
ve.insert(PlayerListEntry {
|
||||||
|
@ -72,10 +84,13 @@ impl PlayerList {
|
||||||
display_name: display_name.into(),
|
display_name: display_name.into(),
|
||||||
flags: EntryFlags::new().with_created_this_tick(true),
|
flags: EntryFlags::new().with_created_this_tick(true),
|
||||||
});
|
});
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes an entry from the player list with the given UUID. Returns
|
||||||
|
/// whether the entry was present in the list.
|
||||||
pub fn remove(&mut self, uuid: Uuid) -> bool {
|
pub fn remove(&mut self, uuid: Uuid) -> bool {
|
||||||
if self.entries.remove(&uuid).is_some() {
|
if self.entries.remove(&uuid).is_some() {
|
||||||
self.removed.insert(uuid);
|
self.removed.insert(uuid);
|
||||||
|
@ -85,10 +100,31 @@ impl PlayerList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes all entries from the player list for which `f` returns `true`.
|
||||||
|
///
|
||||||
|
/// All entries are visited in an unspecified order.
|
||||||
|
pub fn retain(&mut self, mut f: impl FnMut(Uuid, &mut PlayerListEntry) -> bool) {
|
||||||
|
self.entries.retain(|&uuid, entry| {
|
||||||
|
if !f(uuid, entry) {
|
||||||
|
self.removed.insert(uuid);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all entries from the player list.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.removed.extend(self.entries.drain().map(|p| p.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the header part of the player list.
|
||||||
pub fn header(&self) -> &Text {
|
pub fn header(&self) -> &Text {
|
||||||
&self.header
|
&self.header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the header part of the player list.
|
||||||
pub fn set_header(&mut self, header: impl Into<Text>) {
|
pub fn set_header(&mut self, header: impl Into<Text>) {
|
||||||
let header = header.into();
|
let header = header.into();
|
||||||
if self.header != header {
|
if self.header != header {
|
||||||
|
@ -97,10 +133,12 @@ impl PlayerList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the footer part of the player list.
|
||||||
pub fn footer(&self) -> &Text {
|
pub fn footer(&self) -> &Text {
|
||||||
&self.footer
|
&self.footer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the footer part of the player list.
|
||||||
pub fn set_footer(&mut self, footer: impl Into<Text>) {
|
pub fn set_footer(&mut self, footer: impl Into<Text>) {
|
||||||
let footer = footer.into();
|
let footer = footer.into();
|
||||||
if self.footer != footer {
|
if self.footer != footer {
|
||||||
|
@ -109,10 +147,13 @@ impl PlayerList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all entries in an unspecified order.
|
||||||
pub fn entries(&self) -> impl Iterator<Item = (Uuid, &PlayerListEntry)> + '_ {
|
pub fn entries(&self) -> impl Iterator<Item = (Uuid, &PlayerListEntry)> + '_ {
|
||||||
self.entries.iter().map(|(k, v)| (*k, v))
|
self.entries.iter().map(|(k, v)| (*k, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator which allows modifications over all entries. The
|
||||||
|
/// entries are visited in an unspecified order.
|
||||||
pub fn entries_mut(&mut self) -> impl Iterator<Item = (Uuid, &mut PlayerListEntry)> + '_ {
|
pub fn entries_mut(&mut self) -> impl Iterator<Item = (Uuid, &mut PlayerListEntry)> + '_ {
|
||||||
self.entries.iter_mut().map(|(k, v)| (*k, v))
|
self.entries.iter_mut().map(|(k, v)| (*k, v))
|
||||||
}
|
}
|
||||||
|
@ -240,6 +281,7 @@ impl PlayerList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a player entry in the [`PlayerList`].
|
||||||
pub struct PlayerListEntry {
|
pub struct PlayerListEntry {
|
||||||
username: String,
|
username: String,
|
||||||
textures: Option<SignedPlayerTextures>,
|
textures: Option<SignedPlayerTextures>,
|
||||||
|
@ -250,6 +292,7 @@ pub struct PlayerListEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerListEntry {
|
impl PlayerListEntry {
|
||||||
|
/// Gets the username of this entry.
|
||||||
pub fn username(&self) -> &str {
|
pub fn username(&self) -> &str {
|
||||||
&self.username
|
&self.username
|
||||||
}
|
}
|
||||||
|
@ -258,10 +301,12 @@ impl PlayerListEntry {
|
||||||
self.textures.as_ref()
|
self.textures.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the game mode of this entry.
|
||||||
pub fn game_mode(&self) -> GameMode {
|
pub fn game_mode(&self) -> GameMode {
|
||||||
self.game_mode
|
self.game_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the game mode of this entry.
|
||||||
pub fn set_game_mode(&mut self, game_mode: GameMode) {
|
pub fn set_game_mode(&mut self, game_mode: GameMode) {
|
||||||
if self.game_mode != game_mode {
|
if self.game_mode != game_mode {
|
||||||
self.game_mode = game_mode;
|
self.game_mode = game_mode;
|
||||||
|
@ -269,10 +314,12 @@ impl PlayerListEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the ping (latency) of this entry measured in milliseconds.
|
||||||
pub fn ping(&self) -> i32 {
|
pub fn ping(&self) -> i32 {
|
||||||
self.ping
|
self.ping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the ping (latency) of this entry measured in milliseconds.
|
||||||
pub fn set_ping(&mut self, ping: i32) {
|
pub fn set_ping(&mut self, ping: i32) {
|
||||||
if self.ping != ping {
|
if self.ping != ping {
|
||||||
self.ping = ping;
|
self.ping = ping;
|
||||||
|
@ -280,10 +327,12 @@ impl PlayerListEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the display name of this entry.
|
||||||
pub fn display_name(&self) -> Option<&Text> {
|
pub fn display_name(&self) -> Option<&Text> {
|
||||||
self.display_name.as_ref()
|
self.display_name.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the display name of this entry.
|
||||||
pub fn set_display_name(&mut self, display_name: impl Into<Option<Text>>) {
|
pub fn set_display_name(&mut self, display_name: impl Into<Option<Text>>) {
|
||||||
let display_name = display_name.into();
|
let display_name = display_name.into();
|
||||||
if self.display_name != display_name {
|
if self.display_name != display_name {
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
//! Player skins and capes.
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// Contains URLs to the skin and cape of a player.
|
||||||
|
///
|
||||||
|
/// This data has been cryptographically signed to ensure it will not be altered
|
||||||
|
/// by the server.
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct SignedPlayerTextures {
|
pub struct SignedPlayerTextures {
|
||||||
payload: Box<[u8]>,
|
payload: Box<[u8]>,
|
||||||
|
@ -9,14 +15,15 @@ pub struct SignedPlayerTextures {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignedPlayerTextures {
|
impl SignedPlayerTextures {
|
||||||
pub fn payload(&self) -> &[u8] {
|
pub(crate) fn payload(&self) -> &[u8] {
|
||||||
&self.payload
|
&self.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signature(&self) -> &[u8] {
|
pub(crate) fn signature(&self) -> &[u8] {
|
||||||
&self.signature
|
&self.signature
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the unsigned texture URLs.
|
||||||
pub fn to_textures(&self) -> PlayerTextures {
|
pub fn to_textures(&self) -> PlayerTextures {
|
||||||
self.to_textures_fallible()
|
self.to_textures_fallible()
|
||||||
.expect("payload should have been validated earlier")
|
.expect("payload should have been validated earlier")
|
||||||
|
@ -49,9 +56,14 @@ impl SignedPlayerTextures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains URLs to the skin and cape of a player.
|
||||||
#[derive(Clone, PartialEq, Default, Debug)]
|
#[derive(Clone, PartialEq, Default, Debug)]
|
||||||
pub struct PlayerTextures {
|
pub struct PlayerTextures {
|
||||||
|
/// A URL to the skin of a player. Is `None` if the player does not have a
|
||||||
|
/// skin.
|
||||||
pub skin: Option<Url>,
|
pub skin: Option<Url>,
|
||||||
|
/// A URL to the cape of a player. Is `None` if the player does not have a
|
||||||
|
/// cape.
|
||||||
pub cape: Option<Url>,
|
pub cape: Option<Url>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +75,7 @@ impl From<SignedPlayerTextures> for PlayerTextures {
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
pub struct PlayerTexturesPayload {
|
struct PlayerTexturesPayload {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
skin: Option<TextureUrl>,
|
skin: Option<TextureUrl>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
|
|
@ -21,12 +21,12 @@ use vek::{Vec2, Vec3, Vec4};
|
||||||
|
|
||||||
use crate::entity::EntityId;
|
use crate::entity::EntityId;
|
||||||
|
|
||||||
/// Trait for types that can be written to the Minecraft protocol.
|
/// Types that can be written to the Minecraft protocol.
|
||||||
pub trait Encode {
|
pub trait Encode {
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()>;
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for types that can be read from the Minecraft protocol.
|
/// Types that can be read from the Minecraft protocol.
|
||||||
pub trait Decode: Sized {
|
pub trait Decode: Sized {
|
||||||
fn decode(r: &mut impl Read) -> anyhow::Result<Self>;
|
fn decode(r: &mut impl Read) -> anyhow::Result<Self>;
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use crate::protocol::{Decode, Encode};
|
use crate::protocol_inner::{Decode, Encode};
|
||||||
|
|
||||||
/// Represents an angle in steps of 1/256 of a full turn.
|
/// Represents an angle in steps of 1/256 of a full turn.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
@ -11,9 +11,9 @@ impl ByteAngle {
|
||||||
ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8)
|
ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn to_degrees(self) -> f32 {
|
pub fn to_degrees(self) -> f32 {
|
||||||
// self.0 as f32 / 256.0 * 360.0
|
self.0 as f32 / 256.0 * 360.0
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for ByteAngle {
|
impl Encode for ByteAngle {
|
||||||
|
@ -24,6 +24,6 @@ impl Encode for ByteAngle {
|
||||||
|
|
||||||
impl Decode for ByteAngle {
|
impl Decode for ByteAngle {
|
||||||
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
|
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
|
||||||
Ok(ByteAngle(u8::decode(r)?))
|
u8::decode(r).map(ByteAngle)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,7 +12,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
use super::packets::{DecodePacket, EncodePacket};
|
use super::packets::{DecodePacket, EncodePacket};
|
||||||
use crate::protocol::{Decode, Encode, VarInt, MAX_PACKET_SIZE};
|
use crate::protocol_inner::{Decode, Encode, VarInt, MAX_PACKET_SIZE};
|
||||||
|
|
||||||
pub struct Encoder<W> {
|
pub struct Encoder<W> {
|
||||||
write: W,
|
write: W,
|
||||||
|
@ -249,7 +249,7 @@ mod tests {
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::protocol::packets::test::TestPacket;
|
use crate::protocol_inner::packets::test::TestPacket;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn encode_decode() {
|
async fn encode_decode() {
|
|
@ -1,8 +1,6 @@
|
||||||
//! Contains packet definitions and some types contained within them.
|
//! Packet definitions and some types contained within them.
|
||||||
//!
|
//!
|
||||||
//! See <https://wiki.vg/Protocol> for up to date protocol information.
|
//! See <https://wiki.vg/Protocol> for more packet documentation.
|
||||||
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -17,7 +15,7 @@ use vek::Vec3;
|
||||||
|
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::protocol::{
|
use crate::protocol_inner::{
|
||||||
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, Nbt, RawBytes, VarInt,
|
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, Nbt, RawBytes, VarInt,
|
||||||
VarLong,
|
VarLong,
|
||||||
};
|
};
|
||||||
|
@ -319,13 +317,13 @@ macro_rules! def_bitfield {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $crate::protocol::Encode for $name {
|
impl Encode for $name {
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
self.0.encode(w)
|
self.0.encode(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $crate::protocol::Decode for $name {
|
impl Decode for $name {
|
||||||
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
|
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
|
||||||
<$inner_ty>::decode(r).map(Self)
|
<$inner_ty>::decode(r).map(Self)
|
||||||
}
|
}
|
||||||
|
@ -457,7 +455,7 @@ pub mod status {
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
PongResponse 0x01 {
|
PongResponse 0x01 {
|
||||||
/// Should be the same as the payload from [`Ping`].
|
/// Should be the same as the payload from ping.
|
||||||
payload: u64
|
payload: u64
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
// TODO: use derive_more?
|
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
use crate::protocol::{Decode, Encode};
|
use crate::protocol_inner::{Decode, Encode};
|
||||||
|
|
||||||
|
/// An `i32` encoded with variable length.
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct VarInt(pub i32);
|
pub struct VarInt(pub i32);
|
||||||
|
|
|
@ -3,8 +3,9 @@ use std::io::{Read, Write};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
use crate::protocol::{Decode, Encode};
|
use crate::protocol_inner::{Decode, Encode};
|
||||||
|
|
||||||
|
/// An `i64` encoded with variable length.
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct VarLong(pub(crate) i64);
|
pub struct VarLong(pub(crate) i64);
|
||||||
|
|
|
@ -31,30 +31,42 @@ use crate::config::{Config, ServerListPing};
|
||||||
use crate::dimension::{Dimension, DimensionId};
|
use crate::dimension::{Dimension, DimensionId};
|
||||||
use crate::entity::Entities;
|
use crate::entity::Entities;
|
||||||
use crate::player_textures::SignedPlayerTextures;
|
use crate::player_textures::SignedPlayerTextures;
|
||||||
use crate::protocol::codec::{Decoder, Encoder};
|
use crate::protocol_inner::codec::{Decoder, Encoder};
|
||||||
use crate::protocol::packets::handshake::{Handshake, HandshakeNextState};
|
use crate::protocol_inner::packets::handshake::{Handshake, HandshakeNextState};
|
||||||
use crate::protocol::packets::login::c2s::{EncryptionResponse, LoginStart, VerifyTokenOrMsgSig};
|
use crate::protocol_inner::packets::login::c2s::{
|
||||||
use crate::protocol::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression};
|
EncryptionResponse, LoginStart, VerifyTokenOrMsgSig,
|
||||||
use crate::protocol::packets::play::c2s::C2sPlayPacket;
|
};
|
||||||
use crate::protocol::packets::play::s2c::S2cPlayPacket;
|
use crate::protocol_inner::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression};
|
||||||
use crate::protocol::packets::status::c2s::{PingRequest, StatusRequest};
|
use crate::protocol_inner::packets::play::c2s::C2sPlayPacket;
|
||||||
use crate::protocol::packets::status::s2c::{PongResponse, StatusResponse};
|
use crate::protocol_inner::packets::play::s2c::S2cPlayPacket;
|
||||||
use crate::protocol::packets::{login, Property};
|
use crate::protocol_inner::packets::status::c2s::{PingRequest, StatusRequest};
|
||||||
use crate::protocol::{BoundedArray, BoundedString, VarInt};
|
use crate::protocol_inner::packets::status::s2c::{PongResponse, StatusResponse};
|
||||||
|
use crate::protocol_inner::packets::{login, Property};
|
||||||
|
use crate::protocol_inner::{BoundedArray, BoundedString, VarInt};
|
||||||
use crate::util::valid_username;
|
use crate::util::valid_username;
|
||||||
use crate::world::Worlds;
|
use crate::world::Worlds;
|
||||||
use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME};
|
use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME};
|
||||||
|
|
||||||
|
/// Contains the entire state of a running Minecraft server, accessible from
|
||||||
|
/// within the [update](crate::config::Config::update) loop.
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
/// A handle to this server's [`SharedServer`].
|
||||||
pub shared: SharedServer,
|
pub shared: SharedServer,
|
||||||
|
/// All of the clients in the server.
|
||||||
pub clients: Clients,
|
pub clients: Clients,
|
||||||
|
/// All of entities in the server.
|
||||||
pub entities: Entities,
|
pub entities: Entities,
|
||||||
|
/// All of the worlds in the server.
|
||||||
pub worlds: Worlds,
|
pub worlds: Worlds,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle to a running Minecraft server containing state which is accessible
|
/// A handle to a Minecraft server containing the subset of functionality which
|
||||||
/// outside the update loop. Servers are internally refcounted and can be shared
|
/// is accessible outside the [update][update] loop.
|
||||||
/// between threads.
|
///
|
||||||
|
/// `SharedServer`s are internally refcounted and can
|
||||||
|
/// be shared between threads.
|
||||||
|
///
|
||||||
|
/// [update]: crate::config::Config::update
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SharedServer(Arc<SharedServerInner>);
|
pub struct SharedServer(Arc<SharedServerInner>);
|
||||||
|
|
||||||
|
@ -105,43 +117,49 @@ struct NewClientMessage {
|
||||||
reply: oneshot::Sender<S2cPacketChannels>,
|
reply: oneshot::Sender<S2cPacketChannels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result type returned from [`ServerConfig::start`] after the server is
|
/// The result type returned from [`start_server`].
|
||||||
/// shut down.
|
pub type ShutdownResult = Result<(), Box<dyn Error + Send + Sync + 'static>>;
|
||||||
pub type ShutdownResult = Result<(), ShutdownError>;
|
|
||||||
pub type ShutdownError = Box<dyn Error + Send + Sync + 'static>;
|
|
||||||
|
|
||||||
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
|
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
|
||||||
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
|
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
|
||||||
|
|
||||||
impl SharedServer {
|
impl SharedServer {
|
||||||
|
/// Gets a reference to the config object used to start the server.
|
||||||
pub fn config(&self) -> &(impl Config + ?Sized) {
|
pub fn config(&self) -> &(impl Config + ?Sized) {
|
||||||
self.0.cfg.as_ref()
|
self.0.cfg.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the socket address this server is bound to.
|
||||||
pub fn address(&self) -> SocketAddr {
|
pub fn address(&self) -> SocketAddr {
|
||||||
self.0.address
|
self.0.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the configured tick rate of this server.
|
||||||
pub fn tick_rate(&self) -> Ticks {
|
pub fn tick_rate(&self) -> Ticks {
|
||||||
self.0.tick_rate
|
self.0.tick_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether online mode is enabled on this server.
|
||||||
pub fn online_mode(&self) -> bool {
|
pub fn online_mode(&self) -> bool {
|
||||||
self.0.online_mode
|
self.0.online_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the maximum number of connections allowed to the server at once.
|
||||||
pub fn max_connections(&self) -> usize {
|
pub fn max_connections(&self) -> usize {
|
||||||
self.0.max_connections
|
self.0.max_connections
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the configured incoming packet capacity.
|
||||||
pub fn incoming_packet_capacity(&self) -> usize {
|
pub fn incoming_packet_capacity(&self) -> usize {
|
||||||
self.0.incoming_packet_capacity
|
self.0.incoming_packet_capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the configured outgoing incoming packet capacity.
|
||||||
pub fn outgoing_packet_capacity(&self) -> usize {
|
pub fn outgoing_packet_capacity(&self) -> usize {
|
||||||
self.0.outgoing_packet_capacity
|
self.0.outgoing_packet_capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a handle to the tokio instance this server is using.
|
||||||
pub fn tokio_handle(&self) -> &Handle {
|
pub fn tokio_handle(&self) -> &Handle {
|
||||||
&self.0.tokio_handle
|
&self.0.tokio_handle
|
||||||
}
|
}
|
||||||
|
@ -197,7 +215,7 @@ impl SharedServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Immediately stops new connections to the server and initiates server
|
/// Immediately stops new connections to the server and initiates server
|
||||||
/// shutdown. The given result is returned through [`ServerConfig::start`].
|
/// shutdown. The given result is returned through [`start_server`].
|
||||||
///
|
///
|
||||||
/// You may want to disconnect all players with a message prior to calling
|
/// You may want to disconnect all players with a message prior to calling
|
||||||
/// this function.
|
/// this function.
|
||||||
|
@ -213,10 +231,10 @@ impl SharedServer {
|
||||||
|
|
||||||
/// Consumes the configuration and starts the server.
|
/// Consumes the configuration and starts the server.
|
||||||
///
|
///
|
||||||
/// The function returns when the server has shut down, a runtime error
|
/// The function returns once the server has shut down, a runtime error
|
||||||
/// occurs, or the configuration is invalid.
|
/// occurs, or the configuration is invalid.
|
||||||
pub fn start_server(config: impl Config) -> ShutdownResult {
|
pub fn start_server(config: impl Config) -> ShutdownResult {
|
||||||
let shared = setup_server(config).map_err(ShutdownError::from)?;
|
let shared = setup_server(config).map_err(Box::<dyn Error + Send + Sync + 'static>::from)?;
|
||||||
|
|
||||||
let _guard = shared.tokio_handle().enter();
|
let _guard = shared.tokio_handle().enter();
|
||||||
|
|
||||||
|
@ -462,7 +480,7 @@ async fn do_accept_loop(server: SharedServer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::debug!("connection to {remote_addr} ended: {e:#}");
|
log::error!("connection to {remote_addr} ended: {e:#}");
|
||||||
}
|
}
|
||||||
drop(permit);
|
drop(permit);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Efficient spatial entity queries.
|
||||||
|
|
||||||
use vek::{Aabb, Vec3};
|
use vek::{Aabb, Vec3};
|
||||||
|
|
||||||
use crate::bvh::Bvh;
|
use crate::bvh::Bvh;
|
||||||
|
@ -5,6 +7,14 @@ pub use crate::bvh::TraverseStep;
|
||||||
use crate::entity::{Entities, EntityId};
|
use crate::entity::{Entities, EntityId};
|
||||||
use crate::world::WorldId;
|
use crate::world::WorldId;
|
||||||
|
|
||||||
|
/// A data structure for fast spatial queries on entity [hitboxes]. This is used
|
||||||
|
/// to accelerate tasks such as collision detection and ray tracing.
|
||||||
|
///
|
||||||
|
/// The spatial index is only updated at the end of each tick. Any modification
|
||||||
|
/// to an entity that would change its hitbox is not reflected in the spatial
|
||||||
|
/// index until the end of the tick.
|
||||||
|
///
|
||||||
|
/// [hitboxes]: crate::entity::Entity::hitbox
|
||||||
pub struct SpatialIndex {
|
pub struct SpatialIndex {
|
||||||
bvh: Bvh<EntityId>,
|
bvh: Bvh<EntityId>,
|
||||||
}
|
}
|
||||||
|
@ -14,13 +24,46 @@ impl SpatialIndex {
|
||||||
Self { bvh: Bvh::new() }
|
Self { bvh: Bvh::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn traverse<F, T>(&self, mut f: F) -> Option<T>
|
#[doc(hidden)]
|
||||||
where
|
#[deprecated = "This is for documentation tests only"]
|
||||||
F: FnMut(Option<EntityId>, Aabb<f64>) -> TraverseStep<T>,
|
pub fn example_new() -> Self {
|
||||||
{
|
println!("Don't call me!");
|
||||||
self.bvh.traverse(|e, bb| f(e.cloned(), bb))
|
Self::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Invokes `f` with every entity in the spatial index considered
|
||||||
|
/// colliding according to `collides`.
|
||||||
|
///
|
||||||
|
/// `collides` takes an AABB and returns whether or not a collision
|
||||||
|
/// occurred with the given AABB.
|
||||||
|
///
|
||||||
|
/// `f` is called with the entity ID and hitbox of all colliding entities.
|
||||||
|
/// If `f` returns with `Some(x)`, then `query` exits early with
|
||||||
|
/// `Some(x)`. If `f` never returns with `Some`, then query returns `None`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Visit all entities intersecting a 10x10x10 cube centered at the origin.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[allow(deprecated)]
|
||||||
|
/// # let si = valence::spatial_index::SpatialIndex::example_new();
|
||||||
|
/// use valence::vek::*;
|
||||||
|
///
|
||||||
|
/// let cube = Aabb {
|
||||||
|
/// min: Vec3::new(-5.0, -5.0, -5.0),
|
||||||
|
/// max: Vec3::new(5.0, 5.0, 5.0),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let collides = |aabb: Aabb<f64>| aabb.collides_with_aabb(cube);
|
||||||
|
/// let f = |id, _| -> Option<()> {
|
||||||
|
/// println!("Found entity: {id:?}");
|
||||||
|
/// None
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Assume `si` is the spatial index
|
||||||
|
/// si.query(collides, f);
|
||||||
|
/// ```
|
||||||
pub fn query<C, F, T>(&self, mut collides: C, mut f: F) -> Option<T>
|
pub fn query<C, F, T>(&self, mut collides: C, mut f: F) -> Option<T>
|
||||||
where
|
where
|
||||||
C: FnMut(Aabb<f64>) -> bool,
|
C: FnMut(Aabb<f64>) -> bool,
|
||||||
|
@ -97,11 +140,22 @@ impl SpatialIndex {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Explores the spatial index according to `f`.
|
||||||
|
///
|
||||||
|
/// This is a low-level function that should only be used if the other
|
||||||
|
/// methods on this type are too restrictive.
|
||||||
|
pub fn traverse<F, T>(&self, mut f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnMut(Option<EntityId>, Aabb<f64>) -> TraverseStep<T>,
|
||||||
|
{
|
||||||
|
self.bvh.traverse(|e, bb| f(e.cloned(), bb))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) {
|
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) {
|
||||||
self.bvh.build(
|
self.bvh.build(
|
||||||
entities
|
entities
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, e)| e.world() == Some(id))
|
.filter(|(_, e)| e.world() == id)
|
||||||
.map(|(id, e)| (id, e.hitbox())),
|
.map(|(id, e)| (id, e.hitbox())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -113,7 +167,7 @@ impl SpatialIndex {
|
||||||
pub struct RaycastHit {
|
pub struct RaycastHit {
|
||||||
/// The [`EntityId`] of the entity that was hit by the ray.
|
/// The [`EntityId`] of the entity that was hit by the ray.
|
||||||
pub entity: EntityId,
|
pub entity: EntityId,
|
||||||
/// The bounding box of the entity that was hit.
|
/// The bounding box (hitbox) of the entity that was hit.
|
||||||
pub bb: Aabb<f64>,
|
pub bb: Aabb<f64>,
|
||||||
/// The distance from the ray origin to the closest intersection point.
|
/// The distance from the ray origin to the closest intersection point.
|
||||||
/// If the origin of the ray is inside the bounding box, then this will be
|
/// If the origin of the ray is inside the bounding box, then this will be
|
||||||
|
|
23
src/text.rs
23
src/text.rs
|
@ -1,6 +1,3 @@
|
||||||
// TODO: Documentation.
|
|
||||||
// TODO: make fields of Text public?
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -9,19 +6,22 @@ use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::protocol::{BoundedString, Decode, Encode};
|
use crate::protocol_inner::{BoundedString, Decode, Encode};
|
||||||
|
|
||||||
/// Represents formatted text in Minecraft's JSON text format.
|
/// Represents formatted text in Minecraft's JSON text format.
|
||||||
///
|
///
|
||||||
/// Text is used in various places such as chat, window titles,
|
/// Text is used in various places such as chat, window titles,
|
||||||
/// disconnect messages, written books, signs, and more.
|
/// disconnect messages, written books, signs, and more.
|
||||||
///
|
///
|
||||||
/// For more information, see the relevant [Minecraft wiki article](https://minecraft.fandom.com/wiki/Raw_JSON_text_format).
|
/// For more information, see the relevant [Minecraft Wiki article].
|
||||||
///
|
///
|
||||||
/// Note that the current `Deserialize` implementation on this type recognizes
|
/// Note that the current `Deserialize` implementation on this type recognizes
|
||||||
/// only a subset of the full JSON chat component format.
|
/// only a subset of the full JSON chat component format.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// [Minecraft Wiki article]: https://minecraft.fandom.com/wiki/Raw_JSON_text_format
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
/// With [`TextFormat`] in scope, you can write the following:
|
/// With [`TextFormat`] in scope, you can write the following:
|
||||||
/// ```
|
/// ```
|
||||||
/// use valence::text::{Color, Text, TextFormat};
|
/// use valence::text::{Color, Text, TextFormat};
|
||||||
|
@ -82,6 +82,8 @@ pub struct Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
|
/// Returns `true` if the text contains no characters. Returns `false`
|
||||||
|
/// otherwise.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
for extra in &self.extra {
|
for extra in &self.extra {
|
||||||
if !extra.is_empty() {
|
if !extra.is_empty() {
|
||||||
|
@ -99,7 +101,7 @@ impl Text {
|
||||||
/// Provides the methods necessary for working with [`Text`] objects.
|
/// Provides the methods necessary for working with [`Text`] objects.
|
||||||
///
|
///
|
||||||
/// This trait exists to allow using `Into<Text>` types without having to first
|
/// This trait exists to allow using `Into<Text>` types without having to first
|
||||||
/// convert the type into [`Text`]. It is automatically implemented for all
|
/// convert the type into [`Text`]. A blanket implementation exists for all
|
||||||
/// `Into<Text>` types, including [`Text`] itself.
|
/// `Into<Text>` types, including [`Text`] itself.
|
||||||
pub trait TextFormat: Into<Text> {
|
pub trait TextFormat: Into<Text> {
|
||||||
fn into_text(self) -> Text {
|
fn into_text(self) -> Text {
|
||||||
|
@ -366,8 +368,9 @@ impl Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result {
|
pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result {
|
||||||
if let TextContent::Text { text } = &self.content {
|
match &self.content {
|
||||||
w.write_str(text.as_ref())?;
|
TextContent::Text { text } => w.write_str(text.as_ref())?,
|
||||||
|
TextContent::Translate { translate } => w.write_str(translate.as_ref())?,
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in &self.extra {
|
for child in &self.extra {
|
||||||
|
@ -376,8 +379,6 @@ impl Text {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: getters
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<Text>> TextFormat for T {}
|
impl<T: Into<Text>> TextFormat for T {}
|
||||||
|
|
26
src/util.rs
26
src/util.rs
|
@ -1,3 +1,5 @@
|
||||||
|
//! Miscellaneous utilities.
|
||||||
|
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
use num::cast::AsPrimitive;
|
use num::cast::AsPrimitive;
|
||||||
|
@ -8,6 +10,20 @@ use crate::chunk_pos::ChunkPos;
|
||||||
|
|
||||||
/// Returns true if the given string meets the criteria for a valid Minecraft
|
/// Returns true if the given string meets the criteria for a valid Minecraft
|
||||||
/// username.
|
/// username.
|
||||||
|
///
|
||||||
|
/// Usernames are valid if they match the regex `^[a-zA-Z0-9_]{3,16}$`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use valence::util::valid_username;
|
||||||
|
///
|
||||||
|
/// assert!(valid_username("00a"));
|
||||||
|
/// assert!(valid_username("jeb_"));
|
||||||
|
///
|
||||||
|
/// assert!(!valid_username("notavalidusername"));
|
||||||
|
/// assert!(!valid_username("NotValid!"))
|
||||||
|
/// ```
|
||||||
pub fn valid_username(s: &str) -> bool {
|
pub fn valid_username(s: &str) -> bool {
|
||||||
(3..=16).contains(&s.len())
|
(3..=16).contains(&s.len())
|
||||||
&& s.chars()
|
&& s.chars()
|
||||||
|
@ -16,6 +32,8 @@ pub fn valid_username(s: &str) -> bool {
|
||||||
|
|
||||||
const EXTRA_RADIUS: i32 = 3;
|
const EXTRA_RADIUS: i32 = 3;
|
||||||
|
|
||||||
|
/// Returns an iterator over all chunk positions within a view distance,
|
||||||
|
/// centered on a particular chunk position.
|
||||||
pub fn chunks_in_view_distance(
|
pub fn chunks_in_view_distance(
|
||||||
center: ChunkPos,
|
center: ChunkPos,
|
||||||
distance: u8,
|
distance: u8,
|
||||||
|
@ -26,8 +44,8 @@ pub fn chunks_in_view_distance(
|
||||||
.filter(move |&p| is_chunk_in_view_distance(center, p, distance))
|
.filter(move |&p| is_chunk_in_view_distance(center, p, distance))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_chunk_in_view_distance(center: ChunkPos, other: ChunkPos, distance: u8) -> bool {
|
pub fn is_chunk_in_view_distance(p0: ChunkPos, p1: ChunkPos, distance: u8) -> bool {
|
||||||
(center.x as f64 - other.x as f64).powi(2) + (center.z as f64 - other.z as f64).powi(2)
|
(p0.x as f64 - p1.x as f64).powi(2) + (p0.z as f64 - p1.z as f64).powi(2)
|
||||||
<= (distance as f64 + EXTRA_RADIUS as f64).powi(2)
|
<= (distance as f64 + EXTRA_RADIUS as f64).powi(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +72,10 @@ where
|
||||||
aabb
|
aabb
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a normalized direction vector and returns a (yaw, pitch) tuple in
|
/// Takes a normalized direction vector and returns a `(yaw, pitch)` tuple in
|
||||||
/// degrees.
|
/// degrees.
|
||||||
///
|
///
|
||||||
/// This function is the inverse of [`from_yaw_and_pitch`].
|
// /// This function is the inverse of [`from_yaw_and_pitch`].
|
||||||
pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) {
|
pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) {
|
||||||
debug_assert!(d.is_normalized(), "the given vector should be normalized");
|
debug_assert!(d.is_normalized(), "the given vector should be normalized");
|
||||||
|
|
||||||
|
|
55
src/world.rs
55
src/world.rs
|
@ -7,17 +7,27 @@ use crate::dimension::DimensionId;
|
||||||
use crate::player_list::PlayerList;
|
use crate::player_list::PlayerList;
|
||||||
use crate::server::SharedServer;
|
use crate::server::SharedServer;
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::SpatialIndex;
|
use crate::spatial_index::SpatialIndex;
|
||||||
|
|
||||||
pub struct Worlds {
|
pub struct Worlds {
|
||||||
sm: SlotMap<World>,
|
sm: SlotMap<World>,
|
||||||
server: SharedServer,
|
server: SharedServer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A key for a [`World`] on the server.
|
||||||
|
///
|
||||||
|
/// World IDs are either _valid_ or _invalid_. Valid world IDs point to
|
||||||
|
/// worlds that have not been deleted, while invalid IDs point to those that
|
||||||
|
/// have. Once an ID becomes invalid, it will never become valid again.
|
||||||
|
///
|
||||||
|
/// The [`Ord`] instance on this type is correct but otherwise unspecified. This
|
||||||
|
/// is useful for storing IDs in containers such as
|
||||||
|
/// [`BTreeMap`](std::collections::BTreeMap).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
pub struct WorldId(Key);
|
pub struct WorldId(Key);
|
||||||
|
|
||||||
impl WorldId {
|
impl WorldId {
|
||||||
|
/// The value of the default world ID which is always invalid.
|
||||||
pub const NULL: Self = Self(Key::NULL);
|
pub const NULL: Self = Self(Key::NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +39,8 @@ impl Worlds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new world on the server with the provided dimension. A
|
||||||
|
/// reference to the world along with its ID is returned.
|
||||||
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
|
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
|
||||||
let (id, world) = self.sm.insert(World {
|
let (id, world) = self.sm.insert(World {
|
||||||
spatial_index: SpatialIndex::new(),
|
spatial_index: SpatialIndex::new(),
|
||||||
|
@ -43,54 +55,76 @@ impl Worlds {
|
||||||
(WorldId(id), world)
|
(WorldId(id), world)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a world from the server. Any [`WorldId`] referring to the
|
/// Deletes a world from the server.
|
||||||
/// deleted world will be invalidated.
|
|
||||||
///
|
///
|
||||||
/// Note that any entities with positions inside the deleted world will not
|
/// Note that entities located in the world are not deleted themselves.
|
||||||
/// be deleted themselves.
|
/// Additionally, any clients that are still in the deleted world at the end
|
||||||
|
/// of the tick are disconnected.
|
||||||
pub fn delete(&mut self, world: WorldId) -> bool {
|
pub fn delete(&mut self, world: WorldId) -> bool {
|
||||||
self.sm.remove(world.0).is_some()
|
self.sm.remove(world.0).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes all worlds from the server (as if by [`Self::delete`]) for which
|
||||||
|
/// `f` returns `true`.
|
||||||
|
///
|
||||||
|
/// All worlds are visited in an unspecified order.
|
||||||
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) {
|
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) {
|
||||||
self.sm.retain(|k, v| f(WorldId(k), v))
|
self.sm.retain(|k, v| f(WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of worlds on the server.
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.sm.len()
|
self.sm.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a shared reference to the world with the given ID. If
|
||||||
|
/// the ID is invalid, then `None` is returned.
|
||||||
pub fn get(&self, world: WorldId) -> Option<&World> {
|
pub fn get(&self, world: WorldId) -> Option<&World> {
|
||||||
self.sm.get(world.0)
|
self.sm.get(world.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an exclusive reference to the world with the given ID. If the
|
||||||
|
/// ID is invalid, then `None` is returned.
|
||||||
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
|
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
|
||||||
self.sm.get_mut(world.0)
|
self.sm.get_mut(world.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an immutable iterator over all worlds on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
|
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
|
||||||
self.sm.iter().map(|(k, v)| (WorldId(k), v))
|
self.sm.iter().map(|(k, v)| (WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable iterator over all worlds on the server in an
|
||||||
|
/// unspecified ordder.
|
||||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
|
||||||
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
|
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel immutable iterator over all worlds on the server in
|
||||||
|
/// an unspecified order.
|
||||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
|
||||||
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
|
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a parallel mutable iterator over all worlds on the server in an
|
||||||
|
/// unspecified order.
|
||||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
|
||||||
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
|
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A space for chunks, entities, and clients to occupy.
|
||||||
pub struct World {
|
pub struct World {
|
||||||
|
/// Contains all of the entities in this world.
|
||||||
pub spatial_index: SpatialIndex,
|
pub spatial_index: SpatialIndex,
|
||||||
|
/// All of the chunks in this world.
|
||||||
pub chunks: Chunks,
|
pub chunks: Chunks,
|
||||||
|
/// This world's metadata.
|
||||||
pub meta: WorldMeta,
|
pub meta: WorldMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains miscellaneous world state.
|
||||||
pub struct WorldMeta {
|
pub struct WorldMeta {
|
||||||
dimension: DimensionId,
|
dimension: DimensionId,
|
||||||
is_flat: bool,
|
is_flat: bool,
|
||||||
|
@ -99,22 +133,33 @@ pub struct WorldMeta {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorldMeta {
|
impl WorldMeta {
|
||||||
|
/// Gets the dimension the world was created with.
|
||||||
pub fn dimension(&self) -> DimensionId {
|
pub fn dimension(&self) -> DimensionId {
|
||||||
self.dimension
|
self.dimension
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets if this world is considered a superflat world. Superflat worlds
|
||||||
|
/// have a horizon line at y=0.
|
||||||
pub fn is_flat(&self) -> bool {
|
pub fn is_flat(&self) -> bool {
|
||||||
self.is_flat
|
self.is_flat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets if this world is considered a superflat world. Superflat worlds
|
||||||
|
/// have a horizon line at y=0.
|
||||||
|
///
|
||||||
|
/// Clients already in the world must be respawned to see any changes.
|
||||||
pub fn set_flat(&mut self, flat: bool) {
|
pub fn set_flat(&mut self, flat: bool) {
|
||||||
self.is_flat = flat;
|
self.is_flat = flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a shared reference to the world's
|
||||||
|
/// [`PlayerList`](crate::player_list::PlayerList).
|
||||||
pub fn player_list(&self) -> &PlayerList {
|
pub fn player_list(&self) -> &PlayerList {
|
||||||
&self.player_list
|
&self.player_list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an exclusive reference to the world's
|
||||||
|
/// [`PlayerList`](crate::player_list::PlayerList).
|
||||||
pub fn player_list_mut(&mut self) -> &mut PlayerList {
|
pub fn player_list_mut(&mut self) -> &mut PlayerList {
|
||||||
&mut self.player_list
|
&mut self.player_list
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue