diff --git a/src/main/java/de/ellpeck/prettypipes/compat/jei/CraftingTerminalTransferHandler.java b/src/main/java/de/ellpeck/prettypipes/compat/jei/CraftingTerminalTransferHandler.java new file mode 100644 index 0000000..894bb35 --- /dev/null +++ b/src/main/java/de/ellpeck/prettypipes/compat/jei/CraftingTerminalTransferHandler.java @@ -0,0 +1,41 @@ +package de.ellpeck.prettypipes.compat.jei; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import de.ellpeck.prettypipes.packets.PacketGhostSlot; +import de.ellpeck.prettypipes.packets.PacketHandler; +import de.ellpeck.prettypipes.terminal.CraftingTerminalTileEntity; +import de.ellpeck.prettypipes.terminal.ItemTerminalTileEntity; +import de.ellpeck.prettypipes.terminal.containers.CraftingTerminalContainer; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.gui.ingredient.IGuiIngredient; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.container.Slot; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nullable; +import java.util.Map; + +public class CraftingTerminalTransferHandler implements IRecipeTransferHandler { + @Override + public Class getContainerClass() { + return CraftingTerminalContainer.class; + } + + @Nullable + @Override + public IRecipeTransferError transferRecipe(CraftingTerminalContainer container, IRecipeLayout recipeLayout, PlayerEntity player, boolean maxTransfer, boolean doTransfer) { + if (!doTransfer) + return null; + ListMultimap stacks = ArrayListMultimap.create(); + Map> ings = recipeLayout.getItemStacks().getGuiIngredients(); + for (Map.Entry> entry : ings.entrySet()) { + if (entry.getValue().isInput()) + stacks.putAll(entry.getKey() - 1, entry.getValue().getAllIngredients()); + } + PacketHandler.sendToServer(new PacketGhostSlot(container.getTile().getPos(), stacks)); + return null; + } +} diff --git a/src/main/java/de/ellpeck/prettypipes/compat/jei/JEIPrettyPipesPlugin.java b/src/main/java/de/ellpeck/prettypipes/compat/jei/JEIPrettyPipesPlugin.java index d832f04..4dbb232 100644 --- a/src/main/java/de/ellpeck/prettypipes/compat/jei/JEIPrettyPipesPlugin.java +++ b/src/main/java/de/ellpeck/prettypipes/compat/jei/JEIPrettyPipesPlugin.java @@ -5,6 +5,8 @@ import de.ellpeck.prettypipes.misc.PlayerPrefs; import de.ellpeck.prettypipes.terminal.containers.ItemTerminalGui; import mezz.jei.api.IModPlugin; import mezz.jei.api.JeiPlugin; +import mezz.jei.api.constants.VanillaRecipeCategoryUid; +import mezz.jei.api.registration.IRecipeTransferRegistration; import mezz.jei.api.runtime.IIngredientFilter; import mezz.jei.api.runtime.IJeiRuntime; import net.minecraft.client.Minecraft; @@ -46,6 +48,11 @@ public class JEIPrettyPipesPlugin implements IModPlugin { this.runtime = jeiRuntime; } + @Override + public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) { + registration.addRecipeTransferHandler(new CraftingTerminalTransferHandler(), VanillaRecipeCategoryUid.CRAFTING); + } + @SubscribeEvent public void onInitGui(InitGuiEvent.Post event) { Screen screen = event.getGui(); diff --git a/src/main/java/de/ellpeck/prettypipes/packets/PacketGhostSlot.java b/src/main/java/de/ellpeck/prettypipes/packets/PacketGhostSlot.java new file mode 100644 index 0000000..1293531 --- /dev/null +++ b/src/main/java/de/ellpeck/prettypipes/packets/PacketGhostSlot.java @@ -0,0 +1,65 @@ +package de.ellpeck.prettypipes.packets; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import de.ellpeck.prettypipes.Utility; +import de.ellpeck.prettypipes.terminal.CraftingTerminalTileEntity; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class PacketGhostSlot { + + private BlockPos pos; + private ListMultimap stacks; + + public PacketGhostSlot(BlockPos pos, ListMultimap stacks) { + this.pos = pos; + this.stacks = stacks; + } + + private PacketGhostSlot() { + + } + + public static PacketGhostSlot fromBytes(PacketBuffer buf) { + PacketGhostSlot packet = new PacketGhostSlot(); + packet.pos = buf.readBlockPos(); + packet.stacks = ArrayListMultimap.create(); + for (int i = buf.readInt(); i > 0; i--) + packet.stacks.put(buf.readInt(), buf.readItemStack()); + return packet; + } + + public static void toBytes(PacketGhostSlot packet, PacketBuffer buf) { + buf.writeBlockPos(packet.pos); + buf.writeInt(packet.stacks.size()); + for (Map.Entry entry : packet.stacks.entries()) { + buf.writeInt(entry.getKey()); + buf.writeItemStack(entry.getValue()); + } + } + + @SuppressWarnings("Convert2Lambda") + public static void onMessage(PacketGhostSlot message, Supplier ctx) { + ctx.get().enqueueWork(new Runnable() { + @Override + public void run() { + PlayerEntity player = ctx.get().getSender(); + if (player == null) + player = Minecraft.getInstance().player; + CraftingTerminalTileEntity tile = Utility.getTileEntity(CraftingTerminalTileEntity.class, player.world, message.pos); + if (tile != null) + tile.setGhostItems(message.stacks); + } + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/de/ellpeck/prettypipes/packets/PacketHandler.java b/src/main/java/de/ellpeck/prettypipes/packets/PacketHandler.java index a683e34..380ee47 100644 --- a/src/main/java/de/ellpeck/prettypipes/packets/PacketHandler.java +++ b/src/main/java/de/ellpeck/prettypipes/packets/PacketHandler.java @@ -22,6 +22,7 @@ public final class PacketHandler { network.registerMessage(1, PacketButton.class, PacketButton::toBytes, PacketButton::fromBytes, PacketButton::onMessage); network.registerMessage(2, PacketNetworkItems.class, PacketNetworkItems::toBytes, PacketNetworkItems::fromBytes, PacketNetworkItems::onMessage); network.registerMessage(3, PacketRequest.class, PacketRequest::toBytes, PacketRequest::fromBytes, PacketRequest::onMessage); + network.registerMessage(4, PacketGhostSlot.class, PacketGhostSlot::toBytes, PacketGhostSlot::fromBytes, PacketGhostSlot::onMessage); } public static void sendToAllLoaded(World world, BlockPos pos, Object message) { diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalBlock.java b/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalBlock.java index 704fa56..f7bb62d 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalBlock.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalBlock.java @@ -28,22 +28,25 @@ public class CraftingTerminalBlock extends ItemTerminalBlock { CraftingTerminalTileEntity tile = Utility.getTileEntity(CraftingTerminalTileEntity.class, world, pos); if (tile != null) { ItemStack remain = item.stack; - int lowestFitting = -1; + int lowestSlot = -1; do { for (int i = 0; i < tile.craftItems.getSlots(); i++) { ItemStack stack = tile.getRequestedCraftItem(i); + int count = tile.isGhostItem(i) ? 0 : stack.getCount(); if (!ItemHandlerHelper.canItemStacksStackRelaxed(stack, remain)) continue; - if (lowestFitting < 0 || stack.getCount() < tile.getRequestedCraftItem(lowestFitting).getCount()) - lowestFitting = i; + if (lowestSlot < 0 || !tile.isGhostItem(lowestSlot) && count < tile.getRequestedCraftItem(lowestSlot).getCount()) + lowestSlot = i; } - if (lowestFitting >= 0) { - remain = tile.craftItems.insertItem(lowestFitting, remain, false); + if (lowestSlot >= 0) { + ItemStack copy = remain.copy(); + copy.setCount(1); + remain.shrink(1 - tile.craftItems.insertItem(lowestSlot, copy, false).getCount()); if (remain.isEmpty()) return ItemStack.EMPTY; } } - while (lowestFitting >= 0); + while (lowestSlot >= 0); return ItemHandlerHelper.insertItemStacked(tile.items, remain, false); } return item.stack; diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalTileEntity.java b/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalTileEntity.java index ec2abab..6372745 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalTileEntity.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/CraftingTerminalTileEntity.java @@ -1,5 +1,7 @@ package de.ellpeck.prettypipes.terminal; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import de.ellpeck.prettypipes.PrettyPipes; import de.ellpeck.prettypipes.Registry; import de.ellpeck.prettypipes.misc.EquatableItemStack; @@ -8,6 +10,8 @@ import de.ellpeck.prettypipes.network.NetworkItem; import de.ellpeck.prettypipes.network.NetworkLocation; import de.ellpeck.prettypipes.network.NetworkLock; import de.ellpeck.prettypipes.network.PipeNetwork; +import de.ellpeck.prettypipes.packets.PacketGhostSlot; +import de.ellpeck.prettypipes.packets.PacketHandler; import de.ellpeck.prettypipes.terminal.containers.CraftingTerminalContainer; import de.ellpeck.prettypipes.terminal.containers.ItemTerminalContainer; import net.minecraft.entity.player.PlayerEntity; @@ -23,23 +27,69 @@ import net.minecraftforge.items.ItemStackHandler; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.Pair; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class CraftingTerminalTileEntity extends ItemTerminalTileEntity { - public final ItemStackHandler craftItems = new ItemStackHandler(9); + public final ItemStackHandler craftItems = new ItemStackHandler(9) { + @Override + protected void onContentsChanged(int slot) { + for (PlayerEntity playerEntity : CraftingTerminalTileEntity.this.getLookingPlayers()) + playerEntity.openContainer.onCraftMatrixChanged(null); + } + }; + public final ItemStackHandler ghostItems = new ItemStackHandler(9); public CraftingTerminalTileEntity() { super(Registry.craftingTerminalTileEntity); } public ItemStack getRequestedCraftItem(int slot) { - // TODO put ghost slot contents here - return this.craftItems.getStackInSlot(slot); + ItemStack stack = this.craftItems.getStackInSlot(slot); + 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(); + } + + public void setGhostItems(ListMultimap stacks) { + this.updateItems(); + items: + for (int i = 0; i < this.ghostItems.getSlots(); i++) { + List items = stacks.get(i); + if (items.isEmpty()) { + this.ghostItems.setStackInSlot(i, ItemStack.EMPTY); + continue; + } + if (items.size() > 1) { + // set the item into the ghost slot that already has a variant of itself available in the system + for (ItemStack stack : items) { + EquatableItemStack equatable = new EquatableItemStack(stack); + NetworkItem network = this.networkItems.get(equatable); + if (network == null) + continue; + if (network.getLocations().stream().anyMatch(l -> l.getItemAmount(this.world, stack, ItemEqualityType.NBT) > 0)) { + this.ghostItems.setStackInSlot(i, stack); + continue items; + } + } + } + // if the ghost slot wasn't set, then we don't have the item in the system + // so just pick a random one to put into the slot + this.ghostItems.setStackInSlot(i, items.get(0)); + } + + if (!this.world.isRemote) { + ListMultimap clients = ArrayListMultimap.create(); + for (int i = 0; i < this.ghostItems.getSlots(); i++) + clients.put(i, this.ghostItems.getStackInSlot(i)); + PacketHandler.sendToAllLoaded(this.world, this.pos, new PacketGhostSlot(this.pos, clients)); + } } public void requestCraftingItems(PlayerEntity player, boolean all) { @@ -47,6 +97,8 @@ public class CraftingTerminalTileEntity extends ItemTerminalTileEntity { network.startProfile("terminal_request_crafting"); this.updateItems(); + // the highest amount we can craft with the items we have + int lowestAvailable = Integer.MAX_VALUE; // this is the amount of items required for each ingredient when crafting ONE Map requiredItems = new HashMap<>(); for (int i = 0; i < this.craftItems.getSlots(); i++) { @@ -55,15 +107,16 @@ public class CraftingTerminalTileEntity extends ItemTerminalTileEntity { continue; MutableInt amount = requiredItems.computeIfAbsent(new EquatableItemStack(requested), s -> new MutableInt()); amount.add(1); + int fit = requested.getMaxStackSize() - (this.isGhostItem(i) ? 0 : requested.getCount()); + if (lowestAvailable > fit) + lowestAvailable = fit; } - // the highest amount we can craft with the items we have - int lowestAvailable = Integer.MAX_VALUE; for (Map.Entry entry : requiredItems.entrySet()) { EquatableItemStack stack = entry.getKey(); NetworkItem item = this.networkItems.get(stack); + // total amount of available items of this type + int available = 0; if (item != null) { - // total amount of available items of this type - int available = 0; for (NetworkLocation location : item.getLocations()) { for (int slot : location.getStackSlots(this.world, stack.stack, ItemEqualityType.NBT)) { ItemStack inSlot = location.getItemHandler(this.world).extractItem(slot, Integer.MAX_VALUE, true); @@ -76,15 +129,12 @@ public class CraftingTerminalTileEntity extends ItemTerminalTileEntity { // 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(); - int fit = stack.stack.getMaxStackSize() - stack.stack.getCount(); - if (available > fit) - available = fit; if (available < lowestAvailable) lowestAvailable = available; } else { lowestAvailable = 0; } - if (lowestAvailable <= 0) + if (available <= 0) player.sendMessage(new TranslationTextComponent("info." + PrettyPipes.ID + ".not_found", stack.stack.getDisplayName()).setStyle(new Style().setColor(TextFormatting.RED))); } if (lowestAvailable > 0) { diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalTileEntity.java b/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalTileEntity.java index 13f4ad0..7f2fb08 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalTileEntity.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalTileEntity.java @@ -177,7 +177,7 @@ public class ItemTerminalTileEntity extends TileEntity implements INamedContaine return 0; } - private PlayerEntity[] getLookingPlayers() { + protected PlayerEntity[] getLookingPlayers() { return this.world.getPlayers().stream() .filter(p -> p.openContainer instanceof ItemTerminalContainer) .filter(p -> ((ItemTerminalContainer) p.openContainer).tile == this) diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalContainer.java b/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalContainer.java index 2f883a1..5dddb40 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalContainer.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalContainer.java @@ -8,6 +8,7 @@ import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.CraftResultInventory; import net.minecraft.inventory.CraftingInventory; import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.container.ClickType; import net.minecraft.inventory.container.ContainerType; import net.minecraft.inventory.container.CraftingResultSlot; import net.minecraft.inventory.container.Slot; @@ -68,7 +69,17 @@ public class CraftingTerminalContainer extends ItemTerminalContainer { return 65; } - protected CraftingTerminalTileEntity getTile() { + @Override + public ItemStack slotClick(int slotId, int dragType, ClickType clickTypeIn, PlayerEntity player) { + if (slotId > 0) { + Slot slot = this.inventorySlots.get(slotId); + if (slot.inventory == this.craftInventory) + this.getTile().ghostItems.setStackInSlot(slot.getSlotIndex(), ItemStack.EMPTY); + } + return super.slotClick(slotId, dragType, clickTypeIn, player); + } + + public CraftingTerminalTileEntity getTile() { return (CraftingTerminalTileEntity) this.tile; } } diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalGui.java b/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalGui.java index 4018250..d2e15fb 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalGui.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/containers/CraftingTerminalGui.java @@ -1,5 +1,7 @@ package de.ellpeck.prettypipes.terminal.containers; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; import de.ellpeck.prettypipes.PrettyPipes; import de.ellpeck.prettypipes.packets.PacketButton; import de.ellpeck.prettypipes.packets.PacketHandler; @@ -9,6 +11,7 @@ import net.minecraft.client.gui.widget.Widget; import net.minecraft.client.gui.widget.button.Button; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.Slot; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; @@ -29,13 +32,41 @@ public class CraftingTerminalGui extends ItemTerminalGui { int all = hasShiftDown() ? 1 : 0; PacketHandler.sendToServer(new PacketButton(this.container.tile.getPos(), PacketButton.ButtonResult.CRAFT_TERMINAL_REQUEST, all)); })); - this.requestButton.active = !this.getCraftingContainer().craftInventory.isEmpty(); + this.requestButton.active = false; } @Override public void tick() { super.tick(); - this.requestButton.active = !this.getCraftingContainer().craftInventory.isEmpty(); + CraftingTerminalTileEntity tile = this.getCraftingContainer().getTile(); + this.requestButton.active = false; + for (int i = 0; i < tile.craftItems.getSlots(); i++) { + if (!tile.getRequestedCraftItem(i).isEmpty()) { + this.requestButton.active = true; + break; + } + } + } + + @Override + protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { + super.drawGuiContainerForegroundLayer(mouseX, mouseY); + + CraftingTerminalContainer container = this.getCraftingContainer(); + CraftingTerminalTileEntity tile = container.getTile(); + for (int i = 0; i < tile.ghostItems.getSlots(); i++) { + if (!tile.craftItems.getStackInSlot(i).isEmpty()) + continue; + ItemStack ghost = tile.ghostItems.getStackInSlot(i); + if (ghost.isEmpty()) + continue; + int finalI = i; + Slot slot = container.inventorySlots.stream().filter(s -> s.inventory == container.craftInventory && s.getSlotIndex() == finalI).findFirst().orElse(null); + if (slot == null) + continue; + this.minecraft.getItemRenderer().renderItemIntoGUI(ghost, slot.xPos, slot.yPos); + this.minecraft.getItemRenderer().renderItemOverlayIntoGUI(this.font, ghost, slot.xPos, slot.yPos, "0"); + } } @Override diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/containers/ItemTerminalGui.java b/src/main/java/de/ellpeck/prettypipes/terminal/containers/ItemTerminalGui.java index eb120f9..46afd76 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/containers/ItemTerminalGui.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/containers/ItemTerminalGui.java @@ -96,6 +96,8 @@ public class ItemTerminalGui extends ContainerScreen { this.search = this.addButton(new TextFieldWidget(this.font, this.guiLeft + this.getXOffset() + 97, this.guiTop + 6, 86, 8, "")); this.search.setEnableBackgroundDrawing(false); this.lastSearchText = ""; + if (this.items != null) + this.updateWidgets(); } protected int getXOffset() {