diff --git a/build-data/divinemc.at b/build-data/divinemc.at index 446cfb9..5d53e0e 100644 --- a/build-data/divinemc.at +++ b/build-data/divinemc.at @@ -6,6 +6,7 @@ private-f net.minecraft.world.level.levelgen.NoiseChunk$FlatCache noiseFiller private-f net.minecraft.world.level.levelgen.NoiseChunk$NoiseInterpolator noiseFiller private-f net.minecraft.world.level.levelgen.RandomState router private-f net.minecraft.world.level.levelgen.RandomState sampler +public net.minecraft.core.NonNullList list public net.minecraft.util.Mth SIN public net.minecraft.world.entity.ai.Brain sensors public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities lineOfSightTest @@ -14,6 +15,9 @@ public net.minecraft.world.entity.ai.sensing.Sensor scanRate public net.minecraft.world.entity.ai.sensing.Sensor timeToTick public net.minecraft.world.entity.animal.armadillo.Armadillo scuteTime public net.minecraft.world.entity.animal.frog.Tadpole getTicksLeftUntilAdult()I +public net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper +public net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper rebind(Lnet/minecraft/world/level/block/entity/TickingBlockEntity;)V +public net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper ticker public net.minecraft.world.level.chunk.PaletteResize public net.minecraft.world.level.chunk.storage.RegionFile getOversizedData(II)Lnet/minecraft/nbt/CompoundTag; public net.minecraft.world.level.chunk.storage.RegionFile isOversized(II)Z diff --git a/divinemc-server/minecraft-patches/features/0029-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch b/divinemc-server/minecraft-patches/features/0029-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch deleted file mode 100644 index 46be04b..0000000 --- a/divinemc-server/minecraft-patches/features/0029-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Wed, 26 Mar 2025 01:46:49 +0300 -Subject: [PATCH] Leaf: Improve BlockEntity ticking isRemoved check - -Original project: https://github.com/Winds-Studio/Leaf - -This should help for massive hopper chains or hopper matrix. - -diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index dbb4142ea38cdf484e74c81103cebb024ae8813d..32f17328b7980a9dc382c90af76cca04b74c639a 100644 ---- a/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -982,13 +982,26 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - - static class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { - private TickingBlockEntity ticker; -+ private @Nullable BlockEntity blockEntityReference = null; // DivineMC - Improve BlockEntity ticking isRemoved check - - RebindableTickingBlockEntityWrapper(TickingBlockEntity ticker) { - this.ticker = ticker; -+ // DivineMC start - Improve BlockEntity ticking isRemoved check -+ if (ticker instanceof BoundTickingBlockEntity boundTicker) { -+ blockEntityReference = boundTicker.blockEntity; -+ } -+ // DivineMC end - Improve BlockEntity ticking isRemoved check - } - - void rebind(TickingBlockEntity ticker) { - this.ticker = ticker; -+ // DivineMC start - Improve BlockEntity ticking isRemoved check -+ if (ticker instanceof BoundTickingBlockEntity boundTicker) { -+ blockEntityReference = boundTicker.blockEntity; -+ } else { -+ blockEntityReference = null; -+ } -+ // DivineMC end - Improve BlockEntity ticking isRemoved check - } - - @Override -@@ -998,6 +1011,12 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - - @Override - public boolean isRemoved() { -+ // DivineMC start - Improve BlockEntity ticking isRemoved check -+ if (blockEntityReference != null) { -+ return blockEntityReference.isRemoved(); -+ } -+ // DivineMC end - Improve BlockEntity ticking isRemoved check -+ - return this.ticker.isRemoved(); - } - diff --git a/divinemc-server/minecraft-patches/features/0030-Option-to-allow-weird-movement-and-disable-teleporti.patch b/divinemc-server/minecraft-patches/features/0029-Option-to-allow-weird-movement-and-disable-teleporti.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0030-Option-to-allow-weird-movement-and-disable-teleporti.patch rename to divinemc-server/minecraft-patches/features/0029-Option-to-allow-weird-movement-and-disable-teleporti.patch index bbbd9d9..bb99794 100644 --- a/divinemc-server/minecraft-patches/features/0030-Option-to-allow-weird-movement-and-disable-teleporti.patch +++ b/divinemc-server/minecraft-patches/features/0029-Option-to-allow-weird-movement-and-disable-teleporti.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Option to allow weird movement and disable teleporting diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 637c5ca0c004e5de66bc3f84dba5fee47f729579..1bd6368704665f90eaa621366b4dec21bc937a96 100644 +index b44afe316fc8d886b9a21102cddf20d977169c51..b58fc16f9054f36d5ddb2dffabd9274969e56897 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -609,7 +609,7 @@ public class ServerGamePacketListenerImpl diff --git a/divinemc-server/minecraft-patches/features/0031-Optimize-Raids.patch b/divinemc-server/minecraft-patches/features/0030-Optimize-Raids.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0031-Optimize-Raids.patch rename to divinemc-server/minecraft-patches/features/0030-Optimize-Raids.patch index 6880e0a..8be09f1 100644 --- a/divinemc-server/minecraft-patches/features/0031-Optimize-Raids.patch +++ b/divinemc-server/minecraft-patches/features/0030-Optimize-Raids.patch @@ -25,7 +25,7 @@ index 5f2c27800f047f128857044493a6d9325ffd759b..4a2cace22512fe06c1713bc8735e775e // Paper start diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java -index b3a29ce523fb5de71589c7c17598bba17622f988..39b9141c6c64acb362bbf12a1d47901ff75920b6 100644 +index eb62402b7ed4cc76b4d510b1c8781b6f7b5be6ab..9183b8e8c54383a489a1f446d36bef41d11667ba 100644 --- a/net/minecraft/world/entity/raid/Raid.java +++ b/net/minecraft/world/entity/raid/Raid.java @@ -126,6 +126,7 @@ public class Raid { diff --git a/divinemc-server/minecraft-patches/features/0032-Small-optimization-to-LinearPalette.patch b/divinemc-server/minecraft-patches/features/0031-Small-optimization-to-LinearPalette.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0032-Small-optimization-to-LinearPalette.patch rename to divinemc-server/minecraft-patches/features/0031-Small-optimization-to-LinearPalette.patch diff --git a/divinemc-server/minecraft-patches/features/0033-Optimize-VarInt-and-VarLong-write.patch b/divinemc-server/minecraft-patches/features/0032-Optimize-VarInt-and-VarLong-write.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0033-Optimize-VarInt-and-VarLong-write.patch rename to divinemc-server/minecraft-patches/features/0032-Optimize-VarInt-and-VarLong-write.patch diff --git a/divinemc-server/minecraft-patches/features/0034-Optimize-Fluids.patch b/divinemc-server/minecraft-patches/features/0033-Optimize-Fluids.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0034-Optimize-Fluids.patch rename to divinemc-server/minecraft-patches/features/0033-Optimize-Fluids.patch diff --git a/divinemc-server/minecraft-patches/features/0035-Optimize-Structure-Generation.patch b/divinemc-server/minecraft-patches/features/0034-Optimize-Structure-Generation.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0035-Optimize-Structure-Generation.patch rename to divinemc-server/minecraft-patches/features/0034-Optimize-Structure-Generation.patch diff --git a/divinemc-server/minecraft-patches/features/0036-Implement-NoChatReports.patch b/divinemc-server/minecraft-patches/features/0035-Implement-NoChatReports.patch similarity index 99% rename from divinemc-server/minecraft-patches/features/0036-Implement-NoChatReports.patch rename to divinemc-server/minecraft-patches/features/0035-Implement-NoChatReports.patch index 90cad35..3573916 100644 --- a/divinemc-server/minecraft-patches/features/0036-Implement-NoChatReports.patch +++ b/divinemc-server/minecraft-patches/features/0035-Implement-NoChatReports.patch @@ -218,7 +218,7 @@ index 104a9ec97bd39e15f6707f19865fa6fcf47f6e4f..46adbe6ccf1e4291e33a52a6612f6245 // Paper start - Add setting for proxy online mode status return properties.enforceSecureProfile diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 667ef5b2ab50eeb0491f7fe0bc8913ec29a4603a..a7c4fad2b1cb0cbac742a18d37d688bb2663944e 100644 +index 1975aeecbfdebaacecae1c43005d4ff26fa6a263..e5d6e5ec12168936d6d50b2f38a3cb58150b0af1 100644 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java @@ -343,10 +343,64 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack diff --git a/divinemc-server/minecraft-patches/features/0037-Lag-compensation.patch b/divinemc-server/minecraft-patches/features/0036-Lag-compensation.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0037-Lag-compensation.patch rename to divinemc-server/minecraft-patches/features/0036-Lag-compensation.patch index 14e476d..231102a 100644 --- a/divinemc-server/minecraft-patches/features/0037-Lag-compensation.patch +++ b/divinemc-server/minecraft-patches/features/0036-Lag-compensation.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Lag compensation diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index d716f0b5f183d09bd7fc149eaa30b853916e585c..519296e58787a15a0d77de94bc3a591ace2d2ae0 100644 +index 221afb586fa0a0826809fb2c0c956450b461b341..2a03d85b15377f5c5286a6fea5b592b9ff76b791 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -288,6 +288,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(), new com.google.common.util.concurrent.ThreadFactoryBuilder() diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 519296e58787a15a0d77de94bc3a591ace2d2ae0..c4dc21538a6aa917de81ae11d786e8128c80ebf0 100644 +index 2a03d85b15377f5c5286a6fea5b592b9ff76b791..1877ee1431b0f858b6a5da7347d72fe90374e27a 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -2644,8 +2644,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop extends AbstractList { diff --git a/divinemc-server/minecraft-patches/features/0070-Optimize-level-ticking.patch b/divinemc-server/minecraft-patches/features/0069-Optimize-level-ticking.patch similarity index 99% rename from divinemc-server/minecraft-patches/features/0070-Optimize-level-ticking.patch rename to divinemc-server/minecraft-patches/features/0069-Optimize-level-ticking.patch index 9e8158b..b5cc125 100644 --- a/divinemc-server/minecraft-patches/features/0070-Optimize-level-ticking.patch +++ b/divinemc-server/minecraft-patches/features/0069-Optimize-level-ticking.patch @@ -73,7 +73,7 @@ index 386fdc23b35675a7db66d16bf2a8a6dd5b44059a..4934ce03ac533d9c60674632cdac6621 public void tickChunk(LevelChunk chunk, int randomTickSpeed) { diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 24f13e2b0b694ff4dd01aeea876ef874f1828304..404ed7c6ebe2364b9404df6e29d07a0aed83ed08 100644 +index 306590a29f8b6db6c0c68814f3fa28f3f26e448b..3dcdafe18085fc8fff9eb5bb50392ba13a27b066 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -75,7 +75,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p diff --git a/divinemc-server/minecraft-patches/features/0071-Optimize-Moonrise.patch b/divinemc-server/minecraft-patches/features/0070-Optimize-Moonrise.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0071-Optimize-Moonrise.patch rename to divinemc-server/minecraft-patches/features/0070-Optimize-Moonrise.patch diff --git a/divinemc-server/minecraft-patches/features/0072-lithium-combined_heightmap_update.patch b/divinemc-server/minecraft-patches/features/0071-lithium-combined_heightmap_update.patch similarity index 96% rename from divinemc-server/minecraft-patches/features/0072-lithium-combined_heightmap_update.patch rename to divinemc-server/minecraft-patches/features/0071-lithium-combined_heightmap_update.patch index 7dc261a..5870d61 100644 --- a/divinemc-server/minecraft-patches/features/0072-lithium-combined_heightmap_update.patch +++ b/divinemc-server/minecraft-patches/features/0071-lithium-combined_heightmap_update.patch @@ -10,7 +10,7 @@ As part of: Lithium (https://github.com/CaffeineMC/lithium) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 404ed7c6ebe2364b9404df6e29d07a0aed83ed08..853b7459bd6b62bc9eb98953b1ab9435b38de844 100644 +index 3dcdafe18085fc8fff9eb5bb50392ba13a27b066..9cd2a05683d879f56b6e62dfd49ac30341deeb06 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -380,10 +380,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p diff --git a/divinemc-server/minecraft-patches/features/0073-Entity-Status-Lock.patch b/divinemc-server/minecraft-patches/features/0072-Entity-Status-Lock.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0073-Entity-Status-Lock.patch rename to divinemc-server/minecraft-patches/features/0072-Entity-Status-Lock.patch diff --git a/divinemc-server/minecraft-patches/features/0073-lithium-sleeping_block_entity.patch b/divinemc-server/minecraft-patches/features/0073-lithium-sleeping_block_entity.patch new file mode 100644 index 0000000..db4ac08 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0073-lithium-sleeping_block_entity.patch @@ -0,0 +1,2387 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Mon, 29 Sep 2025 17:53:30 +0300 +Subject: [PATCH] lithium: sleeping_block_entity + +This patch is based on the following mixins: +* "net/caffeinemc/mods/lithium/mixin/world/block_entity_ticking/sleeping/*.java" +* "net/caffeinemc/mods/lithium/mixin/block/hopper/*.java" +* "net/caffeinemc/mods/lithium/mixin/util/block_entity_retrieval/LevelMixin.java" +* "net/caffeinemc/mods/lithium/mixin/util/inventory_change_listening/*.java" +* "net/caffeinemc/mods/lithium/mixin/util/inventory_comparator_tracking/*.java" +* "net/caffeinemc/mods/lithium/mixin/util/item_component_and_count_tracking/*.java" +By: 2No2Name <2No2Name@web.de> +As part of: Lithium (https://github.com/CaffeineMC/lithium) +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/net/minecraft/core/component/PatchedDataComponentMap.java b/net/minecraft/core/component/PatchedDataComponentMap.java +index 3af6c1e2549ba3aeb60aa9d498a976be3680c0ee..a0e3824fad70fed3e5b98d951c54c44f59875d0d 100644 +--- a/net/minecraft/core/component/PatchedDataComponentMap.java ++++ b/net/minecraft/core/component/PatchedDataComponentMap.java +@@ -14,7 +14,7 @@ import java.util.Map.Entry; + import java.util.stream.Collectors; + import javax.annotation.Nullable; + +-public final class PatchedDataComponentMap implements DataComponentMap { ++public final class PatchedDataComponentMap implements DataComponentMap, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangePublisher { // DivineMC - lithium: sleeping_block_entity + private final DataComponentMap prototype; + private Reference2ObjectMap, Optional> patch; + private boolean copyOnWrite; +@@ -135,6 +135,7 @@ public final class PatchedDataComponentMap implements DataComponentMap { + } + + private void ensureMapOwnership() { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.subscriber != null) this.subscriber.lithium$notify((PatchedDataComponentMap) (Object) this, 0); // DivineMC - lithium: sleeping_block_entity + if (this.copyOnWrite) { + this.patch = new Reference2ObjectArrayMap<>(this.patch); + this.copyOnWrite = false; +@@ -238,4 +239,22 @@ public final class PatchedDataComponentMap implements DataComponentMap { + public String toString() { + return "{" + this.stream().map(TypedDataComponent::toString).collect(Collectors.joining(", ")) + "}"; + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber; ++ ++ @Override ++ public void lithium$subscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { ++ if (subscriberData != 0) { ++ throw new UnsupportedOperationException("ComponentMapImpl does not support subscriber data"); ++ } ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.combine(this.subscriber, 0, subscriber, 0); ++ } ++ ++ @Override ++ public int lithium$unsubscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber) { ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber); ++ return 0; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/server/commands/data/EntityDataAccessor.java b/net/minecraft/server/commands/data/EntityDataAccessor.java +index 3092454bf7071deca75fecfc203072593fe5c7e7..098dd4647ae1500195729d6531e90c2bb7603eba 100644 +--- a/net/minecraft/server/commands/data/EntityDataAccessor.java ++++ b/net/minecraft/server/commands/data/EntityDataAccessor.java +@@ -55,6 +55,7 @@ public class EntityDataAccessor implements DataAccessor { + try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(this.entity.problemPath(), LOGGER)) { + this.entity.load(TagValueInput.create(scopedCollector, this.entity.registryAccess(), other)); + this.entity.setUUID(uuid); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity) itemEntity.levelCallback.onMove(); // DivineMC - lithium: sleeping_block_entity + } + } + } +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index b50afea7c2e4c61a3df196e74afd8f82b30aca8a..dc5889c97b4aa1fe9be83b1c10c7c855e5a96d45 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -2438,6 +2438,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + for (TickingBlockEntity tickingBlockEntity : this.blockEntityTickers) { + BlockPos pos = tickingBlockEntity.getPos(); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && pos == null) pos = BlockPos.ZERO; // DivineMC - lithium: sleeping_block_entity + csvOutput.writeRow(pos.getX(), pos.getY(), pos.getZ(), tickingBlockEntity.getType()); + } + } +diff --git a/net/minecraft/world/Container.java b/net/minecraft/world/Container.java +index b382665cc125b8b5c0938e5e55984e4bf91d37ff..c112b58996494d97fcd226fc490fe2718a417806 100644 +--- a/net/minecraft/world/Container.java ++++ b/net/minecraft/world/Container.java +@@ -11,7 +11,7 @@ import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.entity.BlockEntity; + +-public interface Container extends Clearable, Iterable { ++public interface Container extends Clearable, Iterable, net.caffeinemc.mods.lithium.api.inventory.LithiumCooldownReceivingInventory, net.caffeinemc.mods.lithium.api.inventory.LithiumTransferConditionInventory { // DivineMC - lithium: sleeping_block_entity + float DEFAULT_DISTANCE_BUFFER = 4.0F; + + int getContainerSize(); +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 2c7c5dab268625e1328f57ac3ec2a735a82fea42..5d0d94475a6045f256fac0d997c0cb40c2d4a07e 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -308,7 +308,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + private static final EntityDataAccessor DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); + protected static final EntityDataAccessor DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE); + public static final EntityDataAccessor DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT); +- private EntityInLevelCallback levelCallback = EntityInLevelCallback.NULL; ++ public EntityInLevelCallback levelCallback = EntityInLevelCallback.NULL; // DivineMC - lithium: sleeping_block_entity - private -> public + private final VecDeltaCodec packetPositionCodec = new VecDeltaCodec(); + public boolean hasImpulse; + @Nullable +@@ -5075,6 +5075,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.setBoundingBox(this.makeBoundingBox()); + } + // Paper end - Block invalid positions and bounding box ++ // DivineMC start - lithium: sleeping_block_entity ++ if (!org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) return; ++ if (this instanceof ItemEntity) { ++ long sectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(this); ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker tracker = net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker.itemEntityMovementTrackerMap.get(new net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.ChunkSectionIdentifier(sectionKey, level.getMinecraftWorld().uuid)); ++ if (tracker != null) tracker.notifyAllListeners(level.getGameTime()); ++ } else if (this instanceof net.minecraft.world.entity.vehicle.ContainerEntity) { ++ long sectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(this); ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker tracker = net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker.containerEntityMovementTrackerMap.get(new net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.ChunkSectionIdentifier(sectionKey, level.getMinecraftWorld().uuid)); ++ if (tracker != null) tracker.notifyAllListeners(level.getGameTime()); ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } + + public void checkDespawn() { +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index f738db4aa54a5961e1484737b99de133f7e92b68..c0d7fc170a005e5a43f15ce52d3a24c4a4a3f72f 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -35,7 +35,7 @@ import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + import net.minecraft.world.phys.Vec3; + +-public class ItemEntity extends Entity implements TraceableEntity { ++public class ItemEntity extends Entity implements TraceableEntity, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangePublisher, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber { // DivineMC - lithium: sleeping_block_entity + private static final EntityDataAccessor DATA_ITEM = SynchedEntityData.defineId(ItemEntity.class, EntityDataSerializers.ITEM_STACK); + private static final float FLOAT_HEIGHT = 0.1F; + public static final float EYE_HEIGHT = 0.2125F; +@@ -551,6 +551,25 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + + public void setItem(ItemStack stack) { ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.subscriber != null) { ++ ItemStack oldStack = this.getItem(); ++ if (oldStack != stack) { ++ if (!oldStack.isEmpty()) { ++ oldStack.lithium$unsubscribe(this); ++ } ++ ++ if (!stack.isEmpty()) { ++ stack.lithium$subscribe(this, this.subscriberData); ++ this.subscriber.lithium$notify((ItemEntity) (Object) this, this.subscriberData); ++ } else { ++ this.subscriber.lithium$forceUnsubscribe((ItemEntity) (Object) this, this.subscriberData); ++ this.subscriber = null; ++ this.subscriberData = 0; ++ } ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + this.getEntityData().set(DATA_ITEM, stack); + this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate + // Purpur start - Item entity immunities +@@ -636,4 +655,75 @@ public class ItemEntity extends Entity implements TraceableEntity { + public SlotAccess getSlot(int slot) { + return slot == 0 ? SlotAccess.of(this::getItem, this::setItem) : super.getSlot(slot); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber; ++ private int subscriberData; ++ ++ private void startTrackingChanges() { ++ ItemStack stack = this.getItem(); ++ if (!stack.isEmpty()) { ++ stack.lithium$subscribe(this, 0); ++ } ++ } ++ ++ @Override ++ public void lithium$subscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { ++ if (this.subscriber == null) { ++ this.startTrackingChanges(); ++ } ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.combine(this.subscriber, this.subscriberData, subscriber, subscriberData); ++ if (this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.Multi) { ++ this.subscriberData = 0; ++ } else { ++ this.subscriberData = subscriberData; ++ } ++ } ++ ++ @Override ++ public int lithium$unsubscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber) { ++ int retval = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataOf(this.subscriber, subscriber, this.subscriberData); ++ this.subscriberData = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData); ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber); ++ ++ if (this.subscriber == null) { ++ ItemStack stack = this.getItem(); ++ if (!stack.isEmpty()) { ++ stack.lithium$unsubscribe(this); ++ } ++ } ++ return retval; ++ } ++ ++ @Override ++ public void lithium$notify(ItemStack publisher, int subscriberData) { ++ if (publisher != this.getItem()) { ++ throw new IllegalStateException("Received notification from an unexpected publisher"); ++ } ++ ++ if (this.subscriber != null) { ++ this.subscriber.lithium$notify(this, this.subscriberData); ++ } ++ } ++ ++ @Override ++ public void lithium$forceUnsubscribe(ItemStack publisher, int subscriberData) { ++ if (this.subscriber != null) { ++ this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); ++ this.subscriber = null; ++ this.subscriberData = 0; ++ } ++ } ++ ++ @Override ++ public void lithium$notifyCount(ItemStack publisher, int subscriberData, int newCount) { ++ if (publisher != this.getItem()) { ++ throw new IllegalStateException("Received notification from an unexpected publisher"); ++ } ++ ++ if (this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { ++ countChangeSubscriber.lithium$notifyCount(this, this.subscriberData, newCount); ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +index 6a008c86f4e360c916b93f0e3a62a9d8b43e74e6..e19439bc89a2bd982aeb04323d0d83bcbd556117 100644 +--- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java ++++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +@@ -21,7 +21,7 @@ import net.minecraft.world.level.storage.ValueOutput; + import net.minecraft.world.level.storage.loot.LootTable; + import net.minecraft.world.phys.Vec3; + +-public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity { ++public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + private NonNullList itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513 + @Nullable + public ResourceKey lootTable; +@@ -218,4 +218,16 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + return this.getBukkitEntity().getLocation(); + } + // CraftBukkit end ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return itemStacks; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ itemStacks = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java +index 97397e5849d3ddc14506776431a69939a2204765..3af415d58e32d89ac7c9289d5a003fc11a98149c 100644 +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -901,6 +901,12 @@ public abstract class AbstractContainerMenu { + } else { + float f = 0.0F; + ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && container instanceof net.caffeinemc.mods.lithium.api.inventory.LithiumInventory inventory) { ++ return net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(inventory).getSignalStrength(container); ++ } ++ // DivineMC end - lithium: sleeping_block_entity ++ + for (int i = 0; i < container.getContainerSize(); i++) { + ItemStack item = container.getItem(i); + if (!item.isEmpty()) { +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index 6b5b6d73897ded23dd2fbf17abb1b5c1ee5b1082..4afc860f0e6fdfa12061b0fc4c62b04dce9cac60 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -94,7 +94,7 @@ import org.apache.commons.lang3.function.TriConsumer; + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.slf4j.Logger; + +-public final class ItemStack implements DataComponentHolder { ++public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangePublisher, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber { // DivineMC - lithium: sleeping_block_entity + private static final List OP_NBT_WARNING = List.of( + Component.translatable("item.op_warning.line1").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), + Component.translatable("item.op_warning.line2").withStyle(ChatFormatting.RED), +@@ -982,6 +982,7 @@ public final class ItemStack implements DataComponentHolder { + + @Nullable + public T set(DataComponentType component, @Nullable T value) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && component == DataComponents.ENCHANTMENTS && this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.EnchantmentSubscriber enchantmentSubscriber) enchantmentSubscriber.lithium$notifyAfterEnchantmentChange(this, this.subscriberData); // DivineMC - lithium: sleeping_block_entity + return this.components.set(component, value); + } + +@@ -1332,6 +1333,23 @@ public final class ItemStack implements DataComponentHolder { + } + + public void setCount(int count) { ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && count != this.count) { ++ if (this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { ++ countChangeSubscriber.lithium$notifyCount(this, this.subscriberData, count); ++ } ++ ++ if (count == 0) { ++ this.components.lithium$unsubscribe(this); ++ ++ if (this.subscriber != null) { ++ this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); ++ this.subscriber = null; ++ this.subscriberData = 0; ++ } ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + this.count = count; + } + +@@ -1387,4 +1405,90 @@ public final class ItemStack implements DataComponentHolder { + public boolean canDestroyBlock(BlockState state, Level level, BlockPos pos, Player player) { + return this.getItem().canDestroyBlock(this, state, level, pos, player); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber; ++ private int subscriberData; ++ ++ @Override ++ public void lithium$subscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { ++ if (this.isEmpty()) { ++ throw new IllegalStateException("Cannot subscribe to an empty ItemStack!"); ++ } ++ ++ if (this.subscriber == null) { ++ this.startTrackingChanges(); ++ } ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.combine(this.subscriber, this.subscriberData, subscriber, subscriberData); ++ if (this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.Multi) { ++ this.subscriberData = 0; ++ } else { ++ this.subscriberData = subscriberData; ++ } ++ } ++ ++ @Override ++ public int lithium$unsubscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber) { ++ if (this.isEmpty()) { ++ throw new IllegalStateException("Cannot unsubscribe from an empty ItemStack!"); ++ } ++ ++ int retval = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataOf(this.subscriber, subscriber, this.subscriberData); ++ this.subscriberData = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData); ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber); ++ ++ if (this.subscriber == null) { ++ this.components.lithium$unsubscribe(this); ++ } ++ return retval; ++ } ++ ++ @Override ++ public void lithium$unsubscribeWithData(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { ++ if (this.isEmpty()) { ++ throw new IllegalStateException("Cannot unsubscribe from an empty ItemStack!"); ++ } ++ ++ this.subscriberData = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData, subscriberData, true); ++ this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber, subscriberData, true); ++ ++ if (this.subscriber == null) { ++ this.components.lithium$unsubscribe(this); ++ } ++ } ++ ++ @Override ++ public boolean lithium$isSubscribedWithData(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { ++ if (this.isEmpty()) { ++ throw new IllegalStateException("Cannot be subscribed to an empty ItemStack!"); ++ } ++ ++ return net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.containsSubscriber(this.subscriber, this.subscriberData, subscriber, subscriberData); ++ } ++ ++ @Override ++ public void lithium$forceUnsubscribe(PatchedDataComponentMap publisher, int subscriberData) { ++ if (publisher != this.components) { ++ throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); ++ } ++ this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); ++ this.subscriber = null; ++ this.subscriberData = 0; ++ } ++ ++ private void startTrackingChanges() { ++ this.components.lithium$subscribe(this, 0); ++ } ++ ++ @Override ++ public void lithium$notify(PatchedDataComponentMap publisher, int subscriberData) { ++ if (publisher != this.components) { ++ throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); ++ } ++ ++ if (this.subscriber != null) { ++ this.subscriber.lithium$notify(this, this.subscriberData); ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 0b7f9af0c4e43115878769043ebd06a09ccdf059..f9f0649bd4afc514618cc05afdc5af9750bbddfa 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -1521,7 +1521,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl + // Spigot end + if (tickingBlockEntity.isRemoved()) { + this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // DivineMC - optimize block entity removals - Fix MC-117075 +- } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { ++ } else if (runsNormally && this.shouldTickBlockPosFilterNull(tickingBlockEntity.getPos())) { // DivineMC - lithium: sleeping_block_entity + tickingBlockEntity.tick(); + // DivineMC start - Parallel world ticking + ++tickedEntities; +@@ -2204,4 +2204,25 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl + return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END; + } + // Purpur end - Add allow water in end world option ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ public BlockEntity lithium$getLoadedExistingBlockEntity(BlockPos pos) { ++ if (!this.isOutsideBuildHeight(pos)) { ++ if (this.isClientSide || Thread.currentThread() == this.thread) { ++ ChunkAccess chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); ++ if (chunk != null) { ++ return chunk.getBlockEntity(pos); ++ } ++ } ++ } ++ return null; ++ } ++ ++ private boolean shouldTickBlockPosFilterNull(BlockPos pos) { ++ if (pos == null) { ++ return false; ++ } ++ return shouldTickBlocksAt(pos); ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/ComposterBlock.java b/net/minecraft/world/level/block/ComposterBlock.java +index 3eb11df5d14ec63911be630ca99d8d9903723f9b..30dfd88822086d0769fab256401cff690f1c2bf5 100644 +--- a/net/minecraft/world/level/block/ComposterBlock.java ++++ b/net/minecraft/world/level/block/ComposterBlock.java +@@ -440,7 +440,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + } + } + +- public static class EmptyContainer extends SimpleContainer implements WorldlyContainer { ++ public static class EmptyContainer extends SimpleContainer implements WorldlyContainer, net.caffeinemc.mods.lithium.common.hopper.BlockStateOnlyInventory { // DivineMC - lithium: sleeping_block_entity + public EmptyContainer(LevelAccessor levelAccessor, BlockPos blockPos) { // CraftBukkit + super(0); + this.bukkitOwner = new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(levelAccessor, blockPos, this); // CraftBukkit +@@ -462,7 +462,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + } + } + +- public static class InputContainer extends SimpleContainer implements WorldlyContainer { ++ public static class InputContainer extends SimpleContainer implements WorldlyContainer, net.caffeinemc.mods.lithium.common.hopper.BlockStateOnlyInventory { // DivineMC - lithium: sleeping_block_entity + private final BlockState state; + private final LevelAccessor level; + private final BlockPos pos; +@@ -508,12 +508,13 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + } + // Paper end - Add CompostItemEvent and EntityCompostItemEvent + this.level.levelEvent(1500, this.pos, blockState != this.state ? 1 : 0); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.changed = false; // DivineMC - lithium: sleeping_block_entity + this.removeItemNoUpdate(0); + } + } + } + +- public static class OutputContainer extends SimpleContainer implements WorldlyContainer { ++ public static class OutputContainer extends SimpleContainer implements WorldlyContainer, net.caffeinemc.mods.lithium.common.hopper.BlockStateOnlyInventory { // DivineMC - lithium: sleeping_block_entity + private final BlockState state; + private final LevelAccessor level; + private final BlockPos pos; +diff --git a/net/minecraft/world/level/block/DiodeBlock.java b/net/minecraft/world/level/block/DiodeBlock.java +index 558751ade918a92a1173096ccfeacf238f4260d0..1a2c56330dc5d75a566b98232d38da54c6412fcc 100644 +--- a/net/minecraft/world/level/block/DiodeBlock.java ++++ b/net/minecraft/world/level/block/DiodeBlock.java +@@ -173,6 +173,7 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { + @Override + protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) { + this.updateNeighborsInFront(level, pos, state); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this instanceof ComparatorBlock && !oldState.is(Blocks.COMPARATOR)) net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracking.notifyNearbyBlockEntitiesAboutNewComparator(level, pos); // DivineMC - lithium: sleeping_block_entity + } + + @Override +diff --git a/net/minecraft/world/level/block/HopperBlock.java b/net/minecraft/world/level/block/HopperBlock.java +index 46a27f60ba407dacdac190b5e292ab3f1db5a078..5496fb061d6adea71b509f8cc455891b1c759d8e 100644 +--- a/net/minecraft/world/level/block/HopperBlock.java ++++ b/net/minecraft/world/level/block/HopperBlock.java +@@ -38,7 +38,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + +-public class HopperBlock extends BaseEntityBlock { ++public class HopperBlock extends BaseEntityBlock implements net.caffeinemc.mods.lithium.common.block.entity.ShapeUpdateHandlingBlockBehaviour { // DivineMC - lithium: sleeping_block_entity + public static final MapCodec CODEC = simpleCodec(HopperBlock::new); + public static final EnumProperty FACING = BlockStateProperties.FACING_HOPPER; + public static final BooleanProperty ENABLED = BlockStateProperties.ENABLED; +@@ -101,6 +101,16 @@ public class HopperBlock extends BaseEntityBlock { + protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) { + if (!oldState.is(state.getBlock())) { + this.checkPoweredState(level, pos, state); ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && level.getBlockState(pos) != state) { ++ for (Direction direction : UPDATE_SHAPE_ORDER) { ++ BlockEntity hopper = level.lithium$getLoadedExistingBlockEntity(pos.relative(direction)); ++ if (hopper instanceof net.caffeinemc.mods.lithium.common.hopper.UpdateReceiver updateReceiver) { ++ updateReceiver.lithium$invalidateCacheOnNeighborUpdate(direction == Direction.DOWN); ++ } ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } + } + +@@ -115,6 +125,7 @@ public class HopperBlock extends BaseEntityBlock { + + @Override + protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && level.lithium$getLoadedExistingBlockEntity(pos) instanceof net.caffeinemc.mods.lithium.common.hopper.UpdateReceiver updateReceiver) updateReceiver.lithium$invalidateCacheOnUndirectedNeighborUpdate(); // DivineMC - lithium: sleeping_block_entity + this.checkPoweredState(level, pos, state); + } + +@@ -168,4 +179,24 @@ public class HopperBlock extends BaseEntityBlock { + protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { + return false; + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ @Override ++ public void lithium$handleShapeUpdate(net.minecraft.world.level.LevelReader levelReader, BlockState myBlockState, BlockPos myPos, BlockPos posFrom, BlockState newState) { ++ if (newState.getBlock() instanceof net.minecraft.world.WorldlyContainerHolder) { ++ this.updateHopper(levelReader, myBlockState, myPos, posFrom); ++ } ++ } ++ ++ private void updateHopper(net.minecraft.world.level.LevelReader world, BlockState myBlockState, BlockPos myPos, BlockPos posFrom) { ++ Direction facing = myBlockState.getValue(HopperBlock.FACING); ++ boolean above = posFrom.getY() == myPos.getY() + 1; ++ if (above || posFrom.getX() == myPos.getX() + facing.getStepX() && posFrom.getY() == myPos.getY() + facing.getStepY() && posFrom.getZ() == myPos.getZ() + facing.getStepZ()) { ++ BlockEntity hopper = ((net.minecraft.world.level.Level) world).lithium$getLoadedExistingBlockEntity(myPos); ++ if (hopper instanceof net.caffeinemc.mods.lithium.common.hopper.UpdateReceiver updateReceiver) { ++ updateReceiver.lithium$invalidateCacheOnNeighborUpdate(above); ++ } ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 57eae0dbb614f57e2a352613c7490145bbfeb5a1..e6d04ab20e800cc1db245efbf9cf23ae5650dff3 100644 +--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -39,7 +39,7 @@ import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + import net.minecraft.world.phys.Vec3; + +-public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible { ++public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.SetChangedHandlingBlockEntity, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + protected static final int SLOT_INPUT = 0; + protected static final int SLOT_FUEL = 1; + protected static final int SLOT_RESULT = 2; +@@ -164,6 +164,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + this.recipesUsed.clear(); + this.recipesUsed.putAll(input.read("RecipesUsed", RECIPES_USED_CODEC).orElse(Map.of())); + this.cookSpeedMultiplier = input.getDoubleOr("Paper.CookSpeedMultiplier", 1); // Paper - cook speed multiplier API ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.isSleeping() && this.level != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -285,6 +286,12 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + setChanged(level, pos, state); + } + ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) { ++ furnace.checkSleep(state); ++ } ++ // DivineMC end - lithium: sleeping_block_entity ++ + if (usedLavaFromUnderneath) furnace.items.set(1, ItemStack.EMPTY); // Purpur - Furnace uses lava from underneath + } + +@@ -544,4 +551,53 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + this.getRecipesToAwardAndPopExperience(serverLevel, Vec3.atCenterOf(pos)); + } + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ this.lithium$setSleepingTicker(null); ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ private void checkSleep(BlockState state) { ++ if (!this.isLit() && this.cookingTimer == 0 && (state.is(Blocks.FURNACE) || state.is(Blocks.BLAST_FURNACE) || state.is(Blocks.SMOKER)) && this.level != null) { ++ this.lithium$startSleeping(); ++ } ++ } ++ ++ @Override ++ public void lithium$handleSetChanged() { ++ if (this.isSleeping() && this.level != null && !this.level.isClientSide) { ++ this.wakeUpNow(); ++ } ++ } ++ ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return items; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ items = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +index 0e4f6455ec48c5a7fcd4613c1c5b79d599e4960a..ceed244209c0ac5d316194fc37b2b9461ceea4e9 100644 +--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +@@ -20,7 +20,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + +-public class BarrelBlockEntity extends RandomizableContainerBlockEntity { ++public class BarrelBlockEntity extends RandomizableContainerBlockEntity implements net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + // CraftBukkit start - add fields and methods + public java.util.List transaction = new java.util.ArrayList<>(); + private int maxStack = MAX_STACK; +@@ -138,6 +138,7 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { + @Override + protected void setItems(NonNullList items) { + this.items = items; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$emitStackListReplaced(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -190,4 +191,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { + double d2 = this.worldPosition.getZ() + 0.5 + unitVec3i.getZ() / 2.0; + this.level.playSound(null, d, d1, d2, sound, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return items; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ items = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index 3df0633fe4e632f7d42289facf4ad79978d50c40..1219c96665af5068c8cdb772309e4ab39e4fc20c 100644 +--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -24,7 +24,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + +-public abstract class BaseContainerBlockEntity extends BlockEntity implements Container, MenuProvider, Nameable { ++public abstract class BaseContainerBlockEntity extends BlockEntity implements Container, MenuProvider, Nameable, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeEmitter { // DivineMC - lithium: sleeping_block_entity + public LockCode lockKey = LockCode.NO_LOCK; + @Nullable + public Component name; +@@ -38,6 +38,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + super.loadAdditional(input); + this.lockKey = LockCode.fromTag(input); + this.name = parseCustomNameSafe(input, "CustomName"); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker inventoryChangeTracker) inventoryChangeTracker.lithium$emitStackListReplaced(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -206,4 +207,98 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + return org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.worldPosition, this.level); + } + // CraftBukkit end ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ it.unimi.dsi.fastutil.objects.ReferenceArraySet inventoryChangeListeners = null; ++ it.unimi.dsi.fastutil.objects.ReferenceArraySet inventoryHandlingTypeListeners = null; ++ ++ @Override ++ public void lithium$emitContentModified() { ++ it.unimi.dsi.fastutil.objects.ReferenceArraySet inventoryChangeListeners = this.inventoryChangeListeners; ++ if (inventoryChangeListeners != null) { ++ for (net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener inventoryChangeListener : inventoryChangeListeners) { ++ inventoryChangeListener.lithium$handleInventoryContentModified(this); ++ } ++ inventoryChangeListeners.clear(); ++ } ++ } ++ ++ @Override ++ public void lithium$emitStackListReplaced() { ++ it.unimi.dsi.fastutil.objects.ReferenceArraySet listeners = this.inventoryHandlingTypeListeners; ++ if (listeners != null && !listeners.isEmpty()) { ++ for (net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener inventoryChangeListener : listeners) { ++ inventoryChangeListener.handleStackListReplaced(this); ++ } ++ listeners.clear(); ++ } ++ ++ if (this instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener listener) { ++ listener.handleStackListReplaced(this); ++ } ++ ++ this.invalidateChangeListening(); ++ } ++ ++ @Override ++ public void lithium$emitRemoved() { ++ it.unimi.dsi.fastutil.objects.ReferenceArraySet listeners = this.inventoryHandlingTypeListeners; ++ if (listeners != null && !listeners.isEmpty()) { ++ for (net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener listener : listeners) { ++ listener.lithium$handleInventoryRemoved(this); ++ } ++ listeners.clear(); ++ } ++ ++ if (this instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener listener) { ++ listener.lithium$handleInventoryRemoved(this); ++ } ++ ++ this.invalidateChangeListening(); ++ } ++ ++ private void invalidateChangeListening() { ++ if (this.inventoryChangeListeners != null) { ++ this.inventoryChangeListeners.clear(); ++ } ++ ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList lithiumStackList = this instanceof net.caffeinemc.mods.lithium.api.inventory.LithiumInventory ? net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackListOrNull((net.caffeinemc.mods.lithium.api.inventory.LithiumInventory) this) : null; ++ if (lithiumStackList != null && this instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker inventoryChangeTracker) { ++ lithiumStackList.removeInventoryModificationCallback(inventoryChangeTracker); ++ } ++ } ++ ++ @Override ++ public void lithium$emitFirstComparatorAdded() { ++ it.unimi.dsi.fastutil.objects.ReferenceArraySet inventoryChangeListeners = this.inventoryChangeListeners; ++ if (inventoryChangeListeners != null && !inventoryChangeListeners.isEmpty()) { ++ inventoryChangeListeners.removeIf(inventoryChangeListener -> inventoryChangeListener.lithium$handleComparatorAdded(this)); ++ } ++ } ++ ++ @Override ++ public void lithium$forwardContentChangeOnce(net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener inventoryChangeListener, net.caffeinemc.mods.lithium.common.hopper.LithiumStackList stackList, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker thisTracker) { ++ if (this.inventoryChangeListeners == null) { ++ this.inventoryChangeListeners = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(1); ++ } ++ stackList.setInventoryModificationCallback(thisTracker); ++ this.inventoryChangeListeners.add(inventoryChangeListener); ++ ++ } ++ ++ @Override ++ public void lithium$forwardMajorInventoryChanges(net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener inventoryChangeListener) { ++ if (this.inventoryHandlingTypeListeners == null) { ++ this.inventoryHandlingTypeListeners = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(1); ++ } ++ this.inventoryHandlingTypeListeners.add(inventoryChangeListener); ++ } ++ ++ @Override ++ public void lithium$stopForwardingMajorInventoryChanges(net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener inventoryChangeListener) { ++ if (this.inventoryHandlingTypeListeners != null) { ++ this.inventoryHandlingTypeListeners.remove(inventoryChangeListener); ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java +index 6ec81403c0aeb1d48d405602df4e27bce8302f38..888f8ef91e820e2f94ee3b67b903bfc1dc4ec40f 100644 +--- a/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -34,7 +34,7 @@ import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + import org.slf4j.Logger; + +-public abstract class BlockEntity { ++public abstract class BlockEntity implements net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracker, net.caffeinemc.mods.lithium.common.block.entity.SetBlockStateHandlingBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.SetChangedHandlingBlockEntity { // DivineMC - lithium: sleeping_block_entity + static boolean ignoreBlockEntityUpdates; // Paper - Perf: Optimize Hoppers + // CraftBukkit start - data containers + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); +@@ -56,6 +56,7 @@ public abstract class BlockEntity { + this.validateBlockState(blockState); + this.blockState = blockState; + this.persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init ++ this.hasComparators = UNKNOWN; // DivineMC - lithium: sleeping_block_entity + } + + private void validateBlockState(BlockState state) { +@@ -239,6 +240,7 @@ public abstract class BlockEntity { + if (this.level != null) { + if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers + setChanged(this.level, this.worldPosition, this.blockState); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) lithium$handleSetChanged(); // DivineMC - lithium: sleeping_block_entity + } + } + +@@ -271,7 +273,9 @@ public abstract class BlockEntity { + } + + public void setRemoved() { ++ this.hasComparators = UNKNOWN; // DivineMC - lithium: sleeping_block_entity + this.remove = true; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.level != null && !this.level.isClientSide() && this instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker inventoryChangeTracker) inventoryChangeTracker.lithium$emitRemoved(); // DivineMC - lithium: sleeping_block_entity + } + + public void clearRemoved() { +@@ -311,6 +315,7 @@ public abstract class BlockEntity { + public void setBlockState(BlockState blockState) { + this.validateBlockState(blockState); + this.blockState = blockState; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$handleSetBlockState(); // DivineMC - lithium: sleeping_block_entity + } + + protected void applyImplicitComponents(DataComponentGetter componentGetter) { +@@ -424,4 +429,32 @@ public abstract class BlockEntity { + return this.persistentLore; + } + // Purpur end - Persistent BlockEntity Lore and DisplayName ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private static final byte UNKNOWN = (byte) -1; ++ private static final byte COMPARATOR_PRESENT = (byte) 1; ++ private static final byte COMPARATOR_ABSENT = (byte) 0; ++ ++ byte hasComparators; ++ ++ @Override ++ public void lithium$onComparatorAdded(net.minecraft.core.Direction direction, int offset) { ++ byte hasComparators = this.hasComparators; ++ if (direction.getAxis() != net.minecraft.core.Direction.Axis.Y && hasComparators != COMPARATOR_PRESENT && offset >= 1 && offset <= 2) { ++ this.hasComparators = COMPARATOR_PRESENT; ++ ++ if (this instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker inventoryChangeTracker) { ++ inventoryChangeTracker.lithium$emitFirstComparatorAdded(); ++ } ++ } ++ } ++ ++ @Override ++ public boolean lithium$hasAnyComparatorNearby() { ++ if (this.hasComparators == UNKNOWN) { ++ this.hasComparators = net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracking.findNearbyComparators(this.level, this.worldPosition) ? COMPARATOR_PRESENT : COMPARATOR_ABSENT; ++ } ++ return this.hasComparators == COMPARATOR_PRESENT; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +index 79a9f1c87de30cda479b55cf70fbc3219a3dcad4..fe2557bc32d5fe804b945cab337fd43249e965de 100644 +--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +@@ -24,7 +24,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + +-public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer { ++public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.SetChangedHandlingBlockEntity, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + private static final int INGREDIENT_SLOT = 3; + private static final int FUEL_SLOT = 4; + private static final int[] SLOTS_FOR_UP = new int[]{3}; +@@ -135,6 +135,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + } + + public static void serverTick(Level level, BlockPos pos, BlockState state, BrewingStandBlockEntity blockEntity) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.checkSleep(state); // DivineMC - lithium: sleeping_block_entity + ItemStack itemStack = blockEntity.items.get(4); + if (blockEntity.fuel <= 0 && itemStack.is(ItemTags.BREWING_FUEL)) { + // CraftBukkit start +@@ -152,6 +153,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + itemStack.shrink(1); + } + // CraftBukkit end ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + setChanged(level, pos, state); + } + +@@ -166,7 +168,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + } else if (!isBrewable || !itemStack1.is(blockEntity.ingredient)) { + blockEntity.brewTime = 0; + } +- ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + setChanged(level, pos, state); + } else if (isBrewable && blockEntity.fuel > 0) { + blockEntity.fuel--; +@@ -179,6 +181,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + blockEntity.brewTime = event.getBrewingTime(); // 400 -> event.getTotalBrewTime() // Paper - use brewing time from event + // CraftBukkit end + blockEntity.ingredient = itemStack1.getItem(); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + setChanged(level, pos, state); + } + +@@ -285,6 +288,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + } + + this.fuel = input.getByteOr("Fuel", (byte)0); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.isSleeping() && this.level != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -331,4 +335,53 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + protected AbstractContainerMenu createMenu(int id, Inventory player) { + return new BrewingStandMenu(id, player, this, this.dataAccess); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ this.lithium$setSleepingTicker(null); ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ private void checkSleep(BlockState state) { ++ if (this.brewTime == 0 && state.is(net.minecraft.world.level.block.Blocks.BREWING_STAND) && this.level != null) { ++ this.lithium$startSleeping(); ++ } ++ } ++ ++ @Override ++ public void lithium$handleSetChanged() { ++ if (this.isSleeping() && this.level != null) { ++ this.wakeUpNow(); ++ } ++ } ++ ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return items; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ items = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index fb7932e17d7d00ee3050e71c88510fa23befb1bb..d4f8dc6e37eea8da251e3dfb917607a79f6f08cf 100644 +--- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -38,7 +38,7 @@ import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + import org.slf4j.Logger; + +-public class CampfireBlockEntity extends BlockEntity implements Clearable { ++public class CampfireBlockEntity extends BlockEntity implements Clearable, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int BURN_COOL_SPEED = 2; + private static final int NUM_SLOTS = 4; +@@ -112,7 +112,11 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + + if (flag) { + setChanged(level, pos, state); ++ // DivineMC start - lithium: sleeping_block_entity ++ } else if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) { ++ campfire.lithium$startSleeping(); + } ++ // DivineMC end - lithium: sleeping_block_entity + } + + public static void cooldownTick(Level level, BlockPos pos, BlockState state, CampfireBlockEntity blockEntity) { +@@ -127,7 +131,11 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + + if (flag) { + setChanged(level, pos, state); ++ // DivineMC start - lithium: sleeping_block_entity ++ } else if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) { ++ blockEntity.lithium$startSleeping(); + } ++ // DivineMC end - lithium: sleeping_block_entity + } + + public static void particleTick(Level level, BlockPos pos, BlockState state, CampfireBlockEntity blockEntity) { +@@ -183,6 +191,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, bytes.capacity())); + }); + // Paper end - Add more Campfire API ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -237,6 +246,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + this.cookingTime[i] = event.getTotalCookTime(); // i -> event.getTotalCookTime() + // CraftBukkit end + this.cookingProgress[i] = 0; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + this.items.set(i, stack.consumeAndReturn(1, entity)); + level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState())); + this.markUpdated(); +@@ -280,4 +290,30 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + public void removeComponentsFromTag(ValueOutput output) { + output.discard("Items"); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ this.lithium$setSleepingTicker(null); ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/net/minecraft/world/level/block/entity/ChestBlockEntity.java +index b7d94ebe0ee995392c355c4237da8443dcc79b21..6a2ce8a227f0f4d24746cd0a40f916d744462129 100644 +--- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java +@@ -24,7 +24,7 @@ import net.minecraft.world.level.block.state.properties.ChestType; + import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + +-public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity { ++public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeEmitter, net.caffeinemc.mods.lithium.common.block.entity.SetBlockStateHandlingBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + private static final int EVENT_SET_OPEN_COUNT = 1; + private NonNullList items = NonNullList.withSize(27, ItemStack.EMPTY); + public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() { +@@ -127,6 +127,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + + public static void lidAnimateTick(Level level, BlockPos pos, BlockState state, ChestBlockEntity blockEntity) { + blockEntity.chestLidController.tickLid(); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.checkSleep(); // DivineMC - lithium: sleeping_block_entity + } + + public static void playSound(Level level, BlockPos pos, BlockState state, SoundEvent sound) { +@@ -148,6 +149,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + @Override + public boolean triggerEvent(int id, int type) { + if (id == 1) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.sleepingTicker != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + this.chestLidController.shouldBeOpen(type > 0); + return true; + } else { +@@ -177,6 +179,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + @Override + protected void setItems(NonNullList items) { + this.items = items; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$emitStackListReplaced(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -217,4 +220,51 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + Block block = state.getBlock(); + level.blockEvent(pos, block, 1, eventParam); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ private void checkSleep() { ++ if (this.getOpenNess(0.0F) == this.getOpenNess(1.0F)) { ++ this.lithium$startSleeping(); ++ } ++ } ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return this.tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return this.sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$handleSetBlockState() { ++ //Handle switching double / single chest state ++ this.lithium$emitRemoved(); ++ } ++ ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return items; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ items = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java +index 969ac280ae563e3412dba406ba68ceaa8a75d519..5c73f153fe62c7280b6d226668eb88fab3e5f7b8 100644 +--- a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java +@@ -22,7 +22,7 @@ import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + import org.slf4j.Logger; + +-public class ChiseledBookShelfBlockEntity extends BlockEntity implements Container { ++public class ChiseledBookShelfBlockEntity extends BlockEntity implements Container, net.caffeinemc.mods.lithium.api.inventory.LithiumTransferConditionInventory { // DivineMC - lithium: sleeping_block_entity + public static final int MAX_BOOKS_IN_STORAGE = 6; + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int DEFAULT_LAST_INTERACTED_SLOT = -1; +@@ -195,4 +195,11 @@ public class ChiseledBookShelfBlockEntity extends BlockEntity implements Contain + public void removeComponentsFromTag(ValueOutput output) { + output.discard("Items"); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ @Override ++ public boolean lithium$itemInsertionTestRequiresStackSize1() { ++ return true; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java +index 9ce4b5a3954eda08ef587cf95dec8ed119b7a598..43901533244da673475dfeae85af0ed50808e933 100644 +--- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java +@@ -22,7 +22,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + +-public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer { ++public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.SetChangedHandlingBlockEntity { // DivineMC - lithium: sleeping_block_entity + public static final int CONTAINER_WIDTH = 3; + public static final int CONTAINER_HEIGHT = 3; + public static final int CONTAINER_SIZE = 9; +@@ -169,6 +169,7 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme + } + }); + this.containerData.set(9, input.getIntOr("triggered", 0)); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.isSleeping() && this.level != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -278,10 +279,12 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme + level.setBlock(pos, state.setValue(CrafterBlock.CRAFTING, false), 3); + } + } ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && i < 0) crafter.checkSleep(); // DivineMC - lithium: sleeping_block_entity + } + + public void setCraftingTicksRemaining(int craftingTicksRemaining) { + this.craftingTicksRemaining = craftingTicksRemaining; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.isSleeping() && this.level != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + } + + public int getRedstoneSignal() { +@@ -300,4 +303,43 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme + private boolean slotCanBeDisabled(int slot) { + return slot > -1 && slot < 9 && this.items.get(slot).isEmpty(); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return this.tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ this.lithium$setSleepingTicker(null); ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return this.sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ private void checkSleep() { ++ if (this.craftingTicksRemaining == 0) { ++ this.lithium$startSleeping(); ++ } ++ } ++ ++ @Override ++ public void lithium$handleSetChanged() { ++ if (this.isSleeping() && this.level != null && !this.level.isClientSide) { ++ this.wakeUpNow(); ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +index ae52dc75335799e55e403e3d3f11e9f1d67e4305..a871aeaf81b11a8e68925420e7a0043e538d6467 100644 +--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +@@ -13,7 +13,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + +-public class DispenserBlockEntity extends RandomizableContainerBlockEntity { ++public class DispenserBlockEntity extends RandomizableContainerBlockEntity implements net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + public static final int CONTAINER_SIZE = 9; + private NonNullList items = NonNullList.withSize(9, ItemStack.EMPTY); + +@@ -134,10 +134,23 @@ public class DispenserBlockEntity extends RandomizableContainerBlockEntity { + @Override + protected void setItems(NonNullList items) { + this.items = items; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$emitStackListReplaced(); // DivineMC - lithium: sleeping_block_entity + } + + @Override + protected AbstractContainerMenu createMenu(int id, Inventory player) { + return new DispenserMenu(id, player, this); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return items; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ items = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +index 363d85c96bd3fb1a1945595df36e30bd6dd2fa4e..d1b8c9fbc8b219ef8d625a3968e49e36a58c7a93 100644 +--- a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +@@ -9,7 +9,7 @@ import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + +-public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity { ++public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity { // DivineMC - lithium: sleeping_block_entity + private final ChestLidController chestLidController = new ChestLidController(); + public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() { + @Override +@@ -57,11 +57,13 @@ public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity + + public static void lidAnimateTick(Level level, BlockPos pos, BlockState state, EnderChestBlockEntity blockEntity) { + blockEntity.chestLidController.tickLid(); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.checkSleep(); // DivineMC - lithium: sleeping_block_entity + } + + @Override + public boolean triggerEvent(int id, int type) { + if (id == 1) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.sleepingTicker != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + this.chestLidController.shouldBeOpen(type > 0); + return true; + } else { +@@ -95,4 +97,35 @@ public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity + public float getOpenNess(float partialTicks) { + return this.chestLidController.getOpenness(partialTicks); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return this.tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return this.sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ private void checkSleep() { ++ if (this.getOpenNess(0.0F) == this.getOpenNess(1.0F)) { ++ this.lithium$startSleeping(); ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index ff2035082b2945c43f126cbe04530c4c51863f93..38a750be8e8609f6d3c90bfee8482585c9145a73 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -28,7 +28,7 @@ import net.minecraft.world.level.storage.ValueInput; + import net.minecraft.world.level.storage.ValueOutput; + import net.minecraft.world.phys.AABB; + +-public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper { ++public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity, net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementListener, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener, net.caffeinemc.mods.lithium.common.hopper.UpdateReceiver, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker { // DivineMC - lithium: sleeping_block_entity + public static final int MOVE_ITEM_SPEED = 8; + public static final int HOPPER_CONTAINER_SIZE = 5; + private static final int[][] CACHED_SLOTS = new int[54][]; +@@ -118,6 +118,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Override + public void setBlockState(BlockState blockState) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.level != null && !this.level.isClientSide() && blockState.getValue(HopperBlock.FACING) != this.getBlockState().getValue(HopperBlock.FACING)) this.invalidateCachedData(); // DivineMC - lithium: sleeping_block_entity + super.setBlockState(blockState); + this.facing = blockState.getValue(HopperBlock.FACING); + } +@@ -136,6 +137,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + boolean result = tryMoveItems(level, pos, state, blockEntity, () -> { + return suckInItems(level, blockEntity); + }); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) blockEntity.checkSleepingConditions(); // DivineMC - lithium: sleeping_block_entity + if (!result && blockEntity.level.spigotConfig.hopperCheck > 1) { + blockEntity.setCooldown(blockEntity.level.spigotConfig.hopperCheck); + } +@@ -198,6 +200,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (flag) { + blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot + setChanged(level, pos, state); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && !blockEntity.isOnCooldown() && !blockEntity.isSleeping() && !state.getValue(HopperBlock.ENABLED)) blockEntity.lithium$startSleeping(); // DivineMC - lithium: sleeping_block_entity + return true; + } + } +@@ -374,6 +377,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + private static void applyCooldown(final Hopper hopper) { + if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { + blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); ++ blockEntity.skipNextSleepCheckAfterCooldown = true; // DivineMC - lithium: sleeping_block_entity + } + } + +@@ -417,11 +421,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + // Paper end - Perf: Optimize Hoppers + + private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) { +- Container attachedContainer = getAttachedContainer(level, pos, blockEntity); ++ Container attachedContainer = org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity ? blockEntity.getInsertInventory(level) : getAttachedContainer(level, pos, blockEntity); // DivineMC - lithium: sleeping_block_entity + if (attachedContainer == null) { + return false; + } else { + Direction opposite = blockEntity.facing.getOpposite(); ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) { ++ Boolean res = lithiumInsert(level, pos, blockEntity, attachedContainer); ++ if (res != null) { ++ return res; ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + if (isFullContainer(attachedContainer, opposite)) { + // DivineMC start - SparklyPaper: Allow throttling hopper checks if the target container is full + if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.hopperThrottleWhenFull && org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.hopperThrottleSkipTicks > 0) { +@@ -535,10 +547,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean suckInItems(Level level, Hopper hopper) { + BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ()); + BlockState blockState = level.getBlockState(blockPos); +- Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); ++ Container sourceContainer = org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity ? getExtractInventory(level, hopper, blockPos, blockState) : getSourceContainer(level, hopper, blockPos, blockState); // DivineMC - lithium: sleeping_block_entity + if (sourceContainer != null) { + Direction direction = Direction.DOWN; + skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) { ++ Boolean res = lithiumExtract(level, hopper, sourceContainer); ++ if (res != null) { ++ return res; ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + + for (int i : getSlots(sourceContainer, direction)) { + if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot +@@ -550,7 +570,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } else { + boolean flag = hopper.isGridAligned() && blockState.isCollisionShapeFullBlock(level, blockPos) && !blockState.is(BlockTags.DOES_NOT_BLOCK_HOPPERS); + if (!flag) { +- for (ItemEntity itemEntity : getItemsAtAndAbove(level, hopper)) { ++ for (ItemEntity itemEntity : org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity ? lithiumGetInputItemEntities(level, hopper) : getItemsAtAndAbove(level, hopper)) { // DivineMC - lithium: sleeping_block_entity + if (addItem(hopper, itemEntity)) { + return true; + } +@@ -720,7 +740,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + // CraftBukkit start + @Nullable + private static Container runHopperInventorySearchEvent( +- Container container, ++ @Nullable Container container, + org.bukkit.craftbukkit.block.CraftBlock hopper, + org.bukkit.craftbukkit.block.CraftBlock searchLocation, + org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType containerType +@@ -848,6 +868,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + public void setCooldown(int cooldownTime) { ++ // DivineMC start - lithium: sleeping_block_entity ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) { ++ if (cooldownTime == 7) { ++ if (this.tickedGameTime == Long.MAX_VALUE) { ++ this.sleepOnlyCurrentTick(); ++ } else { ++ this.wakeUpNow(); ++ } ++ } else if (cooldownTime > 0 && this.sleepingTicker != null) { ++ this.wakeUpNow(); ++ } ++ } ++ // DivineMC end - lithium: sleeping_block_entity + this.cooldownTime = cooldownTime; + } + +@@ -867,6 +900,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + @Override + protected void setItems(NonNullList items) { + this.items = items; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$emitStackListReplaced(); // DivineMC - lithium: sleeping_block_entity + } + + public static void entityInside(Level level, BlockPos pos, BlockState state, Entity entity, HopperBlockEntity blockEntity) { +@@ -881,4 +915,749 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + protected AbstractContainerMenu createMenu(int id, Inventory player) { + return new HopperMenu(id, player, this); + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ @Nullable private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ @Nullable private TickingBlockEntity sleepingTicker = null; ++ private long myModCountAtLastInsert, myModCountAtLastExtract, myModCountAtLastItemCollect; ++ private boolean skipNextSleepCheckAfterCooldown = false; ++ ++ private net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory insertionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.UNKNOWN; ++ private net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory extractionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.UNKNOWN; ++ ++ @Nullable ++ private Container insertBlockInventory, extractBlockInventory; ++ ++ @Nullable ++ private net.caffeinemc.mods.lithium.api.inventory.LithiumInventory insertInventory, extractInventory; ++ @Nullable ++ private net.caffeinemc.mods.lithium.common.hopper.LithiumStackList insertStackList, extractStackList; ++ private long insertStackListModCount, extractStackListModCount; ++ ++ @Nullable ++ private List collectItemEntityTracker; ++ private boolean collectItemEntityTrackerWasEmpty; ++ @Nullable ++ private AABB collectItemEntityBox; ++ private long collectItemEntityAttemptTime; ++ ++ @Nullable ++ private List extractInventoryEntityTracker; ++ @Nullable ++ private AABB extractInventoryEntityBox; ++ private long extractInventoryEntityFailedSearchTime; ++ ++ @Nullable ++ private List insertInventoryEntityTracker; ++ @Nullable ++ private AABB insertInventoryEntityBox; ++ private long insertInventoryEntityFailedSearchTime; ++ ++ private boolean shouldCheckSleep; ++ ++ private void checkSleepingConditions() { ++ if (this.cooldownTime > 0 || this.getLevel() == null || skipNextSleepCheckAfterCooldown) { ++ return; ++ } ++ if (isSleeping()) { ++ return; ++ } ++ if (!this.shouldCheckSleep) { ++ this.shouldCheckSleep = true; ++ return; ++ } ++ boolean listenToExtractTracker = false; ++ boolean listenToInsertTracker = false; ++ boolean listenToExtractEntities = false; ++ boolean listenToItemEntities = false; ++ boolean listenToInsertEntities = false; ++ ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList thisStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(this); ++ ++ if (this.extractionMode != net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE && thisStackList.getFullSlots() != thisStackList.size()) { ++ if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { ++ Container blockInventory = this.extractBlockInventory; ++ if (this.extractStackList != null && ++ blockInventory instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) { ++ if (!this.extractStackList.maybeSendsComparatorUpdatesOnFailedExtract() || (blockInventory instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracker comparatorTracker && !comparatorTracker.lithium$hasAnyComparatorNearby())) { ++ listenToExtractTracker = true; ++ } else { ++ return; ++ } ++ } else { ++ return; ++ } ++ } else if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { ++ BlockState hopperState = this.getBlockState(); ++ listenToExtractEntities = true; ++ ++ BlockPos blockPos = this.getBlockPos().above(); ++ BlockState blockState = this.getLevel().getBlockState(blockPos); ++ if (!blockState.isCollisionShapeFullBlock(this.getLevel(), blockPos) || blockState.is(BlockTags.DOES_NOT_BLOCK_HOPPERS)) { ++ listenToItemEntities = true; ++ } ++ } else { ++ return; ++ } ++ } ++ if (this.insertionMode != net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE && 0 < thisStackList.getOccupiedSlots()) { ++ if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { ++ Container blockInventory = this.insertBlockInventory; ++ if (this.insertStackList != null && blockInventory instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) { ++ listenToInsertTracker = true; ++ } else { ++ return; ++ } ++ } else if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { ++ BlockState hopperState = this.getBlockState(); ++ listenToInsertEntities = true; ++ } else { ++ return; ++ } ++ } ++ ++ if (listenToExtractTracker) { ++ ((net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) this.extractBlockInventory).listenForContentChangesOnce(this.extractStackList, this); ++ } ++ if (listenToInsertTracker) { ++ ((net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) this.insertBlockInventory).listenForContentChangesOnce(this.insertStackList, this); ++ } ++ if (listenToInsertEntities) { ++ if (this.insertInventoryEntityTracker == null || this.insertInventoryEntityTracker.isEmpty()) { ++ return; ++ } ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.listenToEntityMovementOnce(this, insertInventoryEntityTracker); ++ } ++ if (listenToExtractEntities) { ++ if (this.extractInventoryEntityTracker == null || this.extractInventoryEntityTracker.isEmpty()) { ++ return; ++ } ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.listenToEntityMovementOnce(this, extractInventoryEntityTracker); ++ } ++ if (listenToItemEntities) { ++ if (this.collectItemEntityTracker == null || this.collectItemEntityTracker.isEmpty()) { ++ return; ++ } ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.listenToEntityMovementOnce(this, collectItemEntityTracker); ++ } ++ ++ this.listenForContentChangesOnce(thisStackList, this); ++ lithium$startSleeping(); ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(@Nullable TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ @Override ++ public @Nullable TickingBlockEntity lithium$getSleepingTicker() { ++ return sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ this.lithium$setSleepingTicker(null); ++ } ++ ++ @Override ++ public @Nullable net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return tickWrapper; ++ } ++ ++ @Override ++ public boolean lithium$startSleeping() { ++ if (this.isSleeping()) { ++ return false; ++ } ++ ++ net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = this.lithium$getTickWrapper(); ++ if (tickWrapper != null) { ++ this.lithium$setSleepingTicker(tickWrapper.ticker); ++ tickWrapper.rebind(net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity.SLEEPING_BLOCK_ENTITY_TICKER); ++ ++ // Set the last tick time to max value, so other hoppers transferring into this hopper will set it to 7gt ++ // cooldown. Then when waking up, we make sure to not tick this hopper in the same gametick. ++ // This makes the observable hopper cooldown not be different from vanilla. ++ this.tickedGameTime = Long.MAX_VALUE; ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public void handleEntityMovement() { ++ this.wakeUpNow(); ++ } ++ ++ @Override ++ public NonNullList getInventoryLithium() { ++ return items; ++ } ++ ++ @Override ++ public void setInventoryLithium(NonNullList inventory) { ++ this.items = inventory; ++ } ++ ++ @Override ++ public void lithium$handleInventoryContentModified(Container inventory) { ++ wakeUpNow(); ++ } ++ ++ @Override ++ public void lithium$handleInventoryRemoved(Container inventory) { ++ wakeUpNow(); ++ if (inventory == this.insertBlockInventory) { ++ this.invalidateBlockInsertionData(); ++ } ++ if (inventory == this.extractBlockInventory) { ++ this.invalidateBlockExtractionData(); ++ } ++ if (inventory == this) { ++ this.invalidateCachedData(); ++ } ++ } ++ ++ @Override ++ public boolean lithium$handleComparatorAdded(Container inventory) { ++ if (inventory == this.extractBlockInventory) { ++ wakeUpNow(); ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public void lithium$invalidateCacheOnNeighborUpdate(boolean fromAbove) { ++ if (fromAbove) { ++ if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE) { ++ this.invalidateBlockExtractionData(); ++ } ++ } else { ++ if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE) { ++ this.invalidateBlockInsertionData(); ++ } ++ } ++ } ++ ++ @Override ++ public void lithium$invalidateCacheOnUndirectedNeighborUpdate() { ++ if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE) { ++ this.invalidateBlockExtractionData(); ++ } ++ if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE) { ++ this.invalidateBlockInsertionData(); ++ } ++ } ++ ++ @Override ++ public void lithium$invalidateCacheOnNeighborUpdate(Direction fromDirection) { ++ boolean fromAbove = fromDirection == Direction.UP; ++ if (fromAbove || this.getBlockState().getValue(HopperBlock.FACING) == fromDirection) { ++ this.lithium$invalidateCacheOnNeighborUpdate(fromAbove); ++ } ++ } ++ ++ private void invalidateBlockInsertionData() { ++ this.insertionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.UNKNOWN; ++ this.insertBlockInventory = null; ++ this.insertInventory = null; ++ this.insertStackList = null; ++ this.insertStackListModCount = 0; ++ ++ wakeUpNow(); ++ } ++ ++ private void invalidateCachedData() { ++ this.shouldCheckSleep = false; ++ this.invalidateInsertionData(); ++ this.invalidateExtractionData(); ++ } ++ ++ private void invalidateInsertionData() { ++ if (this.level instanceof net.minecraft.server.level.ServerLevel) { ++ if (this.insertInventoryEntityTracker != null) { ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.unregister(this.insertInventoryEntityTracker); ++ this.insertInventoryEntityTracker = null; ++ this.insertInventoryEntityBox = null; ++ this.insertInventoryEntityFailedSearchTime = 0L; ++ } ++ } ++ ++ if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { ++ assert this.insertBlockInventory != null; ++ ((net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) this.insertBlockInventory).stopListenForMajorInventoryChanges(this); ++ } ++ this.invalidateBlockInsertionData(); ++ } ++ ++ private void invalidateExtractionData() { ++ if (this.level instanceof net.minecraft.server.level.ServerLevel) { ++ if (this.extractInventoryEntityTracker != null) { ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.unregister(this.extractInventoryEntityTracker); ++ this.extractInventoryEntityTracker = null; ++ this.extractInventoryEntityBox = null; ++ this.extractInventoryEntityFailedSearchTime = 0L; ++ } ++ if (this.collectItemEntityTracker != null) { ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.unregister(this.collectItemEntityTracker); ++ this.collectItemEntityTracker = null; ++ this.collectItemEntityBox = null; ++ this.collectItemEntityTrackerWasEmpty = false; ++ } ++ } ++ if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { ++ assert this.extractBlockInventory != null; ++ ((net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) this.extractBlockInventory).stopListenForMajorInventoryChanges(this); ++ } ++ this.invalidateBlockExtractionData(); ++ } ++ ++ private void invalidateBlockExtractionData() { ++ this.extractionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.UNKNOWN; ++ this.extractBlockInventory = null; ++ this.extractInventory = null; ++ this.extractStackList = null; ++ this.extractStackListModCount = 0; ++ ++ this.wakeUpNow(); ++ } ++ ++ private static @Nullable Container getExtractInventory(Level world, Hopper hopper, BlockPos extractBlockPos, BlockState extractBlockState) { ++ if (!(hopper instanceof HopperBlockEntity hopperBlockEntity)) { ++ return getSourceContainer(world, hopper, extractBlockPos, extractBlockState); //Hopper Minecarts do not cache Inventories ++ } ++ ++ Container blockInventory = hopperBlockEntity.lithium$getExtractBlockInventory(world, extractBlockPos, extractBlockState); ++ if (blockInventory == null) { ++ blockInventory = hopperBlockEntity.lithium$getExtractEntityInventory(world); ++ } ++ return org.bukkit.event.inventory.HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0 ? blockInventory : runHopperInventorySearchEvent( ++ blockInventory, ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, hopperBlockEntity.getBlockPos()), ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, extractBlockPos), ++ org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType.SOURCE ++ ); ++ } ++ ++ public @Nullable Container lithium$getExtractBlockInventory(Level world, BlockPos extractBlockPos, BlockState extractBlockState) { ++ Container blockInventory = this.extractBlockInventory; ++ if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { ++ return null; ++ } else if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE) { ++ return blockInventory; ++ } else if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { ++ return blockInventory; ++ } else if (this.extractionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_ENTITY) { ++ BlockEntity blockEntity = (BlockEntity) java.util.Objects.requireNonNull(blockInventory); ++ BlockPos pos = blockEntity.getBlockPos(); ++ if (!(blockEntity).isRemoved() && pos.equals(extractBlockPos)) { ++ net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory; ++ if ((optimizedInventory = this.extractInventory) != null) { ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList insertInventoryStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory); ++ if (insertInventoryStackList == this.extractStackList) { ++ return optimizedInventory; ++ } else { ++ this.invalidateBlockExtractionData(); ++ } ++ } else { ++ return blockInventory; ++ } ++ } ++ } ++ ++ blockInventory = getBlockContainer(world, extractBlockPos, extractBlockState); ++ blockInventory = net.caffeinemc.mods.lithium.common.hopper.HopperHelper.replaceDoubleInventory(blockInventory); ++ this.cacheExtractBlockInventory(blockInventory); ++ return blockInventory; ++ } ++ ++ public @Nullable Container lithium$getInsertBlockInventory(Level world) { ++ Container blockInventory = this.insertBlockInventory; ++ if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { ++ return null; ++ } else if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE) { ++ return blockInventory; ++ } else if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { ++ return blockInventory; ++ } else if (this.insertionMode == net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_ENTITY) { ++ BlockEntity blockEntity = (BlockEntity) java.util.Objects.requireNonNull(blockInventory); ++ //Movable Block Entity compatibility - position comparison ++ BlockPos pos = blockEntity.getBlockPos(); ++ Direction direction = this.facing; ++ BlockPos transferPos = this.getBlockPos().relative(direction); ++ if (!(blockEntity).isRemoved() && ++ pos.equals(transferPos)) { ++ net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory; ++ if ((optimizedInventory = this.insertInventory) != null) { ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList insertInventoryStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory); ++ //This check is necessary as sometimes the stacklist is silently replaced (e.g. command making furnace read inventory from nbt) ++ if (insertInventoryStackList == this.insertStackList) { ++ return optimizedInventory; ++ } else { ++ this.invalidateBlockInsertionData(); ++ } ++ } else { ++ return blockInventory; ++ } ++ } ++ } ++ ++ //No Cached Inventory: Get like vanilla and cache ++ Direction direction = this.facing; ++ BlockPos insertBlockPos = this.getBlockPos().relative(direction); ++ BlockState blockState = world.getBlockState(insertBlockPos); ++ blockInventory = getBlockContainer(world, insertBlockPos, blockState); ++ blockInventory = net.caffeinemc.mods.lithium.common.hopper.HopperHelper.replaceDoubleInventory(blockInventory); ++ this.cacheInsertBlockInventory(blockInventory); ++ return blockInventory; ++ } ++ ++ public @Nullable Container getInsertInventory(Level world) { ++ Container blockInventory = getInsertInventory0(world); ++ return org.bukkit.event.inventory.HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0 ? blockInventory : runHopperInventorySearchEvent( ++ blockInventory, ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, this.getBlockPos()), ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, this.getBlockPos().relative(this.facing)), ++ org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType.DESTINATION ++ ); ++ } ++ ++ public @Nullable Container getInsertInventory0(Level world) { ++ Container blockInventory = this.lithium$getInsertBlockInventory(world); ++ if (blockInventory != null) { ++ return blockInventory; ++ } ++ ++ if (this.insertInventoryEntityTracker == null) { ++ this.initInsertInventoryTracker(world); ++ } ++ if (net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.isUnchangedSince(this.insertInventoryEntityFailedSearchTime, this.insertInventoryEntityTracker)) { ++ this.insertInventoryEntityFailedSearchTime = this.tickedGameTime; ++ return null; ++ } ++ this.insertInventoryEntityFailedSearchTime = Long.MIN_VALUE; ++ this.shouldCheckSleep = false; ++ ++ List inventoryEntities = net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker.getEntities(world, this.insertInventoryEntityBox); ++ if (inventoryEntities.isEmpty()) { ++ this.insertInventoryEntityFailedSearchTime = this.tickedGameTime; ++ //Remember failed entity search timestamp. This allows shortcutting if no entity movement happens. ++ return null; ++ } ++ Container inventory = inventoryEntities.get(world.random.nextInt(inventoryEntities.size())); ++ if (inventory instanceof net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory) { ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList insertInventoryStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory); ++ if (inventory != this.insertInventory || this.insertStackList != insertInventoryStackList) { ++ this.cacheInsertLithiumInventory(optimizedInventory); ++ } ++ } ++ ++ return inventory; ++ } ++ ++ private void initCollectItemEntityTracker() { ++ assert this.level instanceof net.minecraft.server.level.ServerLevel; ++ AABB inputBox = this.getSuckAabb().move(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); ++ this.collectItemEntityBox = inputBox; ++ this.collectItemEntityTracker = ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker.registerAt( ++ (net.minecraft.server.level.ServerLevel) this.level, ++ inputBox ++ ); ++ this.collectItemEntityAttemptTime = Long.MIN_VALUE; ++ } ++ ++ private void initExtractInventoryTracker(Level world) { ++ assert world instanceof net.minecraft.server.level.ServerLevel; ++ BlockPos pos = this.worldPosition.relative(Direction.UP); ++ this.extractInventoryEntityBox = new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1); ++ this.extractInventoryEntityTracker = ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker.registerAt( ++ (net.minecraft.server.level.ServerLevel) this.level, ++ this.extractInventoryEntityBox ++ ); ++ this.extractInventoryEntityFailedSearchTime = Long.MIN_VALUE; ++ } ++ ++ private void initInsertInventoryTracker(Level world) { ++ assert world instanceof net.minecraft.server.level.ServerLevel; ++ Direction direction = this.facing; ++ BlockPos pos = this.worldPosition.relative(direction); ++ this.insertInventoryEntityBox = new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1); ++ this.insertInventoryEntityTracker = ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker.registerAt( ++ (net.minecraft.server.level.ServerLevel) this.level, ++ this.insertInventoryEntityBox ++ ); ++ this.insertInventoryEntityFailedSearchTime = Long.MIN_VALUE; ++ } ++ ++ private @Nullable Container lithium$getExtractEntityInventory(Level world) { ++ if (this.extractInventoryEntityTracker == null) { ++ this.initExtractInventoryTracker(world); ++ } ++ if (net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.isUnchangedSince(this.extractInventoryEntityFailedSearchTime, this.extractInventoryEntityTracker)) { ++ this.extractInventoryEntityFailedSearchTime = this.tickedGameTime; ++ return null; ++ } ++ this.extractInventoryEntityFailedSearchTime = Long.MIN_VALUE; ++ this.shouldCheckSleep = false; ++ ++ List inventoryEntities = net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker.getEntities(world, this.extractInventoryEntityBox); ++ if (inventoryEntities.isEmpty()) { ++ this.extractInventoryEntityFailedSearchTime = this.tickedGameTime; ++ //only set unchanged when no entity present. this allows shortcutting this case ++ //shortcutting the entity present case requires checking its change counter ++ return null; ++ } ++ Container inventory = inventoryEntities.get(world.random.nextInt(inventoryEntities.size())); ++ if (inventory instanceof net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory) { ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList extractInventoryStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory); ++ if (inventory != this.extractInventory || this.extractStackList != extractInventoryStackList) { ++ //not caching the inventory (NO_BLOCK_INVENTORY prevents it) ++ //make change counting on the entity inventory possible, without caching it as block inventory ++ this.cacheExtractLithiumInventory(optimizedInventory); ++ } ++ } ++ return inventory; ++ } ++ ++ /** ++ * Makes this hopper remember the given inventory. ++ * ++ * @param insertInventory Block inventory / Blockentity inventory to be remembered ++ */ ++ private void cacheInsertBlockInventory(@Nullable Container insertInventory) { ++ assert !(insertInventory instanceof Entity); ++ if (insertInventory instanceof net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory) { ++ this.cacheInsertLithiumInventory(optimizedInventory); ++ } else { ++ this.insertInventory = null; ++ this.insertStackList = null; ++ this.insertStackListModCount = 0; ++ } ++ ++ if (insertInventory instanceof BlockEntity || insertInventory instanceof net.minecraft.world.CompoundContainer) { ++ this.insertBlockInventory = insertInventory; ++ if (insertInventory instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) { ++ this.insertionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY; ++ ((net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) insertInventory).listenForMajorInventoryChanges(this); ++ } else { ++ this.insertionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_ENTITY; ++ } ++ } else { ++ if (insertInventory == null) { ++ this.insertBlockInventory = null; ++ this.insertionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY; ++ } else { ++ this.insertBlockInventory = insertInventory; ++ this.insertionMode = insertInventory instanceof net.caffeinemc.mods.lithium.common.hopper.BlockStateOnlyInventory ? net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE : net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.UNKNOWN; ++ } ++ } ++ } ++ ++ private void cacheInsertLithiumInventory(net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory) { ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList insertInventoryStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory); ++ this.insertInventory = optimizedInventory; ++ this.insertStackList = insertInventoryStackList; ++ this.insertStackListModCount = insertInventoryStackList.getModCount() - 1; ++ } ++ ++ private void cacheExtractLithiumInventory(net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory) { ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList extractInventoryStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory); ++ this.extractInventory = optimizedInventory; ++ this.extractStackList = extractInventoryStackList; ++ this.extractStackListModCount = extractInventoryStackList.getModCount() - 1; ++ } ++ ++ /** ++ * Makes this hopper remember the given inventory. ++ * ++ * @param extractInventory Block inventory / Blockentity inventory to be remembered ++ */ ++ private void cacheExtractBlockInventory(@Nullable Container extractInventory) { ++ assert !(extractInventory instanceof Entity); ++ if (extractInventory instanceof net.caffeinemc.mods.lithium.api.inventory.LithiumInventory optimizedInventory) { ++ this.cacheExtractLithiumInventory(optimizedInventory); ++ } else { ++ this.extractInventory = null; ++ this.extractStackList = null; ++ this.extractStackListModCount = 0; ++ } ++ ++ if (extractInventory instanceof BlockEntity || extractInventory instanceof net.minecraft.world.CompoundContainer) { ++ this.extractBlockInventory = extractInventory; ++ if (extractInventory instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) { ++ this.extractionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY; ++ ((net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker) extractInventory).listenForMajorInventoryChanges(this); ++ } else { ++ this.extractionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_ENTITY; ++ } ++ } else { ++ if (extractInventory == null) { ++ this.extractBlockInventory = null; ++ this.extractionMode = net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY; ++ } else { ++ this.extractBlockInventory = extractInventory; ++ this.extractionMode = extractInventory instanceof net.caffeinemc.mods.lithium.common.hopper.BlockStateOnlyInventory ? net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.BLOCK_STATE : net.caffeinemc.mods.lithium.common.hopper.HopperCachingState.BlockInventory.UNKNOWN; ++ } ++ } ++ } ++ ++ private static List lithiumGetInputItemEntities(Level world, Hopper hopper) { ++ if (!(hopper instanceof HopperBlockEntity hopperBlockEntity)) { ++ return getItemsAtAndAbove(world, hopper); //optimizations not implemented for hopper minecarts ++ } ++ ++ if (hopperBlockEntity.collectItemEntityTracker == null) { ++ hopperBlockEntity.initCollectItemEntityTracker(); ++ } ++ ++ long modCount = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(hopperBlockEntity).getModCount(); ++ ++ if ((hopperBlockEntity.collectItemEntityTrackerWasEmpty || hopperBlockEntity.myModCountAtLastItemCollect == modCount) && ++ net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker.isUnchangedSince(hopperBlockEntity.collectItemEntityAttemptTime, hopperBlockEntity.collectItemEntityTracker)) { ++ hopperBlockEntity.collectItemEntityAttemptTime = hopperBlockEntity.tickedGameTime; ++ return java.util.Collections.emptyList(); ++ } ++ ++ hopperBlockEntity.myModCountAtLastItemCollect = modCount; ++ hopperBlockEntity.shouldCheckSleep = false; ++ ++ List itemEntities = net.caffeinemc.mods.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker.getEntities(world, hopperBlockEntity.collectItemEntityBox); ++ hopperBlockEntity.collectItemEntityAttemptTime = hopperBlockEntity.tickedGameTime; ++ hopperBlockEntity.collectItemEntityTrackerWasEmpty = itemEntities.isEmpty(); ++ //set unchanged so that if this extract fails and there is no other change to hoppers or items, extracting ++ // items can be skipped. ++ return itemEntities; ++ } ++ ++ private static @Nullable Boolean lithiumInsert(Level world, BlockPos pos, HopperBlockEntity hopperBlockEntity, @Nullable Container insertInventory) { ++ if (insertInventory == null || hopperBlockEntity instanceof net.minecraft.world.WorldlyContainer) { ++ //call the vanilla code to allow other mods inject features ++ //e.g. carpet mod allows hoppers to insert items into wool blocks ++ return null; ++ } ++ ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList hopperStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(hopperBlockEntity); ++ if (hopperBlockEntity.insertInventory == insertInventory && hopperStackList.getModCount() == hopperBlockEntity.myModCountAtLastInsert) { ++ if (hopperBlockEntity.insertStackList != null && hopperBlockEntity.insertStackList.getModCount() == hopperBlockEntity.insertStackListModCount) { ++ //ComparatorUpdatePattern.NO_UPDATE.apply(hopperBlockEntity, hopperStackList); //commented because it's a noop, Hoppers do not send useless comparator updates ++ return false; ++ } ++ } ++ ++ boolean insertInventoryWasEmptyHopperNotDisabled = insertInventory instanceof HopperBlockEntity hopperInv && ++ !hopperInv.isOnCustomCooldown() && hopperBlockEntity.insertStackList != null && ++ hopperBlockEntity.insertStackList.getOccupiedSlots() == 0; ++ ++ boolean insertInventoryHandlesModdedCooldown = ++ insertInventory.canReceiveTransferCooldown() && ++ hopperBlockEntity.insertStackList != null ? ++ hopperBlockEntity.insertStackList.getOccupiedSlots() == 0 : ++ insertInventory.isEmpty(); ++ ++ skipPushModeEventFire = skipHopperEvents; ++ //noinspection ConstantConditions ++ if (!(hopperBlockEntity.insertInventory == insertInventory && hopperBlockEntity.insertStackList.getFullSlots() == hopperBlockEntity.insertStackList.size())) { ++ Direction fromDirection = hopperBlockEntity.facing.getOpposite(); ++ int size = hopperStackList.size(); ++ for (int i = 0; i < size; ++i) { ++ ItemStack transferStack = hopperStackList.get(i); ++ if (!transferStack.isEmpty()) { ++ if (!skipPushModeEventFire && canTakeItemFromContainer(insertInventory, hopperBlockEntity, transferStack, i, Direction.DOWN)) { ++ transferStack = callPushMoveEvent(insertInventory, transferStack, hopperBlockEntity); ++ if (transferStack == null) { // cancelled ++ break; ++ } ++ } ++ boolean transferSuccess = net.caffeinemc.mods.lithium.common.hopper.HopperHelper.tryMoveSingleItem(insertInventory, transferStack, fromDirection); ++ if (transferSuccess) { ++ if (insertInventoryWasEmptyHopperNotDisabled) { ++ HopperBlockEntity receivingHopper = (HopperBlockEntity) insertInventory; ++ int k = 8; ++ if (receivingHopper.tickedGameTime >= hopperBlockEntity.tickedGameTime) { ++ k = 7; ++ } ++ receivingHopper.setCooldown(k); ++ } ++ if (insertInventoryHandlesModdedCooldown) { ++ insertInventory.setTransferCooldown(hopperBlockEntity.tickedGameTime); ++ } ++ insertInventory.setChanged(); ++ return true; ++ } ++ } ++ } ++ } ++ hopperBlockEntity.myModCountAtLastInsert = hopperStackList.getModCount(); ++ if (hopperBlockEntity.insertStackList != null) { ++ hopperBlockEntity.insertStackListModCount = hopperBlockEntity.insertStackList.getModCount(); ++ } ++ return false; ++ } ++ ++ private static @Nullable Boolean lithiumExtract(Level world, Hopper to, Container from) { ++ if (!(to instanceof HopperBlockEntity hopperBlockEntity)) { ++ return null; //optimizations not implemented for hopper minecarts ++ } ++ ++ if (from != hopperBlockEntity.extractInventory || hopperBlockEntity.extractStackList == null) { ++ return null; //from inventory is not an optimized inventory, vanilla fallback ++ } ++ ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList hopperStackList = net.caffeinemc.mods.lithium.common.hopper.InventoryHelper.getLithiumStackList(hopperBlockEntity); ++ net.caffeinemc.mods.lithium.common.hopper.LithiumStackList fromStackList = hopperBlockEntity.extractStackList; ++ ++ if (hopperStackList.getModCount() == hopperBlockEntity.myModCountAtLastExtract) { ++ if (fromStackList.getModCount() == hopperBlockEntity.extractStackListModCount) { ++ if (!(from instanceof net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracker comparatorTracker) || comparatorTracker.lithium$hasAnyComparatorNearby()) { ++ //noinspection CollectionAddedToSelf ++ fromStackList.runComparatorUpdatePatternOnFailedExtract(fromStackList, from); ++ } ++ return false; ++ } ++ } ++ ++ int[] availableSlots = from instanceof WorldlyContainer ? ((WorldlyContainer) from).getSlotsForFace(Direction.DOWN) : null; ++ int fromSize = availableSlots != null ? availableSlots.length : from.getContainerSize(); ++ for (int i = 0; i < fromSize; i++) { ++ int fromSlot = availableSlots != null ? availableSlots[i] : i; ++ ItemStack itemStack = fromStackList.get(fromSlot); ++ if (!itemStack.isEmpty() && canTakeItemFromContainer(to, from, itemStack, fromSlot, Direction.DOWN)) { ++ if (!skipPullModeEventFire) { ++ itemStack = callPullMoveEvent(to, from, itemStack); ++ if (itemStack == null) { // cancelled ++ return true; ++ } ++ } ++ //calling removeStack is necessary due to its side effects (markDirty in LootableContainerBlockEntity) ++ ItemStack takenItem = from.removeItem(fromSlot, 1); ++ assert !takenItem.isEmpty(); ++ boolean transferSuccess = net.caffeinemc.mods.lithium.common.hopper.HopperHelper.tryMoveSingleItem(to, takenItem, null); ++ if (transferSuccess) { ++ to.setChanged(); ++ from.setChanged(); ++ return true; ++ } ++ //put the item back similar to vanilla ++ ItemStack restoredStack = fromStackList.get(fromSlot); ++ if (restoredStack.isEmpty()) { ++ restoredStack = takenItem; ++ } else { ++ restoredStack.grow(1); ++ } ++ //calling setStack is necessary due to its side effects (markDirty in LootableContainerBlockEntity) ++ from.setItem(fromSlot, restoredStack); ++ } ++ } ++ hopperBlockEntity.myModCountAtLastExtract = hopperStackList.getModCount(); ++ if (fromStackList != null) { ++ hopperBlockEntity.extractStackListModCount = fromStackList.getModCount(); ++ } ++ return false; ++ } + } +diff --git a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +index ebea67223ce1d350087c73dff0cc3fe6d7b47ca0..cfc25b24a389d8d2774f534e31c43349d1e85bd5 100644 +--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +@@ -32,7 +32,7 @@ import net.minecraft.world.level.storage.ValueOutput; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; + +-public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer { ++public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer, net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity, net.caffeinemc.mods.lithium.api.inventory.LithiumInventory { // DivineMC - lithium: sleeping_block_entity + public static final int COLUMNS = 9; + public static final int ROWS = 3; + public static final int CONTAINER_SIZE = 27; +@@ -134,6 +134,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl + doNeighborUpdates(level, pos, state); + } + } ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.animationStatus == ShulkerBoxBlockEntity.AnimationStatus.CLOSED && this.progressOld == 0.0f && this.progress == 0.0f) this.lithium$startSleeping(); // DivineMC - lithium: sleeping_block_entity + } + + public ShulkerBoxBlockEntity.AnimationStatus getAnimationStatus() { +@@ -174,6 +175,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl + + @Override + public boolean triggerEvent(int id, int type) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && this.sleepingTicker != null) this.wakeUpNow(); // DivineMC - lithium: sleeping_block_entity + if (id == 1) { + this.openCount = type; + if (type == 0) { +@@ -265,6 +267,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl + @Override + protected void setItems(NonNullList items) { + this.itemStacks = items; ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$emitStackListReplaced(); // DivineMC - lithium: sleeping_block_entity + } + + @Override +@@ -306,4 +309,39 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl + OPENED, + CLOSING; + } ++ ++ // DivineMC start - lithium: sleeping_block_entity ++ private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; ++ private TickingBlockEntity sleepingTicker = null; ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { ++ return tickWrapper; ++ } ++ ++ @Override ++ public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { ++ this.tickWrapper = tickWrapper; ++ } ++ ++ @Override ++ public TickingBlockEntity lithium$getSleepingTicker() { ++ return sleepingTicker; ++ } ++ ++ @Override ++ public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { ++ this.sleepingTicker = sleepingTicker; ++ } ++ ++ @Override ++ public net.minecraft.core.NonNullList getInventoryLithium() { ++ return itemStacks; ++ } ++ ++ @Override ++ public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { ++ itemStacks = inventory; ++ } ++ // DivineMC end - lithium: sleeping_block_entity + } +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index e044830439fe9821ab3f62695d318a6321b8a266..966827ce890f1ac490a3e1f6096ec41aae6db47f 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -85,7 +85,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + +-public abstract class BlockBehaviour implements FeatureElement { ++public abstract class BlockBehaviour implements FeatureElement, net.caffeinemc.mods.lithium.common.block.entity.ShapeUpdateHandlingBlockBehaviour { // DivineMC - lithium: sleeping_block_entity + protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{ + Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP + }; +@@ -157,6 +157,7 @@ public abstract class BlockBehaviour implements FeatureElement { + BlockState neighborState, + RandomSource random + ) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity) this.lithium$handleShapeUpdate(level, state, pos, neighborPos, neighborState); // DivineMC - lithium: sleeping_block_entity + return state; + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 9cd2a05683d879f56b6e62dfd49ac30341deeb06..aefe2b36becfe248effd39bbcd91169ee09beb39 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -891,12 +891,14 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + (pos, ticker1) -> { + TickingBlockEntity tickingBlockEntity = this.createTicker(blockEntity, ticker); + if (ticker1 != null) { ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && blockEntity instanceof net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity sleepingBlockEntity) sleepingBlockEntity.lithium$setTickWrapper(ticker1); // DivineMC - lithium: sleeping_block_entity + ticker1.rebind(tickingBlockEntity); + return (LevelChunk.RebindableTickingBlockEntityWrapper)ticker1; + } else if (this.isInLevel()) { + LevelChunk.RebindableTickingBlockEntityWrapper rebindableTickingBlockEntityWrapper = new LevelChunk.RebindableTickingBlockEntityWrapper( + tickingBlockEntity + ); ++ if (org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.sleepingBlockEntity && blockEntity instanceof net.caffeinemc.mods.lithium.common.block.entity.SleepingBlockEntity sleepingBlockEntity) sleepingBlockEntity.lithium$setTickWrapper(rebindableTickingBlockEntityWrapper); // DivineMC - lithium: sleeping_block_entity + this.level.addBlockEntityTicker(rebindableTickingBlockEntityWrapper); + return rebindableTickingBlockEntityWrapper; + } else { diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumCooldownReceivingInventory.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumCooldownReceivingInventory.java new file mode 100644 index 0000000..d1a39f7 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumCooldownReceivingInventory.java @@ -0,0 +1,30 @@ +package net.caffeinemc.mods.lithium.api.inventory; + +public interface LithiumCooldownReceivingInventory { + + /** + * To be implemented by modded inventories that want to receive hopper-like transfer cooldowns from lithium's (!) + * item transfers. Hopper-like transfer cooldown means a cooldown that is only set if the hopper was empty before + * the transfer. + * NOTE: Lithium does not replace all of vanilla's item transfers. Mod authors still need to implement + * their own hooks for vanilla code even when they require users to install Lithium. + * + * @param currentTime tick time of the item transfer. + */ + default void setTransferCooldown(long currentTime) { + } + + + /** + * To be implemented by modded inventories that want to receive hopper-like transfer cooldowns from lithium's (!) + * item transfers. Hopper-like transfer cooldown means a cooldown that is only set if the hopper was empty before + * the transfer. + * NOTE: Lithium does not replace all of vanilla's item transfers. Mod authors still need to implement + * their own hooks for vanilla code even when they require users to install Lithium. + * + * @return Whether this inventory wants to receive transfer cooldowns from lithium's code + */ + default boolean canReceiveTransferCooldown() { + return false; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumDefaultedList.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumDefaultedList.java new file mode 100644 index 0000000..1bc9cd3 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumDefaultedList.java @@ -0,0 +1,16 @@ +package net.caffeinemc.mods.lithium.api.inventory; + +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; + +public interface LithiumDefaultedList { + /** + * Call this method when the behavior of + * {@link net.minecraft.world.Container#canPlaceItem(int, ItemStack)} + * {@link net.minecraft.world.WorldlyContainer#canPlaceItemThroughFace(int, ItemStack, Direction)} + * {@link net.minecraft.world.WorldlyContainer#canTakeItemThroughFace(int, ItemStack, Direction)} + * or similar functionality changed. + * This method will not need to be called if this change in behavior is triggered by a change of the stack list contents. + */ + void changedInteractionConditions(); +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumInventory.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumInventory.java new file mode 100644 index 0000000..10ee502 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumInventory.java @@ -0,0 +1,61 @@ +package net.caffeinemc.mods.lithium.api.inventory; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; +import net.minecraft.world.entity.vehicle.ContainerEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; + +/** + * Provides the ability for mods to allow Lithium's hopper optimizations to access their inventories' for item transfers. + * This exists because Lithium's optimized hopper logic will only interact with inventories more efficiently than + * vanilla if the stack list can be directly accessed and replaced with Lithium's custom stack list. + * It is not required to implement this interface, but doing so will allow the mod's inventories to benefit from + * Lithium's optimizations. + *

