9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-19 14:59:25 +00:00

feat: lithium: sleeping_block_entity

This commit is contained in:
NONPLAYT
2025-10-01 01:38:10 +03:00
parent 107fe6f864
commit 967d057e52
80 changed files with 4190 additions and 73 deletions

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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<TickTa
@@ -248,7 +248,7 @@ index 8db95b74f88f8096de93115ae8d3fb2e6184ad3b..e044830439fe9821ab3f62695d318a63
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 32f17328b7980a9dc382c90af76cca04b74c639a..6f25be39103cd0bb26bc365d9599b9846c6fe133 100644
index 6d9274f0da9507d0152611d6b7785e0524dedb2d..1ffd82f8459525c73ea2f4a57568eb5966b312dd 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -917,6 +917,19 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p

View File

@@ -36,7 +36,7 @@ index 758ce439d2e10e6ef42a58d147a77093667e0acd..de622982f864d96a5b76efcd69f1836e
new java.util.concurrent.LinkedBlockingQueue<>(),
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<TickTa

View File

@@ -126,7 +126,7 @@ index 81511de113c292549fe5fe720a15bf3e0497ca84..19f74518923783d8d5560b526a1f267d
@Nullable
diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java
index 857aa6e29b57a0a8eea4d7c14971b9dde59bb0d0..1b664a0b3e994d2df38a4fa700280ada9514dd8f 100644
index 854578c7880dc124980142941ee471072668c8e2..ef59461d9685fc33bd9b98a5c3fc5ec17b57841f 100644
--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -342,7 +342,11 @@ public abstract class ChunkGenerator {
@@ -388,7 +388,7 @@ index ee0d9dddb36b6879fa113299e24f1aa3b2b151cc..3af3bf800215ef78b98a4866df572f3b
int i3 = this.spreadType.evaluate(worldgenRandom, i2);
int i4 = this.spreadType.evaluate(worldgenRandom, i2);
diff --git a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
index 1f939b325ec5291b3c4aabc4735c863f9436a6f8..0a544577922bef471d2f07544ad45ce2186d92d1 100644
index f2eb0572b9d97d97bc847082461515a852310dfc..2e77934a90ebce1181229763156e20bb35c96d34 100644
--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
+++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
@@ -119,8 +119,17 @@ public abstract class StructurePlacement {

View File

@@ -9,7 +9,7 @@ Original project: https://github.com/pufferfish-gg/Pufferfish
This patch reduces the main-thread impact of mob spawning by moving spawning work to other threads
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index a5130d66312717737e7b3a6e6f5fac3e4c0a62d7..cd084854b4c9938f5d18b9c16fbfa763323f8d8f 100644
index 1877ee1431b0f858b6a5da7347d72fe90374e27a..dddcde2716bbdca1240bd60bc5ca17aeb1999d57 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -289,6 +289,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

View File

@@ -1356,7 +1356,7 @@ index d23f255de9208f42125fa358a9e8194c984fe4d3..92e9bc9ba577474ca1108b8d06157395
// CraftBukkit end
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 6f25be39103cd0bb26bc365d9599b9846c6fe133..24f13e2b0b694ff4dd01aeea876ef874f1828304 100644
index 1ffd82f8459525c73ea2f4a57568eb5966b312dd..306590a29f8b6db6c0c68814f3fa28f3f26e448b 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -365,6 +365,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p

View File

@@ -5,7 +5,7 @@ Subject: [PATCH] MSPT Tracking for each world
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 6b658ae60ef9a6733749e9bd77fc491b69bbad44..76787f940ae156db1af491dff2145673c082a29b 100644
index aad1f6dffc6831baa8a573add5bbd229cd7b2a9d..a7871b4591593e6b1efa3dc17053de9df600f24c 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1678,7 +1678,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

View File

@@ -20,7 +20,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..aa4dd7517e8be167aef1eaf7aa907e3c
if (var4 instanceof ReportedException reportedException && reportedException.getCause() instanceof OutOfMemoryError) {
throw makeReportedException(var4, packet, processor);
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 76787f940ae156db1af491dff2145673c082a29b..ca5458d6687a322a2b6494dcd7fa903943beea47 100644
index a7871b4591593e6b1efa3dc17053de9df600f24c..7f5f7d82ac8748758964c24f6c9377dda1dabb14 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1687,6 +1687,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

View File

@@ -23,7 +23,7 @@ index 7ca147cf9da67c399806056e5092841f7ca32321..a6bf257ca93e4b3819b65b4ef4ba71d9
double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
if (rangeY != -1) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 85da5aec677d84aea005449d97c1ee3ca616e2e4..4c9e0eaedad3f4a372b48580cbe9b03fe2f8c037 100644
index cb77b3ab59542bc4e8b50aecb23d98186206a0ad..fa48496222ea922204163d48988246c44e09851f 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -145,7 +145,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;

View File

@@ -35,7 +35,7 @@ index fb263fa1f30a7dfcb7ec2656abfb38e5fe88eac9..c3be4c2fd4a544967322a45d3b8c0fe7
};
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index ca5458d6687a322a2b6494dcd7fa903943beea47..b51a7dfd95769766924f25364179ebccb4160a8b 100644
index 7f5f7d82ac8748758964c24f6c9377dda1dabb14..5969e9d929c709500670b086485f68b26a8475ae 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1789,6 +1789,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

View File

@@ -60,7 +60,7 @@ index 51c126735ace8fdde89ad97b5cab62f244212db0..23f6ed26b531ea570fdf2ae48c1e2710
+ public void moonrise$write(final org.bxteam.divinemc.region.IRegionFile regionFile) throws IOException; // DivineMC - Buffered Linear region format
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 7bc07d120ca3b8cffa6b1147f1687b5a4023b709..97f097eff051690ea0bc24b851153c7deafdc008 100644
index 5969e9d929c709500670b086485f68b26a8475ae..1c6749f3492195955c907c1e9812786b0936580a 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -942,10 +942,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

View File

@@ -105,7 +105,7 @@ index 0ad18866c323308ad9b87322932e03a283f740b1..386fdc23b35675a7db66d16bf2a8a6dd
private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 4c9e0eaedad3f4a372b48580cbe9b03fe2f8c037..d947bff42cbf7aaac0b7f62bc78c239a5f6af381 100644
index fa48496222ea922204163d48988246c44e09851f..2c7c5dab268625e1328f57ac3ec2a735a82fea42 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1111,29 +1111,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess

View File

@@ -8,7 +8,7 @@ Original project: https://github.com/PaperMC/Paper
Paper pull request: https://github.com/PaperMC/Paper/pull/8074
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index b59078273bb6214295a448d5607538557d7eb1ee..c097afcdece95e33486c833063dc2aed159d93f7 100644
index 48a02a25c4fa6f3bacefaccd122694156da1a331..a0d45f72e7c35883996214a2c5420d6a996a58aa 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -304,10 +304,15 @@ public abstract class PlayerList {

View File

@@ -5,7 +5,7 @@ Subject: [PATCH] Optimize collections
diff --git a/net/minecraft/core/NonNullList.java b/net/minecraft/core/NonNullList.java
index 7e31c5c8659d24948fd45a2d6ee7bdeca6027d27..95221434fd8eef388f0308b72af3f93407a6e09c 100644
index 8c0d8f4e8f8c4cbf0a0b771c1bd99e54877b4944..b88a8663bb4ebd8bf2c541402ded6fd6e9a97589 100644
--- a/net/minecraft/core/NonNullList.java
+++ b/net/minecraft/core/NonNullList.java
@@ -14,23 +14,23 @@ public class NonNullList<E> extends AbstractList<E> {

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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.
* <p>
* This interface should be implemented by your {@link Container} or
* {@link net.minecraft.world.WorldlyContainer} type to access the stack list.
* <p>
* An inventory must not extend {@link net.minecraft.world.level.block.entity.BlockEntity} if it has a supporting block that
* implements {@link ContainerEntity}.
* <p>
* 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<ItemStack> 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<ItemStack> inventory);
/**
* Generates the loot like a hopper access would do in vanilla.
* <p>
* 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);
}
}
}

View File

@@ -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.
* <p>
* 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;
}
}

View File

@@ -0,0 +1,6 @@
package net.caffeinemc.mods.lithium.common.block.entity;
public interface SetBlockStateHandlingBlockEntity {
default void lithium$handleSetBlockState() {
}
}

View File

@@ -0,0 +1,6 @@
package net.caffeinemc.mods.lithium.common.block.entity;
public interface SetChangedHandlingBlockEntity {
default void lithium$handleSetChanged() {
}
}

View File

@@ -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) { }
}

View File

@@ -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();
}
}

View File

@@ -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 "<lithium_sleeping>";
}
};
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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,4 @@
package net.caffeinemc.mods.lithium.common.hopper;
public interface BlockStateOnlyInventory {
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@@ -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)
}
}

View File

@@ -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<BlockPos, BlockEntity> 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);
}
}
}
}
}
}

View File

@@ -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<ItemStack> stackList = inventory.getInventoryLithium();
if (stackList instanceof LithiumStackList lithiumStackList) {
return lithiumStackList;
}
return upgradeToLithiumStackList(inventory);
}
public static LithiumStackList getLithiumStackListOrNull(LithiumInventory inventory) {
NonNullList<ItemStack> 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<ItemStack> stackList = inventory.getInventoryLithium();
LithiumStackList lithiumStackList = new LithiumStackList(stackList, inventory.getMaxStackSize());
inventory.setInventoryLithium(lithiumStackList);
return lithiumStackList;
}
}

View File

@@ -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<InventoryChangeListener> inventoryChangeListeners = null;
ReferenceOpenHashSet<InventoryChangeListener> 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<InventoryChangeListener> inventoryChangeListeners = this.inventoryChangeListeners;
if (inventoryChangeListeners != null) {
for (InventoryChangeListener inventoryChangeListener : inventoryChangeListeners) {
inventoryChangeListener.lithium$handleInventoryContentModified(this);
}
inventoryChangeListeners.clear();
}
}
@Override
public void lithium$emitStackListReplaced() {
ReferenceOpenHashSet<InventoryChangeListener> listeners = this.inventoryHandlingTypeListeners;
if (listeners != null && !listeners.isEmpty()) {
listeners.forEach(inventoryChangeListener -> inventoryChangeListener.handleStackListReplaced(this));
}
this.invalidateChangeListening();
}
@Override
public void lithium$emitRemoved() {
ReferenceOpenHashSet<InventoryChangeListener> 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<InventoryChangeListener> 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<ItemStack> getInventoryLithium() {
return this.doubleStackList;
}
@Override
public void setInventoryLithium(NonNullList<ItemStack> 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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<ItemStack> implements LithiumDefaultedList, ChangeSubscriber.CountChangeSubscriber<ItemStack> {
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<ItemStack> 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();
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,5 @@
package net.caffeinemc.mods.lithium.common.tracking.entity;
public interface ChunkSectionEntityMovementListener {
void handleEntityMovement();
}

View File

@@ -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<ChunkSectionEntityMovementListener> 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<? extends ChunkSectionEntityMovementTracker> trackers) {
for (ChunkSectionEntityMovementTracker tracker : trackers) {
tracker.unregister();
}
}
public boolean isUnchangedSince(long lastCheckedTime) {
return this.lastChangeTime <= lastCheckedTime;
}
public static boolean isUnchangedSince(long lastCheckedTime, @NotNull List<? extends ChunkSectionEntityMovementTracker> 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<? extends ChunkSectionEntityMovementTracker> 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);
}
}
}

View File

@@ -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<ChunkSectionIdentifier, ChunkSectionInventoryEntityTracker> 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<Container> getEntities(@NotNull Level level, AABB boundingBox) {
return level.getEntitiesOfClass((Class) Container.class, boundingBox, EntitySelector.CONTAINER_ENTITY_SELECTOR);
}
public static @NotNull List<ChunkSectionInventoryEntityTracker> 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<ChunkSectionInventoryEntityTracker> 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;
}
}

View File

@@ -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<ChunkSectionIdentifier, ChunkSectionItemEntityMovementTracker> 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<ItemEntity> getEntities(@NotNull Level level, AABB boundingBox) {
return level.getEntitiesOfClass((Class) ItemEntity.class, boundingBox, EntitySelector.ENTITY_STILL_ALIVE);
}
public static @NotNull List<ChunkSectionItemEntityMovementTracker> 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<ChunkSectionItemEntityMovementTracker> 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;
}
}

View File

@@ -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};
}

View File

@@ -0,0 +1,17 @@
package net.caffeinemc.mods.lithium.common.util.change_tracking;
import net.minecraft.world.item.ItemStack;
public interface ChangePublisher<T> {
void lithium$subscribe(ChangeSubscriber<T> subscriber, int subscriberData);
int lithium$unsubscribe(ChangeSubscriber<T> subscriber);
default void lithium$unsubscribeWithData(ChangeSubscriber<T> subscriber, int index) {
throw new UnsupportedOperationException("Only implemented for ItemStacks");
}
default boolean lithium$isSubscribedWithData(ChangeSubscriber<ItemStack> subscriber, int subscriberData) {
throw new UnsupportedOperationException("Only implemented for ItemStacks");
}
}

View File

@@ -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<T> {
static <T> ChangeSubscriber<T> combine(ChangeSubscriber<T> prevSubscriber, int prevSData, @NotNull ChangeSubscriber<T> newSubscriber, int newSData) {
if (prevSubscriber == null) {
return newSubscriber;
} else if (prevSubscriber instanceof Multi) {
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>(((Multi<T>) prevSubscriber).subscribers);
IntArrayList subscriberDatas = new IntArrayList(((Multi<T>) prevSubscriber).subscriberDatas);
subscribers.add(newSubscriber);
subscriberDatas.add(newSData);
return new Multi<>(subscribers, subscriberDatas);
} else {
ArrayList<ChangeSubscriber<T>> 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 <T> ChangeSubscriber<T> without(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber) {
return without(prevSubscriber, removedSubscriber, 0, false);
}
static <T> ChangeSubscriber<T> without(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int removedSubscriberData, boolean matchData) {
if (prevSubscriber == removedSubscriber) {
return null;
} else if (prevSubscriber instanceof Multi<T> multi) {
int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData);
if (index != -1) {
if (multi.subscribers.size() == 2) {
return multi.subscribers.get(1 - index);
} else {
ArrayList<ChangeSubscriber<T>> 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 <T> int dataWithout(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int subscriberData) {
return dataWithout(prevSubscriber, removedSubscriber, subscriberData, 0, false);
}
static <T> int dataWithout(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int subscriberData, int removedSubscriberData, boolean matchData) {
if (prevSubscriber instanceof Multi<T> 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<ItemStack> subscriber, int subscriberData, ChangeSubscriber<ItemStack> subscriber1, int subscriberData1) {
if (subscriber instanceof Multi<ItemStack> 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<T> extends ChangeSubscriber<T> {
/**
* 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<T> extends ChangeSubscriber<T> {
/**
* 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<T> implements CountChangeSubscriber<T>, EnchantmentSubscriber<T> {
private final ArrayList<ChangeSubscriber<T>> subscribers;
private final IntArrayList subscriberDatas;
public Multi(ArrayList<ChangeSubscriber<T>> subscribers, IntArrayList subscriberDatas) {
this.subscribers = subscribers;
this.subscriberDatas = subscriberDatas;
}
@Override
public void lithium$notify(T publisher, int subscriberData) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
subscriber.lithium$notify(publisher, this.subscriberDatas.getInt(i));
}
}
@Override
public void lithium$forceUnsubscribe(T publisher, int subscriberData) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
subscriber.lithium$forceUnsubscribe(publisher, this.subscriberDatas.getInt(i));
}
}
@Override
public void lithium$notifyCount(T publisher, int subscriberData, int newCount) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
if (subscriber instanceof ChangeSubscriber.CountChangeSubscriber<T> countChangeSubscriber) {
countChangeSubscriber.lithium$notifyCount(publisher, this.subscriberDatas.getInt(i), newCount);
}
}
}
int indexOf(ChangeSubscriber<T> 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<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
if (subscriber instanceof ChangeSubscriber.EnchantmentSubscriber<T> enchantmentSubscriber) {
enchantmentSubscriber.lithium$notifyAfterEnchantmentChange(publisher, this.subscriberDatas.getInt(i));
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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.");