From 65646c86c4a5e1780900d42030c6a8924dd0d893 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Fri, 21 Aug 2026 13:39:10 +0700 Subject: [PATCH 01/29] spawn egg --- steel-core/src/behavior/items/mod.rs | 2 + .../items/place_on_water_block_item.rs | 2 +- steel-core/src/behavior/items/spawn_egg.rs | 197 +++++++++++++ steel-core/src/entity/entity/mod.rs | 104 +++++++ steel-core/src/entity/mob/mod.rs | 16 +- steel-core/src/entity/mod.rs | 6 +- steel-core/src/entity/spawn.rs | 278 +++++++++++++++++- 7 files changed, 601 insertions(+), 4 deletions(-) create mode 100644 steel-core/src/behavior/items/spawn_egg.rs diff --git a/steel-core/src/behavior/items/mod.rs b/steel-core/src/behavior/items/mod.rs index aafc8d060ba..e14acd2aa5d 100644 --- a/steel-core/src/behavior/items/mod.rs +++ b/steel-core/src/behavior/items/mod.rs @@ -29,6 +29,7 @@ mod potion; mod shield; mod shovel; mod sign_item; +mod spawn_egg; mod standing_and_wall_block_item; mod throwable_potion; mod tipped_arrow; @@ -60,6 +61,7 @@ pub use potion::PotionItem; pub use shield::ShieldItem; pub use shovel::ShovelItem; pub use sign_item::{HangingSignItem, SignItem}; +pub use spawn_egg::SpawnEggItem; pub use standing_and_wall_block_item::StandingAndWallBlockItem; pub use throwable_potion::{LingeringPotionItem, SplashPotionItem}; pub use tipped_arrow::TippedArrowItem; diff --git a/steel-core/src/behavior/items/place_on_water_block_item.rs b/steel-core/src/behavior/items/place_on_water_block_item.rs index 314c1c84ac0..51c63516e13 100644 --- a/steel-core/src/behavior/items/place_on_water_block_item.rs +++ b/steel-core/src/behavior/items/place_on_water_block_item.rs @@ -48,7 +48,7 @@ impl ItemBehavior for PlaceOnWaterBlockItem { } } -fn get_player_pov_hit_result( +pub(super) fn get_player_pov_hit_result( world: &Arc, player: &Player, fluid: ClipFluid, diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs new file mode 100644 index 00000000000..ef2e014b0fc --- /dev/null +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -0,0 +1,197 @@ +//! Data-driven vanilla spawn-egg behavior. + +use steel_macros::item_behavior; +use steel_registry::blocks::block_state_ext::BlockStateExt as _; +use steel_registry::data_components::vanilla_components::ENTITY_DATA; +use steel_registry::entity_type::EntityTypeRef; +use steel_registry::item_stack::ItemStack; +use steel_registry::{vanilla_blocks, vanilla_game_events}; +use steel_utils::types::InteractionHand; + +use super::place_on_water_block_item::get_player_pov_hit_result; +use crate::behavior::{ + BLOCK_BEHAVIORS, BlockCollisionContext, InteractionResult, ItemBehavior, UseItemContext, + UseOnContext, +}; +use crate::entity::{ + AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, LivingEntity, Mob, + SharedEntity, add_spawned_entity, apply_item_stack_components, create_entity_instance, + spawn_entity, +}; +use crate::world::ClipFluid; +use crate::world::game_event::GameEventContext; + +/// Behavior for every data-driven `SpawnEggItem` registry entry. +#[item_behavior(class = "SpawnEggItem")] +pub struct SpawnEggItem; + +impl SpawnEggItem { + fn entity_type(stack: &ItemStack) -> Option { + stack.get(ENTITY_DATA).map(|data| data.entity_type()) + } + + fn spawn_mob( + world: &std::sync::Arc, + player: &crate::player::Player, + inventory: &crate::behavior::InventoryAccess, + stack: &ItemStack, + spawn_pos: steel_utils::BlockPos, + try_move_down: bool, + moved_up: bool, + ) -> InteractionResult { + let Some(entity_type) = Self::entity_type(stack) else { + return InteractionResult::Fail; + }; + + let request = EntitySpawnRequest { + entity_type, + placement: EntitySpawnPlacement::Block { + pos: spawn_pos, + try_move_down, + moved_up, + }, + reason: EntitySpawnReason::SpawnItemUse, + finalize_spawn: true, + play_ambient_sound: true, + item_stack: Some(stack), + }; + if spawn_entity(world, request).is_err() { + return InteractionResult::Fail; + } + + inventory.with_item(|item| item.shrink(1)); + world.game_event( + &vanilla_game_events::ENTITY_PLACE, + spawn_pos, + &GameEventContext::new(Some(player), None), + ); + InteractionResult::Success + } + + /// Runs the interaction before normal mob handling. + pub(crate) fn interact_with_mob( + stack: &mut ItemStack, + parent: &M, + ) -> InteractionResult { + if Self::spawn_offspring(stack, parent).is_none() { + return InteractionResult::Pass; + } + stack.shrink(1); + InteractionResult::SuccessServer + } + + fn spawn_offspring(stack: &ItemStack, parent: &M) -> Option { + let entity_type = Self::entity_type(stack)?; + if entity_type != parent.entity_type() { + return None; + } + + let world = parent.level()?; + let offspring = if let Some(animal) = parent.as_animal() { + animal.get_breed_offspring(&world, animal)? + } else { + create_entity_instance(&world, entity_type, parent.position()).ok()? + }; + + let ageable = offspring.as_ageable_mob()?; + ageable.set_baby(true); + if !AgeableMob::is_baby(ageable) { + return None; + } + + offspring.base().set_position_local(parent.position()); + offspring.set_rotation((0.0, 0.0)); + offspring.set_old_position_to_current(); + offspring.base().set_old_rotation_to_current(); + apply_item_stack_components(&offspring, stack).ok()?; + add_spawned_entity(&world, offspring.clone()).ok()?; + Some(offspring) + } +} + +impl ItemBehavior for SpawnEggItem { + fn use_on(&self, context: &mut UseOnContext) -> InteractionResult { + let stack = context.inv.with_item(|item| item.clone()); + if Self::entity_type(&stack).is_none() { + return InteractionResult::Fail; + } + + let clicked_pos = context.hit_result.block_pos; + let clicked_state = context.world.get_block_state(clicked_pos); + if clicked_state.get_block() == &vanilla_blocks::SPAWNER { + // Spawner block-entity mutation is a separate foundation; do not + // incorrectly create a mob beside a spawner in its place. + return InteractionResult::Fail; + } + + let clicked_face = context.hit_result.direction; + let shape = BLOCK_BEHAVIORS + .get_behavior(clicked_state.get_block()) + .get_collision_shape( + clicked_state, + context.world.as_ref(), + clicked_pos, + BlockCollisionContext::empty(), + ); + let spawn_pos = if shape.is_empty() { + clicked_pos + } else { + clicked_face.relative(clicked_pos) + }; + + Self::spawn_mob( + context.world, + context.player, + &context.inv, + &stack, + spawn_pos, + true, + spawn_pos != clicked_pos && clicked_face == steel_utils::Direction::Up, + ) + } + + fn use_item(&self, context: &mut UseItemContext) -> InteractionResult { + let hit_result = + get_player_pov_hit_result(context.world, context.player, ClipFluid::SourceOnly); + if hit_result.miss { + return InteractionResult::Pass; + } + + let stack = context.inv.with_item(|item| item.clone()); + if Self::entity_type(&stack).is_none() { + return InteractionResult::Fail; + } + + let pos = hit_result.block_pos; + let block = context.world.get_block_state(pos).get_block(); + if block != &vanilla_blocks::WATER && block != &vanilla_blocks::LAVA { + return InteractionResult::Pass; + } + if !context.world.may_interact(context.player, pos) { + return InteractionResult::Fail; + } + + Self::spawn_mob( + context.world, + context.player, + &context.inv, + &stack, + pos, + false, + false, + ) + } + + fn interact_living_entity( + &self, + stack: &mut ItemStack, + _player: &crate::player::Player, + target: &dyn LivingEntity, + _hand: InteractionHand, + ) -> InteractionResult { + let Some(parent) = target.as_mob() else { + return InteractionResult::Pass; + }; + Self::interact_with_mob(stack, parent) + } +} diff --git a/steel-core/src/entity/entity/mod.rs b/steel-core/src/entity/entity/mod.rs index 628d98d7343..7ca08693001 100644 --- a/steel-core/src/entity/entity/mod.rs +++ b/steel-core/src/entity/entity/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use super::*; /// Final state accepted from a client-authored movement packet. @@ -3467,6 +3469,108 @@ pub trait Entity: EntityEventSource + ErasedType + Send + Sync + 'static { /// Mirrors vanilla's `Entity.readAdditionalSaveData()`. fn load_additional(&self, _nbt: BorrowedNbtCompoundView<'_, '_>) {} + /// Applies the merged entity data used by vanilla `TypedEntityData.loadInto`. + /// + /// Spawn-item data is merged into the entity's current save state before it + /// is loaded. Keeping that merge at the entity boundary preserves defaults + /// and entity-specific state when the component only overrides one field. + fn apply_spawn_data(&self, nbt: BorrowedNbtCompoundView<'_, '_>) { + let read_int = |key: &str| { + nbt.int(key) + .or_else(|| nbt.short(key).map(i32::from)) + .or_else(|| nbt.byte(key).map(i32::from)) + }; + + if let Some(values) = nbt.list("Pos").and_then(|list| list.doubles()) + && values.len() >= 3 + { + let position = DVec3::new(values[0], values[1], values[2]); + if position.is_finite() { + self.base().set_position_local(position); + } + } + + if let Some(values) = nbt.list("Motion").and_then(|list| list.doubles()) + && values.len() >= 3 + { + self.set_velocity(DVec3::new(values[0], values[1], values[2])); + } + + if let Some(values) = nbt.list("Rotation").and_then(|list| list.floats()) + && values.len() >= 2 + { + self.set_rotation((values[0], values[1])); + if let Some(living) = self.as_living_entity() { + living.set_y_head_rot(values[0]); + living.set_y_body_rot(values[0]); + } + } + + if let Some(fall_distance) = nbt + .double("fall_distance") + .or_else(|| nbt.double("FallDistance")) + { + self.set_fall_distance(fall_distance); + } + + if let Some(on_ground) = nbt.byte("OnGround") { + self.set_on_ground(on_ground != 0); + } + + let mut save_data = self.base().save_data(); + if let Some(air_supply) = read_int("Air") { + save_data.air_supply = air_supply; + } + if let Some(portal_cooldown) = read_int("PortalCooldown") { + save_data.portal_cooldown = portal_cooldown; + } + if let Some(no_gravity) = nbt.byte("NoGravity") { + save_data.no_gravity = no_gravity != 0; + } + if let Some(invulnerable) = nbt.byte("Invulnerable") { + save_data.invulnerable = invulnerable != 0; + } + if let Some(custom_name) = nbt + .get("CustomName") + .and_then(|tag| TextComponent::from_nbt(&tag.to_owned())) + { + save_data.custom_name = Some(custom_name); + } + if let Some(custom_name_visible) = nbt.byte("CustomNameVisible") { + save_data.custom_name_visible = custom_name_visible != 0; + } + if let Some(silent) = nbt.byte("Silent") { + save_data.silent = silent != 0; + } + if let Some(glowing) = nbt.byte("Glowing") { + save_data.glowing = glowing != 0; + } + if let Some(tags) = nbt.list("Tags").and_then(|list| list.strings()) { + save_data.tags = tags + .iter() + .take(MAX_ENTITY_TAGS) + .map(|tag| tag.to_str().into_owned()) + .collect::>(); + } + if let Some(custom_data) = nbt.compound("data") { + save_data.custom_data = custom_data.to_owned(); + } + self.base().replace_save_data(save_data); + + if let Some(remaining_fire_ticks) = read_int("Fire") { + self.set_remaining_fire_ticks(remaining_fire_ticks); + } + if let Some(ticks_frozen) = read_int("TicksFrozen") { + self.set_ticks_frozen(ticks_frozen); + } + if let Some(has_visual_fire) = nbt.byte("HasVisualFire") { + self.base().set_visual_fire(has_visual_fire != 0); + } + + self.load_additional(nbt); + self.sync_base_entity_data(); + } + /// Applies damage to this entity. fn hurt(&self, world: &World, source: &DamageSource, amount: f32) -> bool { let Some(living) = self.as_living_entity() else { diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index d54739fc8c7..06dd358c9a2 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -502,7 +502,21 @@ pub trait Mob: LivingEntity { return InteractionResult::Pass; } - // TODO: Handle name tags and spawn eggs once item-on-entity behavior exists. + let spawn_egg_result = { + let mut item_stack = player + .inventory + .lock() + .get_item_in_hand(hand) + .copy_with_count(1); + crate::behavior::items::SpawnEggItem::interact_with_mob(&mut item_stack, self) + }; + if spawn_egg_result.consumes_action() { + if spawn_egg_result == InteractionResult::SuccessServer { + player.inventory.lock().get_item_in_hand_mut(hand).shrink(1); + } + return spawn_egg_result; + } + let interaction_result = self.interact_entity(player, hand, location); if interaction_result != InteractionResult::Pass { return interaction_result; diff --git a/steel-core/src/entity/mod.rs b/steel-core/src/entity/mod.rs index 8a920182a82..9f07e7eefea 100644 --- a/steel-core/src/entity/mod.rs +++ b/steel-core/src/entity/mod.rs @@ -814,7 +814,11 @@ pub use projectile::{ compute_margin, get_hit_result_on_view_vector, }; pub use registry::{ENTITIES, EntityLoadRequest, EntityRegistry, init_entities}; -pub(crate) use spawn::{AgeableMobGroupData, EntitySpawnReason, SpawnGroupData}; +pub(crate) use spawn::{ + AgeableMobGroupData, EntitySpawnError, EntitySpawnPlacement, EntitySpawnReason, + EntitySpawnRequest, SpawnGroupData, add_spawned_entity, apply_item_stack_components, + create_entity_instance, spawn_entity, +}; pub(crate) use storage::{EntityStorage, EntityStorageAddResult}; pub use synced_data::{EntitySyncedData, LivingEntitySyncedData}; pub(crate) use ticking::{ diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index fc7888d17eb..c72d54b6417 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -1,3 +1,17 @@ +use std::{io::Cursor, sync::Arc}; + +use glam::DVec3; +use simdnbt::borrow::read_compound; +use steel_registry::data_components::vanilla_components::ENTITY_DATA; +use steel_registry::entity_type::EntityTypeRef; +use steel_registry::item_stack::ItemStack; +use steel_utils::nbt::merge_nbt_compounds; +use steel_utils::{BlockPos, WorldAabb, axis::Axis, wrap_degrees}; + +use super::{AddEntityError, ENTITIES, SharedEntity, next_entity_id}; +use crate::physics::{CollisionWorld, WorldCollisionProvider, collide}; +use crate::world::World; + /// Vanilla `EntitySpawnReason`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EntitySpawnReason { @@ -34,6 +48,222 @@ impl EntitySpawnReason { } } +/// Placement modes used by the shared vanilla entity-spawn coordinator. +#[derive(Debug, Clone, Copy)] +pub(crate) enum EntitySpawnPlacement { + /// Spawn at a block center, optionally applying vanilla's downward offset. + Block { + pos: BlockPos, + try_move_down: bool, + moved_up: bool, + }, + /// Spawn at an exact command or lifecycle position. + Exact { + position: DVec3, + rotation: (f32, f32), + }, +} + +impl EntitySpawnPlacement { + fn factory_position(self) -> DVec3 { + match self { + Self::Block { + pos, + try_move_down: true, + .. + } => DVec3::new( + f64::from(pos.x()) + 0.5, + f64::from(pos.y()) + 1.0, + f64::from(pos.z()) + 0.5, + ), + Self::Block { pos, .. } => DVec3::new( + f64::from(pos.x()) + 0.5, + f64::from(pos.y()), + f64::from(pos.z()) + 0.5, + ), + Self::Exact { position, .. } => position, + } + } +} + +/// Request for a complete, world-inserting entity spawn. +#[derive(Debug, Clone, Copy)] +pub(crate) struct EntitySpawnRequest<'a> { + pub entity_type: EntityTypeRef, + pub placement: EntitySpawnPlacement, + pub reason: EntitySpawnReason, + pub finalize_spawn: bool, + pub play_ambient_sound: bool, + pub item_stack: Option<&'a ItemStack>, +} + +/// Failure from the shared spawn coordinator. +#[derive(Debug)] +pub(crate) enum EntitySpawnError { + InvalidPosition, + Peaceful, + MissingFactory, + InvalidEntityData, + AddEntity(AddEntityError), +} + +/// Creates an entity instance through the generated entity factory registry. +pub(crate) fn create_entity_instance( + world: &Arc, + entity_type: EntityTypeRef, + position: DVec3, +) -> Result { + if !position.is_finite() || !World::is_in_spawnable_bounds(BlockPos::from(position)) { + return Err(EntitySpawnError::InvalidPosition); + } + + if world.difficulty() == steel_utils::types::Difficulty::Peaceful + && !entity_type.allowed_in_peaceful + { + return Err(EntitySpawnError::Peaceful); + } + + if !ENTITIES.has_factory(entity_type) { + return Err(EntitySpawnError::MissingFactory); + } + + ENTITIES + .create( + entity_type, + next_entity_id(), + position, + Arc::downgrade(world), + ) + .ok_or(EntitySpawnError::MissingFactory) +} + +/// Inserts a fully initialized entity into the live world entity manager. +pub(crate) fn add_spawned_entity( + world: &Arc, + entity: SharedEntity, +) -> Result<(), AddEntityError> { + world.try_add_entity(entity) +} + +/// Applies the implicit entity data carried by an item stack. +pub(crate) fn apply_item_stack_components( + entity: &SharedEntity, + item_stack: &ItemStack, +) -> Result<(), EntitySpawnError> { + let Some(entity_data) = item_stack.get(ENTITY_DATA) else { + return Ok(()); + }; + + if entity_data.entity_type() != entity.entity_type() { + return Ok(()); + } + + let mut merged = entity.nbt_for_data_compare(); + merge_nbt_compounds(&mut merged, &entity_data.data().copy_tag()); + + let mut bytes = Vec::new(); + merged.write(&mut bytes); + let mut cursor = Cursor::new(bytes.as_slice()); + let borrowed = read_compound(&mut cursor).map_err(|_| EntitySpawnError::InvalidEntityData)?; + entity.apply_spawn_data((&borrowed).into()); + Ok(()) +} + +/// Mirrors vanilla `EntityType.spawn` for server-side entity creation. +pub(crate) fn spawn_entity( + world: &Arc, + request: EntitySpawnRequest<'_>, +) -> Result { + let entity = create_entity_instance( + world, + request.entity_type, + request.placement.factory_position(), + )?; + + let (position, rotation) = match request.placement { + EntitySpawnPlacement::Exact { position, rotation } => (position, rotation), + EntitySpawnPlacement::Block { + pos, + try_move_down, + moved_up, + } => { + let position_above = DVec3::new( + f64::from(pos.x()) + 0.5, + f64::from(pos.y()) + 1.0, + f64::from(pos.z()) + 0.5, + ); + if try_move_down { + entity.base().set_position_local(position_above); + } + + let y_offset = if try_move_down { + entity_y_offset(world, pos, moved_up, entity.bounding_box()) + } else { + 0.0 + }; + ( + DVec3::new( + f64::from(pos.x()) + 0.5, + f64::from(pos.y()) + y_offset, + f64::from(pos.z()) + 0.5, + ), + (wrap_degrees(rand::random::() * 360.0), 0.0), + ) + } + }; + + entity.base().set_position_local(position); + entity.set_rotation(rotation); + entity.set_old_position_to_current(); + entity.base().set_old_rotation_to_current(); + + if request.finalize_spawn + && let Some(mob) = entity.as_mob() + { + mob.set_y_head_rot(rotation.0); + mob.set_y_body_rot(rotation.0); + let _ = mob.finalize_spawn(world, request.reason, None); + } + + if let Some(item_stack) = request.item_stack { + apply_item_stack_components(&entity, item_stack)?; + } + + add_spawned_entity(world, Arc::clone(&entity)).map_err(EntitySpawnError::AddEntity)?; + + if request.play_ambient_sound + && let Some(mob) = entity.as_mob() + { + mob.play_ambient_sound(); + } + + Ok(entity) +} + +fn entity_y_offset( + world: &Arc, + spawn_pos: BlockPos, + moved_up: bool, + entity_box: WorldAabb, +) -> f64 { + let min_y = f64::from(spawn_pos.y()) - if moved_up { 1.0 } else { 0.0 }; + let collision_box = WorldAabb::new( + f64::from(spawn_pos.x()), + min_y, + f64::from(spawn_pos.z()), + f64::from(spawn_pos.x() + 1), + f64::from(spawn_pos.y() + 1), + f64::from(spawn_pos.z() + 1), + ); + let shapes = WorldCollisionProvider::new(world).get_block_collisions(&collision_box); + 1.0 + collide( + Axis::Y, + &entity_box, + &shapes, + if moved_up { -2.0 } else { -1.0 }, + ) +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum SpawnGroupData { AgeableMob(AgeableMobGroupData), @@ -101,7 +331,20 @@ impl AgeableMobGroupData { #[cfg(test)] mod tests { - use super::AgeableMobGroupData; + use std::sync::{Arc, Weak}; + + use glam::DVec3; + use simdnbt::owned::NbtCompound; + use steel_registry::data_components::components::EntityData; + use steel_registry::data_components::{CustomData, vanilla_components::ENTITY_DATA}; + use steel_registry::init_vanilla_registry; + use steel_registry::{vanilla_entities, vanilla_items, vanilla_pig_variants}; + use text_components::TextComponent; + + use crate::entity::entities::PigEntity; + use crate::entity::{AgeableMob, Entity, SharedEntity}; + + use super::{AgeableMobGroupData, apply_item_stack_components}; #[test] fn ageable_group_data_increments_before_later_baby_rolls_can_apply() { @@ -130,4 +373,37 @@ mod tests { ); assert_eq!(group_data.group_size(), 2); } + + #[test] + fn item_entity_data_overrides_entity_state_without_replacing_defaults() { + init_vanilla_registry(); + + let pig = Arc::new(PigEntity::new( + &vanilla_entities::PIG, + 1, + DVec3::ZERO, + Weak::new(), + )); + let entity: SharedEntity = pig.clone(); + pig.set_custom_name(Some(TextComponent::plain("Existing"))); + + let mut payload = NbtCompound::new(); + payload.insert("Age", -24_000); + payload.insert("variant", vanilla_pig_variants::WARM.key.to_string()); + let entity_data = EntityData::new( + &vanilla_entities::PIG, + CustomData::try_from_compound(payload).expect("test entity data should be valid"), + ); + let mut spawn_egg = + steel_registry::item_stack::ItemStack::new(&vanilla_items::PIG_SPAWN_EGG); + spawn_egg.set(ENTITY_DATA, entity_data); + + apply_item_stack_components(&entity, &spawn_egg) + .expect("valid typed entity data should load"); + + assert_eq!(pig.get_age(), -24_000); + assert_eq!(pig.variant().key, vanilla_pig_variants::WARM.key); + assert!(AgeableMob::is_baby(pig.as_ref())); + assert_eq!(pig.custom_name(), Some(TextComponent::plain("Existing"))); + } } From 347507b8e1d83e7cc2d1643d7d8596dedcccdf50 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sat, 22 Aug 2026 16:47:25 +0700 Subject: [PATCH 02/29] consume and formating, im stupid yk? --- steel-core/src/behavior/items/spawn_egg.rs | 34 ++++++++---- steel-core/src/entity/mob/mod.rs | 6 +- steel-core/src/entity/mod.rs | 6 +- steel-core/src/entity/spawn.rs | 64 +++++++++------------- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index ef2e014b0fc..e79426c3324 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -1,24 +1,29 @@ //! Data-driven vanilla spawn-egg behavior. +use std::sync::Arc; + use steel_macros::item_behavior; use steel_registry::blocks::block_state_ext::BlockStateExt as _; +use steel_registry::data_components::components::EntityData; use steel_registry::data_components::vanilla_components::ENTITY_DATA; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; use steel_registry::{vanilla_blocks, vanilla_game_events}; -use steel_utils::types::InteractionHand; +use steel_utils::{BlockPos, types::InteractionHand}; use super::place_on_water_block_item::get_player_pov_hit_result; use crate::behavior::{ - BLOCK_BEHAVIORS, BlockCollisionContext, InteractionResult, ItemBehavior, UseItemContext, - UseOnContext, + BLOCK_BEHAVIORS, BlockCollisionContext, InteractionResult, InventoryAccess, ItemBehavior, + UseItemContext, UseOnContext, }; use crate::entity::{ AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, LivingEntity, Mob, SharedEntity, add_spawned_entity, apply_item_stack_components, create_entity_instance, spawn_entity, }; +use crate::player::Player; use crate::world::ClipFluid; +use crate::world::World; use crate::world::game_event::GameEventContext; /// Behavior for every data-driven `SpawnEggItem` registry entry. @@ -27,15 +32,15 @@ pub struct SpawnEggItem; impl SpawnEggItem { fn entity_type(stack: &ItemStack) -> Option { - stack.get(ENTITY_DATA).map(|data| data.entity_type()) + stack.get(ENTITY_DATA).map(EntityData::entity_type) } fn spawn_mob( - world: &std::sync::Arc, - player: &crate::player::Player, - inventory: &crate::behavior::InventoryAccess, + world: &Arc, + player: &Player, + inventory: &InventoryAccess, stack: &ItemStack, - spawn_pos: steel_utils::BlockPos, + spawn_pos: BlockPos, try_move_down: bool, moved_up: bool, ) -> InteractionResult { @@ -59,7 +64,9 @@ impl SpawnEggItem { return InteractionResult::Fail; } - inventory.with_item(|item| item.shrink(1)); + if !player.has_infinite_materials() { + inventory.with_item(|item| item.shrink(1)); + } world.game_event( &vanilla_game_events::ENTITY_PLACE, spawn_pos, @@ -71,12 +78,15 @@ impl SpawnEggItem { /// Runs the interaction before normal mob handling. pub(crate) fn interact_with_mob( stack: &mut ItemStack, + player: &Player, parent: &M, ) -> InteractionResult { if Self::spawn_offspring(stack, parent).is_none() { return InteractionResult::Pass; } - stack.shrink(1); + if !player.has_infinite_materials() { + stack.shrink(1); + } InteractionResult::SuccessServer } @@ -185,13 +195,13 @@ impl ItemBehavior for SpawnEggItem { fn interact_living_entity( &self, stack: &mut ItemStack, - _player: &crate::player::Player, + player: &Player, target: &dyn LivingEntity, _hand: InteractionHand, ) -> InteractionResult { let Some(parent) = target.as_mob() else { return InteractionResult::Pass; }; - Self::interact_with_mob(stack, parent) + Self::interact_with_mob(stack, player, parent) } } diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index 06dd358c9a2..fb64c85acdb 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -508,10 +508,12 @@ pub trait Mob: LivingEntity { .lock() .get_item_in_hand(hand) .copy_with_count(1); - crate::behavior::items::SpawnEggItem::interact_with_mob(&mut item_stack, self) + crate::behavior::items::SpawnEggItem::interact_with_mob(&mut item_stack, player, self) }; if spawn_egg_result.consumes_action() { - if spawn_egg_result == InteractionResult::SuccessServer { + if spawn_egg_result == InteractionResult::SuccessServer + && !player.has_infinite_materials() + { player.inventory.lock().get_item_in_hand_mut(hand).shrink(1); } return spawn_egg_result; diff --git a/steel-core/src/entity/mod.rs b/steel-core/src/entity/mod.rs index 9f07e7eefea..ec5b10b53f2 100644 --- a/steel-core/src/entity/mod.rs +++ b/steel-core/src/entity/mod.rs @@ -815,9 +815,9 @@ pub use projectile::{ }; pub use registry::{ENTITIES, EntityLoadRequest, EntityRegistry, init_entities}; pub(crate) use spawn::{ - AgeableMobGroupData, EntitySpawnError, EntitySpawnPlacement, EntitySpawnReason, - EntitySpawnRequest, SpawnGroupData, add_spawned_entity, apply_item_stack_components, - create_entity_instance, spawn_entity, + AgeableMobGroupData, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, + SpawnGroupData, add_spawned_entity, apply_item_stack_components, create_entity_instance, + spawn_entity, }; pub(crate) use storage::{EntityStorage, EntityStorageAddResult}; pub use synced_data::{EntitySyncedData, LivingEntitySyncedData}; diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index c72d54b6417..8becac20b75 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -57,11 +57,6 @@ pub(crate) enum EntitySpawnPlacement { try_move_down: bool, moved_up: bool, }, - /// Spawn at an exact command or lifecycle position. - Exact { - position: DVec3, - rotation: (f32, f32), - }, } impl EntitySpawnPlacement { @@ -81,7 +76,6 @@ impl EntitySpawnPlacement { f64::from(pos.y()), f64::from(pos.z()) + 0.5, ), - Self::Exact { position, .. } => position, } } } @@ -104,7 +98,7 @@ pub(crate) enum EntitySpawnError { Peaceful, MissingFactory, InvalidEntityData, - AddEntity(AddEntityError), + AddEntity, } /// Creates an entity instance through the generated entity factory registry. @@ -180,37 +174,31 @@ pub(crate) fn spawn_entity( request.placement.factory_position(), )?; - let (position, rotation) = match request.placement { - EntitySpawnPlacement::Exact { position, rotation } => (position, rotation), - EntitySpawnPlacement::Block { - pos, - try_move_down, - moved_up, - } => { - let position_above = DVec3::new( - f64::from(pos.x()) + 0.5, - f64::from(pos.y()) + 1.0, - f64::from(pos.z()) + 0.5, - ); - if try_move_down { - entity.base().set_position_local(position_above); - } - - let y_offset = if try_move_down { - entity_y_offset(world, pos, moved_up, entity.bounding_box()) - } else { - 0.0 - }; - ( - DVec3::new( - f64::from(pos.x()) + 0.5, - f64::from(pos.y()) + y_offset, - f64::from(pos.z()) + 0.5, - ), - (wrap_degrees(rand::random::() * 360.0), 0.0), - ) - } + let EntitySpawnPlacement::Block { + pos, + try_move_down, + moved_up, + } = request.placement; + let position_above = DVec3::new( + f64::from(pos.x()) + 0.5, + f64::from(pos.y()) + 1.0, + f64::from(pos.z()) + 0.5, + ); + if try_move_down { + entity.base().set_position_local(position_above); + } + + let y_offset = if try_move_down { + entity_y_offset(world, pos, moved_up, entity.bounding_box()) + } else { + 0.0 }; + let position = DVec3::new( + f64::from(pos.x()) + 0.5, + f64::from(pos.y()) + y_offset, + f64::from(pos.z()) + 0.5, + ); + let rotation = (wrap_degrees(rand::random::() * 360.0), 0.0); entity.base().set_position_local(position); entity.set_rotation(rotation); @@ -229,7 +217,7 @@ pub(crate) fn spawn_entity( apply_item_stack_components(&entity, item_stack)?; } - add_spawned_entity(world, Arc::clone(&entity)).map_err(EntitySpawnError::AddEntity)?; + add_spawned_entity(world, Arc::clone(&entity)).map_err(|_| EntitySpawnError::AddEntity)?; if request.play_ambient_sound && let Some(mob) = entity.as_mob() From 6fa3cfc6a174c2aceb10b1dff3805873879e12f6 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sat, 22 Aug 2026 19:44:50 +0700 Subject: [PATCH 03/29] remove hardcode --- steel-core/src/behavior/block/mod.rs | 5 +++++ .../src/behavior/blocks/fluid/liquid_block.rs | 20 +++++++++++++++++++ steel-core/src/behavior/items/spawn_egg.rs | 8 ++++---- steel-core/src/behavior/mod.rs | 8 ++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/steel-core/src/behavior/block/mod.rs b/steel-core/src/behavior/block/mod.rs index 72a341f4a0a..47a6595bd4a 100644 --- a/steel-core/src/behavior/block/mod.rs +++ b/steel-core/src/behavior/block/mod.rs @@ -610,6 +610,11 @@ pub trait BlockBehavior: Send + Sync { false } + /// Returns whether this behavior implements vanilla `LiquidBlock`. + fn is_liquid_block(&self) -> bool { + false + } + /// Mirrors vanilla `DoorBlock.isWoodenDoor`. /// /// Despite the vanilla name, this returns true for any door block type that diff --git a/steel-core/src/behavior/blocks/fluid/liquid_block.rs b/steel-core/src/behavior/blocks/fluid/liquid_block.rs index 787c3fe5805..e3badc828b0 100644 --- a/steel-core/src/behavior/blocks/fluid/liquid_block.rs +++ b/steel-core/src/behavior/blocks/fluid/liquid_block.rs @@ -133,6 +133,10 @@ impl LiquidBlock { } impl BlockBehavior for LiquidBlock { + fn is_liquid_block(&self) -> bool { + true + } + fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option { Some(self.block.default_state()) } @@ -357,4 +361,20 @@ mod tests { assert_eq!(updated, state); assert!(level.scheduled_block_ticks.borrow().is_empty()); } + + #[test] + fn liquid_block_behavior_and_ext_identify_liquid_blocks() { + use crate::behavior::BlockStateBehaviorExt as _; + + init_vanilla_registry(); + init_behaviors(); + + let liquid = LiquidBlock::new(&vanilla_blocks::WATER, &vanilla_fluids::WATER); + assert!(liquid.is_liquid_block()); + + assert!(vanilla_blocks::WATER.default_state().is_liquid_block()); + assert!(vanilla_blocks::LAVA.default_state().is_liquid_block()); + assert!(!vanilla_blocks::STONE.default_state().is_liquid_block()); + assert!(!vanilla_blocks::AIR.default_state().is_liquid_block()); + } } diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index e79426c3324..d653c8267d6 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -13,8 +13,8 @@ use steel_utils::{BlockPos, types::InteractionHand}; use super::place_on_water_block_item::get_player_pov_hit_result; use crate::behavior::{ - BLOCK_BEHAVIORS, BlockCollisionContext, InteractionResult, InventoryAccess, ItemBehavior, - UseItemContext, UseOnContext, + BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, InteractionResult, + InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, }; use crate::entity::{ AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, LivingEntity, Mob, @@ -173,8 +173,8 @@ impl ItemBehavior for SpawnEggItem { } let pos = hit_result.block_pos; - let block = context.world.get_block_state(pos).get_block(); - if block != &vanilla_blocks::WATER && block != &vanilla_blocks::LAVA { + let state = context.world.get_block_state(pos); + if !state.is_liquid_block() { return InteractionResult::Pass; } if !context.world.may_interact(context.player, pos) { diff --git a/steel-core/src/behavior/mod.rs b/steel-core/src/behavior/mod.rs index 37008b00999..cfc5ef1b7e8 100644 --- a/steel-core/src/behavior/mod.rs +++ b/steel-core/src/behavior/mod.rs @@ -132,6 +132,9 @@ pub trait BlockStateBehaviorExt { /// Returns whether this block state extends `BedBlock` fn is_bed(&self) -> bool; + /// Returns whether this block state extends `LiquidBlock`. + fn is_liquid_block(&self) -> bool; + /// Returns whether this block state can be occupied by a forced respawn position fn is_possible_to_respawn_in_this(&self) -> bool; } @@ -166,6 +169,11 @@ impl BlockStateBehaviorExt for BlockStateId { BLOCK_BEHAVIORS.get_behavior(block).is_bed() } + fn is_liquid_block(&self) -> bool { + let block = self.get_block(); + BLOCK_BEHAVIORS.get_behavior(block).is_liquid_block() + } + fn is_possible_to_respawn_in_this(&self) -> bool { let block = self.get_block(); let behavior = BLOCK_BEHAVIORS.get_behavior(block); From 91b16d8fcb4f638700db0b0c60e2b9546d1872d7 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sat, 22 Aug 2026 19:53:18 +0700 Subject: [PATCH 04/29] todo --- steel-core/src/behavior/items/spawn_egg.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index d653c8267d6..2fe565fda97 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -129,6 +129,7 @@ impl ItemBehavior for SpawnEggItem { let clicked_pos = context.hit_result.block_pos; let clicked_state = context.world.get_block_state(clicked_pos); if clicked_state.get_block() == &vanilla_blocks::SPAWNER { + // TODO: Use spawn eggs on a spawner block. // Spawner block-entity mutation is a separate foundation; do not // incorrectly create a mob beside a spawner in its place. return InteractionResult::Fail; @@ -181,7 +182,7 @@ impl ItemBehavior for SpawnEggItem { return InteractionResult::Fail; } - Self::spawn_mob( + let result = Self::spawn_mob( context.world, context.player, &context.inv, @@ -189,7 +190,9 @@ impl ItemBehavior for SpawnEggItem { pos, false, false, - ) + ); + // TODO: Award the ITEM_USED stat for the item if the result is Successful. + result } fn interact_living_entity( From 7f1bdf2adfabf437a61066d26d552fed20d17c41 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sun, 23 Aug 2026 08:14:42 +0700 Subject: [PATCH 05/29] fix lint --- steel-core/src/entity/mob/mod.rs | 3 ++- steel-core/src/entity/spawn.rs | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index fb64c85acdb..b7f54997ce0 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -38,6 +38,7 @@ use steel_utils::types::{Difficulty, InteractionHand}; use steel_utils::{BlockPos, Identifier, WorldAabb, axis::Axis}; use crate::behavior::{BLOCK_BEHAVIORS, BlockCollisionContext, ITEM_BEHAVIORS, InteractionResult}; +use crate::behavior::items::SpawnEggItem; use crate::enchantment_helper::{self, EnchantmentDamageContext, EnchantmentPostAttackContext}; use crate::entity::ai::control::{ BodyRotationInput, MobControls, MoveControlOperation, rotate_if_necessary, rotate_towards, @@ -508,7 +509,7 @@ pub trait Mob: LivingEntity { .lock() .get_item_in_hand(hand) .copy_with_count(1); - crate::behavior::items::SpawnEggItem::interact_with_mob(&mut item_stack, player, self) + SpawnEggItem::interact_with_mob(&mut item_stack, player, self) }; if spawn_egg_result.consumes_action() { if spawn_egg_result == InteractionResult::SuccessServer diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 8becac20b75..851e09161b8 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -6,7 +6,7 @@ use steel_registry::data_components::vanilla_components::ENTITY_DATA; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; use steel_utils::nbt::merge_nbt_compounds; -use steel_utils::{BlockPos, WorldAabb, axis::Axis, wrap_degrees}; +use steel_utils::{BlockPos, WorldAabb, axis::Axis, types::Difficulty, wrap_degrees}; use super::{AddEntityError, ENTITIES, SharedEntity, next_entity_id}; use crate::physics::{CollisionWorld, WorldCollisionProvider, collide}; @@ -111,7 +111,7 @@ pub(crate) fn create_entity_instance( return Err(EntitySpawnError::InvalidPosition); } - if world.difficulty() == steel_utils::types::Difficulty::Peaceful + if world.difficulty() == Difficulty::Peaceful && !entity_type.allowed_in_peaceful { return Err(EntitySpawnError::Peaceful); @@ -382,8 +382,7 @@ mod tests { &vanilla_entities::PIG, CustomData::try_from_compound(payload).expect("test entity data should be valid"), ); - let mut spawn_egg = - steel_registry::item_stack::ItemStack::new(&vanilla_items::PIG_SPAWN_EGG); + let mut spawn_egg = ItemStack::new(&vanilla_items::PIG_SPAWN_EGG); spawn_egg.set(ENTITY_DATA, entity_data); apply_item_stack_components(&entity, &spawn_egg) From 50a0d20f347d980870ea9d7a312077eca524c0dd Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sun, 23 Aug 2026 08:16:03 +0700 Subject: [PATCH 06/29] formating --- steel-core/src/entity/mob/mod.rs | 2 +- steel-core/src/entity/spawn.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index b7f54997ce0..58912de333c 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -37,8 +37,8 @@ use steel_utils::locks::SyncMutex; use steel_utils::types::{Difficulty, InteractionHand}; use steel_utils::{BlockPos, Identifier, WorldAabb, axis::Axis}; -use crate::behavior::{BLOCK_BEHAVIORS, BlockCollisionContext, ITEM_BEHAVIORS, InteractionResult}; use crate::behavior::items::SpawnEggItem; +use crate::behavior::{BLOCK_BEHAVIORS, BlockCollisionContext, ITEM_BEHAVIORS, InteractionResult}; use crate::enchantment_helper::{self, EnchantmentDamageContext, EnchantmentPostAttackContext}; use crate::entity::ai::control::{ BodyRotationInput, MobControls, MoveControlOperation, rotate_if_necessary, rotate_towards, diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 851e09161b8..fdb91fe9603 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -111,9 +111,7 @@ pub(crate) fn create_entity_instance( return Err(EntitySpawnError::InvalidPosition); } - if world.difficulty() == Difficulty::Peaceful - && !entity_type.allowed_in_peaceful - { + if world.difficulty() == Difficulty::Peaceful && !entity_type.allowed_in_peaceful { return Err(EntitySpawnError::Peaceful); } From c3fe9e9241827e79207d26c6acfa5094f436f4b0 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sun, 23 Aug 2026 10:32:33 +0700 Subject: [PATCH 07/29] stat --- steel-core/src/behavior/items/spawn_egg.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 2fe565fda97..fb873c744bf 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -8,6 +8,7 @@ use steel_registry::data_components::components::EntityData; use steel_registry::data_components::vanilla_components::ENTITY_DATA; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; +use steel_registry::stat::vanilla_stat_types; use steel_registry::{vanilla_blocks, vanilla_game_events}; use steel_utils::{BlockPos, types::InteractionHand}; @@ -191,7 +192,11 @@ impl ItemBehavior for SpawnEggItem { false, false, ); - // TODO: Award the ITEM_USED stat for the item if the result is Successful. + if result == InteractionResult::Success { + context + .player + .award_stat(&vanilla_stat_types::ITEM_USED, stack.item()); + } result } From bb7f14880000315ad031bfef5480d3f5ccde823f Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Sun, 23 Aug 2026 10:41:06 +0700 Subject: [PATCH 08/29] format --- steel-core/src/entity/spawn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index fdb91fe9603..5f596b49dab 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -324,6 +324,7 @@ mod tests { use steel_registry::data_components::components::EntityData; use steel_registry::data_components::{CustomData, vanilla_components::ENTITY_DATA}; use steel_registry::init_vanilla_registry; + use steel_registry::item_stack::ItemStack; use steel_registry::{vanilla_entities, vanilla_items, vanilla_pig_variants}; use text_components::TextComponent; From 24bb313f1f87fc1cffe086859d901c6172dcfcf5 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Wed, 26 Aug 2026 09:00:09 +0700 Subject: [PATCH 09/29] function for coco --- steel-core/src/entity/entity/mod.rs | 60 +++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/steel-core/src/entity/entity/mod.rs b/steel-core/src/entity/entity/mod.rs index 7ca08693001..524c75be1b3 100644 --- a/steel-core/src/entity/entity/mod.rs +++ b/steel-core/src/entity/entity/mod.rs @@ -2,6 +2,41 @@ use std::collections::BTreeSet; use super::*; +const MAX_ENTITY_MOTION_COMPONENT: f64 = 10.0; + +fn read_nbt_dvec3( + nbt: &BorrowedNbtCompoundView<'_, '_>, + key: &str, +) -> Option { + let values = nbt.list(key)?.doubles()?; + let &[x, y, z, ..] = values.as_slice() else { + return None; + }; + Some(DVec3::new(x, y, z)) +} + +fn read_nbt_rotation( + nbt: &BorrowedNbtCompoundView<'_, '_>, + key: &str, +) -> Option<(f32, f32)> { + let values = nbt.list(key)?.floats()?; + let &[yaw, pitch, ..] = values.as_slice() else { + return None; + }; + Some((yaw, pitch)) +} + +fn sanitize_nbt_motion(motion: DVec3) -> DVec3 { + let sanitize = |value: f64| { + if value.abs() > MAX_ENTITY_MOTION_COMPONENT { + 0.0 + } else { + value + } + }; + DVec3::new(sanitize(motion.x), sanitize(motion.y), sanitize(motion.z)) +} + /// Final state accepted from a client-authored movement packet. #[derive(Debug, Clone, Copy, PartialEq)] pub struct AcceptedClientMovement { @@ -3481,28 +3516,21 @@ pub trait Entity: EntityEventSource + ErasedType + Send + Sync + 'static { .or_else(|| nbt.byte(key).map(i32::from)) }; - if let Some(values) = nbt.list("Pos").and_then(|list| list.doubles()) - && values.len() >= 3 + if let Some(position) = read_nbt_dvec3(&nbt, "Pos") + && position.is_finite() { - let position = DVec3::new(values[0], values[1], values[2]); - if position.is_finite() { - self.base().set_position_local(position); - } + self.base().set_position_local(position); } - if let Some(values) = nbt.list("Motion").and_then(|list| list.doubles()) - && values.len() >= 3 - { - self.set_velocity(DVec3::new(values[0], values[1], values[2])); + if let Some(motion) = read_nbt_dvec3(&nbt, "Motion") { + self.set_velocity(sanitize_nbt_motion(motion)); } - if let Some(values) = nbt.list("Rotation").and_then(|list| list.floats()) - && values.len() >= 2 - { - self.set_rotation((values[0], values[1])); + if let Some((yaw, pitch)) = read_nbt_rotation(&nbt, "Rotation") { + self.set_rotation((yaw, pitch)); if let Some(living) = self.as_living_entity() { - living.set_y_head_rot(values[0]); - living.set_y_body_rot(values[0]); + living.set_y_head_rot(yaw); + living.set_y_body_rot(yaw); } } From 531bd79741ba3edc29d27c3d36edd47dd381030c Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Wed, 26 Aug 2026 14:52:13 +0700 Subject: [PATCH 10/29] a greatful warn --- steel-core/src/behavior/items/spawn_egg.rs | 12 +++++++++++- steel-core/src/entity/entity/mod.rs | 10 ++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index fb873c744bf..3adf6b45165 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -101,7 +101,17 @@ impl SpawnEggItem { let offspring = if let Some(animal) = parent.as_animal() { animal.get_breed_offspring(&world, animal)? } else { - create_entity_instance(&world, entity_type, parent.position()).ok()? + match create_entity_instance(&world, entity_type, parent.position()) { + Ok(offspring) => offspring, + Err(error) => { + log::warn!( + "Failed to create spawn-egg offspring {} at {:?}: {error:?}", + entity_type.key, + parent.position() + ); + return None; + } + } }; let ageable = offspring.as_ageable_mob()?; diff --git a/steel-core/src/entity/entity/mod.rs b/steel-core/src/entity/entity/mod.rs index 524c75be1b3..f09d6e698da 100644 --- a/steel-core/src/entity/entity/mod.rs +++ b/steel-core/src/entity/entity/mod.rs @@ -4,10 +4,7 @@ use super::*; const MAX_ENTITY_MOTION_COMPONENT: f64 = 10.0; -fn read_nbt_dvec3( - nbt: &BorrowedNbtCompoundView<'_, '_>, - key: &str, -) -> Option { +fn read_nbt_dvec3(nbt: &BorrowedNbtCompoundView<'_, '_>, key: &str) -> Option { let values = nbt.list(key)?.doubles()?; let &[x, y, z, ..] = values.as_slice() else { return None; @@ -15,10 +12,7 @@ fn read_nbt_dvec3( Some(DVec3::new(x, y, z)) } -fn read_nbt_rotation( - nbt: &BorrowedNbtCompoundView<'_, '_>, - key: &str, -) -> Option<(f32, f32)> { +fn read_nbt_rotation(nbt: &BorrowedNbtCompoundView<'_, '_>, key: &str) -> Option<(f32, f32)> { let values = nbt.list(key)?.floats()?; let &[yaw, pitch, ..] = values.as_slice() else { return None; From 22d6a86ed1dbc7f08345366b1ce434af5be1e8b8 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Wed, 26 Aug 2026 15:03:50 +0700 Subject: [PATCH 11/29] sync consume --- steel-core/src/entity/mob/mod.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index 58912de333c..458b66ca99b 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -504,19 +504,11 @@ pub trait Mob: LivingEntity { } let spawn_egg_result = { - let mut item_stack = player - .inventory - .lock() - .get_item_in_hand(hand) - .copy_with_count(1); - SpawnEggItem::interact_with_mob(&mut item_stack, player, self) + let mut inventory = player.inventory.lock(); + let item_stack = inventory.get_item_in_hand_mut(hand); + SpawnEggItem::interact_with_mob(item_stack, player, self) }; if spawn_egg_result.consumes_action() { - if spawn_egg_result == InteractionResult::SuccessServer - && !player.has_infinite_materials() - { - player.inventory.lock().get_item_in_hand_mut(hand).shrink(1); - } return spawn_egg_result; } From c1b4fb7b5e3fafef95e563c94a0b2870e361c8c7 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Wed, 26 Aug 2026 15:31:35 +0700 Subject: [PATCH 12/29] name tag order --- steel-core/src/behavior/items/spawn_egg.rs | 20 ++------ steel-core/src/entity/mob/mod.rs | 60 +++++++++++++++++++--- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 3adf6b45165..2c9c46f764a 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -10,7 +10,7 @@ use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; use steel_registry::stat::vanilla_stat_types; use steel_registry::{vanilla_blocks, vanilla_game_events}; -use steel_utils::{BlockPos, types::InteractionHand}; +use steel_utils::BlockPos; use super::place_on_water_block_item::get_player_pov_hit_result; use crate::behavior::{ @@ -18,9 +18,8 @@ use crate::behavior::{ InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, }; use crate::entity::{ - AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, LivingEntity, Mob, - SharedEntity, add_spawned_entity, apply_item_stack_components, create_entity_instance, - spawn_entity, + AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity, + add_spawned_entity, apply_item_stack_components, create_entity_instance, spawn_entity, }; use crate::player::Player; use crate::world::ClipFluid; @@ -209,17 +208,4 @@ impl ItemBehavior for SpawnEggItem { } result } - - fn interact_living_entity( - &self, - stack: &mut ItemStack, - player: &Player, - target: &dyn LivingEntity, - _hand: InteractionHand, - ) -> InteractionResult { - let Some(parent) = target.as_mob() else { - return InteractionResult::Pass; - }; - Self::interact_with_mob(stack, player, parent) - } } diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index 458b66ca99b..79f640b3a6b 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -503,13 +503,16 @@ pub trait Mob: LivingEntity { return InteractionResult::Pass; } - let spawn_egg_result = { - let mut inventory = player.inventory.lock(); - let item_stack = inventory.get_item_in_hand_mut(hand); - SpawnEggItem::interact_with_mob(item_stack, player, self) - }; - if spawn_egg_result.consumes_action() { - return spawn_egg_result; + let important_interaction = self.check_and_handle_important_interactions(player, hand); + if important_interaction.consumes_action() { + if let Some(world) = self.level() { + world.game_event( + &vanilla_game_events::ENTITY_INTERACT, + self.block_position(), + &GameEventContext::new(Some(player), None), + ); + } + return important_interaction; } let interaction_result = self.interact_entity(player, hand, location); @@ -531,6 +534,49 @@ pub trait Mob: LivingEntity { interaction_result } + /// Handles vanilla `Mob.checkAndHandleImportantInteractions`. + fn check_and_handle_important_interactions( + &self, + player: &Player, + hand: InteractionHand, + ) -> InteractionResult { + let Some(living_entity) = self.as_living_entity() else { + return InteractionResult::Pass; + }; + + let item = { + let inventory = player.inventory.lock(); + inventory.get_item_in_hand(hand).item() + }; + + if item.key == vanilla_items::NAME_TAG.key { + let name_tag_result = { + let mut inventory = player.inventory.lock(); + let item_stack = inventory.get_item_in_hand_mut(hand); + ITEM_BEHAVIORS.get_behavior(item).interact_living_entity( + item_stack, + player, + living_entity, + hand, + ) + }; + if name_tag_result.consumes_action() { + return name_tag_result; + } + } + + let spawn_egg_result = { + let mut inventory = player.inventory.lock(); + let item_stack = inventory.get_item_in_hand_mut(hand); + SpawnEggItem::interact_with_mob(item_stack, player, self) + }; + if spawn_egg_result.consumes_action() { + return spawn_egg_result; + } + + InteractionResult::Pass + } + /// Handles vanilla `Mob.mobInteract`. fn mob_interact(&self, _player: &Player, _hand: InteractionHand) -> InteractionResult { InteractionResult::Pass From cdcfc176abf2becf8e58ec814405cb83f6f8f8c4 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Wed, 26 Aug 2026 22:08:44 +0700 Subject: [PATCH 13/29] custom name spawn egg --- steel-core/src/entity/mob/mod.rs | 2 +- steel-core/src/entity/spawn.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index 0ef539e3e54..c7af17907b2 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -23,7 +23,7 @@ use steel_registry::sound_event::SoundEventRef; use steel_registry::vanilla_block_tags::BlockTag; use steel_registry::{ REGISTRY, RegistryExt, vanilla_attributes, vanilla_damage_types, vanilla_entities, - vanilla_game_events, + vanilla_game_events, vanilla_items, }; use steel_utils::locks::SyncMutex; use steel_utils::types::{Difficulty, InteractionHand}; diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 5f596b49dab..5b4ef41219d 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; -use steel_registry::data_components::vanilla_components::ENTITY_DATA; +use steel_registry::data_components::vanilla_components::{CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; use steel_utils::nbt::merge_nbt_compounds; @@ -142,6 +142,10 @@ pub(crate) fn apply_item_stack_components( entity: &SharedEntity, item_stack: &ItemStack, ) -> Result<(), EntitySpawnError> { + if let Some(custom_name) = item_stack.get(CUSTOM_NAME) { + entity.set_custom_name(Some(custom_name.clone())); + } + let Some(entity_data) = item_stack.get(ENTITY_DATA) else { return Ok(()); }; From 745a584f7a1eab3f02f153cedc99279a79ef73aa Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Thu, 27 Aug 2026 07:57:39 +0700 Subject: [PATCH 14/29] formating --- steel-core/src/behavior/items/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steel-core/src/behavior/items/mod.rs b/steel-core/src/behavior/items/mod.rs index a371c7b9ec0..fe75f211d71 100644 --- a/steel-core/src/behavior/items/mod.rs +++ b/steel-core/src/behavior/items/mod.rs @@ -31,8 +31,8 @@ mod potion; mod shield; mod shovel; mod sign_item; -mod spawn_egg; mod solid_bucket_item; +mod spawn_egg; mod spyglass; mod standing_and_wall_block_item; mod throwable_potion; @@ -69,8 +69,8 @@ pub use potion::PotionItem; pub use shield::ShieldItem; pub use shovel::ShovelItem; pub use sign_item::{HangingSignItem, SignItem}; -pub use spawn_egg::SpawnEggItem; pub use solid_bucket_item::SolidBucketItem; +pub use spawn_egg::SpawnEggItem; pub use spyglass::SpyglassItem; pub use standing_and_wall_block_item::StandingAndWallBlockItem; pub use throwable_potion::{LingeringPotionItem, SplashPotionItem}; From 73209bf5636c8854a0c5dd17905fd8edef526efb Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Thu, 27 Aug 2026 08:04:06 +0700 Subject: [PATCH 15/29] magic cucumber --- steel-core/src/entity/spawn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 5b4ef41219d..a19f45dd595 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -379,7 +379,7 @@ mod tests { pig.set_custom_name(Some(TextComponent::plain("Existing"))); let mut payload = NbtCompound::new(); - payload.insert("Age", -24_000); + payload.insert("Age", pig.get_baby_start_age()); payload.insert("variant", vanilla_pig_variants::WARM.key.to_string()); let entity_data = EntityData::new( &vanilla_entities::PIG, @@ -391,7 +391,7 @@ mod tests { apply_item_stack_components(&entity, &spawn_egg) .expect("valid typed entity data should load"); - assert_eq!(pig.get_age(), -24_000); + assert_eq!(pig.get_age(), pig.get_baby_start_age()); assert_eq!(pig.variant().key, vanilla_pig_variants::WARM.key); assert!(AgeableMob::is_baby(pig.as_ref())); assert_eq!(pig.custom_name(), Some(TextComponent::plain("Existing"))); From ec352c6ca92c882f020e4192ff4d52deffb30cc9 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Thu, 27 Aug 2026 09:44:52 +0700 Subject: [PATCH 16/29] stat and item check --- steel-core/src/behavior/item.rs | 7 +++++- steel-core/src/behavior/items/spawn_egg.rs | 25 ++++++++++++++++++---- steel-core/src/entity/mob/mod.rs | 16 ++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/steel-core/src/behavior/item.rs b/steel-core/src/behavior/item.rs index 657bfea8abb..68a4e8b2721 100644 --- a/steel-core/src/behavior/item.rs +++ b/steel-core/src/behavior/item.rs @@ -14,7 +14,7 @@ use steel_registry::{REGISTRY, RegistryEntry, RegistryExt}; use steel_utils::types::InteractionHand; use text_components::TextComponent; -use crate::behavior::items::DefaultItemBehavior; +use crate::behavior::items::{DefaultItemBehavior, SpawnEggItem}; use crate::behavior::{InteractionResult, UseItemContext, UseOnContext}; use crate::entity::damage::DamageSource; use crate::entity::{Entity, LivingEntity}; @@ -190,6 +190,11 @@ pub trait ItemBehavior: Send + Sync { .get_weapon() .map(|weapon| weapon.item_damage_per_attack) } + + /// Returns this item behavior as a [`SpawnEggItem`], if applicable. + fn as_spawn_egg(&self) -> Option<&SpawnEggItem> { + None + } } /// Registry for item behaviors. diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 2c9c46f764a..6f3e3bc5f3b 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -14,8 +14,8 @@ use steel_utils::BlockPos; use super::place_on_water_block_item::get_player_pov_hit_result; use crate::behavior::{ - BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, InteractionResult, - InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, + BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, ITEM_BEHAVIORS, + InteractionResult, InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, }; use crate::entity::{ AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity, @@ -81,6 +81,13 @@ impl SpawnEggItem { player: &Player, parent: &M, ) -> InteractionResult { + if ITEM_BEHAVIORS + .get_behavior(stack.item()) + .as_spawn_egg() + .is_none() + { + return InteractionResult::Pass; + } if Self::spawn_offspring(stack, parent).is_none() { return InteractionResult::Pass; } @@ -130,6 +137,10 @@ impl SpawnEggItem { } impl ItemBehavior for SpawnEggItem { + fn as_spawn_egg(&self) -> Option<&SpawnEggItem> { + Some(self) + } + fn use_on(&self, context: &mut UseOnContext) -> InteractionResult { let stack = context.inv.with_item(|item| item.clone()); if Self::entity_type(&stack).is_none() { @@ -160,7 +171,7 @@ impl ItemBehavior for SpawnEggItem { clicked_face.relative(clicked_pos) }; - Self::spawn_mob( + let result = Self::spawn_mob( context.world, context.player, &context.inv, @@ -168,7 +179,13 @@ impl ItemBehavior for SpawnEggItem { spawn_pos, true, spawn_pos != clicked_pos && clicked_face == steel_utils::Direction::Up, - ) + ); + if result == InteractionResult::Success { + context + .player + .award_stat(&vanilla_stat_types::ITEM_USED, stack.item()); + } + result } fn use_item(&self, context: &mut UseItemContext) -> InteractionResult { diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index c7af17907b2..6c6c6aa4027 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -556,13 +556,15 @@ pub trait Mob: LivingEntity + Leashable { } } - let spawn_egg_result = { - let mut inventory = player.inventory.lock(); - let item_stack = inventory.get_item_in_hand_mut(hand); - SpawnEggItem::interact_with_mob(item_stack, player, self) - }; - if spawn_egg_result.consumes_action() { - return spawn_egg_result; + if ITEM_BEHAVIORS.get_behavior(item).as_spawn_egg().is_some() { + let spawn_egg_result = { + let mut inventory = player.inventory.lock(); + let item_stack = inventory.get_item_in_hand_mut(hand); + SpawnEggItem::interact_with_mob(item_stack, player, self) + }; + if spawn_egg_result.consumes_action() { + return spawn_egg_result; + } } InteractionResult::Pass From 0523f94b648965d09246382e015109e2fbb087ce Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Thu, 27 Aug 2026 17:11:36 +0700 Subject: [PATCH 17/29] off spring spawn --- steel-core/src/behavior/item.rs | 2 +- steel-core/src/behavior/items/spawn_egg.rs | 4 ++-- steel-core/src/entity/mod.rs | 4 ++-- steel-core/src/entity/spawn.rs | 17 +++++++++++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/steel-core/src/behavior/item.rs b/steel-core/src/behavior/item.rs index 68a4e8b2721..c31071e2adf 100644 --- a/steel-core/src/behavior/item.rs +++ b/steel-core/src/behavior/item.rs @@ -191,7 +191,7 @@ pub trait ItemBehavior: Send + Sync { .map(|weapon| weapon.item_damage_per_attack) } - /// Returns this item behavior as a [`SpawnEggItem`], if applicable. + /// Returns this item behavior as a `SpawnEggItem`. fn as_spawn_egg(&self) -> Option<&SpawnEggItem> { None } diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 6f3e3bc5f3b..b9efb18b769 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -19,7 +19,7 @@ use crate::behavior::{ }; use crate::entity::{ AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity, - add_spawned_entity, apply_item_stack_components, create_entity_instance, spawn_entity, + add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity, }; use crate::player::Player; use crate::world::ClipFluid; @@ -130,7 +130,7 @@ impl SpawnEggItem { offspring.set_rotation((0.0, 0.0)); offspring.set_old_position_to_current(); offspring.base().set_old_rotation_to_current(); - apply_item_stack_components(&offspring, stack).ok()?; + apply_implicit_item_stack_components(&offspring, stack); add_spawned_entity(&world, offspring.clone()).ok()?; Some(offspring) } diff --git a/steel-core/src/entity/mod.rs b/steel-core/src/entity/mod.rs index cd5f332fdb0..c7e89d72ea8 100644 --- a/steel-core/src/entity/mod.rs +++ b/steel-core/src/entity/mod.rs @@ -817,8 +817,8 @@ pub use projectile::{ pub use registry::{ENTITIES, EntityLoadRequest, EntityRegistry, init_entities}; pub(crate) use spawn::{ AgeableMobGroupData, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, - SpawnGroupData, add_spawned_entity, apply_item_stack_components, create_entity_instance, - spawn_entity, + SpawnGroupData, add_spawned_entity, apply_implicit_item_stack_components, + create_entity_instance, spawn_entity, }; pub(crate) use storage::{EntityStorage, EntityStorageAddResult}; pub use synced_data::{EntitySyncedData, LivingEntitySyncedData}; diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index a19f45dd595..dc538191813 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; -use steel_registry::data_components::vanilla_components::{CUSTOM_NAME, ENTITY_DATA}; +use steel_registry::data_components::vanilla_components::{CUSTOM_DATA, CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; use steel_utils::nbt::merge_nbt_compounds; @@ -138,13 +138,22 @@ pub(crate) fn add_spawned_entity( } /// Applies the implicit entity data carried by an item stack. +pub(crate) fn apply_implicit_item_stack_components(entity: &SharedEntity, item_stack: &ItemStack) { + if let Some(custom_name) = item_stack.get(CUSTOM_NAME) { + entity.set_custom_name(Some(custom_name.clone())); + } + + if let Some(custom_data) = item_stack.get(CUSTOM_DATA) { + entity.set_custom_data(custom_data.copy_tag()); + } +} + +/// Applies all entity data carried by an item stack during a normal item spawn. pub(crate) fn apply_item_stack_components( entity: &SharedEntity, item_stack: &ItemStack, ) -> Result<(), EntitySpawnError> { - if let Some(custom_name) = item_stack.get(CUSTOM_NAME) { - entity.set_custom_name(Some(custom_name.clone())); - } + apply_implicit_item_stack_components(entity, item_stack); let Some(entity_data) = item_stack.get(ENTITY_DATA) else { return Ok(()); From 29a55d0c240473466eef06f622dc53b566582a40 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Thu, 27 Aug 2026 17:49:24 +0700 Subject: [PATCH 18/29] position helper --- .../src/chunk_saver/storage/entities.rs | 20 +------- steel-core/src/chunk_saver/storage/mod.rs | 3 -- .../src/chunk_saver/storage/tests/entities.rs | 3 ++ steel-core/src/entity/entity/mod.rs | 5 +- steel-core/src/entity/mod.rs | 21 ++++++++ steel-core/src/entity/registry.rs | 4 ++ steel-core/src/entity/tests/mod.rs | 50 +++++++++++++++---- 7 files changed, 75 insertions(+), 31 deletions(-) diff --git a/steel-core/src/chunk_saver/storage/entities.rs b/steel-core/src/chunk_saver/storage/entities.rs index ae005074afa..336c2884d4c 100644 --- a/steel-core/src/chunk_saver/storage/entities.rs +++ b/steel-core/src/chunk_saver/storage/entities.rs @@ -1,4 +1,5 @@ use super::*; +use crate::entity::clamp_loaded_entity_position; impl ChunkStorage { pub(super) fn entities_to_persistent(entities: &[SharedEntity]) -> Vec { @@ -116,23 +117,6 @@ impl ChunkStorage { } } - pub(super) fn clamp_loaded_entity_position(pos: DVec3) -> DVec3 { - DVec3::new( - pos.x.clamp( - -ENTITY_LOAD_MAX_HORIZONTAL_POSITION, - ENTITY_LOAD_MAX_HORIZONTAL_POSITION, - ), - pos.y.clamp( - -ENTITY_LOAD_MAX_VERTICAL_POSITION, - ENTITY_LOAD_MAX_VERTICAL_POSITION, - ), - pos.z.clamp( - -ENTITY_LOAD_MAX_HORIZONTAL_POSITION, - ENTITY_LOAD_MAX_HORIZONTAL_POSITION, - ), - ) - } - pub(super) fn entity_to_persistent( entity: &SharedEntity, visited: &mut FxHashSet, @@ -369,7 +353,7 @@ impl ChunkStorage { return None; } - let pos = Self::clamp_loaded_entity_position(stored_pos); + let pos = clamp_loaded_entity_position(stored_pos); // Validate position is within expected chunk (sanity check) let expected_chunk = ChunkPos::from_entity_pos(pos); diff --git a/steel-core/src/chunk_saver/storage/mod.rs b/steel-core/src/chunk_saver/storage/mod.rs index 77181debc21..66af96aa5e7 100644 --- a/steel-core/src/chunk_saver/storage/mod.rs +++ b/steel-core/src/chunk_saver/storage/mod.rs @@ -70,9 +70,6 @@ mod ticks; #[cfg(test)] mod tests; -const ENTITY_LOAD_MAX_HORIZONTAL_POSITION: f64 = 3.000_051_2E7; -const ENTITY_LOAD_MAX_VERTICAL_POSITION: f64 = 2.0E7; - /// Converts `Option` to the vanilla 2D data value encoding for persistence. /// -1 = none, 0 = south, 1 = west, 2 = north, 3 = east. const fn direction_to_2d(dir: Option) -> i8 { diff --git a/steel-core/src/chunk_saver/storage/tests/entities.rs b/steel-core/src/chunk_saver/storage/tests/entities.rs index a5b1d417a1e..049cfb2a0b9 100644 --- a/steel-core/src/chunk_saver/storage/tests/entities.rs +++ b/steel-core/src/chunk_saver/storage/tests/entities.rs @@ -1,4 +1,5 @@ use super::*; +use crate::entity::{ENTITY_LOAD_MAX_HORIZONTAL_POSITION, ENTITY_LOAD_MAX_VERTICAL_POSITION}; use steel_registry::RegistryEntry as _; fn test_persistent_end_crystal(pos: DVec3) -> PersistentEntity { @@ -50,6 +51,8 @@ fn persistent_entity_load_clamps_position_like_vanilla() { -ENTITY_LOAD_MAX_HORIZONTAL_POSITION, ) ); + assert_eq!(entity.base().old_position(), entity.position()); + assert_eq!(entity.base().old_rotation(), entity.rotation()); } #[test] diff --git a/steel-core/src/entity/entity/mod.rs b/steel-core/src/entity/entity/mod.rs index cbef839e61f..7630b792d11 100644 --- a/steel-core/src/entity/entity/mod.rs +++ b/steel-core/src/entity/entity/mod.rs @@ -3615,7 +3615,8 @@ pub trait Entity: EntityEventSource + ErasedType + Send + Sync + 'static { if let Some(position) = read_nbt_dvec3(&nbt, "Pos") && position.is_finite() { - self.base().set_position_local(position); + self.base() + .set_position_local(super::clamp_loaded_entity_position(position)); } if let Some(motion) = read_nbt_dvec3(&nbt, "Motion") { @@ -3692,6 +3693,8 @@ pub trait Entity: EntityEventSource + ErasedType + Send + Sync + 'static { } self.load_additional(nbt); + self.set_old_position_to_current(); + self.base().set_old_rotation_to_current(); self.sync_base_entity_data(); } diff --git a/steel-core/src/entity/mod.rs b/steel-core/src/entity/mod.rs index c7e89d72ea8..7688a3ce057 100644 --- a/steel-core/src/entity/mod.rs +++ b/steel-core/src/entity/mod.rs @@ -78,6 +78,27 @@ use crate::{enchantment_helper, entity::damage::DamageSource, player::Player}; use entities::ExperienceOrbEntity; +pub(crate) const ENTITY_LOAD_MAX_HORIZONTAL_POSITION: f64 = 3.000_051_2E7; +pub(crate) const ENTITY_LOAD_MAX_VERTICAL_POSITION: f64 = 2.0E7; + +/// Clamps an entity position using vanilla's load-time world-bound limits. +pub(crate) fn clamp_loaded_entity_position(pos: DVec3) -> DVec3 { + DVec3::new( + pos.x.clamp( + -ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + ), + pos.y.clamp( + -ENTITY_LOAD_MAX_VERTICAL_POSITION, + ENTITY_LOAD_MAX_VERTICAL_POSITION, + ), + pos.z.clamp( + -ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + ), + ) +} + fn nbt_bool(value: bool) -> NbtTag { NbtTag::Byte(i8::from(value)) } diff --git a/steel-core/src/entity/registry.rs b/steel-core/src/entity/registry.rs index 4b43c8f73ea..83f52767669 100644 --- a/steel-core/src/entity/registry.rs +++ b/steel-core/src/entity/registry.rs @@ -103,6 +103,8 @@ impl EntityRegistry { let nbt: BorrowedNbtCompoundView<'_, '_> = nbt.into(); entity.load_additional(nbt); + entity.set_old_position_to_current(); + entity.base().set_old_rotation_to_current(); entity.sync_base_entity_data(); } @@ -188,6 +190,8 @@ impl EntityRegistry { let entity: SharedEntity = Arc::new(RawEntity::from_saved(load, entity_type)); let nbt: BorrowedNbtCompoundView<'_, '_> = nbt.into(); entity.load_additional(nbt); + entity.set_old_position_to_current(); + entity.base().set_old_rotation_to_current(); entity } diff --git a/steel-core/src/entity/tests/mod.rs b/steel-core/src/entity/tests/mod.rs index 0988006445d..288c39c0f7d 100644 --- a/steel-core/src/entity/tests/mod.rs +++ b/steel-core/src/entity/tests/mod.rs @@ -1,6 +1,10 @@ -use std::sync::{Arc, Weak}; +use std::{ + io::Cursor, + sync::{Arc, Weak}, +}; use glam::DVec3; +use simdnbt::borrow::read_compound; use simdnbt::owned::{NbtCompound, NbtList, NbtTag}; use steel_protocol::packets::game::RelativeMovement; use steel_registry::blocks::{ @@ -44,14 +48,15 @@ use crate::world::{LevelReader, World}; use super::{ ActiveMobEffect, AttributeModifier, AttributeModifierOperation, DAMAGE_KNOCKBACK_POWER, - DEFAULT_SWING_DURATION, DEFAULT_TICKS_REQUIRED_TO_FREEZE, Entity, EntityBase, - EntityFluidContact, EntityLevelCallback, EntityMoveError, EntityOwnership, EntitySyncedData, - EntityVerticalMovementStateUpdate, InsideBlockEffectCollector, InsideBlockEffectType, - LivingEntity, LivingEntityBase, LivingTravelInput, MobEffectInstance, RemovalReason, - SPEED_MODIFIER_POWDER_SNOW_ID, SharedEntity, block_state_suffocates_eye_box, - closest_open_space_direction, fall_damage_reset_clip_target, fall_flying_collision_damage, - fall_flying_free_fall_interval, get_input_vector, indirect_passengers, - passenger_transition_position, passenger_transition_rotation, remove_after_changing_dimensions, + DEFAULT_SWING_DURATION, DEFAULT_TICKS_REQUIRED_TO_FREEZE, ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + ENTITY_LOAD_MAX_VERTICAL_POSITION, Entity, EntityBase, EntityFluidContact, EntityLevelCallback, + EntityMoveError, EntityOwnership, EntitySyncedData, EntityVerticalMovementStateUpdate, + InsideBlockEffectCollector, InsideBlockEffectType, LivingEntity, LivingEntityBase, + LivingTravelInput, MobEffectInstance, RemovalReason, SPEED_MODIFIER_POWDER_SNOW_ID, + SharedEntity, block_state_suffocates_eye_box, closest_open_space_direction, + fall_damage_reset_clip_target, fall_flying_collision_damage, fall_flying_free_fall_interval, + get_input_vector, indirect_passengers, passenger_transition_position, + passenger_transition_rotation, remove_after_changing_dimensions, should_apply_entity_cramming_damage, should_apply_resolved_movement, start_riding_entities, transfer_leashables_to_holder, trapdoor_usable_as_ladder_state, }; @@ -214,6 +219,33 @@ fn command_data_compare_nbt_contains_base_and_custom_data() { )); } +#[test] +fn spawn_data_clamps_position_and_refreshes_old_transform() { + let entity = TypedTestEntity::new(1, &vanilla_entities::PIG); + let mut nbt = NbtCompound::new(); + nbt.insert( + "Pos", + NbtList::Double(vec![100_000_000.0, -100_000_000.0, 100_000_000.0]), + ); + nbt.insert("Rotation", NbtList::Float(vec![90.0, -30.0])); + + let mut bytes = Vec::new(); + nbt.write(&mut bytes); + let borrowed = + read_compound(&mut Cursor::new(bytes.as_slice())).expect("test spawn data should be valid"); + entity.apply_spawn_data((&borrowed).into()); + + let expected_position = DVec3::new( + ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + -ENTITY_LOAD_MAX_VERTICAL_POSITION, + ENTITY_LOAD_MAX_HORIZONTAL_POSITION, + ); + assert_eq!(entity.position(), expected_position); + assert_eq!(entity.base().old_position(), expected_position); + assert_eq!(entity.rotation(), (90.0, -30.0)); + assert_eq!(entity.base().old_rotation(), (90.0, -30.0)); +} + #[test] fn command_data_compare_nbt_contains_implemented_living_data() { init_vanilla_registry(); From c04aaffe87835b6db7044d9b15d85c09fb20004b Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Fri, 28 Aug 2026 20:31:00 +0700 Subject: [PATCH 19/29] item stack component and may_use_item_at --- steel-core/src/behavior/items/spawn_egg.rs | 6 +++- .../entity/entities/mobs/passive/pig/mod.rs | 10 ++++++ steel-core/src/entity/entity/mod.rs | 5 +++ steel-core/src/entity/spawn.rs | 34 +++++++++++++++++-- .../src/player/game_mode/block_breaking.rs | 25 ++++++++++++-- 5 files changed, 74 insertions(+), 6 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index b9efb18b769..bd24942ad93 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -205,7 +205,11 @@ impl ItemBehavior for SpawnEggItem { if !state.is_liquid_block() { return InteractionResult::Pass; } - if !context.world.may_interact(context.player, pos) { + if !context.world.may_interact(context.player, pos) + || !context + .player + .may_use_item_at(pos, hit_result.direction, &stack) + { return InteractionResult::Fail; } diff --git a/steel-core/src/entity/entities/mobs/passive/pig/mod.rs b/steel-core/src/entity/entities/mobs/passive/pig/mod.rs index 8ac5199f984..814a07e03dc 100644 --- a/steel-core/src/entity/entities/mobs/passive/pig/mod.rs +++ b/steel-core/src/entity/entities/mobs/passive/pig/mod.rs @@ -9,6 +9,7 @@ use glam::DVec3; use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView; use simdnbt::owned::NbtCompound; use steel_macros::entity_behavior; +use steel_registry::data_components::vanilla_components::{PIG_SOUND_VARIANT, PIG_VARIANT}; use steel_registry::entity_type::{ EntityAttachmentPoint, EntityAttachments, EntityDimensions, EntityTypeRef, }; @@ -237,6 +238,15 @@ impl Entity for PigEntity { self.entity_type } + fn apply_implicit_item_components(&self, item_stack: &ItemStack) { + if let Some(variant) = item_stack.get(PIG_VARIANT) { + self.set_variant(variant.value()); + } + if let Some(sound_variant) = item_stack.get(PIG_SOUND_VARIANT) { + self.set_sound_variant(sound_variant.value()); + } + } + fn base_tick(&self) { Mob::base_tick_mob(self); } diff --git a/steel-core/src/entity/entity/mod.rs b/steel-core/src/entity/entity/mod.rs index 7630b792d11..e236c6ecbc8 100644 --- a/steel-core/src/entity/entity/mod.rs +++ b/steel-core/src/entity/entity/mod.rs @@ -3600,6 +3600,11 @@ pub trait Entity: EntityEventSource + ErasedType + Send + Sync + 'static { /// Mirrors vanilla's `Entity.readAdditionalSaveData()`. fn load_additional(&self, _nbt: BorrowedNbtCompoundView<'_, '_>) {} + /// Applies entity-specific implicit components carried by an item stack. + /// + /// Mirrors the overridable part of vanilla's `Entity.applyImplicitComponents`. + fn apply_implicit_item_components(&self, _item_stack: &ItemStack) {} + /// Applies the merged entity data used by vanilla `TypedEntityData.loadInto`. /// /// Spawn-item data is merged into the entity's current save state before it diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index dc538191813..8053e23dadf 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -139,6 +139,8 @@ pub(crate) fn add_spawned_entity( /// Applies the implicit entity data carried by an item stack. pub(crate) fn apply_implicit_item_stack_components(entity: &SharedEntity, item_stack: &ItemStack) { + entity.apply_implicit_item_components(item_stack); + if let Some(custom_name) = item_stack.get(CUSTOM_NAME) { entity.set_custom_name(Some(custom_name.clone())); } @@ -334,11 +336,14 @@ mod tests { use glam::DVec3; use simdnbt::owned::NbtCompound; + use steel_registry::data_components::CustomData; use steel_registry::data_components::components::EntityData; - use steel_registry::data_components::{CustomData, vanilla_components::ENTITY_DATA}; + use steel_registry::data_components::vanilla_components::{ENTITY_DATA, PIG_VARIANT}; use steel_registry::init_vanilla_registry; use steel_registry::item_stack::ItemStack; - use steel_registry::{vanilla_entities, vanilla_items, vanilla_pig_variants}; + use steel_registry::{ + RegistryReference, vanilla_entities, vanilla_items, vanilla_pig_variants, + }; use text_components::TextComponent; use crate::entity::entities::PigEntity; @@ -405,4 +410,29 @@ mod tests { assert!(AgeableMob::is_baby(pig.as_ref())); assert_eq!(pig.custom_name(), Some(TextComponent::plain("Existing"))); } + + #[test] + fn item_pig_variant_overrides_spawn_variant() { + init_vanilla_registry(); + + let pig = Arc::new(PigEntity::new( + &vanilla_entities::PIG, + 1, + DVec3::ZERO, + Weak::new(), + )); + pig.set_variant(&vanilla_pig_variants::WARM); + let entity: SharedEntity = pig.clone(); + + let mut spawn_egg = ItemStack::new(&vanilla_items::PIG_SPAWN_EGG); + spawn_egg.set( + PIG_VARIANT, + RegistryReference::new(&vanilla_pig_variants::COLD), + ); + + apply_item_stack_components(&entity, &spawn_egg) + .expect("valid pig variant component should apply"); + + assert_eq!(pig.variant().key, vanilla_pig_variants::COLD.key); + } } diff --git a/steel-core/src/player/game_mode/block_breaking.rs b/steel-core/src/player/game_mode/block_breaking.rs index 316bd2e0411..96b275c6ca6 100644 --- a/steel-core/src/player/game_mode/block_breaking.rs +++ b/steel-core/src/player/game_mode/block_breaking.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use steel_protocol::packets::game::CBlockUpdate; use steel_registry::blocks::block_state_ext::BlockStateExt; use steel_registry::data_components::AdventureModePredicate; -use steel_registry::data_components::vanilla_components::CAN_BREAK; +use steel_registry::data_components::vanilla_components::{CAN_BREAK, CAN_PLACE_ON}; use steel_registry::vanilla_attributes; use steel_registry::{ REGISTRY, blocks::properties::Direction, item_stack::ItemStack, vanilla_blocks, @@ -28,6 +28,25 @@ use crate::player::food_data::food_constants; use crate::world::{ConditionalBlockSetResult, World, game_event::GameEventContext}; impl Player { + /// Mirrors vanilla `Player.mayUseItemAt` for adventure-mode item use. + pub(crate) fn may_use_item_at( + &self, + pos: BlockPos, + direction: Direction, + item_stack: &ItemStack, + ) -> bool { + if self.abilities.lock().may_build { + return true; + } + + let Some(can_place_on) = item_stack.get(CAN_PLACE_ON) else { + return false; + }; + let target = pos.relative(direction.opposite()); + let world = self.get_world(); + Self::matches_adventure_mode_predicate(can_place_on, &world, target) + } + /// Mirrors vanilla `Player.blockActionRestricted` for block breaking. pub(super) fn block_action_restricted(&self, world: &World, pos: BlockPos) -> bool { let game_mode = self.game_mode(); @@ -55,10 +74,10 @@ impl Player { let Some(can_break) = can_break else { return true; }; - !Self::can_break_block_in_adventure_mode(&can_break, world, pos) + !Self::matches_adventure_mode_predicate(&can_break, world, pos) } - fn can_break_block_in_adventure_mode( + fn matches_adventure_mode_predicate( predicate: &AdventureModePredicate, world: &World, pos: BlockPos, From e43db9dd54b9f611cc549d60adfe6c033bcaea6f Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Fri, 28 Aug 2026 21:38:34 +0700 Subject: [PATCH 20/29] fix non-op exploit --- steel-core/src/behavior/items/spawn_egg.rs | 1 + steel-core/src/entity/spawn.rs | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index bd24942ad93..33834052a11 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -59,6 +59,7 @@ impl SpawnEggItem { finalize_spawn: true, play_ambient_sound: true, item_stack: Some(stack), + user_is_operator: player.is_operator(), }; if spawn_entity(world, request).is_err() { return InteractionResult::Fail; diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 8053e23dadf..64c78ebc78e 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -89,6 +89,7 @@ pub(crate) struct EntitySpawnRequest<'a> { pub finalize_spawn: bool, pub play_ambient_sound: bool, pub item_stack: Option<&'a ItemStack>, + pub user_is_operator: bool, } /// Failure from the shared spawn coordinator. @@ -154,6 +155,7 @@ pub(crate) fn apply_implicit_item_stack_components(entity: &SharedEntity, item_s pub(crate) fn apply_item_stack_components( entity: &SharedEntity, item_stack: &ItemStack, + user_is_operator: bool, ) -> Result<(), EntitySpawnError> { apply_implicit_item_stack_components(entity, item_stack); @@ -165,6 +167,10 @@ pub(crate) fn apply_item_stack_components( return Ok(()); } + if only_op_can_set_custom_data(entity.entity_type()) && !user_is_operator { + return Ok(()); + } + let mut merged = entity.nbt_for_data_compare(); merge_nbt_compounds(&mut merged, &entity_data.data().copy_tag()); @@ -176,6 +182,14 @@ pub(crate) fn apply_item_stack_components( Ok(()) } +fn only_op_can_set_custom_data(entity_type: EntityTypeRef) -> bool { + entity_type.key.namespace == "minecraft" + && matches!( + entity_type.key.path.as_ref(), + "falling_block" | "command_block_minecart" | "spawner_minecart" + ) +} + /// Mirrors vanilla `EntityType.spawn` for server-side entity creation. pub(crate) fn spawn_entity( world: &Arc, @@ -227,7 +241,7 @@ pub(crate) fn spawn_entity( } if let Some(item_stack) = request.item_stack { - apply_item_stack_components(&entity, item_stack)?; + apply_item_stack_components(&entity, item_stack, request.user_is_operator)?; } add_spawned_entity(world, Arc::clone(&entity)).map_err(|_| EntitySpawnError::AddEntity)?; @@ -402,7 +416,7 @@ mod tests { let mut spawn_egg = ItemStack::new(&vanilla_items::PIG_SPAWN_EGG); spawn_egg.set(ENTITY_DATA, entity_data); - apply_item_stack_components(&entity, &spawn_egg) + apply_item_stack_components(&entity, &spawn_egg, false) .expect("valid typed entity data should load"); assert_eq!(pig.get_age(), pig.get_baby_start_age()); @@ -430,7 +444,7 @@ mod tests { RegistryReference::new(&vanilla_pig_variants::COLD), ); - apply_item_stack_components(&entity, &spawn_egg) + apply_item_stack_components(&entity, &spawn_egg, false) .expect("valid pig variant component should apply"); assert_eq!(pig.variant().key, vanilla_pig_variants::COLD.key); From 45edfaadf050c7ff28b090d237cf20aa6ecb1c86 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Mon, 31 Aug 2026 07:55:05 +0700 Subject: [PATCH 21/29] add more mob component --- .../entities/mobs/passive/chicken/mod.rs | 10 ++ .../entity/entities/mobs/passive/cow/mod.rs | 10 ++ .../entity/entities/mobs/passive/sheep/mod.rs | 8 +- steel-core/src/entity/spawn.rs | 107 ++++++++++++++++-- 4 files changed, 125 insertions(+), 10 deletions(-) diff --git a/steel-core/src/entity/entities/mobs/passive/chicken/mod.rs b/steel-core/src/entity/entities/mobs/passive/chicken/mod.rs index 07c8ebb282e..d96254b9c22 100644 --- a/steel-core/src/entity/entities/mobs/passive/chicken/mod.rs +++ b/steel-core/src/entity/entities/mobs/passive/chicken/mod.rs @@ -10,6 +10,7 @@ use simdnbt::owned::NbtCompound; use steel_macros::entity_behavior; use steel_registry::chicken_sound_variant::{ChickenAge, ChickenSoundVariantRef}; use steel_registry::chicken_variant::ChickenVariantRef; +use steel_registry::data_components::vanilla_components::{CHICKEN_SOUND_VARIANT, CHICKEN_VARIANT}; use steel_registry::entity_type::{ EntityAttachmentPoint, EntityAttachments, EntityDimensions, EntityTypeRef, }; @@ -404,6 +405,15 @@ impl Entity for ChickenEntity { self.entity_type } + fn apply_implicit_item_components(&self, item_stack: &ItemStack) { + if let Some(variant) = item_stack.get(CHICKEN_VARIANT) { + self.set_variant(variant.value()); + } + if let Some(sound_variant) = item_stack.get(CHICKEN_SOUND_VARIANT) { + self.set_sound_variant(sound_variant.value()); + } + } + fn base_tick(&self) { Mob::base_tick_mob(self); } diff --git a/steel-core/src/entity/entities/mobs/passive/cow/mod.rs b/steel-core/src/entity/entities/mobs/passive/cow/mod.rs index 3b9c8bf46ee..cb6dffdf7e3 100644 --- a/steel-core/src/entity/entities/mobs/passive/cow/mod.rs +++ b/steel-core/src/entity/entities/mobs/passive/cow/mod.rs @@ -10,6 +10,7 @@ use steel_macros::entity_behavior; use steel_protocol::packets::game::SoundSource; use steel_registry::cow_sound_variant::CowSoundVariantRef; use steel_registry::cow_variant::CowVariantRef; +use steel_registry::data_components::vanilla_components::{COW_SOUND_VARIANT, COW_VARIANT}; use steel_registry::entity_type::{ EntityAttachmentPoint, EntityAttachments, EntityDimensions, EntityTypeRef, }; @@ -247,6 +248,15 @@ impl Entity for CowEntity { self.entity_type } + fn apply_implicit_item_components(&self, item_stack: &ItemStack) { + if let Some(variant) = item_stack.get(COW_VARIANT) { + self.set_variant(variant.value()); + } + if let Some(sound_variant) = item_stack.get(COW_SOUND_VARIANT) { + self.set_sound_variant(sound_variant.value()); + } + } + fn base_tick(&self) { Mob::base_tick_mob(self); } diff --git a/steel-core/src/entity/entities/mobs/passive/sheep/mod.rs b/steel-core/src/entity/entities/mobs/passive/sheep/mod.rs index 1316d0bfc9a..b5cbfbef6e1 100644 --- a/steel-core/src/entity/entities/mobs/passive/sheep/mod.rs +++ b/steel-core/src/entity/entities/mobs/passive/sheep/mod.rs @@ -7,7 +7,7 @@ use simdnbt::owned::NbtCompound; use steel_macros::entity_behavior; use steel_protocol::packets::game::SoundSource; use steel_registry::biome::BiomeRef; -use steel_registry::data_components::vanilla_components::DYE; +use steel_registry::data_components::vanilla_components::{DYE, SHEEP_COLOR}; use steel_registry::entity_type::{ EntityAttachmentPoint, EntityAttachments, EntityDimensions, EntityTypeRef, }; @@ -361,6 +361,12 @@ impl Entity for SheepEntity { self.entity_type } + fn apply_implicit_item_components(&self, item_stack: &ItemStack) { + if let Some(color) = item_stack.get(SHEEP_COLOR) { + self.set_color(*color); + } + } + fn base_tick(&self) { Mob::base_tick_mob(self); } diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 64c78ebc78e..c5b1d510fbb 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -1,10 +1,11 @@ -use std::{io::Cursor, sync::Arc}; +use std::{io::Cursor, ptr, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; use steel_registry::data_components::vanilla_components::{CUSTOM_DATA, CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; +use steel_registry::vanilla_entities; use steel_utils::nbt::merge_nbt_compounds; use steel_utils::{BlockPos, WorldAabb, axis::Axis, types::Difficulty, wrap_degrees}; @@ -183,11 +184,9 @@ pub(crate) fn apply_item_stack_components( } fn only_op_can_set_custom_data(entity_type: EntityTypeRef) -> bool { - entity_type.key.namespace == "minecraft" - && matches!( - entity_type.key.path.as_ref(), - "falling_block" | "command_block_minecart" | "spawner_minecart" - ) + ptr::eq(entity_type, &vanilla_entities::FALLING_BLOCK) + || ptr::eq(entity_type, &vanilla_entities::COMMAND_BLOCK_MINECART) + || ptr::eq(entity_type, &vanilla_entities::SPAWNER_MINECART) } /// Mirrors vanilla `EntityType.spawn` for server-side entity creation. @@ -352,15 +351,20 @@ mod tests { use simdnbt::owned::NbtCompound; use steel_registry::data_components::CustomData; use steel_registry::data_components::components::EntityData; - use steel_registry::data_components::vanilla_components::{ENTITY_DATA, PIG_VARIANT}; + use steel_registry::data_components::vanilla_components::{ + CHICKEN_SOUND_VARIANT, CHICKEN_VARIANT, COW_SOUND_VARIANT, COW_VARIANT, ENTITY_DATA, + PIG_VARIANT, SHEEP_COLOR, + }; use steel_registry::init_vanilla_registry; use steel_registry::item_stack::ItemStack; use steel_registry::{ - RegistryReference, vanilla_entities, vanilla_items, vanilla_pig_variants, + DyeColor, RegistryReference, vanilla_chicken_sound_variants, vanilla_chicken_variants, + vanilla_cow_sound_variants, vanilla_cow_variants, vanilla_entities, vanilla_items, + vanilla_pig_variants, }; use text_components::TextComponent; - use crate::entity::entities::PigEntity; + use crate::entity::entities::{ChickenEntity, CowEntity, PigEntity, SheepEntity}; use crate::entity::{AgeableMob, Entity, SharedEntity}; use super::{AgeableMobGroupData, apply_item_stack_components}; @@ -449,4 +453,89 @@ mod tests { assert_eq!(pig.variant().key, vanilla_pig_variants::COLD.key); } + + #[test] + fn item_cow_components_override_spawn_state() { + init_vanilla_registry(); + + let cow = Arc::new(CowEntity::new( + &vanilla_entities::COW, + 1, + DVec3::ZERO, + Weak::new(), + )); + let entity: SharedEntity = cow.clone(); + + let mut spawn_egg = ItemStack::new(&vanilla_items::COW_SPAWN_EGG); + spawn_egg.set( + COW_VARIANT, + RegistryReference::new(&vanilla_cow_variants::COLD), + ); + spawn_egg.set( + COW_SOUND_VARIANT, + RegistryReference::new(&vanilla_cow_sound_variants::MOODY), + ); + + apply_item_stack_components(&entity, &spawn_egg, false) + .expect("valid cow components should apply"); + + assert_eq!(cow.variant().key, vanilla_cow_variants::COLD.key); + assert_eq!( + cow.sound_variant().key, + vanilla_cow_sound_variants::MOODY.key + ); + } + + #[test] + fn item_chicken_components_override_spawn_state() { + init_vanilla_registry(); + + let chicken = Arc::new(ChickenEntity::new( + &vanilla_entities::CHICKEN, + 1, + DVec3::ZERO, + Weak::new(), + )); + let entity: SharedEntity = chicken.clone(); + + let mut spawn_egg = ItemStack::new(&vanilla_items::CHICKEN_SPAWN_EGG); + spawn_egg.set( + CHICKEN_VARIANT, + RegistryReference::new(&vanilla_chicken_variants::COLD), + ); + spawn_egg.set( + CHICKEN_SOUND_VARIANT, + RegistryReference::new(&vanilla_chicken_sound_variants::PICKY), + ); + + apply_item_stack_components(&entity, &spawn_egg, false) + .expect("valid chicken components should apply"); + + assert_eq!(chicken.variant().key, vanilla_chicken_variants::COLD.key); + assert_eq!( + chicken.sound_variant().key, + vanilla_chicken_sound_variants::PICKY.key + ); + } + + #[test] + fn item_sheep_color_overrides_spawn_state() { + init_vanilla_registry(); + + let sheep = Arc::new(SheepEntity::new( + &vanilla_entities::SHEEP, + 1, + DVec3::ZERO, + Weak::new(), + )); + let entity: SharedEntity = sheep.clone(); + + let mut spawn_egg = ItemStack::new(&vanilla_items::SHEEP_SPAWN_EGG); + spawn_egg.set(SHEEP_COLOR, DyeColor::Pink); + + apply_item_stack_components(&entity, &spawn_egg, false) + .expect("valid sheep color should apply"); + + assert_eq!(sheep.color(), DyeColor::Pink); + } } From 7d6e1f7b256abc8e510b8ac22252a43a78bb2173 Mon Sep 17 00:00:00 2001 From: ChocoDev Date: Fri, 4 Sep 2026 16:18:18 +0700 Subject: [PATCH 22/29] im stupid --- steel-core/src/entity/spawn.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index b8ffa6cb901..698428ebd72 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -1,4 +1,4 @@ -use std::{io::Cursor, ptr, sync::Arc}; +use std::{io::Cursor, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; @@ -184,9 +184,9 @@ pub(crate) fn apply_item_stack_components( } fn only_op_can_set_custom_data(entity_type: EntityTypeRef) -> bool { - ptr::eq(entity_type, &raw const vanilla_entities::FALLING_BLOCK) - || ptr::eq(entity_type, &raw const vanilla_entities::COMMAND_BLOCK_MINECART) - || ptr::eq(entity_type, &raw const vanilla_entities::SPAWNER_MINECART) + entity_type == &vanilla_entities::FALLING_BLOCK + || entity_type == &vanilla_entities::COMMAND_BLOCK_MINECART + || entity_type == &vanilla_entities::SPAWNER_MINECART } /// Mirrors vanilla `EntityType.spawn` for server-side entity creation. From f8de499754e577c87dd42a31bb09ba85aa2f5d9b Mon Sep 17 00:00:00 2001 From: JunkyDeveloper <56804181+JunkyDeveloper@users.noreply.github.com> Date: Tue, 8 Sep 2026 01:28:24 +0200 Subject: [PATCH 23/29] move work to spawner PR --- steel-core/src/behavior/items/spawn_egg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 53e35bc0a7f..9718a5b7c2d 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -151,7 +151,7 @@ impl ItemBehavior for SpawnEggItem { let clicked_pos = context.hit_result.block_pos; let clicked_state = context.world.get_block_state(clicked_pos); if clicked_state.get_block() == &vanilla_blocks::SPAWNER { - // TODO: Use spawn eggs on a spawner block. + // TODO: Use spawn eggs on a spawner block and also check if the condition is correct // Spawner block-entity mutation is a separate foundation; do not // incorrectly create a mob beside a spawner in its place. return InteractionResult::Fail; From ac10bf7498b7270c2bb932473ab899654227332f Mon Sep 17 00:00:00 2001 From: JunkyDeveloper <56804181+JunkyDeveloper@users.noreply.github.com> Date: Tue, 8 Sep 2026 01:36:38 +0200 Subject: [PATCH 24/29] fix merge conflicts + use consume instead of shrink --- steel-core/src/behavior/items/spawn_egg.rs | 13 +++---------- steel-core/src/entity/mob/mod.rs | 1 + steel-core/src/entity/spawn.rs | 3 ++- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 9718a5b7c2d..37e58884e62 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -17,10 +17,7 @@ use crate::behavior::{ BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, ITEM_BEHAVIORS, InteractionResult, InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, }; -use crate::entity::{ - AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity, - add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity, -}; +use crate::entity::{add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity, AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, LivingEntity, Mob, SharedEntity}; use crate::player::Player; use crate::world::ClipFluid; use crate::world::World; @@ -65,9 +62,7 @@ impl SpawnEggItem { return InteractionResult::Fail; } - if !player.has_infinite_materials() { - inventory.with_item(|item| item.shrink(1)); - } + inventory.with_item(|item| item.consume_one(player.has_infinite_materials())); world.game_event( &vanilla_game_events::ENTITY_PLACE, spawn_pos, @@ -92,9 +87,7 @@ impl SpawnEggItem { if Self::spawn_offspring(stack, parent).is_none() { return InteractionResult::Pass; } - if !player.has_infinite_materials() { - stack.shrink(1); - } + stack.consume_one(player.has_infinite_materials()); InteractionResult::SuccessServer } diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index f4d0c6431f3..c2e8cdd1136 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -54,6 +54,7 @@ use crate::inventory::equipment::EquipmentSlot; use crate::physics::MoveResult; use crate::player::Player; use crate::world::{LevelReader, World}; +use crate::world::game_event::GameEventContext; const MOB_FLAG_NO_AI: i8 = 1; const MOB_FLAG_LEFT_HANDED: i8 = 2; diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 698428ebd72..92e9fbb30be 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -2,12 +2,13 @@ use std::{io::Cursor, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; +use steel_math::wrap_degrees; use steel_registry::data_components::vanilla_components::{CUSTOM_DATA, CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; use steel_registry::vanilla_entities; use steel_utils::nbt::merge_nbt_compounds; -use steel_utils::{BlockPos, WorldAabb, axis::Axis, types::Difficulty, wrap_degrees}; +use steel_utils::{BlockPos, WorldAabb, axis::Axis, types::Difficulty}; use super::{AddEntityError, ENTITIES, SharedEntity, next_entity_id}; use crate::physics::{CollisionWorld, WorldCollisionProvider, collide}; From 6b3e5dfb612c22c463182b73a498b67294e64b63 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper <56804181+JunkyDeveloper@users.noreply.github.com> Date: Tue, 8 Sep 2026 01:37:44 +0200 Subject: [PATCH 25/29] fmt --- steel-core/src/behavior/items/spawn_egg.rs | 5 ++++- steel-core/src/entity/mob/mod.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/steel-core/src/behavior/items/spawn_egg.rs b/steel-core/src/behavior/items/spawn_egg.rs index 37e58884e62..f4cf3b73e5a 100644 --- a/steel-core/src/behavior/items/spawn_egg.rs +++ b/steel-core/src/behavior/items/spawn_egg.rs @@ -17,7 +17,10 @@ use crate::behavior::{ BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, ITEM_BEHAVIORS, InteractionResult, InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, }; -use crate::entity::{add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity, AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, LivingEntity, Mob, SharedEntity}; +use crate::entity::{ + AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity, + add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity, +}; use crate::player::Player; use crate::world::ClipFluid; use crate::world::World; diff --git a/steel-core/src/entity/mob/mod.rs b/steel-core/src/entity/mob/mod.rs index c2e8cdd1136..4c996ac0118 100644 --- a/steel-core/src/entity/mob/mod.rs +++ b/steel-core/src/entity/mob/mod.rs @@ -53,8 +53,8 @@ use crate::entity::{ use crate::inventory::equipment::EquipmentSlot; use crate::physics::MoveResult; use crate::player::Player; -use crate::world::{LevelReader, World}; use crate::world::game_event::GameEventContext; +use crate::world::{LevelReader, World}; const MOB_FLAG_NO_AI: i8 = 1; const MOB_FLAG_LEFT_HANDED: i8 = 2; From 698b34b4a050923378e9029acc2c1a485f1c3ba1 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper <56804181+JunkyDeveloper@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:17:17 +0200 Subject: [PATCH 26/29] will not compile only to pitch idea --- steel-core/src/entity/spawn.rs | 9 +-------- steel-registry/build/entities.rs | 2 ++ steel-registry/src/entity_type.rs | 3 +++ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index 92e9fbb30be..e15b0d92717 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -6,7 +6,6 @@ use steel_math::wrap_degrees; use steel_registry::data_components::vanilla_components::{CUSTOM_DATA, CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; -use steel_registry::vanilla_entities; use steel_utils::nbt::merge_nbt_compounds; use steel_utils::{BlockPos, WorldAabb, axis::Axis, types::Difficulty}; @@ -169,7 +168,7 @@ pub(crate) fn apply_item_stack_components( return Ok(()); } - if only_op_can_set_custom_data(entity.entity_type()) && !user_is_operator { + if entity.entity_type().only_op_can_set_nbt && !user_is_operator { return Ok(()); } @@ -184,12 +183,6 @@ pub(crate) fn apply_item_stack_components( Ok(()) } -fn only_op_can_set_custom_data(entity_type: EntityTypeRef) -> bool { - entity_type == &vanilla_entities::FALLING_BLOCK - || entity_type == &vanilla_entities::COMMAND_BLOCK_MINECART - || entity_type == &vanilla_entities::SPAWNER_MINECART -} - /// Mirrors vanilla `EntityType.spawn` for server-side entity creation. pub(crate) fn spawn_entity( world: &Arc, diff --git a/steel-registry/build/entities.rs b/steel-registry/build/entities.rs index 7e218b992ba..6509288b558 100644 --- a/steel-registry/build/entities.rs +++ b/steel-registry/build/entities.rs @@ -180,6 +180,7 @@ pub(crate) fn build() -> TokenStream { let allowed_in_peaceful = entity_type.allowed_in_peaceful; let can_spawn_far = entity_type.can_spawn_far_from_player; let can_serialize = entity_type.can_serialize; + let only_op_can_set_nbt = vanilla_only_op_can_set_nbt(entity_type_key); let is_abstract_boat = entity_type .class_hierarchy .iter() @@ -261,6 +262,7 @@ pub(crate) fn build() -> TokenStream { allowed_in_peaceful: #allowed_in_peaceful, can_spawn_far_from_player: #can_spawn_far, can_serialize: #can_serialize, + only_op_can_set_nbt: #only_op_can_set_nbt, is_abstract_boat: #is_abstract_boat, is_abstract_minecart: #is_abstract_minecart, is_projectile: #is_projectile, diff --git a/steel-registry/src/entity_type.rs b/steel-registry/src/entity_type.rs index bc2b143741a..5757b17e476 100644 --- a/steel-registry/src/entity_type.rs +++ b/steel-registry/src/entity_type.rs @@ -295,6 +295,9 @@ pub struct EntityType { /// Whether this entity type can be serialized to disk. /// Set to false for transient entities (lightning, fishing hooks, players). pub can_serialize: bool, + /// Whether only server operators may set this entity's data via a spawn + /// item's `ENTITY_DATA` component. Mirrors vanilla `EntityType.onlyOpCanSetNbt`. + pub only_op_can_set_nbt: bool, /// Whether vanilla class hierarchy makes this entity an `AbstractBoat`. pub is_abstract_boat: bool, /// Whether vanilla class hierarchy makes this entity an `AbstractMinecart`. From 00a6812bffcf138856b3fe5a3a9567c39305a557 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Tue, 8 Sep 2026 23:46:38 +0200 Subject: [PATCH 27/29] fix only_op_can_set_nbt --- steel-registry/build/entities.rs | 3 +- steel-registry/build_assets/entities.json | 158 ++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/steel-registry/build/entities.rs b/steel-registry/build/entities.rs index 6509288b558..f75fcbc8c97 100644 --- a/steel-registry/build/entities.rs +++ b/steel-registry/build/entities.rs @@ -29,6 +29,7 @@ struct EntityTypeEntry { class_hierarchy: Vec, #[serde(default = "default_can_serialize")] can_serialize: bool, + only_op_can_set_nbt: bool, #[serde(default)] flags: Option, #[serde(default)] @@ -180,7 +181,7 @@ pub(crate) fn build() -> TokenStream { let allowed_in_peaceful = entity_type.allowed_in_peaceful; let can_spawn_far = entity_type.can_spawn_far_from_player; let can_serialize = entity_type.can_serialize; - let only_op_can_set_nbt = vanilla_only_op_can_set_nbt(entity_type_key); + let only_op_can_set_nbt = entity_type.only_op_can_set_nbt; let is_abstract_boat = entity_type .class_hierarchy .iter() diff --git a/steel-registry/build_assets/entities.json b/steel-registry/build_assets/entities.json index b2aa9fc8339..8a014242379 100644 --- a/steel-registry/build_assets/entities.json +++ b/steel-registry/build_assets/entities.json @@ -63,6 +63,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -297,6 +298,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -540,6 +542,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.allay.Allay", "class_hierarchy": [ @@ -833,6 +836,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.AreaEffectCloud", "class_hierarchy": [ @@ -1048,6 +1052,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.armadillo.Armadillo", "class_hierarchy": [ @@ -1366,6 +1371,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.decoration.ArmorStand", "class_hierarchy": [ @@ -1706,6 +1712,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.arrow.Arrow", "class_hierarchy": [ @@ -1937,6 +1944,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.axolotl.Axolotl", "class_hierarchy": [ @@ -2280,6 +2288,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestRaft", "class_hierarchy": [ @@ -2514,6 +2523,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Raft", "class_hierarchy": [ @@ -2753,6 +2763,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.ambient.Bat", "class_hierarchy": [ @@ -3061,6 +3072,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.bee.Bee", "class_hierarchy": [ @@ -3393,6 +3405,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -3627,6 +3640,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -3874,6 +3888,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Blaze", "class_hierarchy": [ @@ -4166,6 +4181,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.Display$BlockDisplay", "class_hierarchy": [ @@ -4507,6 +4523,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.skeleton.Bogged", "class_hierarchy": [ @@ -4820,6 +4837,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.breeze.Breeze", "class_hierarchy": [ @@ -5106,6 +5124,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.hurtingprojectile.windcharge.BreezeWindCharge", "class_hierarchy": [ @@ -5301,6 +5320,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.camel.Camel", "class_hierarchy": [ @@ -5674,6 +5694,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.camel.CamelHusk", "class_hierarchy": [ @@ -6047,6 +6068,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.feline.Cat", "class_hierarchy": [ @@ -6447,6 +6469,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.spider.CaveSpider", "class_hierarchy": [ @@ -6747,6 +6770,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -6981,6 +7005,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -7219,6 +7244,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.MinecartChest", "class_hierarchy": [ @@ -7464,6 +7490,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.chicken.Chicken", "class_hierarchy": [ @@ -7815,6 +7842,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.fish.Cod", "class_hierarchy": [ @@ -8131,6 +8159,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.golem.CopperGolem", "class_hierarchy": [ @@ -8434,6 +8463,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": true, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.MinecartCommandBlock", "class_hierarchy": [ @@ -8701,6 +8731,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.cow.Cow", "class_hierarchy": [ @@ -9048,6 +9079,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.creaking.Creaking", "class_hierarchy": [ @@ -9383,6 +9415,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Creeper", "class_hierarchy": [ @@ -9695,6 +9728,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -9929,6 +9963,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -10180,6 +10215,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.dolphin.Dolphin", "class_hierarchy": [ @@ -10535,6 +10571,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.Donkey", "class_hierarchy": [ @@ -10879,6 +10916,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.hurtingprojectile.DragonFireball", "class_hierarchy": [ @@ -11066,6 +11104,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.zombie.Drowned", "class_hierarchy": [ @@ -11387,6 +11426,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownEgg", "class_hierarchy": [ @@ -11595,6 +11635,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.ElderGuardian", "class_hierarchy": [ @@ -11916,6 +11957,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.EnderMan", "class_hierarchy": [ @@ -12243,6 +12285,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Endermite", "class_hierarchy": [ @@ -12572,6 +12615,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.boss.enderdragon.EnderDragon", "class_hierarchy": [ @@ -12863,6 +12907,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownEnderpearl", "class_hierarchy": [ @@ -13046,6 +13091,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.boss.enderdragon.EndCrystal", "class_hierarchy": [ @@ -13261,6 +13307,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.illager.Evoker", "class_hierarchy": [ @@ -13579,6 +13626,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.EvokerFangs", "class_hierarchy": [ @@ -13745,6 +13793,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownExperienceBottle", "class_hierarchy": [ @@ -13928,6 +13977,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.ExperienceOrb", "class_hierarchy": [ @@ -14096,6 +14146,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.EyeOfEnder", "class_hierarchy": [ @@ -14267,6 +14318,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": true, "synched_data": { "java_class": "net.minecraft.world.entity.item.FallingBlockEntity", "class_hierarchy": [ @@ -14451,6 +14503,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.hurtingprojectile.LargeFireball", "class_hierarchy": [ @@ -14638,6 +14691,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.FireworkRocketEntity", "class_hierarchy": [ @@ -14856,6 +14910,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.fox.Fox", "class_hierarchy": [ @@ -15224,6 +15279,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.frog.Frog", "class_hierarchy": [ @@ -15557,6 +15613,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.MinecartFurnace", "class_hierarchy": [ @@ -15800,6 +15857,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Ghast", "class_hierarchy": [ @@ -16120,6 +16178,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.happyghast.HappyGhast", "class_hierarchy": [ @@ -16464,6 +16523,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Giant", "class_hierarchy": [ @@ -16750,6 +16810,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.decoration.GlowItemFrame", "class_hierarchy": [ @@ -16981,6 +17042,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.squid.GlowSquid", "class_hierarchy": [ @@ -17323,6 +17385,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.goat.Goat", "class_hierarchy": [ @@ -17675,6 +17738,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Guardian", "class_hierarchy": [ @@ -17996,6 +18060,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.hoglin.Hoglin", "class_hierarchy": [ @@ -18322,6 +18387,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.MinecartHopper", "class_hierarchy": [ @@ -18571,6 +18637,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.Horse", "class_hierarchy": [ @@ -18928,6 +18995,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.zombie.Husk", "class_hierarchy": [ @@ -19274,6 +19342,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.illager.Illusioner", "class_hierarchy": [ @@ -19592,6 +19661,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.Interaction", "class_hierarchy": [ @@ -19797,6 +19867,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.golem.IronGolem", "class_hierarchy": [ @@ -20085,6 +20156,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.item.ItemEntity", "class_hierarchy": [ @@ -20257,6 +20329,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.Display$ItemDisplay", "class_hierarchy": [ @@ -20589,6 +20662,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.decoration.ItemFrame", "class_hierarchy": [ @@ -20795,6 +20869,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -21029,6 +21104,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -21259,6 +21335,7 @@ "allowed_in_peaceful": true, "can_serialize": false, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.decoration.LeashFenceKnotEntity", "class_hierarchy": [ @@ -21417,6 +21494,7 @@ "allowed_in_peaceful": true, "can_serialize": false, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.LightningBolt", "class_hierarchy": [ @@ -21604,6 +21682,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.Llama", "class_hierarchy": [ @@ -21966,6 +22045,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.LlamaSpit", "class_hierarchy": [ @@ -22149,6 +22229,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.cubemob.MagmaCube", "class_hierarchy": [ @@ -22471,6 +22552,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -22705,6 +22787,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -22939,6 +23022,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.decoration.Mannequin", "class_hierarchy": [ @@ -23244,6 +23328,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.Marker", "class_hierarchy": [ @@ -23406,6 +23491,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.Minecart", "class_hierarchy": [ @@ -23651,6 +23737,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.cow.MushroomCow", "class_hierarchy": [ @@ -24002,6 +24089,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.Mule", "class_hierarchy": [ @@ -24371,6 +24459,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.nautilus.Nautilus", "class_hierarchy": [ @@ -24726,6 +24815,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -24960,6 +25050,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -25211,6 +25302,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.feline.Ocelot", "class_hierarchy": [ @@ -25526,6 +25618,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.OminousItemSpawner", "class_hierarchy": [ @@ -25702,6 +25795,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.decoration.painting.Painting", "class_hierarchy": [ @@ -25900,6 +25994,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -26134,6 +26229,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -26385,6 +26481,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.panda.Panda", "class_hierarchy": [ @@ -26765,6 +26862,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.skeleton.Parched", "class_hierarchy": [ @@ -27076,6 +27174,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.parrot.Parrot", "class_hierarchy": [ @@ -27437,6 +27536,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Phantom", "class_hierarchy": [ @@ -27742,6 +27842,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.pig.Pig", "class_hierarchy": [ @@ -28097,6 +28198,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.piglin.Piglin", "class_hierarchy": [ @@ -28444,6 +28546,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.piglin.PiglinBrute", "class_hierarchy": [ @@ -28769,6 +28872,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.illager.Pillager", "class_hierarchy": [ @@ -29108,6 +29212,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.polarbear.PolarBear", "class_hierarchy": [ @@ -29439,6 +29544,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownSplashPotion", "class_hierarchy": [ @@ -29642,6 +29748,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownLingeringPotion", "class_hierarchy": [ @@ -29854,6 +29961,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.fish.Pufferfish", "class_hierarchy": [ @@ -30184,6 +30292,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.rabbit.Rabbit", "class_hierarchy": [ @@ -30528,6 +30637,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Ravager", "class_hierarchy": [ @@ -30853,6 +30963,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.fish.Salmon", "class_hierarchy": [ @@ -31187,6 +31298,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.sheep.Sheep", "class_hierarchy": [ @@ -31522,6 +31634,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Shulker", "class_hierarchy": [ @@ -31829,6 +31942,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.ShulkerBullet", "class_hierarchy": [ @@ -32008,6 +32122,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Silverfish", "class_hierarchy": [ @@ -32307,6 +32422,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.skeleton.Skeleton", "class_hierarchy": [ @@ -32628,6 +32744,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.SkeletonHorse", "class_hierarchy": [ @@ -32971,6 +33088,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.cubemob.Slime", "class_hierarchy": [ @@ -33297,6 +33415,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.hurtingprojectile.SmallFireball", "class_hierarchy": [ @@ -33505,6 +33624,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.sniffer.Sniffer", "class_hierarchy": [ @@ -33839,6 +33959,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.throwableitemprojectile.Snowball", "class_hierarchy": [ @@ -34043,6 +34164,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.golem.SnowGolem", "class_hierarchy": [ @@ -34338,6 +34460,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": true, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.MinecartSpawner", "class_hierarchy": [ @@ -34562,6 +34685,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.arrow.SpectralArrow", "class_hierarchy": [ @@ -34775,6 +34899,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.spider.Spider", "class_hierarchy": [ @@ -35071,6 +35196,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.Boat", "class_hierarchy": [ @@ -35305,6 +35431,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.boat.ChestBoat", "class_hierarchy": [ @@ -35556,6 +35683,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.squid.Squid", "class_hierarchy": [ @@ -35880,6 +36008,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.skeleton.Stray", "class_hierarchy": [ @@ -36183,6 +36312,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Strider", "class_hierarchy": [ @@ -36530,6 +36660,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.cubemob.SulfurCube", "class_hierarchy": [ @@ -36891,6 +37022,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.frog.Tadpole", "class_hierarchy": [ @@ -37201,6 +37333,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.Display$TextDisplay", "class_hierarchy": [ @@ -37549,6 +37682,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.item.PrimedTnt", "class_hierarchy": [ @@ -37733,6 +37867,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.vehicle.minecart.MinecartTNT", "class_hierarchy": [ @@ -37986,6 +38121,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.TraderLlama", "class_hierarchy": [ @@ -38356,6 +38492,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.arrow.ThrownTrident", "class_hierarchy": [ @@ -38599,6 +38736,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.fish.TropicalFish", "class_hierarchy": [ @@ -38933,6 +39071,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.turtle.Turtle", "class_hierarchy": [ @@ -39276,6 +39415,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Vex", "class_hierarchy": [ @@ -39589,6 +39729,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.npc.villager.Villager", "class_hierarchy": [ @@ -39961,6 +40102,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.illager.Vindicator", "class_hierarchy": [ @@ -40286,6 +40428,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.npc.wanderingtrader.WanderingTrader", "class_hierarchy": [ @@ -40620,6 +40763,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.warden.Warden", "class_hierarchy": [ @@ -40920,6 +41064,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.hurtingprojectile.windcharge.WindCharge", "class_hierarchy": [ @@ -41115,6 +41260,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Witch", "class_hierarchy": [ @@ -41446,6 +41592,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.boss.wither.WitherBoss", "class_hierarchy": [ @@ -41784,6 +41931,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.skeleton.WitherSkeleton", "class_hierarchy": [ @@ -42070,6 +42218,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.hurtingprojectile.WitherSkull", "class_hierarchy": [ @@ -42275,6 +42424,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.wolf.Wolf", "class_hierarchy": [ @@ -42671,6 +42821,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.Zoglin", "class_hierarchy": [ @@ -42980,6 +43131,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.zombie.Zombie", "class_hierarchy": [ @@ -43314,6 +43466,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.equine.ZombieHorse", "class_hierarchy": [ @@ -43665,6 +43818,7 @@ "allowed_in_peaceful": true, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.animal.nautilus.ZombieNautilus", "class_hierarchy": [ @@ -44051,6 +44205,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.zombie.ZombieVillager", "class_hierarchy": [ @@ -44420,6 +44575,7 @@ "allowed_in_peaceful": false, "can_serialize": true, "can_spawn_far_from_player": false, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.monster.zombie.ZombifiedPiglin", "class_hierarchy": [ @@ -44737,6 +44893,7 @@ "allowed_in_peaceful": true, "can_serialize": false, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.player.Player", "class_hierarchy": [ @@ -45051,6 +45208,7 @@ "allowed_in_peaceful": true, "can_serialize": false, "can_spawn_far_from_player": true, + "only_op_can_set_nbt": false, "synched_data": { "java_class": "net.minecraft.world.entity.projectile.FishingHook", "class_hierarchy": [ From a93b3d8a7164023d06ed970a94a0cfa6b67630a2 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 9 Sep 2026 00:08:53 +0200 Subject: [PATCH 28/29] fixed --- steel-core/src/entity/spawn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index e15b0d92717..e863e0ea689 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; -use steel_math::wrap_degrees; +use steel_math::{wrap_degrees, DEGREE_360}; use steel_registry::data_components::vanilla_components::{CUSTOM_DATA, CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack; @@ -218,7 +218,7 @@ pub(crate) fn spawn_entity( f64::from(pos.y()) + y_offset, f64::from(pos.z()) + 0.5, ); - let rotation = (wrap_degrees(rand::random::() * 360.0), 0.0); + let rotation = (wrap_degrees(rand::random::() * DEGREE_360), 0.0); entity.base().set_position_local(position); entity.set_rotation(rotation); From b6188834c47a31f174668431427d4be3a2695b57 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 9 Sep 2026 00:10:11 +0200 Subject: [PATCH 29/29] fmt --- steel-core/src/entity/spawn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steel-core/src/entity/spawn.rs b/steel-core/src/entity/spawn.rs index e863e0ea689..93ae077a3ee 100644 --- a/steel-core/src/entity/spawn.rs +++ b/steel-core/src/entity/spawn.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc}; use glam::DVec3; use simdnbt::borrow::read_compound; -use steel_math::{wrap_degrees, DEGREE_360}; +use steel_math::{DEGREE_360, wrap_degrees}; use steel_registry::data_components::vanilla_components::{CUSTOM_DATA, CUSTOM_NAME, ENTITY_DATA}; use steel_registry::entity_type::EntityTypeRef; use steel_registry::item_stack::ItemStack;