feat: guess living entities

This commit is contained in:
Alexander Medvedev 2026-01-13 23:36:49 +01:00
parent 57db90ce1a
commit 0555f804e0
21 changed files with 135 additions and 84 deletions

View File

@ -279,8 +279,9 @@ impl ToTokens for BlockPropertyStruct {
} }
#[inline] #[inline]
#[allow(clippy::manual_range_patterns)]
fn handles_block_id(block_id: u16) -> bool where Self: Sized { fn handles_block_id(block_id: u16) -> bool where Self: Sized {
[#(#block_ids),*].contains(&block_id) matches!(block_id, #(#block_ids)|*)
} }
fn to_state_id(&self, block: &Block) -> u16 { fn to_state_id(&self, block: &Block) -> u16 {
@ -291,14 +292,22 @@ impl ToTokens for BlockPropertyStruct {
} }
fn from_state_id(state_id: u16, block: &Block) -> Self { fn from_state_id(state_id: u16, block: &Block) -> Self {
if !Self::handles_block_id(block.id) { debug_assert!(
panic!("{} is not a valid block for {}", &block.name, #struct_name); Self::handles_block_id(block.id),
} "{} is not a valid block for {}", &block.name, #struct_name
if state_id >= block.states[0].id && state_id <= block.states.last().unwrap().id { );
let index = state_id - block.states[0].id;
Self::from_index(index) let min_id = block.states[0].id;
let max_id = block.states.last().map(|s| s.id).unwrap_or(min_id);
if (min_id..=max_id).contains(&state_id) {
Self::from_index(state_id - min_id)
} else { } else {
#[cfg(debug_assertions)]
panic!("State ID {} does not exist for {}", state_id, &block.name); panic!("State ID {} does not exist for {}", state_id, &block.name);
#[cfg(not(debug_assertions))]
Self::from_index(0)
} }
} }
@ -313,8 +322,10 @@ impl ToTokens for BlockPropertyStruct {
vec![ #(#to_props_entries),* ] vec![ #(#to_props_entries),* ]
} }
#[allow(clippy::manual_range_patterns)]
fn from_props(props: &[(&str, &str)], block: &Block) -> Self { fn from_props(props: &[(&str, &str)], block: &Block) -> Self {
if ![#(#block_ids),*].contains(&block.id) { #[cfg(debug_assertions)]
if !matches!(block.id, #(#block_ids)|*) {
panic!("{} is not a valid block for {}", &block.name, #struct_name); panic!("{} is not a valid block for {}", &block.name, #struct_name);
} }
let mut block_props = Self::default(block); let mut block_props = Self::default(block);

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, fs}; use std::{collections::BTreeMap, fs};
use heck::ToPascalCase; use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
@ -7,7 +7,7 @@ use quote::{format_ident, quote};
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/meta_data_type.json"); println!("cargo:rerun-if-changed=../assets/meta_data_type.json");
let handlers_map: HashMap<String, i32> = let handlers_map: BTreeMap<String, i32> =
serde_json::from_str(&fs::read_to_string("../assets/meta_data_type.json").unwrap()) serde_json::from_str(&fs::read_to_string("../assets/meta_data_type.json").unwrap())
.expect("Failed to parse meta_data_type.json"); .expect("Failed to parse meta_data_type.json");

View File

@ -218,13 +218,7 @@ impl ChunkHeightmaps {
data[packed_array_idx] = data[packed_array_idx].bitand(mask).bitor(height); data[packed_array_idx] = data[packed_array_idx].bitand(mask).bitor(height);
} }
pub fn get_height(&self, _type: ChunkHeightmapType, x: i32, z: i32, min_y: i32) -> i32 { pub fn get_height(&self, heightmap: ChunkHeightmapType, x: i32, z: i32, min_y: i32) -> i32 {
let data = match _type {
ChunkHeightmapType::WorldSurface => &self.world_surface,
ChunkHeightmapType::MotionBlocking => &self.motion_blocking,
ChunkHeightmapType::MotionBlockingNoLeaves => &self.motion_blocking_no_leaves,
};
let local_x = (x & 15) as usize; let local_x = (x & 15) as usize;
let local_z = (z & 15) as usize; let local_z = (z & 15) as usize;
@ -244,6 +238,11 @@ impl ChunkHeightmaps {
let packed_array_idx = column_idx / 7; let packed_array_idx = column_idx / 7;
let data = match heightmap {
ChunkHeightmapType::WorldSurface => &self.world_surface,
ChunkHeightmapType::MotionBlocking => &self.motion_blocking,
ChunkHeightmapType::MotionBlockingNoLeaves => &self.motion_blocking_no_leaves,
};
let height_bit_bytes_i64 = data[packed_array_idx].bitand(mask).to_ne_bytes(); let height_bit_bytes_i64 = data[packed_array_idx].bitand(mask).to_ne_bytes();
(u64::from_ne_bytes(height_bit_bytes_i64) (u64::from_ne_bytes(height_bit_bytes_i64)

View File

@ -239,8 +239,10 @@ impl WouldSurviveBlockPredicate {
pos: &BlockPos, pos: &BlockPos,
) -> bool { ) -> bool {
let block = self.state.get_block(); let block = self.state.get_block();
let state = self.state.get_state();
let pos = self.offset.get(pos); let pos = self.offset.get(pos);
block_registry.can_place_at(block, chunk, &pos, BlockDirection::Up) block_registry.can_place_at(block, state, chunk, &pos)
} }
} }

View File

@ -1,5 +1,5 @@
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, Block, BlockState,
block_properties::{BambooLeaves, BambooLikeProperties, BlockProperties, Integer0To1}, block_properties::{BambooLeaves, BambooLikeProperties, BlockProperties, Integer0To1},
tag, tag,
tag::Taggable, tag::Taggable,
@ -32,7 +32,8 @@ impl BambooFeature {
) -> bool { ) -> bool {
let mut i = 0; let mut i = 0;
if chunk.is_air(&pos.0) { if chunk.is_air(&pos.0) {
if block_registry.can_place_at(&Block::BAMBOO, chunk, &pos, BlockDirection::Up) { if block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos)
{
let height = random.next_bounded_i32(12) + 5; let height = random.next_bounded_i32(12) + 5;
if random.next_f32() < self.probability { if random.next_f32() < self.probability {
let rnd = random.next_bounded_i32(4) + 1; let rnd = random.next_bounded_i32(4) + 1;

View File

@ -1,4 +1,4 @@
use pumpkin_data::{Block, BlockDirection, tag, tag::Taggable}; use pumpkin_data::{Block, tag, tag::Taggable};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -47,7 +47,7 @@ impl NetherForestVegetationFeature {
let nether_block = Block::from_state_id(nether_state.id); let nether_block = Block::from_state_id(nether_state.id);
if !chunk.is_air(&pos.0) if !chunk.is_air(&pos.0)
|| pos.0.y <= chunk.bottom_y() as i32 || pos.0.y <= chunk.bottom_y() as i32
|| block_registry.can_place_at(nether_block, chunk, &pos, BlockDirection::Up) || block_registry.can_place_at(nether_block, nether_state, chunk, &pos)
{ {
continue; continue;
} }

View File

@ -1,4 +1,4 @@
use pumpkin_data::{Block, BlockDirection}; use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use serde::Deserialize; use serde::Deserialize;
@ -25,7 +25,7 @@ impl SimpleBlockFeature {
let state = self.to_place.get(random, pos); let state = self.to_place.get(random, pos);
let block = Block::from_state_id(state.id); let block = Block::from_state_id(state.id);
let block_accessor: &dyn BlockAccessor = chunk; let block_accessor: &dyn BlockAccessor = chunk;
if !block_registry.can_place_at(block, block_accessor, &pos, BlockDirection::Up) { if !block_registry.can_place_at(block, state, block_accessor, &pos) {
return false; return false;
} }

View File

@ -121,9 +121,9 @@ pub trait BlockRegistryExt: Send + Sync {
fn can_place_at( fn can_place_at(
&self, &self,
block: &Block, block: &Block,
state: &BlockState,
block_accessor: &dyn BlockAccessor, block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos, block_pos: &BlockPos,
face: BlockDirection,
) -> bool; ) -> bool;
} }

View File

@ -224,8 +224,8 @@ impl BlockBehaviour for FireBlock {
world: Some(args.world), world: Some(args.world),
block_accessor: args.world, block_accessor: args.world,
block: &Block::FIRE, block: &Block::FIRE,
state: Block::FIRE.default_state,
position: args.position, position: args.position,
direction: BlockDirection::Up,
player: None, player: None,
use_item_on: None, use_item_on: None,
}) })
@ -275,8 +275,8 @@ impl BlockBehaviour for FireBlock {
world: Some(world), world: Some(world),
block_accessor: world.as_ref(), block_accessor: world.as_ref(),
block, block,
state: block.default_state,
position: pos, position: pos,
direction: BlockDirection::Up,
player: None, player: None,
use_item_on: None, use_item_on: None,
}) })

View File

@ -56,8 +56,8 @@ impl FireBlockBase {
world: Some(world), world: Some(world),
block_accessor: world.as_ref(), block_accessor: world.as_ref(),
block: &Block::SOUL_FIRE, block: &Block::SOUL_FIRE,
state: Block::SOUL_FIRE.default_state,
position: block_pos, position: block_pos,
direction: BlockDirection::Up,
player: None, player: None,
use_item_on: None, use_item_on: None,
}) })
@ -69,8 +69,8 @@ impl FireBlockBase {
world: Some(world), world: Some(world),
block_accessor: world.as_ref(), block_accessor: world.as_ref(),
block: &Block::FIRE, block: &Block::FIRE,
state: Block::FIRE.default_state,
position: block_pos, position: block_pos,
direction: BlockDirection::Up,
player: None, player: None,
use_item_on: None, use_item_on: None,
}) })

View File

@ -27,11 +27,8 @@ impl BlockBehaviour for GrindstoneBlock {
}) })
} }
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> { fn can_place_at<'a>(&'a self, _args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move { true })
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, args.direction)
.await
})
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update<'a>(

