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