mirror of
https://github.com/BX-Team/DivineMC.git
synced 2025-12-27 10:49:14 +00:00
feat: lithium: sleeping_block_entity
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user