Registry Redesign (#361)

## Description

- Revert the "biomes/dimensions as entities" idea since it caused too
many problems without much to show for it.
- Make use of `valence_nbt`'s serde support in `valence_biome` and
`valence_dimension`.
- Reduce boilerplate, reorganize `valence_registry` a bit.
- Tweak default biome registry such that `BiomeId::default` always
corresponds to "minecraft:plains".
- Add `Option` and unit variant support to `valence_nbt`'s serde impl.
This commit is contained in:
Ryan Johnson 2023-06-11 09:08:38 -07:00 committed by GitHub
parent e76e913b3c
commit c4741b68b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 690 additions and 1183 deletions

View file

@ -85,7 +85,7 @@ valence_dimension.path = "crates/valence_dimension"
valence_entity.path = "crates/valence_entity" valence_entity.path = "crates/valence_entity"
valence_instance.path = "crates/valence_instance" valence_instance.path = "crates/valence_instance"
valence_inventory.path = "crates/valence_inventory" valence_inventory.path = "crates/valence_inventory"
valence_nbt = { path = "crates/valence_nbt" } valence_nbt = { path = "crates/valence_nbt", features = ["uuid"] }
valence_network.path = "crates/valence_network" valence_network.path = "crates/valence_network"
valence_player_list.path = "crates/valence_player_list" valence_player_list.path = "crates/valence_player_list"
valence_registry.path = "crates/valence_registry" valence_registry.path = "crates/valence_registry"

View file

@ -21,8 +21,8 @@ pub fn idle_update(c: &mut Criterion) {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
server: Res<Server>, server: Res<Server>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -43,8 +43,8 @@ fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -80,8 +80,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
server: Res<Server>, server: Res<Server>,
) { ) {
let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -57,8 +57,8 @@ fn print_tick_time(
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -16,8 +16,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
biome_reg: Res<BiomeRegistry>, biome_reg: Res<BiomeRegistry>,
server: Res<Server>, server: Res<Server>,
) { ) {

View file

@ -22,8 +22,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -26,8 +26,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -21,8 +21,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -32,8 +32,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -24,7 +24,6 @@ pub fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_startup_system(setup_biomes.before(setup))
.add_startup_system(setup) .add_startup_system(setup)
.add_system(init_clients) .add_system(init_clients)
.add_systems(( .add_systems((
@ -37,19 +36,16 @@ pub fn main() {
.run(); .run();
} }
// TODO: this is a hack.
fn setup_biomes(mut biomes: Query<&mut Biome>) {
for mut biome in &mut biomes {
biome.grass_color = Some(0x00ff00);
}
}
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: ResMut<DimensionTypeRegistry>,
biomes: Query<&Biome>, mut biomes: ResMut<BiomeRegistry>,
) { ) {
for (_, _, biome) in biomes.iter_mut() {
biome.effects.grass_color = Some(0x00ff00);
}
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -10..10 { for z in -10..10 {

View file

@ -34,8 +34,8 @@ fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -19,8 +19,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
for block in [BlockState::GRASS_BLOCK, BlockState::DEEPSLATE] { for block in [BlockState::GRASS_BLOCK, BlockState::DEEPSLATE] {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -25,8 +25,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -57,8 +57,8 @@ fn init_clients(
Added<Client>, Added<Client>,
>, >,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, mut client, mut loc, mut is_flat, mut game_mode) in clients.iter_mut() { for (entity, mut client, mut loc, mut is_flat, mut game_mode) in clients.iter_mut() {

View file

@ -24,8 +24,8 @@ struct ParticleVec(Vec<Particle>);
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -27,8 +27,8 @@ fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -21,8 +21,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -60,8 +60,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let seconds_per_day = 86_400; let seconds_per_day = 86_400;
let seed = (SystemTime::now() let seed = (SystemTime::now()

View file

@ -18,8 +18,8 @@ pub fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

View file

@ -4,15 +4,16 @@ use std::time::Instant;
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use uuid::Uuid; use uuid::Uuid;
use valence_biome::BiomeRegistry;
use valence_client::ClientBundleArgs; use valence_client::ClientBundleArgs;
use valence_core::protocol::decode::{PacketDecoder, PacketFrame}; use valence_core::protocol::decode::{PacketDecoder, PacketFrame};
use valence_core::protocol::encode::PacketEncoder; use valence_core::protocol::encode::PacketEncoder;
use valence_core::protocol::var_int::VarInt; use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{Encode, Packet}; use valence_core::protocol::{Encode, Packet};
use valence_core::{ident, CoreSettings, Server}; use valence_core::{ident, CoreSettings, Server};
use valence_dimension::DimensionTypeRegistry;
use valence_entity::Location; use valence_entity::Location;
use valence_network::{ConnectionMode, NetworkSettings}; use valence_network::{ConnectionMode, NetworkSettings};
@ -37,9 +38,17 @@ fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
app.add_plugins(DefaultPlugins); app.add_plugins(DefaultPlugins);
let server = app.world.resource::<Server>(); app.update(); // Initialize plugins.
let instance = Instance::new_unit_testing(ident!("overworld"), server);
let instance = Instance::new(
ident!("overworld"),
app.world.resource::<DimensionTypeRegistry>(),
app.world.resource::<BiomeRegistry>(),
app.world.resource::<Server>(),
);
let instance_ent = app.world.spawn(instance).id(); let instance_ent = app.world.spawn(instance).id();
let (client, client_helper) = create_mock_client(); let (client, client_helper) = create_mock_client();
let client_ent = app.world.spawn(client).id(); let client_ent = app.world.spawn(client).id();
@ -47,14 +56,6 @@ fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
// Set initial location. // Set initial location.
app.world.get_mut::<Location>(client_ent).unwrap().0 = instance_ent; app.world.get_mut::<Location>(client_ent).unwrap().0 = instance_ent;
// Print warnings if there are ambiguities in the schedule.
app.edit_schedule(CoreSchedule::Main, |schedule| {
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Warn,
..Default::default()
});
});
(client_ent, client_helper) (client_ent, client_helper)
} }

View file

@ -4,10 +4,11 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true
bevy_app.workspace = true bevy_app.workspace = true
bevy_ecs.workspace = true bevy_ecs.workspace = true
anyhow.workspace = true serde.workspace = true
tracing.workspace = true tracing.workspace = true
valence_nbt.workspace = true
valence_registry.workspace = true
valence_core.workspace = true valence_core.workspace = true
valence_nbt = { workspace = true, features = ["serde"] }
valence_registry.workspace = true

View file

@ -17,111 +17,43 @@
clippy::dbg_macro clippy::dbg_macro
)] )]
use std::ops::Index; use std::ops::{Deref, DerefMut};
use anyhow::{bail, Context};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
use tracing::error; use tracing::error;
use valence_core::ident; use valence_core::ident;
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_nbt::{compound, Value}; use valence_nbt::serde::CompoundSerializer;
use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue}; use valence_registry::codec::{RegistryCodec, RegistryValue};
use valence_registry::{Registry, RegistryIdx, RegistrySet};
pub struct BiomePlugin; pub struct BiomePlugin;
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct BiomeSet;
impl Plugin for BiomePlugin { impl Plugin for BiomePlugin {
fn build(&self, app: &mut bevy_app::App) { fn build(&self, app: &mut App) {
app.insert_resource(BiomeRegistry { app.init_resource::<BiomeRegistry>()
id_to_biome: vec![], .add_startup_system(load_default_biomes.in_base_set(CoreSet::PreUpdate))
}) .add_system(
.configure_set( update_biome_registry
BiomeSet .in_base_set(CoreSet::PostUpdate)
.in_base_set(CoreSet::PostUpdate) .before(RegistrySet),
.before(RegistryCodecSet), );
)
.add_systems(
(update_biome_registry, remove_biomes_from_registry)
.chain()
.in_set(BiomeSet),
)
.add_startup_system(load_default_biomes.in_base_set(StartupSet::PreStartup));
} }
} }
fn load_default_biomes( fn load_default_biomes(mut reg: ResMut<BiomeRegistry>, codec: Res<RegistryCodec>) {
mut reg: ResMut<BiomeRegistry>, let mut helper = move || -> anyhow::Result<()> {
codec: Res<RegistryCodec>,
mut commands: Commands,
) {
let mut helper = move || {
for value in codec.registry(BiomeRegistry::KEY) { for value in codec.registry(BiomeRegistry::KEY) {
let downfall = *value let biome = Biome::deserialize(value.element.clone())?;
.element
.get("downfall")
.and_then(|v| v.as_float())
.context("invalid downfall")?;
let Some(Value::Compound(effects)) = value.element.get("effects") else { reg.insert(value.name.clone(), biome);
bail!("missing biome effects")
};
let fog_color = *effects
.get("fog_color")
.and_then(|v| v.as_int())
.context("invalid fog color")?;
let sky_color = *effects
.get("sky_color")
.and_then(|v| v.as_int())
.context("invalid sky color")?;
let water_color = *effects
.get("water_color")
.and_then(|v| v.as_int())
.context("invalid water color")?;
let water_fog_color = *effects
.get("water_fog_color")
.and_then(|v| v.as_int())
.context("invalid water fog color")?;
let grass_color = effects.get("grass_color").and_then(|v| v.as_int()).copied();
let has_precipitation = *value
.element
.get("has_precipitation")
.and_then(|v| v.as_byte())
.context("invalid has_precipitation")?
!= 0;
let temperature = *value
.element
.get("temperature")
.and_then(|v| v.as_float())
.context("invalid temperature")?;
let entity = commands
.spawn(Biome {
name: value.name.clone(),
downfall,
fog_color,
sky_color,
water_color,
water_fog_color,
grass_color,
has_precipitation,
temperature,
})
.id();
reg.id_to_biome.push(entity);
} }
// Move "plains" to the front so that `BiomeId::default()` is the ID of plains.
reg.swap_to_front(ident!("plains"));
Ok(()) Ok(())
}; };
@ -130,126 +62,102 @@ fn load_default_biomes(
} }
} }
/// Add new biomes to or update existing biomes in the registry. fn update_biome_registry(reg: Res<BiomeRegistry>, mut codec: ResMut<RegistryCodec>) {
fn update_biome_registry( if reg.is_changed() {
mut reg: ResMut<BiomeRegistry>, let biomes = codec.registry_mut(BiomeRegistry::KEY);
mut codec: ResMut<RegistryCodec>,
biomes: Query<(Entity, &Biome), Changed<Biome>>,
) {
for (entity, biome) in &biomes {
let biome_registry = codec.registry_mut(BiomeRegistry::KEY);
let mut effects = compound! { biomes.clear();
"fog_color" => biome.fog_color,
"sky_color" => biome.sky_color,
"water_color" => biome.water_color,
"water_fog_color" => biome.water_fog_color,
};
if let Some(grass_color) = biome.grass_color { biomes.extend(reg.iter().map(|(_, name, biome)| {
effects.insert("grass_color", grass_color); RegistryValue {
} name: name.into(),
element: biome
let biome_compound = compound! { .serialize(CompoundSerializer)
"downfall" => biome.downfall, .expect("failed to serialize biome"),
"effects" => effects, }
"has_precipitation" => biome.has_precipitation, }));
"temperature" => biome.temperature,
};
if let Some(value) = biome_registry.iter_mut().find(|v| v.name == biome.name) {
value.name = biome.name.clone();
value.element.merge(biome_compound);
} else {
biome_registry.push(RegistryValue {
name: biome.name.clone(),
element: biome_compound,
});
reg.id_to_biome.push(entity);
}
assert_eq!(
biome_registry.len(),
reg.id_to_biome.len(),
"biome registry and biome lookup table differ in length"
);
} }
} }
/// Remove deleted biomes from the registry. #[derive(Resource, Default, Debug)]
fn remove_biomes_from_registry(
mut biomes: RemovedComponents<Biome>,
mut reg: ResMut<BiomeRegistry>,
mut codec: ResMut<RegistryCodec>,
) {
for biome in biomes.iter() {
if let Some(idx) = reg.id_to_biome.iter().position(|entity| *entity == biome) {
reg.id_to_biome.remove(idx);
codec.registry_mut(BiomeRegistry::KEY).remove(idx);
}
}
}
#[derive(Resource)]
pub struct BiomeRegistry { pub struct BiomeRegistry {
id_to_biome: Vec<Entity>, reg: Registry<BiomeId, Biome>,
} }
impl BiomeRegistry { impl BiomeRegistry {
pub const KEY: Ident<&str> = ident!("minecraft:worldgen/biome"); pub const KEY: Ident<&str> = ident!("worldgen/biome");
}
pub fn get_by_id(&self, id: BiomeId) -> Option<Entity> { impl Deref for BiomeRegistry {
self.id_to_biome.get(id.0 as usize).cloned() type Target = Registry<BiomeId, Biome>;
}
pub fn iter(&self) -> impl Iterator<Item = (BiomeId, Entity)> + '_ { fn deref(&self) -> &Self::Target {
self.id_to_biome &self.reg
.iter()
.enumerate()
.map(|(id, biome)| (BiomeId(id as _), *biome))
} }
} }
impl Index<BiomeId> for BiomeRegistry { impl DerefMut for BiomeRegistry {
type Output = Entity; fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.reg
fn index(&self, index: BiomeId) -> &Self::Output {
self.id_to_biome
.get(index.0 as usize)
.unwrap_or_else(|| panic!("invalid {index:?}"))
} }
} }
/// An index into the biome registry. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct BiomeId(u32);
pub struct BiomeId(pub u16);
#[derive(Component, Clone, Debug)] impl RegistryIdx for BiomeId {
const MAX: usize = u32::MAX as _;
#[inline]
fn to_index(self) -> usize {
self.0 as _
}
#[inline]
fn from_index(idx: usize) -> Self {
Self(idx as _)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Biome { pub struct Biome {
pub name: Ident<String>,
pub downfall: f32, pub downfall: f32,
pub fog_color: i32, pub effects: BiomeEffects,
pub sky_color: i32,
pub water_color: i32,
pub water_fog_color: i32,
pub grass_color: Option<i32>,
pub has_precipitation: bool, pub has_precipitation: bool,
pub temperature: f32, pub temperature: f32,
// TODO: more stuff. // TODO: more stuff.
} }
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BiomeEffects {
pub fog_color: u32,
pub sky_color: u32,
pub water_color: u32,
pub water_fog_color: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color: Option<u32>,
// TODO: more stuff.
}
impl Default for Biome { impl Default for Biome {
fn default() -> Self { fn default() -> Self {
Self { Self {
name: ident!("plains").into(),
downfall: 0.4, downfall: 0.4,
fog_color: 12638463, effects: BiomeEffects::default(),
sky_color: 7907327,
water_color: 4159204,
water_fog_color: 329011,
grass_color: None,
has_precipitation: true, has_precipitation: true,
temperature: 0.8, temperature: 0.8,
} }
} }
} }
impl Default for BiomeEffects {
fn default() -> Self {
Self {
fog_color: 12638463,
sky_color: 7907327,
water_color: 4159204,
water_fog_color: 329011,
grass_color: None,
}
}
}

View file

@ -67,7 +67,9 @@ use valence_instance::packet::{
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c, ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c,
}; };
use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet}; use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet};
use valence_registry::{RegistryCodec, RegistryCodecSet, TagsRegistry}; use valence_registry::codec::RegistryCodec;
use valence_registry::tags::TagsRegistry;
use valence_registry::RegistrySet;
pub mod action; pub mod action;
pub mod chat; pub mod chat;
@ -112,7 +114,7 @@ impl Plugin for ClientPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
( (
initial_join.after(RegistryCodecSet), initial_join.after(RegistrySet),
update_chunk_load_dist, update_chunk_load_dist,
read_data_in_old_view read_data_in_old_view
.after(WriteUpdatePacketsToInstancesSet) .after(WriteUpdatePacketsToInstancesSet)

View file

@ -1,156 +0,0 @@
/*
pub use advancement_tab::AdvancementTabC2s;
pub use boat_paddle::BoatPaddleStateC2s;
pub use book_update::BookUpdateC2s;
pub use button_click::ButtonClickC2s;
pub use chat_message::ChatMessageC2s;
pub use click_slot::ClickSlotC2s;
pub use client_command::ClientCommandC2s;
pub use client_settings::ClientSettingsC2s;
pub use client_status::ClientStatusC2s;
pub use close_handled_screen::CloseHandledScreenC2s;
pub use command_execution::CommandExecutionC2s;
pub use craft_request::CraftRequestC2s;
pub use creative_inventory_action::CreativeInventoryActionC2s;
pub use custom_payload::CustomPayloadC2s;
pub use hand_swing::HandSwingC2s;
pub use jigsaw_generating::JigsawGeneratingC2s;
pub use keep_alive::KeepAliveC2s;
pub use message_acknowledgment::MessageAcknowledgmentC2s;
pub use pick_from_inventory::PickFromInventoryC2s;
pub use play_pong::PlayPongC2s;
pub use player_action::PlayerActionC2s;
pub use player_input::PlayerInputC2s;
pub use player_interact_block::PlayerInteractBlockC2s;
pub use player_interact_entity::PlayerInteractEntityC2s;
pub use player_interact_item::PlayerInteractItemC2s;
pub use player_move::{Full, LookAndOnGround, OnGroundOnly, PositionAndOnGround};
pub use player_session::PlayerSessionC2s;
pub use query_block_nbt::QueryBlockNbtC2s;
pub use query_entity_nbt::QueryEntityNbtC2s;
pub use recipe_book_data::RecipeBookDataC2s;
pub use recipe_category_options::RecipeCategoryOptionsC2s;
pub use rename_item::RenameItemC2s;
pub use request_command_completions::RequestCommandCompletionsC2s;
pub use resource_pack_status::ResourcePackStatusC2s;
pub use select_merchant_trade::SelectMerchantTradeC2s;
pub use spectator_teleport::SpectatorTeleportC2s;
pub use teleport_confirm::TeleportConfirmC2s;
pub use update_beacon::UpdateBeaconC2s;
pub use update_command_block::UpdateCommandBlockC2s;
pub use update_command_block_minecart::UpdateCommandBlockMinecartC2s;
pub use update_difficulty::UpdateDifficultyC2s;
pub use update_difficulty_lock::UpdateDifficultyLockC2s;
pub use update_jigsaw::UpdateJigsawC2s;
pub use update_player_abilities::UpdatePlayerAbilitiesC2s;
pub use update_selected_slot::UpdateSelectedSlotC2s;
pub use update_sign::UpdateSignC2s;
pub use update_structure_block::UpdateStructureBlockC2s;
pub use vehicle_move::VehicleMoveC2s;
pub mod advancement_tab;
pub mod boat_paddle;
pub mod book_update;
pub mod button_click;
pub mod chat_message;
pub mod click_slot;
pub mod client_command;
pub mod client_settings;
pub mod client_status;
pub mod close_handled_screen;
pub mod command_execution;
pub mod craft_request;
pub mod creative_inventory_action;
pub mod custom_payload;
pub mod hand_swing;
pub mod jigsaw_generating;
pub mod keep_alive;
pub mod message_acknowledgment;
pub mod pick_from_inventory;
pub mod play_pong;
pub mod player_action;
pub mod player_input;
pub mod player_interact_block;
pub mod player_interact_entity;
pub mod player_interact_item;
pub mod player_move;
pub mod player_session;
pub mod query_block_nbt;
pub mod query_entity_nbt;
pub mod recipe_book_data;
pub mod recipe_category_options;
pub mod rename_item;
pub mod request_command_completions;
pub mod resource_pack_status;
pub mod select_merchant_trade;
pub mod spectator_teleport;
pub mod teleport_confirm;
pub mod update_beacon;
pub mod update_command_block;
pub mod update_command_block_minecart;
pub mod update_difficulty;
pub mod update_difficulty_lock;
pub mod update_jigsaw;
pub mod update_player_abilities;
pub mod update_selected_slot;
pub mod update_sign;
pub mod update_structure_block;
pub mod vehicle_move;
packet_group! {
#[derive(Clone)]
C2sPlayPacket<'a> {
AdvancementTabC2s<'a>,
BoatPaddleStateC2s,
BookUpdateC2s<'a>,
ButtonClickC2s,
ChatMessageC2s<'a>,
ClickSlotC2s,
ClientCommandC2s,
ClientSettingsC2s<'a>,
ClientStatusC2s,
CloseHandledScreenC2s,
CommandExecutionC2s<'a>,
CraftRequestC2s<'a>,
CreativeInventoryActionC2s,
CustomPayloadC2s<'a>,
Full,
HandSwingC2s,
JigsawGeneratingC2s,
KeepAliveC2s,
LookAndOnGround,
MessageAcknowledgmentC2s,
OnGroundOnly,
PickFromInventoryC2s,
PlayerActionC2s,
PlayerInputC2s,
PlayerInteractBlockC2s,
PlayerInteractEntityC2s,
PlayerInteractItemC2s,
PlayerSessionC2s<'a>,
PlayPongC2s,
PositionAndOnGround,
QueryBlockNbtC2s,
QueryEntityNbtC2s,
RecipeBookDataC2s<'a>,
RecipeCategoryOptionsC2s,
RenameItemC2s<'a>,
RequestCommandCompletionsC2s<'a>,
ResourcePackStatusC2s,
SelectMerchantTradeC2s,
SpectatorTeleportC2s,
TeleportConfirmC2s,
UpdateBeaconC2s,
UpdateCommandBlockC2s<'a>,
UpdateCommandBlockMinecartC2s<'a>,
UpdateDifficultyC2s,
UpdateDifficultyLockC2s,
UpdateJigsawC2s<'a>,
UpdatePlayerAbilitiesC2s,
UpdateSelectedSlotC2s,
UpdateSignC2s<'a>,
UpdateStructureBlockC2s<'a>,
VehicleMoveC2s,
}
}
*/

View file

@ -1,338 +0,0 @@
/*
pub use advancement_update::AdvancementUpdateS2c;
pub use block_breaking_progress::BlockBreakingProgressS2c;
pub use block_entity_update::BlockEntityUpdateS2c;
pub use block_event::BlockEventS2c;
pub use block_update::BlockUpdateS2c;
pub use boss_bar::BossBarS2c;
pub use bundle_splitter::BundleSplitter;
pub use chat_message::ChatMessageS2c;
pub use chat_suggestions::ChatSuggestionsS2c;
pub use chunk_biome_data::ChunkBiomeDataS2c;
pub use chunk_data::ChunkDataS2c;
pub use chunk_delta_update::ChunkDeltaUpdateS2c;
pub use chunk_load_distance::ChunkLoadDistanceS2c;
pub use chunk_render_distance_center::ChunkRenderDistanceCenterS2c;
pub use clear_title::ClearTitleS2c;
pub use close_screen::CloseScreenS2c;
pub use command_suggestions::CommandSuggestionsS2c;
pub use command_tree::CommandTreeS2c;
pub use cooldown_update::CooldownUpdateS2c;
pub use craft_failed_response::CraftFailedResponseS2c;
pub use custom_payload::CustomPayloadS2c;
pub use damage_tilt::DamageTiltS2c;
pub use death_message::DeathMessageS2c;
pub use difficulty::DifficultyS2c;
pub use disconnect::DisconnectS2c;
pub use end_combat::EndCombatS2c;
pub use enter_combat::EnterCombatS2c;
pub use entities_destroy::EntitiesDestroyS2c;
pub use entity_animation::EntityAnimationS2c;
pub use entity_attach::EntityAttachS2c;
pub use entity_attributes::EntityAttributesS2c;
pub use entity_damage::EntityDamageS2c;
pub use entity_equipment_update::EntityEquipmentUpdateS2c;
pub use entity_move::{MoveRelative, Rotate, RotateAndMoveRelative};
pub use entity_passengers_set::EntityPassengersSetS2c;
pub use entity_position::EntityPositionS2c;
pub use entity_set_head_yaw::EntitySetHeadYawS2c;
pub use entity_spawn::EntitySpawnS2c;
pub use entity_status::EntityStatusS2c;
pub use entity_status_effect::EntityStatusEffectS2c;
pub use entity_tracker_update::EntityTrackerUpdateS2c;
pub use entity_velocity_update::EntityVelocityUpdateS2c;
pub use experience_bar_update::ExperienceBarUpdateS2c;
pub use experience_orb_spawn::ExperienceOrbSpawnS2c;
pub use explosion::ExplosionS2c;
pub use features::FeaturesS2c;
pub use game_join::GameJoinS2c;
pub use game_message::GameMessageS2c;
pub use game_state_change::GameStateChangeS2c;
pub use health_update::HealthUpdateS2c;
pub use inventory::InventoryS2c;
pub use item_pickup_animation::ItemPickupAnimationS2c;
pub use keep_alive::KeepAliveS2c;
pub use light_update::LightUpdateS2c;
pub use look_at::LookAtS2c;
pub use map_update::MapUpdateS2c;
pub use nbt_query_response::NbtQueryResponseS2c;
pub use open_horse_screen::OpenHorseScreenS2c;
pub use open_screen::OpenScreenS2c;
pub use open_written_book::OpenWrittenBookS2c;
pub use overlay_message::OverlayMessageS2c;
pub use particle::ParticleS2c;
pub use play_ping::PlayPingS2c;
pub use play_sound::PlaySoundS2c;
pub use play_sound_from_entity::PlaySoundFromEntityS2c;
pub use player_abilities::PlayerAbilitiesS2c;
pub use player_action_response::PlayerActionResponseS2c;
pub use player_list::PlayerListS2c;
pub use player_list_header::PlayerListHeaderS2c;
pub use player_position_look::PlayerPositionLookS2c;
pub use player_remove::PlayerRemoveS2c;
pub use player_respawn::PlayerRespawnS2c;
pub use player_spawn::PlayerSpawnS2c;
pub use player_spawn_position::PlayerSpawnPositionS2c;
pub use profileless_chat_message::ProfilelessChatMessageS2c;
pub use remove_entity_status_effect::RemoveEntityStatusEffectS2c;
pub use remove_message::RemoveMessageS2c;
pub use resource_pack_send::ResourcePackSendS2c;
pub use scoreboard_display::ScoreboardDisplayS2c;
pub use scoreboard_objective_update::ScoreboardObjectiveUpdateS2c;
pub use scoreboard_player_update::ScoreboardPlayerUpdateS2c;
pub use screen_handler_property_update::ScreenHandlerPropertyUpdateS2c;
pub use screen_handler_slot_update::ScreenHandlerSlotUpdateS2c;
pub use select_advancement_tab::SelectAdvancementTabS2c;
pub use server_metadata::ServerMetadataS2c;
pub use set_camera_entity::SetCameraEntityS2c;
pub use set_trade_offers::SetTradeOffersS2c;
pub use sign_editor_open::SignEditorOpenS2c;
pub use simulation_distance::SimulationDistanceS2c;
pub use statistics::StatisticsS2c;
pub use stop_sound::StopSoundS2c;
pub use subtitle::SubtitleS2c;
pub use synchronize_recipes::SynchronizeRecipesS2c;
pub use synchronize_tags::SynchronizeTagsS2c;
pub use team::TeamS2c;
pub use title::TitleS2c;
pub use title_fade::TitleFadeS2c;
pub use unload_chunk::UnloadChunkS2c;
pub use unlock_recipes::UnlockRecipesS2c;
pub use update_selected_slot::UpdateSelectedSlotS2c;
pub use vehicle_move::VehicleMoveS2c;
pub use world_border_center_changed::WorldBorderCenterChangedS2c;
pub use world_border_initialize::WorldBorderInitializeS2c;
pub use world_border_interpolate_size::WorldBorderInterpolateSizeS2c;
pub use world_border_size_changed::WorldBorderSizeChangedS2c;
pub use world_border_warning_blocks_changed::WorldBorderWarningBlocksChangedS2c;
pub use world_border_warning_time_changed::WorldBorderWarningTimeChangedS2c;
pub use world_event::WorldEventS2c;
pub use world_time_update::WorldTimeUpdateS2c;
pub mod advancement_update;
pub mod block_breaking_progress;
pub mod block_entity_update;
pub mod block_event;
pub mod block_update;
pub mod boss_bar;
pub mod bundle_splitter;
pub mod chat_message;
pub mod chat_suggestions;
pub mod chunk_biome_data;
pub mod chunk_data;
pub mod chunk_delta_update;
pub mod chunk_load_distance;
pub mod chunk_render_distance_center;
pub mod clear_title;
pub mod close_screen;
pub mod command_suggestions;
pub mod command_tree;
pub mod cooldown_update;
pub mod craft_failed_response;
pub mod custom_payload;
pub mod damage_tilt;
pub mod death_message;
pub mod difficulty;
pub mod disconnect;
pub mod end_combat;
pub mod enter_combat;
pub mod entities_destroy;
pub mod entity_animation;
pub mod entity_attach;
pub mod entity_attributes;
pub mod entity_damage;
pub mod entity_equipment_update;
pub mod entity_move;
pub mod entity_passengers_set;
pub mod entity_position;
pub mod entity_set_head_yaw;
pub mod entity_spawn;
pub mod entity_status;
pub mod entity_status_effect;
pub mod entity_tracker_update;
pub mod entity_velocity_update;
pub mod experience_bar_update;
pub mod experience_orb_spawn;
pub mod explosion;
pub mod features;
pub mod game_join;
pub mod game_message;
pub mod game_state_change;
pub mod health_update;
pub mod inventory;
pub mod item_pickup_animation;
pub mod keep_alive;
pub mod light_update;
pub mod look_at;
pub mod map_update;
pub mod nbt_query_response;
pub mod open_horse_screen;
pub mod open_screen;
pub mod open_written_book;
pub mod overlay_message;
pub mod particle;
pub mod play_ping;
pub mod play_sound;
pub mod play_sound_from_entity;
pub mod player_abilities;
pub mod player_action_response;
pub mod player_list;
pub mod player_list_header;
pub mod player_position_look;
pub mod player_remove;
pub mod player_respawn;
pub mod player_spawn;
pub mod player_spawn_position;
pub mod profileless_chat_message;
pub mod remove_entity_status_effect;
pub mod remove_message;
pub mod resource_pack_send;
pub mod scoreboard_display;
pub mod scoreboard_objective_update;
pub mod scoreboard_player_update;
pub mod screen_handler_property_update;
pub mod screen_handler_slot_update;
pub mod select_advancement_tab;
pub mod server_metadata;
pub mod set_camera_entity;
pub mod set_trade_offers;
pub mod sign_editor_open;
pub mod simulation_distance;
pub mod statistics;
pub mod stop_sound;
pub mod subtitle;
pub mod synchronize_recipes;
pub mod synchronize_tags;
pub mod team;
pub mod title;
pub mod title_fade;
pub mod unload_chunk;
pub mod unlock_recipes;
pub mod update_selected_slot;
pub mod vehicle_move;
pub mod world_border_center_changed;
pub mod world_border_initialize;
pub mod world_border_interpolate_size;
pub mod world_border_size_changed;
pub mod world_border_warning_blocks_changed;
pub mod world_border_warning_time_changed;
pub mod world_event;
pub mod world_time_update;
packet_group! {
#[derive(Clone)]
S2cPlayPacket<'a> {
AdvancementUpdateS2c<'a>,
BlockBreakingProgressS2c,
BlockEntityUpdateS2c<'a>,
BlockEventS2c,
BlockUpdateS2c,
BossBarS2c,
BundleSplitter,
ChatMessageS2c<'a>,
ChatSuggestionsS2c<'a>,
ChunkBiomeDataS2c<'a>,
ChunkDataS2c<'a>,
ChunkDeltaUpdateS2c<'a>,
ChunkLoadDistanceS2c,
ChunkRenderDistanceCenterS2c,
ClearTitleS2c,
CloseScreenS2c,
CommandSuggestionsS2c<'a>,
CommandTreeS2c<'a>,
CooldownUpdateS2c,
CraftFailedResponseS2c<'a>,
CustomPayloadS2c<'a>,
DamageTiltS2c,
DeathMessageS2c<'a>,
DifficultyS2c,
DisconnectS2c<'a>,
EndCombatS2c,
EnterCombatS2c,
EntitiesDestroyS2c<'a>,
EntityAnimationS2c,
EntityAttachS2c,
EntityAttributesS2c<'a>,
EntityDamageS2c,
EntityEquipmentUpdateS2c,
EntityPassengersSetS2c,
EntityPositionS2c,
EntitySetHeadYawS2c,
EntitySpawnS2c,
EntityStatusEffectS2c,
EntityStatusS2c,
EntityTrackerUpdateS2c<'a>,
EntityVelocityUpdateS2c,
ExperienceBarUpdateS2c,
ExperienceOrbSpawnS2c,
ExplosionS2c<'a>,
FeaturesS2c<'a>,
GameJoinS2c<'a>,
GameMessageS2c<'a>,
GameStateChangeS2c,
HealthUpdateS2c,
InventoryS2c<'a>,
ItemPickupAnimationS2c,
KeepAliveS2c,
LightUpdateS2c,
LookAtS2c,
MapUpdateS2c<'a>,
MoveRelative,
NbtQueryResponseS2c,
OpenHorseScreenS2c,
OpenScreenS2c<'a>,
OpenWrittenBookS2c,
OverlayMessageS2c<'a>,
ParticleS2c<'a>,
PlayerAbilitiesS2c,
PlayerActionResponseS2c,
PlayerListHeaderS2c<'a>,
PlayerListS2c<'a>,
PlayerPositionLookS2c,
PlayerRemoveS2c<'a>,
PlayerRespawnS2c<'a>,
PlayerSpawnPositionS2c,
PlayerSpawnS2c,
PlayPingS2c,
PlaySoundFromEntityS2c,
PlaySoundS2c<'a>,
ProfilelessChatMessageS2c<'a>,
RemoveEntityStatusEffectS2c,
RemoveMessageS2c<'a>,
ResourcePackSendS2c<'a>,
Rotate,
RotateAndMoveRelative,
ScoreboardDisplayS2c<'a>,
ScoreboardObjectiveUpdateS2c<'a>,
ScoreboardPlayerUpdateS2c<'a>,
ScreenHandlerPropertyUpdateS2c,
ScreenHandlerSlotUpdateS2c<'a>,
SelectAdvancementTabS2c<'a>,
ServerMetadataS2c<'a>,
SetCameraEntityS2c,
SetTradeOffersS2c,
SignEditorOpenS2c,
SimulationDistanceS2c,
StatisticsS2c,
StopSoundS2c<'a>,
SubtitleS2c<'a>,
SynchronizeRecipesS2c<'a>,
SynchronizeTagsS2c<'a>,
TeamS2c<'a>,
TitleFadeS2c,
TitleS2c<'a>,
UnloadChunkS2c,
UnlockRecipesS2c<'a>,
UpdateSelectedSlotS2c,
VehicleMoveS2c,
WorldBorderCenterChangedS2c,
WorldBorderInitializeS2c,
WorldBorderInterpolateSizeS2c,
WorldBorderSizeChangedS2c,
WorldBorderWarningBlocksChangedS2c,
WorldBorderWarningTimeChangedS2c,
WorldEventS2c,
WorldTimeUpdateS2c,
}
}
*/

View file

@ -7,7 +7,8 @@ edition.workspace = true
anyhow.workspace = true anyhow.workspace = true
bevy_app.workspace = true bevy_app.workspace = true
bevy_ecs.workspace = true bevy_ecs.workspace = true
serde.workspace = true
tracing.workspace = true tracing.workspace = true
valence_registry.workspace = true
valence_nbt.workspace = true
valence_core.workspace = true valence_core.workspace = true
valence_nbt = { workspace = true, features = ["serde"] }
valence_registry.workspace = true

View file

@ -17,155 +17,39 @@
clippy::dbg_macro clippy::dbg_macro
)] )]
use std::collections::BTreeMap; use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use anyhow::{bail, Context};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
use tracing::{error, warn}; use tracing::{error, warn};
use valence_core::ident; use valence_core::ident;
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_nbt::{compound, Value}; use valence_nbt::serde::CompoundSerializer;
use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue}; use valence_registry::codec::{RegistryCodec, RegistryValue};
use valence_registry::{Registry, RegistryIdx, RegistrySet};
pub struct DimensionPlugin; pub struct DimensionPlugin;
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct DimensionSet;
impl Plugin for DimensionPlugin { impl Plugin for DimensionPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(DimensionTypeRegistry { app.init_resource::<DimensionTypeRegistry>()
name_to_dimension: BTreeMap::new(), .add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup))
}) .add_system(
.configure_set( update_dimension_type_registry
DimensionSet .in_base_set(CoreSet::PostUpdate)
.in_base_set(CoreSet::PostUpdate) .before(RegistrySet),
.before(RegistryCodecSet), );
)
.add_systems(
(
update_dimension_type_registry,
remove_dimension_types_from_registry,
)
.chain()
.in_set(DimensionSet),
)
.add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup));
} }
} }
fn update_dimension_type_registry( /// Loads the default dimension types from the registry codec.
mut reg: ResMut<DimensionTypeRegistry>, fn load_default_dimension_types(mut reg: ResMut<DimensionTypeRegistry>, codec: Res<RegistryCodec>) {
mut codec: ResMut<RegistryCodec>,
dimension_types: Query<(Entity, &DimensionType), Changed<DimensionType>>,
) {
for (entity, dim) in &dimension_types {
// In case the name was changed.
reg.name_to_dimension.insert(dim.name.clone(), entity);
let dimension_type_compound = compound! {
"ambient_light" => dim.ambient_light,
"bed_works" => dim.bed_works,
"coordinate_scale" => dim.coordinate_scale,
"effects" => Ident::from(dim.effects),
"has_ceiling" => dim.has_ceiling,
"has_raids" => dim.has_raids,
"has_skylight" => dim.has_skylight,
"height" => dim.height,
"infiniburn" => &dim.infiniburn,
"logical_height" => dim.logical_height,
"min_y" => dim.min_y,
"monster_spawn_block_light_limit" => dim.monster_spawn_block_light_limit,
"natural" => dim.natural,
"piglin_safe" => dim.piglin_safe,
"respawn_anchor_works" => dim.respawn_anchor_works,
"ultrawarm" => dim.ultrawarm,
};
let dimension_type_reg = codec.registry_mut(DimensionTypeRegistry::KEY);
if let Some(value) = dimension_type_reg.iter_mut().find(|v| v.name == dim.name) {
value.name = dim.name.clone();
value.element.merge(dimension_type_compound);
} else {
dimension_type_reg.push(RegistryValue {
name: dim.name.clone(),
element: dimension_type_compound,
});
}
}
}
fn remove_dimension_types_from_registry(
mut reg: ResMut<DimensionTypeRegistry>,
mut codec: ResMut<RegistryCodec>,
mut dimension_types: RemovedComponents<DimensionType>,
) {
for entity in dimension_types.iter() {
if let Some((name, _)) = reg.name_to_dimension.iter().find(|(_, &e)| e == entity) {
let name = name.clone();
reg.name_to_dimension.remove(name.as_str());
let dimension_type_reg = codec.registry_mut(DimensionTypeRegistry::KEY);
if let Some(idx) = dimension_type_reg.iter().position(|v| v.name == name) {
dimension_type_reg.remove(idx);
}
}
}
}
fn load_default_dimension_types(
mut reg: ResMut<DimensionTypeRegistry>,
codec: Res<RegistryCodec>,
mut commands: Commands,
) {
let mut helper = move || -> anyhow::Result<()> { let mut helper = move || -> anyhow::Result<()> {
for value in codec.registry(DimensionTypeRegistry::KEY) { for value in codec.registry(DimensionTypeRegistry::KEY) {
macro_rules! get { let dimension_type = DimensionType::deserialize(value.element.clone())?;
($name:literal, $f:expr) => {{
value
.element
.get($name)
.and_then($f)
.context(concat!("invalid ", $name))?
}};
}
let entity = commands reg.insert(value.name.clone(), dimension_type);
.spawn(DimensionType {
name: value.name.clone(),
ambient_light: *get!("ambient_light", Value::as_float),
bed_works: *get!("bed_works", Value::as_byte) != 0,
coordinate_scale: *get!("coordinate_scale", Value::as_double),
effects: DimensionEffects::from_str(get!("effects", Value::as_string))?,
has_ceiling: *get!("has_ceiling", Value::as_byte) != 0,
has_raids: *get!("has_raids", Value::as_byte) != 0,
has_skylight: *get!("has_skylight", Value::as_byte) != 0,
height: *get!("height", Value::as_int),
infiniburn: get!("infiniburn", Value::as_string).clone(),
logical_height: *get!("logical_height", Value::as_int),
min_y: *get!("min_y", Value::as_int),
monster_spawn_block_light_limit: *get!(
"monster_spawn_block_light_limit",
Value::as_int
),
natural: *get!("natural", Value::as_byte) != 0,
piglin_safe: *get!("piglin_safe", Value::as_byte) != 0,
respawn_anchor_works: *get!("respawn_anchor_works", Value::as_byte) != 0,
ultrawarm: *get!("ultrawarm", Value::as_byte) != 0,
})
.id();
if reg
.name_to_dimension
.insert(value.name.clone(), entity)
.is_some()
{
warn!("duplicate dimension type name of \"{}\"", &value.name);
}
} }
Ok(()) Ok(())
@ -176,30 +60,75 @@ fn load_default_dimension_types(
} }
} }
#[derive(Resource)] /// Updates the registry codec as the dimension type registry is modified by
/// users.
fn update_dimension_type_registry(
reg: Res<DimensionTypeRegistry>,
mut codec: ResMut<RegistryCodec>,
) {
if reg.is_changed() {
let dimension_types = codec.registry_mut(DimensionTypeRegistry::KEY);
dimension_types.clear();
dimension_types.extend(reg.iter().map(|(_, name, dim)| {
RegistryValue {
name: name.into(),
element: dim
.serialize(CompoundSerializer)
.expect("failed to serialize dimension type"),
}
}));
}
}
#[derive(Resource, Default, Debug)]
pub struct DimensionTypeRegistry { pub struct DimensionTypeRegistry {
name_to_dimension: BTreeMap<Ident<String>, Entity>, reg: Registry<DimensionTypeId, DimensionType>,
} }
impl DimensionTypeRegistry { impl DimensionTypeRegistry {
pub const KEY: Ident<&str> = ident!("minecraft:dimension_type"); pub const KEY: Ident<&str> = ident!("dimension_type");
}
pub fn get_by_name(&self, name: Ident<&str>) -> Option<Entity> { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
self.name_to_dimension.get(name.as_str()).copied() pub struct DimensionTypeId(u16);
impl RegistryIdx for DimensionTypeId {
const MAX: usize = u16::MAX as _;
fn to_index(self) -> usize {
self.0 as _
} }
pub fn dimensions(&self) -> impl Iterator<Item = Entity> + '_ { fn from_index(idx: usize) -> Self {
self.name_to_dimension.values().copied() Self(idx as _)
} }
} }
#[derive(Component, Clone, PartialEq, Debug)] impl Deref for DimensionTypeRegistry {
type Target = Registry<DimensionTypeId, DimensionType>;
fn deref(&self) -> &Self::Target {
&self.reg
}
}
impl DerefMut for DimensionTypeRegistry {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.reg
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
pub struct DimensionType { pub struct DimensionType {
pub name: Ident<String>,
pub ambient_light: f32, pub ambient_light: f32,
pub bed_works: bool, pub bed_works: bool,
pub coordinate_scale: f64, pub coordinate_scale: f64,
pub effects: DimensionEffects, pub effects: DimensionEffects,
#[serde(skip_serializing_if = "Option::is_none")]
pub fixed_time: Option<i32>,
pub has_ceiling: bool, pub has_ceiling: bool,
pub has_raids: bool, pub has_raids: bool,
pub has_skylight: bool, pub has_skylight: bool,
@ -208,7 +137,7 @@ pub struct DimensionType {
pub logical_height: i32, pub logical_height: i32,
pub min_y: i32, pub min_y: i32,
pub monster_spawn_block_light_limit: i32, pub monster_spawn_block_light_limit: i32,
/// TODO: monster_spawn_light_level pub monster_spawn_light_level: MonsterSpawnLightLevel,
pub natural: bool, pub natural: bool,
pub piglin_safe: bool, pub piglin_safe: bool,
pub respawn_anchor_works: bool, pub respawn_anchor_works: bool,
@ -218,11 +147,11 @@ pub struct DimensionType {
impl Default for DimensionType { impl Default for DimensionType {
fn default() -> Self { fn default() -> Self {
Self { Self {
name: ident!("minecraft:overworld").into(),
ambient_light: 1.0, ambient_light: 1.0,
bed_works: true, bed_works: true,
coordinate_scale: 1.0, coordinate_scale: 1.0,
effects: DimensionEffects::default(), effects: DimensionEffects::default(),
fixed_time: None,
has_ceiling: false, has_ceiling: false,
has_raids: true, has_raids: true,
has_skylight: true, has_skylight: true,
@ -231,42 +160,46 @@ impl Default for DimensionType {
logical_height: 384, logical_height: 384,
min_y: -64, min_y: -64,
monster_spawn_block_light_limit: 0, monster_spawn_block_light_limit: 0,
monster_spawn_light_level: MonsterSpawnLightLevel::Int(7),
natural: true, natural: true,
piglin_safe: false, piglin_safe: false,
respawn_anchor_works: true, respawn_anchor_works: false,
ultrawarm: false, ultrawarm: false,
} }
} }
} }
/// Determines what skybox/fog effects to use in dimensions. /// Determines what skybox/fog effects to use in dimensions.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum DimensionEffects { pub enum DimensionEffects {
#[serde(rename = "minecraft:overworld")]
#[default] #[default]
Overworld, Overworld,
#[serde(rename = "minecraft:the_nether")]
TheNether, TheNether,
#[serde(rename = "minecraft:the_end")]
TheEnd, TheEnd,
} }
impl From<DimensionEffects> for Ident<&'static str> { #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
fn from(value: DimensionEffects) -> Self { #[serde(untagged)]
match value { pub enum MonsterSpawnLightLevel {
DimensionEffects::Overworld => ident!("overworld"), Int(i32),
DimensionEffects::TheNether => ident!("the_nether"), Tagged(MonsterSpawnLightLevelTagged),
DimensionEffects::TheEnd => ident!("the_end"),
}
}
} }
impl FromStr for DimensionEffects { #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
type Err = anyhow::Error; #[serde(tag = "type", content = "value")]
pub enum MonsterSpawnLightLevelTagged {
#[serde(rename = "minecraft:uniform")]
Uniform {
min_inclusive: i32,
max_inclusive: i32,
},
}
fn from_str(s: &str) -> Result<Self, Self::Err> { impl From<i32> for MonsterSpawnLightLevel {
match Ident::new(s)?.as_str() { fn from(value: i32) -> Self {
"minecraft:overworld" => Ok(DimensionEffects::Overworld), Self::Int(value)
"minecraft:the_nether" => Ok(DimensionEffects::TheNether),
"minecraft:the_end" => Ok(DimensionEffects::TheEnd),
other => bail!("unknown dimension effect \"{other}\""),
}
} }
} }

View file

@ -19,3 +19,4 @@ valence_core.workspace = true
valence_dimension.workspace = true valence_dimension.workspace = true
valence_entity.workspace = true valence_entity.workspace = true
valence_nbt.workspace = true valence_nbt.workspace = true
valence_registry.workspace = true

View file

@ -13,6 +13,7 @@ use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::var_long::VarLong; use valence_core::protocol::var_long::VarLong;
use valence_core::protocol::Encode; use valence_core::protocol::Encode;
use valence_nbt::{compound, Compound}; use valence_nbt::{compound, Compound};
use valence_registry::RegistryIdx;
use crate::packet::{ use crate::packet::{
BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataBlockEntity, ChunkDataS2c, ChunkDeltaUpdateS2c, BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataBlockEntity, ChunkDataS2c, ChunkDeltaUpdateS2c,
@ -414,7 +415,7 @@ impl Chunk<true> {
sect.biomes sect.biomes
.encode_mc_format( .encode_mc_format(
&mut *scratch, &mut *scratch,
|b| b.0.into(), |b| b.to_index() as _,
0, 0,
3, 3,
bit_width(info.biome_registry_len - 1), bit_width(info.biome_registry_len - 1),

View file

@ -31,7 +31,7 @@ pub use chunk_entry::*;
use glam::{DVec3, Vec3}; use glam::{DVec3, Vec3};
use num_integer::div_ceil; use num_integer::div_ceil;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use valence_biome::Biome; use valence_biome::BiomeRegistry;
use valence_core::block_pos::BlockPos; use valence_core::block_pos::BlockPos;
use valence_core::chunk_pos::ChunkPos; use valence_core::chunk_pos::ChunkPos;
use valence_core::despawn::Despawned; use valence_core::despawn::Despawned;
@ -44,7 +44,7 @@ use valence_core::protocol::packet::sound::{PlaySoundS2c, Sound, SoundCategory};
use valence_core::protocol::var_int::VarInt; use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{Encode, Packet}; use valence_core::protocol::{Encode, Packet};
use valence_core::Server; use valence_core::Server;
use valence_dimension::DimensionType; use valence_dimension::DimensionTypeRegistry;
use valence_entity::packet::{ use valence_entity::packet::{
EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c, EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c,
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, RotateAndMoveRelativeS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, RotateAndMoveRelativeS2c,
@ -432,40 +432,33 @@ pub struct InstanceInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct PartitionCell { pub struct PartitionCell {
/// The chunk in this cell. /// The chunk in this cell.
#[doc(hidden)]
pub chunk: Option<Chunk<true>>, pub chunk: Option<Chunk<true>>,
/// If `chunk` went from `Some` to `None` this tick. /// If `chunk` went from `Some` to `None` this tick.
#[doc(hidden)]
pub chunk_removed: bool, pub chunk_removed: bool,
/// Minecraft entities in this cell. /// Minecraft entities in this cell.
#[doc(hidden)]
pub entities: BTreeSet<Entity>, pub entities: BTreeSet<Entity>,
/// Minecraft entities that have entered the chunk this tick, paired with /// Minecraft entities that have entered the chunk this tick, paired with
/// the cell position in this instance they came from. /// the cell position in this instance they came from.
#[doc(hidden)]
pub incoming: Vec<(Entity, Option<ChunkPos>)>, pub incoming: Vec<(Entity, Option<ChunkPos>)>,
/// Minecraft entities that have left the chunk this tick, paired with the /// Minecraft entities that have left the chunk this tick, paired with the
/// cell position in this world they arrived at. /// cell position in this world they arrived at.
#[doc(hidden)]
pub outgoing: Vec<(Entity, Option<ChunkPos>)>, pub outgoing: Vec<(Entity, Option<ChunkPos>)>,
/// A cache of packets to send to all clients that are in view of this cell /// A cache of packets to send to all clients that are in view of this cell
/// at the end of the tick. /// at the end of the tick.
#[doc(hidden)]
pub packet_buf: Vec<u8>, pub packet_buf: Vec<u8>,
} }
impl Instance { impl Instance {
#[track_caller]
pub fn new( pub fn new(
dimension_type_name: impl Into<Ident<String>>, dimension_type_name: impl Into<Ident<String>>,
dimensions: &Query<&DimensionType>, dimensions: &DimensionTypeRegistry,
biomes: &Query<&Biome>, biomes: &BiomeRegistry,
server: &Server, server: &Server,
) -> Self { ) -> Self {
let dimension_type_name = dimension_type_name.into(); let dimension_type_name = dimension_type_name.into();
let Some(dim) = dimensions.iter().find(|d| d.name == dimension_type_name) else { let dim = &dimensions[dimension_type_name.as_str_ident()];
panic!("missing dimension type with name \"{dimension_type_name}\"")
};
assert!(dim.height > 0, "invalid dimension height of {}", dim.height); assert!(dim.height > 0, "invalid dimension height of {}", dim.height);
@ -497,28 +490,6 @@ impl Instance {
} }
} }
/// TODO: Temporary hack for unit testing. Do not use!
#[doc(hidden)]
pub fn new_unit_testing(
dimension_type_name: impl Into<Ident<String>>,
server: &Server,
) -> Self {
Self {
partition: FxHashMap::default(),
info: InstanceInfo {
dimension_type_name: dimension_type_name.into(),
section_count: 24,
min_y: -64,
biome_registry_len: 1,
compression_threshold: server.compression_threshold(),
filler_sky_light_mask: vec![].into(),
filler_sky_light_arrays: vec![].into(),
},
packet_buf: vec![],
scratch: vec![],
}
}
pub fn dimension_type_name(&self) -> Ident<&str> { pub fn dimension_type_name(&self) -> Ident<&str> {
self.info.dimension_type_name.as_str_ident() self.info.dimension_type_name.as_str_ident()
} }

View file

@ -6,6 +6,8 @@ use thiserror::Error;
mod de; mod de;
mod ser; mod ser;
#[cfg(test)]
mod tests;
/// Errors that can occur while serializing or deserializing. /// Errors that can occur while serializing or deserializing.
#[derive(Clone, Error, Debug)] #[derive(Clone, Error, Debug)]
@ -56,105 +58,3 @@ fn i8_vec_to_u8_vec(vec: Vec<i8>) -> Vec<u8> {
Vec::from_raw_parts(vec.as_mut_ptr() as *mut u8, vec.len(), vec.capacity()) Vec::from_raw_parts(vec.as_mut_ptr() as *mut u8, vec.len(), vec.capacity())
} }
} }
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde::{Deserialize, Serialize};
use serde_json::json;
use super::*;
use crate::{compound, Compound, List};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Struct {
foo: i32,
bar: StructInner,
baz: String,
quux: Vec<f32>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct StructInner {
a: bool,
b: i64,
c: Vec<Vec<i32>>,
d: Vec<StructInner>,
}
fn make_struct() -> Struct {
Struct {
foo: i32::MIN,
bar: StructInner {
a: true,
b: 123456789,
c: vec![vec![1, 2, 3], vec![4, 5, 6]],
d: vec![],
},
baz: "🤨".into(),
quux: vec![std::f32::consts::PI, f32::MAX, f32::MIN],
}
}
fn make_compound() -> Compound {
compound! {
"foo" => i32::MIN,
"bar" => compound! {
"a" => true,
"b" => 123456789_i64,
"c" => List::IntArray(vec![vec![1, 2, 3], vec![4, 5, 6]]),
"d" => List::End,
},
"baz" => "🤨",
"quux" => List::Float(vec![
std::f32::consts::PI,
f32::MAX,
f32::MIN,
]),
}
}
fn make_json() -> serde_json::Value {
json!({
"foo": i32::MIN,
"bar": {
"a": true,
"b": 123456789_i64,
"c": [[1, 2, 3], [4, 5, 6]],
"d": []
},
"baz": "🤨",
"quux": [
std::f32::consts::PI,
f32::MAX,
f32::MIN,
]
})
}
#[test]
fn struct_to_compound() {
let c = make_struct().serialize(CompoundSerializer).unwrap();
assert_eq!(c, make_compound());
}
#[test]
fn compound_to_struct() {
let s = Struct::deserialize(make_compound()).unwrap();
assert_eq!(s, make_struct());
}
#[test]
fn compound_to_json() {
let mut j = serde_json::to_value(make_compound()).unwrap();
// Bools map to bytes in NBT, but the result should be the same otherwise.
let p = j.pointer_mut("/bar/a").unwrap();
assert_eq!(*p, serde_json::Value::from(1));
*p = true.into();
assert_eq!(j, make_json());
}
}

View file

@ -288,10 +288,32 @@ impl<'de> Deserializer<'de> for Value {
} }
} }
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
Value::String(s) => visitor.visit_enum(s.into_deserializer()), // Unit variant.
other => other.deserialize_any(visitor),
}
}
forward_to_deserialize_any! { forward_to_deserialize_any! {
i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct seq tuple bytes byte_buf unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any tuple_struct map struct identifier ignored_any
} }
} }

View file

@ -324,11 +324,11 @@ impl Serializer for ValueSerializer {
unsupported!("none") unsupported!("none")
} }
fn serialize_some<T: ?Sized>(self, _value: &T) -> Result<Self::Ok, Self::Error> fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
where where
T: Serialize, T: Serialize,
{ {
unsupported!("some") value.serialize(self)
} }
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
@ -343,9 +343,9 @@ impl Serializer for ValueSerializer {
self, self,
_name: &'static str, _name: &'static str,
_variant_index: u32, _variant_index: u32,
_variant: &'static str, variant: &'static str,
) -> Result<Self::Ok, Self::Error> { ) -> Result<Self::Ok, Self::Error> {
unsupported!("unit variant") Ok(Value::String(variant.into()))
} }
fn serialize_newtype_struct<T: ?Sized>( fn serialize_newtype_struct<T: ?Sized>(

View file

@ -0,0 +1,109 @@
use pretty_assertions::assert_eq;
use serde::{Deserialize, Serialize};
use serde_json::json;
use super::*;
use crate::{compound, Compound, List};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Struct {
foo: i32,
bar: StructInner,
baz: String,
quux: Vec<f32>,
blah: EnumInner,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct StructInner {
a: bool,
b: i64,
c: Vec<Vec<i32>>,
d: Vec<StructInner>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum EnumInner {
A,
B,
C,
}
fn make_struct() -> Struct {
Struct {
foo: i32::MIN,
bar: StructInner {
a: true,
b: 123456789,
c: vec![vec![1, 2, 3], vec![4, 5, 6]],
d: vec![],
},
baz: "🤨".into(),
quux: vec![std::f32::consts::PI, f32::MAX, f32::MIN],
blah: EnumInner::B,
}
}
fn make_compound() -> Compound {
compound! {
"foo" => i32::MIN,
"bar" => compound! {
"a" => true,
"b" => 123456789_i64,
"c" => List::IntArray(vec![vec![1, 2, 3], vec![4, 5, 6]]),
"d" => List::End,
},
"baz" => "🤨",
"quux" => List::Float(vec![
std::f32::consts::PI,
f32::MAX,
f32::MIN,
]),
"blah" => "B"
}
}
fn make_json() -> serde_json::Value {
json!({
"foo": i32::MIN,
"bar": {
"a": true,
"b": 123456789_i64,
"c": [[1, 2, 3], [4, 5, 6]],
"d": []
},
"baz": "🤨",
"quux": [
std::f32::consts::PI,
f32::MAX,
f32::MIN,
],
"blah": "B"
})
}
#[test]
fn struct_to_compound() {
let c = make_struct().serialize(CompoundSerializer).unwrap();
assert_eq!(c, make_compound());
}
#[test]
fn compound_to_struct() {
let s = Struct::deserialize(make_compound()).unwrap();
assert_eq!(s, make_struct());
}
#[test]
fn compound_to_json() {
let mut j = serde_json::to_value(make_compound()).unwrap();
// Bools map to bytes in NBT, but the result should be the same otherwise.
let p = j.pointer_mut("/bar/a").unwrap();
assert_eq!(*p, serde_json::Value::from(1));
*p = true.into();
assert_eq!(j, make_json());
}

View file

@ -4,10 +4,11 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
bevy_app.workspace = true
bevy_ecs.workspace = true
indexmap.workspace = true
serde_json.workspace = true
serde.workspace = true
tracing.workspace = true tracing.workspace = true
valence_core.workspace = true valence_core.workspace = true
valence_nbt.workspace = true valence_nbt.workspace = true
bevy_ecs.workspace = true
bevy_app.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -0,0 +1,137 @@
use std::collections::BTreeMap;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use tracing::error;
use valence_core::ident::Ident;
use valence_nbt::{compound, Compound, List, Value};
use crate::RegistrySet;
pub(super) fn build(app: &mut App) {
app.init_resource::<RegistryCodec>()
.add_system(cache_registry_codec.in_set(RegistrySet));
}
/// Contains the registry codec sent to all players while joining. This contains
/// information for biomes and dimensions among other things.
///
/// Generally, end users should not manipulate the registry codec directly. Use
/// one of the other registry resources instead.
#[derive(Resource, Debug)]
pub struct RegistryCodec {
pub registries: BTreeMap<Ident<String>, Vec<RegistryValue>>,
// TODO: store this in binary form?
cached_codec: Compound,
}
#[derive(Clone, Debug)]
pub struct RegistryValue {
pub name: Ident<String>,
pub element: Compound,
}
impl RegistryCodec {
pub fn cached_codec(&self) -> &Compound {
&self.cached_codec
}
pub fn registry(&self, registry_key: Ident<&str>) -> &Vec<RegistryValue> {
self.registries
.get(registry_key.as_str())
.unwrap_or_else(|| panic!("missing registry for {registry_key}"))
}
pub fn registry_mut(&mut self, registry_key: Ident<&str>) -> &mut Vec<RegistryValue> {
self.registries
.get_mut(registry_key.as_str())
.unwrap_or_else(|| panic!("missing registry for {registry_key}"))
}
}
impl Default for RegistryCodec {
fn default() -> Self {
let codec = include_bytes!("../../../extracted/registry_codec_1.19.4.dat");
let compound = Compound::from_binary(&mut codec.as_slice())
.expect("failed to decode vanilla registry codec")
.0;
let mut registries = BTreeMap::new();
for (k, v) in compound {
let reg_name: Ident<String> = Ident::new(k).expect("invalid registry name").into();
let mut reg_values = vec![];
let Value::Compound(mut outer) = v else {
error!("registry {reg_name} is not a compound");
continue
};
let values = match outer.remove("value") {
Some(Value::List(List::Compound(values))) => values,
Some(Value::List(List::End)) => continue,
_ => {
error!("missing \"value\" compound in {reg_name}");
continue;
}
};
for mut value in values {
let Some(Value::String(name)) = value.remove("name") else {
error!("missing \"name\" string in value for {reg_name}");
continue
};
let name = match Ident::new(name) {
Ok(n) => n.into(),
Err(e) => {
error!("invalid registry value name \"{}\"", e.0);
continue;
}
};
let Some(Value::Compound(element)) = value.remove("element") else {
error!("missing \"element\" compound in value for {reg_name}");
continue
};
reg_values.push(RegistryValue { name, element });
}
registries.insert(reg_name, reg_values);
}
Self {
registries,
// Cache will be created later.
cached_codec: Compound::new(),
}
}
}
fn cache_registry_codec(codec: ResMut<RegistryCodec>) {
if codec.is_changed() {
let codec = codec.into_inner();
codec.cached_codec.clear();
for (reg_name, reg) in &codec.registries {
let mut value = vec![];
for (id, v) in reg.iter().enumerate() {
value.push(compound! {
"id" => id as i32,
"name" => v.name.as_str(),
"element" => v.element.clone(),
});
}
let registry = compound! {
"type" => reg_name.as_str(),
"value" => List::Compound(value),
};
codec.cached_codec.insert(reg_name.as_str(), registry);
}
}
}

View file

@ -17,155 +17,163 @@
clippy::dbg_macro clippy::dbg_macro
)] )]
use std::collections::BTreeMap; pub mod codec;
pub mod tags;
use std::fmt::Debug;
use std::hash::Hash;
use std::marker::PhantomData;
use std::ops::{Index, IndexMut};
use bevy_app::prelude::*; use bevy_app::prelude::*;
pub use bevy_ecs::prelude::*; pub use bevy_ecs::prelude::*;
use tracing::error; use indexmap::map::Entry;
use indexmap::IndexMap;
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_nbt::{compound, Compound, List, Value};
mod tags;
pub use tags::*;
pub struct RegistryPlugin; pub struct RegistryPlugin;
/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that /// The [`SystemSet`] where the [`RegistryCodec`](codec::RegistryCodec) and
/// modify the registry codec should run _before_ this. /// [`TagsRegistry`](tags::TagsRegistry) caches are rebuilt. Systems that modify
/// the registry codec or tags registry should run _before_ this.
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct RegistryCodecSet; pub struct RegistrySet;
impl Plugin for RegistryPlugin { impl Plugin for RegistryPlugin {
fn build(&self, app: &mut bevy_app::App) { fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<RegistryCodec>() app.configure_set(RegistrySet.in_base_set(CoreSet::PostUpdate));
.init_resource::<TagsRegistry>()
.configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate)) codec::build(app);
.add_startup_system(init_tags_registry.in_set(RegistryCodecSet)) tags::build(app);
.add_system(cache_registry_codec.in_set(RegistryCodecSet))
.add_system(cache_tags_packet.in_set(RegistryCodecSet));
} }
} }
fn cache_registry_codec(codec: ResMut<RegistryCodec>) {
if codec.is_changed() {
let codec = codec.into_inner();
codec.cached_codec.clear();
for (reg_name, reg) in &codec.registries {
let mut value = vec![];
for (id, v) in reg.iter().enumerate() {
value.push(compound! {
"id" => id as i32,
"name" => v.name.as_str(),
"element" => v.element.clone(),
});
}
let registry = compound! {
"type" => reg_name.as_str(),
"value" => List::Compound(value),
};
codec.cached_codec.insert(reg_name.as_str(), registry);
}
}
}
/// Contains the registry codec sent to all players while joining. This contains
/// information for biomes and dimensions among other things.
///
/// Generally, end users should not manipulate the registry codec directly. Use
/// one of the other modules instead.
#[derive(Resource, Debug)]
pub struct RegistryCodec {
pub registries: BTreeMap<Ident<String>, Vec<RegistryValue>>,
// TODO: store this in binary form?
cached_codec: Compound,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RegistryValue { pub struct Registry<I, V> {
pub name: Ident<String>, items: IndexMap<Ident<String>, V>,
pub element: Compound, _marker: PhantomData<I>,
} }
impl RegistryCodec { impl<I: RegistryIdx, V> Registry<I, V> {
pub fn cached_codec(&self) -> &Compound { pub fn new() -> Self {
&self.cached_codec
}
pub fn registry(&self, registry_key: Ident<&str>) -> &Vec<RegistryValue> {
self.registries
.get(registry_key.as_str())
.unwrap_or_else(|| panic!("missing registry for {registry_key}"))
}
pub fn registry_mut(&mut self, registry_key: Ident<&str>) -> &mut Vec<RegistryValue> {
self.registries
.get_mut(registry_key.as_str())
.unwrap_or_else(|| panic!("missing registry for {registry_key}"))
}
}
impl Default for RegistryCodec {
fn default() -> Self {
let codec = include_bytes!("../../../extracted/registry_codec_1.19.4.dat");
let compound = Compound::from_binary(&mut codec.as_slice())
.expect("failed to decode vanilla registry codec")
.0;
let mut registries = BTreeMap::new();
for (k, v) in compound {
let reg_name: Ident<String> = Ident::new(k).expect("invalid registry name").into();
let mut reg_values = vec![];
let Value::Compound(mut outer) = v else {
error!("registry {reg_name} is not a compound");
continue
};
let values = match outer.remove("value") {
Some(Value::List(List::Compound(values))) => values,
Some(Value::List(List::End)) => continue,
_ => {
error!("missing \"value\" compound in {reg_name}");
continue;
}
};
for mut value in values {
let Some(Value::String(name)) = value.remove("name") else {
error!("missing \"name\" string in value for {reg_name}");
continue
};
let name = match Ident::new(name) {
Ok(n) => n.into(),
Err(e) => {
error!("invalid registry value name \"{}\"", e.0);
continue;
}
};
let Some(Value::Compound(element)) = value.remove("element") else {
error!("missing \"element\" compound in value for {reg_name}");
continue
};
reg_values.push(RegistryValue { name, element });
}
registries.insert(reg_name, reg_values);
}
Self { Self {
registries, items: IndexMap::new(),
// Cache will be created later. _marker: PhantomData,
cached_codec: Compound::new(), }
}
pub fn insert(&mut self, name: impl Into<Ident<String>>, item: V) -> Option<I> {
if self.items.len() >= I::MAX {
// Too many items in the registry.
return None;
}
let len = self.items.len();
match self.items.entry(name.into()) {
Entry::Occupied(_) => None,
Entry::Vacant(ve) => {
ve.insert(item);
Some(I::from_index(len))
}
}
}
pub fn swap_to_front(&mut self, name: Ident<&str>) {
if let Some(idx) = self.items.get_index_of(name.as_str()) {
self.items.swap_indices(0, idx);
}
}
pub fn remove(&mut self, name: Ident<&str>) -> Option<V> {
self.items.shift_remove(name.as_str())
}
pub fn get(&self, name: Ident<&str>) -> Option<&V> {
self.items.get(name.as_str())
}
pub fn get_mut(&mut self, name: Ident<&str>) -> Option<&mut V> {
self.items.get_mut(name.as_str())
}
pub fn index_of(&self, name: Ident<&str>) -> Option<I> {
self.items.get_index_of(name.as_str()).map(I::from_index)
}
pub fn iter(
&self,
) -> impl DoubleEndedIterator<Item = (I, Ident<&str>, &V)> + ExactSizeIterator + '_ {
self.items
.iter()
.enumerate()
.map(|(i, (k, v))| (I::from_index(i), k.as_str_ident(), v))
}
pub fn iter_mut(
&mut self,
) -> impl DoubleEndedIterator<Item = (I, Ident<&str>, &mut V)> + ExactSizeIterator + '_ {
self.items
.iter_mut()
.enumerate()
.map(|(i, (k, v))| (I::from_index(i), k.as_str_ident(), v))
}
}
impl<I: RegistryIdx, V> Index<I> for Registry<I, V> {
type Output = V;
fn index(&self, index: I) -> &Self::Output {
self.items
.get_index(index.to_index())
.unwrap_or_else(|| panic!("out of bounds registry index of {}", index.to_index()))
.1
}
}
impl<I: RegistryIdx, V> IndexMut<I> for Registry<I, V> {
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.items
.get_index_mut(index.to_index())
.unwrap_or_else(|| panic!("out of bounds registry index of {}", index.to_index()))
.1
}
}
impl<'a, I: RegistryIdx, V> Index<Ident<&'a str>> for Registry<I, V> {
type Output = V;
fn index(&self, index: Ident<&'a str>) -> &Self::Output {
if let Some(item) = self.items.get(index.as_str()) {
item
} else {
panic!("missing registry item with name '{index}'")
} }
} }
} }
impl<'a, I: RegistryIdx, V> IndexMut<Ident<&'a str>> for Registry<I, V> {
fn index_mut(&mut self, index: Ident<&'a str>) -> &mut Self::Output {
if let Some(item) = self.items.get_mut(index.as_str()) {
item
} else {
panic!("missing registry item with name '{index}'")
}
}
}
impl<I, V> Default for Registry<I, V> {
fn default() -> Self {
Self {
items: IndexMap::new(),
_marker: PhantomData,
}
}
}
pub trait RegistryIdx: Copy + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + Debug {
const MAX: usize;
fn to_index(self) -> usize;
fn from_index(idx: usize) -> Self;
}

