feat: add more dispenser behaviors (#2760)

Adds vanilla dispense behavior for snowballs, eggs, splash and
lingering potions, fire charges, wind charges, firework rockets,
empty and filled buckets, flint and steel (including priming TNT)
and honeycomb.

Refactors the player-path helpers (Ignition::ignite_block, bucket
pickup/placement, honeycomb waxing) so they can be reused without a
Player, and emits the wax-on world event for players as well.

Co-authored-by: Mcxiaocaibug <Mcxiaocaibug@users.noreply.github.com>
This commit is contained in:
Mcxiaocaibug
2026-08-14 00:08:40 +08:00
committed by GitHub
parent cb856eb074
commit e61517780a
7 changed files with 497 additions and 124 deletions

View File

@@ -1,9 +1,11 @@
use rand::{Rng, RngExt, rng};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::blocks::tnt::TNTBlock;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs,
@@ -11,18 +13,31 @@ use crate::block::{
};
use crate::entity::decoration::armor_stand::ArmorStandEntity;
use crate::entity::item::ItemEntity;
use crate::entity::projectile::ThrownItemEntity;
use crate::entity::projectile::arrow::{ArrowEntity, ArrowPickup};
use crate::entity::projectile::egg::EggEntity;
use crate::entity::projectile::firework_rocket::FireworkRocketEntity;
use crate::entity::projectile::lingering_potion::LingeringPotionEntity;
use crate::entity::projectile::small_fireball::SmallFireballEntity;
use crate::entity::projectile::snowball::SnowballEntity;
use crate::entity::projectile::splash_potion::SplashPotionEntity;
use crate::entity::projectile::wind_charge::{WIND_CHARGE_GRAVITY, WindChargeEntity};
use crate::entity::tnt::TNTEntity;
use crate::entity::r#type::from_type;
use crate::entity::vehicle::boat::BoatEntity;
use crate::entity::{Entity, EntityBase};
use crate::item::ItemMetadata;
use crate::item::items::boat::BoatItem;
use crate::item::items::bucket::{
FilledBucketItem, play_bucket_evaporation, should_evaporate_in_nether, try_pickup_fluid_at,
try_place_filled_bucket,
};
use crate::item::items::honeycomb::try_wax_block;
use crate::item::items::ignite::ignition::Ignition;
use crate::item::items::spawn_egg::apply_entity_variant;
use crate::world::World;
use crate::block::entities::dispenser::DispenserBlockEntity;
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, Facing};
use pumpkin_data::entity::{EntityType, entity_from_egg};
use pumpkin_data::fluid::Fluid;
@@ -31,6 +46,7 @@ use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::translation;
use pumpkin_data::world::WorldEvent;
use pumpkin_data::{Block, BlockStateId, FacingExt};
use pumpkin_inventory::generic_container_screen_handler::create_generic_3x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
@@ -196,39 +212,7 @@ impl BlockBehaviour for DispenserBlock {
args.block,
);
let ctx = DispenseContext::new(&args, props.facing);
// Still missing some specific dispenser behavior that you can find here:
// https://minecraft.wiki/w/Dispenser#Usage
let arrows = [
Item::ARROW.id,
Item::TIPPED_ARROW.id,
Item::SPECTRAL_ARROW.id,
];
let boats = BoatItem::ids();
if arrows.contains(&item.item.id) {
// Arrows
Self::fire_arrow(&ctx, &mut item).await;
} else if boats.contains(&item.item.id) {
// Boats
if !Self::dispense_boat(&ctx, &mut item).await {
Self::drop_item(&ctx, &mut item).await;
}
} else if item.item.id == Item::ARMOR_STAND.id {
// Armor stands
if !Self::dispense_armor_stand(&ctx, &mut item).await {
Self::drop_item(&ctx, &mut item).await;
}
} else if item.item.id == Item::TNT.id {
// TNT
Self::dispense_tnt(&ctx, &mut item).await;
} else if entity_from_egg(item.item.id).is_some() {
// Spawn eggs
Self::dispense_spawn_egg(&ctx, &mut item).await;
} else {
// Default / Drop
Self::drop_item(&ctx, &mut item).await;
}
Self::dispense(&ctx, dispenser, &mut item).await;
dispenser.set_stack(slot_index, item).await;
} else {
args.world
@@ -255,19 +239,123 @@ impl BlockBehaviour for DispenserBlock {
}
impl DispenserBlock {
const ARROW_DISPENSE_POWER: f64 = 1.1;
const ARROW_DISPENSE_UNCERTAINTY: f64 = 6.0;
// Velocity values match the vanilla dispenser projectile settings.
const DEFAULT_PROJECTILE_POWER: f64 = 1.1;
const DEFAULT_PROJECTILE_UNCERTAINTY: f64 = 6.0;
const POTION_PROJECTILE_POWER: f64 = 1.375;
const POTION_PROJECTILE_UNCERTAINTY: f64 = 3.0;
// Fire charges and wind charges share these values.
const FIREBALL_PROJECTILE_POWER: f64 = 1.0;
const FIREBALL_PROJECTILE_UNCERTAINTY: f64 = 6.666_666_5;
const FIREWORK_PROJECTILE_POWER: f64 = 0.5;
const FIREWORK_PROJECTILE_UNCERTAINTY: f64 = 1.0;
async fn dispense(
ctx: &DispenseContext<'_>,
dispenser: &DispenserBlockEntity,
item: &mut ItemStack,
) {
// Still missing some specific dispenser behavior that you can find here:
// https://minecraft.wiki/w/Dispenser#Usage
let arrows = [
Item::ARROW.id,
Item::TIPPED_ARROW.id,
Item::SPECTRAL_ARROW.id,
];
let boats = BoatItem::ids();
if arrows.contains(&item.item.id) {
// Arrows
Self::fire_arrow(ctx, item).await;
} else if boats.contains(&item.item.id) {
// Boats
if !Self::dispense_boat(ctx, item).await {
Self::drop_item(ctx, item).await;
}
} else if item.item.id == Item::ARMOR_STAND.id {
// Armor stands
if !Self::dispense_armor_stand(ctx, item).await {
Self::drop_item(ctx, item).await;
}
} else if item.item.id == Item::TNT.id {
// TNT
Self::dispense_tnt(ctx, item).await;
} else if item.item.id == Item::SNOWBALL.id {
Self::dispense_snowball(ctx, item).await;
} else if item.item.id == Item::EGG.id {
Self::dispense_egg(ctx, item).await;
} else if item.item.id == Item::SPLASH_POTION.id {
Self::dispense_splash_potion(ctx, item).await;
} else if item.item.id == Item::LINGERING_POTION.id {
Self::dispense_lingering_potion(ctx, item).await;
} else if item.item.id == Item::FIRE_CHARGE.id {
Self::dispense_fire_charge(ctx, item).await;
} else if item.item.id == Item::WIND_CHARGE.id {
Self::dispense_wind_charge(ctx, item).await;
} else if item.item.id == Item::FIREWORK_ROCKET.id {
Self::dispense_firework_rocket(ctx, item).await;
} else if item.item.id == Item::BUCKET.id {
// Empty buckets pick up the fluid in front of the dispenser
Self::dispense_empty_bucket(ctx, dispenser, item).await;
} else if FilledBucketItem::ids().contains(&item.item.id) {
// Filled buckets place their fluid in front of the dispenser
Self::dispense_filled_bucket(ctx, item).await;
} else if item.item.id == Item::FLINT_AND_STEEL.id {
// Flint and steel light fires and prime TNT
Self::dispense_flint_and_steel(ctx, item).await;
} else if item.item.id == Item::HONEYCOMB.id {
// Honeycombs wax copper blocks
Self::dispense_honeycomb(ctx, item).await;
} else if entity_from_egg(item.item.id).is_some() {
// Spawn eggs
Self::dispense_spawn_egg(ctx, item).await;
} else {
// Default / Drop
Self::drop_item(ctx, item).await;
}
}
fn projectile_spawn_position(ctx: &DispenseContext<'_>) -> Vector3<f64> {
ctx.position
.to_centered_f64()
.add(&(to_normal(ctx.facing) * 0.7))
}
fn launch_thrown(
ctx: &DispenseContext<'_>,
thrown: &ThrownItemEntity,
power: f64,
uncertainty: f64,
) {
let facing = to_normal(ctx.facing);
thrown.set_velocity(facing.x, facing.y + 0.1, facing.z, power, uncertainty);
}
async fn finish_projectile_launch(
ctx: &DispenseContext<'_>,
projectile: Arc<dyn EntityBase>,
launch_event: WorldEvent,
) {
ctx.world.spawn_entity(projectile).await;
Self::play_dispense_effects(ctx, launch_event);
}
fn play_dispense_effects(ctx: &DispenseContext<'_>, sound_event: WorldEvent) {
ctx.world.sync_world_event(sound_event, *ctx.position, 0);
ctx.world.sync_world_event(
WorldEvent::ParticlesShootSmoke,
*ctx.position,
to_data3d(ctx.facing),
);
}
async fn fire_arrow(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let projectile = item.split(1);
let facing = to_normal(ctx.facing);
let position = ctx.position.to_centered_f64().add(&(facing * 0.7));
let world = ctx.world;
let arrow_entity = Entity::new(
world.clone(),
position,
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
ArrowEntity::entity_type_for_item(projectile.item),
);
let arrow =
@@ -277,21 +365,16 @@ impl DispenserBlock {
facing.x,
facing.y + 0.1,
facing.z,
Self::ARROW_DISPENSE_POWER,
Self::ARROW_DISPENSE_UNCERTAINTY,
Self::DEFAULT_PROJECTILE_POWER,
Self::DEFAULT_PROJECTILE_UNCERTAINTY,
);
let arrow_arc: Arc<dyn EntityBase> = Arc::new(arrow);
world.spawn_entity(arrow_arc).await;
ctx.world
.sync_world_event(WorldEvent::SoundDispenserProjectileLaunch, *ctx.position, 0);
ctx.world.sync_world_event(
WorldEvent::ParticlesShootSmoke,
*ctx.position,
to_data3d(ctx.facing),
);
Self::finish_projectile_launch(
ctx,
Arc::new(arrow),
WorldEvent::SoundDispenserProjectileLaunch,
)
.await;
}
fn target_position(ctx: &DispenseContext<'_>) -> BlockPos {
@@ -417,8 +500,299 @@ impl DispenserBlock {
.sync_world_event(WorldEvent::SoundDispenserDispense, *ctx.position, 0);
}
async fn dispense_snowball(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let _ = item.split(1);
let entity = Entity::new(
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
&EntityType::SNOWBALL,
);
let snowball = SnowballEntity::new(entity);
Self::launch_thrown(
ctx,
&snowball.thrown,
Self::DEFAULT_PROJECTILE_POWER,
Self::DEFAULT_PROJECTILE_UNCERTAINTY,
);
Self::finish_projectile_launch(
ctx,
Arc::new(snowball),
WorldEvent::SoundDispenserProjectileLaunch,
)
.await;
}
async fn dispense_egg(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let projectile = item.split(1);
let entity = Entity::new(
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
&EntityType::EGG,
);
let egg = EggEntity::new(entity);
egg.set_item_stack(projectile).await;
Self::launch_thrown(
ctx,
&egg.thrown,
Self::DEFAULT_PROJECTILE_POWER,
Self::DEFAULT_PROJECTILE_UNCERTAINTY,
);
Self::finish_projectile_launch(
ctx,
Arc::new(egg),
WorldEvent::SoundDispenserProjectileLaunch,
)
.await;
}
async fn dispense_splash_potion(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let projectile = item.split(1);
let entity = Entity::new(
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
&EntityType::SPLASH_POTION,
);
let potion = SplashPotionEntity::new(entity);
potion.set_item_stack(projectile).await;
Self::launch_thrown(
ctx,
&potion.thrown,
Self::POTION_PROJECTILE_POWER,
Self::POTION_PROJECTILE_UNCERTAINTY,
);
Self::finish_projectile_launch(
ctx,
Arc::new(potion),
WorldEvent::SoundDispenserProjectileLaunch,
)
.await;
}
async fn dispense_lingering_potion(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let projectile = item.split(1);
let entity = Entity::new(
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
&EntityType::LINGERING_POTION,
);
let potion = LingeringPotionEntity::new(entity);
potion.set_item_stack(projectile).await;
Self::launch_thrown(
ctx,
&potion.thrown,
Self::POTION_PROJECTILE_POWER,
Self::POTION_PROJECTILE_UNCERTAINTY,
);
Self::finish_projectile_launch(
ctx,
Arc::new(potion),
WorldEvent::SoundDispenserProjectileLaunch,
)
.await;
}
async fn dispense_fire_charge(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let _ = item.split(1);
let entity = Entity::new(
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
&EntityType::SMALL_FIREBALL,
);
let fireball = SmallFireballEntity::new(entity);
// Vanilla aims fire charges straight along the facing axis, without the +0.1 Y bias
// other projectiles get.
let facing = to_normal(ctx.facing);
fireball.thrown.set_velocity(
facing.x,
facing.y,
facing.z,
Self::FIREBALL_PROJECTILE_POWER,
Self::FIREBALL_PROJECTILE_UNCERTAINTY,
);
Self::finish_projectile_launch(ctx, Arc::new(fireball), WorldEvent::SoundBlazeFireball)
.await;
}
async fn dispense_wind_charge(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let _ = item.split(1);
let entity = Entity::new(
ctx.world.clone(),
Self::projectile_spawn_position(ctx),
&EntityType::WIND_CHARGE,
);
let thrown = ThrownItemEntity {
entity,
owner_id: None,
collides_with_projectiles: false,
has_hit: AtomicBool::new(false),
gravity: WIND_CHARGE_GRAVITY,
};
Self::launch_thrown(
ctx,
&thrown,
Self::FIREBALL_PROJECTILE_POWER,
Self::FIREBALL_PROJECTILE_UNCERTAINTY,
);
Self::finish_projectile_launch(
ctx,
Arc::new(WindChargeEntity::new_normal(thrown)),
WorldEvent::SoundWindChargeShoot,
)
.await;
}
async fn dispense_firework_rocket(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let _ = item.split(1);
let facing = to_normal(ctx.facing);
// Vanilla spawns fireworks closer to the dispenser face and slightly above center.
let position = ctx
.position
.to_centered_f64()
.add(&(facing * (0.7 * 0.5125)))
.add(&Vector3::new(0.0, 0.08, 0.0));
let entity = Entity::new(ctx.world.clone(), position, &EntityType::FIREWORK_ROCKET);
let rocket = FireworkRocketEntity::new(entity);
// `FireworkRocketEntity` does not expose its inner projectile, so replicate
// `ThrownItemEntity::set_velocity` here.
let deviation = 0.017_227_5 * Self::FIREWORK_PROJECTILE_UNCERTAINTY;
let velocity = Vector3::new(facing.x, facing.y + 0.1, facing.z)
.normalize()
.add_raw(
triangle(&mut rng(), 0.0, deviation),
triangle(&mut rng(), 0.0, deviation),
triangle(&mut rng(), 0.0, deviation),
)
.multiply(
Self::FIREWORK_PROJECTILE_POWER,
Self::FIREWORK_PROJECTILE_POWER,
Self::FIREWORK_PROJECTILE_POWER,
);
let rocket_entity = rocket.get_entity();
rocket_entity.set_velocity(velocity);
rocket_entity.set_rotation(
velocity.x.atan2(velocity.z) as f32 * 57.295_776,
velocity.y.atan2(velocity.horizontal_length()) as f32 * 57.295_776,
);
Self::finish_projectile_launch(ctx, Arc::new(rocket), WorldEvent::SoundFireworkShoot).await;
}
async fn dispense_empty_bucket(
ctx: &DispenseContext<'_>,
dispenser: &DispenserBlockEntity,
item: &mut ItemStack,
) {
let front = Self::target_position(ctx);
let Some(filled) = try_pickup_fluid_at(ctx.world, front).await else {
Self::drop_item(ctx, item).await;
return;
};
item.decrement(1);
let filled_stack = ItemStack::new(1, filled);
if item.is_empty() {
*item = filled_stack;
} else if let Some(rest) = Self::add_to_first_free_slot(dispenser, filled_stack).await {
Self::eject_item(ctx, rest).await;
}
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
}
/// Places `stack` into the first empty slot, returning it back if every slot is occupied.
/// The slot currently being dispensed from still holds its pre-dispense stack, so it is
/// never considered free.
async fn add_to_first_free_slot(
dispenser: &DispenserBlockEntity,
stack: ItemStack,
) -> Option<ItemStack> {
let mut items = dispenser.items.write().await;
for slot in items.iter_mut() {
if slot.is_empty() {
*slot = stack;
dispenser.mark_dirty();
return None;
}
}
Some(stack)
}
async fn dispense_filled_bucket(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let front = Self::target_position(ctx);
// TODO: Spawn the stored entity for axolotl/fish/tadpole buckets, like the player path.
let emptied = if should_evaporate_in_nether(item.item, ctx.world) {
play_bucket_evaporation(ctx.world, &front.to_f64());
true
} else {
try_place_filled_bucket(
ctx.world,
item.item,
*ctx.position,
ctx.facing.to_block_direction(),
)
.await
};
if emptied {
*item = ItemStack::new(1, &Item::BUCKET);
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
} else {
Self::drop_item(ctx, item).await;
}
}
async fn dispense_flint_and_steel(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let front = Self::target_position(ctx);
let front_block = ctx.world.get_block(&front);
let ignited = if front_block == &Block::TNT {
TNTBlock::prime(ctx.world, &front).await;
true
} else {
Ignition::ignite_block(
|world: Arc<World>, pos: BlockPos, new_state_id: BlockStateId| async move {
world
.set_block_state(&pos, new_state_id, BlockFlags::NOTIFY_ALL)
.await;
},
ctx.world,
front,
front,
front_block,
)
.await
};
if ignited {
// `damage_item` already consumes the tool from the stack when it breaks.
let _ = item.damage_item(1);
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
} else {
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserFail);
}
}
async fn dispense_honeycomb(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let front = Self::target_position(ctx);
let front_block = ctx.world.get_block(&front);
if try_wax_block(ctx.world, front, front_block).await {
item.decrement(1);
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
} else {
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserFail);
}
}
async fn drop_item(ctx: &DispenseContext<'_>, item: &mut ItemStack) {
let drop_item = item.split(1);
Self::eject_item(ctx, drop_item).await;
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
}
async fn eject_item(ctx: &DispenseContext<'_>, stack: ItemStack) {
let facing = to_normal(ctx.facing);
let mut position = ctx.position.to_centered_f64().add(&(facing * 0.7));
@@ -436,18 +810,7 @@ impl DispenserBlock {
triangle(&mut rng(), facing.z * rd, 0.017_227_5 * 6.),
);
let item_entity = Arc::new(ItemEntity::new_with_velocity(
entity, drop_item, velocity, 40,
));
let item_entity = Arc::new(ItemEntity::new_with_velocity(entity, stack, velocity, 40));
ctx.world.spawn_entity(item_entity).await;
ctx.world
.sync_world_event(WorldEvent::SoundDispenserDispense, *ctx.position, 0);
ctx.world.sync_world_event(
WorldEvent::ParticlesShootSmoke,
*ctx.position,
to_data3d(ctx.facing),
);
}
}

