Repository navigation
Feat: spawn egg #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JunkyDeveloper
merged 40 commits into
Steel-Foundation:master
from
chocodev11:feat/spawn-egg
Sep 8, 2026
Merged
Feat: spawn egg #412
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
65646c8
spawn egg
chocodev11 347507b
consume and formating, im stupid yk?
chocodev11 6fa3cfc
remove hardcode
chocodev11 91b16d8
todo
chocodev11 8da3a4c
Merge branch 'master' into feat/spawn-egg
chocodev11 6e320b4
Merge remote-tracking branch 'upstream' into feat/spawn-egg
chocodev11 8668091
Merge branch 'feat/spawn-egg' of https://github.com/chocodev11/SteelM…
chocodev11 7f1bdf2
fix lint
chocodev11 50a0d20
formating
chocodev11 c3fe9e9
stat
chocodev11 bb7f148
format
chocodev11 24bb313
function for coco
chocodev11 531bd79
a greatful warn
chocodev11 22d6a86
sync consume
chocodev11 c1b4fb7
name tag order
chocodev11 813b161
Merge remote-tracking branch 'upstream' into feat/spawn-egg
chocodev11 cdcfc17
custom name spawn egg
chocodev11 82deb90
Merge branch 'master' into feat/spawn-egg
chocodev11 745a584
formating
chocodev11 73209bf
magic cucumber
chocodev11 ec352c6
stat and item check
chocodev11 0523f94
off spring spawn
chocodev11 29a55d0
position helper
chocodev11 c04aaff
item stack component and may_use_item_at
chocodev11 e43db9d
fix non-op exploit
chocodev11 90ae67a
Merge branch 'master' into feat/spawn-egg
coco875 381958d
Merge remote-tracking branch 'upstream' into feat/spawn-egg
chocodev11 e04e1f5
Merge branch 'feat/spawn-egg' of https://github.com/chocodev11/SteelM…
chocodev11 45edfaa
add more mob component
chocodev11 83ad3b1
Merge remote-tracking branch 'upstream' into feat/spawn-egg
chocodev11 7d6e1f7
im stupid
chocodev11 8577d03
Merge branch 'master' into feat/spawn-egg
JunkyDeveloper f8de499
move work to spawner PR
JunkyDeveloper 89c801c
Merge remote-tracking branch 'chocodev11/feat/spawn-egg' into pr/412
JunkyDeveloper ac10bf7
fix merge conflicts + use consume instead of shrink
JunkyDeveloper 6b3e5df
fmt
JunkyDeveloper 698b34b
will not compile only to pitch idea
JunkyDeveloper 00a6812
fix only_op_can_set_nbt
JunkyDeveloper a93b3d8
fixed
JunkyDeveloper b618883
fmt
JunkyDeveloper File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| //! Data-driven vanilla spawn-egg behavior. | ||
|
|
||
| use std::sync::Arc; | ||
|
|
||
| use steel_macros::item_behavior; | ||
| use steel_registry::blocks::block_state_ext::BlockStateExt as _; | ||
| use steel_registry::data_components::components::EntityData; | ||
| use steel_registry::data_components::vanilla_components::ENTITY_DATA; | ||
| use steel_registry::entity_type::EntityTypeRef; | ||
| use steel_registry::item_stack::ItemStack; | ||
| use steel_registry::stat::vanilla_stat_types; | ||
| use steel_registry::{vanilla_blocks, vanilla_game_events}; | ||
| use steel_utils::BlockPos; | ||
|
|
||
| use crate::behavior::item_utils::get_player_pov_hit_result; | ||
| use crate::behavior::{ | ||
| BLOCK_BEHAVIORS, BlockCollisionContext, BlockStateBehaviorExt as _, ITEM_BEHAVIORS, | ||
| InteractionResult, InventoryAccess, ItemBehavior, UseItemContext, UseOnContext, | ||
| }; | ||
| use crate::entity::{ | ||
| AgeableMob, EntitySpawnPlacement, EntitySpawnReason, EntitySpawnRequest, Mob, SharedEntity, | ||
| add_spawned_entity, apply_implicit_item_stack_components, create_entity_instance, spawn_entity, | ||
| }; | ||
| use crate::player::Player; | ||
| use crate::world::ClipFluid; | ||
| use crate::world::World; | ||
| use crate::world::game_event::GameEventContext; | ||
|
|
||
| /// Behavior for every data-driven `SpawnEggItem` registry entry. | ||
| #[item_behavior(class = "SpawnEggItem")] | ||
| pub struct SpawnEggItem; | ||
|
|
||
| impl SpawnEggItem { | ||
| fn entity_type(stack: &ItemStack) -> Option<EntityTypeRef> { | ||
| stack.get(ENTITY_DATA).map(EntityData::entity_type) | ||
| } | ||
|
|
||
| fn spawn_mob( | ||
| world: &Arc<World>, | ||
| player: &Player, | ||
| inventory: &InventoryAccess, | ||
| stack: &ItemStack, | ||
| spawn_pos: BlockPos, | ||
| try_move_down: bool, | ||
| moved_up: bool, | ||
| ) -> InteractionResult { | ||
| let Some(entity_type) = Self::entity_type(stack) else { | ||
| return InteractionResult::Fail; | ||
| }; | ||
|
|
||
| let request = EntitySpawnRequest { | ||
| entity_type, | ||
| placement: EntitySpawnPlacement::Block { | ||
| pos: spawn_pos, | ||
| try_move_down, | ||
| moved_up, | ||
| }, | ||
| reason: EntitySpawnReason::SpawnItemUse, | ||
| finalize_spawn: true, | ||
| play_ambient_sound: true, | ||
| item_stack: Some(stack), | ||
| user_is_operator: player.is_operator(), | ||
| }; | ||
| if spawn_entity(world, request).is_err() { | ||
| return InteractionResult::Fail; | ||
| } | ||
|
|
||
| inventory.with_item(|item| item.consume_one(player.has_infinite_materials())); | ||
| world.game_event( | ||
| &vanilla_game_events::ENTITY_PLACE, | ||
| spawn_pos, | ||
| &GameEventContext::new(Some(player), None), | ||
| ); | ||
| InteractionResult::Success | ||
| } | ||
|
|
||
| /// Runs the interaction before normal mob handling. | ||
| pub(crate) fn interact_with_mob<M: Mob + ?Sized>( | ||
| stack: &mut ItemStack, | ||
| player: &Player, | ||
| parent: &M, | ||
| ) -> InteractionResult { | ||
| if ITEM_BEHAVIORS | ||
| .get_behavior(stack.item()) | ||
| .as_spawn_egg() | ||
| .is_none() | ||
| { | ||
| return InteractionResult::Pass; | ||
| } | ||
| if Self::spawn_offspring(stack, parent).is_none() { | ||
| return InteractionResult::Pass; | ||
| } | ||
| stack.consume_one(player.has_infinite_materials()); | ||
| InteractionResult::SuccessServer | ||
| } | ||
|
|
||
| fn spawn_offspring<M: Mob + ?Sized>(stack: &ItemStack, parent: &M) -> Option<SharedEntity> { | ||
| let entity_type = Self::entity_type(stack)?; | ||
| if entity_type != parent.entity_type() { | ||
| return None; | ||
| } | ||
|
|
||
| let world = parent.level()?; | ||
| let offspring = if let Some(animal) = parent.as_animal() { | ||
| animal.get_breed_offspring(&world, animal)? | ||
| } else { | ||
| match create_entity_instance(&world, entity_type, parent.position()) { | ||
| Ok(offspring) => offspring, | ||
| Err(error) => { | ||
| log::warn!( | ||
| "Failed to create spawn-egg offspring {} at {:?}: {error:?}", | ||
| entity_type.key, | ||
| parent.position() | ||
| ); | ||
| return None; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| let ageable = offspring.as_ageable_mob()?; | ||
| ageable.set_baby(true); | ||
| if !AgeableMob::is_baby(ageable) { | ||
| return None; | ||
| } | ||
|
|
||
| offspring.base().set_position_local(parent.position()); | ||
| offspring.set_rotation((0.0, 0.0)); | ||
| offspring.set_old_position_to_current(); | ||
| offspring.base().set_old_rotation_to_current(); | ||
| apply_implicit_item_stack_components(&offspring, stack); | ||
| add_spawned_entity(&world, offspring.clone()).ok()?; | ||
| Some(offspring) | ||
| } | ||
| } | ||
|
chocodev11 marked this conversation as resolved.
Resolved
|
||
|
|
||
| impl ItemBehavior for SpawnEggItem { | ||
| fn as_spawn_egg(&self) -> Option<&SpawnEggItem> { | ||
| Some(self) | ||
| } | ||
|
|
||
| fn use_on(&self, context: &mut UseOnContext) -> InteractionResult { | ||
| let stack = context.inv.with_item(|item| item.clone()); | ||
| if Self::entity_type(&stack).is_none() { | ||
| return InteractionResult::Fail; | ||
| } | ||
|
|
||
| let clicked_pos = context.hit_result.block_pos; | ||
| let clicked_state = context.world.get_block_state(clicked_pos); | ||
| if clicked_state.get_block() == &vanilla_blocks::SPAWNER { | ||
|
JunkyDeveloper marked this conversation as resolved.
Resolved
|
||
| // TODO: Use spawn eggs on a spawner block and also check if the condition is correct | ||
| // Spawner block-entity mutation is a separate foundation; do not | ||
| // incorrectly create a mob beside a spawner in its place. | ||
| return InteractionResult::Fail; | ||
| } | ||
|
coco875 marked this conversation as resolved.
Resolved
|
||
|
|
||
| let clicked_face = context.hit_result.direction; | ||
| let shape = BLOCK_BEHAVIORS | ||
| .get_behavior(clicked_state.get_block()) | ||
| .get_collision_shape( | ||
| clicked_state, | ||
| context.world.as_ref(), | ||
| clicked_pos, | ||
| BlockCollisionContext::empty(), | ||
| ); | ||
| let spawn_pos = if shape.is_empty() { | ||
| clicked_pos | ||
| } else { | ||
| clicked_face.relative(clicked_pos) | ||
| }; | ||
|
|
||
| let result = Self::spawn_mob( | ||
| context.world, | ||
| context.player, | ||
| &context.inv, | ||
| &stack, | ||
| spawn_pos, | ||
| true, | ||
| spawn_pos != clicked_pos && clicked_face == steel_utils::Direction::Up, | ||
| ); | ||
| if result == InteractionResult::Success { | ||
| context | ||
| .player | ||
| .award_stat(&vanilla_stat_types::ITEM_USED, stack.item()); | ||
| } | ||
| result | ||
| } | ||
|
|
||
| fn use_item(&self, context: &mut UseItemContext) -> InteractionResult { | ||
| let hit_result = | ||
| get_player_pov_hit_result(context.world, context.player, ClipFluid::SourceOnly); | ||
| if hit_result.miss { | ||
| return InteractionResult::Pass; | ||
| } | ||
|
|
||
| let stack = context.inv.with_item(|item| item.clone()); | ||
| if Self::entity_type(&stack).is_none() { | ||
| return InteractionResult::Fail; | ||
| } | ||
|
|
||
| let pos = hit_result.block_pos; | ||
| let state = context.world.get_block_state(pos); | ||
| if !state.is_liquid_block() { | ||
| return InteractionResult::Pass; | ||
| } | ||
| if !context.world.may_interact(context.player, pos) | ||
| || !context | ||
| .player | ||
| .may_use_item_at(pos, hit_result.direction, &stack) | ||
| { | ||
| return InteractionResult::Fail; | ||
| } | ||
|
|
||
| let result = Self::spawn_mob( | ||
| context.world, | ||
| context.player, | ||
| &context.inv, | ||
| &stack, | ||
| pos, | ||
| false, | ||
| false, | ||
| ); | ||
| if result == InteractionResult::Success { | ||
| context | ||
| .player | ||
| .award_stat(&vanilla_stat_types::ITEM_USED, stack.item()); | ||
| } | ||
| result | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.