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