feat: building snow golem

This commit is contained in:
Alexander Medvedev 2026-01-20 21:44:49 +01:00
parent f7b70bafb6
commit 59c82aa30e
24 changed files with 143 additions and 133 deletions

View File

@ -14,16 +14,36 @@ use pumpkin_util::math::vector3::Vector3;
use thiserror::Error;
bitflags! {
/// Flags used to control the side effects of a block state change.
/// These match the internal bitmask used by Minecraft's `setBlockState` method
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockFlags: u32 {
/// Causes a neighbor update to be sent to surrounding blocks.
/// This is what makes observers detect changes or redstone components react
const NOTIFY_NEIGHBORS = 0b000_0000_0001;
/// Notifies listeners (like clients) that the block has changed.
/// On the server, this triggers a packet to be sent to players in range
const NOTIFY_LISTENERS = 0b000_0000_0010;
/// Combines neighbor notification and listener notification.
/// This is the "standard" update used for most block placements
const NOTIFY_ALL = 0b000_0000_0011;
/// Forces the block state to be set even if it matches the current state
/// Used by items like the Debug Stick to bypass "virtual" change checks
const FORCE_STATE = 0b000_0000_0100;
/// Prevents the previous block from dropping items when it is replaced
/// Commonly used when a block is "transformed" rather than destroyed
const SKIP_DROPS = 0b000_0000_1000;
/// Signals that the block is being moved (usually by a piston)
/// This prevents certain "on-break" logic from firing until the move is complete
const MOVED = 0b000_0001_0000;
/// Prevents redstone wire from re-calculating its shape/power immediately
/// Used during massive redstone updates to reduce calculation lag
const SKIP_REDSTONE_WIRE_STATE_REPLACEMENT = 0b000_0010_0000;
/// If set, the `on_replaced` callback for block entities (containers) is skipped
/// Useful if you are moving a Block Entity and don't want it to drop its contents yet
const SKIP_BLOCK_ENTITY_REPLACED_CALLBACK = 0b000_0100_0000;
/// Prevents the `on_added` logic from firing for the new block state
/// Use this to avoid recursive placement loops or unnecessary initialization
const SKIP_BLOCK_ADDED_CALLBACK = 0b000_1000_0000;
}
}

View File

@ -1,10 +1,15 @@
use pumpkin_data::{
Block,
block_properties::{BlockProperties, WallTorchLikeProperties},
entity::EntityType,
world::WorldEvent,
};
use pumpkin_world::BlockStateId;
use pumpkin_world::{BlockStateId, world::BlockFlags};
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs};
use crate::{
block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs, PlacedArgs},
entity::{Entity, passive::snow_golem::SnowGolemEntity},
};
pub struct CarvedPumpkinBlock;
@ -31,4 +36,39 @@ impl BlockBehaviour for CarvedPumpkinBlock {
props.to_state_id(args.block)
})
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
// Mojang uses some BlockPattern magic, way too complex tbh
Box::pin(async {
let down_pos = args.position.down();
let upper = args.world.get_block(&down_pos).await;
let lower = args.world.get_block(&down_pos.down()).await;
if upper == &Block::SNOW_BLOCK && lower == &Block::SNOW_BLOCK {
for i in 0..3 {
let pos = args.position.down_height(i);
args.world
.set_block_state(
&pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_LISTENERS,
)
.await;
args.world
.sync_world_event(
WorldEvent::BlockBroken,
pos,
Block::SNOW_BLOCK.default_state.id.into(),
)
.await;
}
let entity = Entity::new(
args.world.clone(),
down_pos.down().to_centered_f64(),
&EntityType::SNOW_GOLEM,
);
let golem = SnowGolemEntity::make(entity).await;
args.world.spawn_entity(golem).await;
}
})
}
}

View File