View File

@ -3,9 +3,9 @@ use crate::block::{
OnScheduledTickArgs, OnScheduledTickArgs,
}; };
use crate::world::World; use crate::world::World;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::HorizontalFacing; use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::block_properties::{BlockProperties, LadderLikeProperties}; use pumpkin_data::block_properties::{BlockProperties, LadderLikeProperties};
use pumpkin_data::{BlockDirection, HorizontalFacingExt};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId; use pumpkin_world::BlockStateId;
@ -25,11 +25,12 @@ impl BlockBehaviour for LadderBlock {
} }
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> { fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
let props = LadderLikeProperties::from_state_id(args.state.id, args.block);
args.block_accessor args.block_accessor
.get_block_state(&args.use_item_on.unwrap().position) .get_block_state(&args.use_item_on.unwrap().position)
.await .await
.is_side_solid(args.direction.opposite()) .is_side_solid(props.facing.opposite().to_block_direction())
&& args.direction.is_horizontal()
}) })
} }

View File

@ -138,7 +138,12 @@ impl BlockBehaviour for ButtonBlock {
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> { fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, args.direction) WallMountedBlock::can_place_at(
self,
args.block_accessor,
args.position,
self.get_direction(args.state.id, args.block),
)
.await .await
}) })
} }

View File

