Update to 1.19.4 (#302)

## Description

Closes #291

- Update extractors to support Minecraft 1.19.4
- Update code generators.
- Changed generated entity component names to avoid name collisions.
- Update `glam` version.
- Added `Encode` and `Decode` for `glam` types in `valence_protocol`.
- Fixed inconsistent packet names and assign packet IDs automatically.
- Remove `ident` and rename `ident_str` to `ident`.
- Rework registry codec configuration. Biomes and dimensions exist as
entities.`BiomeRegistry` and `DimensionTypeRegistry` resources have been
added. The vanilla registry codec is loaded at startup.

### Issues
- Creating new instances has become more tedious than it should be. This
will be addressed later.

## Test Plan

Steps:
1. Boot up a vanilla server with online mode disabled.
2. Run the `packet_inspector`.
3. Connect to the vanilla server through the packet inspector to ensure
all packets are updated correctly.
4. Close the vanilla server and try some valence examples.
This commit is contained in:
Ryan Johnson 2023-03-31 14:58:47 -07:00 committed by GitHub
parent 53573642ec
commit 9c9f672a22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 162436 additions and 154695 deletions

View file

@ -34,3 +34,4 @@ atty = "0.2"
directories = "4.0"
serde = { version = "1.0.152", features = ["derive"] }
toml = "0.7.2"
valence_nbt = { path = "../valence_nbt", version = "0.5.0", features = ["preserve_order"] }

View file

@ -5,7 +5,8 @@ edition = "2021"
[dependencies]
anyhow = "1.0.65"
glam = "0.22.0"
glam = "0.23.0"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"
valence = { path = "../valence" }
valence_protocol = { path = "../valence_protocol" }

View file

@ -7,15 +7,21 @@ use crate::extras::*;
const SPAWN_Y: i32 = 64;
pub fn build_app(app: &mut App) {
app.add_plugin(ServerPlugin::new(()))
app.add_plugin(ServerPlugin::new(()).with_connection_mode(ConnectionMode::Offline))
.add_startup_system(setup)
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
.add_system(init_clients)
.add_system(despawn_disconnected_clients);
.add_system(despawn_disconnected_clients)
.add_system(toggle_gamemode_on_sneak.in_schedule(EventLoopSchedule));
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
biomes: Query<&Biome>,
dimensions: Query<&DimensionType>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {

View file

@ -1,6 +1,6 @@
[package]
name = "valence"
version = "0.2.0+mc1.19.3"
version = "0.2.0+mc1.19.4"
edition = "2021"
description = "A framework for building Minecraft servers in Rust."
repository = "https://github.com/rj00a/valence"
@ -21,7 +21,7 @@ bevy_ecs = "0.10"
bitfield-struct = "0.3.1"
bytes = "1.2.1"
flume = "0.10.14"
glam = "0.22.0"
glam = "0.23.0"
hmac = "0.12.1"
num = "0.4.0"
parking_lot = "0.12.1"
@ -45,6 +45,7 @@ valence_nbt = { version = "0.5.0", path = "../valence_nbt", features = [
valence_protocol = { version = "0.1.0", path = "../valence_protocol", features = [
"encryption",
"compression",
"glam",
] }
[dependencies.reqwest]
@ -55,7 +56,7 @@ features = ["rustls-tls", "json"]
[dev-dependencies]
approx = "0.5.1"
glam = { version = "0.22.0", features = ["approx"] }
glam = { version = "0.23.0", features = ["approx"] }
noise = "0.8.2"
tracing-subscriber = "0.3.16"

View file

@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use anyhow::Context;
use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
use proc_macro2::TokenStream;
use quote::quote;
@ -17,8 +18,8 @@ struct Entity {
}
#[derive(Deserialize, Clone, Debug)]
struct EntityData {
types: BTreeMap<String, i32>,
struct EntityTypes {
entity_type: BTreeMap<String, i32>,
}
#[derive(Deserialize, Clone, Debug)]
@ -32,7 +33,7 @@ struct Field {
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "type", content = "default_value", rename_all = "snake_case")]
enum Value {
Byte(u8),
Byte(i8),
Integer(i32),
Long(i64),
Float(f32),
@ -50,6 +51,7 @@ enum Value {
OptionalBlockPos(Option<BlockPos>),
Facing(String),
OptionalUuid(Option<String>),
BlockState(String),
OptionalBlockState(Option<String>),
NbtCompound(String),
Particle(String),
@ -65,6 +67,18 @@ enum Value {
FrogVariant(String),
OptionalGlobalPos(Option<()>), // TODO
PaintingVariant(String),
SnifferState(String),
Vector3f {
x: f32,
y: f32,
z: f32,
},
Quaternionf {
x: f32,
y: f32,
z: f32,
w: f32,
},
}
#[derive(Deserialize, Debug, Clone, Copy)]
@ -91,22 +105,26 @@ impl Value {
Value::OptionalBlockPos(_) => 11,
Value::Facing(_) => 12,
Value::OptionalUuid(_) => 13,
Value::OptionalBlockState(_) => 14,
Value::NbtCompound(_) => 15,
Value::Particle(_) => 16,
Value::VillagerData { .. } => 17,
Value::OptionalInt(_) => 18,
Value::EntityPose(_) => 19,
Value::CatVariant(_) => 20,
Value::FrogVariant(_) => 21,
Value::OptionalGlobalPos(_) => 22,
Value::PaintingVariant(_) => 23,
Value::BlockState(_) => 14,
Value::OptionalBlockState(_) => 15,
Value::NbtCompound(_) => 16,
Value::Particle(_) => 17,
Value::VillagerData { .. } => 18,
Value::OptionalInt(_) => 19,
Value::EntityPose(_) => 20,
Value::CatVariant(_) => 21,
Value::FrogVariant(_) => 22,
Value::OptionalGlobalPos(_) => 23,
Value::PaintingVariant(_) => 24,
Value::SnifferState(_) => 25,
Value::Vector3f { .. } => 26,
Value::Quaternionf { .. } => 27,
}
}
pub fn field_type(&self) -> TokenStream {
match self {
Value::Byte(_) => quote!(u8),
Value::Byte(_) => quote!(i8),
Value::Integer(_) => quote!(i32),
Value::Long(_) => quote!(i64),
Value::Float(_) => quote!(f32),
@ -120,6 +138,7 @@ impl Value {
Value::OptionalBlockPos(_) => quote!(Option<crate::protocol::block_pos::BlockPos>),
Value::Facing(_) => quote!(crate::protocol::types::Direction),
Value::OptionalUuid(_) => quote!(Option<::uuid::Uuid>),
Value::BlockState(_) => quote!(crate::protocol::block::BlockState),
Value::OptionalBlockState(_) => quote!(crate::protocol::block::BlockState),
Value::NbtCompound(_) => quote!(crate::nbt::Compound),
Value::Particle(_) => quote!(crate::protocol::packet::s2c::play::particle::Particle),
@ -130,6 +149,9 @@ impl Value {
Value::FrogVariant(_) => quote!(crate::entity::FrogKind),
Value::OptionalGlobalPos(_) => quote!(()), // TODO
Value::PaintingVariant(_) => quote!(crate::entity::PaintingKind),
Value::SnifferState(_) => quote!(crate::entity::SnifferState),
Value::Vector3f { .. } => quote!(::glam::f32::Vec3),
Value::Quaternionf { .. } => quote!(::glam::f32::Quat),
}
}
@ -175,6 +197,9 @@ impl Value {
assert!(uuid.is_none());
quote!(None)
}
Value::BlockState(_) => {
quote!(crate::protocol::block::BlockState::default())
}
Value::OptionalBlockState(bs) => {
assert!(bs.is_none());
quote!(crate::protocol::block::BlockState::default())
@ -223,6 +248,14 @@ impl Value {
let variant = ident(p.to_pascal_case());
quote!(crate::entity::PaintingKind::#variant)
}
Value::SnifferState(s) => {
let state = ident(s.to_pascal_case());
quote!(crate::entity::SnifferState::#state)
}
Value::Vector3f { x, y, z } => quote!(::glam::f32::Vec3::new(#x, #y, #z)),
Value::Quaternionf { x, y, z, w } => quote! {
::glam::f32::Quat::from_xyzw(#x, #y, #z, #w)
},
}
}
@ -240,26 +273,14 @@ type Entities = BTreeMap<String, Entity>;
pub fn build() -> anyhow::Result<TokenStream> {
let entity_types =
serde_json::from_str::<EntityData>(include_str!("../../../extracted/entity_data.json"))?
.types;
serde_json::from_str::<EntityTypes>(include_str!("../../../extracted/misc.json"))
.context("failed to deserialize misc.json")?
.entity_type;
let entities: Entities =
serde_json::from_str::<Entities>(include_str!("../../../extracted/entities.json"))?
serde_json::from_str::<Entities>(include_str!("../../../extracted/entities.json"))
.context("failed to deserialize entities.json")?
.into_iter()
.map(|(entity_name, mut entity)| {
let change_name = |mut name: String| {
if let Some(stripped) = name.strip_suffix("Entity") {
if !stripped.is_empty() {
name = stripped.into();
}
}
name
};
entity.parent = entity.parent.map(change_name);
(change_name(entity_name), entity)
})
.collect();
let mut entity_kind_consts = TokenStream::new();
@ -271,18 +292,20 @@ pub fn build() -> anyhow::Result<TokenStream> {
for (entity_name, entity) in entities.clone() {
let entity_name_ident = ident(&entity_name);
let shouty_entity_name = entity_name.to_shouty_snake_case();
let shouty_entity_name_ident = ident(&shouty_entity_name);
let snake_entity_name = entity_name.to_snake_case();
let snake_entity_name_ident = ident(&snake_entity_name);
let stripped_shouty_entity_name = strip_entity_suffix(&entity_name).to_shouty_snake_case();
let stripped_shouty_entity_name_ident = ident(&stripped_shouty_entity_name);
let stripped_snake_entity_name = strip_entity_suffix(&entity_name).to_snake_case();
let stripped_snake_entity_name_ident = ident(&stripped_snake_entity_name);
let mut module_body = TokenStream::new();
if let Some(parent_name) = entity.parent {
let snake_parent_name = parent_name.to_snake_case();
let stripped_snake_parent_name = strip_entity_suffix(&parent_name).to_snake_case();
let module_doc =
format!("Parent class: [`{snake_parent_name}`][super::{snake_parent_name}].");
let module_doc = format!(
"Parent class: \
[`{stripped_snake_parent_name}`][super::{stripped_snake_parent_name}]."
);
module_body.extend([quote! {
#![doc = #module_doc]
@ -294,11 +317,11 @@ pub fn build() -> anyhow::Result<TokenStream> {
let entity_type_id = entity_types[&entity_type];
entity_kind_consts.extend([quote! {
pub const #shouty_entity_name_ident: EntityKind = EntityKind(#entity_type_id);
pub const #stripped_shouty_entity_name_ident: EntityKind = EntityKind(#entity_type_id);
}]);
entity_kind_fmt_args.extend([quote! {
EntityKind::#shouty_entity_name_ident => write!(f, "{} ({})", #entity_type_id, #shouty_entity_name),
EntityKind::#stripped_shouty_entity_name_ident => write!(f, "{} ({})", #entity_type_id, #stripped_shouty_entity_name),
}]);
let translation_key_expr = if let Some(key) = entity.translation_key {
@ -308,7 +331,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
};
translation_key_arms.extend([quote! {
EntityKind::#shouty_entity_name_ident => #translation_key_expr,
EntityKind::#stripped_shouty_entity_name_ident => #translation_key_expr,
}]);
// Create bundle type.
@ -318,11 +341,15 @@ pub fn build() -> anyhow::Result<TokenStream> {
for marker_or_field in collect_bundle_fields(&entity_name, &entities) {
match marker_or_field {
MarkerOrField::Marker { entity_name } => {
let stripped_entity_name = strip_entity_suffix(entity_name);
let snake_entity_name_ident = ident(entity_name.to_snake_case());
let stripped_snake_entity_name_ident =
ident(stripped_entity_name.to_snake_case());
let pascal_entity_name_ident = ident(entity_name.to_pascal_case());
bundle_fields.extend([quote! {
pub #snake_entity_name_ident: super::#snake_entity_name_ident::#pascal_entity_name_ident,
pub #snake_entity_name_ident: super::#stripped_snake_entity_name_ident::#pascal_entity_name_ident,
}]);
bundle_init_fields.extend([quote! {
@ -333,14 +360,15 @@ pub fn build() -> anyhow::Result<TokenStream> {
let snake_field_name = field.name.to_snake_case();
let pascal_field_name = field.name.to_pascal_case();
let pascal_field_name_ident = ident(&pascal_field_name);
let snake_entity_name = entity_name.to_snake_case();
let snake_entity_name_ident = ident(&snake_entity_name);
let stripped_entity_name = strip_entity_suffix(entity_name);
let stripped_snake_entity_name = stripped_entity_name.to_snake_case();
let stripped_snake_entity_name_ident = ident(&stripped_snake_entity_name);
let field_name_ident =
ident(format!("{snake_entity_name}_{snake_field_name}"));
ident(format!("{stripped_snake_entity_name}_{snake_field_name}"));
bundle_fields.extend([quote! {
pub #field_name_ident: super::#snake_entity_name_ident::#pascal_field_name_ident,
pub #field_name_ident: super::#stripped_snake_entity_name_ident::#pascal_field_name_ident,
}]);
bundle_init_fields.extend([quote! {
@ -370,7 +398,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
}]);
bundle_init_fields.extend([quote! {
kind: super::EntityKind::#shouty_entity_name_ident,
kind: super::EntityKind::#stripped_shouty_entity_name_ident,
id: Default::default(),
uuid: Default::default(),
location: Default::default(),
@ -389,8 +417,9 @@ pub fn build() -> anyhow::Result<TokenStream> {
}]);
let bundle_name_ident = ident(format!("{entity_name}Bundle"));
let bundle_doc =
format!("The bundle of components for spawning `{snake_entity_name}` entities.");
let bundle_doc = format!(
"The bundle of components for spawning `{stripped_snake_entity_name}` entities."
);
module_body.extend([quote! {
#[doc = #bundle_doc]
@ -427,8 +456,11 @@ pub fn build() -> anyhow::Result<TokenStream> {
}
}]);
let system_name_ident = ident(format!("update_{snake_entity_name}_{snake_field_name}"));
let component_path = quote!(#snake_entity_name_ident::#pascal_field_name_ident);
let system_name_ident = ident(format!(
"update_{stripped_snake_entity_name}_{snake_field_name}"
));
let component_path =
quote!(#stripped_snake_entity_name_ident::#pascal_field_name_ident);
system_names.push(quote!(#system_name_ident));
@ -456,7 +488,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
}]);
}
let marker_doc = format!("Marker component for `{snake_entity_name}` entities.");
let marker_doc = format!("Marker component for `{stripped_snake_entity_name}` entities.");
module_body.extend([quote! {
#[doc = #marker_doc]
@ -466,7 +498,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
modules.extend([quote! {
#[allow(clippy::module_inception)]
pub mod #snake_entity_name_ident {
pub mod #stripped_snake_entity_name_ident {
#module_body
}
}]);
@ -556,3 +588,14 @@ fn collect_bundle_fields<'a>(
res
}
fn strip_entity_suffix(string: &str) -> String {
let stripped = string.strip_suffix("Entity").unwrap_or(string);
if stripped.is_empty() {
string
} else {
stripped
}
.to_owned()
}

View file

@ -9,18 +9,18 @@ use crate::ident;
#[derive(Deserialize, Clone, Debug)]
struct EntityEvents {
statuses: BTreeMap<String, u8>,
animations: BTreeMap<String, u8>,
entity_status: BTreeMap<String, u8>,
entity_animation: BTreeMap<String, u8>,
}
pub fn build() -> anyhow::Result<TokenStream> {
let entity_data: EntityEvents =
serde_json::from_str(include_str!("../../../extracted/entity_data.json"))?;
let entity_events: EntityEvents =
serde_json::from_str(include_str!("../../../extracted/misc.json"))?;
let mut statuses: Vec<_> = entity_data.statuses.into_iter().collect();
let mut statuses: Vec<_> = entity_events.entity_status.into_iter().collect();
statuses.sort_by_key(|(_, id)| *id);
let mut animations: Vec<_> = entity_data.animations.into_iter().collect();
let mut animations: Vec<_> = entity_events.entity_animation.into_iter().collect();
animations.sort_by_key(|(_, id)| *id);
let entity_status_variants: Vec<_> = statuses

View file

@ -18,9 +18,9 @@ pub fn main() -> anyhow::Result<()> {
let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?;
for (g, file_name) in generators {
for (generator, file_name) in generators {
let path = Path::new(&out_dir).join(file_name);
let code = g()?.to_string();
let code = generator()?.to_string();
fs::write(&path, code)?;
// Format the output for debugging purposes.

View file

@ -3,7 +3,7 @@
use std::time::Instant;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::instance::{Chunk, Instance};
use valence::prelude::*;
@ -48,8 +48,13 @@ fn print_tick_time(server: Res<Server>, time: Res<TickStart>, clients: Query<(),
}
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -74,7 +79,7 @@ fn init_clients(
for (entity, uuid, mut game_mode) in &mut clients {
*game_mode = GameMode::Creative;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,

View file

@ -4,35 +4,12 @@ use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::prelude::*;
const SPAWN_Y: i32 = 0;
const BIOME_COUNT: usize = 10;
pub fn main() {
tracing_subscriber::fmt().init();
App::new()
.add_plugin(
ServerPlugin::new(()).with_biomes(
(1..BIOME_COUNT)
.map(|i| {
let color = (0xffffff / BIOME_COUNT * i) as u32;
Biome {
name: ident!("valence:test_biome_{i}"),
sky_color: color,
water_fog_color: color,
fog_color: color,
water_color: color,
foliage_color: Some(color),
grass_color: Some(color),
..Default::default()
}
})
.chain(std::iter::once(Biome {
name: ident!("plains"),
..Default::default()
}))
.collect::<Vec<_>>(),
),
)
.add_plugin(ServerPlugin::new(()))
.add_startup_system(setup)
.add_systems((
default_event_handler.in_schedule(EventLoopSchedule),
@ -43,8 +20,16 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
biome_reg: Res<BiomeRegistry>,
server: Res<Server>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
let biome_count = biome_reg.iter().count();
for z in -5..5 {
for x in -5..5 {
@ -61,11 +46,12 @@ fn setup(mut commands: Commands, server: Res<Server>) {
for cx in 0..4 {
let height = chunk.section_count() * 16;
for cy in 0..height / 4 {
let biome_id = server
.biomes()
.nth((cx + cz * 4 + cy * 4 * 4) % BIOME_COUNT)
let biome_id = biome_reg
.iter()
.nth((cx + cz * 4 + cy * 4 * 4) % biome_count)
.unwrap()
.0;
chunk.set_biome(cx, cy, cz, biome_id);
}
}

View file

@ -2,7 +2,7 @@
use valence::client::event::{ChatMessage, PlayerInteractBlock};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::nbt::{compound, List};
use valence::prelude::*;
use valence::protocol::types::Hand;
@ -27,8 +27,13 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -71,7 +76,7 @@ fn init_clients(
for (entity, uuid, mut game_mode) in &mut clients {
*game_mode = GameMode::Creative;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([1.5, FLOOR_Y as f64 + 1.0, 1.5]),
look: Look::new(-90.0, 0.0),

View file

@ -2,7 +2,7 @@
use valence::client::event::{PlayerInteractBlock, StartDigging, StartSneaking, StopDestroyBlock};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
use valence::protocol::types::Hand;
@ -30,8 +30,13 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -57,7 +62,7 @@ fn init_clients(
*game_mode = GameMode::Creative;
client.send_message("Welcome to Valence! Build something cool.".italic());
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,

View file

@ -3,7 +3,7 @@
use tracing::warn;
use valence::client::event::{PlayerInteractBlock, StartSneaking};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
const SPAWN_Y: i32 = 64;
@ -25,8 +25,13 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -58,7 +63,7 @@ fn init_clients(
for (entity, uuid, mut game_mode) in &mut clients {
*game_mode = GameMode::Creative;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]),
uuid: *uuid,

View file

@ -2,9 +2,9 @@
use bevy_ecs::query::WorldQuery;
use glam::Vec3Swizzles;
use valence::client::event::{PlayerInteract, StartSprinting, StopSprinting};
use valence::client::event::{PlayerInteractEntity, StartSprinting, StopSprinting};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::entity::EntityStatuses;
use valence::prelude::*;
@ -33,8 +33,13 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -77,7 +82,7 @@ fn init_clients(
last_attacked_tick: 0,
has_bonus_knockback: false,
},
PlayerBundle {
PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.5, SPAWN_Y as f64, 0.5]),
uuid: *uuid,
@ -102,7 +107,7 @@ fn handle_combat_events(
mut clients: Query<CombatQuery>,
mut start_sprinting: EventReader<StartSprinting>,
mut stop_sprinting: EventReader<StopSprinting>,
mut interact_with_entity: EventReader<PlayerInteract>,
mut interact_with_entity: EventReader<PlayerInteractEntity>,
) {
for &StartSprinting { client } in start_sprinting.iter() {
if let Ok(mut client) = clients.get_mut(client) {
@ -116,7 +121,7 @@ fn handle_combat_events(
}
}
for &PlayerInteract {
for &PlayerInteractEntity {
client: attacker_client,
entity_id,
..
@ -161,13 +166,9 @@ fn handle_combat_events(
attacker.state.has_bonus_knockback = false;
victim
.client
.trigger_status(EntityStatus::DamageFromGenericSource);
victim.client.trigger_status(EntityStatus::PlayAttackSound);
victim
.statuses
.trigger(EntityStatus::DamageFromGenericSource);
victim.statuses.trigger(EntityStatus::PlayAttackSound);
}
}

View file

@ -4,7 +4,7 @@ use std::mem;
use valence::client::event::{StartDigging, StartSneaking};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
const BOARD_MIN_X: i32 = -30;
@ -26,10 +26,8 @@ pub fn main() {
tracing_subscriber::fmt().init();
App::new()
.add_plugin(ServerPlugin::new(()).with_biomes(vec![Biome {
grass_color: Some(0x00ff00),
..Default::default()
}]))
.add_plugin(ServerPlugin::new(()))
.add_startup_system(setup_biomes.before(setup))
.add_startup_system(setup)
.add_system(init_clients)
.add_systems((default_event_handler, toggle_cell_on_dig).in_schedule(EventLoopSchedule))
@ -43,8 +41,20 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
// 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>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -10..10 {
for x in -10..10 {
@ -82,7 +92,7 @@ fn init_clients(
.italic(),
);
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position(SPAWN_POS),
uuid: *uuid,

View file

@ -4,10 +4,10 @@ use std::f64::consts::TAU;
use glam::{DQuat, EulerRot};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
type SpherePartBundle = valence::entity::cow::CowBundle;
type SpherePartBundle = valence::entity::cow::CowEntityBundle;
const SPHERE_CENTER: DVec3 = DVec3::new(0.5, SPAWN_POS.y as f64 + 2.0, 0.5);
const SPHERE_AMOUNT: usize = 200;
@ -35,8 +35,13 @@ fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -67,7 +72,7 @@ fn init_clients(
for (entity, uuid, mut game_mode) in &mut clients {
*game_mode = GameMode::Creative;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([
SPAWN_POS.x as f64 + 0.5,

View file

@ -2,7 +2,7 @@
use valence::client::event::{PerformRespawn, StartSneaking};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
const SPAWN_Y: i32 = 64;
@ -22,9 +22,14 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
for block in [BlockState::GRASS_BLOCK, BlockState::DEEPSLATE] {
let mut instance = server.new_instance(DimensionId::default());
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -53,7 +58,7 @@ fn init_clients(
"Welcome to Valence! Sneak to die in the game (but not in real life).".italic(),
);
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.iter().next().unwrap()),
position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,

View file

@ -6,7 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use rand::seq::SliceRandom;
use rand::Rng;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
use valence::protocol::packet::s2c::play::TitleFadeS2c;
use valence::protocol::sound::Sound;
@ -54,6 +54,8 @@ struct GameState {
fn init_clients(
mut clients: Query<(Entity, &mut Client, &UniqueId, &mut IsFlat, &mut GameMode), Added<Client>>,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
mut commands: Commands,
) {
for (entity, mut client, uuid, mut is_flat, mut game_mode) in clients.iter_mut() {
@ -69,9 +71,9 @@ fn init_clients(
last_block_timestamp: 0,
};
let instance = server.new_instance(DimensionId::default());
let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
let player = PlayerBundle {
let player = PlayerEntityBundle {
location: Location(entity),
uuid: *uuid,
..Default::default()

View file

@ -3,7 +3,7 @@
use std::fmt;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
const SPAWN_Y: i32 = 64;
@ -25,8 +25,13 @@ pub fn main() {
#[derive(Resource)]
struct ParticleVec(Vec<Particle>);
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -49,7 +54,7 @@ fn init_clients(
for (entity, uuid, mut game_mode) in &mut clients {
*game_mode = GameMode::Creative;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]),
uuid: *uuid,

View file

@ -26,8 +26,14 @@ fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>, mut player_list: ResMut<PlayerList>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
mut player_list: ResMut<PlayerList>,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {

View file

@ -1,11 +1,11 @@
#![allow(clippy::type_complexity)]
use valence::client::event::{PlayerInteract, ResourcePackStatus, ResourcePackStatusChange};
use valence::client::event::{PlayerInteractEntity, ResourcePackStatus, ResourcePackStatusChange};
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::sheep::SheepBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::entity::sheep::SheepEntityBundle;
use valence::prelude::*;
use valence::protocol::packet::c2s::play::player_interact::Interaction;
use valence::protocol::packet::c2s::play::player_interact_entity::EntityInteraction;
const SPAWN_Y: i32 = 64;
@ -29,8 +29,13 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
let mut instance = server.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -46,7 +51,7 @@ fn setup(mut commands: Commands, server: Res<Server>) {
let instance_ent = commands.spawn(instance).id();
commands.spawn(SheepBundle {
commands.spawn(SheepEntityBundle {
location: Location(instance_ent),
position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 2.0]),
look: Look::new(180.0, 0.0),
@ -65,7 +70,7 @@ fn init_clients(
client.send_message("Hit the sheep to prompt for the resource pack.".italic());
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,
@ -74,12 +79,12 @@ fn init_clients(
}
}
fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<PlayerInteract>) {
fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<PlayerInteractEntity>) {
for event in events.iter() {
let Ok(mut client) = clients.get_mut(event.client) else {
continue;
};
if event.interact == Interaction::Attack {
if event.interact == EntityInteraction::Attack {
client.set_resource_pack(
"https://github.com/valence-rs/valence/raw/main/assets/example_pack.zip",
"d7c6108849fb190ec2a49f2d38b7f1f897d9ce9f",

View file

@ -10,7 +10,7 @@ use flume::{Receiver, Sender};
use noise::{NoiseFn, SuperSimplex};
use tracing::info;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
const SPAWN_POS: DVec3 = DVec3::new(0.0, 200.0, 0.0);
@ -61,7 +61,12 @@ pub fn main() {
.run();
}
fn setup(mut commands: Commands, server: Res<Server>) {
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let seconds_per_day = 86_400;
let seed = (SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
@ -102,7 +107,7 @@ fn setup(mut commands: Commands, server: Res<Server>) {
receiver: finished_receiver,
});
let instance = server.new_instance(DimensionId::default());
let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
commands.spawn(instance);
}
@ -116,7 +121,7 @@ fn init_clients(
is_flat.0 = true;
*game_mode = GameMode::Creative;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position(SPAWN_POS),
uuid: *uuid,

View file

@ -19,10 +19,13 @@ pub fn main() {
.run();
}
fn setup(world: &mut World) {
let mut instance = world
.resource::<Server>()
.new_instance(DimensionId::default());
fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
@ -36,7 +39,7 @@ fn setup(world: &mut World) {
}
}
world.spawn(instance);
commands.spawn(instance);
}
fn init_clients(

View file

@ -1,234 +1,240 @@
//! Biome configuration and identification.
//!
//! **NOTE:**
//!
//! - Modifying the biome registry after the server has started can
//! break invariants within instances and clients! Make sure there are no
//! instances or clients spawned before mutating.
//! - A biome named "minecraft:plains" must exist. Otherwise, vanilla clients
//! will be disconnected. A biome named "minecraft:plains" is added by
//! default.
use std::collections::HashSet;
use std::ops::Index;
use anyhow::ensure;
use tracing::warn;
use valence_nbt::{compound, Compound};
use anyhow::{bail, Context};
use bevy_app::{CoreSet, Plugin, StartupSet};
use bevy_ecs::prelude::*;
use tracing::error;
use valence_nbt::{compound, Value};
use valence_protocol::ident;
use valence_protocol::ident::Ident;
/// Identifies a particular [`Biome`] on the server.
///
/// The default biome ID refers to the first biome added in
/// [`ServerPlugin::biomes`].
///
/// To obtain biome IDs for other biomes, see [`ServerPlugin::biomes`].
///
/// [`ServerPlugin::biomes`]: crate::config::ServerPlugin::biomes
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BiomeId(pub(crate) u16);
use crate::registry_codec::{RegistryCodec, RegistryCodecSet, RegistryValue};
/// Contains the configuration for a biome.
///
/// Biomes are registered once at startup through
/// [`ServerPlugin::with_biomes`]
///
/// [`ServerPlugin::with_biomes`]: crate::config::ServerPlugin::with_biomes
#[derive(Clone, Debug)]
#[derive(Resource)]
pub struct BiomeRegistry {
id_to_biome: Vec<Entity>,
}
impl BiomeRegistry {
pub const KEY: Ident<&str> = ident!("minecraft:worldgen/biome");
pub fn get_by_id(&self, id: BiomeId) -> Option<Entity> {
self.id_to_biome.get(id.0 as usize).cloned()
}
pub fn iter(&self) -> impl Iterator<Item = (BiomeId, Entity)> + '_ {
self.id_to_biome
.iter()
.enumerate()
.map(|(id, biome)| (BiomeId(id as _), *biome))
}
}
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:?}"))
}
}
/// An index into the biome registry.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct BiomeId(pub u16);
#[derive(Component, Clone, Debug)]
pub struct Biome {
/// The unique name for this biome. The name can be
/// seen in the F3 debug menu.
pub name: Ident<String>,
pub precipitation: BiomePrecipitation,
pub sky_color: u32,
pub water_fog_color: u32,
pub fog_color: u32,
pub water_color: u32,
pub foliage_color: Option<u32>,
pub grass_color: Option<u32>,
pub grass_color_modifier: BiomeGrassColorModifier,
pub music: Option<BiomeMusic>,
pub ambient_sound: Option<Ident<String>>,
pub additions_sound: Option<BiomeAdditionsSound>,
pub mood_sound: Option<BiomeMoodSound>,
pub particle: Option<BiomeParticle>,
// TODO
// * depth: f32
// * temperature: f32
// * scale: f32
// * downfall: f32
// * category
// * temperature_modifier
}
impl Biome {
pub(crate) fn to_biome_registry_item(&self, id: i32) -> Compound {
compound! {
"name" => self.name.clone(),
"id" => id,
"element" => compound! {
"precipitation" => match self.precipitation {
BiomePrecipitation::Rain => "rain",
BiomePrecipitation::Snow => "snow",
BiomePrecipitation::None => "none",
},
"depth" => 0.125_f32,
"temperature" => 0.8_f32,
"scale" => 0.05_f32,
"downfall" => 0.4_f32,
"category" => "none",
"effects" => {
let mut eff = compound! {
"sky_color" => self.sky_color as i32,
"water_fog_color" => self.water_fog_color as i32,
"fog_color" => self.fog_color as i32,
"water_color" => self.water_color as i32,
};
if let Some(color) = self.foliage_color {
eff.insert("foliage_color", color as i32);
}
if let Some(color) = self.grass_color {
eff.insert("grass_color", color as i32);
}
match self.grass_color_modifier {
BiomeGrassColorModifier::Swamp => eff.insert("grass_color_modifier", "swamp"),
BiomeGrassColorModifier::DarkForest => eff.insert("grass_color_modifier", "dark_forest"),
BiomeGrassColorModifier::None => None
};
if let Some(music) = &self.music {
eff.insert("music", compound! {
"replace_current_music" => music.replace_current_music,
"sound" => music.sound.clone(),
"max_delay" => music.max_delay,
"min_delay" => music.min_delay,
});
}
if let Some(s) = &self.ambient_sound {
eff.insert("ambient_sound", s.clone());
}
if let Some(a) = &self.additions_sound {
eff.insert("additions_sound", compound! {
"sound" => a.sound.clone(),
"tick_chance" => a.tick_chance,
});
}
if let Some(m) = &self.mood_sound {
eff.insert("mood_sound", compound! {
"sound" => m.sound.clone(),
"tick_delay" => m.tick_delay,
"offset" => m.offset,
"block_search_extent" => m.block_search_extent,
});
}
if let Some(p) = &self.particle {
eff.insert(
"particle",
compound! {
"probability" => p.probability,
"options" => compound! {
"type" => p.kind.clone(),
}
},
);
}
eff
},
}
}
}
}
pub(crate) fn validate_biomes(biomes: &[Biome]) -> anyhow::Result<()> {
ensure!(!biomes.is_empty(), "at least one biome must be present");
ensure!(
biomes.len() <= u16::MAX as _,
"more than u16::MAX biomes present"
);
let mut names = HashSet::new();
for biome in biomes {
ensure!(
names.insert(biome.name.clone()),
"biome \"{}\" already exists",
biome.name
);
}
if !names.contains(&ident!("plains")) {
warn!(
"A biome named \"plains\" is missing from the biome registry! Due to a bug in the \
vanilla client, players may not be able to join the game!"
);
}
Ok(())
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 has_precipitation: bool,
pub temperature: f32,
// TODO: more stuff.
}
impl Default for Biome {
fn default() -> Self {
Self {
name: ident!("plains"),
precipitation: BiomePrecipitation::default(),
sky_color: 7907327,
water_fog_color: 329011,
name: ident!("plains").into(),
downfall: 0.4,
fog_color: 12638463,
sky_color: 7907327,
water_color: 4159204,
foliage_color: None,
water_fog_color: 329011,
grass_color: None,
grass_color_modifier: BiomeGrassColorModifier::default(),
music: None,
ambient_sound: None,
additions_sound: None,
mood_sound: None,
particle: None,
has_precipitation: true,
temperature: 0.8,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum BiomePrecipitation {
#[default]
Rain,
Snow,
None,
pub(crate) struct BiomePlugin;
impl Plugin for BiomePlugin {
fn build(&self, app: &mut bevy_app::App) {
app.insert_resource(BiomeRegistry {
id_to_biome: vec![],
})
.add_systems(
(update_biome_registry, remove_biomes_from_registry)
.chain()
.in_base_set(CoreSet::PostUpdate)
.before(RegistryCodecSet),
)
.add_startup_system(load_default_biomes.in_base_set(StartupSet::PreStartup));
}
}
/// Minecraft handles grass colors for swamps and dark oak forests in a special
/// way.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum BiomeGrassColorModifier {
Swamp,
DarkForest,
#[default]
None,
fn load_default_biomes(
mut reg: ResMut<BiomeRegistry>,
codec: Res<RegistryCodec>,
mut commands: Commands,
) {
let mut helper = move || {
for value in codec.registry(BiomeRegistry::KEY) {
let downfall = *value
.element
.get("downfall")
.and_then(|v| v.as_float())
.context("invalid downfall")?;
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);
}
Ok(())
};
if let Err(e) = helper() {
error!("failed to load default biomes from registry codec: {e:#}");
}
}
#[derive(Clone, Debug)]
pub struct BiomeMusic {
pub replace_current_music: bool,
pub sound: Ident<String>,
pub min_delay: i32,
pub max_delay: i32,
/// 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);
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,
};
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"
);
}
}
#[derive(Clone, Debug)]
pub struct BiomeAdditionsSound {
pub sound: Ident<String>,
pub tick_chance: f64,
}
#[derive(Clone, Debug)]
pub struct BiomeMoodSound {
pub sound: Ident<String>,
pub tick_delay: i32,
pub offset: f64,
pub block_search_extent: i32,
}
#[derive(Clone, Debug)]
pub struct BiomeParticle {
pub probability: f32,
pub kind: Ident<String>,
/// 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);
}
}
}

View file

@ -33,11 +33,11 @@ use valence_protocol::types::{GlobalPos, SoundCategory};
use valence_protocol::var_int::VarInt;
use valence_protocol::Packet;
use crate::biome::BiomeRegistry;
use crate::component::{
Despawned, GameMode, Location, Look, OldLocation, OldPosition, OnGround, Ping, Position,
Properties, UniqueId, Username,
};
use crate::dimension::DimensionId;
use crate::entity::{
EntityId, EntityKind, EntityStatus, HeadYaw, ObjectData, TrackedData, Velocity,
};
@ -45,6 +45,7 @@ use crate::instance::{Instance, WriteUpdatePacketsToInstancesSet};
use crate::inventory::{Inventory, InventoryKind};
use crate::packet::WritePacket;
use crate::prelude::ScratchBuf;
use crate::registry_codec::{RegistryCodec, RegistryCodecSet};
use crate::server::{NewClientInfo, Server};
use crate::util::velocity_to_packet_units;
use crate::view::{ChunkPos, ChunkView};
@ -508,8 +509,8 @@ impl OldViewItem<'_> {
}
}
#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct DeathLocation(pub Option<(DimensionId, BlockPos)>);
#[derive(Component, Clone, PartialEq, Eq, Default, Debug)]
pub struct DeathLocation(pub Option<(Ident<String>, BlockPos)>);
#[derive(Component, Debug)]
pub struct KeepaliveState {
@ -612,13 +613,13 @@ pub(crate) struct ClientPlugin;
/// packets to clients should happen before this. Otherwise, the data
/// will arrive one tick late.
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct FlushPacketsSet;
pub struct FlushPacketsSet;
impl Plugin for ClientPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_systems(
(
initial_join,
initial_join.after(RegistryCodecSet),
update_chunk_load_dist,
read_data_in_old_view
.after(WriteUpdatePacketsToInstancesSet)
@ -668,28 +669,29 @@ struct ClientJoinQuery {
}
fn initial_join(
server: Res<Server>,
codec: Res<RegistryCodec>,
mut clients: Query<ClientJoinQuery, Added<Client>>,
instances: Query<&Instance>,
mut commands: Commands,
) {
for mut q in &mut clients {
let Ok(instance) = instances.get(q.loc.0) else {
warn!("Client {:?} joined nonexistent instance {:?}. Disconnecting.", q.entity, q.loc.0);
warn!("client {:?} joined nonexistent instance {:?}", q.entity, q.loc.0);
commands.entity(q.entity).remove::<Client>();
continue
};
let dimension_names = server
.dimensions()
.map(|(_, dim)| dim.name.as_str_ident().into())
let dimension_names: Vec<Ident<Cow<str>>> = codec
.registry(BiomeRegistry::KEY)
.iter()
.map(|value| value.name.as_str_ident().into())
.collect();
let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
let dimension_name: Ident<Cow<str>> = instance.dimension_type_name().into();
let last_death_location = q.death_loc.0.map(|(id, pos)| GlobalPos {
dimension_name: server.dimension(id).name.as_str_ident().into(),
position: pos,
let last_death_location = q.death_loc.0.as_ref().map(|(id, pos)| GlobalPos {
dimension_name: id.as_str_ident().into(),
position: *pos,
});
// The login packet is prepended so that it's sent before all the other packets.
@ -700,9 +702,9 @@ fn initial_join(
game_mode: (*q.game_mode).into(),
previous_game_mode: q.prev_game_mode.0.map(|g| g as i8).unwrap_or(-1),
dimension_names,
registry_codec: Cow::Borrowed(server.registry_codec()),
dimension_type_name: dimension_name.into(),
dimension_name: dimension_name.into(),
registry_codec: Cow::Borrowed(codec.cached_codec()),
dimension_type_name: dimension_name.clone(),
dimension_name,
hashed_seed: q.hashed_seed.0 as i64,
max_players: VarInt(0), // Ignored by clients.
view_distance: VarInt(q.view_distance.0 as i32),
@ -738,7 +740,6 @@ fn respawn(
Changed<Location>,
>,
instances: Query<&Instance>,
server: Res<Server>,
) {
for (mut client, loc, death_loc, hashed_seed, game_mode, prev_game_mode, is_debug, is_flat) in
&mut clients
@ -753,11 +754,11 @@ fn respawn(
continue
};
let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
let dimension_name = instance.dimension_type_name();
let last_death_location = death_loc.0.map(|(id, pos)| GlobalPos {
dimension_name: server.dimension(id).name.as_str_ident().into(),
position: pos,
let last_death_location = death_loc.0.as_ref().map(|(id, pos)| GlobalPos {
dimension_name: id.as_str_ident().into(),
position: *pos,
});
client.write_packet(&PlayerRespawnS2c {
@ -1149,7 +1150,6 @@ fn teleport(
pitch: if changed_pitch { look.pitch } else { 0.0 },
flags,
teleport_id: VarInt(state.teleport_id_counter as i32),
dismount_vehicle: false, // TODO?
});
state.pending_teleports = state.pending_teleports.wrapping_add(1);

View file

@ -63,7 +63,7 @@ pub fn default_event_handler(
q.view_dist.0 = *view_distance;
if let Some(mut parts) = q.player_model_parts {
parts.set_if_neq(PlayerModelParts(u8::from(*displayed_skin_parts)));
parts.set_if_neq(PlayerModelParts(u8::from(*displayed_skin_parts) as i8));
}
if let Some(mut player_main_arm) = q.main_arm {

View file

@ -18,7 +18,7 @@ use valence_protocol::packet::c2s::play::click_slot::{ClickMode, Slot};
use valence_protocol::packet::c2s::play::client_command::Action as ClientCommandAction;
use valence_protocol::packet::c2s::play::client_settings::{ChatMode, DisplayedSkinParts, MainArm};
use valence_protocol::packet::c2s::play::player_action::Action as PlayerAction;
use valence_protocol::packet::c2s::play::player_interact::Interaction;
use valence_protocol::packet::c2s::play::player_interact_entity::EntityInteraction;
use valence_protocol::packet::c2s::play::recipe_category_options::RecipeBookId;
use valence_protocol::packet::c2s::play::update_command_block::Mode as CommandBlockMode;
use valence_protocol::packet::c2s::play::update_structure_block::{
@ -158,14 +158,14 @@ pub struct QueryEntityNbt {
/// Left or right click interaction with an entity's hitbox.
#[derive(Clone, Debug)]
pub struct PlayerInteract {
pub struct PlayerInteractEntity {
pub client: Entity,
/// The raw ID of the entity being interacted with.
pub entity_id: i32,
/// If the client was sneaking during the interaction.
pub sneaking: bool,
/// The kind of interaction that occurred.
pub interact: Interaction,
pub interact: EntityInteraction,
}
#[derive(Clone, Debug)]
@ -586,7 +586,7 @@ events! {
QueryEntityNbt
}
1 {
PlayerInteract
PlayerInteractEntity
JigsawGenerating
UpdateDifficultyLock
PlayerMove
@ -982,8 +982,8 @@ fn handle_one_packet(
entity_id: p.entity_id.0,
});
}
C2sPlayPacket::PlayerInteractC2s(p) => {
events.1.player_interact.send(PlayerInteract {
C2sPlayPacket::PlayerInteractEntityC2s(p) => {
events.1.player_interact_entity.send(PlayerInteractEntity {
client: entity,
entity_id: p.entity_id.0,
sneaking: p.sneaking,
@ -1018,7 +1018,7 @@ fn handle_one_packet(
locked: p.locked,
});
}
C2sPlayPacket::PositionAndOnGroundC2s(p) => {
C2sPlayPacket::PositionAndOnGround(p) => {
if q.teleport_state.pending_teleports != 0 {
return Ok(false);
}
@ -1035,7 +1035,7 @@ fn handle_one_packet(
q.teleport_state.synced_pos = p.position.into();
q.on_ground.0 = p.on_ground;
}
C2sPlayPacket::FullC2s(p) => {
C2sPlayPacket::Full(p) => {
if q.teleport_state.pending_teleports != 0 {
return Ok(false);
}
@ -1056,7 +1056,7 @@ fn handle_one_packet(
q.teleport_state.synced_look.pitch = p.pitch;
q.on_ground.0 = p.on_ground;
}
C2sPlayPacket::LookAndOnGroundC2s(p) => {
C2sPlayPacket::LookAndOnGround(p) => {
if q.teleport_state.pending_teleports != 0 {
return Ok(false);
}
@ -1075,7 +1075,7 @@ fn handle_one_packet(
q.teleport_state.synced_look.pitch = p.pitch;
q.on_ground.0 = p.on_ground;
}
C2sPlayPacket::OnGroundOnlyC2s(p) => {
C2sPlayPacket::OnGroundOnly(p) => {
if q.teleport_state.pending_teleports != 0 {
return Ok(false);
}

View file

@ -9,8 +9,6 @@ use tracing::error;
use uuid::Uuid;
use valence_protocol::text::Text;
use crate::biome::Biome;
use crate::dimension::Dimension;
use crate::server::{NewClientInfo, SharedServer};
#[derive(Clone)]
@ -101,36 +99,6 @@ pub struct ServerPlugin<A> {
/// An unspecified value is used that should be adequate for most
/// situations. This default may change in future versions.
pub outgoing_capacity: usize,
/// The list of [`Dimension`]s usable on the server.
///
/// The dimensions returned by [`ServerPlugin::dimensions`] will be in the
/// same order as this `Vec`.
///
/// The number of elements in the `Vec` must be in `1..=u16::MAX`.
/// Additionally, the documented requirements on the fields of [`Dimension`]
/// must be met.
///
/// # Default Value
///
/// `vec![Dimension::default()]`
pub dimensions: Arc<[Dimension]>,
/// The list of [`Biome`]s usable on the server.
///
/// The biomes returned by [`SharedServer::biomes`] will be in the same
/// order as this `Vec`.
///
/// The number of elements in the `Vec` must be in `1..=u16::MAX`.
/// Additionally, the documented requirements on the fields of [`Biome`]
/// must be met.
///
/// **NOTE**: As of 1.19.2, there is a bug in the client which prevents
/// joining the game when a biome named "minecraft:plains" is not present.
/// Ensure there is a biome named "plains".
///
/// # Default Value
///
/// `vec![Biome::default()]`.
pub biomes: Arc<[Biome]>,
}
impl<A: AsyncCallbacks> ServerPlugin<A> {
@ -148,8 +116,6 @@ impl<A: AsyncCallbacks> ServerPlugin<A> {
compression_threshold: Some(256),
incoming_capacity: 2097152, // 2 MiB
outgoing_capacity: 8388608, // 8 MiB
dimensions: [Dimension::default()].as_slice().into(),
biomes: [Biome::default()].as_slice().into(),
}
}
@ -208,20 +174,6 @@ impl<A: AsyncCallbacks> ServerPlugin<A> {
self.outgoing_capacity = outgoing_capacity;
self
}
/// See [`Self::dimensions`].
#[must_use]
pub fn with_dimensions(mut self, dimensions: impl Into<Arc<[Dimension]>>) -> Self {
self.dimensions = dimensions.into();
self
}
/// See [`Self::biomes`].
#[must_use]
pub fn with_biomes(mut self, biomes: impl Into<Arc<[Biome]>>) -> Self {
self.biomes = biomes.into();
self
}
}
impl<A: AsyncCallbacks + Default> Default for ServerPlugin<A> {

View file

@ -1,184 +1,83 @@
//! Dimension configuration and identification.
//! Dimension type configuration and identification.
//!
//! **NOTE:**
//!
//! - Modifying the dimension type registry after the server has started can
//! break invariants within instances and clients! Make sure there are no
//! instances or clients spawned before mutating.
use std::collections::HashSet;
use std::collections::BTreeMap;
use std::str::FromStr;
use anyhow::ensure;
use valence_nbt::{compound, Compound};
use anyhow::{bail, Context};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use tracing::{error, warn};
use valence_nbt::{compound, Value};
use valence_protocol::ident;
use valence_protocol::ident::Ident;
/// Identifies a particular [`Dimension`] on the server.
///
/// The default dimension ID refers to the first dimension added in
/// [`ServerPlugin::dimensions`].
///
/// To obtain dimension IDs for other dimensions, look at
/// [`ServerPlugin::dimensions`].
///
/// [`ServerPlugin::dimensions`]: crate::config::ServerPlugin::dimensions
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct DimensionId(pub(crate) u16);
use crate::registry_codec::{RegistryCodec, RegistryCodecSet, RegistryValue};
/// The default dimension ID corresponds to the first element in the `Vec`
/// returned by [`ServerPlugin::dimensions`].
///
/// [`ServerPlugin::dimensions`]: crate::config::ServerPlugin::dimensions
impl Default for DimensionId {
fn default() -> Self {
Self(0)
#[derive(Resource)]
pub struct DimensionTypeRegistry {
name_to_dimension: BTreeMap<Ident<String>, Entity>,
}
impl DimensionTypeRegistry {
pub const KEY: Ident<&str> = ident!("minecraft:dimension_type");
pub fn get_by_name(&self, name: Ident<&str>) -> Option<Entity> {
self.name_to_dimension.get(name.as_str()).copied()
}
pub fn dimensions(&self) -> impl Iterator<Item = Entity> + '_ {
self.name_to_dimension.values().copied()
}
}
/// Contains the configuration for a dimension type.
///
/// On creation, each [`Instance`] in Valence is assigned a dimension. The
/// dimension determines certain properties of the world such as its height and
/// ambient lighting.
///
/// In Minecraft, "dimension" and "dimension type" are two distinct concepts.
/// For instance, the Overworld and Nether are dimensions, each with
/// their own dimension type. A dimension in this library is analogous to a
/// [`Instance`] while [`Dimension`] represents a dimension type.
///
/// [`Instance`]: crate::instance::Instance
#[derive(Clone, Debug)]
pub struct Dimension {
/// The unique name for this dimension.
#[derive(Component, Clone, PartialEq, Debug)]
pub struct DimensionType {
pub name: Ident<String>,
/// When false, compasses will spin randomly.
pub natural: bool,
/// Must be between 0.0 and 1.0.
pub ambient_light: f32,
/// Must be between 0 and 24000.
pub fixed_time: Option<u16>,
/// Determines what skybox/fog effects to use.
pub bed_works: bool,
pub coordinate_scale: f64,
pub effects: DimensionEffects,
/// The minimum Y coordinate in which blocks can exist in this dimension.
///
/// `min_y` must meet the following conditions:
/// * `min_y % 16 == 0`
/// * `-2032 <= min_y <= 2016`
pub min_y: i32,
/// The total height in which blocks can exist in this dimension.
///
/// `height` must meet the following conditions:
/// * `height % 16 == 0`
/// * `0 <= height <= 4064`
/// * `min_y + height <= 2032`
pub has_ceiling: bool,
pub has_raids: bool,
pub has_skylight: bool,
pub height: i32,
// TODO: add other fields.
// * infiniburn
// * monster_spawn_light_level
// * monster_spawn_block_light_level
// * respawn_anchor_works
// * has_skylight
// * bed_works
// * has_raids
// * logical_height
// * coordinate_scale
// * ultrawarm
// * has_ceiling
pub infiniburn: String,
pub logical_height: i32,
pub min_y: i32,
pub monster_spawn_block_light_limit: i32,
/// TODO: monster_spawn_light_level
pub natural: bool,
pub piglin_safe: bool,
pub respawn_anchor_works: bool,
pub ultrawarm: bool,
}
impl Dimension {
pub(crate) fn to_dimension_registry_item(&self, id: i32) -> Compound {
compound! {
"name" => self.name.clone(),
"id" => id,
"element" => {
let mut element = compound! {
"piglin_safe" => true,
"has_raids" => true,
"monster_spawn_light_level" => 0,
"monster_spawn_block_light_limit" => 0,
"natural" => self.natural,
"ambient_light" => self.ambient_light,
"infiniburn" => "#minecraft:infiniburn_overworld",
"respawn_anchor_works" => true,
"has_skylight" => true,
"bed_works" => true,
"effects" => match self.effects {
DimensionEffects::Overworld => "overworld",
DimensionEffects::TheNether => "the_nether",
DimensionEffects::TheEnd => "the_end",
},
"min_y" => self.min_y,
"height" => self.height,
"logical_height" => self.height,
"coordinate_scale" => 1.0,
"ultrawarm" => false,
"has_ceiling" => false,
};
if let Some(t) = self.fixed_time {
element.insert("fixed_time", t as i64);
}
element
},
}
}
}
pub(crate) fn validate_dimensions(dimensions: &[Dimension]) -> anyhow::Result<()> {
ensure!(
!dimensions.is_empty(),
"at least one dimension must be present"
);
ensure!(
dimensions.len() <= u16::MAX as usize,
"more than u16::MAX dimensions present"
);
let mut names = HashSet::new();
for dim in dimensions {
let name = &dim.name;
ensure!(
names.insert(name.clone()),
"dimension \"{name}\" already exists",
);
ensure!(
dim.min_y % 16 == 0 && (-2032..=2016).contains(&dim.min_y),
"invalid min_y in dimension {name}",
);
ensure!(
dim.height % 16 == 0
&& (0..=4064).contains(&dim.height)
&& dim.min_y.saturating_add(dim.height) <= 2032,
"invalid height in dimension {name}",
);
ensure!(
(0.0..=1.0).contains(&dim.ambient_light),
"ambient_light is out of range in dimension {name}",
);
if let Some(fixed_time) = dim.fixed_time {
ensure!(
(0..=24_000).contains(&fixed_time),
"fixed_time is out of range in dimension {name}",
);
}
}
Ok(())
}
impl Default for Dimension {
impl Default for DimensionType {
fn default() -> Self {
Self {
name: ident!("overworld"),
natural: true,
name: ident!("minecraft:overworld").into(),
ambient_light: 1.0,
fixed_time: None,
bed_works: true,
coordinate_scale: 1.0,
effects: DimensionEffects::default(),
min_y: -64,
has_ceiling: false,
has_raids: true,
has_skylight: true,
height: 384,
infiniburn: "#minecraft:infiniburn_overworld".into(),
logical_height: 384,
min_y: -64,
monster_spawn_block_light_limit: 0,
natural: true,
piglin_safe: false,
respawn_anchor_works: true,
ultrawarm: false,
}
}
}
@ -191,3 +90,166 @@ pub enum DimensionEffects {
TheNether,
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"),
}
}
}
impl FromStr for DimensionEffects {
type Err = anyhow::Error;
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}\""),
}
}
}
pub(crate) struct DimensionPlugin;
impl Plugin for DimensionPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(DimensionTypeRegistry {
name_to_dimension: BTreeMap::new(),
})
.add_systems(
(
update_dimension_type_registry,
remove_dimension_types_from_registry,
)
.chain()
.in_base_set(CoreSet::PostUpdate)
.before(RegistryCodecSet),
)
.add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup));
}
}
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,
) {
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 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);
}
}
Ok(())
};
if let Err(e) = helper() {
error!("failed to load default dimension types from registry codec: {e:#}");
}
}

View file

@ -357,6 +357,18 @@ pub enum PaintingKind {
DonkeyKong,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum SnifferState {
#[default]
Idling,
FeelingHappy,
Scenting,
Sniffing,
Searching,
Digging,
Rising,
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Encode, Decode)]
pub struct EulerAngle {
pub pitch: f32,
@ -390,7 +402,8 @@ impl Decode<'_> for OptionalInt {
}
}
/// Maintains information about all spawned Minecraft entities.
/// A [`Resource`] which maintains information about all spawned Minecraft
/// entities.
#[derive(Resource, Debug)]
pub struct EntityManager {
/// Maps protocol IDs to ECS entities.
@ -461,7 +474,7 @@ macro_rules! flags {
#[doc = "."]
#[inline]
pub fn [< set_$flag >] (&mut self, $flag: bool) {
self.0 = (self.0 & !(1 << $offset)) | (($flag as u8) << $offset);
self.0 = (self.0 & !(1 << $offset)) | (($flag as i8) << $offset);
}
}
)*

View file

@ -15,11 +15,12 @@ use rustc_hash::FxHashMap;
use valence_protocol::array::LengthPrefixedArray;
use valence_protocol::block_pos::BlockPos;
use valence_protocol::byte_angle::ByteAngle;
use valence_protocol::ident::Ident;
use valence_protocol::packet::s2c::play::particle::Particle;
use valence_protocol::packet::s2c::play::{
EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c,
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, OverlayMessageS2c,
ParticleS2c, PlaySoundS2c, RotateAndMoveRelativeS2c, RotateS2c,
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelative, OverlayMessageS2c, ParticleS2c,
PlaySoundS2c, Rotate, RotateAndMoveRelative,
};
use valence_protocol::sound::Sound;
use valence_protocol::text::Text;
@ -27,9 +28,10 @@ use valence_protocol::types::SoundCategory;
use valence_protocol::var_int::VarInt;
use valence_protocol::Packet;
use crate::biome::Biome;
use crate::client::FlushPacketsSet;
use crate::component::{Despawned, Location, Look, OldLocation, OldPosition, OnGround, Position};
use crate::dimension::DimensionId;
use crate::dimension::DimensionType;
use crate::entity::{
EntityAnimations, EntityId, EntityKind, EntityStatuses, HeadYaw, InitEntitiesSet,
PacketByteRange, TrackedData, Velocity,
@ -46,27 +48,6 @@ mod paletted_container;
/// An Instance represents a Minecraft world, which consist of [`Chunk`]s.
/// It manages updating clients when chunks change, and caches chunk and entity
/// update packets on a per-chunk basis.
///
/// To create a new instance, use [`SharedServer::new_instance`].
/// ```
/// use bevy_app::prelude::*;
/// use valence::prelude::*;
///
/// let mut app = App::new();
/// app.add_plugin(ServerPlugin::new(()));
/// let server = app.world.get_resource::<Server>().unwrap();
/// let instance = server.new_instance(DimensionId::default());
/// ```
/// Now you can actually spawn a new [`Entity`] with `instance`.
/// ```
/// # use bevy_app::prelude::*;
/// # use valence::prelude::*;
/// # let mut app = App::new();
/// # app.add_plugin(ServerPlugin::new(()));
/// # let server = app.world.get_resource::<Server>().unwrap();
/// # let instance = server.new_instance(DimensionId::default());
/// let instance_entity = app.world.spawn(instance);
/// ```
#[derive(Component)]
pub struct Instance {
pub(crate) partition: FxHashMap<ChunkPos, PartitionCell>,
@ -79,7 +60,7 @@ pub struct Instance {
}
pub(crate) struct InstanceInfo {
dimension: DimensionId,
dimension_type_name: Ident<String>,
section_count: usize,
min_y: i32,
biome_registry_len: usize,
@ -110,8 +91,19 @@ pub(crate) struct PartitionCell {
}
impl Instance {
pub(crate) fn new(dimension: DimensionId, shared: &SharedServer) -> Self {
let dim = shared.dimension(dimension);
pub fn new(
dimension_type_name: impl Into<Ident<String>>,
dimensions: &Query<&DimensionType>,
biomes: &Query<&Biome>,
shared: &SharedServer,
) -> 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}\"")
};
assert!(dim.height > 0, "invalid dimension height of {}", dim.height);
let light_section_count = (dim.height / 16 + 2) as usize;
@ -124,10 +116,10 @@ impl Instance {
Self {
partition: FxHashMap::default(),
info: InstanceInfo {
dimension,
dimension_type_name,
section_count: (dim.height / 16) as usize,
min_y: dim.min_y,
biome_registry_len: shared.biomes().len(),
biome_registry_len: biomes.iter().count(),
compression_threshold: shared.compression_threshold(),
filler_sky_light_mask: sky_light_mask.into(),
filler_sky_light_arrays: vec![
@ -141,8 +133,30 @@ impl Instance {
}
}
pub fn dimension(&self) -> DimensionId {
self.info.dimension
/// TODO: Temporary hack for unit testing. Do not use!
#[doc(hidden)]
pub fn new_unit_testing(
dimension_type_name: impl Into<Ident<String>>,
shared: &SharedServer,
) -> 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: shared.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()
}
pub fn section_count(&self) -> usize {
@ -658,7 +672,7 @@ impl UpdateEntityQueryItem<'_> {
let changed_position = self.pos.0 != self.old_pos.get();
if changed_position && !needs_teleport && self.look.is_changed() {
writer.write_packet(&RotateAndMoveRelativeS2c {
writer.write_packet(&RotateAndMoveRelative {
entity_id,
delta: (position_delta * 4096.0).to_array().map(|v| v as i16),
yaw: ByteAngle::from_degrees(self.look.yaw),
@ -667,7 +681,7 @@ impl UpdateEntityQueryItem<'_> {
});
} else {
if changed_position && !needs_teleport {
writer.write_packet(&MoveRelativeS2c {
writer.write_packet(&MoveRelative {
entity_id,
delta: (position_delta * 4096.0).to_array().map(|v| v as i16),
on_ground: self.on_ground.0,
@ -675,7 +689,7 @@ impl UpdateEntityQueryItem<'_> {
}
if self.look.is_changed() {
writer.write_packet(&RotateS2c {
writer.write_packet(&Rotate {
entity_id,
yaw: ByteAngle::from_degrees(self.look.yaw),
pitch: ByteAngle::from_degrees(self.look.pitch),

View file

@ -37,6 +37,7 @@ pub mod inventory;
pub mod packet;
pub mod player_list;
pub mod player_textures;
pub mod registry_codec;
pub mod server;
#[cfg(any(test, doctest))]
mod unit_test;
@ -48,14 +49,14 @@ pub mod prelude {
pub use async_trait::async_trait;
pub use bevy_app::prelude::*;
pub use bevy_ecs::prelude::*;
pub use biome::{Biome, BiomeId};
pub use biome::{Biome, BiomeId, BiomeRegistry};
pub use client::event::{EventLoopSchedule, EventLoopSet};
pub use client::*;
pub use component::*;
pub use config::{
AsyncCallbacks, ConnectionMode, PlayerSampleEntry, ServerListPing, ServerPlugin,
};
pub use dimension::{Dimension, DimensionId};
pub use dimension::{DimensionType, DimensionTypeRegistry};
pub use entity::{EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw};
pub use glam::DVec3;
pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance};

View file

@ -0,0 +1,145 @@
use std::collections::BTreeMap;
use bevy_app::{CoreSet, Plugin};
pub use bevy_ecs::prelude::*;
use tracing::error;
use valence_nbt::{compound, Compound, List, Value};
use valence_protocol::ident::Ident;
/// 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,
}
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 = valence_nbt::from_binary_slice(&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(),
}
}
}
/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that
/// modify the registry codec should run _before_ this.
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct RegistryCodecSet;
pub(crate) struct RegistryCodecPlugin;
impl Plugin for RegistryCodecPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<RegistryCodec>()
.configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate))
.add_system(cache_registry_codec.in_set(RegistryCodecSet));
}
}
fn cache_registry_codec(codec: ResMut<RegistryCodec>) {
if codec.is_changed() {
let codec = codec.into_inner();
codec.cached_codec.clear();
for (reg_name, reg) in &codec.registries {
let mut value = vec![];
for (id, v) in reg.iter().enumerate() {
value.push(compound! {
"id" => id as i32,
"name" => v.name.as_str(),
"element" => v.element.clone(),
});
}
let registry = compound! {
"type" => reg_name.as_str(),
"value" => List::Compound(value),
};
codec.cached_codec.insert(reg_name.as_str(), registry);
}
}
}

View file

@ -1,4 +1,3 @@
use std::iter::FusedIterator;
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
use std::sync::Arc;
@ -14,21 +13,20 @@ use rsa::{PublicKeyParts, RsaPrivateKey};
use tokio::runtime::{Handle, Runtime};
use tokio::sync::Semaphore;
use uuid::Uuid;
use valence_nbt::{compound, Compound, List};
use valence_protocol::ident;
use valence_protocol::types::Property;
use crate::biome::{validate_biomes, Biome, BiomeId};
use crate::biome::BiomePlugin;
use crate::client::event::EventLoopSet;
use crate::client::{ClientBundle, ClientPlugin};
use crate::config::{AsyncCallbacks, ConnectionMode, ServerPlugin};
use crate::dimension::{validate_dimensions, Dimension, DimensionId};
use crate::dimension::DimensionPlugin;
use crate::entity::EntityPlugin;
use crate::instance::{Instance, InstancePlugin};
use crate::instance::InstancePlugin;
use crate::inventory::{InventoryPlugin, InventorySettings};
use crate::player_list::PlayerListPlugin;
use crate::prelude::event::ClientEventPlugin;
use crate::prelude::ComponentPlugin;
use crate::registry_codec::RegistryCodecPlugin;
use crate::server::connect::do_accept_loop;
use crate::weather::WeatherPlugin;
@ -83,11 +81,6 @@ struct SharedServerInner {
/// Holding a runtime handle is not enough to keep tokio working. We need
/// to store the runtime here so we don't drop it.
_tokio_runtime: Option<Runtime>,
dimensions: Arc<[Dimension]>,
biomes: Arc<[Biome]>,
/// Contains info about dimensions, biomes, and chats.
/// Sent to all clients when joining.
registry_codec: Compound,
/// Sender for new clients past the login stage.
new_clients_send: Sender<ClientBundle>,
/// Receiver for new clients past the login stage.
@ -105,12 +98,6 @@ struct SharedServerInner {
}
impl SharedServer {
/// Creates a new [`Instance`] component with the given dimension.
#[must_use]
pub fn new_instance(&self, dimension: DimensionId) -> Instance {
Instance::new(dimension, self)
}
/// Gets the socket address this server is bound to.
pub fn address(&self) -> SocketAddr {
self.0.address
@ -151,48 +138,6 @@ impl SharedServer {
pub fn tokio_handle(&self) -> &Handle {
&self.0.tokio_handle
}
/// Obtains a [`Dimension`] by using its corresponding [`DimensionId`].
#[track_caller]
pub fn dimension(&self, id: DimensionId) -> &Dimension {
self.0
.dimensions
.get(id.0 as usize)
.expect("invalid dimension ID")
}
/// Returns an iterator over all added dimensions and their associated
/// [`DimensionId`].
pub fn dimensions(&self) -> impl FusedIterator<Item = (DimensionId, &Dimension)> + Clone {
self.0
.dimensions
.iter()
.enumerate()
.map(|(i, d)| (DimensionId(i as u16), d))
}
/// Obtains a [`Biome`] by using its corresponding [`BiomeId`].
#[track_caller]
pub fn biome(&self, id: BiomeId) -> &Biome {
self.0.biomes.get(id.0 as usize).expect("invalid biome ID")
}
/// Returns an iterator over all added biomes and their associated
/// [`BiomeId`] in ascending order.
pub fn biomes(
&self,
) -> impl ExactSizeIterator<Item = (BiomeId, &Biome)> + DoubleEndedIterator + FusedIterator + Clone
{
self.0
.biomes
.iter()
.enumerate()
.map(|(i, b)| (BiomeId(i as u16), b))
}
pub(crate) fn registry_codec(&self) -> &Compound {
&self.0.registry_codec
}
}
/// Contains information about a new client joining the server.
@ -243,11 +188,6 @@ pub fn build_plugin(
None => plugin.tokio_handle.clone().unwrap(),
};
validate_dimensions(&plugin.dimensions)?;
validate_biomes(&plugin.biomes)?;
let registry_codec = make_registry_codec(&plugin.dimensions, &plugin.biomes);
let (new_clients_send, new_clients_recv) = flume::bounded(64);
let shared = SharedServer(Arc::new(SharedServerInner {
@ -260,9 +200,6 @@ pub fn build_plugin(
outgoing_capacity: plugin.outgoing_capacity,
tokio_handle,
_tokio_runtime: runtime,
dimensions: plugin.dimensions.clone(),
biomes: plugin.biomes.clone(),
registry_codec,
new_clients_send,
new_clients_recv,
connection_sema: Arc::new(Semaphore::new(plugin.max_connections)),
@ -331,7 +268,10 @@ pub fn build_plugin(
app.add_system(increment_tick_counter.in_base_set(CoreSet::Last));
// Add internal plugins.
app.add_plugin(ComponentPlugin)
app.add_plugin(RegistryCodecPlugin)
.add_plugin(BiomePlugin)
.add_plugin(DimensionPlugin)
.add_plugin(ComponentPlugin)
.add_plugin(ClientPlugin)
.add_plugin(ClientEventPlugin)
.add_plugin(EntityPlugin)
@ -346,32 +286,3 @@ pub fn build_plugin(
fn increment_tick_counter(mut server: ResMut<Server>) {
server.current_tick += 1;
}
fn make_registry_codec(dimensions: &[Dimension], biomes: &[Biome]) -> Compound {
let dimensions = dimensions
.iter()
.enumerate()
.map(|(id, dim)| dim.to_dimension_registry_item(id as i32))
.collect();
let biomes = biomes
.iter()
.enumerate()
.map(|(id, biome)| biome.to_biome_registry_item(id as i32))
.collect();
compound! {
ident!("dimension_type") => compound! {
"type" => ident!("dimension_type"),
"value" => List::Compound(dimensions),
},
ident!("worldgen/biome") => compound! {
"type" => ident!("worldgen/biome"),
"value" => List::Compound(biomes),
},
ident!("chat_type") => compound! {
"type" => ident!("chat_type"),
"value" => List::Compound(vec![]),
},
}
}

View file

@ -34,7 +34,7 @@ use valence_protocol::raw::RawBytes;
use valence_protocol::text::Text;
use valence_protocol::types::Property;
use valence_protocol::var_int::VarInt;
use valence_protocol::{ident_str, translation_key, Decode, MINECRAFT_VERSION, PROTOCOL_VERSION};
use valence_protocol::{ident, translation_key, Decode, MINECRAFT_VERSION, PROTOCOL_VERSION};
use crate::config::{AsyncCallbacks, ConnectionMode, ServerListPing};
use crate::server::connection::InitialConnection;
@ -439,7 +439,7 @@ pub(super) async fn login_velocity(
// Send Player Info Request into the Plugin Channel
conn.send_packet(&LoginQueryRequestS2c {
message_id: VarInt(message_id),
channel: ident_str!("velocity:player_info").into(),
channel: ident!("velocity:player_info").into(),
data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]),
})
.await?;

View file

@ -81,7 +81,7 @@ mod tests {
let (client_ent, mut client_helper) = scenario_single_client(&mut app);
// Send a packet as the client to the server.
let packet = valence_protocol::packet::c2s::play::PositionAndOnGroundC2s {
let packet = valence_protocol::packet::c2s::play::PositionAndOnGround {
position: [12.0, 64.0, 0.0],
on_ground: true,
};

View file

@ -6,12 +6,12 @@ use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
use bytes::BytesMut;
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::packet::S2cPlayPacket;
use valence_protocol::Packet;
use valence_protocol::{ident, Packet};
use crate::client::{ClientBundle, ClientConnection};
use crate::component::Location;
use crate::config::{ConnectionMode, ServerPlugin};
use crate::dimension::DimensionId;
use crate::instance::Instance;
use crate::server::{NewClientInfo, Server};
/// Creates a mock client bundle that can be used for unit testing.
@ -166,7 +166,7 @@ pub fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
);
let server = app.world.resource::<Server>();
let instance = server.new_instance(DimensionId::default());
let instance = Instance::new_unit_testing(ident!("overworld"), server);
let instance_ent = app.world.spawn(instance).id();
let (client, client_helper) = create_mock_client(gen_client_info("test"));

View file

@ -29,6 +29,7 @@ tempfile = "3.3.0"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"
zip = "0.6.3"
valence_protocol = { version = "0.1.0", path = "../valence_protocol" }
[dev-dependencies.reqwest]
version = "0.11.12"

View file

@ -6,9 +6,8 @@ use std::thread;
use clap::Parser;
use flume::{Receiver, Sender};
use tracing::warn;
use valence::bevy_app::AppExit;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::entity::player::PlayerEntityBundle;
use valence::prelude::*;
use valence_anvil::{AnvilChunk, AnvilWorld};
@ -38,8 +37,35 @@ type Priority = u64;
pub fn main() {
tracing_subscriber::fmt().init();
let cli = Cli::parse();
let dir = cli.path;
if !dir.exists() {
eprintln!("Directory `{}` does not exist. Exiting.", dir.display());
return;
} else if !dir.is_dir() {
eprintln!("`{}` is not a directory. Exiting.", dir.display());
return;
}
let anvil = AnvilWorld::new(dir);
let (finished_sender, finished_receiver) = flume::unbounded();
let (pending_sender, pending_receiver) = flume::unbounded();
// Process anvil chunks in a different thread to avoid blocking the main tick
// loop.
thread::spawn(move || anvil_worker(pending_receiver, finished_sender, anvil));
let game_state = GameState {
pending: HashMap::new(),
sender: pending_sender,
receiver: finished_receiver,
};
App::new()
.add_plugin(ServerPlugin::new(()))
.insert_resource(game_state)
.add_startup_system(setup)
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
.add_systems(
@ -55,38 +81,14 @@ pub fn main() {
.run();
}
fn setup(world: &mut World) {
let cli = Cli::parse();
let dir = cli.path;
if !dir.exists() {
eprintln!("Directory `{}` does not exist. Exiting.", dir.display());
world.send_event(AppExit);
} else if !dir.is_dir() {
eprintln!("`{}` is not a directory. Exiting.", dir.display());
world.send_event(AppExit);
}
let anvil = AnvilWorld::new(dir);
let (finished_sender, finished_receiver) = flume::unbounded();
let (pending_sender, pending_receiver) = flume::unbounded();
// Process anvil chunks in a different thread to avoid blocking the main tick
// loop.
thread::spawn(move || anvil_worker(pending_receiver, finished_sender, anvil));
world.insert_resource(GameState {
pending: HashMap::new(),
sender: pending_sender,
receiver: finished_receiver,
});
let instance = world
.resource::<Server>()
.new_instance(DimensionId::default());
world.spawn(instance);
fn setup(
mut commands: Commands,
dimensions: Query<&DimensionType>,
biomes: Query<&Biome>,
server: Res<Server>,
) {
let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
commands.spawn(instance);
}
fn init_clients(
@ -98,7 +100,7 @@ fn init_clients(
*game_mode = GameMode::Creative;
is_flat.0 = true;
commands.entity(entity).insert(PlayerBundle {
commands.entity(entity).insert(PlayerEntityBundle {
location: Location(instances.single()),
position: Position(SPAWN_POS),
uuid: *uuid,

View file

@ -19,6 +19,7 @@ mod to_valence;
pub struct AnvilWorld {
/// Path to the "region" subdirectory in the world root.
region_root: PathBuf,
// TODO: LRU cache for region file handles.
/// Maps region (x, z) positions to region files.
regions: BTreeMap<(i32, i32), Region>,
}
@ -83,6 +84,8 @@ impl AnvilWorld {
Entry::Vacant(ve) => {
// Load the region file if it exists. Otherwise, the chunk is considered absent.
// TODO: Add tombstone for missing region file in `regions`.
let path = self
.region_root
.join(format!("r.{region_x}.{region_z}.mca"));

View file

@ -32,6 +32,59 @@ impl Compound {
pub fn written_size(&self, root_name: &str) -> usize {
written_size(self, root_name)
}
/// Inserts all items from `other` into `self` recursively.
///
/// # Example
///
/// ```
/// use valence_nbt::compound;
///
/// let mut this = compound! {
/// "foo" => 10,
/// "bar" => compound! {
/// "baz" => 20,
/// }
/// };
///
/// let other = compound! {
/// "foo" => 15,
/// "bar" => compound! {
/// "quux" => "hello",
/// }
/// };
///
/// this.merge(other);
///
/// assert_eq!(
/// this,
/// compound! {
/// "foo" => 15,
/// "bar" => compound! {
/// "baz" => 20,
/// "quux" => "hello",
/// }
/// }
/// );
/// ```
pub fn merge(&mut self, other: Compound) {
for (k, v) in other {
match (self.entry(k), v) {
(Entry::Occupied(mut oe), Value::Compound(other)) => {
if let Value::Compound(this) = oe.get_mut() {
// Insert compound recursively.
this.merge(other);
}
}
(Entry::Occupied(mut oe), value) => {
oe.insert(value);
}
(Entry::Vacant(ve), value) => {
ve.insert(value);
}
}
}
}
}
impl fmt::Debug for Compound {

View file

@ -98,8 +98,6 @@ impl List {
}
}
/// We can not create new identities in stable Rust using macros, so we provide
/// them in the macro invocation itself.
macro_rules! nbt_conversion {
( $($nbt_type:ident = $value_type:ty => $is_function:ident $as_function:ident $as_mut_function:ident $into_function:ident)+ ) => {
$(
@ -221,6 +219,12 @@ impl From<String> for Value {
}
}
impl From<&String> for Value {
fn from(value: &String) -> Self {
Self::String(value.clone())
}
}
impl<'a> From<&'a str> for Value {
fn from(v: &'a str) -> Self {
Self::String(v.to_owned())

View file

@ -12,6 +12,7 @@ byteorder = "1.4.3"
bytes = "1.2.1"
cfb8 = { version = "0.7.1", optional = true }
flate2 = { version = "1.0.24", optional = true }
glam = { version = "0.23.0", optional = true }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.87"
thiserror = "1.0.37"
@ -40,3 +41,4 @@ serde_json = "1.0.85"
[features]
encryption = ["dep:aes", "dep:cfb8"]
compression = ["dep:flate2"]
glam = ["dep:glam"]

View file

@ -473,7 +473,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
let ident = &block_entity.ident;
quote! {
Self::#name => ident_str!(#ident),
Self::#name => ident!(#ident),
}
})
.collect::<TokenStream>();

View file

@ -8,6 +8,7 @@ use proc_macro2::{Ident, Span};
mod block;
mod enchant;
mod item;
mod packet_id;
mod sound;
mod translation_key;
@ -20,6 +21,7 @@ pub fn main() -> anyhow::Result<()> {
(item::build, "item.rs"),
(sound::build, "sound.rs"),
(translation_key::build, "translation_key.rs"),
(packet_id::build, "packet_id.rs"),
];
let out_dir = env::var_os("OUT_DIR").context("failed to get OUT_DIR env var")?;

View file

@ -0,0 +1,38 @@
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
use crate::ident;
#[derive(Deserialize)]
struct Packet {
name: String,
side: String,
state: String,
id: i32,
}
pub fn build() -> anyhow::Result<TokenStream> {
let packets: Vec<Packet> =
serde_json::from_str(include_str!("../../../extracted/packets.json"))?;
let mut consts = TokenStream::new();
for packet in packets {
let stripped_name = packet.name.strip_suffix("Packet").unwrap_or(&packet.name);
let name_ident = ident(stripped_name.to_pascal_case());
let id = packet.id;
let doc = format!("Side: {}\nState: {}", packet.side, packet.state);
consts.extend([quote! {
#[doc = #doc]
#[allow(non_upper_case_globals)]
pub const #name_ident: i32 = #id;
}]);
}
Ok(consts)
}

View file

@ -58,7 +58,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
let str_name = &sound.name;
let name = ident(str_name.to_pascal_case());
quote! {
Self::#name => ident_str!(#str_name),
Self::#name => ident!(#str_name),
}
})
.collect::<TokenStream>();

View file

@ -6,12 +6,11 @@ use std::io::Write;
use std::iter::FusedIterator;
use anyhow::Context;
use valence_protocol_macros::ident_str;
use crate::ident::Ident;
use crate::item::ItemKind;
use crate::var_int::VarInt;
use crate::{Decode, Encode, Result};
use crate::{ident, Decode, Encode, Result};
include!(concat!(env!("OUT_DIR"), "/block.rs"));

View file

@ -64,6 +64,15 @@ impl<S> Ident<S> {
}
}
pub fn to_string_ident(&self) -> Ident<String>
where
S: AsRef<str>,
{
Ident {
string: self.as_str().to_owned(),
}
}
pub fn into_inner(self) -> S {
self.string
}
@ -138,6 +147,12 @@ impl<S: Borrow<str>> Borrow<str> for Ident<S> {
}
}
impl From<Ident<&str>> for String {
fn from(value: Ident<&str>) -> Self {
value.as_str().to_owned()
}
}
impl From<Ident<String>> for String {
fn from(value: Ident<String>) -> Self {
value.into_inner()
@ -323,40 +338,11 @@ where
}
}
/// Convenience macro for constructing an [`Ident<String>`] from a format
/// string.
///
/// The arguments to this macro are forwarded to [`std::format`].
///
/// # Panics
///
/// The macro will cause a panic if the formatted string is not a valid resource
/// identifier. See [`Ident`] for more information.
///
/// [`Ident<String>`]: [Ident]
///
/// # Examples
///
/// ```
/// use valence_protocol::ident;
///
/// let namespace = "my_namespace";
/// let path = "my_path";
///
/// let id = ident!("{namespace}:{path}");
///
/// assert_eq!(id.namespace(), "my_namespace");
/// assert_eq!(id.path(), "my_path");
/// ```
#[macro_export]
macro_rules! ident {
($($arg:tt)*) => {{
$crate::ident::Ident::<String>::try_from(::std::format!($($arg)*)).unwrap()
}}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ident;
#[test]
fn check_namespace_and_path() {
let id = ident!("namespace:path");
@ -374,19 +360,19 @@ mod tests {
#[test]
#[should_panic]
fn parse_invalid_0() {
ident!("");
Ident::new("").unwrap();
}
#[test]
#[should_panic]
fn parse_invalid_1() {
ident!(":");
Ident::new(":").unwrap();
}
#[test]
#[should_panic]
fn parse_invalid_2() {
ident!("foo:bar:baz");
Ident::new("foo:bar:baz").unwrap();
}
#[test]

View file

@ -214,6 +214,155 @@ impl Decode<'_> for f64 {
}
}
#[cfg(feature = "glam")]
mod glam {
use ::glam::*;
use super::*;
impl Encode for Vec2 {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(w)
}
}
impl Decode<'_> for Vec2 {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self {
x: f32::decode(r)?,
y: f32::decode(r)?,
})
}
}
impl Encode for Vec3 {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(&mut w)?;
self.z.encode(w)
}
}
impl Decode<'_> for Vec3 {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self {
x: f32::decode(r)?,
y: f32::decode(r)?,
z: f32::decode(r)?,
})
}
}
impl Encode for Vec3A {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(&mut w)?;
self.z.encode(w)
}
}
impl Decode<'_> for Vec3A {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self::new(f32::decode(r)?, f32::decode(r)?, f32::decode(r)?))
}
}
impl Encode for Vec4 {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(&mut w)?;
self.z.encode(&mut w)?;
self.w.encode(&mut w)
}
}
impl Decode<'_> for Vec4 {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self::new(
f32::decode(r)?,
f32::decode(r)?,
f32::decode(r)?,
f32::decode(r)?,
))
}
}
impl Encode for Quat {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(&mut w)?;
self.z.encode(&mut w)?;
self.w.encode(w)
}
}
impl Decode<'_> for Quat {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self::from_xyzw(
f32::decode(r)?,
f32::decode(r)?,
f32::decode(r)?,
f32::decode(r)?,
))
}
}
impl Encode for DVec2 {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(w)
}
}
impl Decode<'_> for DVec2 {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self {
x: f64::decode(r)?,
y: f64::decode(r)?,
})
}
}
impl Encode for DVec3 {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(&mut w)?;
self.z.encode(w)
}
}
impl Decode<'_> for DVec3 {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self {
x: f64::decode(r)?,
y: f64::decode(r)?,
z: f64::decode(r)?,
})
}
}
impl Encode for DQuat {
fn encode(&self, mut w: impl Write) -> Result<()> {
self.x.encode(&mut w)?;
self.y.encode(&mut w)?;
self.z.encode(&mut w)?;
self.w.encode(w)
}
}
impl Decode<'_> for DQuat {
fn decode(r: &mut &[u8]) -> Result<Self> {
Ok(Self::from_xyzw(
f64::decode(r)?,
f64::decode(r)?,
f64::decode(r)?,
f64::decode(r)?,
))
}
}
}
// ==== Pointer ==== //
impl<T: Encode + ?Sized> Encode for &T {

View file

@ -71,15 +71,15 @@ use std::io::Write;
use std::{fmt, io};
pub use anyhow::{Error, Result};
pub use valence_protocol_macros::{ident_str, Decode, Encode, Packet};
pub use valence_protocol_macros::{ident, Decode, Encode, Packet};
pub use {uuid, valence_nbt as nbt};
/// The Minecraft protocol version this library currently targets.
pub const PROTOCOL_VERSION: i32 = 761;
pub const PROTOCOL_VERSION: i32 = 762;
/// The stringified name of the Minecraft version this library currently
/// targets.
pub const MINECRAFT_VERSION: &str = "1.19.3";
pub const MINECRAFT_VERSION: &str = "1.19.4";
pub mod array;
pub mod block;

View file

@ -17,7 +17,7 @@ macro_rules! packet_group {
(
$(#[$attrs:meta])*
$enum_name:ident<$enum_life:lifetime> {
$($packet_id:literal = $packet:ident $(<$life:lifetime>)?),* $(,)?
$($packet:ident $(<$life:lifetime>)?),* $(,)?
}
) => {
$(#[$attrs])*
@ -35,10 +35,10 @@ macro_rules! packet_group {
}
impl<$enum_life> crate::Packet<$enum_life> for $packet$(<$life>)? {
const PACKET_ID: i32 = $packet_id;
const PACKET_ID: i32 = crate::packet::id::$packet;
fn packet_id(&self) -> i32 {
$packet_id
Self::PACKET_ID
}
fn packet_name(&self) -> &str {
@ -49,7 +49,7 @@ macro_rules! packet_group {
fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> {
use ::valence_protocol::__private::{Encode, Context, VarInt};
VarInt($packet_id)
VarInt(Self::PACKET_ID)
.encode(&mut w)
.context("failed to encode packet ID")?;
@ -61,7 +61,7 @@ macro_rules! packet_group {
use ::valence_protocol::__private::{Decode, Context, VarInt, ensure};
let id = VarInt::decode(r).context("failed to decode packet ID")?.0;
ensure!(id == $packet_id, "unexpected packet ID {} (expected {})", id, $packet_id);
ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID);
Self::decode(r)
}
@ -130,7 +130,7 @@ macro_rules! packet_group {
(
$(#[$attrs:meta])*
$enum_name:ident {
$($packet_id:literal = $packet:ident),* $(,)?
$($packet:ident),* $(,)?
}
) => {
$(#[$attrs])*
@ -148,10 +148,10 @@ macro_rules! packet_group {
}
impl crate::Packet<'_> for $packet {
const PACKET_ID: i32 = $packet_id;
const PACKET_ID: i32 = crate::packet::id::$packet;
fn packet_id(&self) -> i32 {
$packet_id
Self::PACKET_ID
}
fn packet_name(&self) -> &str {
@ -162,7 +162,7 @@ macro_rules! packet_group {
fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> {
use ::valence_protocol::__private::{Encode, Context, VarInt};
VarInt($packet_id)
VarInt(Self::PACKET_ID)
.encode(&mut w)
.context("failed to encode packet ID")?;
@ -174,7 +174,7 @@ macro_rules! packet_group {
use ::valence_protocol::__private::{Decode, Context, VarInt, ensure};
let id = VarInt::decode(r).context("failed to decode packet ID")?.0;
ensure!(id == $packet_id, "unexpected packet ID {} (expected {})", id, $packet_id);
ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID);
Self::decode(r)
}
@ -243,3 +243,9 @@ macro_rules! packet_group {
pub mod c2s;
pub mod s2c;
/// Contains the packet ID for every packet. The compiler will yell at us when
/// we forget to use one, which is nice.
mod id {
include!(concat!(env!("OUT_DIR"), "/packet_id.rs"));
}

View file

@ -7,7 +7,7 @@ pub mod handshake {
packet_group! {
#[derive(Clone)]
C2sHandshakePacket<'a> {
0 = HandshakeC2s<'a>
HandshakeC2s<'a>
}
}
}
@ -22,8 +22,8 @@ pub mod status {
packet_group! {
#[derive(Clone)]
C2sStatusPacket {
0 = QueryRequestC2s,
1 = QueryPingC2s,
QueryPingC2s,
QueryRequestC2s,
}
}
}
@ -40,9 +40,9 @@ pub mod login {
packet_group! {
#[derive(Clone)]
C2sLoginPacket<'a> {
0 = LoginHelloC2s<'a>,
1 = LoginKeyC2s<'a>,
2 = LoginQueryResponseC2s<'a>,
LoginHelloC2s<'a>,
LoginKeyC2s<'a>,
LoginQueryResponseC2s<'a>,
}
}
}
@ -70,10 +70,10 @@ pub mod play {
pub use play_pong::PlayPongC2s;
pub use player_action::PlayerActionC2s;
pub use player_input::PlayerInputC2s;
pub use player_interact::PlayerInteractC2s;
pub use player_interact_block::PlayerInteractBlockC2s;
pub use player_interact_entity::PlayerInteractEntityC2s;
pub use player_interact_item::PlayerInteractItemC2s;
pub use player_move::{FullC2s, LookAndOnGroundC2s, OnGroundOnlyC2s, PositionAndOnGroundC2s};
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;
@ -119,8 +119,8 @@ pub mod play {
pub mod play_pong;
pub mod player_action;
pub mod player_input;
pub mod player_interact;
pub mod player_interact_block;
pub mod player_interact_entity;
pub mod player_interact_item;
pub mod player_move;
pub mod player_session;
@ -149,57 +149,57 @@ pub mod play {
packet_group! {
#[derive(Clone)]
C2sPlayPacket<'a> {
0 = TeleportConfirmC2s,
1 = QueryBlockNbtC2s,
2 = UpdateDifficultyC2s,
3 = MessageAcknowledgmentC2s,
4 = CommandExecutionC2s<'a>,
5 = ChatMessageC2s<'a>,
6 = ClientStatusC2s,
7 = ClientSettingsC2s<'a>,
8 = RequestCommandCompletionsC2s<'a>,
9 = ButtonClickC2s,
10 = ClickSlotC2s,
11 = CloseHandledScreenC2s,
12 = CustomPayloadC2s<'a>,
13 = BookUpdateC2s<'a>,
14 = QueryEntityNbtC2s,
15 = PlayerInteractC2s,
16 = JigsawGeneratingC2s,
17 = KeepAliveC2s,
18 = UpdateDifficultyLockC2s,
19 = PositionAndOnGroundC2s,
20 = FullC2s,
21 = LookAndOnGroundC2s,
22 = OnGroundOnlyC2s,
23 = VehicleMoveC2s,
24 = BoatPaddleStateC2s,
25 = PickFromInventoryC2s,
26 = CraftRequestC2s<'a>,
27 = UpdatePlayerAbilitiesC2s,
28 = PlayerActionC2s,
29 = ClientCommandC2s,
30 = PlayerInputC2s,
31 = PlayPongC2s,
32 = PlayerSessionC2s<'a>,
33 = RecipeCategoryOptionsC2s,
34 = RecipeBookDataC2s<'a>,
35 = RenameItemC2s<'a>,
36 = ResourcePackStatusC2s,
37 = AdvancementTabC2s<'a>,
38 = SelectMerchantTradeC2s,
39 = UpdateBeaconC2s,
40 = UpdateSelectedSlotC2s,
41 = UpdateCommandBlockC2s<'a>,
42 = UpdateCommandBlockMinecartC2s<'a>,
43 = CreativeInventoryActionC2s,
44 = UpdateJigsawC2s<'a>,
45 = UpdateStructureBlockC2s<'a>,
46 = UpdateSignC2s<'a>,
47 = HandSwingC2s,
48 = SpectatorTeleportC2s,
49 = PlayerInteractBlockC2s,
50 = PlayerInteractItemC2s
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

@ -3,14 +3,14 @@ use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerInteractC2s {
pub struct PlayerInteractEntityC2s {
pub entity_id: VarInt,
pub interact: Interaction,
pub interact: EntityInteraction,
pub sneaking: bool,
}
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)]
pub enum Interaction {
pub enum EntityInteraction {
Interact(Hand),
Attack,
InteractAt { target: [f32; 3], hand: Hand },

View file

@ -1,13 +1,13 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PositionAndOnGroundC2s {
pub struct PositionAndOnGround {
pub position: [f64; 3],
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct FullC2s {
pub struct Full {
pub position: [f64; 3],
pub yaw: f32,
pub pitch: f32,
@ -15,13 +15,13 @@ pub struct FullC2s {
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct LookAndOnGroundC2s {
pub struct LookAndOnGround {
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct OnGroundOnlyC2s {
pub struct OnGroundOnly {
pub on_ground: bool,
}

View file

@ -8,8 +8,8 @@ pub mod status {
packet_group! {
#[derive(Clone)]
S2cStatusPacket<'a> {
0 = QueryResponseS2c<'a>,
1 = QueryPongS2c,
QueryPongS2c,
QueryResponseS2c<'a>,
}
}
}
@ -30,11 +30,11 @@ pub mod login {
packet_group! {
#[derive(Clone)]
S2cLoginPacket<'a> {
0 = LoginDisconnectS2c<'a>,
1 = LoginHelloS2c<'a>,
2 = LoginSuccessS2c<'a>,
3 = LoginCompressionS2c,
4 = LoginQueryRequestS2c<'a>,
LoginCompressionS2c,
LoginDisconnectS2c<'a>,
LoginHelloS2c<'a>,
LoginQueryRequestS2c<'a>,
LoginSuccessS2c<'a>,
}
}
}
@ -46,30 +46,34 @@ pub mod play {
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_titles::ClearTitlesS2c;
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::{MoveRelativeS2c, RotateAndMoveRelativeS2c, RotateS2c};
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;
@ -119,11 +123,11 @@ pub mod play {
pub use scoreboard_player_update::ScoreboardPlayerUpdateS2c;
pub use screen_handler_property_update::ScreenHandlerPropertyUpdateS2c;
pub use screen_handler_slot_update::ScreenHandlerSlotUpdateS2c;
pub use select_advancements_tab::SelectAdvancementsTabS2c;
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::SignEditorOpen;
pub use sign_editor_open::SignEditorOpenS2c;
pub use simulation_distance::SimulationDistanceS2c;
pub use statistics::StatisticsS2c;
pub use stop_sound::StopSoundS2c;
@ -152,30 +156,34 @@ pub mod play {
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_titles;
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;
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;
@ -225,7 +233,7 @@ pub mod play {
pub mod scoreboard_player_update;
pub mod screen_handler_property_update;
pub mod screen_handler_slot_update;
pub mod select_advancements_tab;
pub mod select_advancement_tab;
pub mod server_metadata;
pub mod set_camera_entity;
pub mod set_trade_offers;
@ -255,113 +263,117 @@ pub mod play {
packet_group! {
#[derive(Clone)]
S2cPlayPacket<'a> {
0 = EntitySpawnS2c,
1 = ExperienceOrbSpawnS2c,
2 = PlayerSpawnS2c,
3 = EntityAnimationS2c,
4 = StatisticsS2c,
5 = PlayerActionResponseS2c,
6 = BlockBreakingProgressS2c,
7 = BlockEntityUpdateS2c<'a>,
8 = BlockEventS2c,
9 = BlockUpdateS2c,
10 = BossBarS2c,
11 = DifficultyS2c,
12 = ClearTitlesS2c,
13 = CommandSuggestionsS2c<'a>,
14 = CommandTreeS2c<'a>,
15 = CloseScreenS2c,
16 = InventoryS2c<'a>,
17 = ScreenHandlerPropertyUpdateS2c,
18 = ScreenHandlerSlotUpdateS2c<'a>,
19 = CooldownUpdateS2c,
20 = ChatSuggestionsS2c<'a>,
21 = CustomPayloadS2c<'a>,
22 = RemoveMessageS2c<'a>,
23 = DisconnectS2c<'a>,
24 = ProfilelessChatMessageS2c<'a>,
25 = EntityStatusS2c,
26 = ExplosionS2c<'a>,
27 = UnloadChunkS2c,
28 = GameStateChangeS2c,
29 = OpenHorseScreenS2c,
30 = WorldBorderInitializeS2c,
31 = KeepAliveS2c,
32 = ChunkDataS2c<'a>,
33 = WorldEventS2c,
34 = ParticleS2c<'a>,
35 = LightUpdateS2c,
36 = GameJoinS2c<'a>,
37 = MapUpdateS2c<'a>,
38 = SetTradeOffersS2c,
39 = MoveRelativeS2c,
40 = RotateAndMoveRelativeS2c,
41 = RotateS2c,
42 = VehicleMoveS2c,
43 = OpenWrittenBookS2c,
44 = OpenScreenS2c<'a>,
45 = SignEditorOpen,
46 = PlayPingS2c,
47 = CraftFailedResponseS2c<'a>,
48 = PlayerAbilitiesS2c,
49 = ChatMessageS2c<'a>,
50 = EndCombatS2c,
51 = EnterCombatS2c,
52 = DeathMessageS2c<'a>,
53 = PlayerRemoveS2c<'a>,
54 = PlayerListS2c<'a>,
55 = LookAtS2c,
56 = PlayerPositionLookS2c,
57 = UnlockRecipesS2c<'a>,
58 = EntitiesDestroyS2c<'a>,
59 = RemoveEntityStatusEffectS2c,
60 = ResourcePackSendS2c<'a>,
61 = PlayerRespawnS2c<'a>,
62 = EntitySetHeadYawS2c,
63 = ChunkDeltaUpdateS2c<'a>,
64 = SelectAdvancementsTabS2c<'a>,
65 = ServerMetadataS2c<'a>,
66 = OverlayMessageS2c<'a>,
67 = WorldBorderCenterChangedS2c,
68 = WorldBorderInterpolateSizeS2c,
69 = WorldBorderSizeChangedS2c,
70 = WorldBorderWarningTimeChangedS2c,
71 = WorldBorderWarningBlocksChangedS2c,
72 = SetCameraEntityS2c,
73 = UpdateSelectedSlotS2c,
74 = ChunkRenderDistanceCenterS2c,
75 = ChunkLoadDistanceS2c,
76 = PlayerSpawnPositionS2c,
77 = ScoreboardDisplayS2c<'a>,
78 = EntityTrackerUpdateS2c<'a>,
79 = EntityAttachS2c,
80 = EntityVelocityUpdateS2c,
81 = EntityEquipmentUpdateS2c,
82 = ExperienceBarUpdateS2c,
83 = HealthUpdateS2c,
84 = ScoreboardObjectiveUpdateS2c<'a>,
85 = EntityPassengersSetS2c,
86 = TeamS2c<'a>,
87 = ScoreboardPlayerUpdateS2c<'a>,
88 = SimulationDistanceS2c,
89 = SubtitleS2c<'a>,
90 = WorldTimeUpdateS2c,
91 = TitleS2c<'a>,
92 = TitleFadeS2c,
93 = PlaySoundFromEntityS2c,
94 = PlaySoundS2c<'a>,
95 = StopSoundS2c<'a>,
96 = GameMessageS2c<'a>,
97 = PlayerListHeaderS2c<'a>,
98 = NbtQueryResponseS2c,
99 = ItemPickupAnimationS2c,
100 = EntityPositionS2c,
101 = AdvancementUpdateS2c<'a>,
102 = EntityAttributesS2c<'a>,
103 = FeaturesS2c<'a>,
104 = EntityStatusEffectS2c,
105 = SynchronizeRecipesS2c<'a>,
106 = SynchronizeTagsS2c<'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

@ -0,0 +1,4 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct BundleSplitter;

View file

@ -0,0 +1,16 @@
use std::borrow::Cow;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct ChunkBiomeDataS2c<'a> {
pub chunks: Cow<'a, [ChunkBiome<'a>]>,
}
#[derive(Clone, Debug, Encode, Decode)]
pub struct ChunkBiome<'a> {
pub chunk_x: i32,
pub chunk_z: i32,
/// Chunk data structure, with sections containing only the `Biomes` field.
pub data: &'a [u8],
}

View file

@ -1,6 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct ClearTitlesS2c {
pub struct ClearTitleS2c {
pub reset: bool,
}

View file

@ -0,0 +1,10 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct DamageTiltS2c {
/// The ID of the entity taking damage.
pub entity_id: VarInt,
/// The direction the damage is coming from in relation to the entity.
pub yaw: f32,
}

View file

@ -0,0 +1,23 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct EntityDamageS2c {
/// The ID of the entity taking damage
pub entity_id: VarInt,
/// The ID of the type of damage taken
pub source_type_id: VarInt,
/// The ID + 1 of the entity responsible for the damage, if present. If not
/// present, the value is 0
pub source_cause_id: VarInt,
/// The ID + 1 of the entity that directly dealt the damage, if present. If
/// not present, the value is 0. If this field is present:
/// * and damage was dealt indirectly, such as by the use of a projectile,
/// this field will contain the ID of such projectile;
/// * and damage was dealt dirctly, such as by manually attacking, this
/// field will contain the same value as Source Cause ID.
pub source_direct_id: VarInt,
/// The Notchian server sends the Source Position when the damage was dealt
/// by the /damage command and a position was specified
pub source_pos: Option<[f64; 3]>,
}

View file

@ -3,14 +3,14 @@ use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct MoveRelativeS2c {
pub struct MoveRelative {
pub entity_id: VarInt,
pub delta: [i16; 3],
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct RotateAndMoveRelativeS2c {
pub struct RotateAndMoveRelative {
pub entity_id: VarInt,
pub delta: [i16; 3],
pub yaw: ByteAngle,
@ -19,7 +19,7 @@ pub struct RotateAndMoveRelativeS2c {
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct RotateS2c {
pub struct Rotate {
pub entity_id: VarInt,
pub yaw: ByteAngle,
pub pitch: ByteAngle,

View file

@ -10,7 +10,6 @@ pub struct PlayerPositionLookS2c {
pub pitch: f32,
pub flags: Flags,
pub teleport_id: VarInt,
pub dismount_vehicle: bool,
}
#[bitfield(u8)]

View file

@ -4,6 +4,6 @@ use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct SelectAdvancementsTabS2c<'a> {
pub struct SelectAdvancementTabS2c<'a> {
pub identifier: Option<Ident<Cow<'a, str>>>,
}

View file

@ -5,7 +5,7 @@ use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct ServerMetadataS2c<'a> {
pub motd: Option<Cow<'a, Text>>,
pub icon: Option<&'a str>,
pub motd: Cow<'a, Text>,
pub icon: Option<&'a [u8]>,
pub enforce_secure_chat: bool,
}

View file

@ -2,6 +2,6 @@ use crate::block_pos::BlockPos;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct SignEditorOpen {
pub struct SignEditorOpenS2c {
pub location: BlockPos,
}

View file

@ -10,7 +10,8 @@ use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct SynchronizeRecipesS2c<'a> {
pub recipes: Vec<Recipe<'a>>,
// TODO: this should be a Vec<Recipe<'a>>
pub recipes: crate::raw::RawBytes<'a>,
}
#[derive(Clone, PartialEq, Debug)]

View file

@ -1,5 +1,5 @@
use crate::ident;
use crate::ident::Ident;
use crate::ident_str;
use crate::packet::s2c::play::play_sound::SoundId;
include!(concat!(env!("OUT_DIR"), "/sound.rs"));
@ -22,7 +22,7 @@ mod tests {
assert_eq!(
Sound::BlockBellUse.to_id(),
SoundId::Direct {
id: Ident::new("block.bell.use").unwrap(),
id: ident!("block.bell.use").into(),
range: None
},
);

View file

@ -14,7 +14,7 @@ fn check_path(s: &str) -> bool {
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/'))
}
pub fn ident_str(item: TokenStream) -> Result<TokenStream> {
pub fn ident(item: TokenStream) -> Result<TokenStream> {
let ident_lit: LitStr = parse2(item)?;
let mut ident = ident_lit.value();

View file

@ -14,7 +14,7 @@ use syn::{
mod decode;
mod encode;
mod ident_str;
mod ident;
mod packet;
#[proc_macro_derive(Encode, attributes(tag))]
@ -42,8 +42,8 @@ pub fn derive_packet(item: StdTokenStream) -> StdTokenStream {
}
#[proc_macro]
pub fn ident_str(item: StdTokenStream) -> StdTokenStream {
match ident_str::ident_str(item.into()) {
pub fn ident(item: StdTokenStream) -> StdTokenStream {
match ident::ident(item.into()) {
Ok(tokens) => tokens.into(),
Err(e) => e.into_compile_error().into(),
}

View file

@ -9,9 +9,7 @@ use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::packet::c2s::handshake::handshake::NextState;
use valence_protocol::packet::c2s::handshake::HandshakeC2s;
use valence_protocol::packet::c2s::login::LoginHelloC2s;
use valence_protocol::packet::c2s::play::{
KeepAliveC2s, PositionAndOnGroundC2s, TeleportConfirmC2s,
};
use valence_protocol::packet::c2s::play::{KeepAliveC2s, PositionAndOnGround, TeleportConfirmC2s};
use valence_protocol::packet::{C2sHandshakePacket, S2cLoginPacket, S2cPlayPacket};
use valence_protocol::var_int::VarInt;
use valence_protocol::PROTOCOL_VERSION;
@ -137,7 +135,7 @@ pub async fn make_session<'a>(params: &SessionParams<'a>) -> anyhow::Result<()>
teleport_id: p.teleport_id,
})?;
enc.append_packet(&PositionAndOnGroundC2s {
enc.append_packet(&PositionAndOnGround {
position: p.position,
on_ground: true,
})?;

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,18 @@
{
"AbstractDecorationEntity": {
"parent": "Entity",
"fields": [],
"default_bounding_box": {
"size_x": 0.0,
"size_y": 0.0,
"size_z": 0.0
}
"fields": []
},
"AbstractDonkeyEntity": {
"parent": "AbstractHorseEntity",
"fields": [
{
"name": "chest",
"index": 19,
"index": 18,
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 0.8999999761581421,
"size_y": 1.8700000047683716,
"size_z": 0.8999999761581421
}
]
},
"AbstractFireballEntity": {
"parent": "ExplosiveProjectileEntity",
@ -33,12 +23,7 @@
"type": "item_stack",
"default_value": "1 air"
}
],
"default_bounding_box": {
"size_x": 0.3125,
"size_y": 0.3125,
"size_z": 0.3125
}
]
},
"AbstractHorseEntity": {
"parent": "AnimalEntity",
@ -48,19 +33,8 @@
"index": 17,
"type": "byte",
"default_value": 0
},
{
"name": "owner_uuid",
"index": 18,
"type": "optional_uuid",
"default_value": null
}
],
"default_bounding_box": {
"size_x": 0.8999999761581421,
"size_y": 1.8700000047683716,
"size_z": 0.8999999761581421
}
]
},
"AbstractMinecartEntity": {
"parent": "Entity",
@ -101,12 +75,7 @@
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 0.9800000190734863,
"size_y": 0.699999988079071,
"size_z": 0.9800000190734863
}
]
},
"AbstractPiglinEntity": {
"parent": "HostileEntity",
@ -117,21 +86,11 @@
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9500000476837158,
"size_z": 0.6000000238418579
}
]
},
"AbstractSkeletonEntity": {
"parent": "HostileEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9900000095367432,
"size_z": 0.6000000238418579
}
"fields": []
},
"AllayEntity": {
"parent": "PathAwareEntity",
@ -159,21 +118,11 @@
},
"AmbientEntity": {
"parent": "MobEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.5,
"size_y": 0.8999999761581421,
"size_z": 0.5
}
"fields": []
},
"AnimalEntity": {
"parent": "PassiveEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.4000000059604645,
"size_y": 0.699999988079071,
"size_z": 0.4000000059604645
}
"fields": []
},
"AreaEffectCloudEntity": {
"parent": "Entity",
@ -397,6 +346,24 @@
"size_z": 0.6000000238418579
}
},
"BlockDisplayEntity": {
"parent": "DisplayEntity",
"type": "block_display",
"translation_key": "entity.minecraft.block_display",
"fields": [
{
"name": "block_state",
"index": 22,
"type": "block_state",
"default_value": "Block{minecraft:air}"
}
],
"default_bounding_box": {
"size_x": 0.0,
"size_y": 0.0,
"size_z": 0.0
}
},
"BoatEntity": {
"parent": "Entity",
"type": "boat",
@ -458,15 +425,15 @@
"fields": [
{
"name": "dashing",
"index": 19,
"index": 18,
"type": "boolean",
"default_value": false
},
{
"name": "last_pose_tick",
"index": 20,
"index": 19,
"type": "long",
"default_value": -52
"default_value": 0
}
],
"default_bounding_box": {
@ -631,6 +598,113 @@
"size_z": 0.6000000238418579
}
},
"DisplayEntity": {
"parent": "Entity",
"fields": [
{
"name": "start_interpolation",
"index": 8,
"type": "integer",
"default_value": 0
},
{
"name": "interpolation_duration",
"index": 9,
"type": "integer",
"default_value": 0
},
{
"name": "translation",
"index": 10,
"type": "vector3f",
"default_value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
},
{
"name": "scale",
"index": 11,
"type": "vector3f",
"default_value": {
"x": 1.0,
"y": 1.0,
"z": 1.0
}
},
{
"name": "left_rotation",
"index": 12,
"type": "quaternionf",
"default_value": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"w": 1.0
}
},
{
"name": "right_rotation",
"index": 13,
"type": "quaternionf",
"default_value": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"w": 1.0
}
},
{
"name": "billboard",
"index": 14,
"type": "byte",
"default_value": 0
},
{
"name": "brightness",
"index": 15,
"type": "integer",
"default_value": -1
},
{
"name": "view_range",
"index": 16,
"type": "float",
"default_value": 1.0
},
{
"name": "shadow_radius",
"index": 17,
"type": "float",
"default_value": 0.0
},
{
"name": "shadow_strength",
"index": 18,
"type": "float",
"default_value": 1.0
},
{
"name": "width",
"index": 19,
"type": "float",
"default_value": 0.0
},
{
"name": "height",
"index": 20,
"type": "float",
"default_value": 0.0
},
{
"name": "glow_color_override",
"index": 21,
"type": "integer",
"default_value": -1
}
]
},
"DolphinEntity": {
"parent": "WaterCreatureEntity",
"type": "dolphin",
@ -864,12 +938,7 @@
"type": "integer",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 1.0,
"size_y": 1.0,
"size_z": 1.0
}
]
},
"EvokerEntity": {
"parent": "SpellcastingIllagerEntity",
@ -917,12 +986,7 @@
},
"ExplosiveProjectileEntity": {
"parent": "ProjectileEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.3125,
"size_y": 0.3125,
"size_z": 0.3125
}
"fields": []
},
"EyeOfEnderEntity": {
"parent": "Entity",
@ -1014,12 +1078,7 @@
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 0.699999988079071,
"size_y": 0.4000000059604645,
"size_z": 0.699999988079071
}
]
},
"FishingBobberEntity": {
"parent": "ProjectileEntity",
@ -1047,12 +1106,7 @@
},
"FlyingEntity": {
"parent": "MobEntity",
"fields": [],
"default_bounding_box": {
"size_x": 4.0,
"size_y": 4.0,
"size_z": 4.0
}
"fields": []
},
"FoxEntity": {
"parent": "AnimalEntity",
@ -1222,12 +1276,7 @@
},
"GolemEntity": {
"parent": "PathAwareEntity",
"fields": [],
"default_bounding_box": {
"size_x": 1.0,
"size_y": 1.0,
"size_z": 1.0
}
"fields": []
},
"GuardianEntity": {
"parent": "HostileEntity",
@ -1248,9 +1297,9 @@
}
],
"default_bounding_box": {
"size_x": 1.997499942779541,
"size_y": 1.997499942779541,
"size_z": 1.997499942779541
"size_x": 0.8500000238418579,
"size_y": 0.8500000238418579,
"size_z": 0.8500000238418579
}
},
"HoglinEntity": {
@ -1289,7 +1338,7 @@
"fields": [
{
"name": "variant",
"index": 19,
"index": 18,
"type": "integer",
"default_value": 0
}
@ -1302,12 +1351,7 @@
},
"HostileEntity": {
"parent": "PathAwareEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9900000095367432,
"size_z": 0.6000000238418579
}
"fields": []
},
"HuskEntity": {
"parent": "ZombieEntity",
@ -1322,12 +1366,7 @@
},
"IllagerEntity": {
"parent": "RaiderEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9500000476837158,
"size_z": 0.6000000238418579
}
"fields": []
},
"IllusionerEntity": {
"parent": "SpellcastingIllagerEntity",
@ -1340,6 +1379,36 @@
"size_z": 0.6000000238418579
}
},
"InteractionEntity": {
"parent": "Entity",
"type": "interaction",
"translation_key": "entity.minecraft.interaction",
"fields": [
{
"name": "width",
"index": 8,
"type": "float",
"default_value": 1.0
},
{
"name": "height",
"index": 9,
"type": "float",
"default_value": 1.0
},
{
"name": "response",
"index": 10,
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 1.0,
"size_y": 1.0,
"size_z": 1.0
}
},
"IronGolemEntity": {
"parent": "GolemEntity",
"type": "iron_golem",
@ -1358,6 +1427,30 @@
"size_z": 1.399999976158142
}
},
"ItemDisplayEntity": {
"parent": "DisplayEntity",
"type": "item_display",
"translation_key": "entity.minecraft.item_display",
"fields": [
{
"name": "item",
"index": 22,
"type": "item_stack",
"default_value": "1 air"
},
{
"name": "item_display",
"index": 23,
"type": "byte",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 0.0,
"size_y": 0.0,
"size_z": 0.0
}
},
"ItemEntity": {
"parent": "Entity",
"type": "item",
@ -1435,7 +1528,7 @@
"name": "health",
"index": 9,
"type": "float",
"default_value": 30.0
"default_value": 20.0
},
{
"name": "potion_swirls_color",
@ -1467,12 +1560,7 @@
"type": "optional_block_pos",
"default_value": null
}
],
"default_bounding_box": {
"size_x": 1.0,
"size_y": 1.0,
"size_z": 1.0
}
]
},
"LlamaEntity": {
"parent": "AbstractDonkeyEntity",
@ -1481,19 +1569,19 @@
"fields": [
{
"name": "strength",
"index": 20,
"index": 19,
"type": "integer",
"default_value": 0
},
{
"name": "carpet_color",
"index": 21,
"index": 20,
"type": "integer",
"default_value": -1
},
{
"name": "variant",
"index": 22,
"index": 21,
"type": "integer",
"default_value": 0
}
@ -1546,12 +1634,7 @@
"type": "integer",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9500000476837158,
"size_z": 0.6000000238418579
}
]
},
"MinecartEntity": {
"parent": "AbstractMinecartEntity",
@ -1573,12 +1656,7 @@
"type": "byte",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 1.0,
"size_y": 1.0,
"size_z": 1.0
}
]
},
"MooshroomEntity": {
"parent": "CowEntity",
@ -1720,30 +1798,15 @@
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 0.4000000059604645,
"size_y": 0.699999988079071,
"size_z": 0.4000000059604645
}
]
},
"PathAwareEntity": {
"parent": "MobEntity",
"fields": [],
"default_bounding_box": {
"size_x": 1.0,
"size_y": 1.0,
"size_z": 1.0
}
"fields": []
},
"PatrolEntity": {
"parent": "HostileEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9500000476837158,
"size_z": 0.6000000238418579
}
"fields": []
},
"PersistentProjectileEntity": {
"parent": "ProjectileEntity",
@ -1760,12 +1823,7 @@
"type": "byte",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 0.5,
"size_y": 0.5,
"size_z": 0.5
}
]
},
"PhantomEntity": {
"parent": "FlyingEntity",
@ -1942,12 +2000,7 @@
},
"ProjectileEntity": {
"parent": "Entity",
"fields": [],
"default_bounding_box": {
"size_x": 0.25,
"size_y": 0.25,
"size_z": 0.25
}
"fields": []
},
"PufferfishEntity": {
"parent": "FishEntity",
@ -1994,12 +2047,7 @@
"type": "boolean",
"default_value": false
}
],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9500000476837158,
"size_z": 0.6000000238418579
}
]
},
"RavagerEntity": {
"parent": "RaiderEntity",
@ -2025,12 +2073,7 @@
},
"SchoolingFishEntity": {
"parent": "FishEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.699999988079071,
"size_y": 0.4000000059604645,
"size_z": 0.699999988079071
}
"fields": []
},
"SheepEntity": {
"parent": "AnimalEntity",
@ -2160,6 +2203,30 @@
"size_z": 0.3125
}
},
"SnifferEntity": {
"parent": "AnimalEntity",
"type": "sniffer",
"translation_key": "entity.minecraft.sniffer",
"fields": [
{
"name": "state",
"index": 17,
"type": "sniffer_state",
"default_value": "idling"
},
{
"name": "finish_dig_time",
"index": 18,
"type": "integer",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 1.899999976158142,
"size_y": 1.75,
"size_z": 1.899999976158142
}
},
"SnowGolemEntity": {
"parent": "GolemEntity",
"type": "snow_golem",
@ -2220,12 +2287,7 @@
"type": "byte",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 1.9500000476837158,
"size_z": 0.6000000238418579
}
]
},
"SpiderEntity": {
"parent": "HostileEntity",
@ -2240,9 +2302,9 @@
}
],
"default_bounding_box": {
"size_x": 1.399999976158142,
"size_y": 0.8999999761581421,
"size_z": 1.399999976158142
"size_x": 0.699999988079071,
"size_y": 0.5,
"size_z": 0.699999988079071
}
},
"SquidEntity": {
@ -2258,12 +2320,7 @@
},
"StorageMinecartEntity": {
"parent": "AbstractMinecartEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.9800000190734863,
"size_y": 0.699999988079071,
"size_z": 0.9800000190734863
}
"fields": []
},
"StrayEntity": {
"parent": "AbstractSkeletonEntity",
@ -2332,30 +2389,57 @@
"type": "optional_uuid",
"default_value": null
}
],
"default_bounding_box": {
"size_x": 0.6000000238418579,
"size_y": 0.699999988079071,
"size_z": 0.6000000238418579
}
]
},
"TameableShoulderEntity": {
"parent": "TameableEntity",
"fields": [],
"fields": []
},
"TextDisplayEntity": {
"parent": "DisplayEntity",
"type": "text_display",
"translation_key": "entity.minecraft.text_display",
"fields": [
{
"name": "text",
"index": 22,
"type": "text_component",
"default_value": ""
},
{
"name": "line_width",
"index": 23,
"type": "integer",
"default_value": 200
},
{
"name": "background",
"index": 24,
"type": "integer",
"default_value": 1073741824
},
{
"name": "text_opacity",
"index": 25,
"type": "byte",
"default_value": -1
},
{
"name": "text_display_flags",
"index": 26,
"type": "byte",
"default_value": 0
}
],
"default_bounding_box": {
"size_x": 0.5,
"size_y": 0.8999999761581421,
"size_z": 0.5
"size_x": 0.0,
"size_y": 0.0,
"size_z": 0.0
}
},
"ThrownEntity": {
"parent": "ProjectileEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.25,
"size_y": 0.25,
"size_z": 0.25
}
"fields": []
},
"ThrownItemEntity": {
"parent": "ThrownEntity",
@ -2366,12 +2450,7 @@
"type": "item_stack",
"default_value": "1 air"
}
],
"default_bounding_box": {
"size_x": 0.25,
"size_y": 0.25,
"size_z": 0.25
}
]
},
"TntEntity": {
"parent": "Entity",
@ -2593,12 +2672,7 @@
},
"WaterCreatureEntity": {
"parent": "PathAwareEntity",
"fields": [],
"default_bounding_box": {
"size_x": 0.8999999761581421,
"size_y": 0.6000000238418579,
"size_z": 0.8999999761581421
}
"fields": []
},
"WitchEntity": {
"parent": "RaiderEntity",
@ -2789,7 +2863,7 @@
"type": "villager_data",
"default_value": {
"type": "plains",
"profession": "fletcher",
"profession": "none",
"level": 1
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"types": {
"entity_type": {
"allay": 0,
"area_effect_cloud": 1,
"armor_stand": 2,
@ -8,121 +8,125 @@
"bat": 5,
"bee": 6,
"blaze": 7,
"boat": 8,
"chest_boat": 9,
"cat": 10,
"camel": 11,
"block_display": 8,
"boat": 9,
"camel": 10,
"cat": 11,
"cave_spider": 12,
"chicken": 13,
"cod": 14,
"cow": 15,
"creeper": 16,
"dolphin": 17,
"donkey": 18,
"dragon_fireball": 19,
"drowned": 20,
"elder_guardian": 21,
"end_crystal": 22,
"ender_dragon": 23,
"enderman": 24,
"endermite": 25,
"evoker": 26,
"evoker_fangs": 27,
"experience_orb": 28,
"eye_of_ender": 29,
"falling_block": 30,
"firework_rocket": 31,
"fox": 32,
"frog": 33,
"ghast": 34,
"giant": 35,
"glow_item_frame": 36,
"glow_squid": 37,
"goat": 38,
"guardian": 39,
"hoglin": 40,
"horse": 41,
"husk": 42,
"illusioner": 43,
"iron_golem": 44,
"item": 45,
"item_frame": 46,
"fireball": 47,
"leash_knot": 48,
"lightning_bolt": 49,
"llama": 50,
"llama_spit": 51,
"magma_cube": 52,
"marker": 53,
"minecart": 54,
"chest_minecart": 55,
"command_block_minecart": 56,
"furnace_minecart": 57,
"hopper_minecart": 58,
"spawner_minecart": 59,
"tnt_minecart": 60,
"mule": 61,
"mooshroom": 62,
"ocelot": 63,
"painting": 64,
"panda": 65,
"parrot": 66,
"phantom": 67,
"pig": 68,
"piglin": 69,
"piglin_brute": 70,
"pillager": 71,
"polar_bear": 72,
"tnt": 73,
"pufferfish": 74,
"rabbit": 75,
"ravager": 76,
"salmon": 77,
"sheep": 78,
"shulker": 79,
"shulker_bullet": 80,
"silverfish": 81,
"skeleton": 82,
"skeleton_horse": 83,
"slime": 84,
"small_fireball": 85,
"snow_golem": 86,
"snowball": 87,
"spectral_arrow": 88,
"spider": 89,
"squid": 90,
"stray": 91,
"strider": 92,
"tadpole": 93,
"egg": 94,
"ender_pearl": 95,
"experience_bottle": 96,
"potion": 97,
"trident": 98,
"trader_llama": 99,
"tropical_fish": 100,
"turtle": 101,
"vex": 102,
"villager": 103,
"vindicator": 104,
"wandering_trader": 105,
"warden": 106,
"witch": 107,
"wither": 108,
"wither_skeleton": 109,
"wither_skull": 110,
"wolf": 111,
"zoglin": 112,
"zombie": 113,
"zombie_horse": 114,
"zombie_villager": 115,
"zombified_piglin": 116,
"player": 117,
"fishing_bobber": 118
"chest_boat": 13,
"chest_minecart": 14,
"chicken": 15,
"cod": 16,
"command_block_minecart": 17,
"cow": 18,
"creeper": 19,
"dolphin": 20,
"donkey": 21,
"dragon_fireball": 22,
"drowned": 23,
"egg": 24,
"elder_guardian": 25,
"end_crystal": 26,
"ender_dragon": 27,
"ender_pearl": 28,
"enderman": 29,
"endermite": 30,
"evoker": 31,
"evoker_fangs": 32,
"experience_bottle": 33,
"experience_orb": 34,
"eye_of_ender": 35,
"falling_block": 36,
"firework_rocket": 37,
"fox": 38,
"frog": 39,
"furnace_minecart": 40,
"ghast": 41,
"giant": 42,
"glow_item_frame": 43,
"glow_squid": 44,
"goat": 45,
"guardian": 46,
"hoglin": 47,
"hopper_minecart": 48,
"horse": 49,
"husk": 50,
"illusioner": 51,
"interaction": 52,
"iron_golem": 53,
"item": 54,
"item_display": 55,
"item_frame": 56,
"fireball": 57,
"leash_knot": 58,
"lightning_bolt": 59,
"llama": 60,
"llama_spit": 61,
"magma_cube": 62,
"marker": 63,
"minecart": 64,
"mooshroom": 65,
"mule": 66,
"ocelot": 67,
"painting": 68,
"panda": 69,
"parrot": 70,
"phantom": 71,
"pig": 72,
"piglin": 73,
"piglin_brute": 74,
"pillager": 75,
"polar_bear": 76,
"potion": 77,
"pufferfish": 78,
"rabbit": 79,
"ravager": 80,
"salmon": 81,
"sheep": 82,
"shulker": 83,
"shulker_bullet": 84,
"silverfish": 85,
"skeleton": 86,
"skeleton_horse": 87,
"slime": 88,
"small_fireball": 89,
"sniffer": 90,
"snow_golem": 91,
"snowball": 92,
"spawner_minecart": 93,
"spectral_arrow": 94,
"spider": 95,
"squid": 96,
"stray": 97,
"strider": 98,
"tadpole": 99,
"text_display": 100,
"tnt": 101,
"tnt_minecart": 102,
"trader_llama": 103,
"trident": 104,
"tropical_fish": 105,
"turtle": 106,
"vex": 107,
"villager": 108,
"vindicator": 109,
"wandering_trader": 110,
"warden": 111,
"witch": 112,
"wither": 113,
"wither_skeleton": 114,
"wither_skull": 115,
"wolf": 116,
"zoglin": 117,
"zombie": 118,
"zombie_horse": 119,
"zombie_villager": 120,
"zombified_piglin": 121,
"player": 122,
"fishing_bobber": 123
},
"statuses": {
"entity_status": {
"add_sprinting_particles_or_reset_spawner_minecart_spawn_delay": 1,
"damage_from_generic_source": 2,
"play_death_sound_or_add_projectile_hit_particles": 3,
"play_attack_sound": 4,
"stop_attack": 5,
@ -153,18 +157,14 @@
"break_shield": 30,
"pull_hooked_entity": 31,
"hit_armor_stand": 32,
"damage_from_thorns": 33,
"stop_looking_at_villager": 34,
"use_totem_of_undying": 35,
"damage_from_drowning": 36,
"damage_from_fire": 37,
"add_dolphin_happy_villager_particles": 38,
"stun_ravager": 39,
"tame_ocelot_failed": 40,
"tame_ocelot_success": 41,
"add_splash_particles": 42,
"add_cloud_particles": 43,
"damage_from_berry_bush": 44,
"create_eating_particles": 45,
"add_portal_particles": 46,
"break_mainhand": 47,
@ -177,22 +177,21 @@
"drip_rich_honey": 54,
"swap_hands": 55,
"reset_wolf_shake": 56,
"damage_from_freezing": 57,
"prepare_ram": 58,
"finish_ram": 59,
"add_death_particles": 60,
"ears_twitch": 61,
"sonic_boom": 62
"sonic_boom": 62,
"field_42621": 63
},
"animations": {
"entity_animation": {
"swing_main_hand": 0,
"damage": 1,
"wake_up": 2,
"swing_off_hand": 3,
"crit": 4,
"enchanted_hit": 5
},
"villager_types": {
"villager_type": {
"desert": 0,
"jungle": 1,
"plains": 2,
@ -201,7 +200,7 @@
"swamp": 5,
"taiga": 6
},
"villager_professions": {
"villager_profession": {
"none": 0,
"armorer": 1,
"butcher": 2,
@ -218,7 +217,7 @@
"toolsmith": 13,
"weaponsmith": 14
},
"cat_variants": {
"cat_variant": {
"tabby": 0,
"black": 1,
"red": 2,
@ -231,12 +230,12 @@
"jellie": 9,
"all_black": 10
},
"frog_variants": {
"frog_variant": {
"temperate": 0,
"warm": 1,
"cold": 2
},
"painting_variants": {
"painting_variant": {
"kebab": {
"id": 0,
"width": 16,
@ -388,7 +387,7 @@
"height": 48
}
},
"facing": {
"direction": {
"down": 0,
"up": 1,
"north": 2,
@ -396,7 +395,7 @@
"west": 4,
"east": 5
},
"poses": {
"entity_pose": {
"standing": 0,
"fall_flying": 1,
"sleeping": 2,
@ -413,7 +412,7 @@
"emerging": 13,
"digging": 14
},
"particle_types": {
"particle_type": {
"ambient_entity_effect": 0,
"angry_villager": 1,
"block": 2,
@ -443,69 +442,111 @@
"firework": 26,
"fishing": 27,
"flame": 28,
"sculk_soul": 29,
"sculk_charge": 30,
"sculk_charge_pop": 31,
"soul_fire_flame": 32,
"soul": 33,
"flash": 34,
"happy_villager": 35,
"composter": 36,
"heart": 37,
"instant_effect": 38,
"item": 39,
"vibration": 40,
"item_slime": 41,
"item_snowball": 42,
"large_smoke": 43,
"lava": 44,
"mycelium": 45,
"note": 46,
"poof": 47,
"portal": 48,
"rain": 49,
"smoke": 50,
"sneeze": 51,
"spit": 52,
"squid_ink": 53,
"sweep_attack": 54,
"totem_of_undying": 55,
"underwater": 56,
"splash": 57,
"witch": 58,
"bubble_pop": 59,
"current_down": 60,
"bubble_column_up": 61,
"nautilus": 62,
"dolphin": 63,
"campfire_cosy_smoke": 64,
"campfire_signal_smoke": 65,
"dripping_honey": 66,
"falling_honey": 67,
"landing_honey": 68,
"falling_nectar": 69,
"falling_spore_blossom": 70,
"ash": 71,
"crimson_spore": 72,
"warped_spore": 73,
"spore_blossom_air": 74,
"dripping_obsidian_tear": 75,
"falling_obsidian_tear": 76,
"landing_obsidian_tear": 77,
"reverse_portal": 78,
"white_ash": 79,
"small_flame": 80,
"snowflake": 81,
"dripping_dripstone_lava": 82,
"falling_dripstone_lava": 83,
"dripping_dripstone_water": 84,
"falling_dripstone_water": 85,
"glow_squid_ink": 86,
"glow": 87,
"wax_on": 88,
"wax_off": 89,
"electric_spark": 90,
"scrape": 91,
"shriek": 92
"dripping_cherry_leaves": 29,
"falling_cherry_leaves": 30,
"landing_cherry_leaves": 31,
"sculk_soul": 32,
"sculk_charge": 33,
"sculk_charge_pop": 34,
"soul_fire_flame": 35,
"soul": 36,
"flash": 37,
"happy_villager": 38,
"composter": 39,
"heart": 40,
"instant_effect": 41,
"item": 42,
"vibration": 43,
"item_slime": 44,
"item_snowball": 45,
"large_smoke": 46,
"lava": 47,
"mycelium": 48,
"note": 49,
"poof": 50,
"portal": 51,
"rain": 52,
"smoke": 53,
"sneeze": 54,
"spit": 55,
"squid_ink": 56,
"sweep_attack": 57,
"totem_of_undying": 58,
"underwater": 59,
"splash": 60,
"witch": 61,
"bubble_pop": 62,
"current_down": 63,
"bubble_column_up": 64,
"nautilus": 65,
"dolphin": 66,
"campfire_cosy_smoke": 67,
"campfire_signal_smoke": 68,
"dripping_honey": 69,
"falling_honey": 70,
"landing_honey": 71,
"falling_nectar": 72,
"falling_spore_blossom": 73,
"ash": 74,
"crimson_spore": 75,
"warped_spore": 76,
"spore_blossom_air": 77,
"dripping_obsidian_tear": 78,
"falling_obsidian_tear": 79,
"landing_obsidian_tear": 80,
"reverse_portal": 81,
"white_ash": 82,
"small_flame": 83,
"snowflake": 84,
"dripping_dripstone_lava": 85,
"falling_dripstone_lava": 86,
"dripping_dripstone_water": 87,
"falling_dripstone_water": 88,
"glow_squid_ink": 89,
"glow": 90,
"wax_on": 91,
"wax_off": 92,
"electric_spark": 93,
"scrape": 94,
"shriek": 95
},
"sniffer_state": {
"idling": 0,
"feeling_happy": 1,
"scenting": 2,
"sniffing": 3,
"searching": 4,
"digging": 5,
"rising": 6
},
"tracked_data_handler": {
"byte": 0,
"integer": 1,
"long": 2,
"float": 3,
"string": 4,
"text_component": 5,
"optional_text_component": 6,
"item_stack": 7,
"block_state": 14,
"optional_block_state": 15,
"boolean": 8,
"particle": 17,
"rotation": 9,
"block_pos": 10,
"optional_block_pos": 11,
"facing": 12,
"optional_uuid": 13,
"optional_global_pos": 23,
"nbt_compound": 16,
"villager_data": 18,
"optional_int": 19,
"entity_pose": 20,
"cat_variant": 21,
"frog_variant": 22,
"painting_variant": 24,
"sniffer_state": 25,
"vector3f": 26,
"quaternionf": 27
}
}

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
This is a Fabric mod for Minecraft that extracts data about different things in Minecraft, like blocks, packets, etc. All the extracted data is stored in the sibling `extracted` folder.
### How to use
## How to use
Here's how to regenerate the contents of `extracted`.
@ -12,4 +12,14 @@ From this directory, run the following
./gradlew runServer
```
This will run the extractor and immediately exit, outputting the files that are listed in the logs. These need to be manually moved to `extracted` to be committed.
This will run the extractor and immediately exit, outputting the files that are listed in the logs.
Next, run `copy_extractor_output.sh`. This copies the files to `extracted` so that they can be comitted.
```sh
./copy_extractor_output.sh
```
## Contributing
Run `./gradlew genSources` to generate Minecraft Java source files for your IDE.

View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
cd "$(dirname "$0")" || return
rm ../extracted/*.json
cp run/valence_extractor_output/*.json ../extracted/

View file

@ -2,12 +2,12 @@
org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.19.3
yarn_mappings=1.19.3+build.3
loader_version=0.14.11
minecraft_version=1.19.4
yarn_mappings=1.19.4+build.1
loader_version=0.14.18
# Mod Properties
mod_version=1.0.0
maven_group=dev.00a
archives_base_name=valence-extractor
# Dependencies
fabric_version=0.73.2+1.19.3
fabric_version=0.76.0+1.19.4

View file

@ -56,8 +56,8 @@ public class DummyWorld extends World {
}
}
private DummyWorld(MutableWorldProperties properties, RegistryKey<World> registryRef, RegistryEntry<DimensionType> dimension, Supplier<Profiler> profiler, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) {
super(properties, registryRef, dimension, profiler, isClient, debugWorld, seed, maxChainedNeighborUpdates);
private DummyWorld(MutableWorldProperties properties, RegistryKey<World> registryRef, DynamicRegistryManager registryManager, RegistryEntry<DimensionType> dimension, Supplier<Profiler> profiler, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) {
super(properties, registryRef, registryManager, dimension, profiler, isClient, debugWorld, seed, maxChainedNeighborUpdates);
}
@Override

View file

@ -41,7 +41,7 @@ public class Main implements ModInitializer {
new Blocks(),
new Enchants(),
new Entities(),
new EntityData(),
new Misc(),
new Items(),
new Packets(),
new Sounds(),

View file

@ -1,6 +1,8 @@
package rs.valence.extractor.extractors;
import com.google.gson.*;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityType;
@ -9,6 +11,7 @@ import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.passive.CatVariant;
import net.minecraft.entity.passive.FrogVariant;
import net.minecraft.entity.passive.SnifferEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.registry.Registries;
@ -19,6 +22,9 @@ import net.minecraft.util.math.EulerAngle;
import net.minecraft.util.math.GlobalPos;
import net.minecraft.village.VillagerData;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import rs.valence.extractor.ClassComparator;
import rs.valence.extractor.DummyPlayerEntity;
import rs.valence.extractor.DummyWorld;
@ -50,7 +56,8 @@ public class Entities implements Main.Extractor {
// TODO: return text as json element.
return new Pair<>("text_component", new JsonPrimitive(((Text) val).getString()));
} else if (handler == TrackedDataHandlerRegistry.OPTIONAL_TEXT_COMPONENT) {
var res = ((Optional<?>) val).map(o -> (JsonElement) new JsonPrimitive(((Text) o).getString())).orElse(JsonNull.INSTANCE);
var res = ((Optional<?>) val).map(o -> (JsonElement) new JsonPrimitive(((Text) o).getString()))
.orElse(JsonNull.INSTANCE);
return new Pair<>("optional_text_component", res);
} else if (handler == TrackedDataHandlerRegistry.ITEM_STACK) {
// TODO
@ -83,11 +90,17 @@ public class Entities implements Main.Extractor {
} else if (handler == TrackedDataHandlerRegistry.FACING) {
return new Pair<>("facing", new JsonPrimitive(val.toString()));
} else if (handler == TrackedDataHandlerRegistry.OPTIONAL_UUID) {
var res = ((Optional<?>) val).map(o -> (JsonElement) new JsonPrimitive(o.toString())).orElse(JsonNull.INSTANCE);
var res = ((Optional<?>) val).map(o -> (JsonElement) new JsonPrimitive(o.toString()))
.orElse(JsonNull.INSTANCE);
return new Pair<>("optional_uuid", res);
} else if (handler == TrackedDataHandlerRegistry.BLOCK_STATE) {
// TODO: get raw block state ID.
var state = (BlockState) val;
return new Pair<>("block_state", new JsonPrimitive(state.toString()));
} else if (handler == TrackedDataHandlerRegistry.OPTIONAL_BLOCK_STATE) {
// TODO: get raw block state ID.
var res = ((Optional<?>) val).map(o -> (JsonElement) new JsonPrimitive(o.toString())).orElse(JsonNull.INSTANCE);
var res = ((Optional<?>) val).map(o -> (JsonElement) new JsonPrimitive(o.toString()))
.orElse(JsonNull.INSTANCE);
return new Pair<>("optional_block_state", res);
} else if (handler == TrackedDataHandlerRegistry.NBT_COMPOUND) {
// TODO: base64 binary representation or SNBT?
@ -110,9 +123,11 @@ public class Entities implements Main.Extractor {
} else if (handler == TrackedDataHandlerRegistry.ENTITY_POSE) {
return new Pair<>("entity_pose", new JsonPrimitive(((EntityPose) val).name().toLowerCase(Locale.ROOT)));
} else if (handler == TrackedDataHandlerRegistry.CAT_VARIANT) {
return new Pair<>("cat_variant", new JsonPrimitive(Registries.CAT_VARIANT.getId((CatVariant) val).getPath()));
return new Pair<>("cat_variant",
new JsonPrimitive(Registries.CAT_VARIANT.getId((CatVariant) val).getPath()));
} else if (handler == TrackedDataHandlerRegistry.FROG_VARIANT) {
return new Pair<>("frog_variant", new JsonPrimitive(Registries.FROG_VARIANT.getId((FrogVariant) val).getPath()));
return new Pair<>("frog_variant",
new JsonPrimitive(Registries.FROG_VARIANT.getId((FrogVariant) val).getPath()));
} else if (handler == TrackedDataHandlerRegistry.OPTIONAL_GLOBAL_POS) {
return new Pair<>("optional_global_pos", ((Optional<?>) val).map(o -> {
var gp = (GlobalPos) o;
@ -130,19 +145,29 @@ public class Entities implements Main.Extractor {
} else if (handler == TrackedDataHandlerRegistry.PAINTING_VARIANT) {
var variant = ((RegistryEntry<?>) val).getKey().map(k -> k.getValue().getPath()).orElse("");
return new Pair<>("painting_variant", new JsonPrimitive(variant));
} else if (handler == TrackedDataHandlerRegistry.SNIFFER_STATE) {
return new Pair<>("sniffer_state", new JsonPrimitive(((SnifferEntity.State) val).name().toLowerCase(Locale.ROOT)));
} else if (handler == TrackedDataHandlerRegistry.VECTOR3F) {
var vec = (Vector3f) val;
var json = new JsonObject();
json.addProperty("x", vec.x);
json.addProperty("y", vec.y);
json.addProperty("z", vec.z);
return new Pair<>("vector3f", json);
} else if (handler == TrackedDataHandlerRegistry.QUATERNIONF) {
var quat = (Quaternionf) val;
var json = new JsonObject();
json.addProperty("x", quat.x);
json.addProperty("y", quat.y);
json.addProperty("z", quat.z);
json.addProperty("w", quat.w);
return new Pair<>("quaternionf", json);
} else {
throw new IllegalArgumentException("Unexpected tracked data type");
throw new IllegalArgumentException(
"Unexpected tracked handler of ID " + TrackedDataHandlerRegistry.getId(handler));
}
}
private static Bit bit(String name, int index) {
return new Bit(name, index);
}
private static Map.Entry<String, Bit[]> bits(String fieldName, Bit... bits) {
return Map.entry(fieldName, bits);
}
@Override
public String fileName() {
return "entities.json";
@ -155,7 +180,8 @@ public class Entities implements Main.Extractor {
final var entityClassToType = new HashMap<Class<? extends Entity>, EntityType<?>>();
for (var f : EntityType.class.getFields()) {
if (f.getType().equals(EntityType.class)) {
var entityClass = (Class<? extends Entity>) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0];
var entityClass = (Class<? extends Entity>) ((ParameterizedType) f.getGenericType())
.getActualTypeArguments()[0];
var entityType = (EntityType<?>) f.get(null);
entityClassToType.put(entityClass, entityType);
@ -169,13 +195,19 @@ public class Entities implements Main.Extractor {
for (var entry : entityClassToType.entrySet()) {
var entityClass = entry.getKey();
@Nullable var entityType = entry.getValue();
@Nullable
var entityType = entry.getValue();
assert entityType != null;
// While we can use the tracked data registry and reflection to get the tracked fields on entities, we won't know what their default values are because they are assigned in the entity's constructor.
// To obtain this, we create a dummy world to spawn the entities into and read the data tracker field from the base entity class.
// We also handle player entities specially since they cannot be spawned with EntityType#create.
final var entityInstance = entityType.equals(EntityType.PLAYER) ? DummyPlayerEntity.INSTANCE : entityType.create(DummyWorld.INSTANCE);
// While we can use the tracked data registry and reflection to get the tracked
// fields on entities, we won't know what their default values are because they
// are assigned in the entity's constructor.
// To obtain this, we create a dummy world to spawn the entities into and read
// the data tracker field from the base entity class.
// We also handle player entities specially since they cannot be spawned with
// EntityType#create.
final var entityInstance = entityType.equals(EntityType.PLAYER) ? DummyPlayerEntity.INSTANCE
: entityType.create(DummyWorld.INSTANCE);
final var dataTracker = (DataTracker) dataTrackerField.get(entityInstance);
@ -217,7 +249,7 @@ public class Entities implements Main.Extractor {
entityJson.add("fields", fieldsJson);
var bb = entityInstance.getBoundingBox();
if (bb != null) {
if (bb != null && entityType != null) {
var boundingBoxJson = new JsonObject();
boundingBoxJson.addProperty("size_x", bb.getXLength());
@ -245,7 +277,4 @@ public class Entities implements Main.Extractor {
return entitiesJson;
}
private record Bit(String name, int index) {
}
}

View file

@ -1,107 +0,0 @@
package rs.valence.extractor.extractors;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityStatuses;
import net.minecraft.network.packet.s2c.play.EntityAnimationS2CPacket;
import net.minecraft.registry.Registries;
import net.minecraft.util.math.Direction;
import rs.valence.extractor.Main;
import java.lang.reflect.Modifier;
import java.util.Locale;
public class EntityData implements Main.Extractor {
@Override
public String fileName() {
return "entity_data.json";
}
@Override
public JsonElement extract() throws Exception {
var dataJson = new JsonObject();
var typesJson = new JsonObject();
for (var type : Registries.ENTITY_TYPE) {
typesJson.addProperty(Registries.ENTITY_TYPE.getId(type).getPath(), Registries.ENTITY_TYPE.getRawId(type));
}
dataJson.add("types", typesJson);
var statusesJson = new JsonObject();
for (var field : EntityStatuses.class.getDeclaredFields()) {
if (field.canAccess(null) && field.get(null) instanceof Byte code) {
if (field.getName().equals("field_30030")) {
statusesJson.addProperty("stop_attack", code);
} else {
statusesJson.addProperty(field.getName().toLowerCase(Locale.ROOT), code);
}
}
}
dataJson.add("statuses", statusesJson);
var animationsJson = new JsonObject();
for (var field : EntityAnimationS2CPacket.class.getDeclaredFields()) {
field.setAccessible(true);
if (Modifier.isStatic(field.getModifiers()) && field.canAccess(null) && field.get(null) instanceof Integer i) {
animationsJson.addProperty(field.getName().toLowerCase(Locale.ROOT), i);
}
}
dataJson.add("animations", animationsJson);
var villagerTypesJson = new JsonObject();
for (var type : Registries.VILLAGER_TYPE) {
villagerTypesJson.addProperty(Registries.VILLAGER_TYPE.getId(type).getPath(), Registries.VILLAGER_TYPE.getRawId(type));
}
dataJson.add("villager_types", villagerTypesJson);
var villagerProfessionsJson = new JsonObject();
for (var profession : Registries.VILLAGER_PROFESSION) {
villagerProfessionsJson.addProperty(profession.id(), Registries.VILLAGER_PROFESSION.getRawId(profession));
}
dataJson.add("villager_professions", villagerProfessionsJson);
var catVariantsJson = new JsonObject();
for (var variant : Registries.CAT_VARIANT) {
catVariantsJson.addProperty(Registries.CAT_VARIANT.getId(variant).getPath(), Registries.CAT_VARIANT.getRawId(variant));
}
dataJson.add("cat_variants", catVariantsJson);
var frogVariantsJson = new JsonObject();
for (var variant : Registries.FROG_VARIANT) {
frogVariantsJson.addProperty(Registries.FROG_VARIANT.getId(variant).getPath(), Registries.FROG_VARIANT.getRawId(variant));
}
dataJson.add("frog_variants", frogVariantsJson);
var paintingVariantsJson = new JsonObject();
for (var variant : Registries.PAINTING_VARIANT) {
var variantJson = new JsonObject();
variantJson.addProperty("id", Registries.PAINTING_VARIANT.getRawId(variant));
variantJson.addProperty("width", variant.getWidth());
variantJson.addProperty("height", variant.getHeight());
paintingVariantsJson.add(Registries.PAINTING_VARIANT.getId(variant).getPath(), variantJson);
}
dataJson.add("painting_variants", paintingVariantsJson);
var facingJson = new JsonObject();
for (var dir : Direction.values()) {
facingJson.addProperty(dir.getName(), dir.getId());
}
dataJson.add("facing", facingJson);
var posesJson = new JsonObject();
var poses = EntityPose.values();
for (int i = 0; i < poses.length; i++) {
posesJson.addProperty(poses[i].name().toLowerCase(Locale.ROOT), i);
}
dataJson.add("poses", posesJson);
var particleTypesJson = new JsonObject();
for (var type : Registries.PARTICLE_TYPE) {
particleTypesJson.addProperty(Registries.PARTICLE_TYPE.getId(type).getPath(), Registries.PARTICLE_TYPE.getRawId(type));
}
dataJson.add("particle_types", particleTypesJson);
return dataJson;
}
}

View file

@ -0,0 +1,134 @@
package rs.valence.extractor.extractors;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityStatuses;
import net.minecraft.entity.data.TrackedDataHandler;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.passive.SnifferEntity;
import net.minecraft.network.packet.s2c.play.EntityAnimationS2CPacket;
import net.minecraft.registry.Registries;
import net.minecraft.util.math.Direction;
import rs.valence.extractor.Main;
import java.lang.reflect.Modifier;
import java.util.Locale;
public class Misc implements Main.Extractor {
@Override
public String fileName() {
return "misc.json";
}
@Override
public JsonElement extract() throws Exception {
var miscJson = new JsonObject();
var entityTypeJson = new JsonObject();
for (var type : Registries.ENTITY_TYPE) {
entityTypeJson.addProperty(Registries.ENTITY_TYPE.getId(type).getPath(),
Registries.ENTITY_TYPE.getRawId(type));
}
miscJson.add("entity_type", entityTypeJson);
var entityStatusJson = new JsonObject();
for (var field : EntityStatuses.class.getDeclaredFields()) {
if (field.canAccess(null) && field.get(null) instanceof Byte code) {
if (field.getName().equals("field_30030")) {
entityStatusJson.addProperty("stop_attack", code);
} else {
entityStatusJson.addProperty(field.getName().toLowerCase(Locale.ROOT), code);
}
}
}
miscJson.add("entity_status", entityStatusJson);
var entityAnimationJson = new JsonObject();
for (var field : EntityAnimationS2CPacket.class.getDeclaredFields()) {
field.setAccessible(true);
if (Modifier.isStatic(field.getModifiers()) && field.canAccess(null)
&& field.get(null) instanceof Integer i) {
entityAnimationJson.addProperty(field.getName().toLowerCase(Locale.ROOT), i);
}
}
miscJson.add("entity_animation", entityAnimationJson);
var villagerTypeJson = new JsonObject();
for (var type : Registries.VILLAGER_TYPE) {
villagerTypeJson.addProperty(Registries.VILLAGER_TYPE.getId(type).getPath(),
Registries.VILLAGER_TYPE.getRawId(type));
}
miscJson.add("villager_type", villagerTypeJson);
var villagerProfessionJson = new JsonObject();
for (var profession : Registries.VILLAGER_PROFESSION) {
villagerProfessionJson.addProperty(profession.id(), Registries.VILLAGER_PROFESSION.getRawId(profession));
}
miscJson.add("villager_profession", villagerProfessionJson);
var catVariantJson = new JsonObject();
for (var variant : Registries.CAT_VARIANT) {
catVariantJson.addProperty(Registries.CAT_VARIANT.getId(variant).getPath(),
Registries.CAT_VARIANT.getRawId(variant));
}
miscJson.add("cat_variant", catVariantJson);
var frogVariantJson = new JsonObject();
for (var variant : Registries.FROG_VARIANT) {
frogVariantJson.addProperty(Registries.FROG_VARIANT.getId(variant).getPath(),
Registries.FROG_VARIANT.getRawId(variant));
}
miscJson.add("frog_variant", frogVariantJson);
var paintingVariantJson = new JsonObject();
for (var variant : Registries.PAINTING_VARIANT) {
var variantJson = new JsonObject();
variantJson.addProperty("id", Registries.PAINTING_VARIANT.getRawId(variant));
variantJson.addProperty("width", variant.getWidth());
variantJson.addProperty("height", variant.getHeight());
paintingVariantJson.add(Registries.PAINTING_VARIANT.getId(variant).getPath(), variantJson);
}
miscJson.add("painting_variant", paintingVariantJson);
var directionJson = new JsonObject();
for (var dir : Direction.values()) {
directionJson.addProperty(dir.getName(), dir.getId());
}
miscJson.add("direction", directionJson);
var entityPoseJson = new JsonObject();
var poses = EntityPose.values();
for (int i = 0; i < poses.length; i++) {
entityPoseJson.addProperty(poses[i].name().toLowerCase(Locale.ROOT), i);
}
miscJson.add("entity_pose", entityPoseJson);
var particleTypesJson = new JsonObject();
for (var type : Registries.PARTICLE_TYPE) {
particleTypesJson.addProperty(Registries.PARTICLE_TYPE.getId(type).getPath(),
Registries.PARTICLE_TYPE.getRawId(type));
}
miscJson.add("particle_type", particleTypesJson);
var snifferStateJson = new JsonObject();
for (var state : SnifferEntity.State.values()) {
snifferStateJson.addProperty(state.name().toLowerCase(Locale.ROOT), state.ordinal());
}
miscJson.add("sniffer_state", snifferStateJson);
var trackedDataHandlerJson = new JsonObject();
for (var field : TrackedDataHandlerRegistry.class.getDeclaredFields()) {
field.setAccessible(true);
if (Modifier.isStatic(field.getModifiers()) && field.get(null) instanceof TrackedDataHandler<?> handler) {
var name = field.getName().toLowerCase(Locale.ROOT);
var id = TrackedDataHandlerRegistry.getId(handler);
trackedDataHandlerJson.addProperty(name, id);
}
}
miscJson.add("tracked_data_handler", trackedDataHandlerJson);
return miscJson;
}
}

View file

@ -18,29 +18,23 @@ public class Packets implements Main.Extractor {
@Override
public JsonElement extract() {
var packetsJson = new JsonObject();
var packetsJson = new JsonArray();
for (var side : NetworkSide.values()) {
var sideJson = new JsonObject();
for (var state : NetworkState.values()) {
var stateJson = new JsonArray();
var map = state.getPacketIdToPacketMap(side);
for (var id : new TreeSet<>(map.keySet())) {
var packetJson = new JsonObject();
packetJson.addProperty("name", map.get(id.intValue()).getSimpleName());
packetJson.addProperty("side", side.name().toLowerCase(Locale.ROOT));
packetJson.addProperty("state", state.name().toLowerCase(Locale.ROOT));
packetJson.addProperty("id", id);
stateJson.add(packetJson);
packetsJson.add(packetJson);
}
sideJson.add(state.name().toLowerCase(Locale.ROOT), stateJson);
}
packetsJson.add(side.name().toLowerCase(Locale.ROOT), sideJson);
}
return packetsJson;