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