@ -104,7 +104,12 @@ impl BlockBehaviour for LeverBlock {
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> { fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, args.direction) WallMountedBlock::can_place_at(
self,
args.block_accessor,
args.position,
self.get_direction(args.state.id, args.block),
)
.await .await
}) })
} }

View File

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, Block, BlockDirection, HorizontalFacingExt,
block_properties::BlockProperties, block_properties::BlockProperties,
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
}; };
@ -45,7 +45,14 @@ impl BlockBehaviour for TripwireHookBlock {
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> { fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
Self::can_place_at(args.block_accessor, args.position, args.direction).await let props = TripwireHookProperties::from_state_id(args.state.id, args.block);
Self::can_place_at(
args.block_accessor,
args.position,
props.facing.to_block_direction(),
)
.await
}) })
} }

View File

@ -230,8 +230,8 @@ pub struct CanPlaceAtArgs<'a> {
pub world: Option<&'a World>, pub world: Option<&'a World>,
pub block_accessor: &'a dyn BlockAccessor, pub block_accessor: &'a dyn BlockAccessor,
pub block: &'a Block, pub block: &'a Block,
pub state: &'a BlockState,
pub position: &'a BlockPos, pub position: &'a BlockPos,
pub direction: BlockDirection,
pub player: Option<&'a Player>, pub player: Option<&'a Player>,
pub use_item_on: Option<&'a SUseItemOn>, pub use_item_on: Option<&'a SUseItemOn>,
} }

