Clean up dimension/biome code (#239)

This commit is contained in:
Ryan Johnson 2023-02-14 03:36:52 -08:00 committed by GitHub
parent c494b83a56
commit 909f7d3909
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 149 deletions

View file

@ -54,7 +54,7 @@ pub struct Biome {
impl Biome { impl Biome {
pub(crate) fn to_biome_registry_item(&self, id: i32) -> Compound { pub(crate) fn to_biome_registry_item(&self, id: i32) -> Compound {
let mut reg = compound! { compound! {
"name" => self.name.clone(), "name" => self.name.clone(),
"id" => id, "id" => id,
"element" => compound! { "element" => compound! {
@ -68,7 +68,6 @@ impl Biome {
"scale" => 0.05_f32, "scale" => 0.05_f32,
"downfall" => 0.4_f32, "downfall" => 0.4_f32,
"category" => "none", "category" => "none",
// "temperature_modifier" =>
"effects" => { "effects" => {
let mut eff = compound! { let mut eff = compound! {
"sky_color" => self.sky_color as i32, "sky_color" => self.sky_color as i32,
@ -120,24 +119,22 @@ impl Biome {
}); });
} }
if let Some(p) = &self.particle {
eff.insert(
"particle",
compound! {
"probability" => p.probability,
"options" => compound! {
"type" => p.kind.clone(),
}
},
);
}
eff eff
}, },
} }
};
if let Some(p) = &self.particle {
reg.insert(
"particle",
compound! {
"probability" => p.probability,
"options" => compound! {
"type" => p.kind.clone(),
}
},
);
} }
reg
} }
} }

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::net::IpAddr; use std::net::IpAddr;
use std::num::Wrapping; use std::num::Wrapping;
@ -10,12 +11,14 @@ use uuid::Uuid;
use valence_protocol::packets::s2c::particle::Particle; use valence_protocol::packets::s2c::particle::Particle;
use valence_protocol::packets::s2c::play::{ use valence_protocol::packets::s2c::play::{
AcknowledgeBlockChange, CombatDeath, DisconnectPlay, EntityEvent, GameEvent, KeepAliveS2c, AcknowledgeBlockChange, CombatDeath, DisconnectPlay, EntityEvent, GameEvent, KeepAliveS2c,
LoginPlayOwned, ParticleS2c, PluginMessageS2c, RemoveEntitiesEncode, ResourcePackS2c, LoginPlay, ParticleS2c, PluginMessageS2c, RemoveEntitiesEncode, ResourcePackS2c, Respawn,
RespawnOwned, SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata, SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
SetEntityVelocity, SetRenderDistance, SetSubtitleText, SetTitleAnimationTimes, SetTitleText, SetEntityVelocity, SetRenderDistance, SetSubtitleText, SetTitleAnimationTimes, SetTitleText,
SynchronizePlayerPosition, SystemChatMessage, UnloadChunk, SynchronizePlayerPosition, SystemChatMessage, UnloadChunk,
}; };
use valence_protocol::types::{GameEventKind, GameMode, Property, SyncPlayerPosLookFlags}; use valence_protocol::types::{
GameEventKind, GameMode, GlobalPos, Property, SyncPlayerPosLookFlags,
};
use valence_protocol::{ use valence_protocol::{
BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Text, BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Text,
Username, VarInt, Username, VarInt,
@ -636,23 +639,30 @@ fn update_one_client(
if client.is_new { if client.is_new {
client.needs_respawn = false; client.needs_respawn = false;
let dimension_names: Vec<_> = server let dimension_names = server
.dimensions() .dimensions()
.map(|(id, _)| id.dimension_name()) .map(|(_, dim)| dim.name.as_str_ident())
.collect(); .collect();
let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
let last_death_location = client.death_location.map(|(id, pos)| GlobalPos {
dimension_name: server.dimension(id).name.as_str_ident(),
position: pos,
});
// The login packet is prepended so that it is sent before all the other // The login packet is prepended so that it is sent before all the other
// packets. Some packets don't work correctly when sent before the login packet, // packets. Some packets don't work correctly when sent before the login packet,
// which is why we're doing this. // which is why we're doing this.
client.enc.prepend_packet(&LoginPlayOwned { client.enc.prepend_packet(&LoginPlay {
entity_id: 0, // ID 0 is reserved for clients. entity_id: 0, // ID 0 is reserved for clients.
is_hardcore: client.is_hardcore, is_hardcore: client.is_hardcore,
game_mode: client.game_mode, game_mode: client.game_mode,
previous_game_mode: -1, previous_game_mode: -1,
dimension_names, dimension_names,
registry_codec: server.registry_codec().clone(), registry_codec: Cow::Borrowed(server.registry_codec()),
dimension_type_name: instance.dimension().dimension_type_name(), dimension_type_name: dimension_name,
dimension_name: instance.dimension().dimension_name(), dimension_name,
hashed_seed: 42, hashed_seed: 42,
max_players: VarInt(0), // Unused max_players: VarInt(0), // Unused
view_distance: VarInt(client.view_distance() as i32), view_distance: VarInt(client.view_distance() as i32),
@ -661,9 +671,7 @@ fn update_one_client(
enable_respawn_screen: client.has_respawn_screen, enable_respawn_screen: client.has_respawn_screen,
is_debug: false, is_debug: false,
is_flat: client.is_flat, is_flat: client.is_flat,
last_death_location: client last_death_location,
.death_location
.map(|(id, pos)| (id.dimension_name(), pos)),
})?; })?;
/* /*
@ -683,18 +691,23 @@ fn update_one_client(
if client.needs_respawn { if client.needs_respawn {
client.needs_respawn = false; client.needs_respawn = false;
client.enc.append_packet(&RespawnOwned { let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
dimension_type_name: instance.dimension().dimension_type_name(),
dimension_name: instance.dimension().dimension_name(), let last_death_location = client.death_location.map(|(id, pos)| GlobalPos {
dimension_name: server.dimension(id).name.as_str_ident(),
position: pos,
});
client.enc.append_packet(&Respawn {
dimension_type_name: dimension_name,
dimension_name,
hashed_seed: 0, hashed_seed: 0,
game_mode: client.game_mode, game_mode: client.game_mode,
previous_game_mode: -1, previous_game_mode: -1,
is_debug: false, is_debug: false,
is_flat: client.is_flat, is_flat: client.is_flat,
copy_metadata: true, copy_metadata: true,
last_death_location: client last_death_location,
.death_location
.map(|(id, pos)| (id.dimension_name(), pos)),
})?; })?;
} }
} }

View file

@ -1,12 +1,12 @@
//! Dimension configuration and identification. //! Dimension configuration and identification.
use std::collections::HashSet;
use anyhow::ensure; use anyhow::ensure;
use valence_nbt::{compound, Compound}; use valence_nbt::{compound, Compound};
use valence_protocol::ident; use valence_protocol::ident;
use valence_protocol::ident::Ident; use valence_protocol::ident::Ident;
use crate::LIBRARY_NAMESPACE;
/// Identifies a particular [`Dimension`] on the server. /// Identifies a particular [`Dimension`] on the server.
/// ///
/// The default dimension ID refers to the first dimension added in /// The default dimension ID refers to the first dimension added in
@ -15,20 +15,10 @@ use crate::LIBRARY_NAMESPACE;
/// To obtain dimension IDs for other dimensions, look at /// To obtain dimension IDs for other dimensions, look at
/// [`ServerPlugin::dimensions`]. /// [`ServerPlugin::dimensions`].
/// ///
/// [`ServerPlugin::dimensions`]: crate::server::SharedServer::dimensions /// [`ServerPlugin::dimensions`]: crate::config::ServerPlugin::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(crate) fn dimension_type_name(self) -> Ident<String> {
ident!("{LIBRARY_NAMESPACE}:dimension_type_{}", self.0)
}
pub(crate) fn dimension_name(self) -> Ident<String> {
ident!("{LIBRARY_NAMESPACE}:dimension_{}", self.0)
}
}
/// 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 [`ServerPlugin::dimensions`]. /// returned by [`ServerPlugin::dimensions`].
/// ///
@ -53,6 +43,8 @@ impl Default for DimensionId {
/// [`Instance`]: crate::instance::Instance /// [`Instance`]: crate::instance::Instance
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Dimension { pub struct Dimension {
/// The unique name for this dimension.
pub name: Ident<String>,
/// When false, compasses 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.
@ -89,36 +81,42 @@ pub struct Dimension {
} }
impl Dimension { impl Dimension {
pub(crate) fn to_dimension_registry_item(&self) -> Compound { pub(crate) fn to_dimension_registry_item(&self, id: i32) -> Compound {
let mut item = compound! { compound! {
"piglin_safe" => true, "name" => self.name.clone(),
"has_raids" => true, "id" => id,
"monster_spawn_light_level" => 0, "element" => {
"monster_spawn_block_light_limit" => 0, let mut element = compound! {
"natural" => self.natural, "piglin_safe" => true,
"ambient_light" => self.ambient_light, "has_raids" => true,
"infiniburn" => "#minecraft:infiniburn_overworld", "monster_spawn_light_level" => 0,
"respawn_anchor_works" => true, "monster_spawn_block_light_limit" => 0,
"has_skylight" => true, "natural" => self.natural,
"bed_works" => true, "ambient_light" => self.ambient_light,
"effects" => match self.effects { "infiniburn" => "#minecraft:infiniburn_overworld",
DimensionEffects::Overworld => "overworld", "respawn_anchor_works" => true,
DimensionEffects::TheNether => "the_nether", "has_skylight" => true,
DimensionEffects::TheEnd => "the_end", "bed_works" => true,
"effects" => match self.effects {
DimensionEffects::Overworld => "overworld",
DimensionEffects::TheNether => "the_nether",
DimensionEffects::TheEnd => "the_end",
},
"min_y" => self.min_y,
"height" => self.height,
"logical_height" => self.height,
"coordinate_scale" => 1.0,
"ultrawarm" => false,
"has_ceiling" => false,
};
if let Some(t) = self.fixed_time {
element.insert("fixed_time", t as i64);
}
element
}, },
"min_y" => self.min_y,
"height" => self.height,
"logical_height" => self.height,
"coordinate_scale" => 1.0,
"ultrawarm" => false,
"has_ceiling" => false,
};
if let Some(t) = self.fixed_time {
item.insert("fixed_time", t as i64);
} }
item
} }
} }
@ -133,28 +131,37 @@ pub(crate) fn validate_dimensions(dimensions: &[Dimension]) -> anyhow::Result<()
"more than u16::MAX dimensions present" "more than u16::MAX dimensions present"
); );
for (i, dim) in dimensions.iter().enumerate() { let mut names = HashSet::new();
for dim in dimensions {
let name = &dim.name;
ensure!(
names.insert(name.clone()),
"dimension \"{name}\" already exists",
);
ensure!( ensure!(
dim.min_y % 16 == 0 && (-2032..=2016).contains(&dim.min_y), dim.min_y % 16 == 0 && (-2032..=2016).contains(&dim.min_y),
"invalid min_y in dimension #{i}", "invalid min_y in dimension {name}",
); );
ensure!( ensure!(
dim.height % 16 == 0 dim.height % 16 == 0
&& (0..=4064).contains(&dim.height) && (0..=4064).contains(&dim.height)
&& dim.min_y.saturating_add(dim.height) <= 2032, && dim.min_y.saturating_add(dim.height) <= 2032,
"invalid height in dimension #{i}", "invalid height in dimension {name}",
); );
ensure!( ensure!(
(0.0..=1.0).contains(&dim.ambient_light), (0.0..=1.0).contains(&dim.ambient_light),
"ambient_light is out of range in dimension #{i}", "ambient_light is out of range in dimension {name}",
); );
if let Some(fixed_time) = dim.fixed_time { if let Some(fixed_time) = dim.fixed_time {
ensure!( ensure!(
(0..=24_000).contains(&fixed_time), (0..=24_000).contains(&fixed_time),
"fixed_time is out of range in dimension #{i}", "fixed_time is out of range in dimension {name}",
); );
} }
} }
@ -165,6 +172,7 @@ pub(crate) fn validate_dimensions(dimensions: &[Dimension]) -> anyhow::Result<()
impl Default for Dimension { impl Default for Dimension {
fn default() -> Self { fn default() -> Self {
Self { Self {
name: ident!("overworld"),
natural: true, natural: true,
ambient_light: 1.0, ambient_light: 1.0,
fixed_time: None, fixed_time: None,

View file

@ -94,8 +94,6 @@ pub mod prelude {
#[derive(Copy, Clone, Component)] #[derive(Copy, Clone, Component)]
pub struct Despawned; pub struct Despawned;
const LIBRARY_NAMESPACE: &str = "valence";
/// Let's pretend that [`NULL_ENTITY`] was created by spawning an entity, /// Let's pretend that [`NULL_ENTITY`] was created by spawning an entity,
/// immediately despawning it, and then stealing its [`Entity`] ID. The user /// immediately despawning it, and then stealing its [`Entity`] ID. The user
/// doesn't need to know about this. /// doesn't need to know about this.

View file

@ -1,7 +1,7 @@
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -106,8 +106,6 @@ struct SharedServerInner {
/// A semaphore used to limit the number of simultaneous connections to the /// A semaphore used to limit the number of simultaneous connections to the
/// server. Closing this semaphore stops new connections. /// server. Closing this semaphore stops new connections.
connection_sema: Arc<Semaphore>, connection_sema: Arc<Semaphore>,
/// The result that will be returned when the server is shut down.
shutdown_result: Mutex<Option<anyhow::Result<()>>>,
/// The RSA keypair used for encryption with clients. /// The RSA keypair used for encryption with clients.
rsa_key: RsaPrivateKey, rsa_key: RsaPrivateKey,
/// The public part of `rsa_key` encoded in DER, which is an ASN.1 format. /// The public part of `rsa_key` encoded in DER, which is an ASN.1 format.
@ -166,6 +164,7 @@ impl SharedServer {
} }
/// Obtains a [`Dimension`] by using its corresponding [`DimensionId`]. /// Obtains a [`Dimension`] by using its corresponding [`DimensionId`].
#[track_caller]
pub fn dimension(&self, id: DimensionId) -> &Dimension { pub fn dimension(&self, id: DimensionId) -> &Dimension {
self.0 self.0
.dimensions .dimensions
@ -184,6 +183,7 @@ impl SharedServer {
} }
/// Obtains a [`Biome`] by using its corresponding [`BiomeId`]. /// Obtains a [`Biome`] by using its corresponding [`BiomeId`].
#[track_caller]
pub fn biome(&self, id: BiomeId) -> &Biome { pub fn biome(&self, id: BiomeId) -> &Biome {
self.0.biomes.get(id.0 as usize).expect("invalid biome ID") self.0.biomes.get(id.0 as usize).expect("invalid biome ID")
} }
@ -209,19 +209,6 @@ impl SharedServer {
pub fn start_instant(&self) -> Instant { pub fn start_instant(&self) -> Instant {
self.0.start_instant self.0.start_instant
} }
/// Immediately stops new connections to the server and initiates server
/// shutdown.
///
/// You may want to disconnect all players with a message prior to calling
/// this function.
pub fn shutdown<E>(&self, res: Result<(), E>)
where
E: Into<anyhow::Error>,
{
self.0.connection_sema.close();
*self.0.shutdown_result.lock().unwrap() = Some(res.map_err(|e| e.into()));
}
} }
/// Contains information about a new client joining the server. /// Contains information about a new client joining the server.
@ -296,7 +283,6 @@ pub fn build_plugin(
new_clients_send, new_clients_send,
new_clients_recv, new_clients_recv,
connection_sema: Arc::new(Semaphore::new(plugin.max_connections)), connection_sema: Arc::new(Semaphore::new(plugin.max_connections)),
shutdown_result: Mutex::new(None),
rsa_key, rsa_key,
public_key_der, public_key_der,
http_client: Default::default(), http_client: Default::default(),
@ -439,13 +425,7 @@ fn make_registry_codec(dimensions: &[Dimension], biomes: &[Biome]) -> Compound {
let dimensions = dimensions let dimensions = dimensions
.iter() .iter()
.enumerate() .enumerate()
.map(|(id, dim)| { .map(|(id, dim)| dim.to_dimension_registry_item(id as i32))
compound! {
"name" => DimensionId(id as u16).dimension_type_name(),
"id" => id as i32,
"element" => dim.to_dimension_registry_item(),
}
})
.collect(); .collect();
let biomes = biomes let biomes = biomes
@ -461,9 +441,7 @@ fn make_registry_codec(dimensions: &[Dimension], biomes: &[Biome]) -> Compound {
}, },
ident!("worldgen/biome") => compound! { ident!("worldgen/biome") => compound! {
"type" => ident!("worldgen/biome"), "type" => ident!("worldgen/biome"),
"value" => { "value" => List::Compound(biomes),
List::Compound(biomes)
}
}, },
ident!("chat_type") => compound! { ident!("chat_type") => compound! {
"type" => ident!("chat_type"), "type" => ident!("chat_type"),

View file

@ -44,7 +44,7 @@ pub async fn do_accept_loop(shared: SharedServer, callbacks: Arc<impl AsyncCallb
let listener = match TcpListener::bind(shared.0.address).await { let listener = match TcpListener::bind(shared.0.address).await {
Ok(listener) => listener, Ok(listener) => listener,
Err(e) => { Err(e) => {
shared.shutdown(Err(e).context("failed to start TCP listener")); error!("failed to start TCP listener: {e}");
return; return;
} }
}; };

View file

@ -463,7 +463,7 @@ pub mod play {
/// Same values as `game_mode` but with -1 to indicate no previous. /// Same values as `game_mode` but with -1 to indicate no previous.
pub previous_game_mode: i8, pub previous_game_mode: i8,
pub dimension_names: Vec<Ident<&'a str>>, pub dimension_names: Vec<Ident<&'a str>>,
pub registry_codec: Compound, pub registry_codec: Cow<'a, Compound>,
pub dimension_type_name: Ident<&'a str>, pub dimension_type_name: Ident<&'a str>,
pub dimension_name: Ident<&'a str>, pub dimension_name: Ident<&'a str>,
pub hashed_seed: i64, pub hashed_seed: i64,
@ -477,29 +477,6 @@ pub mod play {
pub last_death_location: Option<GlobalPos<'a>>, pub last_death_location: Option<GlobalPos<'a>>,
} }
// TODO: remove this.
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x24]
pub struct LoginPlayOwned {
pub entity_id: i32,
pub is_hardcore: bool,
pub game_mode: GameMode,
pub previous_game_mode: i8,
pub dimension_names: Vec<Ident<String>>,
pub registry_codec: Compound,
pub dimension_type_name: Ident<String>,
pub dimension_name: Ident<String>,
pub hashed_seed: i64,
pub max_players: VarInt,
pub view_distance: VarInt,
pub simulation_distance: VarInt,
pub reduced_debug_info: bool,
pub enable_respawn_screen: bool,
pub is_debug: bool,
pub is_flat: bool,
pub last_death_location: Option<(Ident<String>, BlockPos)>,
}
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)] #[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x26] #[packet_id = 0x26]
pub struct MerchantOffers { pub struct MerchantOffers {
@ -676,21 +653,6 @@ pub mod play {
pub last_death_location: Option<GlobalPos<'a>>, pub last_death_location: Option<GlobalPos<'a>>,
} }
// TODO: remove
#[derive(Clone, PartialEq, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x3d]
pub struct RespawnOwned {
pub dimension_type_name: Ident<String>,
pub dimension_name: Ident<String>,
pub hashed_seed: u64,
pub game_mode: GameMode,
pub previous_game_mode: i8,
pub is_debug: bool,
pub is_flat: bool,
pub copy_metadata: bool,
pub last_death_location: Option<(Ident<String>, BlockPos)>,
}
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)] #[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x3e] #[packet_id = 0x3e]
pub struct SetHeadRotation { pub struct SetHeadRotation {