feat: add end crystals

This commit is contained in:
Alexander Medvedev 2025-08-21 17:54:07 +02:00
parent 94bdae9684
commit 8feb6c9b93
23 changed files with 173 additions and 42 deletions

View File

@ -164,7 +164,7 @@ impl BlockBehaviour for BedBlock {
.await;
args.world
.explode(args.server, bed_head_pos.to_centered_f64(), 5.0)
.explode(bed_head_pos.to_centered_f64(), 5.0)
.await;
return BlockActionResult::SuccessServer;

View File

@ -0,0 +1,67 @@
use core::f32;
use std::sync::Arc;
use crate::entity::{Entity, EntityBase, NBTStorage, living::LivingEntity};
use async_trait::async_trait;
use pumpkin_data::damage::DamageType;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::java::client::play::{MetaDataType, Metadata};
use pumpkin_util::math::vector3::Vector3;
pub struct EndCrystalEntity {
entity: Entity,
}
impl EndCrystalEntity {
pub fn new(entity: Entity) -> Self {
Self { entity }
}
}
impl EndCrystalEntity {
pub async fn set_show_bottom(&self, show_bottom: bool) {
self.entity
.send_meta_data(&[Metadata::new(9, MetaDataType::Boolean, show_bottom)])
.await;
}
}
#[async_trait]
impl NBTStorage for EndCrystalEntity {
async fn write_nbt(&self, _nbt: &mut NbtCompound) {}
async fn read_nbt_non_mut(&self, _nbt: &NbtCompound) {}
}
#[async_trait]
impl EntityBase for EndCrystalEntity {
fn get_entity(&self) -> &Entity {
&self.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}
async fn damage_with_context(
&self,
_caller: Arc<dyn EntityBase>,
_amount: f32,
damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
if damage_type != DamageType::EXPLOSION {
self.entity.world.explode(self.entity.pos.load(), 6.0).await;
}
// TODO
self.entity.remove().await;
true
}
fn as_nbt_storage(&self) -> &dyn NBTStorage {
self
}
}

View File

@ -1 +1,2 @@
pub mod end_crystal;
pub mod painting;

View File

@ -1,8 +1,8 @@
use async_trait::async_trait;
use pumpkin_data::Block;
use pumpkin_data::{entity::EntityType};
use pumpkin_data::entity::EntityType;
use pumpkin_protocol::java::client::play::{MetaDataType, Metadata};
use pumpkin_util::math::{position::BlockPos};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, world::BlockFlags};
use std::sync::{Arc, atomic::Ordering};
use uuid::Uuid;

View File

