Skip to content
76 changes: 76 additions & 0 deletions steel-core/src/behavior/items/egg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Egg item behavior (`EggItem`).
//!
//! Throwing an egg spawns a [`ThrownEggEntity`] from the player's eye, shot
//! along their look direction, and consumes one egg (creative-mode count
//! restoration is handled by the caller). Mirrors vanilla `EggItem.use`.

use std::sync::Arc;

use steel_macros::item_behavior;
use steel_protocol::packets::game::SoundSource;
use steel_registry::stat::vanilla_stat_types;
use steel_registry::{sound_events, vanilla_entities};

use crate::behavior::context::{InteractionResult, UseItemContext};
use crate::behavior::item::ItemBehavior;
use crate::entity::entities::ThrownEggEntity;
use crate::entity::{Entity, next_entity_id, spawn_throwable_item_projectile};

/// Vanilla `EggItem.PROJECTILE_SHOOT_POWER`.
const SHOOT_POWER: f32 = 1.5;
/// Vanilla `EggItem.use` throw sound volume.
const THROW_SOUND_VOLUME: f32 = 0.5;
/// Vanilla `EggItem.use` throw pitch jitter scale: `0.4 / (random * 0.4 + 0.8)`.
const THROW_PITCH_JITTER_SCALE: f32 = 0.4;
/// Vanilla `EggItem.use` throw pitch jitter base.
const THROW_PITCH_JITTER_BASE: f32 = 0.8;
/// Vanilla `EggItem.use` throw uncertainty (`spawnProjectileFromRotation`).
const THROW_UNCERTAINTY: f32 = 1.0;

/// Behavior for the egg item.
#[item_behavior(class = "EggItem")]
pub struct EggItem;

impl ItemBehavior for EggItem {
fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
let player = context.player;
let world = context.world;

let pitch = THROW_PITCH_JITTER_SCALE
/ (rand::random::<f32>() * THROW_PITCH_JITTER_SCALE + THROW_PITCH_JITTER_BASE);
world.play_sound_at(
&sound_events::ENTITY_EGG_THROW,
SoundSource::Players,
player.position(),
THROW_SOUND_VOLUME,
pitch,
None,
);

let mut thrown_item = context.inv.with_item(|item| item.clone());
let Some(_egg) = spawn_throwable_item_projectile(
world,
player,
&mut thrown_item,
SHOOT_POWER,
THROW_UNCERTAINTY,
|spawn_pos| {
ThrownEggEntity::new(
&vanilla_entities::EGG,
next_entity_id(),
spawn_pos,
Arc::downgrade(world),
)
},
) else {
return InteractionResult::Fail;
};

player.award_stat(&vanilla_stat_types::ITEM_USED, thrown_item.item);
if !player.has_infinite_materials() {
context.inv.with_item(|item| item.shrink(1));
}

InteractionResult::Success
}
}
58 changes: 24 additions & 34 deletions steel-core/src/behavior/items/ender_pearl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use std::sync::Arc;

use glam::DVec3;
use steel_macros::item_behavior;
use steel_protocol::packets::game::SoundSource;
use steel_registry::stat::vanilla_stat_types;
Expand All @@ -15,10 +14,12 @@ use steel_registry::{sound_events, vanilla_entities};
use crate::behavior::context::{InteractionResult, UseItemContext};
use crate::behavior::item::ItemBehavior;
use crate::entity::entities::EnderPearlEntity;
use crate::entity::{Entity, Projectile, SharedEntity, ThrowableItemProjectile, next_entity_id};
use crate::entity::{Entity, next_entity_id, spawn_throwable_item_projectile};

/// Vanilla `EnderpearlItem.PROJECTILE_SHOOT_POWER`.
const SHOOT_POWER: f32 = 1.5;
/// Vanilla `EnderpearlItem.use` throw uncertainty (`spawnProjectileFromRotation`).
const THROW_UNCERTAINTY: f32 = 1.0;

/// Behavior for the ender pearl item.
#[item_behavior(class = "EnderpearlItem")]
Expand All @@ -39,41 +40,30 @@ impl ItemBehavior for EnderPearlItem {
None,
);

let thrown_item = context.inv.with_item(|item| item.clone());

// Vanilla `ThrowableItemProjectile` spawns at the shooter's eye minus 0.1.
let player_pos = player.position();
let spawn_pos = DVec3::new(player_pos.x, player.get_eye_y() - 0.1, player_pos.z);

let pearl = Arc::new(EnderPearlEntity::new(
&vanilla_entities::ENDER_PEARL,
next_entity_id(),
spawn_pos,
Arc::downgrade(world),
));
if let Some(owner) = world.players.get_by_uuid(&player.gameprofile.id) {
let owner: SharedEntity = owner;
pearl.set_owner_entity(Some(&owner));
} else {
pearl.set_owner_uuid(Some(player.gameprofile.id));
}
pearl.set_item_clamped(thrown_item);

let (yaw, player_pitch) = player.rotation();
pearl.shoot_from_rotation(player, player_pitch, yaw, 0.0, SHOOT_POWER, 1.0);

let entity: SharedEntity = pearl;
if let Err(error) = world.try_add_entity(entity.clone()) {
log::debug!("failed to spawn ender pearl: {error}");
let mut thrown_item = context.inv.with_item(|item| item.clone());
let Some(entity) = spawn_throwable_item_projectile(
world,
player,
&mut thrown_item,
SHOOT_POWER,
THROW_UNCERTAINTY,
|spawn_pos| {
EnderPearlEntity::new(
&vanilla_entities::ENDER_PEARL,
next_entity_id(),
spawn_pos,
Arc::downgrade(world),
)
},
) else {
return InteractionResult::Fail;
}
};
player.register_ender_pearl(&entity);

context.inv.with_item(|itemstack| {
let item = itemstack.item();
itemstack.shrink(1);
player.award_stat(&vanilla_stat_types::ITEM_USED, item);
});
player.award_stat(&vanilla_stat_types::ITEM_USED, thrown_item.item);
if !player.has_infinite_materials() {
context.inv.with_item(|item| item.shrink(1));
}

InteractionResult::Success
}
Expand Down
2 changes: 2 additions & 0 deletions steel-core/src/behavior/items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod copper_chest_events;
mod default;
mod dye;
mod dynamic_name;
mod egg;
mod ender_eye;
mod ender_pearl;
mod firework_rocket;
Expand Down Expand Up @@ -48,6 +49,7 @@ pub use bucket::BucketItem;
pub use compass::CompassItem;
pub use default::DefaultItemBehavior;
pub use dye::DyeItem;
pub use egg::EggItem;
pub use ender_eye::EnderEyeItem;
pub use ender_pearl::EnderPearlItem;
pub use firework_rocket::FireworkRocketItem;
Expand Down
Loading
Loading