461 lines
14 KiB
Java
461 lines
14 KiB
Java
package net.minecraft.world.level.block.entity;
|
|
|
|
import java.util.List;
|
|
import java.util.function.BooleanSupplier;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.NonNullList;
|
|
import net.minecraft.core.HolderLookup.Provider;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.world.Container;
|
|
import net.minecraft.world.ContainerHelper;
|
|
import net.minecraft.world.WorldlyContainer;
|
|
import net.minecraft.world.WorldlyContainerHolder;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntitySelector;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.player.Inventory;
|
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
|
import net.minecraft.world.inventory.HopperMenu;
|
|
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.ChestBlock;
|
|
import net.minecraft.world.level.block.HopperBlock;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.AABB;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper {
|
|
public static final int MOVE_ITEM_SPEED = 8;
|
|
public static final int HOPPER_CONTAINER_SIZE = 5;
|
|
private static final int[][] CACHED_SLOTS = new int[54][];
|
|
private NonNullList<ItemStack> items = NonNullList.withSize(5, ItemStack.EMPTY);
|
|
private int cooldownTime = -1;
|
|
private long tickedGameTime;
|
|
private Direction facing;
|
|
|
|
public HopperBlockEntity(BlockPos pos, BlockState blockState) {
|
|
super(BlockEntityType.HOPPER, pos, blockState);
|
|
this.facing = blockState.getValue(HopperBlock.FACING);
|
|
}
|
|
|
|
@Override
|
|
protected void loadAdditional(CompoundTag tag, Provider registries) {
|
|
super.loadAdditional(tag, registries);
|
|
this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
|
|
if (!this.tryLoadLootTable(tag)) {
|
|
ContainerHelper.loadAllItems(tag, this.items, registries);
|
|
}
|
|
|
|
this.cooldownTime = tag.getInt("TransferCooldown");
|
|
}
|
|
|
|
@Override
|
|
protected void saveAdditional(CompoundTag tag, Provider registries) {
|
|
super.saveAdditional(tag, registries);
|
|
if (!this.trySaveLootTable(tag)) {
|
|
ContainerHelper.saveAllItems(tag, this.items, registries);
|
|
}
|
|
|
|
tag.putInt("TransferCooldown", this.cooldownTime);
|
|
}
|
|
|
|
@Override
|
|
public int getContainerSize() {
|
|
return this.items.size();
|
|
}
|
|
|
|
@Override
|
|
public ItemStack removeItem(int slot, int amount) {
|
|
this.unpackLootTable(null);
|
|
return ContainerHelper.removeItem(this.getItems(), slot, amount);
|
|
}
|
|
|
|
@Override
|
|
public void setItem(int slot, ItemStack stack) {
|
|
this.unpackLootTable(null);
|
|
this.getItems().set(slot, stack);
|
|
stack.limitSize(this.getMaxStackSize(stack));
|
|
}
|
|
|
|
@Override
|
|
public void setBlockState(BlockState blockState) {
|
|
super.setBlockState(blockState);
|
|
this.facing = blockState.getValue(HopperBlock.FACING);
|
|
}
|
|
|
|
@Override
|
|
protected Component getDefaultName() {
|
|
return Component.translatable("container.hopper");
|
|
}
|
|
|
|
public static void pushItemsTick(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity) {
|
|
blockEntity.cooldownTime--;
|
|
blockEntity.tickedGameTime = level.getGameTime();
|
|
if (!blockEntity.isOnCooldown()) {
|
|
blockEntity.setCooldown(0);
|
|
tryMoveItems(level, pos, state, blockEntity, () -> suckInItems(level, blockEntity));
|
|
}
|
|
}
|
|
|
|
private static boolean tryMoveItems(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier validator) {
|
|
if (level.isClientSide) {
|
|
return false;
|
|
} else {
|
|
if (!blockEntity.isOnCooldown() && (Boolean)state.getValue(HopperBlock.ENABLED)) {
|
|
boolean bl = false;
|
|
if (!blockEntity.isEmpty()) {
|
|
bl = ejectItems(level, pos, blockEntity);
|
|
}
|
|
|
|
if (!blockEntity.inventoryFull()) {
|
|
bl |= validator.getAsBoolean();
|
|
}
|
|
|
|
if (bl) {
|
|
blockEntity.setCooldown(8);
|
|
setChanged(level, pos, state);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean inventoryFull() {
|
|
for (ItemStack itemStack : this.items) {
|
|
if (itemStack.isEmpty() || itemStack.getCount() != itemStack.getMaxStackSize()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) {
|
|
Container container = getAttachedContainer(level, pos, blockEntity);
|
|
if (container == null) {
|
|
return false;
|
|
} else {
|
|
Direction direction = blockEntity.facing.getOpposite();
|
|
if (isFullContainer(container, direction)) {
|
|
return false;
|
|
} else {
|
|
for (int i = 0; i < blockEntity.getContainerSize(); i++) {
|
|
ItemStack itemStack = blockEntity.getItem(i);
|
|
if (!itemStack.isEmpty()) {
|
|
int j = itemStack.getCount();
|
|
ItemStack itemStack2 = addItem(blockEntity, container, blockEntity.removeItem(i, 1), direction);
|
|
if (itemStack2.isEmpty()) {
|
|
container.setChanged();
|
|
return true;
|
|
}
|
|
|
|
itemStack.setCount(j);
|
|
if (j == 1) {
|
|
blockEntity.setItem(i, itemStack);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int[] getSlots(Container container, Direction direction) {
|
|
if (container instanceof WorldlyContainer worldlyContainer) {
|
|
return worldlyContainer.getSlotsForFace(direction);
|
|
} else {
|
|
int i = container.getContainerSize();
|
|
if (i < CACHED_SLOTS.length) {
|
|
int[] is = CACHED_SLOTS[i];
|
|
if (is != null) {
|
|
return is;
|
|
} else {
|
|
int[] js = createFlatSlots(i);
|
|
CACHED_SLOTS[i] = js;
|
|
return js;
|
|
}
|
|
} else {
|
|
return createFlatSlots(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int[] createFlatSlots(int size) {
|
|
int[] is = new int[size];
|
|
int i = 0;
|
|
|
|
while (i < is.length) {
|
|
is[i] = i++;
|
|
}
|
|
|
|
return is;
|
|
}
|
|
|
|
/**
|
|
* @return {@code false} if the {@code container} has any room to place items in
|
|
*/
|
|
private static boolean isFullContainer(Container container, Direction direction) {
|
|
int[] is = getSlots(container, direction);
|
|
|
|
for (int i : is) {
|
|
ItemStack itemStack = container.getItem(i);
|
|
if (itemStack.getCount() < itemStack.getMaxStackSize()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static boolean suckInItems(Level level, Hopper hopper) {
|
|
BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ());
|
|
BlockState blockState = level.getBlockState(blockPos);
|
|
Container container = getSourceContainer(level, hopper, blockPos, blockState);
|
|
if (container != null) {
|
|
Direction direction = Direction.DOWN;
|
|
|
|
for (int i : getSlots(container, direction)) {
|
|
if (tryTakeInItemFromSlot(hopper, container, i, direction)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} else {
|
|
boolean bl = hopper.isGridAligned() && blockState.isCollisionShapeFullBlock(level, blockPos) && !blockState.is(BlockTags.DOES_NOT_BLOCK_HOPPERS);
|
|
if (!bl) {
|
|
for (ItemEntity itemEntity : getItemsAtAndAbove(level, hopper)) {
|
|
if (addItem(hopper, itemEntity)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pulls from the specified slot in the container and places in any available slot in the hopper.
|
|
* @return {@code true} if the entire stack was moved.
|
|
*/
|
|
private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction) {
|
|
ItemStack itemStack = container.getItem(slot);
|
|
if (!itemStack.isEmpty() && canTakeItemFromContainer(hopper, container, itemStack, slot, direction)) {
|
|
int i = itemStack.getCount();
|
|
ItemStack itemStack2 = addItem(container, hopper, container.removeItem(slot, 1), null);
|
|
if (itemStack2.isEmpty()) {
|
|
container.setChanged();
|
|
return true;
|
|
}
|
|
|
|
itemStack.setCount(i);
|
|
if (i == 1) {
|
|
container.setItem(slot, itemStack);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static boolean addItem(Container container, ItemEntity item) {
|
|
boolean bl = false;
|
|
ItemStack itemStack = item.getItem().copy();
|
|
ItemStack itemStack2 = addItem(null, container, itemStack, null);
|
|
if (itemStack2.isEmpty()) {
|
|
bl = true;
|
|
item.setItem(ItemStack.EMPTY);
|
|
item.discard();
|
|
} else {
|
|
item.setItem(itemStack2);
|
|
}
|
|
|
|
return bl;
|
|
}
|
|
|
|
/**
|
|
* Attempts to place the passed stack in the container, using as many slots as required.
|
|
* @return any leftover stack
|
|
*/
|
|
public static ItemStack addItem(@Nullable Container source, Container destination, ItemStack stack, @Nullable Direction direction) {
|
|
if (destination instanceof WorldlyContainer worldlyContainer && direction != null) {
|
|
int[] is = worldlyContainer.getSlotsForFace(direction);
|
|
|
|
for (int i = 0; i < is.length && !stack.isEmpty(); i++) {
|
|
stack = tryMoveInItem(source, destination, stack, is[i], direction);
|
|
}
|
|
} else {
|
|
int j = destination.getContainerSize();
|
|
|
|
for (int i = 0; i < j && !stack.isEmpty(); i++) {
|
|
stack = tryMoveInItem(source, destination, stack, i, direction);
|
|
}
|
|
}
|
|
|
|
return stack;
|
|
}
|
|
|
|
private static boolean canPlaceItemInContainer(Container container, ItemStack stack, int slot, @Nullable Direction direction) {
|
|
return !container.canPlaceItem(slot, stack)
|
|
? false
|
|
: !(container instanceof WorldlyContainer worldlyContainer && !worldlyContainer.canPlaceItemThroughFace(slot, stack, direction));
|
|
}
|
|
|
|
private static boolean canTakeItemFromContainer(Container source, Container destination, ItemStack stack, int slot, Direction direction) {
|
|
return !destination.canTakeItem(source, slot, stack)
|
|
? false
|
|
: !(destination instanceof WorldlyContainer worldlyContainer && !worldlyContainer.canTakeItemThroughFace(slot, stack, direction));
|
|
}
|
|
|
|
private static ItemStack tryMoveInItem(@Nullable Container source, Container destination, ItemStack stack, int slot, @Nullable Direction direction) {
|
|
ItemStack itemStack = destination.getItem(slot);
|
|
if (canPlaceItemInContainer(destination, stack, slot, direction)) {
|
|
boolean bl = false;
|
|
boolean bl2 = destination.isEmpty();
|
|
if (itemStack.isEmpty()) {
|
|
destination.setItem(slot, stack);
|
|
stack = ItemStack.EMPTY;
|
|
bl = true;
|
|
} else if (canMergeItems(itemStack, stack)) {
|
|
int i = stack.getMaxStackSize() - itemStack.getCount();
|
|
int j = Math.min(stack.getCount(), i);
|
|
stack.shrink(j);
|
|
itemStack.grow(j);
|
|
bl = j > 0;
|
|
}
|
|
|
|
if (bl) {
|
|
if (bl2 && destination instanceof HopperBlockEntity hopperBlockEntity && !hopperBlockEntity.isOnCustomCooldown()) {
|
|
int j = 0;
|
|
if (source instanceof HopperBlockEntity hopperBlockEntity2 && hopperBlockEntity.tickedGameTime >= hopperBlockEntity2.tickedGameTime) {
|
|
j = 1;
|
|
}
|
|
|
|
hopperBlockEntity.setCooldown(8 - j);
|
|
}
|
|
|
|
destination.setChanged();
|
|
}
|
|
}
|
|
|
|
return stack;
|
|
}
|
|
|
|
@Nullable
|
|
private static Container getAttachedContainer(Level level, BlockPos pos, HopperBlockEntity blockEntity) {
|
|
return getContainerAt(level, pos.relative(blockEntity.facing));
|
|
}
|
|
|
|
@Nullable
|
|
private static Container getSourceContainer(Level level, Hopper hopper, BlockPos pos, BlockState state) {
|
|
return getContainerAt(level, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ());
|
|
}
|
|
|
|
public static List<ItemEntity> getItemsAtAndAbove(Level level, Hopper hopper) {
|
|
AABB aABB = hopper.getSuckAabb().move(hopper.getLevelX() - 0.5, hopper.getLevelY() - 0.5, hopper.getLevelZ() - 0.5);
|
|
return level.getEntitiesOfClass(ItemEntity.class, aABB, EntitySelector.ENTITY_STILL_ALIVE);
|
|
}
|
|
|
|
@Nullable
|
|
public static Container getContainerAt(Level level, BlockPos pos) {
|
|
return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
|
}
|
|
|
|
@Nullable
|
|
private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z) {
|
|
Container container = getBlockContainer(level, pos, state);
|
|
if (container == null) {
|
|
container = getEntityContainer(level, x, y, z);
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
@Nullable
|
|
private static Container getBlockContainer(Level level, BlockPos pos, BlockState state) {
|
|
Block block = state.getBlock();
|
|
if (block instanceof WorldlyContainerHolder) {
|
|
return ((WorldlyContainerHolder)block).getContainer(state, level, pos);
|
|
} else if (state.hasBlockEntity() && level.getBlockEntity(pos) instanceof Container container) {
|
|
if (container instanceof ChestBlockEntity && block instanceof ChestBlock) {
|
|
container = ChestBlock.getContainer((ChestBlock)block, state, level, pos, true);
|
|
}
|
|
|
|
return container;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
private static Container getEntityContainer(Level level, double x, double y, double z) {
|
|
List<Entity> list = level.getEntities((Entity)null, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR);
|
|
return !list.isEmpty() ? (Container)list.get(level.random.nextInt(list.size())) : null;
|
|
}
|
|
|
|
private static boolean canMergeItems(ItemStack stack1, ItemStack stack2) {
|
|
return stack1.getCount() <= stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2);
|
|
}
|
|
|
|
@Override
|
|
public double getLevelX() {
|
|
return this.worldPosition.getX() + 0.5;
|
|
}
|
|
|
|
@Override
|
|
public double getLevelY() {
|
|
return this.worldPosition.getY() + 0.5;
|
|
}
|
|
|
|
@Override
|
|
public double getLevelZ() {
|
|
return this.worldPosition.getZ() + 0.5;
|
|
}
|
|
|
|
@Override
|
|
public boolean isGridAligned() {
|
|
return true;
|
|
}
|
|
|
|
private void setCooldown(int cooldownTime) {
|
|
this.cooldownTime = cooldownTime;
|
|
}
|
|
|
|
private boolean isOnCooldown() {
|
|
return this.cooldownTime > 0;
|
|
}
|
|
|
|
private boolean isOnCustomCooldown() {
|
|
return this.cooldownTime > 8;
|
|
}
|
|
|
|
@Override
|
|
protected NonNullList<ItemStack> getItems() {
|
|
return this.items;
|
|
}
|
|
|
|
@Override
|
|
protected void setItems(NonNullList<ItemStack> items) {
|
|
this.items = items;
|
|
}
|
|
|
|
public static void entityInside(Level level, BlockPos pos, BlockState state, Entity entity, HopperBlockEntity blockEntity) {
|
|
if (entity instanceof ItemEntity itemEntity
|
|
&& !itemEntity.getItem().isEmpty()
|
|
&& entity.getBoundingBox().move(-pos.getX(), -pos.getY(), -pos.getZ()).intersects(blockEntity.getSuckAabb())) {
|
|
tryMoveItems(level, pos, state, blockEntity, () -> addItem(blockEntity, itemEntity));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected AbstractContainerMenu createMenu(int containerId, Inventory inventory) {
|
|
return new HopperMenu(containerId, inventory, this);
|
|
}
|
|
}
|