Registry Redesign (#361)

## Description

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

View file

@ -85,7 +85,7 @@ valence_dimension.path = "crates/valence_dimension"
valence_entity.path = "crates/valence_entity"
valence_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"

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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>,
) {

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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 {

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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() {

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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()

View file

@ -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);

View file

@ -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)
}

View file

@ -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

View file

@ -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
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(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));
.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);
biomes.extend(reg.iter().map(|(_, name, biome)| {
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.
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,
}
}
}

View file

@ -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)

View file

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

View file

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

View file

@ -7,7 +7,8 @@ edition.workspace = true
anyhow.workspace = true
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

View file

@ -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
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(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));
.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)
}
}

View file

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

View file

@ -13,6 +13,7 @@ use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::var_long::VarLong;
use valence_core::protocol::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),

View file

@ -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()
}

View file

@ -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());
}
}

View file

@ -288,10 +288,32 @@ impl<'de> Deserializer<'de> for Value {
}
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
Value::String(s) => visitor.visit_enum(s.into_deserializer()), // Unit variant.
other => other.deserialize_any(visitor),
}
}
forward_to_deserialize_any! {
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
}
}

View file

@ -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>(

View file

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

View file

@ -4,10 +4,11 @@ version.workspace = true
edition.workspace = true
[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

View file

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

View file

@ -17,155 +17,163 @@
clippy::dbg_macro
)]
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;
}

View file

@ -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");

View file

@ -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);