@ -18,12 +18,12 @@ use crate::world::loot::{LootContextParameters, LootTableExt};
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::advanced_config;
use pumpkin_data::Block;
use pumpkin_data::damage::DeathMessageType;
use pumpkin_data::data_component_impl::{DeathProtectionImpl, EquipmentSlot, FoodImpl};
use pumpkin_data::effect::StatusEffect;
use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType};
use pumpkin_data::sound::SoundCategory;
use pumpkin_data::Block;
use pumpkin_data::{damage::DamageType, sound::Sound};
use pumpkin_inventory::entity_equipment::EntityEquipment;
use pumpkin_nbt::compound::NbtCompound;
@ -871,6 +871,7 @@ impl LivingEntity {
if let Some(player) = caller.get_player() {
return player.inventory.held_item();
}
// TODO: this is wrong
let slot = self
.equipment_slots
.get(&PlayerInventory::OFF_HAND_SLOT)

View File

@ -2,7 +2,7 @@ use super::{Entity, EntityBase, NBTStorage, living::LivingEntity};
use crate::server::Server;
use async_trait::async_trait;
use core::f32;
use pumpkin_data::{Block};
use pumpkin_data::Block;
use pumpkin_protocol::{
codec::var_int::VarInt,
java::client::play::{MetaDataType, Metadata},
@ -65,7 +65,7 @@ impl EntityBase for TNTEntity {
self.entity.remove().await;
self.entity
.world
.explode(server, self.entity.pos.load(), self.power)
.explode(self.entity.pos.load(), self.power)
.await;
} else {
entity.update_fluid_state(&caller).await;

View File

@ -5,7 +5,11 @@ use pumpkin_util::math::vector3::Vector3;
use uuid::Uuid;
use crate::{
entity::{Entity, EntityBase, decoration::painting::PaintingEntity, mob::zombie::Zombie},
entity::{
Entity, EntityBase,
decoration::{end_crystal::EndCrystalEntity, painting::PaintingEntity},
mob::zombie::Zombie,
},
world::World,
};
@ -21,6 +25,7 @@ pub async fn from_type(
let mob: Arc<dyn EntityBase> = match entity_type.id {
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::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)),
// TODO
_ => Arc::new(entity), // Fallback Entity
};

View File

@ -5,10 +5,10 @@ use async_trait::async_trait;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::{OakDoorLikeProperties, PaleOakWoodLikeProperties};
use pumpkin_data::item::Item;
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
pub struct AxeItem;
@ -24,7 +24,7 @@ impl ItemBehaviour for AxeItem {
#[allow(clippy::too_many_lines)]
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
_face: BlockDirection,

View File

@ -0,0 +1,58 @@
use std::sync::Arc;
use crate::entity::Entity;
use crate::entity::decoration::end_crystal::EndCrystalEntity;
use crate::entity::player::Player;
use crate::item::{ItemBehaviour, ItemMetadata};
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use uuid::Uuid;
pub struct EndCrystalItem;
impl ItemMetadata for EndCrystalItem {
fn ids() -> Box<[u16]> {
[Item::END_CRYSTAL.id].into()
}
}
#[async_trait]
impl ItemBehaviour for EndCrystalItem {
async fn use_on_block(
&self,
item: &mut ItemStack,
player: &Player,
location: BlockPos,
_face: BlockDirection,
_block: &Block,
_server: &Server,
) {
let world = player.world();
let block = world.get_block(&location).await;
if block != &Block::OBSIDIAN && block != &Block::BEDROCK {
return;
}
let location = location.up();
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
location.to_f64(),
&EntityType::END_CRYSTAL,
false,
);
let end_crystal = Arc::new(EndCrystalEntity::new(entity));
world.spawn_entity(end_crystal.clone()).await;
end_crystal.set_show_bottom(false).await;
item.decrement_unless_creative(player.gamemode.load(), 1);
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}

View File

@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::{server::Server, world::portal::end::EndPortal};
use pumpkin_data::{Block, BlockDirection, item::Item};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
use crate::item::{ItemBehaviour, ItemMetadata};
@ -21,7 +22,7 @@ impl ItemMetadata for EnderEyeItem {
impl ItemBehaviour for EnderEyeItem {
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
_face: BlockDirection,

View File

@ -26,7 +26,7 @@ impl ItemMetadata for HoeItem {
impl ItemBehaviour for HoeItem {
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
face: BlockDirection,

View File

@ -17,6 +17,7 @@ use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::entities::BlockEntity;
use pumpkin_world::block::entities::sign::SignBlockEntity;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
pub struct HoneyCombItem;
@ -31,7 +32,7 @@ impl ItemMetadata for HoneyCombItem {
impl ItemBehaviour for HoneyCombItem {
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
_face: BlockDirection,

View File

@ -7,6 +7,7 @@ use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound;
use pumpkin_data::sound::SoundCategory;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
use crate::entity::player::Player;
@ -27,7 +28,7 @@ impl ItemMetadata for FireChargeItem {
impl ItemBehaviour for FireChargeItem {
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
face: BlockDirection,
@ -44,7 +45,6 @@ impl ItemBehaviour for FireChargeItem {
.play_block_sound(Sound::ItemFirechargeUse, SoundCategory::Blocks, pos)
.await;
},
_item,
player,
location,
face,

View File

@ -8,6 +8,7 @@ use pumpkin_data::Block;
use pumpkin_data::BlockDirection;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
use std::sync::Arc;
@ -25,7 +26,7 @@ impl ItemMetadata for FlintAndSteelItem {
impl ItemBehaviour for FlintAndSteelItem {
async fn use_on_block(
&self,
item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
face: BlockDirection,
@ -38,7 +39,6 @@ impl ItemBehaviour for FlintAndSteelItem {
.set_block_state(&pos, new_state_id, BlockFlags::NOTIFY_ALL)
.await;
},
item,
player,
location,
face,

View File

@ -4,7 +4,6 @@ use crate::entity::player::Player;
use crate::server::Server;
use crate::world::World;
use pumpkin_data::fluid::Fluid;
use pumpkin_data::item::Item;
use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::math::position::BlockPos;
use std::sync::Arc;
@ -14,7 +13,6 @@ pub struct Ignition;
impl Ignition {
pub async fn ignite_block<F, Fut>(
ignite_logic: F,
_item: &Item,
player: &Player,
location: BlockPos,
face: BlockDirection,

View File

@ -15,6 +15,7 @@ use pumpkin_data::tag::Taggable;
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;
@ -51,7 +52,7 @@ impl ItemMetadata for MinecartItem {
impl ItemBehaviour for MinecartItem {
async fn use_on_block(
&self,
item: &Item,
item: &mut ItemStack,
player: &Player,
location: BlockPos,
_face: BlockDirection,
@ -74,7 +75,7 @@ impl ItemBehaviour for MinecartItem {
.is_ascending()
};
let height = if is_ascending { 0.5 } else { 0.0 };
let entity_type = Self::item_to_entity(item);
let entity_type = Self::item_to_entity(item.item);
let pos = location.to_f64();
let entity = Arc::new(Entity::new(
Uuid::new_v4(),

View File

@ -2,6 +2,7 @@ pub mod axe;
pub mod bucket;
pub mod dye;
pub mod egg;
pub mod end_crystal;
pub mod ender_eye;
pub mod glowing_ink_sac;
pub mod hoe;
@ -15,6 +16,7 @@ pub mod snowball;
pub mod swords;
pub mod trident;
use crate::item::items::end_crystal::EndCrystalItem;
use crate::item::items::minecart::MinecartItem;
use super::registry::ItemRegistry;
@ -51,6 +53,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
manager.register(FilledBucketItem);
manager.register(ShovelItem);
manager.register(AxeItem);
manager.register(EndCrystalItem);
manager.register(MinecartItem);
manager.register(HoneyCombItem);
manager.register(EnderEyeItem);

View File

@ -4,11 +4,11 @@ use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::{BlockProperties, CampfireLikeProperties};
use pumpkin_data::item::Item;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::world::WorldEvent;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use pumpkin_world::world::BlockFlags;
use rand::{Rng, rng};
@ -24,7 +24,7 @@ impl ItemMetadata for ShovelItem {
impl ItemBehaviour for ShovelItem {
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
player: &Player,
location: BlockPos,
face: BlockDirection,

View File

@ -11,6 +11,7 @@ use pumpkin_data::BlockDirection;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack;
pub trait ItemMetadata {
fn ids() -> Box<[u16]>;
@ -22,7 +23,7 @@ pub trait ItemBehaviour: Send + Sync {
async fn use_on_block(
&self,
_item: &Item,
_item: &mut ItemStack,
_player: &Player,
_location: BlockPos,
_face: BlockDirection,

View File

@ -4,6 +4,7 @@ use pumpkin_data::Block;
use pumpkin_data::BlockDirection;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::item::ItemStack;
use std::collections::HashMap;
use std::sync::Arc;
@ -32,17 +33,17 @@ impl ItemRegistry {
pub async fn use_on_block(
&self,
item: &Item,
stack: &mut ItemStack,
player: &Player,
location: BlockPos,
face: BlockDirection,
block: &Block,
server: &Server,
) {
let pumpkin_item = self.get_pumpkin_item(item);
let pumpkin_item = self.get_pumpkin_item(stack.item);
if let Some(pumpkin_item) = pumpkin_item {
pumpkin_item
.use_on_block(item, player, location, face, block, server)
.use_on_block(stack, player, location, face, block, server)
.await;
}
}

View File

@ -1432,20 +1432,14 @@ impl JavaClient {
return Ok(());
}
let mut stack = item.lock().await;
server
.item_registry
.use_on_block(
item.lock().await.item,
player,
position,
face,
block,
server,
)
.use_on_block(&mut stack, player, position, face, block, server)
.await;
// Check if the item is a block, because not every item can be placed :D
let item_id = item.lock().await.item.id;
let item_id = stack.item.id;
if let Some(block) = Block::from_item_id(item_id) {
should_try_decrement = self
.run_is_block_place(player, block, server, use_item_on, position, face)
@ -1453,7 +1447,7 @@ impl JavaClient {
}
// Check if the item is a spawn egg
let item_id = item.lock().await.item.id;
let item_id = stack.item.id;
if let Some(entity) = entity_from_egg(item_id) {
self.spawn_entity_from_egg(player, entity, position, face)
.await;
@ -1464,7 +1458,7 @@ impl JavaClient {
// TODO: Config
// Decrease block count
if player.gamemode.load() != GameMode::Creative {
item.lock().await.decrement(1);
stack.decrement(1);
}
}

View File

@ -6,7 +6,6 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use crate::{
block::{ExplodeArgs, drop_loot},
server::Server,
world::loot::LootContextParameters,
};
@ -74,14 +73,14 @@ impl Explosion {
map
}
pub async fn explode(&self, server: &Server, world: &Arc<World>) {
pub async fn explode(&self, world: &Arc<World>) {
let blocks = self.get_blocks_to_destroy(world).await;
// TODO: Entity damage, fire
for (pos, (block, state)) in blocks {
if state.is_air() {
continue;
}
let pumpkin_block = server.block_registry.get_pumpkin_block(block);
let pumpkin_block = world.block_registry.get_pumpkin_block(block);
world.set_block_state(&pos, 0, BlockFlags::NOTIFY_ALL).await;

View File

@ -1726,9 +1726,9 @@ impl World {
player.set_health(20.0).await;
}
pub async fn explode(self: &Arc<Self>, server: &Server, position: Vector3<f64>, power: f32) {
pub async fn explode(self: &Arc<Self>, position: Vector3<f64>, power: f32) {
let explosion = Explosion::new(power, position);
explosion.explode(server, self).await;
explosion.explode(self).await;
let particle = if power < 2.0 {
Particle::Explosion
} else {