+ * This interface should be implemented by your {@link Container} or + * {@link net.minecraft.world.WorldlyContainer} type to access the stack list. + *

+ * An inventory must not extend {@link net.minecraft.world.level.block.entity.BlockEntity} if it has a supporting block that + * implements {@link ContainerEntity}. + *

+ * The hopper interaction behavior of a LithiumInventory should only change if the content of the inventory + * stack list also changes. For example, an inventory which only accepts an item if it already contains an item of the + * same type would work fine (changing the acceptance condition only happens when changing the inventory contents here). + * However, an inventory which accepts an item only if a certain block is near its position will need to signal this + * change to hoppers by calling {@link LithiumDefaultedList#changedInteractionConditions()}. + * + * @author 2No2Name + */ +public interface LithiumInventory extends Container { + + /** + * Getter for the inventory stack list of this inventory. + * + * @return inventory stack list + */ + NonNullList getInventoryLithium(); + + /** + * Setter for the inventory stack list of this inventory. + * Used to replace the stack list with Lithium's custom stack list. + * + * @param inventory inventory stack list + */ + void setInventoryLithium(NonNullList inventory); + + /** + * Generates the loot like a hopper access would do in vanilla. + *

+ * If a modded inventory has custom loot generation code, it will be required to override this + * loot generation method. Otherwise, its loot may be generated too late. + */ + default void generateLootLithium() { + if (this instanceof RandomizableContainerBlockEntity) { + ((RandomizableContainerBlockEntity) this).unpackLootTable(null); + } + if (this instanceof ContainerEntity) { + ((ContainerEntity) this).unpackChestVehicleLootTable(null); + } + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumTransferConditionInventory.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumTransferConditionInventory.java new file mode 100644 index 0000000..6aab68b --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/api/inventory/LithiumTransferConditionInventory.java @@ -0,0 +1,22 @@ +package net.caffeinemc.mods.lithium.api.inventory; + +public interface LithiumTransferConditionInventory { + + /** + * Implement this method to signal that the inventory requires a stack size of 1 for item insertion tests. + * Lithium's hopper optimization transfers a single item, but to avoid excessive copying of item stacks, it passes + * the original stack to the inventory's insertion test. If the inventory requires a stack size of 1 for this test, + * the stack should be copied. However, lithium cannot detect whether the copy is necessary and this method is meant + * to signal this requirement. When the method is not implemented even though it is required, Lithium's hopper + * optimizations may not transfer items correctly to this inventory. + *

+ * The only vanilla inventory that requires this is the Chiseled Bookshelf. Mods with such special inventories + * should implement this method in the inventories' class. + * (It is not required to implement this interface, just the method is enough.) + * + * @return whether the inventory requires a stack size of 1 for item insertion tests + */ + default boolean lithium$itemInsertionTestRequiresStackSize1() { + return false; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SetBlockStateHandlingBlockEntity.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SetBlockStateHandlingBlockEntity.java new file mode 100644 index 0000000..73b5ea3 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SetBlockStateHandlingBlockEntity.java @@ -0,0 +1,6 @@ +package net.caffeinemc.mods.lithium.common.block.entity; + +public interface SetBlockStateHandlingBlockEntity { + default void lithium$handleSetBlockState() { + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SetChangedHandlingBlockEntity.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SetChangedHandlingBlockEntity.java new file mode 100644 index 0000000..b14ebe3 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SetChangedHandlingBlockEntity.java @@ -0,0 +1,6 @@ +package net.caffeinemc.mods.lithium.common.block.entity; + +public interface SetChangedHandlingBlockEntity { + default void lithium$handleSetChanged() { + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/ShapeUpdateHandlingBlockBehaviour.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/ShapeUpdateHandlingBlockBehaviour.java new file mode 100644 index 0000000..a2875de --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/ShapeUpdateHandlingBlockBehaviour.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.lithium.common.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; + +public interface ShapeUpdateHandlingBlockBehaviour { + default void lithium$handleShapeUpdate(LevelReader world, BlockState myBlockState, BlockPos myPos, BlockPos posFrom, BlockState newState) { } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SleepUntilTimeBlockEntityTickInvoker.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SleepUntilTimeBlockEntityTickInvoker.java new file mode 100644 index 0000000..935e9fd --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SleepUntilTimeBlockEntityTickInvoker.java @@ -0,0 +1,34 @@ +package net.caffeinemc.mods.lithium.common.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.TickingBlockEntity; + +public record SleepUntilTimeBlockEntityTickInvoker(BlockEntity sleepingBlockEntity, long sleepUntilTickExclusive, TickingBlockEntity delegate) implements TickingBlockEntity { + @Override + public void tick() { + //noinspection ConstantConditions + long tickTime = this.sleepingBlockEntity.getLevel().getGameTime(); + if (tickTime >= this.sleepUntilTickExclusive) { + ((SleepingBlockEntity) this.sleepingBlockEntity).setTicker(this.delegate); + this.delegate.tick(); + } + } + + @Override + public boolean isRemoved() { + return this.sleepingBlockEntity.isRemoved(); + } + + @Override + public BlockPos getPos() { + return this.sleepingBlockEntity.getBlockPos(); + } + + @Override + public String getType() { + //noinspection ConstantConditions + return BlockEntityType.getKey(this.sleepingBlockEntity.getType()).toString(); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SleepingBlockEntity.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SleepingBlockEntity.java new file mode 100644 index 0000000..ed01911 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/SleepingBlockEntity.java @@ -0,0 +1,80 @@ +package net.caffeinemc.mods.lithium.common.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.TickingBlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; + +public interface SleepingBlockEntity { + TickingBlockEntity SLEEPING_BLOCK_ENTITY_TICKER = new TickingBlockEntity() { + public void tick() { + } + + public boolean isRemoved() { + return false; + } + + public BlockPos getPos() { + return null; + } + + public String getType() { + return ""; + } + }; + + LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper(); + + void lithium$setTickWrapper(LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper); + + TickingBlockEntity lithium$getSleepingTicker(); + + void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker); + + default boolean lithium$startSleeping() { + if (this.isSleeping()) { + return false; + } + + LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = this.lithium$getTickWrapper(); + if (tickWrapper == null) { + return false; + } + this.lithium$setSleepingTicker(tickWrapper.ticker); + tickWrapper.rebind(SleepingBlockEntity.SLEEPING_BLOCK_ENTITY_TICKER); + return true; + } + + default void sleepOnlyCurrentTick() { + TickingBlockEntity sleepingTicker = this.lithium$getSleepingTicker(); + LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = this.lithium$getTickWrapper(); + if (sleepingTicker == null) { + sleepingTicker = tickWrapper.ticker; + } + Level world = ((BlockEntity) this).getLevel(); + tickWrapper.rebind(new SleepUntilTimeBlockEntityTickInvoker((BlockEntity) this, world.getGameTime() + 1, sleepingTicker)); + this.lithium$setSleepingTicker(null); + } + + default void wakeUpNow() { + TickingBlockEntity sleepingTicker = this.lithium$getSleepingTicker(); + if (sleepingTicker == null) { + return; + } + this.setTicker(sleepingTicker); + this.lithium$setSleepingTicker(null); + } + + default void setTicker(TickingBlockEntity delegate) { + LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = this.lithium$getTickWrapper(); + if (tickWrapper == null) { + return; + } + tickWrapper.rebind(delegate); + } + + default boolean isSleeping() { + return this.lithium$getSleepingTicker() != null; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeEmitter.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeEmitter.java new file mode 100644 index 0000000..840c5c6 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeEmitter.java @@ -0,0 +1,30 @@ +package net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking; + +import net.caffeinemc.mods.lithium.common.hopper.LithiumStackList; + +/** + * Interface for Objects that can emit various inventory change events. This does not mean that the inventory + * creates those events - this requirement is met by InventoryChangeTracker. This distinction is needed due + * to modded inventories being able to inherit from LockableContainerBlockEntity, which does not guarantee the creation + * of the required events but implements most of the inventory change listening. + * The forwarding methods below are helpers, it is not recommended to call them from outside InventoryChangeTracker.java + */ +public interface InventoryChangeEmitter { + void lithium$emitStackListReplaced(); + + void lithium$emitRemoved(); + + void lithium$emitContentModified(); + + void lithium$emitFirstComparatorAdded(); + + void lithium$forwardContentChangeOnce(InventoryChangeListener inventoryChangeListener, LithiumStackList stackList, InventoryChangeTracker thisTracker); + + void lithium$forwardMajorInventoryChanges(InventoryChangeListener inventoryChangeListener); + + void lithium$stopForwardingMajorInventoryChanges(InventoryChangeListener inventoryChangeListener); + + default void emitCallbackReplaced() { + this.lithium$emitRemoved(); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeListener.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeListener.java new file mode 100644 index 0000000..2f821ea --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeListener.java @@ -0,0 +1,15 @@ +package net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking; + +import net.minecraft.world.Container; + +public interface InventoryChangeListener { + default void handleStackListReplaced(Container inventory) { + this.lithium$handleInventoryRemoved(inventory); + } + + void lithium$handleInventoryContentModified(Container inventory); + + void lithium$handleInventoryRemoved(Container inventory); + + boolean lithium$handleComparatorAdded(Container inventory); +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeTracker.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeTracker.java new file mode 100644 index 0000000..1e06298 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_change_tracking/InventoryChangeTracker.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking; + +import net.caffeinemc.mods.lithium.common.hopper.LithiumStackList; + +public interface InventoryChangeTracker extends InventoryChangeEmitter { + default void listenForContentChangesOnce(LithiumStackList stackList, InventoryChangeListener inventoryChangeListener) { + this.lithium$forwardContentChangeOnce(inventoryChangeListener, stackList, this); + } + + default void listenForMajorInventoryChanges(InventoryChangeListener inventoryChangeListener) { + this.lithium$forwardMajorInventoryChanges(inventoryChangeListener); + } + + default void stopListenForMajorInventoryChanges(InventoryChangeListener inventoryChangeListener) { + this.lithium$stopForwardingMajorInventoryChanges(inventoryChangeListener); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_comparator_tracking/ComparatorTracker.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_comparator_tracking/ComparatorTracker.java new file mode 100644 index 0000000..4ae78f9 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_comparator_tracking/ComparatorTracker.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking; + +import net.minecraft.core.Direction; + +public interface ComparatorTracker { + void lithium$onComparatorAdded(Direction direction, int offset); + + boolean lithium$hasAnyComparatorNearby(); +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_comparator_tracking/ComparatorTracking.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_comparator_tracking/ComparatorTracking.java new file mode 100644 index 0000000..3ca8181 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/block/entity/inventory_comparator_tracking/ComparatorTracking.java @@ -0,0 +1,45 @@ +package net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking; + +import net.caffeinemc.mods.lithium.common.util.DirectionConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.Container; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class ComparatorTracking { + public static void notifyNearbyBlockEntitiesAboutNewComparator(Level world, BlockPos pos) { + BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); + for (Direction searchDirection : DirectionConstants.HORIZONTAL) { + for (int searchOffset = 1; searchOffset <= 2; searchOffset++) { + searchPos.set(pos); + searchPos.move(searchDirection, searchOffset); + BlockState blockState = world.getBlockState(searchPos); + if (blockState.getBlock() instanceof EntityBlock) { + BlockEntity blockEntity = world.lithium$getLoadedExistingBlockEntity(searchPos); + if (blockEntity instanceof Container) { + blockEntity.lithium$onComparatorAdded(searchDirection, searchOffset); + } + } + } + } + } + + public static boolean findNearbyComparators(Level world, BlockPos pos) { + BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); + for (Direction searchDirection : DirectionConstants.HORIZONTAL) { + for (int searchOffset = 1; searchOffset <= 2; searchOffset++) { + searchPos.set(pos); + searchPos.move(searchDirection, searchOffset); + BlockState blockState = world.getBlockState(searchPos); + if (blockState.is(Blocks.COMPARATOR)) { + return true; + } + } + } + return false; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/BlockStateOnlyInventory.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/BlockStateOnlyInventory.java new file mode 100644 index 0000000..c7b9b0d --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/BlockStateOnlyInventory.java @@ -0,0 +1,4 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +public interface BlockStateOnlyInventory { +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/ComparatorUpdatePattern.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/ComparatorUpdatePattern.java new file mode 100644 index 0000000..021c7d3 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/ComparatorUpdatePattern.java @@ -0,0 +1,91 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import net.minecraft.world.level.block.entity.BlockEntity; + +/** + * Pattern of comparator updates that the given inventory is sending when a hopper + * unsuccessfully attempts to take items from it. This pattern is independent from + * the hopper, given that it fails to take items. + * Background: A hopper trying to take items will take one item out of each slot and + * put it back right after. Some types of inventories will send comparator updates + * every time a hopper does that. Multiple consecutive comparator updates + * are not distinguishable from a single one and can therefore be omitted. + *

+ * A hopper failing to take items can be predicted by testing whether the inventory + * modification counter {@link LithiumStackList} of the hopper or the inventory above + * have changed since just before the previous attempt to take items. + *

+ * Decrementing and immediately incrementing the signal strength of an inventory + * cannot be distinguished from setting the signal strength to 0 temporarily. + * + * @author 2No2Name + */ +public enum ComparatorUpdatePattern { + NO_UPDATE { + @Override + public ComparatorUpdatePattern thenUpdate() { + return UPDATE; + } + + @Override + public ComparatorUpdatePattern thenDecrementUpdateIncrementUpdate() { + return DECREMENT_UPDATE_INCREMENT_UPDATE; + } + }, + UPDATE { + @Override + public void apply(BlockEntity blockEntity, LithiumStackList stackList) { + blockEntity.setChanged(); + } + + @Override + public ComparatorUpdatePattern thenDecrementUpdateIncrementUpdate() { + return UPDATE_DECREMENT_UPDATE_INCREMENT_UPDATE; + } + }, + DECREMENT_UPDATE_INCREMENT_UPDATE { + @Override + public void apply(BlockEntity blockEntity, LithiumStackList stackList) { + stackList.setReducedSignalStrengthOverride(); + blockEntity.setChanged(); + stackList.clearSignalStrengthOverride(); + blockEntity.setChanged(); + } + + @Override + public boolean isChainable() { + return false; + } + }, + UPDATE_DECREMENT_UPDATE_INCREMENT_UPDATE { + @Override + public void apply(BlockEntity blockEntity, LithiumStackList stackList) { + blockEntity.setChanged(); + stackList.setReducedSignalStrengthOverride(); + blockEntity.setChanged(); + stackList.clearSignalStrengthOverride(); + blockEntity.setChanged(); + } + + @Override + public boolean isChainable() { + return false; + } + }; + + public void apply(BlockEntity blockEntity, LithiumStackList stackList) { + + } + + public ComparatorUpdatePattern thenUpdate() { + return this; + } + + public ComparatorUpdatePattern thenDecrementUpdateIncrementUpdate() { + return this; + } + + public boolean isChainable() { + return true; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/HopperCachingState.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/HopperCachingState.java new file mode 100644 index 0000000..b0df5a3 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/HopperCachingState.java @@ -0,0 +1,12 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +public class HopperCachingState { + + public enum BlockInventory { + UNKNOWN, // No information cached + BLOCK_STATE, // Known to be Composter-like inventory (inventory from block, but not block entity, only depends on block state) + BLOCK_ENTITY, // Known to be BlockEntity inventory without removal tracking capability + REMOVAL_TRACKING_BLOCK_ENTITY, // Known to be BlockEntity inventory with removal tracking capability + NO_BLOCK_INVENTORY // Known to be a block without hopper interaction (-> interact with entities instead) + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/HopperHelper.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/HopperHelper.java new file mode 100644 index 0000000..429d890 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/HopperHelper.java @@ -0,0 +1,151 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import net.caffeinemc.mods.lithium.common.util.DirectionConstants; +import net.caffeinemc.mods.lithium.common.world.WorldHelper; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.WorldlyContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.HopperBlockEntity; +import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class HopperHelper { + + public static boolean tryMoveSingleItem(Container to, ItemStack stack, @Nullable Direction fromDirection) { + ItemStack transferChecker; + if (to.lithium$itemInsertionTestRequiresStackSize1()) { + transferChecker = stack.copy(); + transferChecker.setCount(1); + } else { + transferChecker = stack; + } + + WorldlyContainer toSided = to instanceof WorldlyContainer ? ((WorldlyContainer) to) : null; + if (toSided != null && fromDirection != null) { + int[] slots = toSided.getSlotsForFace(fromDirection); + + for (int slotIndex = 0; slotIndex < slots.length; ++slotIndex) { + if (tryMoveSingleItem(to, toSided, stack, transferChecker, slots[slotIndex], fromDirection)) { + return true; //caller needs to take the item from the original inventory and call to.markDirty() + } + } + } else { + int j = to.getContainerSize(); + for (int slot = 0; slot < j; ++slot) { + if (tryMoveSingleItem(to, toSided, stack, transferChecker, slot, fromDirection)) { + return true; //caller needs to take the item from the original inventory and call to.markDirty() + } + } + } + return false; + } + + public static boolean tryMoveSingleItem(Container to, @Nullable WorldlyContainer toSided, ItemStack transferStack, ItemStack transferChecker, int targetSlot, @Nullable Direction fromDirection) { + ItemStack toStack = to.getItem(targetSlot); + if (to.canPlaceItem(targetSlot, transferChecker) && (toSided == null || toSided.canPlaceItemThroughFace(targetSlot, transferChecker, fromDirection))) { + int toCount; + if (toStack.isEmpty()) { + ItemStack singleItem = transferStack.split(1); + to.setItem(targetSlot, singleItem); + return true; //caller needs to call to.markDirty() + } else if (toStack.getMaxStackSize() > (toCount = toStack.getCount()) && to.getMaxStackSize() > toCount && ItemStack.isSameItemSameComponents(toStack, transferStack)) { + transferStack.shrink(1); + toStack.grow(1); + return true; //caller needs to call to.markDirty() + } + } + return false; + } + + private static int calculateReducedSignalStrength(float contentWeight, int inventorySize, int inventoryMaxCountPerStack, int numOccupiedSlots, int itemStackCount, int itemStackMaxCount) { + //contentWeight adaption can include rounding error for non-power of 2 max stack sizes, which do not exist in vanilla anyways + int maxStackSize = Math.min(inventoryMaxCountPerStack, itemStackMaxCount); + int newNumOccupiedSlots = numOccupiedSlots - (itemStackCount == 1 ? 1 : 0); + float newContentWeight = contentWeight - (1f / (float) maxStackSize); + newContentWeight /= (float) inventorySize; + return Mth.floor(newContentWeight * 14.0F) + (newNumOccupiedSlots > 0 ? 1 : 0); + } + + public static ComparatorUpdatePattern determineComparatorUpdatePattern(Container from, LithiumStackList fromStackList) { + if ((from instanceof HopperBlockEntity) || !(from instanceof RandomizableContainerBlockEntity)) { + return ComparatorUpdatePattern.NO_UPDATE; + } + //calculate the signal strength of the inventory, but also keep the content weight variable + float contentWeight = 0f; + int numOccupiedSlots = 0; + + for (int j = 0; j < from.getContainerSize(); ++j) { + ItemStack itemStack = from.getItem(j); + if (!itemStack.isEmpty()) { + int maxStackSize = Math.min(from.getMaxStackSize(), itemStack.getMaxStackSize()); + contentWeight += itemStack.getCount() / (float) maxStackSize; + ++numOccupiedSlots; + } + } + float f = contentWeight; + f /= (float) from.getContainerSize(); + int originalSignalStrength = Mth.floor(f * 14.0F) + (numOccupiedSlots > 0 ? 1 : 0); + + + ComparatorUpdatePattern updatePattern = ComparatorUpdatePattern.NO_UPDATE; + //check the signal strength change when failing to extract from each slot + int[] availableSlots = from instanceof WorldlyContainer ? ((WorldlyContainer) from).getSlotsForFace(Direction.DOWN) : null; + WorldlyContainer sidedInventory = from instanceof WorldlyContainer ? (WorldlyContainer) from : null; + int fromSize = availableSlots != null ? availableSlots.length : from.getContainerSize(); + for (int i = 0; i < fromSize; i++) { + int fromSlot = availableSlots != null ? availableSlots[i] : i; + ItemStack itemStack = fromStackList.get(fromSlot); + if (!itemStack.isEmpty() && (sidedInventory == null || sidedInventory.canTakeItemThroughFace(fromSlot, itemStack, Direction.DOWN))) { + int newSignalStrength = calculateReducedSignalStrength(contentWeight, from.getContainerSize(), from.getMaxStackSize(), numOccupiedSlots, itemStack.getCount(), itemStack.getMaxStackSize()); + if (newSignalStrength != originalSignalStrength) { + updatePattern = updatePattern.thenDecrementUpdateIncrementUpdate(); + } else { + updatePattern = updatePattern.thenUpdate(); + } + if (!updatePattern.isChainable()) { + break; //if the pattern is indistinguishable from all extensions of the pattern, stop iterating + } + } + } + return updatePattern; + } + + public static Container replaceDoubleInventory(Container blockInventory) { + if (blockInventory instanceof CompoundContainer doubleInventory) { + doubleInventory = LithiumDoubleInventory.getLithiumInventory(doubleInventory); + if (doubleInventory != null) { + return doubleInventory; + } + } + return blockInventory; + } + + public static void updateHopperOnUpdateSuppression(Level level, BlockPos pos, int flags, LevelChunk worldChunk, boolean stateChange) { + if ((flags & Block.UPDATE_NEIGHBORS) == 0 && stateChange) { + //No block updates were sent. We need to update nearby hoppers to avoid outdated inventory caches being used + + //Small performance improvement when getting block entities within the same chunk. + Map blockEntities = WorldHelper.areNeighborsWithinSameChunk(pos) ? worldChunk.getBlockEntities() : null; + if (blockEntities == null || !blockEntities.isEmpty()) { + for (Direction direction : DirectionConstants.ALL) { + BlockPos offsetPos = pos.relative(direction); + //Directly get the block entity instead of getting the block state first. Maybe that is faster, maybe not. + BlockEntity hopper = blockEntities != null ? blockEntities.get(offsetPos) : level.lithium$getLoadedExistingBlockEntity(offsetPos); + if (hopper instanceof HopperBlockEntity hopperBlockEntity) { + hopperBlockEntity.lithium$invalidateCacheOnNeighborUpdate(direction == Direction.DOWN); + } + } + } + } + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/InventoryHelper.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/InventoryHelper.java new file mode 100644 index 0000000..adff870 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/InventoryHelper.java @@ -0,0 +1,35 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import net.caffeinemc.mods.lithium.api.inventory.LithiumInventory; +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; + +public class InventoryHelper { + public static LithiumStackList getLithiumStackList(LithiumInventory inventory) { + NonNullList stackList = inventory.getInventoryLithium(); + if (stackList instanceof LithiumStackList lithiumStackList) { + return lithiumStackList; + } + return upgradeToLithiumStackList(inventory); + } + + public static LithiumStackList getLithiumStackListOrNull(LithiumInventory inventory) { + NonNullList stackList = inventory.getInventoryLithium(); + if (stackList instanceof LithiumStackList lithiumStackList) { + return lithiumStackList; + } + return null; + } + + private static LithiumStackList upgradeToLithiumStackList(LithiumInventory inventory) { + //generate loot to avoid any problems with directly accessing the inventory slots + //the loot that is generated here is not generated earlier than in vanilla, because vanilla generates loot + //when the hopper checks whether the inventory is empty or full + inventory.generateLootLithium(); + //get the stack list after generating loot, just in case generating loot creates a new stack list + NonNullList stackList = inventory.getInventoryLithium(); + LithiumStackList lithiumStackList = new LithiumStackList(stackList, inventory.getMaxStackSize()); + inventory.setInventoryLithium(lithiumStackList); + return lithiumStackList; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumDoubleInventory.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumDoubleInventory.java new file mode 100644 index 0000000..b058af6 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumDoubleInventory.java @@ -0,0 +1,173 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.caffeinemc.mods.lithium.api.inventory.LithiumInventory; +import net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeEmitter; +import net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener; +import net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker; +import net.caffeinemc.mods.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracker; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; + +public class LithiumDoubleInventory extends CompoundContainer implements LithiumInventory, InventoryChangeTracker, InventoryChangeEmitter, InventoryChangeListener, ComparatorTracker { + + private final LithiumInventory first; + private final LithiumInventory second; + + private LithiumStackList doubleStackList; + + ReferenceOpenHashSet inventoryChangeListeners = null; + ReferenceOpenHashSet inventoryHandlingTypeListeners = null; + + /** + * This method returns the same LithiumDoubleInventory instance for equal (same children in same order) + * doubleInventory parameters until {@link #lithium$emitRemoved()} is called. After that a new LithiumDoubleInventory object + * may be in use. + * + * @param doubleInventory A double inventory + * @return The only non-removed LithiumDoubleInventory instance for the double inventory. Null if not compatible + */ + public static LithiumDoubleInventory getLithiumInventory(CompoundContainer doubleInventory) { + Container vanillaFirst = doubleInventory.container1; + Container vanillaSecond = doubleInventory.container2; + if (vanillaFirst != vanillaSecond && vanillaFirst instanceof LithiumInventory first && vanillaSecond instanceof LithiumInventory second) { + LithiumDoubleInventory newDoubleInventory = new LithiumDoubleInventory(first, second); + LithiumDoubleStackList doubleStackList = LithiumDoubleStackList.getOrCreate( + newDoubleInventory, + InventoryHelper.getLithiumStackList(first), + InventoryHelper.getLithiumStackList(second), + newDoubleInventory.getMaxStackSize() + ); + newDoubleInventory.doubleStackList = doubleStackList; + return doubleStackList.doubleInventory; + } + return null; + } + + private LithiumDoubleInventory(LithiumInventory first, LithiumInventory second) { + super(first, second); + this.first = first; + this.second = second; + } + + @Override + public void lithium$emitContentModified() { + ReferenceOpenHashSet inventoryChangeListeners = this.inventoryChangeListeners; + if (inventoryChangeListeners != null) { + for (InventoryChangeListener inventoryChangeListener : inventoryChangeListeners) { + inventoryChangeListener.lithium$handleInventoryContentModified(this); + } + inventoryChangeListeners.clear(); + } + } + + @Override + public void lithium$emitStackListReplaced() { + ReferenceOpenHashSet listeners = this.inventoryHandlingTypeListeners; + if (listeners != null && !listeners.isEmpty()) { + listeners.forEach(inventoryChangeListener -> inventoryChangeListener.handleStackListReplaced(this)); + } + + this.invalidateChangeListening(); + } + + @Override + public void lithium$emitRemoved() { + ReferenceOpenHashSet listeners = this.inventoryHandlingTypeListeners; + if (listeners != null && !listeners.isEmpty()) { + listeners.forEach(listener -> listener.lithium$handleInventoryRemoved(this)); + } + + this.invalidateChangeListening(); + } + + private void invalidateChangeListening() { + if (this.inventoryChangeListeners != null) { + this.inventoryChangeListeners.clear(); + } + + LithiumStackList lithiumStackList = InventoryHelper.getLithiumStackListOrNull(this); + if (lithiumStackList != null) { + lithiumStackList.removeInventoryModificationCallback(this); + } + } + + @Override + public void lithium$emitFirstComparatorAdded() { + ReferenceOpenHashSet inventoryChangeListeners = this.inventoryChangeListeners; + if (inventoryChangeListeners != null && !inventoryChangeListeners.isEmpty()) { + inventoryChangeListeners.removeIf(inventoryChangeListener -> inventoryChangeListener.lithium$handleComparatorAdded(this)); + } + } + + @Override + public void lithium$forwardContentChangeOnce(InventoryChangeListener inventoryChangeListener, LithiumStackList stackList, InventoryChangeTracker thisTracker) { + if (this.inventoryChangeListeners == null) { + this.inventoryChangeListeners = new ReferenceOpenHashSet<>(1); + } + stackList.setInventoryModificationCallback(thisTracker); + this.inventoryChangeListeners.add(inventoryChangeListener); + + } + + @Override + public void lithium$forwardMajorInventoryChanges(InventoryChangeListener inventoryChangeListener) { + if (this.inventoryHandlingTypeListeners == null) { + this.inventoryHandlingTypeListeners = new ReferenceOpenHashSet<>(1); + + ((InventoryChangeTracker) this.first).listenForMajorInventoryChanges(this); + ((InventoryChangeTracker) this.second).listenForMajorInventoryChanges(this); + } + this.inventoryHandlingTypeListeners.add(inventoryChangeListener); + } + + @Override + public void lithium$stopForwardingMajorInventoryChanges(InventoryChangeListener inventoryChangeListener) { + if (this.inventoryHandlingTypeListeners != null) { + this.inventoryHandlingTypeListeners.remove(inventoryChangeListener); + if (this.inventoryHandlingTypeListeners.isEmpty()) { + ((InventoryChangeTracker) this.first).stopListenForMajorInventoryChanges(this); + ((InventoryChangeTracker) this.second).stopListenForMajorInventoryChanges(this); + } + } + } + + @Override + public NonNullList getInventoryLithium() { + return this.doubleStackList; + } + + @Override + public void setInventoryLithium(NonNullList inventory) { + throw new UnsupportedOperationException(); + } + + @Override + public void lithium$handleInventoryContentModified(Container inventory) { + this.lithium$emitContentModified(); + } + + @Override + public void lithium$handleInventoryRemoved(Container inventory) { + this.lithium$emitRemoved(); + } + + @Override + public boolean lithium$handleComparatorAdded(Container inventory) { + this.lithium$emitFirstComparatorAdded(); + return this.inventoryChangeListeners.isEmpty(); + } + + @Override + public void lithium$onComparatorAdded(Direction direction, int offset) { + throw new UnsupportedOperationException("Call onComparatorAdded(Direction direction, int offset) on the inventory half only!"); + } + + @Override + public boolean lithium$hasAnyComparatorNearby() { + return ((ComparatorTracker) this.first).lithium$hasAnyComparatorNearby() || ((ComparatorTracker) this.second).lithium$hasAnyComparatorNearby(); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumDoubleStackList.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumDoubleStackList.java new file mode 100644 index 0000000..e6aa77d --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumDoubleStackList.java @@ -0,0 +1,145 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Class to allow DoubleInventory to have LithiumStackList optimizations. + * The objects should be immutable and their state should be limited to the first and second inventory. + * Other state must be managed carefully, as at any time objects of this class may be replaced with new instances. + */ +public class LithiumDoubleStackList extends LithiumStackList { + private final LithiumStackList first; + private final LithiumStackList second; + final LithiumDoubleInventory doubleInventory; + + private long signalStrengthChangeCount; + + public LithiumDoubleStackList(LithiumDoubleInventory doubleInventory, LithiumStackList first, LithiumStackList second, int maxCountPerStack) { + super(maxCountPerStack); + this.first = first; + this.second = second; + this.doubleInventory = doubleInventory; + } + + public static LithiumDoubleStackList getOrCreate(LithiumDoubleInventory doubleInventory, LithiumStackList first, LithiumStackList second, int maxCountPerStack) { + LithiumDoubleStackList parentStackList = first.parent; + if (parentStackList == null || parentStackList != second.parent || parentStackList.first != first || parentStackList.second != second) { + if (parentStackList != null) { + parentStackList.doubleInventory.lithium$emitRemoved(); + } + parentStackList = new LithiumDoubleStackList(doubleInventory, first, second, maxCountPerStack); + first.parent = parentStackList; + second.parent = parentStackList; + } + return parentStackList; + } + + @Override + public long getModCount() { + return this.first.getModCount() + this.second.getModCount(); + } + + @Override + public void changedALot() { + throw new UnsupportedOperationException("Call changed() on the inventory half only!"); + } + + @Override + public void changed() { + throw new UnsupportedOperationException("Call changed() on the inventory half only!"); + } + + @Override + public ItemStack set(int index, ItemStack element) { + if (index >= this.first.size()) { + return this.second.set(index - this.first.size(), element); + } else { + return this.first.set(index, element); + } + } + + @Override + public void add(int slot, ItemStack element) { + throw new UnsupportedOperationException("Call add(int value, ItemStack element) on the inventory half only!"); + } + + @Override + public ItemStack remove(int index) { + throw new UnsupportedOperationException("Call remove(int value, ItemStack element) on the inventory half only!"); + } + + @Override + public void clear() { + this.first.clear(); + this.second.clear(); + } + + @Override + public int getSignalStrength(Container inventory) { + //signal strength override state has to be stored in the halves, because this object may be replaced with a copy at any time + boolean signalStrengthOverride = this.first.hasSignalStrengthOverride() || this.second.hasSignalStrengthOverride(); + if (signalStrengthOverride) { + return 0; + } + int cachedSignalStrength = this.cachedSignalStrength; + if (cachedSignalStrength == -1 || this.getModCount() != this.signalStrengthChangeCount) { + cachedSignalStrength = this.calculateSignalStrength(Integer.MAX_VALUE); + this.signalStrengthChangeCount = this.getModCount(); + this.cachedSignalStrength = cachedSignalStrength; + return cachedSignalStrength; + } + return cachedSignalStrength; + } + + @Override + public void setReducedSignalStrengthOverride() { + this.first.setReducedSignalStrengthOverride(); + this.second.setReducedSignalStrengthOverride(); + } + + @Override + public void clearSignalStrengthOverride() { + this.first.clearSignalStrengthOverride(); + this.second.clearSignalStrengthOverride(); + } + + /** + * @param masterStackList the stacklist of the inventory that comparators read from (double inventory for double chests) + * @param inventory the blockentity / inventory that this stacklist is inside + */ + public void runComparatorUpdatePatternOnFailedExtract(LithiumStackList masterStackList, Container inventory) { + if (inventory instanceof CompoundContainer compoundContainer) { + this.first.runComparatorUpdatePatternOnFailedExtract( + this, compoundContainer.container1 + ); + this.second.runComparatorUpdatePatternOnFailedExtract( + this, compoundContainer.container2 + ); + } + } + + @NotNull + @Override + public ItemStack get(int index) { + return index >= this.first.size() ? this.second.get(index - this.first.size()) : this.first.get(index); + } + + @Override + public int size() { + return this.first.size() + this.second.size(); + } + + public void setInventoryModificationCallback(@NotNull InventoryChangeTracker inventoryModificationCallback) { + this.first.setInventoryModificationCallback(inventoryModificationCallback); + this.second.setInventoryModificationCallback(inventoryModificationCallback); + } + + public void removeInventoryModificationCallback(@NotNull InventoryChangeTracker inventoryModificationCallback) { + this.first.removeInventoryModificationCallback(inventoryModificationCallback); + this.second.removeInventoryModificationCallback(inventoryModificationCallback); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumStackList.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumStackList.java new file mode 100644 index 0000000..db52944 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/LithiumStackList.java @@ -0,0 +1,287 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import net.caffeinemc.mods.lithium.api.inventory.LithiumDefaultedList; +import net.caffeinemc.mods.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker; +import net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber; +import net.minecraft.core.NonNullList; +import net.minecraft.util.Mth; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class LithiumStackList extends NonNullList implements LithiumDefaultedList, ChangeSubscriber.CountChangeSubscriber { + final int maxCountPerStack; + + protected int cachedSignalStrength; + private ComparatorUpdatePattern cachedComparatorUpdatePattern; + + private boolean signalStrengthOverride; + + private long modCount; + private int occupiedSlots; + private int fullSlots; + + LithiumDoubleStackList parent; //only used for double chests + + InventoryChangeTracker inventoryModificationCallback; + + public LithiumStackList(NonNullList original, int maxCountPerStack) { + super(original.list, ItemStack.EMPTY); + this.maxCountPerStack = maxCountPerStack; + + this.cachedSignalStrength = -1; + this.cachedComparatorUpdatePattern = null; + this.modCount = 0; + this.signalStrengthOverride = false; + + this.occupiedSlots = 0; + this.fullSlots = 0; + int size = this.size(); + for (int i = 0; i < size; i++) { + ItemStack stack = this.get(i); + if (!stack.isEmpty()) { + this.occupiedSlots++; + if (stack.getMaxStackSize() <= stack.getCount()) { + this.fullSlots++; + } + stack.lithium$subscribe(this, i); + } + } + + this.inventoryModificationCallback = null; + } + + public LithiumStackList(int maxCountPerStack) { + super(null, ItemStack.EMPTY); + this.maxCountPerStack = maxCountPerStack; + this.cachedSignalStrength = -1; + this.inventoryModificationCallback = null; + } + + public long getModCount() { + return this.modCount; + } + + public void changedALot() { + this.changed(); + + //fix the slot mapping of all stacks in the inventory + //fix occupied/full slot counters + this.occupiedSlots = 0; + this.fullSlots = 0; + int size = this.size(); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < size; i++) { + ItemStack stack = this.get(i); + if (!stack.isEmpty()) { + this.occupiedSlots++; + if (stack.getMaxStackSize() <= stack.getCount()) { + this.fullSlots++; + } + stack.lithium$unsubscribe(this); + } + } + for (int i = 0; i < size; i++) { + ItemStack stack = this.get(i); + if (!stack.isEmpty()) { + stack.lithium$subscribe(this, i); + } + } + + } + + /** + * Method that must be invoked before or after a change of the inventory to update important values. If done too + * early or too late, behavior might be incorrect. + */ + public void changed() { + this.cachedSignalStrength = -1; + this.cachedComparatorUpdatePattern = null; + this.modCount++; + + InventoryChangeTracker inventoryModificationCallback = this.inventoryModificationCallback; + if (inventoryModificationCallback != null) { + this.inventoryModificationCallback = null; + inventoryModificationCallback.lithium$emitContentModified(); + } + } + + @Override + public ItemStack set(int index, ItemStack element) { + ItemStack previous = super.set(index, element); + + //Handle vanilla's item stack resurrection in HopperBlockEntity extract(Hopper hopper, Inventory inventory, int slot, Direction side): + // Item stacks are set to 0 items, then back to 1. Then inventory.set(index, element) is called. + // At this point, the LithiumStackList unsubscribed from the stack when it reached 0. + // Handle: If the previous == element, and the stack is not subscribed, we handle it as if an empty stack was replaced. + if (previous == element && !element.isEmpty()) { + boolean notSubscribed = previous.lithium$isSubscribedWithData(this, index); + if (!notSubscribed) { + previous = ItemStack.EMPTY; + } + } + + if (previous != element) { + if (!previous.isEmpty()) { + previous.lithium$unsubscribeWithData(this, index); + } + if (!element.isEmpty()) { + element.lithium$subscribe(this, index); + } + + this.occupiedSlots += (previous.isEmpty() ? 1 : 0) - (element.isEmpty() ? 1 : 0); + this.fullSlots += (element.getCount() >= element.getMaxStackSize() ? 1 : 0) - (previous.getCount() >= previous.getMaxStackSize() ? 1 : 0); + this.changed(); + } + + return previous; + } + + @Override + public void add(int slot, ItemStack element) { + super.add(slot, element); + if (!element.isEmpty()) { + element.lithium$subscribe(this, this.indexOf(element)); + } + this.changedALot(); + } + + @Override + public ItemStack remove(int index) { + ItemStack previous = super.remove(index); + if (!previous.isEmpty()) { + previous.lithium$unsubscribeWithData(this, index); + } + this.changedALot(); + return previous; + } + + @Override + public void clear() { + int size = this.size(); + for (int i = 0; i < size; i++) { + ItemStack stack = this.get(i); + if (!stack.isEmpty()) { + stack.lithium$unsubscribeWithData(this, i); + } + } + super.clear(); + this.changedALot(); + } + + public boolean hasSignalStrengthOverride() { + return this.signalStrengthOverride; + } + + public int getSignalStrength(Container inventory) { + if (this.signalStrengthOverride) { + return 0; + } + int signalStrength = this.cachedSignalStrength; + if (signalStrength == -1) { + return this.cachedSignalStrength = this.calculateSignalStrength(inventory.getContainerSize()); + } + return signalStrength; + } + + /** + * [VanillaCopy] {@link net.minecraft.world.inventory.AbstractContainerMenu#getRedstoneSignalFromContainer(Container)} + * + * @return the signal strength for this inventory + */ + int calculateSignalStrength(int inventorySize) { + int i = 0; + float f = 0.0F; + + inventorySize = Math.min(inventorySize, this.size()); + for (int j = 0; j < inventorySize; ++j) { + ItemStack itemStack = this.get(j); + if (!itemStack.isEmpty()) { + f += (float) itemStack.getCount() / (float) Math.min(this.maxCountPerStack, itemStack.getMaxStackSize()); + ++i; + } + } + + f /= (float) inventorySize; + return Mth.floor(f * 14.0F) + (i > 0 ? 1 : 0); + } + + public void setReducedSignalStrengthOverride() { + this.signalStrengthOverride = true; + } + + public void clearSignalStrengthOverride() { + this.signalStrengthOverride = false; + } + + + /** + * @param masterStackList the stacklist of the inventory that comparators read from (double inventory for double chests) + * @param inventory the blockentity / inventory that this stacklist is inside + */ + public void runComparatorUpdatePatternOnFailedExtract(LithiumStackList masterStackList, Container inventory) { + if (inventory instanceof BlockEntity) { + if (this.cachedComparatorUpdatePattern == null) { + this.cachedComparatorUpdatePattern = HopperHelper.determineComparatorUpdatePattern(inventory, masterStackList); + } + this.cachedComparatorUpdatePattern.apply((BlockEntity) inventory, masterStackList); + } + } + + public boolean maybeSendsComparatorUpdatesOnFailedExtract() { + return this.cachedComparatorUpdatePattern == null || this.cachedComparatorUpdatePattern != ComparatorUpdatePattern.NO_UPDATE; + } + + public int getOccupiedSlots() { + return this.occupiedSlots; + } + + public int getFullSlots() { + return this.fullSlots; + } + + @Override + public void changedInteractionConditions() { + this.changed(); + } + + + public void setInventoryModificationCallback(@NotNull InventoryChangeTracker inventoryModificationCallback) { + if (this.inventoryModificationCallback != null && this.inventoryModificationCallback != inventoryModificationCallback) { + this.inventoryModificationCallback.emitCallbackReplaced(); + } + this.inventoryModificationCallback = inventoryModificationCallback; + } + + public void removeInventoryModificationCallback(@NotNull InventoryChangeTracker inventoryModificationCallback) { + if (this.inventoryModificationCallback != null && this.inventoryModificationCallback == inventoryModificationCallback) { + this.inventoryModificationCallback = null; + } + } + + @Override + public void lithium$notify(@Nullable ItemStack publisher, int subscriberData) { + //Item component changes: LithiumStackList does not care about this + } + + @Override + public void lithium$forceUnsubscribe(ItemStack publisher, int subscriberData) { + throw new UnsupportedOperationException("Cannot force unsubscribe on a LithiumStackList!"); + } + + @Override + public void lithium$notifyCount(ItemStack stack, int index, int newCount) { + assert stack == this.get(index); + int count = stack.getCount(); + if (newCount <= 0) { + stack.lithium$unsubscribeWithData(this, index); + } + int maxCount = stack.getMaxStackSize(); + this.occupiedSlots -= newCount <= 0 ? 1 : 0; + this.fullSlots += (newCount >= maxCount ? 1 : 0) - (count >= maxCount ? 1 : 0); + + this.changed(); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/UpdateReceiver.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/UpdateReceiver.java new file mode 100644 index 0000000..e2fc959 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/hopper/UpdateReceiver.java @@ -0,0 +1,11 @@ +package net.caffeinemc.mods.lithium.common.hopper; + +import net.minecraft.core.Direction; + +public interface UpdateReceiver { + void lithium$invalidateCacheOnNeighborUpdate(boolean above); + + void lithium$invalidateCacheOnUndirectedNeighborUpdate(); + + void lithium$invalidateCacheOnNeighborUpdate(Direction fromDirection); +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionEntityMovementListener.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionEntityMovementListener.java new file mode 100644 index 0000000..919abce --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionEntityMovementListener.java @@ -0,0 +1,5 @@ +package net.caffeinemc.mods.lithium.common.tracking.entity; + +public interface ChunkSectionEntityMovementListener { + void handleEntityMovement(); +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionEntityMovementTracker.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionEntityMovementTracker.java new file mode 100644 index 0000000..652e1fa --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionEntityMovementTracker.java @@ -0,0 +1,77 @@ +package net.caffeinemc.mods.lithium.common.tracking.entity; + +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.UUID; + +public abstract class ChunkSectionEntityMovementTracker { + protected long lastChangeTime = 0; + protected final ReferenceOpenHashSet listeners = new ReferenceOpenHashSet<>(); + protected final ChunkSectionIdentifier identifier; + protected int userCount = 0; + + public ChunkSectionEntityMovementTracker(long sectionKey, UUID levelId) { + identifier = ChunkSectionIdentifier.of(sectionKey, levelId); + } + + public void register() { + this.userCount++; + } + + public abstract void unregister(); + + public static void unregister(@NotNull List trackers) { + for (ChunkSectionEntityMovementTracker tracker : trackers) { + tracker.unregister(); + } + } + + public boolean isUnchangedSince(long lastCheckedTime) { + return this.lastChangeTime <= lastCheckedTime; + } + + public static boolean isUnchangedSince(long lastCheckedTime, @NotNull List trackers) { + for (ChunkSectionEntityMovementTracker tracker : trackers) { + if (!tracker.isUnchangedSince(lastCheckedTime)) { + return false; + } + } + return true; + } + + public void listenToEntityMovementOnce(ChunkSectionEntityMovementListener listener) { + this.listeners.add(listener); + } + + public static void listenToEntityMovementOnce(ChunkSectionEntityMovementListener listener, @NotNull List trackers) { + for (ChunkSectionEntityMovementTracker tracker : trackers) { + tracker.listenToEntityMovementOnce(listener); + } + } + + private void setChanged(long atTime) { + if (atTime > this.lastChangeTime) { + this.lastChangeTime = atTime; + } + } + + public void notifyAllListeners(long time) { + if (!listeners.isEmpty()) { + for (ChunkSectionEntityMovementListener listener : listeners) { + listener.handleEntityMovement(); + } + listeners.clear(); + } + setChanged(time); + } + + public record ChunkSectionIdentifier(long sectionKey, UUID levelId) { + @Contract("_, _ -> new") + public static @NotNull ChunkSectionIdentifier of(long sectionKey, UUID levelId) { + return new ChunkSectionIdentifier(sectionKey, levelId); + } + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionInventoryEntityTracker.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionInventoryEntityTracker.java new file mode 100644 index 0000000..2119b5c --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionInventoryEntityTracker.java @@ -0,0 +1,73 @@ +package net.caffeinemc.mods.lithium.common.tracking.entity; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import net.caffeinemc.mods.lithium.common.util.tuples.WorldSectionBox; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EntitySelector; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChunkSectionInventoryEntityTracker extends ChunkSectionEntityMovementTracker { + public static final Map containerEntityMovementTrackerMap = new ConcurrentHashMap<>(); + + public ChunkSectionInventoryEntityTracker(long sectionKey, UUID levelId) { + super(sectionKey, levelId); + } + + @Override + public void unregister() { + this.userCount--; + if (this.userCount <= 0) { + containerEntityMovementTrackerMap.remove(identifier); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static @NotNull List getEntities(@NotNull Level level, AABB boundingBox) { + return level.getEntitiesOfClass((Class) Container.class, boundingBox, EntitySelector.CONTAINER_ENTITY_SELECTOR); + } + + public static @NotNull List registerAt(ServerLevel world, AABB interactionArea) { + WorldSectionBox worldSectionBox = WorldSectionBox.entityAccessBox(world, interactionArea); + UUID levelId = world.uuid; + + if (worldSectionBox.chunkX1() == worldSectionBox.chunkX2() && + worldSectionBox.chunkY1() == worldSectionBox.chunkY2() && + worldSectionBox.chunkZ1() == worldSectionBox.chunkZ2()) { + return Collections.singletonList(registerAt( + CoordinateUtils.getChunkSectionKey(worldSectionBox.chunkX1(), worldSectionBox.chunkY1(), worldSectionBox.chunkZ1()), + levelId + )); + } + + List trackers = new ArrayList<>(); + + for (int x = worldSectionBox.chunkX1(); x <= worldSectionBox.chunkX2(); x++) { + for (int y = worldSectionBox.chunkY1(); y <= worldSectionBox.chunkY2(); y++) { + for (int z = worldSectionBox.chunkZ1(); z <= worldSectionBox.chunkZ2(); z++) { + trackers.add(registerAt(CoordinateUtils.getChunkSectionKey(x, y, z), levelId)); + } + } + } + + return trackers; + } + + private static @NotNull ChunkSectionInventoryEntityTracker registerAt(long key, UUID levelId) { + ChunkSectionInventoryEntityTracker tracker = containerEntityMovementTrackerMap.computeIfAbsent( + new ChunkSectionIdentifier(key, levelId), + k -> new ChunkSectionInventoryEntityTracker(key, levelId) + ); + tracker.register(); + return tracker; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionItemEntityMovementTracker.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionItemEntityMovementTracker.java new file mode 100644 index 0000000..d3662a5 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/tracking/entity/ChunkSectionItemEntityMovementTracker.java @@ -0,0 +1,73 @@ +package net.caffeinemc.mods.lithium.common.tracking.entity; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import net.caffeinemc.mods.lithium.common.util.tuples.WorldSectionBox; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntitySelector; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChunkSectionItemEntityMovementTracker extends ChunkSectionEntityMovementTracker { + public static final Map itemEntityMovementTrackerMap = new ConcurrentHashMap<>(); + + public ChunkSectionItemEntityMovementTracker(long sectionKey, UUID levelId) { + super(sectionKey, levelId); + } + + @Override + public void unregister() { + this.userCount--; + if (this.userCount <= 0) { + itemEntityMovementTrackerMap.remove(identifier); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static @NotNull List getEntities(@NotNull Level level, AABB boundingBox) { + return level.getEntitiesOfClass((Class) ItemEntity.class, boundingBox, EntitySelector.ENTITY_STILL_ALIVE); + } + + public static @NotNull List registerAt(ServerLevel world, AABB interactionArea) { + WorldSectionBox worldSectionBox = WorldSectionBox.entityAccessBox(world, interactionArea); + UUID levelId = world.uuid; + + if (worldSectionBox.chunkX1() == worldSectionBox.chunkX2() && + worldSectionBox.chunkY1() == worldSectionBox.chunkY2() && + worldSectionBox.chunkZ1() == worldSectionBox.chunkZ2()) { + return Collections.singletonList(registerAt( + CoordinateUtils.getChunkSectionKey(worldSectionBox.chunkX1(), worldSectionBox.chunkY1(), worldSectionBox.chunkZ1()), + levelId + )); + } + + List trackers = new ArrayList<>(); + + for (int x = worldSectionBox.chunkX1(); x <= worldSectionBox.chunkX2(); x++) { + for (int y = worldSectionBox.chunkY1(); y <= worldSectionBox.chunkY2(); y++) { + for (int z = worldSectionBox.chunkZ1(); z <= worldSectionBox.chunkZ2(); z++) { + trackers.add(registerAt(CoordinateUtils.getChunkSectionKey(x, y, z), levelId)); + } + } + } + + return trackers; + } + + private static @NotNull ChunkSectionItemEntityMovementTracker registerAt(long key, UUID levelId) { + ChunkSectionItemEntityMovementTracker tracker = itemEntityMovementTrackerMap.computeIfAbsent( + new ChunkSectionIdentifier(key, levelId), + k -> new ChunkSectionItemEntityMovementTracker(key, levelId) + ); + tracker.register(); + return tracker; + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/DirectionConstants.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/DirectionConstants.java new file mode 100644 index 0000000..df1e3a3 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/DirectionConstants.java @@ -0,0 +1,16 @@ +package net.caffeinemc.mods.lithium.common.util; + +import net.minecraft.core.Direction; + +/** + * Pre-initialized constants to avoid unnecessary allocations. + */ +public final class DirectionConstants { + private DirectionConstants() { + } + + public static final Direction[] ALL = Direction.values(); + public static final Direction[] VERTICAL = {Direction.DOWN, Direction.UP}; + public static final Direction[] HORIZONTAL = {Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH}; + public static final byte[] HORIZONTAL_OPPOSITE_INDICES = {1, 0, 3, 2}; +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java new file mode 100644 index 0000000..5a2035c --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.lithium.common.util.change_tracking; + +import net.minecraft.world.item.ItemStack; + +public interface ChangePublisher { + void lithium$subscribe(ChangeSubscriber subscriber, int subscriberData); + + int lithium$unsubscribe(ChangeSubscriber subscriber); + + default void lithium$unsubscribeWithData(ChangeSubscriber subscriber, int index) { + throw new UnsupportedOperationException("Only implemented for ItemStacks"); + } + + default boolean lithium$isSubscribedWithData(ChangeSubscriber subscriber, int subscriberData) { + throw new UnsupportedOperationException("Only implemented for ItemStacks"); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java new file mode 100644 index 0000000..9c016a4 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java @@ -0,0 +1,194 @@ +package net.caffeinemc.mods.lithium.common.util.change_tracking; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +public interface ChangeSubscriber { + + static ChangeSubscriber combine(ChangeSubscriber prevSubscriber, int prevSData, @NotNull ChangeSubscriber newSubscriber, int newSData) { + if (prevSubscriber == null) { + return newSubscriber; + } else if (prevSubscriber instanceof Multi) { + ArrayList> subscribers = new ArrayList<>(((Multi) prevSubscriber).subscribers); + IntArrayList subscriberDatas = new IntArrayList(((Multi) prevSubscriber).subscriberDatas); + subscribers.add(newSubscriber); + subscriberDatas.add(newSData); + return new Multi<>(subscribers, subscriberDatas); + } else { + ArrayList> subscribers = new ArrayList<>(); + IntArrayList subscriberDatas = new IntArrayList(); + subscribers.add(prevSubscriber); + subscriberDatas.add(prevSData); + subscribers.add(newSubscriber); + subscriberDatas.add(newSData); + return new Multi<>(subscribers, subscriberDatas); + } + } + + static ChangeSubscriber without(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber) { + return without(prevSubscriber, removedSubscriber, 0, false); + } + + static ChangeSubscriber without(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int removedSubscriberData, boolean matchData) { + if (prevSubscriber == removedSubscriber) { + return null; + } else if (prevSubscriber instanceof Multi multi) { + int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData); + if (index != -1) { + if (multi.subscribers.size() == 2) { + return multi.subscribers.get(1 - index); + } else { + ArrayList> subscribers = new ArrayList<>(multi.subscribers); + IntArrayList subscriberDatas = new IntArrayList(multi.subscriberDatas); + subscribers.remove(index); + subscriberDatas.removeInt(index); + + return new Multi<>(subscribers, subscriberDatas); + } + } else { + return prevSubscriber; + } + } else { + return prevSubscriber; + } + } + + static int dataWithout(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int subscriberData) { + return dataWithout(prevSubscriber, removedSubscriber, subscriberData, 0, false); + } + + static int dataWithout(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int subscriberData, int removedSubscriberData, boolean matchData) { + if (prevSubscriber instanceof Multi multi) { + int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData); + if (index != -1) { + if (multi.subscribers.size() == 2) { + return multi.subscriberDatas.getInt(1 - index); + } else { + return subscriberData; + } + } else { + return subscriberData; + } + } + return prevSubscriber == removedSubscriber ? 0 : subscriberData; + } + + static int dataOf(ChangeSubscriber subscribers, ChangeSubscriber subscriber, int subscriberData) { + return subscribers instanceof Multi multi ? multi.subscriberDatas.getInt(multi.subscribers.indexOf(subscriber)) : subscriberData; + } + + static boolean containsSubscriber(ChangeSubscriber subscriber, int subscriberData, ChangeSubscriber subscriber1, int subscriberData1) { + if (subscriber instanceof Multi multi) { + return multi.indexOf(subscriber1, subscriberData1, true) != -1; + } + return subscriber == subscriber1 && subscriberData == subscriberData1; + } + + + /** + * Notify the subscriber that the publisher will be changed immediately after this call. + * + * @param publisher The publisher that is about to change + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + */ + void lithium$notify(@Nullable T publisher, int subscriberData); + + /** + * Notify the subscriber about being unsubscribed from the publisher. Used when the publisher becomes invalid. + * The subscriber should not attempt to unsubscribe itself from the publisher in this method. + * + * @param publisher The publisher unsubscribed from + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + */ + void lithium$forceUnsubscribe(T publisher, int subscriberData); + + interface CountChangeSubscriber extends ChangeSubscriber { + + /** + * Notify the subscriber that the publisher's count data will be changed immediately after this call. + * + * @param publisher The publisher that is about to change + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + * @param newCount The new count of the publisher + */ + void lithium$notifyCount(T publisher, int subscriberData, int newCount); + } + + interface EnchantmentSubscriber extends ChangeSubscriber { + + /** + * Notify the subscriber that the publisher's enchantment data has been changed immediately before this call. + * + * @param publisher The publisher that has changed + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + */ + void lithium$notifyAfterEnchantmentChange(T publisher, int subscriberData); + } + + class Multi implements CountChangeSubscriber, EnchantmentSubscriber { + private final ArrayList> subscribers; + private final IntArrayList subscriberDatas; + + public Multi(ArrayList> subscribers, IntArrayList subscriberDatas) { + this.subscribers = subscribers; + this.subscriberDatas = subscriberDatas; + } + + @Override + public void lithium$notify(T publisher, int subscriberData) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + subscriber.lithium$notify(publisher, this.subscriberDatas.getInt(i)); + } + } + + @Override + public void lithium$forceUnsubscribe(T publisher, int subscriberData) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + subscriber.lithium$forceUnsubscribe(publisher, this.subscriberDatas.getInt(i)); + } + } + + @Override + public void lithium$notifyCount(T publisher, int subscriberData, int newCount) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + if (subscriber instanceof ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { + countChangeSubscriber.lithium$notifyCount(publisher, this.subscriberDatas.getInt(i), newCount); + } + } + } + + int indexOf(ChangeSubscriber subscriber, int subscriberData, boolean matchData) { + if (!matchData) { + return this.subscribers.indexOf(subscriber); + } else { + for (int i = 0; i < this.subscribers.size(); i++) { + if (this.subscribers.get(i) == subscriber && this.subscriberDatas.getInt(i) == subscriberData) { + return i; + } + } + return -1; + } + } + + @Override + public void lithium$notifyAfterEnchantmentChange(T publisher, int subscriberData) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + if (subscriber instanceof ChangeSubscriber.EnchantmentSubscriber enchantmentSubscriber) { + enchantmentSubscriber.lithium$notifyAfterEnchantmentChange(publisher, this.subscriberDatas.getInt(i)); + } + } + } + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/tuples/WorldSectionBox.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/tuples/WorldSectionBox.java new file mode 100644 index 0000000..e34d970 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/tuples/WorldSectionBox.java @@ -0,0 +1,20 @@ +package net.caffeinemc.mods.lithium.common.util.tuples; + +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; + +//Y values use coordinates, not indices (y=0 -> chunkY=0) +//upper bounds are EXCLUSIVE +public record WorldSectionBox(Level world, int chunkX1, int chunkY1, int chunkZ1, int chunkX2, int chunkY2, + int chunkZ2) { + public static WorldSectionBox entityAccessBox(Level world, AABB box) { + int minX = SectionPos.posToSectionCoord(box.minX - 2.0D); + int minY = SectionPos.posToSectionCoord(box.minY - 4.0D); + int minZ = SectionPos.posToSectionCoord(box.minZ - 2.0D); + int maxX = SectionPos.posToSectionCoord(box.maxX + 2.0D) + 1; + int maxY = SectionPos.posToSectionCoord(box.maxY) + 1; + int maxZ = SectionPos.posToSectionCoord(box.maxZ + 2.0D) + 1; + return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ); + } +} diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/world/WorldHelper.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/world/WorldHelper.java new file mode 100644 index 0000000..fa30816 --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/world/WorldHelper.java @@ -0,0 +1,12 @@ +package net.caffeinemc.mods.lithium.common.world; + +import net.minecraft.core.BlockPos; + +public class WorldHelper { + public static boolean areNeighborsWithinSameChunk(BlockPos pos) { + int localX = pos.getX() & 15; + int localZ = pos.getZ() & 15; + + return localX > 0 && localZ > 0 && localX < 15 && localZ < 15; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java index a6fb8e4..55a9c24 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java @@ -378,6 +378,7 @@ public class DivineConfig { public static boolean optimizedDragonRespawn = false; public static boolean reduceChuckLoadAndLookup = true; public static boolean createSnapshotOnRetrievingBlockState = true; + public static boolean sleepingBlockEntity = false; public static boolean hopperThrottleWhenFull = false; public static int hopperThrottleSkipTicks = 0; @@ -482,6 +483,8 @@ public class DivineConfig { createSnapshotOnRetrievingBlockState = getBoolean(ConfigCategory.PERFORMANCE.key("optimizations.create-snapshot-on-retrieving-block-state"), createSnapshotOnRetrievingBlockState, "Whether to create a snapshot (copy) of BlockState data when plugins retrieve them.", "If false, plugins get direct BlockState access for better performance but risk data corruption from poor plugin design."); + sleepingBlockEntity = getBoolean(ConfigCategory.PERFORMANCE.key("optimizations.sleeping-block-entity"), sleepingBlockEntity, + "When enabled, block entities will enter a sleep state when they are inactive."); hopperThrottleWhenFull = getBoolean(ConfigCategory.PERFORMANCE.key("optimizations.hopper-throttle-when-full.enabled"), hopperThrottleWhenFull, "When enabled, hoppers will throttle if target container is full.");