@ -20,7 +20,6 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, item::ItemStack, tick::TickPriority, world::BlockFlags};
use rand::Rng;
use uuid::Uuid;
#[pumpkin_block("minecraft:composter")]
pub struct ComposterBlock;
@ -142,13 +141,7 @@ impl ComposterBlock {
};
let item_entity = ItemEntity::new(
Entity::new(
Uuid::new_v4(),
world.clone(),
item_position,
&EntityType::ITEM,
false,
),
Entity::new(world.clone(), item_position, &EntityType::ITEM),
ItemStack::new(1, &Item::BONE_MEAL),
)
.await;

View File

@ -8,6 +8,7 @@ pub mod candle_cakes;
pub mod candles;
pub mod carpet;
pub mod carved_pumpkin;
pub mod wither_skull;
pub mod chain;
pub mod chests;
pub mod chiseled_bookshelf;

View File

@ -10,7 +10,6 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
use std::sync::Arc;
use uuid::Uuid;
#[pumpkin_block("minecraft:pumpkin")]
pub struct PumpkinBlock;
@ -39,11 +38,9 @@ impl crate::block::BlockBehaviour for PumpkinBlock {
)
.await;
let entity = Entity::new(
Uuid::new_v4(),
args.world.clone(),
args.position.to_f64(),
&EntityType::ITEM,
false,
);
let item_entity =
Arc::new(ItemEntity::new(entity, ItemStack::new(4, &Item::PUMPKIN_SEEDS)).await);

View File

