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_instance.path = "crates/valence_instance"
|
||||
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_player_list.path = "crates/valence_player_list"
|
||||
valence_registry.path = "crates/valence_registry"
|
||||
|
|
|
@ -21,8 +21,8 @@ pub fn idle_update(c: &mut Criterion) {
|
|||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
server: Res<Server>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
|
|
@ -43,8 +43,8 @@ fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@ pub fn main() {
|
|||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
server: Res<Server>,
|
||||
) {
|
||||
let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
|
|
@ -57,8 +57,8 @@ fn print_tick_time(
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ pub fn main() {
|
|||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
biome_reg: Res<BiomeRegistry>,
|
||||
server: Res<Server>,
|
||||
) {
|
||||
|
|
|
@ -22,8 +22,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -32,8 +32,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(setup_biomes.before(setup))
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_systems((
|
||||
|
@ -37,19 +36,16 @@ pub fn main() {
|
|||
.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(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: ResMut<DimensionTypeRegistry>,
|
||||
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);
|
||||
|
||||
for z in -10..10 {
|
||||
|
|
|
@ -34,8 +34,8 @@ fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
for block in [BlockState::GRASS_BLOCK, BlockState::DEEPSLATE] {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
|
|
@ -25,8 +25,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -57,8 +57,8 @@ fn init_clients(
|
|||
Added<Client>,
|
||||
>,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
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(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -60,8 +60,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let seconds_per_day = 86_400;
|
||||
let seed = (SystemTime::now()
|
||||
|
|
|
@ -18,8 +18,8 @@ pub fn main() {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
|
@ -4,15 +4,16 @@ use std::time::Instant;
|
|||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use uuid::Uuid;
|
||||
use valence_biome::BiomeRegistry;
|
||||
use valence_client::ClientBundleArgs;
|
||||
use valence_core::protocol::decode::{PacketDecoder, PacketFrame};
|
||||
use valence_core::protocol::encode::PacketEncoder;
|
||||
use valence_core::protocol::var_int::VarInt;
|
||||
use valence_core::protocol::{Encode, Packet};
|
||||
use valence_core::{ident, CoreSettings, Server};
|
||||
use valence_dimension::DimensionTypeRegistry;
|
||||
use valence_entity::Location;
|
||||
use valence_network::{ConnectionMode, NetworkSettings};
|
||||
|
||||
|
@ -37,9 +38,17 @@ fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
|
|||
|
||||
app.add_plugins(DefaultPlugins);
|
||||
|
||||
let server = app.world.resource::<Server>();
|
||||
let instance = Instance::new_unit_testing(ident!("overworld"), server);
|
||||
app.update(); // Initialize plugins.
|
||||
|
||||
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 (client, client_helper) = create_mock_client();
|
||||
|
||||
let client_ent = app.world.spawn(client).id();
|
||||
|
@ -47,14 +56,6 @@ fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
|
|||
// Set initial location.
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,11 @@ version.workspace = true
|
|||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
valence_nbt.workspace = true
|
||||
valence_registry.workspace = true
|
||||
valence_core.workspace = true
|
||||
valence_nbt = { workspace = true, features = ["serde"] }
|
||||
valence_registry.workspace = true
|
||||
|
|
|
@ -17,111 +17,43 @@
|
|||
clippy::dbg_macro
|
||||
)]
|
||||
|
||||
use std::ops::Index;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
use valence_core::ident;
|
||||
use valence_core::ident::Ident;
|
||||
use valence_nbt::{compound, Value};
|
||||
use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue};
|
||||
use valence_nbt::serde::CompoundSerializer;
|
||||
use valence_registry::codec::{RegistryCodec, RegistryValue};
|
||||
use valence_registry::{Registry, RegistryIdx, RegistrySet};
|
||||
|
||||
pub struct BiomePlugin;
|
||||
|
||||
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
|
||||
struct BiomeSet;
|
||||
|
||||
impl Plugin for BiomePlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.insert_resource(BiomeRegistry {
|
||||
id_to_biome: vec![],
|
||||
})
|
||||
.configure_set(
|
||||
BiomeSet
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(RegistryCodecSet),
|
||||
)
|
||||
.add_systems(
|
||||
(update_biome_registry, remove_biomes_from_registry)
|
||||
.chain()
|
||||
.in_set(BiomeSet),
|
||||
)
|
||||
.add_startup_system(load_default_biomes.in_base_set(StartupSet::PreStartup));
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<BiomeRegistry>()
|
||||
.add_startup_system(load_default_biomes.in_base_set(CoreSet::PreUpdate))
|
||||
.add_system(
|
||||
update_biome_registry
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(RegistrySet),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_default_biomes(
|
||||
mut reg: ResMut<BiomeRegistry>,
|
||||
codec: Res<RegistryCodec>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let mut helper = move || {
|
||||
fn load_default_biomes(mut reg: ResMut<BiomeRegistry>, codec: Res<RegistryCodec>) {
|
||||
let mut helper = move || -> anyhow::Result<()> {
|
||||
for value in codec.registry(BiomeRegistry::KEY) {
|
||||
let downfall = *value
|
||||
.element
|
||||
.get("downfall")
|
||||
.and_then(|v| v.as_float())
|
||||
.context("invalid downfall")?;
|
||||
let biome = Biome::deserialize(value.element.clone())?;
|
||||
|
||||
let Some(Value::Compound(effects)) = value.element.get("effects") else {
|
||||
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);
|
||||
reg.insert(value.name.clone(), biome);
|
||||
}
|
||||
|
||||
// Move "plains" to the front so that `BiomeId::default()` is the ID of plains.
|
||||
reg.swap_to_front(ident!("plains"));
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
|
@ -130,126 +62,102 @@ fn load_default_biomes(
|
|||
}
|
||||
}
|
||||
|
||||
/// Add new biomes to or update existing biomes in the registry.
|
||||
fn update_biome_registry(
|
||||
mut reg: ResMut<BiomeRegistry>,
|
||||
mut codec: ResMut<RegistryCodec>,
|
||||
biomes: Query<(Entity, &Biome), Changed<Biome>>,
|
||||
) {
|
||||
for (entity, biome) in &biomes {
|
||||
let biome_registry = codec.registry_mut(BiomeRegistry::KEY);
|
||||
fn update_biome_registry(reg: Res<BiomeRegistry>, mut codec: ResMut<RegistryCodec>) {
|
||||
if reg.is_changed() {
|
||||
let biomes = codec.registry_mut(BiomeRegistry::KEY);
|
||||
|
||||
let mut effects = compound! {
|
||||
"fog_color" => biome.fog_color,
|
||||
"sky_color" => biome.sky_color,
|
||||
"water_color" => biome.water_color,
|
||||
"water_fog_color" => biome.water_fog_color,
|
||||
};
|
||||
biomes.clear();
|
||||
|
||||
if let Some(grass_color) = biome.grass_color {
|
||||
effects.insert("grass_color", grass_color);
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
biomes.extend(reg.iter().map(|(_, name, biome)| {
|
||||
RegistryValue {
|
||||
name: name.into(),
|
||||
element: biome
|
||||
.serialize(CompoundSerializer)
|
||||
.expect("failed to serialize biome"),
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove deleted biomes from the registry.
|
||||
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)]
|
||||
#[derive(Resource, Default, Debug)]
|
||||
pub struct BiomeRegistry {
|
||||
id_to_biome: Vec<Entity>,
|
||||
reg: Registry<BiomeId, Biome>,
|
||||
}
|
||||
|
||||
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> {
|
||||
self.id_to_biome.get(id.0 as usize).cloned()
|
||||
}
|
||||
impl Deref for BiomeRegistry {
|
||||
type Target = Registry<BiomeId, Biome>;
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (BiomeId, Entity)> + '_ {
|
||||
self.id_to_biome
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, biome)| (BiomeId(id as _), *biome))
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.reg
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<BiomeId> for BiomeRegistry {
|
||||
type Output = Entity;
|
||||
|
||||
fn index(&self, index: BiomeId) -> &Self::Output {
|
||||
self.id_to_biome
|
||||
.get(index.0 as usize)
|
||||
.unwrap_or_else(|| panic!("invalid {index:?}"))
|
||||
impl DerefMut for BiomeRegistry {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.reg
|
||||
}
|
||||
}
|
||||
|
||||
/// An index into the biome registry.
|
||||
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub struct BiomeId(pub u16);
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct BiomeId(u32);
|
||||
|
||||
#[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 name: Ident<String>,
|
||||
pub downfall: f32,
|
||||
pub fog_color: i32,
|
||||
pub sky_color: i32,
|
||||
pub water_color: i32,
|
||||
pub water_fog_color: i32,
|
||||
pub grass_color: Option<i32>,
|
||||
pub effects: BiomeEffects,
|
||||
pub has_precipitation: bool,
|
||||
pub temperature: f32,
|
||||
// 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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: ident!("plains").into(),
|
||||
downfall: 0.4,
|
||||
fog_color: 12638463,
|
||||
sky_color: 7907327,
|
||||
water_color: 4159204,
|
||||
water_fog_color: 329011,
|
||||
grass_color: None,
|
||||
effects: BiomeEffects::default(),
|
||||
has_precipitation: true,
|
||||
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,
|
||||
};
|
||||
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 chat;
|
||||
|
@ -112,7 +114,7 @@ impl Plugin for ClientPlugin {
|
|||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
(
|
||||
initial_join.after(RegistryCodecSet),
|
||||
initial_join.after(RegistrySet),
|
||||
update_chunk_load_dist,
|
||||
read_data_in_old_view
|
||||
.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
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
valence_registry.workspace = true
|
||||
valence_nbt.workspace = true
|
||||
valence_core.workspace = true
|
||||
valence_nbt = { workspace = true, features = ["serde"] }
|
||||
valence_registry.workspace = true
|
|
@ -17,155 +17,39 @@
|
|||
clippy::dbg_macro
|
||||
)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{error, warn};
|
||||
use valence_core::ident;
|
||||
use valence_core::ident::Ident;
|
||||
use valence_nbt::{compound, Value};
|
||||
use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue};
|
||||
use valence_nbt::serde::CompoundSerializer;
|
||||
use valence_registry::codec::{RegistryCodec, RegistryValue};
|
||||
use valence_registry::{Registry, RegistryIdx, RegistrySet};
|
||||
|
||||
pub struct DimensionPlugin;
|
||||
|
||||
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
struct DimensionSet;
|
||||
|
||||
impl Plugin for DimensionPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(DimensionTypeRegistry {
|
||||
name_to_dimension: BTreeMap::new(),
|
||||
})
|
||||
.configure_set(
|
||||
DimensionSet
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(RegistryCodecSet),
|
||||
)
|
||||
.add_systems(
|
||||
(
|
||||
update_dimension_type_registry,
|
||||
remove_dimension_types_from_registry,
|
||||
)
|
||||
.chain()
|
||||
.in_set(DimensionSet),
|
||||
)
|
||||
.add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup));
|
||||
app.init_resource::<DimensionTypeRegistry>()
|
||||
.add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup))
|
||||
.add_system(
|
||||
update_dimension_type_registry
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(RegistrySet),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_dimension_type_registry(
|
||||
mut reg: ResMut<DimensionTypeRegistry>,
|
||||
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,
|
||||
) {
|
||||
/// Loads the default dimension types from the registry codec.
|
||||
fn load_default_dimension_types(mut reg: ResMut<DimensionTypeRegistry>, codec: Res<RegistryCodec>) {
|
||||
let mut helper = move || -> anyhow::Result<()> {
|
||||
for value in codec.registry(DimensionTypeRegistry::KEY) {
|
||||
macro_rules! get {
|
||||
($name:literal, $f:expr) => {{
|
||||
value
|
||||
.element
|
||||
.get($name)
|
||||
.and_then($f)
|
||||
.context(concat!("invalid ", $name))?
|
||||
}};
|
||||
}
|
||||
let dimension_type = DimensionType::deserialize(value.element.clone())?;
|
||||
|
||||
let entity = commands
|
||||
.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);
|
||||
}
|
||||
reg.insert(value.name.clone(), dimension_type);
|
||||
}
|
||||
|
||||
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 {
|
||||
name_to_dimension: BTreeMap<Ident<String>, Entity>,
|
||||
reg: Registry<DimensionTypeId, DimensionType>,
|
||||
}
|
||||
|
||||
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> {
|
||||
self.name_to_dimension.get(name.as_str()).copied()
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
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> + '_ {
|
||||
self.name_to_dimension.values().copied()
|
||||
fn from_index(idx: usize) -> Self {
|
||||
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 name: Ident<String>,
|
||||
pub ambient_light: f32,
|
||||
pub bed_works: bool,
|
||||
pub coordinate_scale: f64,
|
||||
pub effects: DimensionEffects,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fixed_time: Option<i32>,
|
||||
pub has_ceiling: bool,
|
||||
pub has_raids: bool,
|
||||
pub has_skylight: bool,
|
||||
|
@ -208,7 +137,7 @@ pub struct DimensionType {
|
|||
pub logical_height: i32,
|
||||
pub min_y: i32,
|
||||
pub monster_spawn_block_light_limit: i32,
|
||||
/// TODO: monster_spawn_light_level
|
||||
pub monster_spawn_light_level: MonsterSpawnLightLevel,
|
||||
pub natural: bool,
|
||||
pub piglin_safe: bool,
|
||||
pub respawn_anchor_works: bool,
|
||||
|
@ -218,11 +147,11 @@ pub struct DimensionType {
|
|||
impl Default for DimensionType {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: ident!("minecraft:overworld").into(),
|
||||
ambient_light: 1.0,
|
||||
bed_works: true,
|
||||
coordinate_scale: 1.0,
|
||||
effects: DimensionEffects::default(),
|
||||
fixed_time: None,
|
||||
has_ceiling: false,
|
||||
has_raids: true,
|
||||
has_skylight: true,
|
||||
|
@ -231,42 +160,46 @@ impl Default for DimensionType {
|
|||
logical_height: 384,
|
||||
min_y: -64,
|
||||
monster_spawn_block_light_limit: 0,
|
||||
monster_spawn_light_level: MonsterSpawnLightLevel::Int(7),
|
||||
natural: true,
|
||||
piglin_safe: false,
|
||||
respawn_anchor_works: true,
|
||||
respawn_anchor_works: false,
|
||||
ultrawarm: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
#[serde(rename = "minecraft:overworld")]
|
||||
#[default]
|
||||
Overworld,
|
||||
#[serde(rename = "minecraft:the_nether")]
|
||||
TheNether,
|
||||
#[serde(rename = "minecraft:the_end")]
|
||||
TheEnd,
|
||||
}
|
||||
|
||||
impl From<DimensionEffects> for Ident<&'static str> {
|
||||
fn from(value: DimensionEffects) -> Self {
|
||||
match value {
|
||||
DimensionEffects::Overworld => ident!("overworld"),
|
||||
DimensionEffects::TheNether => ident!("the_nether"),
|
||||
DimensionEffects::TheEnd => ident!("the_end"),
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MonsterSpawnLightLevel {
|
||||
Int(i32),
|
||||
Tagged(MonsterSpawnLightLevelTagged),
|
||||
}
|
||||
|
||||
impl FromStr for DimensionEffects {
|
||||
type Err = anyhow::Error;
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[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> {
|
||||
match Ident::new(s)?.as_str() {
|
||||
"minecraft:overworld" => Ok(DimensionEffects::Overworld),
|
||||
"minecraft:the_nether" => Ok(DimensionEffects::TheNether),
|
||||
"minecraft:the_end" => Ok(DimensionEffects::TheEnd),
|
||||
other => bail!("unknown dimension effect \"{other}\""),
|
||||
}
|
||||
impl From<i32> for MonsterSpawnLightLevel {
|
||||
fn from(value: i32) -> Self {
|
||||
Self::Int(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,3 +19,4 @@ valence_core.workspace = true
|
|||
valence_dimension.workspace = true
|
||||
valence_entity.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::Encode;
|
||||
use valence_nbt::{compound, Compound};
|
||||
use valence_registry::RegistryIdx;
|
||||
|
||||
use crate::packet::{
|
||||
BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataBlockEntity, ChunkDataS2c, ChunkDeltaUpdateS2c,
|
||||
|
@ -414,7 +415,7 @@ impl Chunk<true> {
|
|||
sect.biomes
|
||||
.encode_mc_format(
|
||||
&mut *scratch,
|
||||
|b| b.0.into(),
|
||||
|b| b.to_index() as _,
|
||||
0,
|
||||
3,
|
||||
bit_width(info.biome_registry_len - 1),
|
||||
|
|
|
@ -31,7 +31,7 @@ pub use chunk_entry::*;
|
|||
use glam::{DVec3, Vec3};
|
||||
use num_integer::div_ceil;
|
||||
use rustc_hash::FxHashMap;
|
||||
use valence_biome::Biome;
|
||||
use valence_biome::BiomeRegistry;
|
||||
use valence_core::block_pos::BlockPos;
|
||||
use valence_core::chunk_pos::ChunkPos;
|
||||
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::{Encode, Packet};
|
||||
use valence_core::Server;
|
||||
use valence_dimension::DimensionType;
|
||||
use valence_dimension::DimensionTypeRegistry;
|
||||
use valence_entity::packet::{
|
||||
EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c,
|
||||
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, RotateAndMoveRelativeS2c,
|
||||
|
@ -432,40 +432,33 @@ pub struct InstanceInfo {
|
|||
#[derive(Debug)]
|
||||
pub struct PartitionCell {
|
||||
/// The chunk in this cell.
|
||||
#[doc(hidden)]
|
||||
pub chunk: Option<Chunk<true>>,
|
||||
/// If `chunk` went from `Some` to `None` this tick.
|
||||
#[doc(hidden)]
|
||||
pub chunk_removed: bool,
|
||||
/// Minecraft entities in this cell.
|
||||
#[doc(hidden)]
|
||||
pub entities: BTreeSet<Entity>,
|
||||
/// Minecraft entities that have entered the chunk this tick, paired with
|
||||
/// the cell position in this instance they came from.
|
||||
#[doc(hidden)]
|
||||
pub incoming: Vec<(Entity, Option<ChunkPos>)>,
|
||||
/// Minecraft entities that have left the chunk this tick, paired with the
|
||||
/// cell position in this world they arrived at.
|
||||
#[doc(hidden)]
|
||||
pub outgoing: Vec<(Entity, Option<ChunkPos>)>,
|
||||
/// A cache of packets to send to all clients that are in view of this cell
|
||||
/// at the end of the tick.
|
||||
#[doc(hidden)]
|
||||
pub packet_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
#[track_caller]
|
||||
pub fn new(
|
||||
dimension_type_name: impl Into<Ident<String>>,
|
||||
dimensions: &Query<&DimensionType>,
|
||||
biomes: &Query<&Biome>,
|
||||
dimensions: &DimensionTypeRegistry,
|
||||
biomes: &BiomeRegistry,
|
||||
server: &Server,
|
||||
) -> Self {
|
||||
let dimension_type_name = dimension_type_name.into();
|
||||
|
||||
let Some(dim) = dimensions.iter().find(|d| d.name == dimension_type_name) else {
|
||||
panic!("missing dimension type with name \"{dimension_type_name}\"")
|
||||
};
|
||||
let dim = &dimensions[dimension_type_name.as_str_ident()];
|
||||
|
||||
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> {
|
||||
self.info.dimension_type_name.as_str_ident()
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ use thiserror::Error;
|
|||
|
||||
mod de;
|
||||
mod ser;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Errors that can occur while serializing or deserializing.
|
||||
#[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())
|
||||
}
|
||||
}
|
||||
|
||||
#[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! {
|
||||
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
|
||||
tuple_struct map struct enum identifier ignored_any
|
||||
bytes byte_buf unit unit_struct newtype_struct seq tuple
|
||||
tuple_struct map struct identifier ignored_any
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -324,11 +324,11 @@ impl Serializer for ValueSerializer {
|
|||
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
|
||||
T: Serialize,
|
||||
{
|
||||
unsupported!("some")
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||
|
@ -343,9 +343,9 @@ impl Serializer for ValueSerializer {
|
|||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
variant: &'static str,
|
||||
) -> Result<Self::Ok, Self::Error> {
|
||||
unsupported!("unit variant")
|
||||
Ok(Value::String(variant.into()))
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
indexmap.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
valence_core.workspace = true
|
||||
valence_nbt.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
bevy_app.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
valence_nbt.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
|
||||
)]
|
||||
|
||||
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::*;
|
||||
pub use bevy_ecs::prelude::*;
|
||||
use tracing::error;
|
||||
use indexmap::map::Entry;
|
||||
use indexmap::IndexMap;
|
||||
use valence_core::ident::Ident;
|
||||
use valence_nbt::{compound, Compound, List, Value};
|
||||
|
||||
mod tags;
|
||||
|
||||
pub use tags::*;
|
||||
|
||||
pub struct RegistryPlugin;
|
||||
|
||||
/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that
|
||||
/// modify the registry codec should run _before_ this.
|
||||
/// The [`SystemSet`] where the [`RegistryCodec`](codec::RegistryCodec) and
|
||||
/// [`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)]
|
||||
pub struct RegistryCodecSet;
|
||||
pub struct RegistrySet;
|
||||
|
||||
impl Plugin for RegistryPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.init_resource::<RegistryCodec>()
|
||||
.init_resource::<TagsRegistry>()
|
||||
.configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate))
|
||||
.add_startup_system(init_tags_registry.in_set(RegistryCodecSet))
|
||||
.add_system(cache_registry_codec.in_set(RegistryCodecSet))
|
||||
.add_system(cache_tags_packet.in_set(RegistryCodecSet));
|
||||
app.configure_set(RegistrySet.in_base_set(CoreSet::PostUpdate));
|
||||
|
||||
codec::build(app);
|
||||
tags::build(app);
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct RegistryValue {
|
||||
pub name: Ident<String>,
|
||||
pub element: Compound,
|
||||
pub struct Registry<I, V> {
|
||||
items: IndexMap<Ident<String>, V>,
|
||||
_marker: PhantomData<I>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
impl<I: RegistryIdx, V> Registry<I, V> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
registries,
|
||||
// Cache will be created later.
|
||||
cached_codec: Compound::new(),
|
||||
items: IndexMap::new(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
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 bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use serde::Deserialize;
|
||||
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::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)]
|
||||
#[packet(id = packet_id::SYNCHRONIZE_TAGS_S2C)]
|
||||
pub struct SynchronizeTagsS2c<'a> {
|
||||
|
@ -32,8 +41,8 @@ pub struct TagEntry {
|
|||
pub entries: Vec<VarInt>,
|
||||
}
|
||||
|
||||
impl<'a> TagsRegistry {
|
||||
pub(crate) fn build_synchronize_tags(&'a self) -> SynchronizeTagsS2c<'a> {
|
||||
impl TagsRegistry {
|
||||
fn build_synchronize_tags(&self) -> SynchronizeTagsS2c {
|
||||
SynchronizeTagsS2c {
|
||||
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 =
|
||||
serde_json::from_str::<Vec<Registry>>(include_str!("../../../extracted/tags.json"))
|
||||
.expect("tags.json is invalid");
|
||||
|
|
|
@ -22,8 +22,8 @@ pub fn build_app(app: &mut App) {
|
|||
fn setup(
|
||||
mut commands: Commands,
|
||||
server: Res<Server>,
|
||||
biomes: Query<&Biome>,
|
||||
dimensions: Query<&DimensionType>,
|
||||
biomes: Res<BiomeRegistry>,
|
||||
dimensions: Res<DimensionTypeRegistry>,
|
||||
) {
|
||||
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||
|
||||
|
|
Loading…
Reference in a new issue