mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
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:
parent
e76e913b3c
commit
c4741b68b8
|
@ -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"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(RegistryCodecSet),
|
.before(RegistrySet),
|
||||||
)
|
);
|
||||||
.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
|
||||||
|
.serialize(CompoundSerializer)
|
||||||
|
.expect("failed to serialize biome"),
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
let biome_compound = compound! {
|
|
||||||
"downfall" => biome.downfall,
|
|
||||||
"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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -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
|
|
@ -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(RegistryCodecSet),
|
.before(RegistrySet),
|
||||||
)
|
);
|
||||||
.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}\""),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
109
crates/valence_nbt/src/serde/tests.rs
Normal file
109
crates/valence_nbt/src/serde/tests.rs
Normal 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());
|
||||||
|
}
|
|
@ -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
|
|
137
crates/valence_registry/src/codec.rs
Normal file
137
crates/valence_registry/src/codec.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue