fix: bedrock SActorEvent packet

This commit is contained in:
Alexander Medvedev 2026-06-28 13:26:32 +02:00
parent 28e8187f23
commit 8c5d6dc405
19 changed files with 100 additions and 103 deletions

View File

@ -1,4 +1,4 @@
use crate::serial::PacketWrite;
use crate::{codec::var_long::VarLong, serial::PacketWrite};
use std::io::{Error, Write};
use pumpkin_macros::packet;
@ -9,7 +9,7 @@ use crate::codec::var_int::VarInt;
#[derive(Debug, PacketWrite)]
#[packet(27)]
pub struct SActorEvent {
pub entity_runtime_id: VarInt,
pub entity_runtime_id: VarLong,
pub event_type: ActorEventType,
pub event_data: VarInt,
pub fire_at_position: Option<Vector3<f32>>,

View File

@ -5,19 +5,19 @@ use crate::{
codec::var_int::VarInt,
ser::{NetworkWriteExt, WritingError},
};
use pumpkin_data::packet::clientbound::CONFIG_REGISTRY_DATA;
use pumpkin_data::{packet::clientbound::CONFIG_REGISTRY_DATA, registry::RegistryEntryData};
use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion};
#[java_packet(CONFIG_REGISTRY_DATA)]
pub struct CRegistryData<'a> {
pub registry_id: &'a ResourceLocation,
pub entries: &'a [RegistryEntry],
pub entries: &'a [RegistryEntryData],
}
impl<'a> CRegistryData<'a> {
#[must_use]
pub const fn new(registry_id: &'a ResourceLocation, entries: &'a [RegistryEntry]) -> Self {
pub const fn new(registry_id: &'a ResourceLocation, entries: &'a [RegistryEntryData]) -> Self {
Self {
registry_id,
entries,
@ -25,18 +25,6 @@ impl<'a> CRegistryData<'a> {
}
}
pub struct RegistryEntry {
pub entry_id: ResourceLocation,
pub data: Option<Box<[u8]>>,
}
impl RegistryEntry {
#[must_use]
pub const fn new(entry_id: ResourceLocation, data: Option<Box<[u8]>>) -> Self {
Self { entry_id, data }
}
}
impl ClientPacket for CRegistryData<'_> {
fn write_packet_data(
&self,

View File

@ -15,22 +15,28 @@ impl Pool {
///
/// # Returns
/// An `Option<E>` representing the selected element, or `None` if the distribution is empty.
pub fn get<E: Clone>(distribution: &[Weighted<E>], random: &mut RandomGenerator) -> Option<E> {
pub fn get<'a, E>(
distribution: &'a [Weighted<E>],
random: &mut RandomGenerator,
) -> Option<&'a E> {
let mut total_weight = 0;
for dist in distribution {
total_weight += dist.weight;
}
let mut index = random.next_bounded_i32(total_weight);
if total_weight < 64 {
return Some(FlattenedContent::get(index, distribution, total_weight));
}
// WrappedContent
for dist in distribution {
index -= dist.weight;
if index >= 0 {
continue;
}
return Some(dist.data.clone());
return Some(&dist.data);
}
None
}
@ -58,16 +64,17 @@ impl FlattenedContent {
///
/// # Returns
/// The element corresponding to the given index.
pub fn get<E: Clone>(index: i32, entries: &[Weighted<E>], total_weight: i32) -> E {
let mut final_entries = Vec::with_capacity(total_weight as usize);
pub fn get<E>(index: i32, entries: &[Weighted<E>], _total_weight: i32) -> &E {
let mut cur_index = 0;
for entry in entries {
let weight = entry.weight;
for i in cur_index..cur_index + weight {
final_entries.insert(i as usize, entry.data.clone());
if index >= cur_index && index < cur_index + weight {
return &entry.data;
}
cur_index += weight;
}
final_entries[index as usize].clone()
&entries[0].data
}
}

View File

@ -21,11 +21,10 @@ fn main() {
for (pool_id, elements) in pools {
pool_code.push_str(&format!(
" \"minecraft:{}\" | \"{}\" => Some(&[\n",
pool_id, pool_id
" \"minecraft:{pool_id}\" | \"{pool_id}\" => Some(&[\n"
));
for element in elements {
pool_code.push_str(&format!(" \"{}\",\n", element));
pool_code.push_str(&format!(" \"{element}\",\n"));
}
pool_code.push_str(" ]),\n");
}
@ -37,7 +36,7 @@ fn main() {
pool_code.push_str(" _ => None,\n");
pool_code.push_str(" }\n}\n");
fs::write(&dest_path, format!("{}\n{}", code, pool_code)).unwrap();
fs::write(&dest_path, format!("{code}\n{pool_code}")).unwrap();
println!("cargo:rerun-if-changed=assets/structures");
}
@ -56,7 +55,7 @@ fn process_dir(
let new_prefix = if prefix.is_empty() {
name
} else {
format!("{}/{}", prefix, name)
format!("{prefix}/{name}")
};
process_dir(&path, &new_prefix, code, pools);
} else if name.ends_with(".nbt") {

View File

@ -11,19 +11,19 @@ impl RawBlockState {
#[inline]
#[must_use]
pub fn to_state(&self) -> &'static BlockState {
pub const fn to_state(&self) -> &'static BlockState {
BlockState::from_id(self.0)
}
#[inline]
#[must_use]
pub fn to_block(&self) -> &'static Block {
pub const fn to_block(&self) -> &'static Block {
Block::from_state_id(self.0)
}
#[inline]
#[must_use]
pub fn to_block_id(&self) -> u16 {
pub const fn to_block_id(&self) -> u16 {
Block::get_raw_id_from_state_id(self.0)
}
}

View File

@ -161,7 +161,7 @@ pub struct ChunkNoiseFunctionBuilderOptions {
impl ChunkNoiseFunctionBuilderOptions {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
pub const fn new(
horizontal_cell_block_count: usize,
vertical_cell_block_count: usize,
vertical_cell_count: usize,

View File

@ -114,7 +114,7 @@ pub struct Beardifier {
}
impl Beardifier {
pub fn new(
pub const fn new(
structures: Vec<BeardifierStructure>,
junctions: Vec<BeardifierJunction>,
affected_box: Option<BlockBox>,

View File

@ -34,17 +34,17 @@ impl FindTopSurface {
}
#[must_use]
pub fn density_index(&self) -> usize {
pub const fn density_index(&self) -> usize {
self.density_index
}
#[must_use]
pub fn upper_bound_index(&self) -> usize {
pub const fn upper_bound_index(&self) -> usize {
self.upper_bound_index
}
#[must_use]
pub fn cell_height(&self) -> i32 {
pub const fn cell_height(&self) -> i32 {
self.data.cell_height
}
}

View File

@ -458,7 +458,7 @@ impl ProtoNoiseRouters {
} => {
let mut min_value = f64::INFINITY;
let mut max_value = f64::NEG_INFINITY;
for &idx in functions_indices.iter() {
for &idx in *functions_indices {
let min = stack[idx].min();
let max = stack[idx].max();
if min < min_value {

View File

@ -348,7 +348,7 @@ impl ProtoChunk {
}
#[must_use]
pub fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
pub const fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
match heightmap {
HeightMap::WorldSurfaceWg => self.top_block_height_exclusive(x, z),
HeightMap::WorldSurface => self.top_block_height_exclusive(x, z),
@ -395,7 +395,7 @@ impl ProtoChunk {
}
#[inline]
fn local_pos_to_block_index(&self, x: i32, y: i32, z: i32) -> usize {
const fn local_pos_to_block_index(&self, x: i32, y: i32, z: i32) -> usize {
self.height() as usize * CHUNK_DIM as usize * x as usize
+ CHUNK_DIM as usize * y as usize
+ z as usize
@ -403,7 +403,7 @@ impl ProtoChunk {
#[inline]
#[must_use]
pub fn local_biome_pos_to_biome_index(&self, x: i32, y: i32, z: i32) -> usize {
pub const fn local_biome_pos_to_biome_index(&self, x: i32, y: i32, z: i32) -> usize {
let biome_height = self.height() as usize >> 2;
biome_height * biome_coords::from_block(CHUNK_DIM as i32) as usize * x as usize
+ biome_coords::from_block(CHUNK_DIM as i32) as usize * y as usize

View File

@ -150,7 +150,7 @@ impl StructurePieceBase for IglooPiece {
_block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator,
_seed: i64,
_chunk_box: &BlockBox,
chunk_box: &BlockBox,
) {
let origin = self.shiftable_structure_piece.piece.bounding_box.min;
@ -179,7 +179,7 @@ impl StructurePieceBase for IglooPiece {
self.rotation,
false,
&[],
Some(_chunk_box),
Some(chunk_box),
);
// Place basement components if present
@ -210,7 +210,7 @@ impl StructurePieceBase for IglooPiece {
self.rotation,
false,
&[],
Some(_chunk_box),
Some(chunk_box),
);
}
}
@ -228,7 +228,7 @@ impl StructurePieceBase for IglooPiece {
self.rotation,
false,
&[],
Some(_chunk_box),
Some(chunk_box),
);
}
}

View File

@ -51,6 +51,7 @@ impl TemplatePool {
}
/// Discovers a pool from the filesystem/embedded assets.
#[must_use]
pub fn discover(id: &str) -> Option<Self> {
let elements = crate::generation::structure::template::get_pool_elements(id)?;
@ -84,6 +85,7 @@ pub enum JigsawJointType {
impl JigsawJointType {
#[allow(clippy::should_implement_trait)]
#[must_use]
pub fn from_str(s: &str) -> Self {
match s {
"aligned" => Self::Aligned,
@ -107,6 +109,7 @@ pub struct JigsawBlock {
}
impl JigsawBlock {
#[must_use]
pub fn from_template_block(
block: &crate::generation::structure::template::TemplateBlock,
palette: &PaletteEntry,
@ -122,8 +125,7 @@ impl JigsawBlock {
.properties
.iter()
.find(|(k, _)| *k == "orientation")
.map(|(_, v)| v.clone())
.unwrap_or_else(|| "north_up".to_string());
.map_or_else(|| "north_up".to_string(), |(_, v)| v.clone());
let mut parts = facing_str.split('_');
let facing_part = parts.next().unwrap_or("north");
@ -166,6 +168,7 @@ impl JigsawBlock {
})
}
#[must_use]
pub fn can_attach(
source: &Self,
target_facing: pumpkin_util::BlockDirection,
@ -217,7 +220,7 @@ impl StructurePieceBase for PoolElementStructurePiece {
_block_registry: &dyn crate::world::WorldPortalExt,
_random: &mut pumpkin_util::random::RandomGenerator,
_seed: i64,
_chunk_box: &pumpkin_util::math::block_box::BlockBox,
chunk_box: &pumpkin_util::math::block_box::BlockBox,
) {
let origin =
pumpkin_util::math::vector3::Vector3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z);
@ -238,7 +241,7 @@ impl StructurePieceBase for PoolElementStructurePiece {
self.rotation,
false,
&processors,
Some(_chunk_box),
Some(chunk_box),
);
// Post-process: replace jigsaw blocks with their final_state
@ -246,12 +249,12 @@ impl StructurePieceBase for PoolElementStructurePiece {
let wx = jigsaw.pos.0.x;
let wy = jigsaw.pos.0.y;
let wz = jigsaw.pos.0.z;
if wx < _chunk_box.min.x
|| wx > _chunk_box.max.x
|| wy < _chunk_box.min.y
|| wy > _chunk_box.max.y
|| wz < _chunk_box.min.z
|| wz > _chunk_box.max.z
if wx < chunk_box.min.x
|| wx > chunk_box.max.x
|| wy < chunk_box.min.y
|| wy > chunk_box.max.y
|| wz < chunk_box.min.z
|| wz > chunk_box.max.z
{
continue;
}
@ -282,6 +285,7 @@ pub struct JigsawGenerator {
}
impl JigsawGenerator {
#[must_use]
pub fn new(start_pool: &str, size: i32) -> Self {
Self {
start_pool: start_pool.to_string(),
@ -291,12 +295,14 @@ impl JigsawGenerator {
}
}
#[must_use]
pub fn with_start_jigsaw(mut self, name: &str) -> Self {
self.start_jigsaw_name = Some(name.to_string());
self
}
pub fn with_expansion_hack(mut self, use_hack: bool) -> Self {
#[must_use]
pub const fn with_expansion_hack(mut self, use_hack: bool) -> Self {
self.use_expansion_hack = use_hack;
self
}
@ -332,22 +338,22 @@ impl StructureGenerator for JigsawGenerator {
.and_then(|s| s.max_distance_from_center)
.unwrap_or(80); // Vanilla default is 80
let liquid_settings = structure
.and_then(|s| s.liquid_settings)
.map(|ls| match ls {
"apply_waterlogging" => LiquidSettings::ApplyWaterlog,
"ignore_waterlogging" => LiquidSettings::IgnoreWaterlogDone,
_ => LiquidSettings::ApplyWaterlog,
})
.unwrap_or(LiquidSettings::ApplyWaterlog);
let liquid_settings =
structure
.and_then(|s| s.liquid_settings)
.map_or(LiquidSettings::ApplyWaterlog, |ls| match ls {
"apply_waterlogging" => LiquidSettings::ApplyWaterlog,
"ignore_waterlogging" => LiquidSettings::IgnoreWaterlogDone,
_ => LiquidSettings::ApplyWaterlog,
});
let dimension_padding = structure
.and_then(|s| s.dimension_padding)
.map(|dp| DimensionPadding {
top: dp,
bottom: dp,
})
.unwrap_or(DimensionPadding::ZERO);
let dimension_padding =
structure
.and_then(|s| s.dimension_padding)
.map_or(DimensionPadding::ZERO, |dp| DimensionPadding {
top: dp,
bottom: dp,
});
JigsawPlacement::add_pieces(
&mut context,

View File

@ -38,7 +38,8 @@ pub struct MaxDistance {
}
impl MaxDistance {
pub fn new(horizontal: i32) -> Self {
#[must_use]
pub const fn new(horizontal: i32) -> Self {
Self {
horizontal,
vertical: 384, // Default Y_SIZE (min_y to max_y)
@ -54,7 +55,8 @@ pub const MAX_DEPTH: i32 = 20;
pub struct PoolAliasLookup;
impl PoolAliasLookup {
pub fn lookup<'a>(&self, id: &'a str) -> &'a str {
#[must_use]
pub const fn lookup<'a>(&self, id: &'a str) -> &'a str {
// In a complete implementation, this would look up the alias in the context/registry.
// Returning the ID directly acts as a fallback/default behavior.
id
@ -121,8 +123,8 @@ impl JigsawPlacement {
adjusted_position.0.z + rotated_size.z - 1,
);
let center_x = (box_.max.x + box_.min.x) / 2;
let center_z = (box_.max.z + box_.min.z) / 2;
let center_x = i32::midpoint(box_.max.x, box_.min.x);
let center_z = i32::midpoint(box_.max.z, box_.min.z);
let bottom_y = if project_start_to_heightmap {
if let Some(sampler) = &mut context.height_sampler {
@ -227,7 +229,7 @@ impl JigsawPlacement {
let source_projection = piece_projections[source_piece_idx];
let source_rigid = source_projection == JigsawProjection::Rigid;
'jigsaw_loop: for source_jigsaw in source_jigsaws.iter() {
'jigsaw_loop: for source_jigsaw in &source_jigsaws {
let raw_pool_id = &source_jigsaw.pool;
if raw_pool_id == "minecraft:empty" || raw_pool_id.is_empty() {
continue;
@ -544,9 +546,8 @@ impl JigsawPlacement {
// Helper to determine the max Y height of a pool for the expansion hack
fn get_pool_max_y_size(pool_id: &str) -> i32 {
let pool = match TemplatePool::discover(pool_id) {
Some(p) => p,
None => return 0,
let Some(pool) = TemplatePool::discover(pool_id) else {
return 0;
};
let mut max_y = 0;
@ -561,7 +562,7 @@ fn get_pool_max_y_size(pool_id: &str) -> i32 {
max_y
}
fn is_box_inside(outer: &BlockBox, inner: &BlockBox) -> bool {
const fn is_box_inside(outer: &BlockBox, inner: &BlockBox) -> bool {
inner.min.x >= outer.min.x
&& inner.max.x <= outer.max.x
&& inner.min.y >= outer.min.y
@ -570,7 +571,7 @@ fn is_box_inside(outer: &BlockBox, inner: &BlockBox) -> bool {
&& inner.max.z <= outer.max.z
}
fn intersects_exclusive(a: &BlockBox, b: &BlockBox) -> bool {
const fn intersects_exclusive(a: &BlockBox, b: &BlockBox) -> bool {
// Strictly greater/less than checks perfectly emulate Vanilla's AABB deflate(0.25)
// by completely ignoring touching boundaries where coords are equal.
a.max.x > b.min.x
@ -649,7 +650,7 @@ fn can_attach(source: &JigsawBlock, target: &JigsawBlock, target_rotation: Rotat
true
}
fn rotate_direction(
const fn rotate_direction(
dir: pumpkin_util::BlockDirection,
rotation: Rotation,
) -> pumpkin_util::BlockDirection {

View File

@ -73,7 +73,8 @@ impl PaletteEntry {
Self { name, properties }
}
/// Parses a block state string (e.g. "minecraft:oak_log[axis=x]") into a palette entry.
/// Parses a block state string (e.g. "`minecraft:oak_log`[axis=x]") into a palette entry.
#[must_use]
pub fn from_string(s: &str) -> Self {
if let Some(bracket_pos) = s.find('[') {
let name = s[..bracket_pos].to_string();

View File

@ -8,6 +8,7 @@ use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_protocol::bedrock::client::take_item_actor::CTakeItemActor;
use pumpkin_protocol::bedrock::server::actor_event::{ActorEventType, SActorEvent};
use pumpkin_protocol::codec::var_long::VarLong;
use pumpkin_protocol::codec::var_ulong::VarULong;
use pumpkin_util::GameMode;
use pumpkin_util::Hand;
@ -2172,7 +2173,7 @@ impl EntityBase for LivingEntity {
let config = &world.server.upgrade().unwrap().advanced_config.pvp;
if config.hurt_animation {
let entity_id = VarInt(self.entity.entity_id);
let entity_id = self.entity.entity_id;
let hurt_yaw = source.map_or(0.0, |source| {
let src = source.get_entity().pos.load();
let tgt = self.entity.pos.load();
@ -2180,13 +2181,16 @@ impl EntityBase for LivingEntity {
- self.entity.yaw.load()
});
let hurt_event = SActorEvent {
entity_runtime_id: entity_id,
entity_runtime_id: VarLong(entity_id as i64),
event_type: ActorEventType::Hurt,
event_data: VarInt(0),
fire_at_position: None,
};
world
.broadcast_editioned(&CHurtAnimation::new(entity_id, hurt_yaw), &hurt_event)
.broadcast_editioned(
&CHurtAnimation::new(VarInt(entity_id), hurt_yaw),
&hurt_event,
)
.await;
}

View File

@ -16,7 +16,7 @@ use pumpkin_data::{registry::Registry, translation};
use pumpkin_protocol::{
ConnectionState,
java::{
client::config::{CFinishConfig, CRegistryData, CUpdateTags, RegistryEntry},
client::config::{CFinishConfig, CRegistryData, CUpdateTags},
server::config::{
ResourcePackResponseResult, SClientInformationConfig, SConfigCookieResponse,
SConfigResourcePack, SKeepAlive, SKnownPacks, SPluginMessage,
@ -164,13 +164,11 @@ impl JavaClient {
let version = self.version.load();
let registry = Registry::get_synced(version);
for registry in registry {
let entries: Vec<RegistryEntry> = registry
.registry_entries
.iter()
.map(|r| RegistryEntry::new(r.entry_id.clone(), r.data.clone()))
.collect();
self.send_packet_now(&CRegistryData::new(&registry.registry_id, &entries))
.await;
self.send_packet_now(&CRegistryData::new(
&registry.registry_id,
&registry.registry_entries,
))
.await;
// if let Some(tag) = RegistryKey::from_string(&registry.registry_id.path)
// && pumpkin_data::tag::get_registry_key_tags(self.version.load(), tag).is_some()
// {

View File

@ -57,10 +57,7 @@ impl PluginLoader for NativePluginLoader {
}
fn can_load(&self, path: &Path) -> bool {
let ext = path
.extension()
.and_then(|s| s.to_str())
.unwrap_or_default();
let ext = path.extension().unwrap_or_default();
if cfg!(target_os = "windows") {
ext.eq_ignore_ascii_case("dll")

View File

@ -47,10 +47,7 @@ impl PluginLoader for WasmPluginLoader {
}
fn can_load(&self, path: &Path) -> bool {
let ext = path
.extension()
.and_then(|s| s.to_str())
.unwrap_or_default();
let ext = path.extension().unwrap_or_default();
ext.eq_ignore_ascii_case("wasm")
}

View File

@ -687,7 +687,6 @@ impl PluginManager {
if path
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext.eq_ignore_ascii_case("deactivated"))
{
continue;