View File

@ -306,9 +306,9 @@ impl BlockRegistryExt for BlockRegistry {
fn can_place_at( fn can_place_at(
&self, &self,
block: &pumpkin_data::Block, block: &pumpkin_data::Block,
state: &BlockState,
block_accessor: &dyn BlockAccessor, block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos, block_pos: &BlockPos,
face: BlockDirection,
) -> bool { ) -> bool {
futures::executor::block_on(async move { futures::executor::block_on(async move {
self.can_place_at( self.can_place_at(
@ -317,8 +317,8 @@ impl BlockRegistryExt for BlockRegistry {
block_accessor, block_accessor,
None, None,
block, block,
state,
block_pos, block_pos,
face,
None, None,
) )
.await .await
@ -493,8 +493,8 @@ impl BlockRegistry {
block_accessor: &dyn BlockAccessor, block_accessor: &dyn BlockAccessor,
player: Option<&Player>, player: Option<&Player>,
block: &Block, block: &Block,
state: &BlockState,
position: &BlockPos, position: &BlockPos,
direction: BlockDirection,
use_item_on: Option<&SUseItemOn>, use_item_on: Option<&SUseItemOn>,
) -> bool { ) -> bool {
let pumpkin_block = self.get_pumpkin_block(block); let pumpkin_block = self.get_pumpkin_block(block);
@ -505,8 +505,8 @@ impl BlockRegistry {
world, world,
block_accessor, block_accessor,
block, block,
state,
position, position,
direction,
player, player,
use_item_on, use_item_on,
}) })

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::{EntityType, MobCategory};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use uuid::Uuid; use uuid::Uuid;
@ -8,6 +8,7 @@ use crate::{
entity::{ entity::{
Entity, EntityBase, Entity, EntityBase,
decoration::{end_crystal::EndCrystalEntity, painting::PaintingEntity}, decoration::{end_crystal::EndCrystalEntity, painting::PaintingEntity},
living::LivingEntity,
mob::zombie::Zombie, mob::zombie::Zombie,
}, },
world::World, world::World,
@ -25,8 +26,14 @@ pub async fn from_type(
id if id == EntityType::ZOMBIE.id => Zombie::make(entity).await, id if id == EntityType::ZOMBIE.id => Zombie::make(entity).await,
id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)), id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)),
id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)), id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)),
// TODO // Fallback Entity
_ => Arc::new(entity), // Fallback Entity _ => {
if entity_type.category == &MobCategory::MISC {
Arc::new(entity)
} else {
Arc::new(LivingEntity::new(entity))
}
}
}; };
mob mob

View File

@ -1999,8 +1999,8 @@ impl JavaClient {
world.as_ref(), world.as_ref(),
Some(player), Some(player),
block, block,
block.default_state,
&final_block_pos, &final_block_pos,
final_face,
Some(&use_item_on), Some(&use_item_on),
) )
.await .await

View File

