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