diff --git a/patches/minecraft/net/minecraft/world/level/Level.java.patch b/patches/minecraft/net/minecraft/world/level/Level.java.patch index 9fba42183c..dc5d86d69c 100644 --- a/patches/minecraft/net/minecraft/world/level/Level.java.patch +++ b/patches/minecraft/net/minecraft/world/level/Level.java.patch @@ -17,13 +17,15 @@ private final Thread f_46423_; private final boolean f_46424_; private int f_46425_; -@@ -99,8 +_,12 @@ +@@ -99,8 +_,14 @@ private final WorldBorder f_46447_; private final BiomeManager f_46420_; private final ResourceKey f_46421_; + public boolean restoringBlockSnapshots = false; + public boolean captureBlockSnapshots = false; + public java.util.ArrayList capturedBlockSnapshots = new java.util.ArrayList<>(); ++ private final java.util.ArrayList freshBlockEntities = new java.util.ArrayList<>(); ++ private final java.util.ArrayList pendingFreshBlockEntities = new java.util.ArrayList<>(); protected Level(WritableLevelData p_46450_, ResourceKey p_46451_, final DimensionType p_46452_, Supplier p_46453_, boolean p_46454_, boolean p_46455_, long p_46456_) { + super(Level.class); @@ -119,12 +121,30 @@ } }); CrashReportCategory.m_178950_(crashreportcategory, this, p_46587_, blockstate); -@@ -404,8 +_,9 @@ +@@ -401,11 +_,28 @@ + (this.f_151504_ ? this.f_151503_ : this.f_151512_).add(p_151526_); + } + ++ public void addFreshBlockEntities(java.util.Collection beList) { ++ if (this.f_151504_) { ++ this.pendingFreshBlockEntities.addAll(beList); ++ } else { ++ this.freshBlockEntities.addAll(beList); ++ } ++ } ++ protected void m_46463_() { ProfilerFiller profilerfiller = this.m_46473_(); profilerfiller.m_6180_("blockEntities"); -- this.f_151504_ = true; -+ this.f_151504_ = true;// Forge: Move above remove to prevent CMEs ++ if (!this.pendingFreshBlockEntities.isEmpty()) { ++ this.freshBlockEntities.addAll(this.pendingFreshBlockEntities); ++ this.pendingFreshBlockEntities.clear(); ++ } + this.f_151504_ = true; ++ if (!this.freshBlockEntities.isEmpty()) { ++ this.freshBlockEntities.forEach(BlockEntity::onLoad); ++ this.freshBlockEntities.clear(); ++ } if (!this.f_151503_.isEmpty()) { + this.f_46434_.forEach(e -> e.onChunkUnloaded()); this.f_151512_.addAll(this.f_151503_); diff --git a/patches/minecraft/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patches/minecraft/net/minecraft/world/level/chunk/LevelChunk.java.patch index 09f8920546..6bea36c49c 100644 --- a/patches/minecraft/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/patches/minecraft/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -63,6 +63,14 @@ } return blockentity; +@@ -379,6 +_,7 @@ + if (this.m_156370_()) { + this.m_156404_(p_156391_); + this.m_156406_(p_156391_); ++ p_156391_.onLoad(); + } + + } @@ -418,9 +_,14 @@ public CompoundTag m_8051_(BlockPos p_62932_) { BlockEntity blockentity = this.m_7702_(p_62932_); @@ -87,6 +95,14 @@ }); } +@@ -763,6 +_,7 @@ + } + + public void m_156369_() { ++ this.f_62776_.addFreshBlockEntities(this.f_62779_.values()); + this.f_62779_.values().forEach((p_156409_) -> { + this.m_156404_(p_156409_); + this.m_156406_(p_156409_); @@ -826,6 +_,7 @@ if (LevelChunk.this.m_156410_(blockpos)) { try { diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockEntity.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockEntity.java index 9c1a9e38b4..6454b10bb2 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockEntity.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockEntity.java @@ -26,6 +26,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.Blocks; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.phys.AABB; import net.minecraft.core.BlockPos; import net.minecraft.world.phys.shapes.VoxelShape; @@ -87,7 +88,8 @@ public interface IForgeBlockEntity extends ICapabilitySerializable default void onChunkUnloaded(){} /** - * Called when this is first added to the world (by {@link World#addTileEntity(TileEntity)}). + * Called when this is first added to the world (by {@link LevelChunk#addAndRegisterBlockEntity(BlockEntity)}) + * or right before the first tick when the chunk is generated or loaded from disk. * Override instead of adding {@code if (firstTick)} stuff in update. */ default void onLoad() diff --git a/src/test/java/net/minecraftforge/debug/block/BlockEntityOnLoadTest.java b/src/test/java/net/minecraftforge/debug/block/BlockEntityOnLoadTest.java new file mode 100644 index 0000000000..ea13b08001 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/block/BlockEntityOnLoadTest.java @@ -0,0 +1,109 @@ +package net.minecraftforge.debug.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.*; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.*; +import net.minecraft.world.level.block.state.BlockBehaviour.Properties; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MaterialColor; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fmllegacy.RegistryObject; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; + +@Mod("be_onload_test") +public class BlockEntityOnLoadTest +{ + private static final boolean ENABLED = true; + + private static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, "be_onload_test"); + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, "be_onload_test"); + private static final DeferredRegister> BE_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, "be_onload_test"); + + private static final RegistryObject TEST_BLOCK = BLOCKS.register("be_onload_testblock", () -> new TestBlock(Properties.of(Material.DECORATION, MaterialColor.SAND))); + private static final RegistryObject TEST_BLOCK_ITEM = ITEMS.register("be_onload_testblock", () -> new BlockItem(TEST_BLOCK.get(), new Item.Properties().tab(CreativeModeTab.TAB_DECORATIONS))); + private static final RegistryObject> TEST_BE_TYPE = BE_TYPES.register("be_onload_testbe", () -> BlockEntityType.Builder.of(TestBlockEntity::new, TEST_BLOCK.get()).build(null)); + + public BlockEntityOnLoadTest() + { + if (ENABLED) + { + IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + BLOCKS.register(modBus); + ITEMS.register(modBus); + BE_TYPES.register(modBus); + } + } + + private static class TestBlock extends Block implements EntityBlock + { + public TestBlock(Properties props) { super(props); } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) + { + LOGGER.info("[BE_ONLOAD] Block#use at pos {} for {}", pos, level.getBlockEntity(pos)); + return super.use(state, level, pos, player, hand, hit); + } + + @Override + @Nullable + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) + { + return new TestBlockEntity(pos, state); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) + { + return (beLevel, bePos, beState, be) -> ((TestBlockEntity) be).tick(); + } + } + + private static class TestBlockEntity extends BlockEntity + { + private static final Logger LOGGER = LogManager.getLogger(); + private boolean loaded = false; + + public TestBlockEntity(BlockPos pos, BlockState state) + { + super(TEST_BE_TYPE.get(), pos, state); + } + + @Override + public void onLoad() + { + LOGGER.info("[BE_ONLOAD] BlockEntity#onLoad at pos {} for {}", worldPosition, this); + getLevel().setBlockAndUpdate(worldPosition.above(), Blocks.SAND.defaultBlockState()); + loaded = true; + } + + private boolean first = true; + public void tick() + { + if (first) + { + first = false; + LOGGER.info("[BE_ONLOAD] TestBlockEntity#tick at pos {} for {}", worldPosition, this); + if (!loaded) + { + throw new IllegalStateException(String.format("BlockEntity at %s ticked before onLoad()!", getBlockPos())); + } + } + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 2b20890e77..6a0b43d17b 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -160,5 +160,7 @@ license="LGPL v2.1" modId="gui_layer_test" [[mods]] modId="add_pack_finders_test" +[[mods]] + modId="be_onload_test" -# ADD ABOVE THIS LINE \ No newline at end of file +# ADD ABOVE THIS LINE