From 867159643ccca01da5f74cb724c8d250c05dcd92 Mon Sep 17 00:00:00 2001 From: Spectrall <105946758+Spectrall368@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:27:52 +0200 Subject: [PATCH] Add ForgeSpawnEggItem to lazily handle EntityTypes (#10626) --- .../IDispenseItemBehavior.java.patch | 26 +++- .../minecraft/item/SpawnEggItem.java.patch | 26 ++++ .../common/ForgeSpawnEggItem.java | 135 ++++++++++++++++++ .../common/extensions/IForgeEntity.java | 3 +- src/main/resources/forge.sas | 1 + .../debug/item/ForgeSpawnEggItemTest.java | 79 ++++++++++ src/test/resources/META-INF/mods.toml | 2 + .../models/item/test_spawn_egg.json | 3 + 8 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 patches/minecraft/net/minecraft/item/SpawnEggItem.java.patch create mode 100644 src/main/java/net/minecraftforge/common/ForgeSpawnEggItem.java create mode 100644 src/test/java/net/minecraftforge/debug/item/ForgeSpawnEggItemTest.java create mode 100644 src/test/resources/assets/forge_spawnegg_test/models/item/test_spawn_egg.json diff --git a/patches/minecraft/net/minecraft/dispenser/IDispenseItemBehavior.java.patch b/patches/minecraft/net/minecraft/dispenser/IDispenseItemBehavior.java.patch index ca367b12cd..8c8ff2b7bc 100644 --- a/patches/minecraft/net/minecraft/dispenser/IDispenseItemBehavior.java.patch +++ b/patches/minecraft/net/minecraft/dispenser/IDispenseItemBehavior.java.patch @@ -1,6 +1,28 @@ --- a/net/minecraft/dispenser/IDispenseItemBehavior.java +++ b/net/minecraft/dispenser/IDispenseItemBehavior.java -@@ -295,8 +295,9 @@ +@@ -62,6 +62,7 @@ + import net.minecraft.world.World; + + public interface IDispenseItemBehavior { ++ org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger(); + IDispenseItemBehavior field_223216_a_ = (p_210297_0_, p_210297_1_) -> { + return p_210297_1_; + }; +@@ -162,7 +163,12 @@ + public ItemStack func_82487_b(IBlockSource p_82487_1_, ItemStack p_82487_2_) { + Direction direction = p_82487_1_.func_189992_e().func_177229_b(DispenserBlock.field_176441_a); + EntityType entitytype = ((SpawnEggItem)p_82487_2_.func_77973_b()).func_208076_b(p_82487_2_.func_77978_p()); +- entitytype.func_220331_a(p_82487_1_.func_197524_h(), p_82487_2_, (PlayerEntity)null, p_82487_1_.func_180699_d().func_177972_a(direction), SpawnReason.DISPENSER, direction != Direction.UP, false); ++ try { // FORGE: fix potential crash ++ entitytype.func_220331_a(p_82487_1_.func_197524_h(), p_82487_2_, (PlayerEntity) null, p_82487_1_.func_180699_d().func_177972_a(direction), SpawnReason.DISPENSER, direction != Direction.UP, false); ++ } catch (Exception e) { ++ LOGGER.error("Error while dispensing spawn egg from dispenser at {}", p_82487_1_.func_180699_d(), e); ++ return ItemStack.field_190927_a; ++ } + p_82487_2_.func_190918_g(1); + return p_82487_2_; + } +@@ -295,8 +301,9 @@ world.func_175656_a(blockpos, Blocks.field_150480_ab.func_176223_P()); } else if (FlintAndSteelItem.func_219997_a(blockstate)) { world.func_175656_a(blockpos, blockstate.func_206870_a(BlockStateProperties.field_208190_q, Boolean.valueOf(true))); @@ -12,7 +34,7 @@ world.func_217377_a(blockpos, false); } else { this.field_218407_b = false; -@@ -426,15 +427,23 @@ +@@ -426,15 +433,23 @@ } }); DispenserBlock.func_199774_a(Items.field_151097_aZ.func_199767_j(), new OptionalDispenseBehavior() { diff --git a/patches/minecraft/net/minecraft/item/SpawnEggItem.java.patch b/patches/minecraft/net/minecraft/item/SpawnEggItem.java.patch new file mode 100644 index 0000000000..041ac45dfc --- /dev/null +++ b/patches/minecraft/net/minecraft/item/SpawnEggItem.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/item/SpawnEggItem.java ++++ b/net/minecraft/item/SpawnEggItem.java +@@ -35,11 +35,14 @@ + private final int field_195989_d; + private final EntityType field_200890_d; + ++ /** @deprecated Forge: Use {@link net.minecraftforge.common.ForgeSpawnEggItem} instead for suppliers */ ++ @Deprecated + public SpawnEggItem(EntityType p_i48465_1_, int p_i48465_2_, int p_i48465_3_, Item.Properties p_i48465_4_) { + super(p_i48465_4_); + this.field_200890_d = p_i48465_1_; + this.field_195988_c = p_i48465_2_; + this.field_195989_d = p_i48465_3_; ++ if (p_i48465_1_ != null) + field_195987_b.put(p_i48465_1_, this); + } + +@@ -121,6 +124,8 @@ + return p_195983_1_ == 0 ? this.field_195988_c : this.field_195989_d; + } + ++ /** @deprecated Forge: call {@link net.minecraftforge.common.ForgeSpawnEggItem#fromEntityType(EntityType)} instead */ ++ @Deprecated + @Nullable + public static SpawnEggItem func_200889_b(@Nullable EntityType p_200889_0_) { + return field_195987_b.get(p_200889_0_); diff --git a/src/main/java/net/minecraftforge/common/ForgeSpawnEggItem.java b/src/main/java/net/minecraftforge/common/ForgeSpawnEggItem.java new file mode 100644 index 0000000000..cdcbcc4b41 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ForgeSpawnEggItem.java @@ -0,0 +1,135 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common; + +import net.minecraft.block.DispenserBlock; +import net.minecraft.dispenser.DefaultDispenseItemBehavior; +import net.minecraft.dispenser.IBlockSource; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.item.ItemStack; +import net.minecraft.item.SpawnEggItem; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.Direction; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ColorHandlerEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class ForgeSpawnEggItem extends SpawnEggItem +{ + private static final List MOD_EGGS = new ArrayList<>(); + private static final Map, ForgeSpawnEggItem> TYPE_MAP = new IdentityHashMap<>(); + private final Supplier> typeSupplier; + + public ForgeSpawnEggItem(Supplier> type, int backgroundColor, int highlightColor, Properties props) + { + super((EntityType) null, backgroundColor, highlightColor, props); + this.typeSupplier = type; + + MOD_EGGS.add(this); + } + + @Override + public EntityType getType(@Nullable CompoundNBT tag) + { + EntityType type = super.getType(tag); + return type != null ? type : typeSupplier.get(); + } + + @Nullable + protected DefaultDispenseItemBehavior createDispenseBehavior() + { + return DEFAULT_DISPENSE_BEHAVIOR; + } + + @Nullable + public static SpawnEggItem fromEntityType(@Nullable EntityType type) + { + SpawnEggItem ret = TYPE_MAP.get(type); + return ret != null ? ret : SpawnEggItem.getEgg(type); + } + + + + private static final DefaultDispenseItemBehavior DEFAULT_DISPENSE_BEHAVIOR = new DefaultDispenseItemBehavior() + { + @Override + protected ItemStack dispenseStack(IBlockSource source, ItemStack stack) + { + Direction face = source.getBlockState().get(DispenserBlock.FACING); + EntityType type = ((SpawnEggItem) stack.getItem()).getType(stack.getTag()); + + // FORGE: fix potential crash + try + { + type.spawn(source.getWorld(), stack, null, source.getBlockPos().offset(face), SpawnReason.DISPENSER, face != Direction.UP, false); + } + catch (Exception exception) + { + DefaultDispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", source.getBlockPos(), exception); + return ItemStack.EMPTY; + } + + stack.shrink(1); + return stack; + } + }; + + @Mod.EventBusSubscriber(modid = "forge", bus = Mod.EventBusSubscriber.Bus.MOD) + private static class CommonHandler + { + @SubscribeEvent + public static void onCommonSetup(FMLCommonSetupEvent event) + { + MOD_EGGS.forEach(egg -> + { + DefaultDispenseItemBehavior dispenseBehavior = egg.createDispenseBehavior(); + if (dispenseBehavior != null) + { + DispenserBlock.registerDispenseBehavior(egg, dispenseBehavior); + } + + TYPE_MAP.put(egg.typeSupplier.get(), egg); + }); + } + } + + @Mod.EventBusSubscriber(value = Dist.CLIENT, modid = "forge", bus = Mod.EventBusSubscriber.Bus.MOD) + private static class ColorRegisterHandler + { + @SubscribeEvent(priority = EventPriority.HIGHEST) + public static void registerSpawnEggColors(ColorHandlerEvent.Item event) + { + MOD_EGGS.forEach(egg -> + event.getItemColors().register((stack, layer) -> egg.getColor(layer), egg) + ); + } + } +} diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java b/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java index 054e9a7e8d..03e4e1a490 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java @@ -39,6 +39,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; +import net.minecraftforge.common.ForgeSpawnEggItem; import net.minecraftforge.common.capabilities.ICapabilitySerializable; public interface IForgeEntity extends ICapabilitySerializable @@ -115,7 +116,7 @@ public interface IForgeEntity extends ICapabilitySerializable return new ItemStack(Items.END_CRYSTAL); else { - SpawnEggItem egg = SpawnEggItem.getEgg(getEntity().getType()); + SpawnEggItem egg = ForgeSpawnEggItem.fromEntityType(getEntity().getType()); if (egg != null) return new ItemStack(egg); } return ItemStack.EMPTY; diff --git a/src/main/resources/forge.sas b/src/main/resources/forge.sas index 12bacaebbb..dcda08432f 100644 --- a/src/main/resources/forge.sas +++ b/src/main/resources/forge.sas @@ -40,6 +40,7 @@ net/minecraft/block/WoodType func_227046_a_()Ljava/util/stream/Stream; # getValu net/minecraft/block/WoodType func_227048_b_()Ljava/lang/String; # getName net/minecraft/item/DyeColor func_218388_g()I # getTextColor net/minecraft/item/DyeColor func_196058_b(I)Lnet/minecraft/item/DyeColor; # byFireworkColor +net/minecraft/item/SpawnEggItem func_200889_b(Lnet/minecraft/entity/EntityType;)Lnet/minecraft/item/SpawnEggItem; # byId net/minecraft/item/crafting/Ingredient func_193365_a()[Lnet/minecraft/item/ItemStack; # getMatchingStacks net/minecraft/item/crafting/Ingredient func_193369_a([Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/crafting/Ingredient; # fromStacks net/minecraft/item/crafting/IRecipe func_222128_h()Lnet/minecraft/item/ItemStack; # getIcon diff --git a/src/test/java/net/minecraftforge/debug/item/ForgeSpawnEggItemTest.java b/src/test/java/net/minecraftforge/debug/item/ForgeSpawnEggItemTest.java new file mode 100644 index 0000000000..74d623cd6e --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/item/ForgeSpawnEggItemTest.java @@ -0,0 +1,79 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.item; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.EntityRendererManager; +import net.minecraft.client.renderer.entity.PigRenderer; +import net.minecraft.entity.EntityClassification; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.passive.PigEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.ForgeSpawnEggItem; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.RegistryObject; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; + +@Mod(value = ForgeSpawnEggItemTest.MODID) +public class ForgeSpawnEggItemTest +{ + static final String MODID = "forge_spawnegg_test"; + static final boolean ENABLED = true; + + private static final DeferredRegister> ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITIES, MODID); + private static final RegistryObject> ENTITY = ENTITIES.register("test_entity", () -> + EntityType.Builder.create(PigEntity::new, EntityClassification.CREATURE).size(1, 1).build("test_entity") + ); + + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); + private static final RegistryObject EGG = ITEMS.register("test_spawn_egg", () -> + new ForgeSpawnEggItem(ENTITY, 0x0000FF, 0xFF0000, new Item.Properties().group(ItemGroup.MISC)) + ); + + public ForgeSpawnEggItemTest() + { + if (ENABLED) + { + ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); + ENTITIES.register(FMLJavaModLoadingContext.get().getModEventBus()); + + FMLJavaModLoadingContext.get().getModEventBus().register(this); + } + } + + @Mod.EventBusSubscriber(modid = MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) + private static class ClientEvents + { + @SubscribeEvent + public static void onClientSetup(final FMLClientSetupEvent event) + { + if (!ENABLED) { return; } + + EntityRendererManager manager = Minecraft.getInstance().getRenderManager(); + manager.register(ENTITY.get(), new PigRenderer(manager)); + } + } +} \ No newline at end of file diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index c618f0b966..4fc58cbdfb 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -72,3 +72,5 @@ loaderVersion="[28,)" modId="player_name_event_test" [[mods]] modId="custom_elytra_test" +[[mods]] + modId="forge_spawnegg_test" \ No newline at end of file diff --git a/src/test/resources/assets/forge_spawnegg_test/models/item/test_spawn_egg.json b/src/test/resources/assets/forge_spawnegg_test/models/item/test_spawn_egg.json new file mode 100644 index 0000000000..fcdb62dea4 --- /dev/null +++ b/src/test/resources/assets/forge_spawnegg_test/models/item/test_spawn_egg.json @@ -0,0 +1,3 @@ +{ + "parent": "minecraft:item/template_spawn_egg" +} \ No newline at end of file