PrettyPipes/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalBlockEntity.java

248 lines
12 KiB
Java
Raw Normal View History

2020-05-08 22:58:16 +02:00
package de.ellpeck.prettypipes.terminal;
import de.ellpeck.prettypipes.PrettyPipes;
import de.ellpeck.prettypipes.Registry;
2020-10-17 14:29:37 +02:00
import de.ellpeck.prettypipes.Utility;
2020-05-09 13:40:46 +02:00
import de.ellpeck.prettypipes.misc.EquatableItemStack;
2021-03-03 01:56:19 +01:00
import de.ellpeck.prettypipes.misc.ItemEquality;
2020-05-09 13:40:46 +02:00
import de.ellpeck.prettypipes.network.NetworkLocation;
import de.ellpeck.prettypipes.network.PipeNetwork;
2020-05-09 14:56:58 +02:00
import de.ellpeck.prettypipes.packets.PacketGhostSlot;
import de.ellpeck.prettypipes.packets.PacketHandler;
2021-12-02 14:44:26 +01:00
import de.ellpeck.prettypipes.pipe.PipeBlockEntity;
2020-05-08 22:58:16 +02:00
import de.ellpeck.prettypipes.terminal.containers.CraftingTerminalContainer;
2021-12-02 16:55:04 +01:00
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.world.entity.player.Inventory;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.entity.player.Player;
2021-12-02 16:55:04 +01:00
import net.minecraft.world.inventory.AbstractContainerMenu;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.item.ItemStack;
2021-12-02 16:55:04 +01:00
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.items.ItemStackHandler;
2020-05-09 13:40:46 +02:00
import org.apache.commons.lang3.mutable.MutableInt;
2020-05-08 22:58:16 +02:00
import javax.annotation.Nullable;
2020-05-09 14:56:58 +02:00
import java.util.*;
2020-10-14 23:39:11 +02:00
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
2020-05-08 22:58:16 +02:00
2021-12-02 14:44:26 +01:00
public class CraftingTerminalBlockEntity extends ItemTerminalBlockEntity {
2020-05-08 22:58:16 +02:00
2020-05-09 14:56:58 +02:00
public final ItemStackHandler craftItems = new ItemStackHandler(9) {
@Override
protected void onContentsChanged(int slot) {
2021-12-02 16:55:04 +01:00
for (var playerEntity : CraftingTerminalBlockEntity.this.getLookingPlayers())
playerEntity.containerMenu.slotsChanged(null);
2020-05-09 14:56:58 +02:00
}
};
public final ItemStackHandler ghostItems = new ItemStackHandler(9);
2021-12-02 16:55:04 +01:00
public CraftingTerminalBlockEntity(BlockPos pos, BlockState state) {
super(Registry.craftingTerminalBlockEntity, pos, state);
2020-05-08 22:58:16 +02:00
}
public ItemStack getRequestedCraftItem(int slot) {
2021-12-02 16:55:04 +01:00
var stack = this.craftItems.getStackInSlot(slot);
2020-05-09 14:56:58 +02:00
if (!stack.isEmpty())
return stack;
return this.ghostItems.getStackInSlot(slot);
}
public boolean isGhostItem(int slot) {
return this.craftItems.getStackInSlot(slot).isEmpty() && !this.ghostItems.getStackInSlot(slot).isEmpty();
}
2022-06-27 14:19:51 +02:00
public void setGhostItems(List<PacketGhostSlot.Entry> stacks) {
2020-05-09 14:56:58 +02:00
this.updateItems();
2021-12-02 16:55:04 +01:00
for (var i = 0; i < this.ghostItems.getSlots(); i++) {
2023-07-07 19:54:52 +02:00
var items = stacks.get(i).getStacks(this.level);
2020-05-09 14:56:58 +02:00
if (items.isEmpty()) {
this.ghostItems.setStackInSlot(i, ItemStack.EMPTY);
continue;
}
2021-12-02 16:55:04 +01:00
var toSet = items.get(0);
// if we have more than one item to choose from, we want to pick the one that we have most of in the system
2020-05-09 14:56:58 +02:00
if (items.size() > 1) {
2021-12-02 16:55:04 +01:00
var highestAmount = 0;
for (var stack : items) {
var amount = 0;
// check existing items
2021-12-02 16:55:04 +01:00
var network = this.networkItems.get(new EquatableItemStack(stack, ItemEquality.NBT));
if (network != null) {
amount = network.getLocations().stream()
2021-12-02 16:55:04 +01:00
.mapToInt(l -> l.getItemAmount(this.level, stack, ItemEquality.NBT))
.sum();
}
// check craftables
if (amount <= 0 && highestAmount <= 0) {
2021-12-02 16:55:04 +01:00
var pipe = this.getConnectedPipe();
if (pipe != null)
2021-12-02 16:55:04 +01:00
amount = PipeNetwork.get(this.level).getCraftableAmount(pipe.getBlockPos(), null, stack, new Stack<>(), ItemEquality.NBT);
}
if (amount > highestAmount) {
highestAmount = amount;
toSet = stack;
2020-05-09 14:56:58 +02:00
}
}
}
this.ghostItems.setStackInSlot(i, toSet.copy());
2020-05-09 14:56:58 +02:00
}
2021-12-02 16:55:04 +01:00
if (!this.level.isClientSide) {
2022-06-27 14:19:51 +02:00
List<PacketGhostSlot.Entry> clients = new ArrayList<>();
2021-12-02 16:55:04 +01:00
for (var i = 0; i < this.ghostItems.getSlots(); i++)
2023-07-07 19:54:52 +02:00
clients.add(new PacketGhostSlot.Entry(this.level, Collections.singletonList(this.ghostItems.getStackInSlot(i))));
2021-12-02 16:55:04 +01:00
PacketHandler.sendToAllLoaded(this.level, this.getBlockPos(), new PacketGhostSlot(this.getBlockPos(), clients));
2020-05-09 14:56:58 +02:00
}
}
public void requestCraftingItems(Player player, int maxAmount, boolean force) {
2021-12-02 16:55:04 +01:00
var pipe = this.getConnectedPipe();
2020-10-15 04:15:52 +02:00
if (pipe == null)
return;
2021-12-02 16:55:04 +01:00
var network = PipeNetwork.get(this.level);
2020-05-09 13:40:46 +02:00
network.startProfile("terminal_request_crafting");
this.updateItems();
2020-10-14 23:39:11 +02:00
// get the amount of crafts that we can do
2022-06-27 13:57:06 +02:00
var lowestAvailable = CraftingTerminalBlockEntity.getAvailableCrafts(pipe, this.craftItems.getSlots(), i -> ItemHandlerHelper.copyStackWithSize(this.getRequestedCraftItem(i), 1), this::isGhostItem, s -> {
2021-12-02 16:55:04 +01:00
var item = this.networkItems.get(s);
2020-10-14 23:39:11 +02:00
return item != null ? item.getLocations() : Collections.emptyList();
2022-06-27 13:57:06 +02:00
}, ItemTerminalBlockEntity.onItemUnavailable(player, force), new Stack<>(), ItemEquality.NBT);
// if we're forcing, just pretend we have one available
if (lowestAvailable <= 0 && force)
lowestAvailable = maxAmount;
2020-05-09 13:40:46 +02:00
if (lowestAvailable > 0) {
// if we're limiting the amount, pretend we only have that amount available
if (maxAmount < lowestAvailable)
lowestAvailable = maxAmount;
2021-12-02 16:55:04 +01:00
for (var i = 0; i < this.craftItems.getSlots(); i++) {
var requested = this.getRequestedCraftItem(i);
2020-05-09 13:40:46 +02:00
if (requested.isEmpty())
continue;
requested = requested.copy();
requested.setCount(lowestAvailable);
2022-06-27 13:57:06 +02:00
this.requestItemImpl(requested, ItemTerminalBlockEntity.onItemUnavailable(player, force));
2020-05-09 13:40:46 +02:00
}
2022-06-27 13:57:06 +02:00
player.sendSystemMessage(Component.translatable("info." + PrettyPipes.ID + ".sending_ingredients", lowestAvailable).setStyle(Style.EMPTY.applyFormat(ChatFormatting.GREEN)));
} else {
2022-06-27 13:57:06 +02:00
player.sendSystemMessage(Component.translatable("info." + PrettyPipes.ID + ".hold_alt"));
}
2020-05-09 13:40:46 +02:00
network.endProfile();
}
@Override
public void saveAdditional(CompoundTag compound) {
super.saveAdditional(compound);
compound.put("craft_items", this.craftItems.serializeNBT());
}
@Override
2021-12-02 16:55:04 +01:00
public void load(CompoundTag compound) {
this.craftItems.deserializeNBT(compound.getCompound("craft_items"));
2021-12-02 16:55:04 +01:00
super.load(compound);
}
2020-05-08 22:58:16 +02:00
@Override
2021-12-02 16:55:04 +01:00
public Component getDisplayName() {
2022-06-27 13:57:06 +02:00
return Component.translatable("container." + PrettyPipes.ID + ".crafting_terminal");
2020-05-08 22:58:16 +02:00
}
@Nullable
@Override
2021-12-02 16:55:04 +01:00
public AbstractContainerMenu createMenu(int window, Inventory inv, Player player) {
return new CraftingTerminalContainer(Registry.craftingTerminalContainer, window, player, this.worldPosition);
2020-05-08 22:58:16 +02:00
}
2020-10-14 23:39:11 +02:00
2020-10-17 14:29:37 +02:00
@Override
public ItemStack insertItem(BlockPos pipePos, Direction direction, ItemStack remain, boolean simulate) {
2021-12-02 16:55:04 +01:00
var pos = pipePos.relative(direction);
var tile = Utility.getBlockEntity(CraftingTerminalBlockEntity.class, this.level, pos);
2020-10-17 14:29:37 +02:00
if (tile != null) {
remain = remain.copy();
2021-12-02 16:55:04 +01:00
var lowestSlot = -1;
2020-10-17 14:29:37 +02:00
do {
2021-12-02 16:55:04 +01:00
for (var i = 0; i < tile.craftItems.getSlots(); i++) {
var stack = tile.getRequestedCraftItem(i);
var count = tile.isGhostItem(i) ? 0 : stack.getCount();
if (!ItemHandlerHelper.canItemStacksStack(stack, remain))
continue;
// ensure that a single non-stackable item can still enter a ghost slot
if (!stack.isStackable() && count >= 1)
2020-10-17 14:29:37 +02:00
continue;
if (lowestSlot < 0 || !tile.isGhostItem(lowestSlot) && count < tile.getRequestedCraftItem(lowestSlot).getCount())
lowestSlot = i;
}
if (lowestSlot >= 0) {
2021-12-02 16:55:04 +01:00
var copy = remain.copy();
2020-10-17 14:29:37 +02:00
copy.setCount(1);
// if there were remaining items inserting into the slot with lowest contents, we're overflowing
if (tile.craftItems.insertItem(lowestSlot, copy, simulate).getCount() > 0)
break;
remain.shrink(1);
2020-10-17 14:29:37 +02:00
if (remain.isEmpty())
return ItemStack.EMPTY;
}
}
while (lowestSlot >= 0);
return ItemHandlerHelper.insertItemStacked(tile.items, remain, simulate);
}
return remain;
}
2021-12-02 14:44:26 +01:00
public static int getAvailableCrafts(PipeBlockEntity tile, int slots, Function<Integer, ItemStack> inputFunction, Predicate<Integer> isGhost, Function<EquatableItemStack, Collection<NetworkLocation>> locationsFunction, Consumer<ItemStack> unavailableConsumer, Stack<ItemStack> dependencyChain, ItemEquality... equalityTypes) {
2021-12-02 16:55:04 +01:00
var network = PipeNetwork.get(tile.getLevel());
2020-10-14 23:39:11 +02:00
// the highest amount we can craft with the items we have
2021-12-02 16:55:04 +01:00
var lowestAvailable = Integer.MAX_VALUE;
2020-10-14 23:39:11 +02:00
// this is the amount of items required for each ingredient when crafting ONE
Map<EquatableItemStack, MutableInt> requiredItems = new HashMap<>();
2021-12-02 16:55:04 +01:00
for (var i = 0; i < slots; i++) {
var requested = inputFunction.apply(i);
2020-10-14 23:39:11 +02:00
if (requested.isEmpty())
continue;
2021-12-02 16:55:04 +01:00
var amount = requiredItems.computeIfAbsent(new EquatableItemStack(requested, equalityTypes), s -> new MutableInt());
amount.add(requested.getCount());
// if no items fit into the crafting input, we still want to pretend they do for requesting
2021-12-02 16:55:04 +01:00
var fit = Math.max(requested.getMaxStackSize() - (isGhost.test(i) ? 0 : requested.getCount()), 1);
2020-10-14 23:39:11 +02:00
if (lowestAvailable > fit)
lowestAvailable = fit;
}
2021-12-02 16:55:04 +01:00
for (var entry : requiredItems.entrySet()) {
var stack = entry.getKey();
2020-10-14 23:39:11 +02:00
// total amount of available items of this type
2021-12-02 16:55:04 +01:00
var available = 0;
for (var location : locationsFunction.apply(stack)) {
var amount = location.getItemAmount(tile.getLevel(), stack.stack(), equalityTypes);
2020-10-14 23:39:11 +02:00
if (amount <= 0)
continue;
2021-12-02 16:55:04 +01:00
amount -= network.getLockedAmount(location.getPos(), stack.stack(), null, equalityTypes);
2020-10-14 23:39:11 +02:00
available += amount;
}
// divide the total by the amount required to get the amount that
// we have available for each crafting slot that contains this item
available /= entry.getValue().intValue();
2020-10-15 04:15:52 +02:00
// check how many craftable items we have and add those on if we need to
if (available < lowestAvailable) {
2021-12-02 16:55:04 +01:00
var craftable = network.getCraftableAmount(tile.getBlockPos(), unavailableConsumer, stack.stack(), dependencyChain, equalityTypes);
2020-10-15 04:15:52 +02:00
if (craftable > 0)
available += craftable / entry.getValue().intValue();
2020-10-14 23:39:11 +02:00
}
2021-12-02 16:55:04 +01:00
// clamp to the lowest available
2020-10-14 23:39:11 +02:00
if (available < lowestAvailable)
lowestAvailable = available;
if (available <= 0 && unavailableConsumer != null)
2021-12-02 16:55:04 +01:00
unavailableConsumer.accept(stack.stack());
2020-10-14 23:39:11 +02:00
}
return lowestAvailable;
}
2020-05-08 22:58:16 +02:00
}