View File

@@ -133,10 +133,11 @@ async fn give_player_bucket_item(player: &Player, item: &'static Item) {
}
}
async fn try_pickup_bucket_item(
/// Tries to pick up powder snow, a waterlogged block, or a fluid source block at `block_pos`,
/// returning the matching filled bucket item on success.
pub(crate) async fn try_pickup_fluid_at(
world: &Arc<World>,
block_pos: BlockPos,
direction: BlockDirection,
) -> Option<&'static Item> {
let (block, state) = world.get_block_and_state_id(&block_pos);
@@ -178,6 +179,18 @@ async fn try_pickup_bucket_item(
});
}
None
}
async fn try_pickup_bucket_item(
world: &Arc<World>,
block_pos: BlockPos,
direction: BlockDirection,
) -> Option<&'static Item> {
if let Some(item) = try_pickup_fluid_at(world, block_pos).await {
return Some(item);
}
let target_pos = block_pos.offset(direction.to_offset());
let (block, state) = world.get_block_and_state_id(&target_pos);
if waterlogged_check(block, state).is_some() {
@@ -192,17 +205,17 @@ async fn try_pickup_bucket_item(
None
}
fn should_evaporate_in_nether(item: &Item, world: &World) -> bool {
pub(crate) fn should_evaporate_in_nether(item: &Item, world: &World) -> bool {
item.id != Item::LAVA_BUCKET.id
&& item.id != Item::POWDER_SNOW_BUCKET.id
&& world.dimension == Dimension::THE_NETHER
}
fn play_bucket_evaporation(world: &Arc<World>, player: &Player) {
pub(crate) fn play_bucket_evaporation(world: &Arc<World>, position: &Vector3<f64>) {
world.play_sound_raw(
Sound::BlockFireExtinguish as u16,
SoundCategory::Blocks,
&player.position(),
position,
0.5,
(rand::random::<f32>() - rand::random::<f32>()).mul_add(0.8, 2.6),
);
@@ -233,7 +246,7 @@ async fn try_place_powder_snow(
true
}
async fn try_place_filled_bucket(
pub(crate) async fn try_place_filled_bucket(
world: &Arc<World>,
item: &Item,
pos: BlockPos,
@@ -365,7 +378,7 @@ impl ItemBehaviour for FilledBucketItem {
};
if should_evaporate_in_nether(item, &world) {
play_bucket_evaporation(&world, player);
play_bucket_evaporation(&world, &player.position());
return;
}
if !try_place_filled_bucket(&world, item, pos, direction).await {

View File

@@ -9,6 +9,7 @@ use crate::block::registry::BlockActionResult;
use crate::entity::player::Player;
use crate::item::{ItemBehaviour, ItemMetadata};
use crate::server::Server;
use crate::world::World;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::OakDoorLikeProperties;
use pumpkin_data::item::Item;
@@ -42,26 +43,28 @@ impl ItemBehaviour for HoneyCombItem {
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let world = player.world();
try_wax_block(&world, location, block).await;
})
}
// First we try to strip the block. by getting his equivalent and applying it the axis.
let replacement = get_waxed_equivalent(block.id);
// If there is a strip equivalent.
if let Some(replacement) = replacement {
// get block state of the old log.
// get the log properties
// create new properties for the new log.
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
/// Waxes the block at `location` if it has a waxed equivalent, emitting the wax
/// particles and sound on success.
pub(crate) async fn try_wax_block(world: &Arc<World>, location: BlockPos, block: &Block) -> bool {
let Some(replacement) = get_waxed_equivalent(block.id) else {
return false;
};
let new_block = replacement.to_block();
let new_state_id = if block.has_tag(&tag::Block::MINECRAFT_DOORS)
&& block.has_tag(&tag::Block::MINECRAFT_DOORS)
{
// get block state of the old log.
let new_state_id = if block.has_tag(&tag::Block::MINECRAFT_DOORS) {
// Carry the door state over to the waxed door.
let door_information = world.get_block_state_id(&location);
// get the log properties
let door_props = OakDoorLikeProperties::from_state_id(door_information, block);
// create new properties for the new log.
let mut new_door_properties = OakDoorLikeProperties::default(new_block);
// Set old axis to the new log.
new_door_properties.facing = door_props.facing;
new_door_properties.open = door_props.open;
new_door_properties.half = door_props.half;
@@ -69,20 +72,15 @@ impl ItemBehaviour for HoneyCombItem {
new_door_properties.powered = door_props.powered;
new_door_properties.to_state_id(new_block)
} else {
// TODO: Also carry over the properties of trapdoors.
new_block.default_state.id
};
// TODO Implements trapdoors
world
.set_block_state(&location, new_state_id, BlockFlags::NOTIFY_ALL)
.await;
}
})
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
world.sync_world_event(WorldEvent::ParticlesAndSoundWaxOn, location, 0);
true
}
impl HoneyCombItem {

View File

@@ -37,6 +37,7 @@ impl ItemBehaviour for FireChargeItem {
_server: &'a Server,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let world = player.world();
Ignition::ignite_block(
|world: Arc<World>, pos: BlockPos, new_state_id: BlockStateId| async move {
world
@@ -45,9 +46,9 @@ impl ItemBehaviour for FireChargeItem {
world.play_block_sound(Sound::ItemFirechargeUse, SoundCategory::Blocks, pos);
},
player,
&world,
location,
face,
location.offset(face.to_offset()),
block,
)
.await;

View File

@@ -60,9 +60,9 @@ impl ItemBehaviour for FlintAndSteelItem {
.set_block_state(&pos, new_state_id, BlockFlags::NOTIFY_ALL)
.await;
},
player,
&world,
location,
face,
location.offset(face.to_offset()),
block,
)
.await;

View File

@@ -1,34 +1,32 @@
use crate::block::blocks::fire::FireBlockBase;
use crate::block::blocks::fire::fire::FireBlock;
use crate::entity::player::Player;
use crate::world::World;
use pumpkin_data::fluid::Fluid;
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, BlockDirection, BlockStateId, tag};
use pumpkin_data::{Block, BlockStateId, tag};
use pumpkin_util::math::position::BlockPos;
use std::sync::Arc;
pub struct Ignition;
impl Ignition {
/// Lights `block` at `location` itself if it can be lit (campfires, candles, candle
/// cakes), otherwise places a fire block at `fire_pos`.
pub async fn ignite_block<F, Fut>(
ignite_logic: F,
player: &Player,
world: &Arc<World>,
location: BlockPos,
face: BlockDirection,
fire_pos: BlockPos,
block: &Block,
) -> bool
where
F: FnOnce(Arc<World>, BlockPos, BlockStateId) -> Fut,
Fut: Future<Output = ()>,
{
let world = player.world();
let pos = location.offset(face.to_offset());
if world.get_fluid(&location).name != Fluid::EMPTY.name {
return false;
}
let fire_block = FireBlockBase::get_fire_type(&world, &pos);
let fire_block = FireBlockBase::get_fire_type(world, &fire_pos);
let state_id = world.get_block_state_id(&location);
@@ -37,9 +35,9 @@ impl Ignition {
return true;
}
let state_id = FireBlock.get_state_for_position(&world, &fire_block, &pos);
if FireBlockBase::can_place_at(&world, &pos) {
ignite_logic(world.clone(), pos, state_id).await;
let state_id = FireBlock.get_state_for_position(world, &fire_block, &fire_pos);
if FireBlockBase::can_place_at(world, &fire_pos) {
ignite_logic(world.clone(), fire_pos, state_id).await;
return true;
}

View File

@@ -1,3 +1,3 @@
pub mod fire_charge;
pub mod flint_and_steel;
mod ignition;
pub mod ignition;