Skip to main content

steel_core/behavior/items/
ender_pearl.rs

1//! Ender pearl item behavior (`EnderpearlItem`).
2//!
3//! Throwing an ender pearl spawns a [`EnderPearlEntity`] from the player's eye,
4//! shot along their look direction, and consumes one pearl (creative-mode count
5//! restoration is handled by the caller). Mirrors vanilla `EnderpearlItem.use`.
6
7use std::sync::Arc;
8
9use glam::DVec3;
10use steel_macros::item_behavior;
11use steel_protocol::packets::game::SoundSource;
12use steel_registry::{sound_events, vanilla_entities};
13
14use crate::behavior::context::{InteractionResult, UseItemContext};
15use crate::behavior::item::ItemBehavior;
16use crate::entity::entities::EnderPearlEntity;
17use crate::entity::{Entity, Projectile, SharedEntity, ThrowableItemProjectile, next_entity_id};
18
19/// Vanilla `EnderpearlItem.PROJECTILE_SHOOT_POWER`.
20const SHOOT_POWER: f32 = 1.5;
21
22/// Behavior for the ender pearl item.
23#[item_behavior(class = "EnderpearlItem")]
24pub struct EnderPearlItem;
25
26impl ItemBehavior for EnderPearlItem {
27    fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
28        let player = context.player;
29        let world = context.world;
30
31        let pitch = 0.4 / (rand::random::<f32>() * 0.4 + 0.8);
32        world.play_sound_at(
33            &sound_events::ENTITY_ENDER_PEARL_THROW,
34            SoundSource::Neutral,
35            player.position(),
36            0.5,
37            pitch,
38            None,
39        );
40
41        let thrown_item = context.inv.with_item(|item| item.clone());
42
43        // Vanilla `ThrowableItemProjectile` spawns at the shooter's eye minus 0.1.
44        let player_pos = player.position();
45        let spawn_pos = DVec3::new(player_pos.x, player.get_eye_y() - 0.1, player_pos.z);
46
47        let pearl = Arc::new(EnderPearlEntity::new(
48            &vanilla_entities::ENDER_PEARL,
49            next_entity_id(),
50            spawn_pos,
51            Arc::downgrade(world),
52        ));
53        if let Some(owner) = world.players.get_by_uuid(&player.gameprofile.id) {
54            let owner: SharedEntity = owner;
55            pearl.set_owner_entity(Some(&owner));
56        } else {
57            pearl.set_owner_uuid(Some(player.gameprofile.id));
58        }
59        pearl.set_item_clamped(thrown_item);
60
61        let (yaw, player_pitch) = player.rotation();
62        pearl.shoot_from_rotation(player, player_pitch, yaw, 0.0, SHOOT_POWER, 1.0);
63
64        let entity: SharedEntity = pearl;
65        if let Err(error) = world.try_add_entity(entity.clone()) {
66            log::debug!("failed to spawn ender pearl: {error}");
67            return InteractionResult::Fail;
68        }
69        player.register_ender_pearl(&entity);
70
71        // TODO: award the ITEM_USED stat once a stats system exists.
72        context.inv.with_item(|item| item.shrink(1));
73
74        InteractionResult::Success
75    }
76}