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::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#[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}