steel_core/behavior/items/
spawn_egg.rs1use std::sync::Arc;
4
5use steel_macros::item_behavior;
6use steel_registry::blocks::block_state_ext::BlockStateExt as _;
7use steel_registry::data_components::components::EntityData;
8use steel_registry::data_components::vanilla_components::ENTITY_DATA;
9use steel_registry::entity_type::EntityTypeRef;
10use steel_registry::item_stack::ItemStack;
11use steel_registry::stat::vanilla_stat_types;
12use steel_registry::{vanilla_blocks, vanilla_game_events};
13use steel_utils::BlockPos;
14
15use crate::behavior::item_utils::get_player_pov_hit_result;
16use crate::behavior::{
17 BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, ITEM_BEHAVIORS,
18 InteractionResult, InventoryAccess, ItemBehavior, UseItemContext, UseOnContext,
19};
20use crate::entity::{
21 AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity,
22 add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity,
23};
24use crate::player::Player;
25use crate::world::ClipFluid;
26use crate::world::World;
27use crate::world::game_event::GameEventContext;
28
29#[item_behavior(class = "SpawnEggItem")]
31pub struct SpawnEggItem;
32
33impl SpawnEggItem {
34 fn entity_type(stack: &ItemStack) -> Option<EntityTypeRef> {
35 stack.get(ENTITY_DATA).map(EntityData::entity_type)
36 }
37
38 fn spawn_mob(
39 world: &Arc<World>,
40 player: &Player,
41 inventory: &InventoryAccess,
42 stack: &ItemStack,
43 spawn_pos: BlockPos,
44 try_move_down: bool,
45 moved_up: bool,
46 ) -> InteractionResult {
47 let Some(entity_type) = Self::entity_type(stack) else {
48 return InteractionResult::Fail;
49 };
50
51 let request = EntitySpawnRequest {
52 entity_type,
53 placement: EntitySpawnPlacement::Block {
54 pos: spawn_pos,
55 try_move_down,
56 moved_up,
57 },
58 reason: EntitySpawnReason::SpawnItemUse,
59 finalize_spawn: true,
60 play_ambient_sound: true,
61 item_stack: Some(stack),
62 user_is_operator: player.is_operator(),
63 };
64 if spawn_entity(world, request).is_err() {
65 return InteractionResult::Fail;
66 }
67
68 inventory.with_item(|item| item.consume_one(player.has_infinite_materials()));
69 world.game_event(
70 &vanilla_game_events::ENTITY_PLACE,
71 spawn_pos,
72 &GameEventContext::new(Some(player), None),
73 );
74 InteractionResult::Success
75 }
76
77 pub(crate) fn interact_with_mob<M: Mob + ?Sized>(
79 stack: &mut ItemStack,
80 player: &Player,
81 parent: &M,
82 ) -> InteractionResult {
83 if ITEM_BEHAVIORS
84 .get_behavior(stack.item())
85 .as_spawn_egg()
86 .is_none()
87 {
88 return InteractionResult::Pass;
89 }
90 if Self::spawn_offspring(stack, parent).is_none() {
91 return InteractionResult::Pass;
92 }
93 stack.consume_one(player.has_infinite_materials());
94 InteractionResult::SuccessServer
95 }
96
97 fn spawn_offspring<M: Mob + ?Sized>(stack: &ItemStack, parent: &M) -> Option<SharedEntity> {
98 let entity_type = Self::entity_type(stack)?;
99 if entity_type != parent.entity_type() {
100 return None;
101 }
102
103 let world = parent.level()?;
104 let offspring = if let Some(ageable) = parent.as_ageable_mob() {
105 ageable.get_breed_offspring(&world, ageable)?
106 } else {
107 match create_entity_instance(&world, entity_type, parent.position()) {
108 Ok(offspring) => offspring,
109 Err(error) => {
110 log::warn!(
111 "Failed to create spawn-egg offspring {} at {:?}: {error:?}",
112 entity_type.key,
113 parent.position()
114 );
115 return None;
116 }
117 }
118 };
119
120 let ageable = offspring.as_ageable_mob()?;
121 ageable.set_baby(true);
122 if !AgeableMob::is_baby(ageable) {
123 return None;
124 }
125
126 offspring.base().set_position_local(parent.position());
127 offspring.set_rotation((0.0, 0.0));
128 offspring.set_old_position_to_current();
129 offspring.base().set_old_rotation_to_current();
130 apply_implicit_item_stack_components(&offspring, stack);
131 add_spawned_entity(&world, offspring.clone()).ok()?;
132 Some(offspring)
133 }
134}
135
136impl ItemBehavior for SpawnEggItem {
137 fn as_spawn_egg(&self) -> Option<&SpawnEggItem> {
138 Some(self)
139 }
140
141 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
142 let stack = context.inv.with_item(|item| item.clone());
143 if Self::entity_type(&stack).is_none() {
144 return InteractionResult::Fail;
145 }
146
147 let clicked_pos = context.hit_result.block_pos;
148 let clicked_state = context.world.get_block_state(clicked_pos);
149 if clicked_state.get_block() == &vanilla_blocks::SPAWNER {
150 return InteractionResult::Fail;
154 }
155
156 let clicked_face = context.hit_result.direction;
157 let shape = BLOCK_BEHAVIORS
158 .get_behavior(clicked_state.get_block())
159 .get_collision_shape(
160 clicked_state,
161 context.world.as_ref(),
162 clicked_pos,
163 BlockCollisionContext::empty(),
164 );
165 let spawn_pos = if shape.is_empty() {
166 clicked_pos
167 } else {
168 clicked_face.relative(clicked_pos)
169 };
170
171 let result = Self::spawn_mob(
172 context.world,
173 context.player,
174 &context.inv,
175 &stack,
176 spawn_pos,
177 true,
178 spawn_pos != clicked_pos && clicked_face == steel_utils::Direction::Up,
179 );
180 if result == InteractionResult::Success {
181 context
182 .player
183 .award_stat(&vanilla_stat_types::ITEM_USED, stack.item());
184 }
185 result
186 }
187
188 fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
189 let hit_result =
190 get_player_pov_hit_result(context.world, context.player, ClipFluid::SourceOnly);
191 if hit_result.miss {
192 return InteractionResult::Pass;
193 }
194
195 let stack = context.inv.with_item(|item| item.clone());
196 if Self::entity_type(&stack).is_none() {
197 return InteractionResult::Fail;
198 }
199
200 let pos = hit_result.block_pos;
201 let state = context.world.get_block_state(pos);
202 if !state.is_liquid_block() {
203 return InteractionResult::Pass;
204 }
205 if !context.world.may_interact(context.player, pos)
206 || !context
207 .player
208 .may_use_item_at(pos, hit_result.direction, &stack)
209 {
210 return InteractionResult::Fail;
211 }
212
213 let result = Self::spawn_mob(
214 context.world,
215 context.player,
216 &context.inv,
217 &stack,
218 pos,
219 false,
220 false,
221 );
222 if result == InteractionResult::Success {
223 context
224 .player
225 .award_stat(&vanilla_stat_types::ITEM_USED, stack.item());
226 }
227 result
228 }
229}