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:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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> {
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.caffeinemc.mods.lithium.common.block.entity;
|
||||
|
||||
public interface SetBlockStateHandlingBlockEntity {
|
||||
default void lithium$handleSetBlockState() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.caffeinemc.mods.lithium.common.block.entity;
|
||||
|
||||
public interface SetChangedHandlingBlockEntity {
|
||||
default void lithium$handleSetChanged() {
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package net.caffeinemc.mods.lithium.common.hopper;
|
||||
|
||||
public interface BlockStateOnlyInventory {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.caffeinemc.mods.lithium.common.tracking.entity;
|
||||
|
||||
public interface ChunkSectionEntityMovementListener {
|
||||
void handleEntityMovement();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
Reference in New Issue
Block a user