steel_core/behavior/items/
firework_rocket.rs1use 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#[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 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}