@ -559,40 +559,41 @@ impl World {
pub async fn tick(self: &Arc<Self>, server: &Server) { pub async fn tick(self: &Arc<Self>, server: &Server) {
let start = tokio::time::Instant::now(); let start = tokio::time::Instant::now();
// 1. Block & Environment
self.flush_block_updates().await; self.flush_block_updates().await;
// tick block entities
self.flush_synced_block_events().await; self.flush_synced_block_events().await;
self.tick_environment().await; self.tick_environment().await;
let env_done = start.elapsed();
// 2. Chunks
let chunk_start = tokio::time::Instant::now(); let chunk_start = tokio::time::Instant::now();
// log::debug!("Ticking chunks");
self.tick_chunks().await; self.tick_chunks().await;
let chunk_elapsed = chunk_start.elapsed(); let chunk_elapsed = chunk_start.elapsed();
// 3. Players
let player_start = tokio::time::Instant::now();
let players_to_tick: Vec<_> = self.players.read().await.values().cloned().collect(); let players_to_tick: Vec<_> = self.players.read().await.values().cloned().collect();
let player_count = players_to_tick.len();
// log::debug!("Ticking players"); for player in &players_to_tick {
// player ticks
for player in players_to_tick {
player.tick(server).await; player.tick(server).await;
} }
let player_elapsed = player_start.elapsed();
// 4. Entities & Collision
let entity_start = tokio::time::Instant::now();
let entities_to_tick: Vec<_> = self.entities.read().await.values().cloned().collect(); let entities_to_tick: Vec<_> = self.entities.read().await.values().cloned().collect();
let entity_count = entities_to_tick.len();
// log::debug!("Ticking entities");
// Entity ticks
for entity in entities_to_tick { for entity in entities_to_tick {
entity.get_entity().age.fetch_add(1, Relaxed); entity.get_entity().age.fetch_add(1, Relaxed);
entity.tick(entity.clone(), server).await; entity.tick(entity.clone(), server).await;
for player in self.players.read().await.values() {
for player in &players_to_tick {
if player if player
.living_entity .living_entity
.entity .entity
.bounding_box .bounding_box
.load() .load()
// This is vanilla, but TODO: change this when is in a vehicle
.expand(1.0, 0.5, 1.0) .expand(1.0, 0.5, 1.0)
.intersects(&entity.get_entity().bounding_box.load()) .intersects(&entity.get_entity().bounding_box.load())
{ {
@ -601,14 +602,22 @@ impl World {
} }
} }
} }
let entity_elapsed = entity_start.elapsed();
self.level.chunk_loading.lock().unwrap().send_change(); self.level.chunk_loading.lock().unwrap().send_change();
if start.elapsed().as_millis() > 50 { // 5. Detailed Slow Tick Logging
let total_elapsed = start.elapsed();
if total_elapsed.as_millis() > 50 {
log::warn!( log::warn!(
"Slow Tick: Total {:?} | Chunks {:?}", "Slow Tick [{}ms]: Chunks: {:?} | Players({}): {:?} | Entities({}): {:?} | Env: {:?}",
start.elapsed(), total_elapsed.as_millis(),
chunk_elapsed, chunk_elapsed,
player_count,
player_elapsed,
entity_count,
entity_elapsed,
env_done
); );
} }
} }

View File

@ -14,6 +14,8 @@ use pumpkin_util::math::get_section_cord;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::xoroshiro128::Xoroshiro;
use pumpkin_util::random::{RandomImpl, get_seed};
use pumpkin_world::chunk::{ChunkData, ChunkHeightmapType}; use pumpkin_world::chunk::{ChunkData, ChunkHeightmapType};
use rand::seq::IndexedRandom; use rand::seq::IndexedRandom;
use rand::{Rng, rng}; use rand::{Rng, rng};
@ -131,7 +133,7 @@ impl LocalMobCapCalculator {
} }
#[derive(Debug)] #[derive(Debug)]
struct PointCharge(Vector3<f64>, f64); // pos charge struct PointCharge(Vector3<f64>, f64);
impl PointCharge { impl PointCharge {
fn get_potential_change(&self, pos: &BlockPos) -> f64 { fn get_potential_change(&self, pos: &BlockPos) -> f64 {
@ -296,13 +298,23 @@ pub fn get_filtered_spawning_categories(
spawn_enemies: bool, spawn_enemies: bool,
spawn_passives: bool, spawn_passives: bool,
) -> Vec<&'static MobCategory> { ) -> Vec<&'static MobCategory> {
let mut ret = Vec::with_capacity(8); let mut ret = Vec::with_capacity(MobCategory::SPAWNING_CATEGORIES.len());
for category in MobCategory::SPAWNING_CATEGORIES { for category in MobCategory::SPAWNING_CATEGORIES {
if (spawn_friendlies || !category.is_friendly) let is_type_allowed = if category.is_friendly {
&& (spawn_enemies || category.is_friendly) spawn_friendlies
&& (spawn_passives || !category.is_persistent) } else {
&& state.can_spawn_for_category_global(category) spawn_enemies
{ };
if !is_type_allowed {
continue;
}
if category.is_persistent && !spawn_passives {
continue;
}
if state.can_spawn_for_category_global(category) {
ret.push(category); ret.push(category);
} }
} }
@ -323,7 +335,6 @@ pub async fn spawn_for_chunk(
.await .await
{ {
let random_pos = get_random_pos_within(world.min_y, chunk_pos, chunk).await; let random_pos = get_random_pos_within(world.min_y, chunk_pos, chunk).await;
// debug!("try random pos: {:?}", random_pos);
if random_pos.0.y > world.min_y { if random_pos.0.y > world.min_y {
spawn_category_for_position(category, world, random_pos, chunk_pos, spawn_state) spawn_category_for_position(category, world, random_pos, chunk_pos, spawn_state)
.await; .await;
@ -336,8 +347,10 @@ pub async fn get_random_pos_within(
chunk_pos: &Vector2<i32>, chunk_pos: &Vector2<i32>,
chunk: &Arc<RwLock<ChunkData>>, chunk: &Arc<RwLock<ChunkData>>,
) -> BlockPos { ) -> BlockPos {
let x = (chunk_pos.x << 4) + rng().random_range(0..16); let mut rng = Xoroshiro::from_seed(get_seed());
let z = (chunk_pos.y << 4) + rng().random_range(0..16);
let x = (chunk_pos.x << 4) + rng.next_bounded_i32(16);
let z = (chunk_pos.y << 4) + rng.next_bounded_i32(16);
let temp_y = let temp_y =
chunk chunk
.read() .read()
@ -345,7 +358,7 @@ pub async fn get_random_pos_within(
.heightmap .heightmap
.get_height(ChunkHeightmapType::WorldSurface, x, z, min_y) .get_height(ChunkHeightmapType::WorldSurface, x, z, min_y)
+ 1; + 1;
let y = rng().random_range(min_y..=temp_y); let y = rng.next_inbetween_i32(min_y, temp_y);
BlockPos::new(x, y, z) BlockPos::new(x, y, z)
} }
@ -365,8 +378,6 @@ pub async fn spawn_category_for_position(
let mut new_z = new_pos.0.z; let mut new_z = new_pos.0.z;
let mut random_group_size = (rng().random::<f32>() * 4.).ceil() as i32; let mut random_group_size = (rng().random::<f32>() * 4.).ceil() as i32;
let mut inc = 0; let mut inc = 0;
#[expect(unused_variables)]
let group_size = 0;
'outer: while inc < random_group_size { 'outer: while inc < random_group_size {
new_x += rng().random_range(0..6) - rng().random_range(0..6); new_x += rng().random_range(0..6) - rng().random_range(0..6);
new_z += rng().random_range(0..6) - rng().random_range(0..6); new_z += rng().random_range(0..6) - rng().random_range(0..6);
@ -379,12 +390,10 @@ pub async fn spawn_category_for_position(
world, world,
chunk_pos, chunk_pos,
) { ) {
// debug!("{new_pos:?} failed, too near to player or spawn point dst: {player_distance}");
inc += 1; inc += 1;
continue; continue;
} }
let Some(spawner) = get_random_spawn_mob_at(world, category, &new_pos).await else { let Some(spawner) = get_random_spawn_mob_at(world, category, &new_pos).await else {
// debug!("{new_pos:?} failed, no random spawn mob at category: {category:?}");
break 'outer; break 'outer;
}; };
random_group_size = rng().random_range(spawner.min_count..=spawner.max_count); random_group_size = rng().random_range(spawner.min_count..=spawner.max_count);
@ -399,12 +408,10 @@ pub async fn spawn_category_for_position(
) )
.await .await
{ {
// debug!("{new_pos:?} failed, not valid spawn position");
inc += 1; inc += 1;
continue; continue;
} }
if !spawn_state.can_spawn(entity_type, &new_pos, world).await { if !spawn_state.can_spawn(entity_type, &new_pos, world).await {
// debug!("{new_pos:?} failed, can't spawn at");
inc += 1; inc += 1;
continue; continue;
} }