View file

@ -1,5 +1,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use valence_core::ident::Ident; use valence_core::ident::Ident;
@ -8,6 +9,14 @@ use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{packet_id, Decode, Encode, Packet}; use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_core::Server; use valence_core::Server;
use crate::RegistrySet;
pub(super) fn build(app: &mut App) {
app.init_resource::<TagsRegistry>()
.add_startup_system(init_tags_registry)
.add_system(cache_tags_packet.in_set(RegistrySet));
}
#[derive(Clone, Debug, Encode, Decode, Packet)] #[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SYNCHRONIZE_TAGS_S2C)] #[packet(id = packet_id::SYNCHRONIZE_TAGS_S2C)]
pub struct SynchronizeTagsS2c<'a> { pub struct SynchronizeTagsS2c<'a> {
@ -32,8 +41,8 @@ pub struct TagEntry {
pub entries: Vec<VarInt>, pub entries: Vec<VarInt>,
} }
impl<'a> TagsRegistry { impl TagsRegistry {
pub(crate) fn build_synchronize_tags(&'a self) -> SynchronizeTagsS2c<'a> { fn build_synchronize_tags(&self) -> SynchronizeTagsS2c {
SynchronizeTagsS2c { SynchronizeTagsS2c {
registries: Cow::Borrowed(&self.registries), registries: Cow::Borrowed(&self.registries),
} }
@ -44,7 +53,7 @@ impl<'a> TagsRegistry {
} }
} }
pub(crate) fn init_tags_registry(mut tags: ResMut<TagsRegistry>) { pub fn init_tags_registry(mut tags: ResMut<TagsRegistry>) {
let registries = let registries =
serde_json::from_str::<Vec<Registry>>(include_str!("../../../extracted/tags.json")) serde_json::from_str::<Vec<Registry>>(include_str!("../../../extracted/tags.json"))
.expect("tags.json is invalid"); .expect("tags.json is invalid");

View file

@ -22,8 +22,8 @@ pub fn build_app(app: &mut App) {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
server: Res<Server>, server: Res<Server>,
biomes: Query<&Biome>, biomes: Res<BiomeRegistry>,
dimensions: Query<&DimensionType>, dimensions: Res<DimensionTypeRegistry>,
) { ) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);