@ -28,7 +28,6 @@ use pumpkin_world::world::BlockFlags;
use rand::{Rng, rng};
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
struct DropperScreenFactory(Arc<dyn Inventory>);
@ -200,13 +199,7 @@ impl BlockBehaviour for DropperBlock {
Facing::Up | Facing::Down => 0.125,
_ => 0.15625,
};
let entity = Entity::new(
Uuid::new_v4(),
args.world.clone(),
position,
&EntityType::ITEM,
false,
);
let entity = Entity::new(args.world.clone(), position, &EntityType::ITEM);
let rd = rng().random::<f64>() * 0.1 + 0.2;
let velocity = Vector3::new(
triangle(&mut rng(), facing.x * rd, 0.017_227_5 * 6.),

View File

@ -15,7 +15,6 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::world::BlockFlags;
use rand::Rng;
use uuid::Uuid;
use super::redstone::block_receives_redstone_power;
@ -24,13 +23,7 @@ pub struct TNTBlock;
impl TNTBlock {
pub async fn prime(world: &Arc<World>, location: &BlockPos) {
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
location.to_f64(),
&EntityType::TNT,
false,
);
let entity = Entity::new(world.clone(), location.to_f64(), &EntityType::TNT);
let pos = entity.pos.load();
let tnt = Arc::new(TNTEntity::new(entity, DEFAULT_POWER, DEFAULT_FUSE));
world.spawn_entity(tnt).await;
@ -85,13 +78,7 @@ impl BlockBehaviour for TNTBlock {
fn explode<'a>(&'a self, args: ExplodeArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let entity = Entity::new(
Uuid::new_v4(),
args.world.clone(),
args.position.to_f64(),
&EntityType::TNT,
false,
);
let entity = Entity::new(args.world.clone(), args.position.to_f64(), &EntityType::TNT);
let angle = rand::random::<f64>() * std::f64::consts::TAU;
entity
.set_velocity(Vector3::new(-angle.sin() * 0.02, 0.2, -angle.cos() * 0.02))

View File

View File

@ -6,7 +6,6 @@ use std::sync::{
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3;
use uuid::Uuid;
use crate::{entity::EntityBaseFuture, server::Server, world::World};
@ -33,13 +32,7 @@ impl ExperienceOrbEntity {
while amount > 0 {
let i = Self::round_to_orb_size(amount);
amount -= i;
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::EXPERIENCE_ORB,
false,
);
let entity = Entity::new(world.clone(), position, &EntityType::EXPERIENCE_ORB);
let orb = Arc::new(Self::new(entity, i));
world.spawn_entity(orb).await;
}

View File

@ -5,7 +5,6 @@ use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, world::BlockFlags};
use std::sync::{Arc, atomic::Ordering};
use uuid::Uuid;
use crate::{
entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity},
@ -38,13 +37,7 @@ impl FallingEntity {
.await;
let position = position.0.to_f64().add_raw(0.5, 0.0, 0.5);
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::FALLING_BLOCK,
false,
);
let entity = Entity::new(world.clone(), position, &EntityType::FALLING_BLOCK);
entity.data.store(i32::from(block_state), Ordering::Relaxed);
let entity = Arc::new(Self::new(entity, block_state));
world.spawn_entity(entity).await;

View File

@ -51,6 +51,7 @@ use std::sync::{
},
};
use tokio::sync::Mutex;
use uuid::Uuid;
pub mod ai;
pub mod decoration;
@ -368,11 +369,18 @@ pub struct Entity {
impl Entity {
pub fn new(
world: Arc<World>,
position: Vector3<f64>,
entity_type: &'static EntityType,
) -> Self {
Self::from_uuid(Uuid::new_v4(), world, position, entity_type)
}
pub fn from_uuid(
entity_uuid: uuid::Uuid,
world: Arc<World>,
position: Vector3<f64>,
entity_type: &'static EntityType,
invulnerable: bool,
) -> Self {
let floor_x = position.x.floor() as i32;
let floor_y = position.y.floor() as i32;
@ -421,7 +429,7 @@ impl Entity {
&bounding_box_size,
)),
entity_dimension: AtomicCell::new(bounding_box_size),
invulnerable: AtomicBool::new(invulnerable),
invulnerable: AtomicBool::new(false),
damage_immunities: Vec::new(),
data: AtomicI32::new(0),
fire_ticks: AtomicI32::new(-1),

View File

@ -1 +1,2 @@
pub mod snow_golem;
pub mod wolf;

View File

@ -0,0 +1,46 @@
use std::sync::{Arc, Weak};
use pumpkin_data::entity::EntityType;
use crate::entity::{
Entity, NBTStorage,
ai::goal::{look_around::LookAroundGoal, look_at_entity::LookAtEntityGoal},
mob::{Mob, MobEntity},
};
pub struct SnowGolemEntity {
pub mob_entity: MobEntity,
}
impl SnowGolemEntity {
pub async fn make(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity);
let wolf = Self { mob_entity };
let mob_arc = Arc::new(wolf);
let mob_weak: Weak<dyn Mob> = {
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
Arc::downgrade(&mob_arc)
};
{
let mut goal_selector = mob_arc.mob_entity.goals_selector.lock().await;
// TODO
goal_selector.add_goal(
8,
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0),
);
goal_selector.add_goal(8, Box::new(LookAroundGoal::default()));
};
mob_arc
}
}
impl NBTStorage for SnowGolemEntity {}
impl Mob for SnowGolemEntity {
fn get_mob_entity(&self) -> &MobEntity {
&self.mob_entity
}
}

View File

@ -426,13 +426,16 @@ impl Player {
let player_uuid = gameprofile.id;
let living_entity = LivingEntity::new(Entity::new(
let living_entity = LivingEntity::new(Entity::from_uuid(
player_uuid,
world.clone(),
Vector3::new(0.0, 100.0, 0.0),
&EntityType::PLAYER,
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
));
living_entity.entity.invulnerable.store(
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
Ordering::Relaxed,
);
let inventory = Arc::new(PlayerInventory::new(
living_entity.entity_equipment.clone(),
@ -1723,13 +1726,7 @@ impl Player {
pub async fn drop_item(&self, item_stack: ItemStack) {
let item_pos = self.living_entity.entity.pos.load()
+ Vector3::new(0.0, f64::from(EntityType::PLAYER.eye_height) - 0.3, 0.0);
let entity = Entity::new(
Uuid::new_v4(),
self.world().clone(),
item_pos,
&EntityType::ITEM,
false,
);
let entity = Entity::new(self.world().clone(), item_pos, &EntityType::ITEM);
let pitch = f64::from(self.living_entity.entity.pitch.load()).to_radians();
let yaw = f64::from(self.living_entity.entity.yaw.load()).to_radians();

View File

@ -15,7 +15,7 @@ use crate::{
creeper::CreeperEntity, drowned::DrownedEntity, zombie::ZombieEntity,
zombie_villager::ZombieVillagerEntity,
},
passive::wolf::WolfEntity,
passive::{snow_golem::SnowGolemEntity, wolf::WolfEntity},
},
world::World,
};
@ -26,13 +26,14 @@ pub async fn from_type(
world: &Arc<World>,
uuid: Uuid,
) -> Arc<dyn EntityBase> {
let entity = Entity::new(uuid, world.clone(), position, entity_type, false);
let entity = Entity::from_uuid(uuid, world.clone(), position, entity_type);
let mob: Arc<dyn EntityBase> = match entity_type.id {
id if id == EntityType::ZOMBIE.id => ZombieEntity::make(entity).await,
id if id == EntityType::DROWNED.id => DrownedEntity::make(entity).await,
id if id == EntityType::ZOMBIE_VILLAGER.id => ZombieVillagerEntity::make(entity).await,
id if id == EntityType::CREEPER.id => CreeperEntity::make(entity).await,
id if id == EntityType::SNOW_GOLEM.id => SnowGolemEntity::make(entity).await,
id if id == EntityType::WOLF.id => WolfEntity::make(entity).await,
id if id == EntityType::ARMOR_STAND.id => Arc::new(ArmorStandEntity::new(entity)),
id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)),

View File

@ -15,7 +15,6 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::wrap_degrees;
use pumpkin_world::item::ItemStack;
use uuid::Uuid;
pub struct ArmorStandItem;
@ -78,13 +77,7 @@ impl ItemBehaviour for ArmorStandItem {
let (player_yaw, _) = player.rotation();
let rotation = ((wrap_degrees(player_yaw - 180.0) + 22.5) / 45.0).floor() * 45.0;
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::ARMOR_STAND,
false,
);
let entity = Entity::new(world.clone(), position, &EntityType::ARMOR_STAND);
entity.set_rotation(rotation, 0.0);

View File

@ -8,7 +8,6 @@ use crate::item::{ItemBehaviour, ItemMetadata};
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound;
use uuid::Uuid;
pub struct EggItem;
@ -37,13 +36,7 @@ impl ItemBehaviour for EggItem {
)
.await;
// TODO: Implement eggs the right way, so there is a chance of spawning chickens
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::EGG,
false,
);
let entity = Entity::new(world.clone(), position, &EntityType::EGG);
let egg = ThrownItemEntity::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load();

View File

@ -12,7 +12,6 @@ use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack;
use uuid::Uuid;
pub struct EndCrystalItem;
@ -41,13 +40,7 @@ impl ItemBehaviour for EndCrystalItem {
}
let location = location.up();
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
location.to_f64(),
&EntityType::END_CRYSTAL,
false,
);
let entity = Entity::new(world.clone(), location.to_f64(), &EntityType::END_CRYSTAL);
let end_crystal = Arc::new(EndCrystalEntity::new(entity));
world.spawn_entity(end_crystal.clone()).await;
end_crystal.set_show_bottom(false).await;

View File

@ -14,7 +14,6 @@ use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack;
use uuid::Uuid;
pub struct FireworkRocketItem;
@ -38,7 +37,6 @@ impl ItemBehaviour for FireworkRocketItem {
Box::pin(async move {
let world = player.world();
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
Vector3::new(
f64::from(location.0.x) + f64::from(cursor_pos.x),
@ -46,7 +44,6 @@ impl ItemBehaviour for FireworkRocketItem {
f64::from(location.0.z) + f64::from(cursor_pos.z),
),
&EntityType::FIREWORK_ROCKET,
false,
);
let entity = FireworkRocketEntity::new(entity).await;
world.spawn_entity(Arc::new(entity)).await;
@ -62,11 +59,9 @@ impl ItemBehaviour for FireworkRocketItem {
if player.get_entity().fall_flying.load(Ordering::Relaxed) {
let world = player.world();
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
player.get_entity().pos.load(),
&EntityType::FIREWORK_ROCKET,
false,
);
let entity = FireworkRocketEntity::new_shot(entity, player.get_entity()).await;
world.spawn_entity(Arc::new(entity)).await;

View File

@ -14,7 +14,6 @@ use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
use std::pin::Pin;
use std::sync::Arc;
use uuid::Uuid;
pub struct HoeItem;
@ -88,13 +87,7 @@ impl ItemBehaviour for HoeItem {
BlockDirection::West => location.up().to_f64().add_raw(-1.0, -0.4, 0.0),
BlockDirection::East => location.up().to_f64().add_raw(1.0, -0.4, 0.0),
};
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
location,
&EntityType::ITEM,
false,
);
let entity = Entity::new(world.clone(), location, &EntityType::ITEM);
// TODO: Merge stacks together
let item_entity = Arc::new(
ItemEntity::new(entity, ItemStack::new(1, &Item::HANGING_ROOTS)).await,

View File

@ -16,7 +16,6 @@ use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack;
use uuid::Uuid;
pub struct MinecartItem;
@ -79,11 +78,9 @@ impl ItemBehaviour for MinecartItem {
let entity_type = Self::item_to_entity(item.item);
let pos = location.to_f64();
let entity = Arc::new(Entity::new(
Uuid::new_v4(),
world.clone(),
Vector3::new(pos.x, pos.y + 0.0625 + height, pos.z),
entity_type,
false,
));
world.spawn_entity(entity).await;
})

View File

@ -8,7 +8,6 @@ use crate::item::{ItemBehaviour, ItemMetadata};
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound;
use uuid::Uuid;
pub struct SnowBallItem;
@ -36,13 +35,7 @@ impl ItemBehaviour for SnowBallItem {
&position,
)
.await;
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::SNOWBALL,
false,
);
let entity = Entity::new(world.clone(), position, &EntityType::SNOWBALL);
let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load();

View File

@ -5,7 +5,6 @@ use crate::entity::player::Player;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound;
use uuid::Uuid;
use crate::entity::Entity;
use crate::entity::projectile::ThrownItemEntity;
@ -42,13 +41,7 @@ impl ItemBehaviour for WindChargeItem {
)
.await;
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::WIND_CHARGE,
false,
);
let entity = Entity::new(world.clone(), position, &EntityType::WIND_CHARGE);
let wind_charge = ThrownItemEntity::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load();

View File

@ -1126,20 +1126,16 @@ impl World {
!= &Block::LIGHTNING_ROD
{
let entity = Entity::new(
Uuid::new_v4(),
self.clone(),
random_pos.to_f64(),
&EntityType::SKELETON_HORSE,
false,
);
self.spawn_entity(Arc::new(entity)).await;
}
let entity = Entity::new(
Uuid::new_v4(),
self.clone(),
random_pos.to_f64().add_raw(0.5, 0., 0.5),
&EntityType::LIGHTNING_BOLT,
false,
);
self.spawn_entity(Arc::new(entity)).await;
}
@ -2762,7 +2758,7 @@ impl World {
f64::from(pos.0.z) + 0.5 + rand::rng().random_range(-0.25..0.25),
);
let entity = Entity::new(Uuid::new_v4(), self.clone(), pos, &EntityType::ITEM, false);
let entity = Entity::new(self.clone(), pos, &EntityType::ITEM);
let item_entity = Arc::new(ItemEntity::new(entity, stack).await);
self.spawn_entity(item_entity).await;
}
@ -2808,13 +2804,7 @@ impl World {
rng.next_triangular(XZ_MODE, TRIANGULAR_DEVIATION),
);
let entity = Entity::new(
Uuid::new_v4(),
self.clone(),
Vector3::new(x, y, z),
&EntityType::ITEM,
false,
);
let entity = Entity::new(self.clone(), Vector3::new(x, y, z), &EntityType::ITEM);
let entity = Arc::new(ItemEntity::new_with_velocity(entity, item, velocity, 10).await);
self.spawn_entity(entity).await;
}