Skip to main content

steel_core/behavior/items/
firework_rocket.rs

1//! Firework rocket item behavior (`FireworkRocketItem`).
2//!
3//! Rockets can be placed against a block face or used while fall flying to
4//! attach a boosting rocket to the player.
5
6use std::sync::Arc;
7
8use glam::DVec3;
9use steel_macros::item_behavior;
10use steel_protocol::packets::game::SoundSource;
11use steel_registry::{sound_events, vanilla_entities};
12use steel_utils::Direction;
13
14use crate::behavior::context::{InteractionResult, UseItemContext, UseOnContext};
15use crate::behavior::item::ItemBehavior;
16use crate::enchantment_helper;
17use crate::entity::entities::FireworkRocketEntity;
18use crate::entity::{Entity, Projectile, SharedEntity, next_entity_id};
19use crate::world::World;
20
21const ROCKET_PLACEMENT_OFFSET: f64 = 0.15;
22
23/// Behavior for the firework rocket item.
24#[item_behavior(class = "FireworkRocketItem")]
25pub struct FireworkRocketItem;
26
27impl FireworkRocketItem {
28    fn add_rocket(world: &Arc<World>, rocket: FireworkRocketEntity) -> SharedEntity {
29        let entity: SharedEntity = Arc::new(rocket);
30        if let Err(error) = world.try_add_entity(Arc::clone(&entity)) {
31            log::debug!("failed to spawn firework rocket: {error}");
32        }
33        entity
34    }
35
36    fn placement_position(click_location: DVec3, direction: Direction) -> DVec3 {
37        let (step_x, step_y, step_z) = direction.offset();
38        click_location
39            + DVec3::new(
40                f64::from(step_x) * ROCKET_PLACEMENT_OFFSET,
41                f64::from(step_y) * ROCKET_PLACEMENT_OFFSET,
42                f64::from(step_z) * ROCKET_PLACEMENT_OFFSET,
43            )
44    }
45}
46
47impl ItemBehavior for FireworkRocketItem {
48    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
49        if context.player.is_fall_flying() {
50            return InteractionResult::Pass;
51        }
52
53        let source_item = context.inv.with_item(|item| item.clone());
54        let position =
55            Self::placement_position(context.hit_result.location, context.hit_result.direction);
56        let rocket = FireworkRocketEntity::launched(
57            &vanilla_entities::FIREWORK_ROCKET,
58            next_entity_id(),
59            position,
60            Arc::downgrade(context.world),
61            source_item,
62        );
63        rocket.set_owner_uuid(Some(context.player.uuid()));
64        let rocket = Self::add_rocket(context.world, rocket);
65        context.inv.with_item(|item| {
66            enchantment_helper::on_projectile_spawned(
67                context.world,
68                item,
69                rocket.as_ref(),
70                Some(context.player),
71            );
72            item.shrink(1);
73        });
74
75        InteractionResult::Success
76    }
77
78    fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
79        if !context.player.is_fall_flying() {
80            return InteractionResult::Pass;
81        }
82
83        if context.player.drop_all_leash_connections(None) {
84            context.world.play_sound_at(
85                &sound_events::ITEM_LEAD_BREAK,
86                SoundSource::Neutral,
87                context.player.position(),
88                1.0,
89                1.0,
90                None,
91            );
92        }
93
94        let source_item = context.inv.with_item(|item| item.clone());
95        let rocket = FireworkRocketEntity::attached_to_living(
96            &vanilla_entities::FIREWORK_ROCKET,
97            next_entity_id(),
98            Arc::downgrade(context.world),
99            source_item,
100            context.player,
101        );
102        let rocket = Self::add_rocket(context.world, rocket);
103        context.inv.with_item(|item| {
104            enchantment_helper::on_projectile_spawned(
105                context.world,
106                item,
107                rocket.as_ref(),
108                Some(context.player),
109            );
110            item.shrink(1);
111        });
112        // TODO: Award `Stats.ITEM_USED` once Steel has a statistics foundation.
113
114        InteractionResult::Success
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn placement_position_offsets_outside_clicked_face() {
124        let click = DVec3::new(1.25, 2.5, 3.75);
125
126        assert_eq!(
127            FireworkRocketItem::placement_position(click, Direction::Up),
128            DVec3::new(1.25, 2.65, 3.75)
129        );
130        assert_eq!(
131            FireworkRocketItem::placement_position(click, Direction::West),
132            DVec3::new(1.1, 2.5, 3.75)
133        );
134    }
135}