diff --git a/dependencies.gradle b/dependencies.gradle index ec6767e637c..7a1dea7d03f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -42,6 +42,7 @@ dependencies { api("codechicken:codechickenlib:3.2.3.358") api("com.cleanroommc:modularui:2.5.0-rc3") { transitive = false } api("com.cleanroommc:groovyscript:1.2.0-hotfix1") { transitive = false } + api("curse.maven:inventory-bogosorter-632327:4951607-deobf-4951608-sources-4951609") api("CraftTweaker2:CraftTweaker2-MC1120-Main:1.12-4.1.20.700") api("appeng:ae2-uel:v0.56.4") { transitive = false } api rfg.deobf("curse.maven:ctm-267602:2915363") // CTM 1.0.2.31 diff --git a/src/main/java/gregtech/api/capability/GregtechDataCodes.java b/src/main/java/gregtech/api/capability/GregtechDataCodes.java index 19058699a19..b2e7077cc59 100644 --- a/src/main/java/gregtech/api/capability/GregtechDataCodes.java +++ b/src/main/java/gregtech/api/capability/GregtechDataCodes.java @@ -46,6 +46,9 @@ public static int assignId() { // Misc TEs (Transformer, World Accelerator) public static final int SYNC_TILE_MODE = assignId(); + // Crafting Station + public static final int UPDATE_CLIENT_HANDLER = assignId(); + // Clipboard public static final int CREATE_FAKE_UI = assignId(); public static final int MOUSE_POSITION = assignId(); diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index c696ee00759..08f8a70957b 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -16,7 +18,7 @@ public class ItemHandlerList implements IItemHandlerModifiable { private final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); - private final Map baseIndexOffset = new IdentityHashMap<>(); + private final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); public ItemHandlerList(List itemHandlerList) { int currentSlotIndex = 0; @@ -33,6 +35,10 @@ public ItemHandlerList(List itemHandlerList) { } } + public int getIndexOffset(IItemHandler handler) { + return baseIndexOffset.getOrDefault(handler, -1); + } + @Override public int getSlots() { return handlerBySlotIndex.size(); @@ -40,22 +46,27 @@ public int getSlots() { @Override public void setStackInSlot(int slot, @NotNull ItemStack stack) { + if (invalidSlot(slot)) return; IItemHandler itemHandler = handlerBySlotIndex.get(slot); - if (!(itemHandler instanceof IItemHandlerModifiable)) - throw new UnsupportedOperationException("Handler " + itemHandler + " does not support this method"); - ((IItemHandlerModifiable) itemHandler).setStackInSlot(slot - baseIndexOffset.get(itemHandler), stack); + if (itemHandler instanceof IItemHandlerModifiable modifiable) { + modifiable.setStackInSlot(slot - baseIndexOffset.get(itemHandler), stack); + } else { + itemHandler.extractItem(slot, Integer.MAX_VALUE, false); + itemHandler.insertItem(slot, stack, false); + } } @NotNull @Override public ItemStack getStackInSlot(int slot) { + if (invalidSlot(slot)) return ItemStack.EMPTY; IItemHandler itemHandler = handlerBySlotIndex.get(slot); - int realSlot = slot - baseIndexOffset.get(itemHandler); return itemHandler.getStackInSlot(slot - baseIndexOffset.get(itemHandler)); } @Override public int getSlotLimit(int slot) { + if (invalidSlot(slot)) return 0; IItemHandler itemHandler = handlerBySlotIndex.get(slot); return itemHandler.getSlotLimit(slot - baseIndexOffset.get(itemHandler)); } @@ -63,6 +74,7 @@ public int getSlotLimit(int slot) { @NotNull @Override public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { + if (invalidSlot(slot)) return stack; IItemHandler itemHandler = handlerBySlotIndex.get(slot); return itemHandler.insertItem(slot - baseIndexOffset.get(itemHandler), stack, simulate); } @@ -70,6 +82,7 @@ public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate @NotNull @Override public ItemStack extractItem(int slot, int amount, boolean simulate) { + if (invalidSlot(slot)) return ItemStack.EMPTY; IItemHandler itemHandler = handlerBySlotIndex.get(slot); return itemHandler.extractItem(slot - baseIndexOffset.get(itemHandler), amount, simulate); } @@ -78,4 +91,8 @@ public ItemStack extractItem(int slot, int amount, boolean simulate) { public Collection getBackingHandlers() { return Collections.unmodifiableCollection(handlerBySlotIndex.values()); } + + private boolean invalidSlot(int slot) { + return slot < 0 && slot >= this.getSlots(); + } } diff --git a/src/main/java/gregtech/api/gui/widgets/CraftingStationInputWidgetGroup.java b/src/main/java/gregtech/api/gui/widgets/CraftingStationInputWidgetGroup.java deleted file mode 100644 index 0f95c5af62b..00000000000 --- a/src/main/java/gregtech/api/gui/widgets/CraftingStationInputWidgetGroup.java +++ /dev/null @@ -1,68 +0,0 @@ -package gregtech.api.gui.widgets; - -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.IRenderContext; -import gregtech.api.gui.Widget; -import gregtech.api.util.Position; -import gregtech.common.metatileentities.storage.CraftingRecipeLogic; - -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.items.ItemStackHandler; - -public class CraftingStationInputWidgetGroup extends AbstractWidgetGroup { - - protected CraftingRecipeLogic recipeResolver; - protected short tintLocations; - public static final int LIGHT_RED = 0x66FF0000; - - public CraftingStationInputWidgetGroup(int x, int y, ItemStackHandler craftingGrid, - CraftingRecipeLogic recipeResolver) { - super(new Position(x, y)); - - // crafting grid - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - this.addWidget(new PhantomSlotWidget(craftingGrid, j + i * 3, x + j * 18, y + i * 18) - .setBackgroundTexture(GuiTextures.SLOT)); - } - } - - this.recipeResolver = recipeResolver; - } - - @Override - public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRenderContext context) { - super.drawInBackground(mouseX, mouseY, partialTicks, context); - if (this.widgets.size() == 9) { // In case someone added more... - for (int i = 0; i < 9; i++) { - Widget widget = widgets.get(i); - if (widget instanceof PhantomSlotWidget && ((tintLocations >> i) & 1) == 0) { // In other words, is this - // slot usable? - int color = LIGHT_RED; - - PhantomSlotWidget phantomSlotWidget = (PhantomSlotWidget) widget; - drawSolidRect(phantomSlotWidget.getPosition().x + 1, phantomSlotWidget.getPosition().y + 1, - phantomSlotWidget.getSize().getWidth() - 2, phantomSlotWidget.getSize().getWidth() - 2, - color); - } - } - } - } - - @Override - public void detectAndSendChanges() { - super.detectAndSendChanges(); - short newTintLocations = recipeResolver.getTintLocations(); - if (tintLocations != newTintLocations) { - this.tintLocations = newTintLocations; - writeUpdateInfo(2, buffer -> buffer.writeShort(tintLocations)); - } - } - - public void readUpdateInfo(int id, PacketBuffer buffer) { - super.readUpdateInfo(id, buffer); - if (id == 2) { - tintLocations = buffer.readShort(); - } - } -} diff --git a/src/main/java/gregtech/api/items/itemhandlers/GTItemStackHandler.java b/src/main/java/gregtech/api/items/itemhandlers/GTItemStackHandler.java index eab9612c747..91c5a6f2853 100644 --- a/src/main/java/gregtech/api/items/itemhandlers/GTItemStackHandler.java +++ b/src/main/java/gregtech/api/items/itemhandlers/GTItemStackHandler.java @@ -25,9 +25,16 @@ public GTItemStackHandler(MetaTileEntity metaTileEntity, NonNullList this.metaTileEntity = metaTileEntity; } + @Override + public void setStackInSlot(int slot, ItemStack stack) { + if (ItemStack.areItemStacksEqual(stack, getStackInSlot(slot))) + return; + + super.setStackInSlot(slot, stack); + } + @Override public void onContentsChanged(int slot) { - super.onContentsChanged(slot); metaTileEntity.markDirty(); } } diff --git a/src/main/java/gregtech/api/items/toolitem/ItemGTToolbelt.java b/src/main/java/gregtech/api/items/toolitem/ItemGTToolbelt.java index 2f4af5961da..ab6bce96dd7 100644 --- a/src/main/java/gregtech/api/items/toolitem/ItemGTToolbelt.java +++ b/src/main/java/gregtech/api/items/toolitem/ItemGTToolbelt.java @@ -115,7 +115,7 @@ public ModularPanel buildUI(HandGuiData guiData, PanelSyncManager guiSyncManager int heightBonus = (handler.getSlots() / 9) * 18; - SlotGroup group = new SlotGroup("toolbelt_inventory", 9); + SlotGroup group = new SlotGroup("toolbelt_inventory", Math.min(handler.getSlots(), 9)); guiSyncManager.registerSlotGroup(group); List slots = new ArrayList<>(); @@ -669,7 +669,6 @@ public boolean isItemValid(int slot, @NotNull ItemStack stack) { @Override protected void onContentsChanged(int slot) { - if (this.selectedSlot == slot) this.selectedSlot = -1; this.updateSlot(slot); this.update(); diff --git a/src/main/java/gregtech/api/mui/GTGuiTextures.java b/src/main/java/gregtech/api/mui/GTGuiTextures.java index b92acd290b0..f34c08a936a 100644 --- a/src/main/java/gregtech/api/mui/GTGuiTextures.java +++ b/src/main/java/gregtech/api/mui/GTGuiTextures.java @@ -214,6 +214,8 @@ public static class IDs { public static final UITexture MENU_OVERLAY = fullImage("textures/gui/overlay/menu_overlay.png"); + public static final UITexture RECIPE_LOCK = fullImage("textures/gui/widget/lock.png"); + // todo bronze/steel/primitive fluid slots? // SLOT OVERLAYS @@ -355,6 +357,7 @@ public static class IDs { public static final UITexture BUTTON_AUTO_COLLAPSE = fullImage( "textures/gui/widget/button_auto_collapse_overlay.png"); public static final UITexture BUTTON_X = fullImage("textures/gui/widget/button_x_overlay.png", true); + public static final UITexture BUTTON_CLEAR_GRID = fullImage("textures/gui/widget/button_clear_grid.png", false); public static final UITexture BUTTON_CROSS = fullImage("textures/gui/widget/button_cross.png"); public static final UITexture BUTTON_REDSTONE_ON = fullImage("textures/gui/widget/button_redstone_on.png"); diff --git a/src/main/java/gregtech/api/mui/GregTechGuiTransferHandler.java b/src/main/java/gregtech/api/mui/GregTechGuiTransferHandler.java new file mode 100644 index 00000000000..9332b8a9a70 --- /dev/null +++ b/src/main/java/gregtech/api/mui/GregTechGuiTransferHandler.java @@ -0,0 +1,69 @@ +package gregtech.api.mui; + +import gregtech.api.mui.sync.PagedWidgetSyncHandler; +import gregtech.common.metatileentities.storage.CraftingRecipeLogic; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; + +import com.cleanroommc.modularui.screen.ModularContainer; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GregTechGuiTransferHandler implements IRecipeTransferHandler { + + private final IRecipeTransferHandlerHelper handlerHelper; + + public GregTechGuiTransferHandler(IRecipeTransferHandlerHelper handlerHelper) { + this.handlerHelper = handlerHelper; + } + + @Override + public @NotNull Class getContainerClass() { + return ModularContainer.class; + } + + @Override + public @Nullable IRecipeTransferError transferRecipe(ModularContainer container, + @NotNull IRecipeLayout recipeLayout, + @NotNull EntityPlayer player, boolean maxTransfer, + boolean doTransfer) { + if (!container.getSyncManager().isOpen("workbench")) { + return null; + } + PanelSyncManager syncManager = container.getSyncManager().getPanelSyncManager("workbench"); + var recipeLogic = (CraftingRecipeLogic) syncManager.getSyncHandler("recipe_logic:0"); + var pageController = (PagedWidgetSyncHandler) syncManager.getSyncHandler("page_controller:0"); + + if (!doTransfer) { + // todo highlighting in JEI? + return null; + } + + var matrix = extractMatrix(recipeLayout.getItemStacks()); + recipeLogic.fillCraftingGrid(matrix); + pageController.setPage(0); + return null; + } + + private Int2ObjectMap extractMatrix(IGuiItemStackGroup stackGroup) { + var ingredients = stackGroup.getGuiIngredients(); + Int2ObjectMap matrix = new Int2ObjectArrayMap<>(9); + for (var slot : ingredients.keySet()) { + if (slot != 0) { + var ing = ingredients.get(slot).getDisplayedIngredient(); + if (ing == null) continue; + matrix.put(slot - 1, ingredients.get(slot).getDisplayedIngredient()); + } + } + return matrix; + } +} diff --git a/src/main/java/gregtech/api/mui/InputAccessor.java b/src/main/java/gregtech/api/mui/InputAccessor.java new file mode 100644 index 00000000000..9cac66d865a --- /dev/null +++ b/src/main/java/gregtech/api/mui/InputAccessor.java @@ -0,0 +1,23 @@ +package gregtech.api.mui; + +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.screen.viewport.LocatedWidget; + +// todo remove on next mui2 update +// this can't be a true accessor because of illegal classloading +public interface InputAccessor { + + boolean held(); + + void held(boolean held); + + void timeHeld(long a); + + LocatedWidget lastPressed(); + + void lastPressed(LocatedWidget last); + + void lastButton(int b); + + void addInteractable(Interactable i); +} diff --git a/src/main/java/gregtech/api/mui/sync/PagedWidgetSyncHandler.java b/src/main/java/gregtech/api/mui/sync/PagedWidgetSyncHandler.java new file mode 100644 index 00000000000..c91cee8c5a3 --- /dev/null +++ b/src/main/java/gregtech/api/mui/sync/PagedWidgetSyncHandler.java @@ -0,0 +1,45 @@ +package gregtech.api.mui.sync; + +import net.minecraft.network.PacketBuffer; + +import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.widgets.PagedWidget; + +public class PagedWidgetSyncHandler extends SyncHandler { + + private final PagedWidget.Controller controller; + public static final int SET_PAGE = 0; + + public PagedWidgetSyncHandler(PagedWidget.Controller controller) { + this.controller = controller; + } + + @Override + public void readOnClient(int id, PacketBuffer buf) { + if (id == SET_PAGE) { + setPage(buf.readVarInt(), false); + } + } + + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == SET_PAGE) { + setPage(buf.readVarInt(), false); + } + } + + public void setPage(int page) { + setPage(page, true); + } + + public void setPage(int page, boolean sync) { + if (controller.isInitialised() && page != getPage()) { + controller.setPage(page); + if (sync) sync(SET_PAGE, buffer -> buffer.writeVarInt(page)); + } + } + + public int getPage() { + return controller.getActivePageIndex(); + } +} diff --git a/src/main/java/gregtech/api/storage/ICraftingStorage.java b/src/main/java/gregtech/api/storage/ICraftingStorage.java deleted file mode 100644 index 15d49e87511..00000000000 --- a/src/main/java/gregtech/api/storage/ICraftingStorage.java +++ /dev/null @@ -1,15 +0,0 @@ -package gregtech.api.storage; - -import gregtech.common.metatileentities.storage.CraftingRecipeMemory; - -import net.minecraft.world.World; -import net.minecraftforge.items.ItemStackHandler; - -public interface ICraftingStorage { - - World getWorld(); - - ItemStackHandler getCraftingGrid(); - - CraftingRecipeMemory getRecipeMemory(); -} diff --git a/src/main/java/gregtech/api/util/ItemStackHashStrategy.java b/src/main/java/gregtech/api/util/ItemStackHashStrategy.java index fb68bc99112..405bcaf842e 100644 --- a/src/main/java/gregtech/api/util/ItemStackHashStrategy.java +++ b/src/main/java/gregtech/api/util/ItemStackHashStrategy.java @@ -58,7 +58,7 @@ static ItemStackHashStrategy comparingItemDamageCount() { */ class ItemStackHashStrategyBuilder { - private boolean item, count, damage, tag; + private boolean item, count, damage, tag, meta; /** * Defines whether the Item type should be considered for equality. @@ -93,6 +93,17 @@ public ItemStackHashStrategyBuilder compareDamage(boolean choice) { return this; } + /** + * Defines whether metadata values should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public ItemStackHashStrategyBuilder compareMetadata(boolean choice) { + meta = choice; + return this; + } + /** * Defines whether NBT Tags should be considered for equality. * @@ -116,7 +127,8 @@ public int hashCode(@Nullable ItemStack o) { item ? o.getItem() : null, count ? o.getCount() : null, damage ? o.getItemDamage() : null, - tag ? o.getTagCompound() : null); + tag ? o.getTagCompound() : null, + meta ? o.getMetadata() : null); } @Override @@ -127,6 +139,7 @@ public boolean equals(@Nullable ItemStack a, @Nullable ItemStack b) { return (!item || a.getItem() == b.getItem()) && (!count || a.getCount() == b.getCount()) && (!damage || a.getItemDamage() == b.getItemDamage()) && + (!meta || a.getMetadata() == b.getMetadata()) && (!tag || Objects.equals(a.getTagCompound(), b.getTagCompound())); } }; diff --git a/src/main/java/gregtech/client/utils/RenderUtil.java b/src/main/java/gregtech/client/utils/RenderUtil.java index f38d5cee0d4..7228bd8212e 100644 --- a/src/main/java/gregtech/client/utils/RenderUtil.java +++ b/src/main/java/gregtech/client/utils/RenderUtil.java @@ -26,6 +26,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; import codechicken.lib.vec.Matrix4; +import com.cleanroommc.modularui.api.MCHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; @@ -384,6 +385,24 @@ public static void renderItemOverLay(float x, float y, float z, float scale, Ite net.minecraft.client.renderer.RenderHelper.disableStandardItemLighting(); } + // adapted from com.cleanroommc.modularui.drawable.GuiDraw.java + // todo merge this with the method from the qstorage mui2 port + public static void renderItem(ItemStack item, int x, int y, float width, float height) { + if (item.isEmpty()) return; + GlStateManager.pushMatrix(); + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.enableDepth(); + GlStateManager.translate(x, y, 0); + GlStateManager.scale(width / 16f, height / 16f, 1); + RenderItem renderItem = MCHelper.getMc().getRenderItem(); + renderItem.renderItemAndEffectIntoGUI(MCHelper.getPlayer(), item, 0, 0); + renderItem.renderItemOverlayIntoGUI(MCHelper.getFontRenderer(), item, 0, 0, null); + GlStateManager.disableDepth(); + RenderHelper.enableStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.popMatrix(); + } + public static void renderFluidOverLay(float x, float y, float width, float height, float z, FluidStack fluidStack, float alpha) { if (fluidStack != null) { diff --git a/src/main/java/gregtech/common/gui/widget/craftingstation/CraftingSlotWidget.java b/src/main/java/gregtech/common/gui/widget/craftingstation/CraftingSlotWidget.java deleted file mode 100644 index 973ef3c478b..00000000000 --- a/src/main/java/gregtech/common/gui/widget/craftingstation/CraftingSlotWidget.java +++ /dev/null @@ -1,208 +0,0 @@ -package gregtech.common.gui.widget.craftingstation; - -import gregtech.api.gui.impl.ModularUIContainer; -import gregtech.api.gui.ingredient.IRecipeTransferHandlerWidget; -import gregtech.api.gui.widgets.SlotWidget; -import gregtech.api.util.OverlayedItemHandler; -import gregtech.common.metatileentities.storage.CraftingRecipeLogic; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.inventory.IInventory; -import net.minecraft.inventory.InventoryCraftResult; -import net.minecraft.item.ItemStack; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; - -import com.google.common.base.Preconditions; -import mezz.jei.api.gui.IGuiIngredient; -import mezz.jei.api.gui.IRecipeLayout; -import org.lwjgl.input.Mouse; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -public class CraftingSlotWidget extends SlotWidget implements IRecipeTransferHandlerWidget { - - private final CraftingRecipeLogic recipeResolver; - private boolean canTakeStack = false; - - public CraftingSlotWidget(CraftingRecipeLogic recipeResolver, int slotIndex, int xPosition, int yPosition) { - super(createInventory(recipeResolver), slotIndex, xPosition, yPosition, false, false); - this.recipeResolver = recipeResolver; - } - - private static IInventory createInventory(CraftingRecipeLogic resolver) { - return resolver == null ? new InventoryCraftResult() : resolver.getCraftingResultInventory(); - } - - @Override - public void handleClientAction(int id, PacketBuffer buffer) { - super.handleClientAction(id, buffer); - if (id == 1) { - HashMap ingredients = new HashMap<>(); - int ingredientAmount = buffer.readVarInt(); - try { - for (int i = 0; i < ingredientAmount; i++) { - ingredients.put(buffer.readVarInt(), buffer.readItemStack()); - } - } catch (IOException exception) { - throw new RuntimeException(exception); - } - recipeResolver.fillCraftingGrid(ingredients); - } - if (id == 2) { - if (recipeResolver.isRecipeValid()) { - ClickData clickData = ClickData.readFromBuf(buffer); - boolean isShiftDown = clickData.isShiftClick; - boolean isLeftClick = clickData.button == 0; - boolean isRightClick = clickData.button == 1; - EntityPlayer player = gui.entityPlayer; - if (isShiftDown) { - OverlayedItemHandler playerInventory = new OverlayedItemHandler( - new PlayerMainInvWrapper(gui.entityPlayer.inventory)); - ItemStack toMerge = slotReference.getStack(); - int crafts = this.slotReference.getStack().getCount(); - if (isLeftClick) { - if (crafts != 0) { - // limit shift click to one stack at a time - int totalCrafts = 0; - int maxCrafts = toMerge.getMaxStackSize() / crafts; - for (int i = 0; i < maxCrafts; i++) { - if (canMergeToInv(playerInventory, toMerge, crafts) && - recipeResolver.performRecipe(gui.entityPlayer)) { - this.recipeResolver.refreshOutputSlot(); - recipeResolver.handleItemCraft(this.slotReference.getStack(), gui.entityPlayer); - totalCrafts += crafts; - } - } - ItemStack toAdd = this.slotReference.getStack().copy(); - toAdd.setCount(totalCrafts); - player.inventory.addItemStackToInventory(toAdd); - } - } else if (isRightClick) { - int totalCrafts = 0; - while (canMergeToInv(playerInventory, toMerge, crafts) && - recipeResolver.performRecipe(gui.entityPlayer)) { - this.recipeResolver.refreshOutputSlot(); - recipeResolver.handleItemCraft(this.slotReference.getStack(), gui.entityPlayer); - totalCrafts += crafts; - } - ItemStack toAdd = this.slotReference.getStack().copy(); - toAdd.setCount(totalCrafts); - player.inventory.addItemStackToInventory(toAdd); - } - } else { - if (isLeftClick) { - if (canMerge(player.inventory.getItemStack(), this.slotReference.getStack()) && - recipeResolver.performRecipe(gui.entityPlayer)) { - this.recipeResolver.refreshOutputSlot(); - recipeResolver.handleItemCraft(this.slotReference.getStack(), gui.entityPlayer); - // send slot changes now, both of consumed items in inventory and result slot - ItemStack result = this.slotReference.getStack(); - mergeToHand(result); - } - } else if (isRightClick) { - while (canMerge(player.inventory.getItemStack(), this.slotReference.getStack()) && - recipeResolver.performRecipe(gui.entityPlayer)) { - this.recipeResolver.refreshOutputSlot(); - recipeResolver.handleItemCraft(this.slotReference.getStack(), gui.entityPlayer); - ItemStack result = this.slotReference.getStack(); - mergeToHand(result); - } - } - } - uiAccess.sendHeldItemUpdate(); - // send slot changes now, both of consumed items in inventory and result slot - gui.entityPlayer.openContainer.detectAndSendChanges(); - uiAccess.sendSlotUpdate(this); - } - } - } - - private static boolean canMerge(ItemStack stack, ItemStack stack1) { - if (stack.isEmpty()) return true; - if (ItemStack.areItemsEqual(stack, stack1) && ItemStack.areItemStackTagsEqual(stack, stack1)) { - return stack.getCount() + stack1.getCount() <= stack.getMaxStackSize(); - } - return false; - } - - private static boolean canMergeToInv(OverlayedItemHandler inventory, ItemStack stack, int crafts) { - return inventory.insertStackedItemStack(stack, crafts) == 0; - } - - private void mergeToHand(ItemStack toMerge) { - EntityPlayer player = gui.entityPlayer; - ItemStack itemInHand = gui.entityPlayer.inventory.getItemStack(); - if (itemInHand.isEmpty()) { - itemInHand = toMerge; - player.inventory.setItemStack(itemInHand); - } else - if (ItemStack.areItemsEqual(itemInHand, toMerge) && ItemStack.areItemStackTagsEqual(itemInHand, toMerge)) { - // if the hand is not empty, try to merge the result with the hand - if (itemInHand.getCount() + toMerge.getCount() <= itemInHand.getMaxStackSize()) { - // if the result of the merge is smaller than the max stack size, merge - itemInHand.grow(toMerge.getCount()); - player.inventory.setItemStack(itemInHand); - } - } - } - - @Override - public void detectAndSendChanges() { - super.detectAndSendChanges(); - if (recipeResolver == null) { - return; - } - boolean isRecipeValid = recipeResolver.isRecipeValid(); - if (isRecipeValid != canTakeStack) { - this.canTakeStack = isRecipeValid; - writeUpdateInfo(1, buf -> buf.writeBoolean(canTakeStack)); - } - } - - @Override - public void readUpdateInfo(int id, PacketBuffer buffer) { - super.readUpdateInfo(id, buffer); - if (id == 1) { - this.canTakeStack = buffer.readBoolean(); - } - } - - @Override - public boolean canMergeSlot(ItemStack stack) { - return false; - } - - @Override - public boolean mouseClicked(int mouseX, int mouseY, int button) { - if (isMouseOverElement(mouseX, mouseY) && gui != null) { - ClickData clickData = new ClickData(Mouse.getEventButton(), isShiftDown(), isCtrlDown()); - writeClientAction(2, clickData::writeToBuf); - } - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public String transferRecipe(ModularUIContainer container, IRecipeLayout recipeLayout, EntityPlayer player, - boolean maxTransfer, boolean doTransfer) { - if (!doTransfer) { - return null; - } - Map> ingredients = new HashMap<>( - recipeLayout.getItemStacks().getGuiIngredients()); - ingredients.values().removeIf(it -> it.getAllIngredients().isEmpty() || !it.isInput()); - writeClientAction(1, buf -> { - buf.writeVarInt(ingredients.size()); - for (Entry> entry : ingredients.entrySet()) { - buf.writeVarInt(entry.getKey()); - ItemStack itemStack = entry.getValue().getDisplayedIngredient(); - Preconditions.checkNotNull(itemStack); - buf.writeItemStack(itemStack); - } - }); - return null; - } -} diff --git a/src/main/java/gregtech/common/gui/widget/craftingstation/ItemListGridWidget.java b/src/main/java/gregtech/common/gui/widget/craftingstation/ItemListGridWidget.java deleted file mode 100644 index 3dc7f19a512..00000000000 --- a/src/main/java/gregtech/common/gui/widget/craftingstation/ItemListGridWidget.java +++ /dev/null @@ -1,260 +0,0 @@ -package gregtech.common.gui.widget.craftingstation; - -import gregtech.api.gui.INativeWidget; -import gregtech.api.gui.Widget; -import gregtech.api.gui.widgets.ScrollableListWidget; -import gregtech.api.gui.widgets.WidgetGroup; -import gregtech.api.util.ItemStackHashStrategy; -import gregtech.common.inventory.IItemInfo; -import gregtech.common.inventory.IItemList; -import gregtech.common.inventory.IItemList.InsertMode; -import gregtech.common.inventory.SimpleItemInfo; - -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.network.PacketBuffer; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.*; - -public class ItemListGridWidget extends ScrollableListWidget { - - private static final Comparator COMPARATOR = Comparator.comparing( - IItemInfo::getItemStack, - Comparator.comparingInt(it -> Item.REGISTRY.getIDForObject(it.getItem())) - .thenComparing(ItemStack::getItemDamage) - .thenComparing(ItemStack::hasTagCompound) - .thenComparing(it -> -Objects.hashCode(it.getTagCompound())) - .thenComparing(it -> -it.getCount())); - - @Nullable - private final IItemList itemList; - private final int slotAmountX; - private final int slotAmountY; - private int slotRowsAmount = 0; - private final Map cachedItemList = new Object2ObjectOpenCustomHashMap<>( - ItemStackHashStrategy.comparingAllButCount()); - private final List itemsChanged = new ArrayList<>(); - private final List itemsRemoved = new ArrayList<>(); - - private final List displayItemList = new ArrayList<>(); - - public ItemListGridWidget(int x, int y, int slotsX, int slotsY, @Nullable IItemList itemList) { - super(x, y, slotsX * 18 + 10, slotsY * 18); - this.itemList = itemList; - this.slotAmountX = slotsX; - this.slotAmountY = slotsY; - } - - @Nullable - public IItemList getItemList() { - return itemList; - } - - @Nullable - public IItemInfo getItemInfoAt(int index) { - return displayItemList.size() > index ? displayItemList.get(index) : null; - } - - @Override - public boolean mouseClicked(int mouseX, int mouseY, int button) { - boolean result = super.mouseClicked(mouseX, mouseY, button); - if (!result && isShiftDown()) { - INativeWidget hoveredSlot = findHoveredSlot(mouseX, mouseY); - if (hoveredSlot != null) { - dispatchOtherSlotShiftClick(hoveredSlot); - return true; - } - } - return result; - } - - private void dispatchOtherSlotShiftClick(INativeWidget clickedSlot) { - ItemStack stackInSlot = clickedSlot.getHandle().getStack(); - if (!stackInSlot.isEmpty()) { - writeClientAction(4, buf -> buf.writeVarInt(clickedSlot.getHandle().slotNumber)); - } - } - - private void handleSlotShiftClick(INativeWidget clickedSlot) { - ItemStack itemStack = clickedSlot.getHandle().getStack(); - if (clickedSlot.getHandle().canTakeStack(gui.entityPlayer) && !itemStack.isEmpty()) { - itemStack = clickedSlot.onItemTake(gui.entityPlayer, itemStack, true); - int amountInserted = getItemList().insertItem(itemStack, itemStack.getCount(), false, - InsertMode.LOWEST_PRIORITY); - if (amountInserted > 0) { - clickedSlot.onItemTake(gui.entityPlayer, itemStack, false); - itemStack.shrink(amountInserted); - if (!clickedSlot.canMergeSlot(itemStack)) { - gui.entityPlayer.dropItem(itemStack.copy(), false, false); - itemStack.setCount(0); - } - clickedSlot.getHandle().onSlotChanged(); - uiAccess.sendSlotUpdate(clickedSlot); - gui.entityPlayer.openContainer.detectAndSendChanges(); - } - } - } - - private void addSlotRows(int amount) { - for (int i = 0; i < amount; i++) { - int widgetAmount = widgets.size(); - WidgetGroup widgetGroup = new WidgetGroup(); - for (int j = 0; j < slotAmountX; j++) { - Widget widget = new ItemListSlotWidget(j * 18, 0, this, widgetAmount * slotAmountX + j); - widgetGroup.addWidget(widget); - } - addWidget(widgetGroup); - } - } - - private void removeSlotRows(int amount) { - for (int i = 0; i < amount; i++) { - Widget slotWidget = widgets.remove(widgets.size() - 1); - removeWidget(slotWidget); - } - } - - private void modifySlotRows(int delta) { - if (delta > 0) { - addSlotRows(delta); - } else { - removeSlotRows(delta); - } - } - - private void checkItemListForChanges() { - Iterator iterator = cachedItemList.keySet().iterator(); - while (iterator.hasNext()) { - ItemStack itemStack = iterator.next(); - if (!itemList.hasItemStored(itemStack)) { - iterator.remove(); - itemsRemoved.add(itemStack); - } - } - for (ItemStack itemStack : itemList.getStoredItems()) { - IItemInfo itemInfo = itemList.getItemInfo(itemStack); - if (itemInfo == null) - continue; - - if (!cachedItemList.containsKey(itemStack)) { - SimpleItemInfo lookupInfo = new SimpleItemInfo(itemStack); - int totalAmount = itemInfo.getTotalItemAmount(); - - if (totalAmount == 0) { - itemsRemoved.add(itemStack); - } else { - lookupInfo.setTotalItemAmount(totalAmount); - cachedItemList.put(itemStack, lookupInfo); - itemsChanged.add(lookupInfo); - } - } else { - SimpleItemInfo cachedItemInfo = cachedItemList.get(itemStack); - if (cachedItemInfo.getTotalItemAmount() != itemInfo.getTotalItemAmount()) { - cachedItemInfo.setTotalItemAmount(itemInfo.getTotalItemAmount()); - itemsChanged.add(cachedItemInfo); - } - } - } - } - - @Override - public void detectAndSendChanges() { - super.detectAndSendChanges(); - if (itemList == null) return; - int amountOfItemTypes = itemList.getStoredItems().size(); - int slotRowsRequired = Math.max(slotAmountY, (int) Math.ceil(amountOfItemTypes / (slotAmountX * 1.0))); - if (slotRowsAmount != slotRowsRequired) { - int slotsToAdd = slotRowsRequired - slotRowsAmount; - this.slotRowsAmount = slotRowsRequired; - writeUpdateInfo(2, buf -> buf.writeVarInt(slotsToAdd)); - modifySlotRows(slotsToAdd); - } - - this.itemsChanged.clear(); - this.itemsRemoved.clear(); - checkItemListForChanges(); - if (!itemsChanged.isEmpty() || !itemsRemoved.isEmpty()) { - writeUpdateInfo(3, buf -> { - buf.writeVarInt(itemsRemoved.size()); - for (ItemStack stack : itemsRemoved) { - buf.writeItemStack(stack); - } - buf.writeVarInt(itemsChanged.size()); - for (SimpleItemInfo itemInfo : itemsChanged) { - buf.writeItemStack(itemInfo.getItemStack()); - buf.writeVarInt(itemInfo.getTotalItemAmount()); - } - }); - } - } - - @Override - public void readUpdateInfo(int id, PacketBuffer buffer) { - super.readUpdateInfo(id, buffer); - if (id == 2) { - int slotsToAdd = buffer.readVarInt(); - modifySlotRows(slotsToAdd); - } - if (id == 3) { - try { - int itemsRemoved = buffer.readVarInt(); - for (int i = 0; i < itemsRemoved; i++) { - ItemStack itemStack = buffer.readItemStack(); - this.displayItemList.removeIf( - it -> ItemStackHashStrategy.comparingAllButCount().equals(it.getItemStack(), itemStack)); - } - int itemsChanged = buffer.readVarInt(); - for (int i = 0; i < itemsChanged; i++) { - ItemStack itemStack = buffer.readItemStack(); - int newTotalAmount = buffer.readVarInt(); - SimpleItemInfo itemInfo = displayItemList.stream() - .filter(it -> ItemStackHashStrategy.comparingAllButCount().equals(it.getItemStack(), - itemStack)) - .findAny() - .orElse(null); - if (itemInfo == null) { - itemInfo = new SimpleItemInfo(itemStack); - this.displayItemList.add(itemInfo); - } - itemInfo.setTotalItemAmount(newTotalAmount); - } - this.displayItemList.sort(COMPARATOR); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } - - @Override - public void handleClientAction(int id, PacketBuffer buffer) { - super.handleClientAction(id, buffer); - if (id == 4) { - INativeWidget clickedSlot = findSlotByNumber(buffer.readVarInt()); - if (clickedSlot != null) { - handleSlotShiftClick(clickedSlot); - } - } - } - - @Nullable - private INativeWidget findHoveredSlot(int mouseX, int mouseY) { - return gui.guiWidgets.values().stream() - .flatMap(it -> it.getNativeWidgets().stream()) - .filter(it -> it.getHandle().isEnabled()) - .filter(it -> it.getHandle().canTakeStack(gui.entityPlayer)) - .filter(it -> ((Widget) it).isMouseOverElement(mouseX, mouseY)) - .findFirst().orElse(null); - } - - @Nullable - private INativeWidget findSlotByNumber(int slotNumber) { - return gui.guiWidgets.values().stream() - .flatMap(it -> it.getNativeWidgets().stream()) - .filter(it -> it.getHandle().slotNumber == slotNumber) - .findFirst().orElse(null); - } -} diff --git a/src/main/java/gregtech/common/gui/widget/craftingstation/ItemListSlotWidget.java b/src/main/java/gregtech/common/gui/widget/craftingstation/ItemListSlotWidget.java deleted file mode 100644 index f087b3d0708..00000000000 --- a/src/main/java/gregtech/common/gui/widget/craftingstation/ItemListSlotWidget.java +++ /dev/null @@ -1,231 +0,0 @@ -package gregtech.common.gui.widget.craftingstation; - -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.IRenderContext; -import gregtech.api.gui.Widget; -import gregtech.api.util.Position; -import gregtech.api.util.Size; -import gregtech.client.utils.TooltipHelper; -import gregtech.common.inventory.IItemInfo; -import gregtech.common.inventory.IItemList; -import gregtech.common.inventory.IItemList.InsertMode; - -import net.minecraft.client.resources.I18n; -import net.minecraft.entity.player.InventoryPlayer; -import net.minecraft.item.ItemStack; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.text.TextFormatting; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.List; - -public class ItemListSlotWidget extends Widget { - - private final ItemListGridWidget gridWidget; - private final int index; - - ItemListSlotWidget(int x, int y, ItemListGridWidget gridWidget, int index) { - super(new Position(x, y), new Size(18, 18)); - this.gridWidget = gridWidget; - this.index = index; - } - - public static String formatItemAmount(int itemAmount) { - return Integer.toString(itemAmount); - } - - @Override - public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRenderContext context) { - super.drawInBackground(mouseX, mouseY, partialTicks, context); - Position position = getPosition(); - GuiTextures.SLOT.draw(position.x, position.y, 18, 18); - IItemInfo itemInfo = gridWidget.getItemInfoAt(index); - int stackX = position.x + 1; - int stackY = position.y + 1; - if (itemInfo != null) { - ItemStack itemStack = itemInfo.getItemStack(); - // Used to reset the ItemStack count after drawing. Avoids copying the itemStack - int cachedCount = itemStack.getCount(); - // Set the count to 1 to prevent stack size from being drawn in drawItemStack - itemStack.setCount(1); - String itemAmountStr = formatItemAmount(itemInfo.getTotalItemAmount()); - drawItemStack(itemStack, stackX, stackY, null); - drawStringFixedCorner(itemAmountStr, stackX + 17, stackY + 17, 16777215, true, 0.5f); - itemStack.setCount(cachedCount); - } - if (isMouseOverElement(mouseX, mouseY)) { - drawSelectionOverlay(stackX, stackY, 16, 16); - } - } - - @Override - public void drawInForeground(int mouseX, int mouseY) { - super.drawInForeground(mouseX, mouseY); - IItemInfo itemInfo = gridWidget.getItemInfoAt(index); - if (itemInfo != null && isMouseOverElement(mouseX, mouseY)) { - ItemStack itemStack = itemInfo.getItemStack(); - List tooltip = getItemToolTip(itemStack); - int totalItemStored = itemInfo.getTotalItemAmount(); - String itemStoredText = I18n.format("gregtech.item_list.item_stored", totalItemStored); - tooltip.add(TextFormatting.GRAY + itemStoredText); - drawHoveringText(itemStack, tooltip, -1, mouseX, mouseY); - } - } - - private void setCreativeHeldItem(@NotNull ItemStack itemStack) { - InventoryPlayer inventory = gui.entityPlayer.inventory; - if (!itemStack.isEmpty() && inventory.getItemStack().isEmpty()) { - itemStack.setCount(itemStack.getMaxStackSize()); - inventory.setItemStack(itemStack); - } - } - - private static int getAmountToTake(@NotNull ItemStack itemStack, int maxAmount, int button) { - int maxStackSize = Math.min(itemStack.getMaxStackSize(), maxAmount); - return button == 0 ? maxStackSize : (maxStackSize >= 2 ? maxStackSize / 2 : 1); - } - - // returns true if something actually happened - private boolean insertHeldItemStack(int button, boolean isClient) { - InventoryPlayer inventory = gui.entityPlayer.inventory; - int amountToInsert = button == 1 ? 1 : Integer.MAX_VALUE; - if (!inventory.getItemStack().isEmpty()) { - if (!isClient) { - // on server, we lookup item list to see how much we can actually insert - ItemStack heldItemStack = inventory.getItemStack(); - IItemList itemList = gridWidget.getItemList(); - int amountInserted = itemList.insertItem(heldItemStack, - Math.min(heldItemStack.getCount(), amountToInsert), false, InsertMode.LOWEST_PRIORITY); - heldItemStack.shrink(amountInserted); - uiAccess.sendHeldItemUpdate(); - gui.entityPlayer.openContainer.detectAndSendChanges(); - return amountInserted > 0; - } else { - // on client we assume we can insert full stack into the network - inventory.getItemStack().shrink(amountToInsert); - return true; - } - } - return false; - } - - private void extractItemStack(ItemStack itemStack, int amount, boolean isClient) { - InventoryPlayer inventory = gui.entityPlayer.inventory; - if (inventory.getItemStack().isEmpty()) { - if (!isClient) { - // on server, we try to extract from the network - IItemList itemList = gridWidget.getItemList(); - int amountExtracted = itemList.extractItem(itemStack, amount, false); - if (amountExtracted > 0) { - ItemStack resultStack = itemStack.copy(); - resultStack.setCount(amountExtracted); - inventory.setItemStack(resultStack); - } - uiAccess.sendHeldItemUpdate(); - } else { - // on client we assume we can extract as much items as user wishes - ItemStack resultStack = itemStack.copy(); - resultStack.setCount(amount); - inventory.setItemStack(resultStack); - } - } - } - - private void handleMouseClick(@Nullable IItemInfo itemInfo, int button, boolean isClient) { - if (button == 2) { - if (itemInfo != null && gui.entityPlayer.isCreative()) { - ItemStack itemStack = itemInfo.getItemStack().copy(); - setCreativeHeldItem(itemStack); - } - } else if (button == 0 || button == 1) { - if (insertHeldItemStack(button, isClient) || - !gui.entityPlayer.inventory.getItemStack().isEmpty()) { - return; - } - if (itemInfo != null) { - ItemStack itemStack = itemInfo.getItemStack(); - int extractAmount = getAmountToTake(itemStack, itemInfo.getTotalItemAmount(), button); - extractItemStack(itemStack, extractAmount, isClient); - } - } - } - - private void handleSelfShiftClick(@NotNull IItemInfo itemInfo) { - ItemStack itemStack = itemInfo.getItemStack().copy(); - itemStack.setCount(itemStack.getMaxStackSize()); - int currentStackSize = itemStack.getCount(); - uiAccess.attemptMergeStack(itemStack, true, true); - int amountToExtract = Math.min(currentStackSize - itemStack.getCount(), itemInfo.getTotalItemAmount()); - if (amountToExtract > 0) { - int extracted = gridWidget.getItemList().extractItem(itemInfo.getItemStack(), amountToExtract, false); - ItemStack resultStack = itemInfo.getItemStack().copy(); - resultStack.setCount(extracted); - if (!resultStack.isEmpty()) { - uiAccess.attemptMergeStack(resultStack, true, false); - gui.entityPlayer.openContainer.detectAndSendChanges(); - if (!resultStack.isEmpty()) { - gui.entityPlayer.dropItem(resultStack, false, false); - } - } - } - } - - @Override - public void handleClientAction(int id, PacketBuffer buffer) { - super.handleClientAction(id, buffer); - if (id == 1) { - try { - ItemStack itemStack = buffer.readItemStack(); - int button = buffer.readVarInt(); - IItemInfo itemInfo = itemStack.isEmpty() ? null : gridWidget.getItemList().getItemInfo(itemStack); - handleMouseClick(itemInfo, button, false); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else if (id == 2) { - try { - ItemStack itemStack = buffer.readItemStack(); - IItemInfo itemInfo = gridWidget.getItemList().getItemInfo(itemStack); - if (itemInfo != null) { - handleSelfShiftClick(itemInfo); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - private void dispatchMouseClick(int button) { - IItemInfo itemInfo = gridWidget.getItemInfoAt(index); - handleMouseClick(itemInfo, button, true); - ItemStack itemStack = itemInfo == null ? ItemStack.EMPTY : itemInfo.getItemStack(); - writeClientAction(1, buf -> { - buf.writeItemStack(itemStack); - buf.writeVarInt(button); - }); - } - - private void dispatchSelfShiftClick() { - IItemInfo itemInfo = gridWidget.getItemInfoAt(index); - if (itemInfo != null) { - writeClientAction(2, buf -> buf.writeItemStack(itemInfo.getItemStack())); - } - } - - @Override - public boolean mouseClicked(int mouseX, int mouseY, int button) { - if (isMouseOverElement(mouseX, mouseY)) { - boolean shiftClick = TooltipHelper.isShiftDown(); - if (!shiftClick) { - dispatchMouseClick(button); - } else { - dispatchSelfShiftClick(); - } - return true; - } - return false; - } -} diff --git a/src/main/java/gregtech/common/gui/widget/craftingstation/MemorizedRecipeWidget.java b/src/main/java/gregtech/common/gui/widget/craftingstation/MemorizedRecipeWidget.java deleted file mode 100644 index 4bbf93311b8..00000000000 --- a/src/main/java/gregtech/common/gui/widget/craftingstation/MemorizedRecipeWidget.java +++ /dev/null @@ -1,100 +0,0 @@ -package gregtech.common.gui.widget.craftingstation; - -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.IRenderContext; -import gregtech.api.gui.widgets.SlotWidget; -import gregtech.api.util.Position; -import gregtech.common.metatileentities.storage.CraftingRecipeMemory; -import gregtech.common.metatileentities.storage.CraftingRecipeMemory.MemorizedRecipe; - -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.resources.I18n; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.inventory.ClickType; -import net.minecraft.item.ItemStack; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.items.IItemHandlerModifiable; -import net.minecraftforge.items.ItemStackHandler; - -import java.util.List; - -public class MemorizedRecipeWidget extends SlotWidget { - - private final CraftingRecipeMemory recipeMemory; - private final int recipeIndex; - private boolean recipeLocked = false; - private final IItemHandlerModifiable craftingGrid; - - public MemorizedRecipeWidget(CraftingRecipeMemory recipeMemory, int index, IItemHandlerModifiable craftingGrid, - int xPosition, int yPosition) { - super(new ItemStackHandler(1), 0, xPosition, yPosition, false, false); - this.recipeMemory = recipeMemory; - this.recipeIndex = index; - this.craftingGrid = craftingGrid; - } - - @Override - public void detectAndSendChanges() { - super.detectAndSendChanges(); - MemorizedRecipe recipe = recipeMemory.getRecipeAtIndex(recipeIndex); - ItemStack resultStack = recipe == null ? ItemStack.EMPTY : recipe.getRecipeResult(); - if (!ItemStack.areItemStacksEqual(resultStack, slotReference.getStack())) { - slotReference.putStack(resultStack); - uiAccess.sendSlotUpdate(this); - } - boolean recipeLocked = recipe != null && recipe.isRecipeLocked(); - if (this.recipeLocked != recipeLocked) { - this.recipeLocked = recipeLocked; - writeUpdateInfo(1, buf -> buf.writeBoolean(recipeLocked)); - } - } - - @Override - public void drawInForeground(int mouseX, int mouseY) { - super.drawInForeground(mouseX, mouseY); - if (isMouseOverElement(mouseX, mouseY) && slotReference.getHasStack()) { - ((ISlotWidget) slotReference).setHover(false); - GlStateManager.disableDepth(); - List tooltip = getItemToolTip(slotReference.getStack()); - tooltip.add(I18n.format("gregtech.recipe_memory_widget.tooltip.1")); - tooltip.add(I18n.format("gregtech.recipe_memory_widget.tooltip.2")); - drawHoveringText(slotReference.getStack(), tooltip, -1, mouseX, mouseY); - GlStateManager.enableDepth(); - } - } - - @Override - public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRenderContext context) { - super.drawInBackground(mouseX, mouseY, partialTicks, context); - if (recipeLocked) { - GlStateManager.translate(0, 0, 160); - Position pos = getPosition(); - GlStateManager.disableDepth(); - GuiTextures.LOCK.draw(pos.x, pos.y + 10, 8, 8); - GlStateManager.enableDepth(); - GlStateManager.translate(0, 0, -160); - } - } - - @Override - public void readUpdateInfo(int id, PacketBuffer buffer) { - super.readUpdateInfo(id, buffer); - if (id == 1) this.recipeLocked = buffer.readBoolean(); - } - - @Override - public ItemStack slotClick(int dragType, ClickType clickTypeIn, EntityPlayer player) { - if (!player.world.isRemote) { - MemorizedRecipe recipe = recipeMemory.getRecipeAtIndex(recipeIndex); - if (recipe != null && !recipe.getRecipeResult().isEmpty()) { - if (clickTypeIn == ClickType.PICKUP) { - recipeMemory.loadRecipe(recipeIndex, craftingGrid); - player.openContainer.detectAndSendChanges(); - } else if (clickTypeIn == ClickType.QUICK_MOVE) { - recipe.setRecipeLocked(!recipe.isRecipeLocked()); - } - } - } - return ItemStack.EMPTY; - } -} diff --git a/src/main/java/gregtech/common/inventory/handlers/ToolItemStackHandler.java b/src/main/java/gregtech/common/inventory/handlers/ToolItemStackHandler.java index 54a2d048ee1..f164b1b152e 100644 --- a/src/main/java/gregtech/common/inventory/handlers/ToolItemStackHandler.java +++ b/src/main/java/gregtech/common/inventory/handlers/ToolItemStackHandler.java @@ -1,7 +1,6 @@ package gregtech.common.inventory.handlers; -import gregtech.api.items.toolitem.IGTTool; -import gregtech.api.unification.OreDictUnifier; +import gregtech.api.items.toolitem.ToolHelper; import net.minecraft.item.ItemStack; @@ -13,19 +12,18 @@ public ToolItemStackHandler(int size) { super(size); } + @Override + public boolean isItemValid(int slot, ItemStack stack) { + return ToolHelper.isTool(stack); + } + @Override @NotNull public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { - if (stack.getItem().getToolClasses(stack).isEmpty()) return stack; - if (stack.getItem() instanceof IGTTool && - ((IGTTool) stack.getItem()).getToolStats().isSuitableForCrafting(stack)) { + if (isItemValid(slot, stack)) { return super.insertItem(slot, stack, simulate); } - if (stack.isItemStackDamageable() && OreDictUnifier.getOreDictionaryNames(stack).stream() - .anyMatch(s -> s.startsWith("craftingTool"))) { - return super.insertItem(slot, stack, simulate); - } return stack; } } diff --git a/src/main/java/gregtech/common/metatileentities/storage/CachedRecipeData.java b/src/main/java/gregtech/common/metatileentities/storage/CachedRecipeData.java index 403eed12261..f40d7b37d32 100755 --- a/src/main/java/gregtech/common/metatileentities/storage/CachedRecipeData.java +++ b/src/main/java/gregtech/common/metatileentities/storage/CachedRecipeData.java @@ -1,151 +1,22 @@ package gregtech.common.metatileentities.storage; -import gregtech.api.util.ItemStackHashStrategy; -import gregtech.common.crafting.ShapedOreEnergyTransferRecipe; -import gregtech.common.inventory.IItemList; -import gregtech.common.inventory.itemsource.ItemSources; - import net.minecraft.inventory.InventoryCrafting; -import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipe; -import net.minecraft.item.crafting.Ingredient; import net.minecraft.world.World; -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2BooleanMap; -import it.unimi.dsi.fastutil.objects.Object2BooleanOpenCustomHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import org.jetbrains.annotations.Nullable; public class CachedRecipeData { - private final ItemSources itemSources; private IRecipe recipe; - private final Object2IntMap requiredItems = new Object2IntOpenCustomHashMap<>( - ItemStackHashStrategy.comparingAllButCount()); - private final Int2ObjectMap> replaceAttemptMap = new Int2ObjectArrayMap<>(); - private final InventoryCrafting inventory; - - public CachedRecipeData(ItemSources sourceList, IRecipe recipe, InventoryCrafting inventoryCrafting) { - this.itemSources = sourceList; - this.recipe = recipe; - this.inventory = inventoryCrafting; - } - - public short attemptMatchRecipe() { - short itemsFound = 0; - this.requiredItems.clear(); - for (int i = 0; i < inventory.getSizeInventory(); i++) { - if (getIngredientEquivalent(i)) - itemsFound += 1 << i; // ingredient was found, and indicate in the short of this fact - } - if (itemsFound != CraftingRecipeLogic.ALL_INGREDIENTS_PRESENT) { - requiredItems.clear(); - } - return itemsFound; - } + private IRecipe previousRecipe; - protected boolean consumeRecipeItems() { - boolean gathered = true; - Object2IntMap gatheredItems = new Object2IntOpenCustomHashMap<>( - ItemStackHashStrategy.comparingAllButCount()); - if (requiredItems.isEmpty()) { - return false; - } - for (Object2IntMap.Entry entry : requiredItems.object2IntEntrySet()) { - ItemStack stack = entry.getKey(); - int requestedAmount = entry.getIntValue(); - int extractedAmount = itemSources.extractItem(stack, requestedAmount, false); - if (extractedAmount != requestedAmount) { - gatheredItems.put(stack.copy(), extractedAmount); - gathered = false; - break; - } else { - gatheredItems.put(stack.copy(), requestedAmount); - } - } - if (!gathered) { - for (Object2IntMap.Entry entry : gatheredItems.object2IntEntrySet()) { - itemSources.insertItem(entry.getKey(), entry.getIntValue(), false, - IItemList.InsertMode.HIGHEST_PRIORITY); - } - } - return gathered; + public CachedRecipeData() { + this(null); } - public boolean getIngredientEquivalent(int slot) { - ItemStack currentStack = inventory.getStackInSlot(slot); - if (currentStack.isEmpty()) { - return true; // stack is empty, nothing to return - } - - if (simulateExtractItem(currentStack)) { - return true; - } - - ItemStack previousStack = recipe.getCraftingResult(inventory); - - Object2BooleanMap map = replaceAttemptMap.computeIfAbsent(slot, - (m) -> new Object2BooleanOpenCustomHashMap<>(ItemStackHashStrategy.comparingAllButCount())); - - // iterate stored items to find equivalent - for (ItemStack itemStack : itemSources.getStoredItems()) { - boolean matchedPreviously = false; - if (map.containsKey(itemStack)) { - if (!map.get(itemStack)) { - continue; - } else { - // cant return here before checking if: - // The item is available for extraction - // The recipe output is still the same, as depending on the ingredient, the output NBT may change - matchedPreviously = true; - } - } - - if (!matchedPreviously) { - boolean matched = false; - // Matching shapeless recipes actually is very bad for performance, as it checks the entire - // recipe ingredients recursively, so we fail early here if none of the recipes ingredients can - // take the stack - for (Ingredient in : recipe.getIngredients()) { - if (in.apply(itemStack)) { - matched = true; - break; - } - } - if (!matched) { - map.put(itemStack.copy(), false); - continue; - } - } - - // update item in slot, and check that recipe matches and output item is equal to the expected one - inventory.setInventorySlotContents(slot, itemStack); - if (recipe.matches(inventory, itemSources.getWorld()) && - (ItemStack.areItemStacksEqual(recipe.getCraftingResult(inventory), previousStack) || - recipe instanceof ShapedOreEnergyTransferRecipe)) { - map.put(itemStack, true); - // ingredient matched, attempt to extract it and return if successful - if (simulateExtractItem(itemStack)) { - return true; - } - } - map.put(itemStack, false); - inventory.setInventorySlotContents(slot, currentStack); - } - // nothing matched, so return null - return false; - } - - private boolean simulateExtractItem(ItemStack itemStack) { - int amountToExtract = requiredItems.getOrDefault(itemStack, 0) + 1; - int extracted = itemSources.extractItem(itemStack, amountToExtract, true); - if (extracted == amountToExtract) { - requiredItems.put(itemStack.copy(), amountToExtract); - return true; - } - return false; + public CachedRecipeData(@Nullable IRecipe recipe) { + this.recipe = recipe; } public boolean matches(InventoryCrafting inventoryCrafting, World world) { @@ -156,11 +27,15 @@ public boolean matches(InventoryCrafting inventoryCrafting, World world) { } public void setRecipe(IRecipe newRecipe) { + this.previousRecipe = this.recipe; this.recipe = newRecipe; - this.replaceAttemptMap.clear(); } public IRecipe getRecipe() { return recipe; } + + public IRecipe getPreviousRecipe() { + return previousRecipe; + } } diff --git a/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeLogic.java b/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeLogic.java index 035b741ef76..e7cd589e5eb 100644 --- a/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeLogic.java +++ b/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeLogic.java @@ -1,69 +1,108 @@ package gregtech.common.metatileentities.storage; -import gregtech.api.storage.ICraftingStorage; +import gregtech.api.items.toolitem.ItemGTToolbelt; import gregtech.api.util.DummyContainer; -import gregtech.common.inventory.IItemList; -import gregtech.common.inventory.itemsource.ItemSources; -import gregtech.common.inventory.itemsource.sources.TileItemSource; +import gregtech.api.util.GTLog; +import gregtech.api.util.GTTransferUtils; +import gregtech.api.util.GTUtility; +import gregtech.api.util.ItemStackHashStrategy; +import gregtech.common.crafting.ShapedOreEnergyTransferRecipe; +import gregtech.common.mui.widget.workbench.CraftingInputSlot; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.InventoryCraftResult; import net.minecraft.inventory.InventoryCrafting; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.IRecipe; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.math.BlockPos; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.network.PacketBuffer; import net.minecraft.world.World; import net.minecraftforge.common.ForgeHooks; -import net.minecraftforge.fml.common.FMLCommonHandler; -import net.minecraftforge.items.ItemStackHandler; +import net.minecraftforge.common.crafting.IShapedRecipe; +import net.minecraftforge.items.IItemHandlerModifiable; -import com.google.common.collect.Lists; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.ints.Int2BooleanArrayMap; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; -public class CraftingRecipeLogic { +public class CraftingRecipeLogic extends SyncHandler { + + // client only + public static final int UPDATE_INGREDIENTS = 1; + public static final int RESET_INGREDIENTS = 2; + public static final int SYNC_STACK = 4; + + // server only + public static final int UPDATE_MATRIX = 0; private final World world; - private final ItemSources itemSources; - private final ItemStackHandler craftingGrid; - private final ItemStack[] oldCraftingGrid = new ItemStack[9]; - private final InventoryCrafting inventoryCrafting = new InventoryCrafting(new DummyContainer(), 3, 3); + private IItemHandlerModifiable availableHandlers; + private final Hash.Strategy strategy = ItemStackHashStrategy.builder() + .compareItem(true) + .compareMetadata(true) + .build(); + + /** + * Used to lookup a list of slots for a given stack + * filled by {@link CraftingRecipeLogic#refreshStackMap()} + **/ + private final Map> stackLookupMap = new Object2ObjectOpenCustomHashMap<>(this.strategy); + + /** + * List of items needed to complete the crafting recipe, filled by + * {@link CraftingRecipeLogic#getIngredientEquivalent(CraftingInputSlot)} )} + **/ + private final Map requiredItems = new Object2IntOpenCustomHashMap<>( + this.strategy); + + private final Int2IntMap compactedIndexes = new Int2IntArrayMap(9); + private final Int2IntMap slotMap = new Int2IntArrayMap(); + + private final Map> replaceAttemptMap = new Int2ObjectArrayMap<>(); + private final InventoryCrafting craftingMatrix; private final IInventory craftingResultInventory = new InventoryCraftResult(); - private ItemStack oldResult = ItemStack.EMPTY; private final CachedRecipeData cachedRecipeData; - private final CraftingRecipeMemory recipeMemory; - private IRecipe cachedRecipe = null; - private int itemsCrafted = 0; - public static short ALL_INGREDIENTS_PRESENT = 511; - private short tintLocation = ALL_INGREDIENTS_PRESENT; + private final CraftingInputSlot[] inputSlots = new CraftingInputSlot[9]; - public CraftingRecipeLogic(ICraftingStorage craftingStorage) { - this.world = craftingStorage.getWorld(); - this.craftingGrid = craftingStorage.getCraftingGrid(); - this.recipeMemory = craftingStorage.getRecipeMemory(); - this.itemSources = new ItemSources(world); - this.cachedRecipeData = new CachedRecipeData(itemSources, null, inventoryCrafting); - } - - public ItemSources getItemSourceList() { - return itemSources; + public CraftingRecipeLogic(World world, IItemHandlerModifiable handlers, IItemHandlerModifiable craftingMatrix) { + this.world = world; + this.availableHandlers = handlers; + this.craftingMatrix = wrapHandler(craftingMatrix); + this.cachedRecipeData = new CachedRecipeData(); } public IInventory getCraftingResultInventory() { return craftingResultInventory; } - public int getItemsCraftedAmount() { - return itemsCrafted; + public InventoryCrafting getCraftingMatrix() { + return this.craftingMatrix; + } + + public void updateSlotMap(int offset, int slot) { + slotMap.put(offset + slot, slotMap.size()); } - public void setItemsCraftedAmount(int itemsCrafted) { - this.itemsCrafted = itemsCrafted; + public void clearSlotMap() { + slotMap.clear(); + } + + public void updateInventory(IItemHandlerModifiable handler) { + this.availableHandlers = handler; } public void clearCraftingGrid() { @@ -71,143 +110,396 @@ public void clearCraftingGrid() { } public void fillCraftingGrid(Map ingredients) { - for (int i = 0; i < craftingGrid.getSlots(); i++) { - craftingGrid.setStackInSlot(i, ingredients.getOrDefault(i + 1, ItemStack.EMPTY)); + for (int i = 0; i < craftingMatrix.getSizeInventory(); i++) { + craftingMatrix.setInventorySlotContents(i, ingredients.getOrDefault(i, ItemStack.EMPTY)); } + syncMatrix(); + updateCurrentRecipe(); } - private boolean hasCraftingGridUpdated() { - boolean craftingGridChanged = false; - for (int i = 0; i < craftingGrid.getSlots(); i++) { - ItemStack oldStack = oldCraftingGrid[i]; - ItemStack newStack = craftingGrid.getStackInSlot(i); - if (oldStack == null || oldStack.isEmpty()) { - if (newStack.isEmpty()) { - continue; - } - oldStack = newStack; - oldCraftingGrid[i] = oldStack; - inventoryCrafting.setInventorySlotContents(i, newStack.copy()); - craftingGridChanged = true; - } else if (newStack.isEmpty()) { - oldCraftingGrid[i] = null; - inventoryCrafting.setInventorySlotContents(i, ItemStack.EMPTY); - craftingGridChanged = true; - } else if (!ItemStack.areItemsEqual(oldStack, newStack) || - !ItemStack.areItemStackTagsEqual(oldStack, newStack)) { - oldCraftingGrid[i] = newStack; - inventoryCrafting.setInventorySlotContents(i, newStack.copy()); - craftingGridChanged = true; - } - } - return craftingGridChanged; + public void setInputSlot(CraftingInputSlot slot, int index) { + this.inputSlots[index] = slot; } - public boolean performRecipe(EntityPlayer player) { - if (!isRecipeValid()) { - return false; + public boolean performRecipe() { + return isRecipeValid() && attemptMatchRecipe() && consumeRecipeItems(); + } + + public boolean isRecipeValid() { + return cachedRecipeData.getRecipe() != null && cachedRecipeData.matches(craftingMatrix, this.world); + } + + /** + * Attempts to match the crafting matrix against all connected inventories + * + * @return true if all items matched + */ + public boolean attemptMatchRecipe() { + for (CraftingInputSlot slot : this.inputSlots) { + if (!slot.hasIngredients) { + return false; + } } - if (!cachedRecipeData.consumeRecipeItems()) { + return true; + } + + protected boolean consumeRecipeItems() { + if (requiredItems.isEmpty()) { return false; } - ForgeHooks.setCraftingPlayer(player); - // todo right here is where tools get damaged (in UI) - List remainingItems = cachedRecipe.getRemainingItems(inventoryCrafting); - ForgeHooks.setCraftingPlayer(null); - for (int i = 0; i < remainingItems.size(); i++) { - ItemStack itemStack = remainingItems.get(i); - if (itemStack.isEmpty()) { - continue; - } + Map gatheredItems = new Int2IntOpenHashMap(); - ItemStack current = inventoryCrafting.getStackInSlot(i); - inventoryCrafting.setInventorySlotContents(i, itemStack); - if (!cachedRecipe.matches(inventoryCrafting, itemSources.getWorld())) { - inventoryCrafting.setInventorySlotContents(i, current); + for (var entry : requiredItems.entrySet()) { + ItemStack stack = entry.getKey(); + int requestedAmount = entry.getValue(); + var slotList = stackLookupMap.get(stack); + + int extractedAmount = 0; + for (int slot : slotList) { + var extracted = availableHandlers.extractItem(slot, requestedAmount, true); + gatheredItems.put(slot, extracted.getCount()); + extractedAmount += extracted.getCount(); + requestedAmount -= extracted.getCount(); + if (requestedAmount == 0) break; } + if (extractedAmount < requestedAmount) return false; + } - int remainingAmount = itemStack.getCount() - itemSources.insertItem(itemStack, itemStack.getCount(), false, - IItemList.InsertMode.HIGHEST_PRIORITY); - if (remainingAmount > 0) { - itemStack.setCount(remainingAmount); - player.addItemStackToInventory(itemStack); - if (itemStack.getCount() > 0) { - player.dropItem(itemStack, false, false); - } + boolean extracted = false; + for (var gathered : gatheredItems.entrySet()) { + int slot = gathered.getKey(), amount = gathered.getValue(); + var stack = availableHandlers.getStackInSlot(slot); + boolean hasContainer = stack.getItem().hasContainerItem(stack); + + if (hasContainer && stack.getCount() > 1) { + var useStack = stack.splitStack(1); + var newStack = ForgeHooks.getContainerItem(useStack); + if (newStack.isEmpty()) return false; + + GTTransferUtils.insertItem(this.availableHandlers, newStack, false); + } else if (hasContainer) { + var usedStack = ForgeHooks.getContainerItem(stack); + availableHandlers.setStackInSlot(slot, usedStack); + } else { + availableHandlers.extractItem(slot, amount, false); } + extracted = true; } - return true; + return extracted; } - public void handleItemCraft(ItemStack itemStack, EntityPlayer player) { - itemStack.onCrafting(world, player, 1); - itemStack.getItem().onCreated(itemStack, world, player); - // if we're not simulated, fire the event, unlock recipe and add crafted items, and play sounds - FMLCommonHandler.instance().firePlayerCraftingEvent(player, itemStack, inventoryCrafting); + /** + *

+ * Searches all connected inventories for the slot's stack, and uses + * {@link CraftingRecipeLogic#findSubstitute(int, ItemStack)} to look for valid substitutes + *

+ *
+ *

+ * This method also fills out {@link CraftingRecipeLogic#requiredItems} for use in + * {@link CraftingRecipeLogic#consumeRecipeItems()} + *

+ * + * @param slot slot whose current stack to find a substitute for + * @return true if the stack in the slot can be extracted or has a valid substitute + */ + public boolean getIngredientEquivalent(CraftingInputSlot slot) { + ItemStack currentStack = slot.getStack(); + if (currentStack.isEmpty()) { + return true; // stack is empty, nothing to return + } - if (cachedRecipe != null && !cachedRecipe.isDynamic()) { - player.unlockRecipes(Lists.newArrayList(cachedRecipe)); + int count = requiredItems.getOrDefault(currentStack, 0); + if (simulateExtractItem(currentStack, count + 1)) { + requiredItems.put(currentStack, ++count); + return true; } - if (cachedRecipe != null) { - ItemStack resultStack = cachedRecipe.getCraftingResult(inventoryCrafting); - this.itemsCrafted += resultStack.getCount(); - recipeMemory.notifyRecipePerformed(craftingGrid, resultStack); + + ItemStack substitute = findSubstitute(slot.getIndex(), currentStack); + if (substitute.isEmpty()) return false; + + count = requiredItems.getOrDefault(substitute, 0); + if (simulateExtractItem(substitute, count + 1)) { + requiredItems.put(substitute, ++count); + return true; } + return false; } - public void refreshOutputSlot() { - ItemStack itemStack = ItemStack.EMPTY; - if (cachedRecipe != null) { - itemStack = cachedRecipe.getCraftingResult(inventoryCrafting); + /** + *

+ * Searches through all connected inventories for a replacement stack that can be used in the recipe + *

+ * + * @param craftingIndex Index of the current crafting slot + * @param stack The stack to find a substitute for + * @return a valid replacement stack, or {@link ItemStack#EMPTY} if no valid replacements exist + */ + public ItemStack findSubstitute(int craftingIndex, ItemStack stack) { + Object2BooleanMap map = replaceAttemptMap.computeIfAbsent(craftingIndex, + (m) -> new Object2BooleanOpenCustomHashMap<>(ItemStackHashStrategy.comparingAllButCount())); + + ItemStack substitute = ItemStack.EMPTY; + + var recipe = getCachedRecipe(); + List ingredients = new ArrayList<>(recipe.getIngredients()); + ingredients.removeIf(ingredient -> ingredient == Ingredient.EMPTY); + int index = compactedIndexes.get(craftingIndex); + + // iterate stored items to find equivalent + for (int i = 0; i < this.availableHandlers.getSlots(); i++) { + var itemStack = availableHandlers.getStackInSlot(i); + if (itemStack.isEmpty() || this.strategy.equals(itemStack, stack)) continue; + + boolean matchedPreviously = false; + if (map.containsKey(itemStack)) { + if (map.getBoolean(itemStack)) { + // cant return here before checking if: + // The item is available for extraction + // The recipe output is still the same, as depending on + // the ingredient, the output NBT may change + matchedPreviously = true; + } + } + + // this is also every tick + if (itemStack.getItem() instanceof ItemGTToolbelt) { + // we need to do this here because of ingredient apply + ItemGTToolbelt.setCraftingSlot(slotMap.get(i), (EntityPlayerMP) getSyncManager().getPlayer()); + } + + if (!matchedPreviously) { + // Matching shapeless recipes actually is very bad for performance, as it checks the entire + // recipe ingredients recursively, so we fail early here if none of the recipes ingredients can + // take the stack + boolean matched = false; + if (!(recipe instanceof IShapedRecipe)) { + for (Ingredient ing : ingredients) { + if (ing.apply(itemStack)) { + matched = true; + break; + } + } + } else { + // for shaped recipes, check the exact ingredient instead + // ingredients should be in the correct order + if (index >= 0 && index < ingredients.size()) + matched = ingredients.get(index).apply(itemStack); + else { + GTLog.logger.warn("Compacted index \"{}\" is out of bounds for list size \"{}\"", index, + ingredients.size()); + } + } + if (!matched) { + map.put(GTUtility.copy(1, itemStack), false); + continue; + } + } + + ItemStack previousResult = recipe.getCraftingResult(craftingMatrix); + + // update item in slot, and check that recipe matches and output item is equal to the expected one + craftingMatrix.setInventorySlotContents(craftingIndex, itemStack); + var newResult = recipe.getCraftingResult(craftingMatrix); + // this will send packets every tick for the toolbelt, not sure what can be done + if ((cachedRecipeData.matches(craftingMatrix, world) && + ItemStack.areItemStacksEqual(newResult, previousResult)) || + recipe instanceof ShapedOreEnergyTransferRecipe) { + // ingredient matched, return the substitute + craftingMatrix.setInventorySlotContents(craftingIndex, stack); + map.put(GTUtility.copy(1, itemStack), true); + substitute = itemStack; + break; + } + map.put(GTUtility.copy(1, itemStack), false); + craftingMatrix.setInventorySlotContents(craftingIndex, stack); } - this.craftingResultInventory.setInventorySlotContents(0, itemStack); + return substitute; } - public boolean isRecipeValid() { - return cachedRecipeData.getRecipe() != null && cachedRecipeData.matches(inventoryCrafting, this.world) && - cachedRecipeData.attemptMatchRecipe() == ALL_INGREDIENTS_PRESENT; + /** + * Attempts to extract the given stack from connected inventories + * + * @param itemStack stack from the crafting matrix + * @return true if the stack was successfully extracted or the stack is empty + */ + private boolean simulateExtractItem(ItemStack itemStack, int count) { + if (itemStack.isEmpty()) return true; + if (!stackLookupMap.containsKey(itemStack)) return false; + + int extracted = 0; + + for (int slot : stackLookupMap.get(itemStack)) { + var slotStack = availableHandlers.extractItem(slot, count, true); + // we are certain the stack map is correct + if (slotStack.getItem() instanceof ItemGTToolbelt) { + ItemGTToolbelt.setCraftingSlot(slotMap.get(slot), (EntityPlayerMP) getSyncManager().getPlayer()); + } + extracted += slotStack.getCount(); + if (extracted >= count) return true; + } + + return false; } - private void updateCurrentRecipe() { - if (!cachedRecipeData.matches(inventoryCrafting, world) || - !ItemStack.areItemStacksEqual(oldResult, cachedRecipe.getCraftingResult(inventoryCrafting))) { - IRecipe newRecipe = CraftingManager.findMatchingRecipe(inventoryCrafting, world); - this.cachedRecipe = newRecipe; + public void updateCurrentRecipe() { + if (!cachedRecipeData.matches(craftingMatrix, world)) { + IRecipe newRecipe = CraftingManager.findMatchingRecipe(craftingMatrix, world); ItemStack resultStack = ItemStack.EMPTY; if (newRecipe != null) { - resultStack = newRecipe.getCraftingResult(inventoryCrafting); - oldResult = resultStack.copy(); + resultStack = newRecipe.getCraftingResult(craftingMatrix); } this.craftingResultInventory.setInventorySlotContents(0, resultStack); this.cachedRecipeData.setRecipe(newRecipe); } } - public void update() { - // update item sources every tick for fast tinting updates - itemSources.update(); - if (getCachedRecipeData().getRecipe() != null) { - tintLocation = getCachedRecipeData().attemptMatchRecipe(); - } else { - tintLocation = ALL_INGREDIENTS_PRESENT; + public IRecipe getCachedRecipe() { + return this.cachedRecipeData.getRecipe(); + } + + @Override + public void detectAndSendChanges(boolean init) { + var recipe = getCachedRecipe(); + if (recipe == null) { + var prevRecipe = cachedRecipeData.getPreviousRecipe(); + if (prevRecipe == null) return; + cachedRecipeData.setRecipe(null); + for (CraftingInputSlot inputSlot : this.inputSlots) { + inputSlot.hasIngredients = true; + } + syncToClient(RESET_INGREDIENTS); + return; + } + + compactedIndexes.clear(); + requiredItems.clear(); + refreshStackMap(); + final Map map = new Int2BooleanArrayMap(); + int next = 0; + for (CraftingInputSlot slot : this.inputSlots) { + final boolean hadIngredients = slot.hasIngredients; + + // check if existing stack works + var slotStack = slot.getStack(); + if (slotStack.isEmpty()) { + if (!hadIngredients) { + slot.hasIngredients = true; + map.put(slot.getIndex(), slot.hasIngredients); + } + continue; + } + + compactedIndexes.put(slot.getIndex(), next++); + int count = requiredItems.getOrDefault(slotStack, 0) + 1; + slot.hasIngredients = simulateExtractItem(slotStack, count); + + if (slot.hasIngredients) { + requiredItems.put(GTUtility.copy(1, slotStack), count); + } else { + // check if substitute exists + ItemStack substitute = findSubstitute(slot.getIndex(), slotStack); + if (!substitute.isEmpty()) { + count = requiredItems.getOrDefault(substitute, 0) + 1; + slot.hasIngredients = simulateExtractItem(substitute, count); + requiredItems.put(GTUtility.copy(1, substitute), count); + } + } + + if (hadIngredients != slot.hasIngredients) + map.put(slot.getIndex(), slot.hasIngredients); + } + + // only sync when something has changed + if (!map.isEmpty()) { + syncToClient(UPDATE_INGREDIENTS, buffer -> { + buffer.writeByte(map.size()); + for (var set : map.entrySet()) { + buffer.writeByte(set.getKey()); + buffer.writeBoolean(set.getValue()); + } + }); + } + } + + /** + * Searches available handlers and + * adds the stack and slots the stack lookup map + */ + public void refreshStackMap() { + // the stack lookup map is a pain to do "correctly" + // so just clear and reset every tick in detectAndSendChanges() + stackLookupMap.clear(); + for (int i = 0; i < this.availableHandlers.getSlots(); i++) { + var curStack = this.availableHandlers.getStackInSlot(i); + if (curStack.isEmpty()) continue; + + Set slots; + if (stackLookupMap.containsKey(curStack)) { + slots = stackLookupMap.get(curStack); + } else { + stackLookupMap.put(GTUtility.copy(1, curStack), slots = new IntArraySet()); + } + slots.add(i); } - if (hasCraftingGridUpdated()) { - updateCurrentRecipe(); + } + + public void writeMatrix(PacketBuffer buffer) { + buffer.writeVarInt(craftingMatrix.getSizeInventory()); + for (int i = 0; i < craftingMatrix.getSizeInventory(); i++) { + NetworkUtils.writeItemStack(buffer, craftingMatrix.getStackInSlot(i)); } } - public short getTintLocations() { - return tintLocation; + @Override + public void readOnClient(int id, PacketBuffer buf) { + if (id == UPDATE_INGREDIENTS) { + int size = buf.readByte(); + for (int i = 0; i < size; i++) { + this.inputSlots[buf.readByte()].hasIngredients = buf.readBoolean(); + } + } else if (id == SYNC_STACK) { + getSyncManager().setCursorItem(NetworkUtils.readItemStack(buf)); + } else if (id == RESET_INGREDIENTS) { + for (CraftingInputSlot inputSlot : this.inputSlots) { + inputSlot.hasIngredients = true; + } + } } - public void checkNeighbourInventories(BlockPos blockPos) { - for (EnumFacing side : EnumFacing.VALUES) { - TileItemSource itemSource = new TileItemSource(world, blockPos, side); - this.itemSources.addItemHandler(itemSource); + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == UPDATE_MATRIX) { + int size = buf.readVarInt(); + for (int i = 0; i < size; i++) { + this.craftingMatrix.setInventorySlotContents(i, NetworkUtils.readItemStack(buf)); + } + this.updateCurrentRecipe(); } } - public CachedRecipeData getCachedRecipeData() { - return this.cachedRecipeData; + public void syncMatrix() { + if (getSyncManager().isClient()) + syncToServer(UPDATE_MATRIX, this::writeMatrix); + } + + public static InventoryCrafting wrapHandler(IItemHandlerModifiable handler) { + return new InventoryCrafting(new DummyContainer(), 3, 3) { + + @Override + public ItemStack getStackInRowAndColumn(int row, int column) { + int index = row + (3 * column); + return handler.getStackInSlot(index); + } + + @Override + public ItemStack getStackInSlot(int index) { + return handler.getStackInSlot(index); + } + + @Override + public void setInventorySlotContents(int index, ItemStack stack) { + handler.setStackInSlot(index, GTUtility.copy(1, stack)); + } + }; } } diff --git a/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeMemory.java b/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeMemory.java index daa4ff85b3c..af7f20b0b0b 100644 --- a/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeMemory.java +++ b/src/main/java/gregtech/common/metatileentities/storage/CraftingRecipeMemory.java @@ -1,73 +1,139 @@ package gregtech.common.metatileentities.storage; +import gregtech.api.util.ItemStackHashStrategy; + import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.network.PacketBuffer; import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.ItemStackHandler; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.utils.MouseData; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class CraftingRecipeMemory { +import java.util.Map; + +public class CraftingRecipeMemory extends SyncHandler { + + // client and server + public static final int UPDATE_RECIPES = 1; + + // client only + public static final int SYNC_RECIPE = 4; + public static final int OFFSET_RECIPE = 5; + public static final int REMOVE_RECIPE = 2; + public static final int MAKE_RECIPE = 3; + public static final int UPDATE_LOGIC = 6; + + // server only + public static final int MOUSE_CLICK = 2; + + private final Hash.Strategy strategy = ItemStackHashStrategy.builder() + .compareItem(true) + .compareMetadata(true) + .build(); private final MemorizedRecipe[] memorizedRecipes; + private final IItemHandlerModifiable craftingMatrix; - public CraftingRecipeMemory(int memorySize) { + public CraftingRecipeMemory(int memorySize, IItemHandlerModifiable craftingMatrix) { this.memorizedRecipes = new MemorizedRecipe[memorySize]; + this.craftingMatrix = craftingMatrix; } - public void loadRecipe(int index, IItemHandlerModifiable craftingGrid) { + public void loadRecipe(int index) { MemorizedRecipe recipe = memorizedRecipes[index]; if (recipe != null) { - copyInventoryItems(recipe.craftingMatrix, craftingGrid); + copyInventoryItems(recipe.craftingMatrix, this.craftingMatrix); + getRecipeLogic().updateCurrentRecipe(); + syncToClient(UPDATE_LOGIC); } } + public CraftingRecipeLogic getRecipeLogic() { + return (CraftingRecipeLogic) getSyncManager().getSyncHandler("recipe_logic:0"); + } + @Nullable public MemorizedRecipe getRecipeAtIndex(int index) { return memorizedRecipes[index]; } - @Nullable - private MemorizedRecipe offsetRecipe(int startIndex) { - MemorizedRecipe previousRecipe = memorizedRecipes[startIndex]; + @SuppressWarnings("DataFlowIssue") + public @NotNull ItemStack getRecipeOutputAtIndex(int index) { + return hasRecipe(index) ? getRecipeAtIndex(index).getRecipeResult() : ItemStack.EMPTY; + } + + /** + * Offsets recipes from {@code startIndex} to the right, skipping locked recipes + * + * @param startIndex the index to start offsetting recipes + */ + private void offsetRecipe(int startIndex) { + MemorizedRecipe previousRecipe = removeRecipe(startIndex); + if (previousRecipe == null) return; for (int i = startIndex + 1; i < memorizedRecipes.length; i++) { MemorizedRecipe recipe = memorizedRecipes[i]; if (recipe != null && recipe.recipeLocked) continue; memorizedRecipes[i] = previousRecipe; - if (recipe == null) return null; + memorizedRecipes[i].index = i; + + // we found a null recipe and there's no more recipes to check, + if (recipe == null) return; + previousRecipe = recipe; } - return previousRecipe; } @Nullable private MemorizedRecipe findOrCreateRecipe(ItemStack resultItemStack) { // search preexisting recipe with identical recipe result + MemorizedRecipe existing = null; for (MemorizedRecipe memorizedRecipe : memorizedRecipes) { if (memorizedRecipe != null && - ItemStack.areItemStacksEqual(memorizedRecipe.recipeResult, resultItemStack)) { - return memorizedRecipe; + strategy.equals(memorizedRecipe.recipeResult, resultItemStack)) { + existing = memorizedRecipe; + break; } } + + // we already have a recipe that matches + // move it to the front + if (existing != null) { + // it's already at the front or it's locked + if (existing.index == 0 || existing.recipeLocked) return existing; + + int removed = existing.index; + removeRecipe(existing.index); + syncToClient(REMOVE_RECIPE, buffer -> buffer.writeByte(removed)); + offsetRecipe(0); + syncToClient(OFFSET_RECIPE, buffer -> buffer.writeByte(0)); + existing.index = 0; + return memorizedRecipes[0] = existing; + } + // put new memorized recipe into array for (int i = 0; i < memorizedRecipes.length; i++) { MemorizedRecipe memorizedRecipe; if (memorizedRecipes[i] == null) { - memorizedRecipe = new MemorizedRecipe(); + memorizedRecipe = new MemorizedRecipe(i); } else if (memorizedRecipes[i].recipeLocked) { continue; } else { - memorizedRecipe = offsetRecipe(i); - if (memorizedRecipe == null) { - memorizedRecipe = new MemorizedRecipe(); - } + offsetRecipe(i); + memorizedRecipe = new MemorizedRecipe(i); + syncToClient(OFFSET_RECIPE, buffer -> buffer.writeByte(memorizedRecipe.index)); } memorizedRecipe.initialize(resultItemStack); - memorizedRecipes[i] = memorizedRecipe; - return memorizedRecipe; + return memorizedRecipes[i] = memorizedRecipe; } return null; } @@ -75,8 +141,10 @@ private MemorizedRecipe findOrCreateRecipe(ItemStack resultItemStack) { public void notifyRecipePerformed(IItemHandler craftingGrid, ItemStack resultStack) { MemorizedRecipe recipe = findOrCreateRecipe(resultStack); if (recipe != null) { + // notify slot and sync to client recipe.updateCraftingMatrix(craftingGrid); recipe.timesUsed++; + syncToClient(SYNC_RECIPE, recipe::writeToBuffer); } } @@ -100,7 +168,7 @@ public void deserializeNBT(NBTTagCompound tagCompound) { for (int i = 0; i < resultList.tagCount(); i++) { NBTTagCompound entryComponent = resultList.getCompoundTagAt(i); int slotIndex = entryComponent.getInteger("Slot"); - MemorizedRecipe recipe = MemorizedRecipe.deserializeNBT(entryComponent.getCompoundTag("Recipe")); + MemorizedRecipe recipe = MemorizedRecipe.deserializeNBT(entryComponent.getCompoundTag("Recipe"), slotIndex); this.memorizedRecipes[slotIndex] = recipe; } } @@ -112,14 +180,112 @@ private static void copyInventoryItems(IItemHandler src, IItemHandlerModifiable } } + public final MemorizedRecipe removeRecipe(int index) { + if (hasRecipe(index)) { + MemorizedRecipe removed = memorizedRecipes[index]; + memorizedRecipes[index] = null; + return removed; + } + return null; + } + + public final boolean hasRecipe(int index) { + return memorizedRecipes[index] != null; + } + + public void writeInitialSyncData(@NotNull PacketBuffer buf) { + this.writeRecipes(buf); + } + + public void receiveInitialSyncData(@NotNull PacketBuffer buf) { + this.readRecipes(buf); + } + + @Override + public void readOnClient(int id, PacketBuffer buf) { + if (id == UPDATE_RECIPES) { + this.readRecipes(buf); + } else if (id == REMOVE_RECIPE) { + this.removeRecipe(buf.readByte()); + } else if (id == MAKE_RECIPE) { + int index = buf.readByte(); + var recipe = memorizedRecipes[index]; + if (recipe == null) recipe = new MemorizedRecipe(index); + recipe.recipeResult = NetworkUtils.readItemStack(buf); + recipe.index = index; + memorizedRecipes[index] = recipe; + } else if (id == SYNC_RECIPE) { + var recipe = MemorizedRecipe.fromBuffer(buf); + memorizedRecipes[recipe.index] = recipe; + } else if (id == OFFSET_RECIPE) { + this.offsetRecipe(buf.readByte()); + } else if (id == UPDATE_LOGIC) { + getRecipeLogic().updateCurrentRecipe(); + } + } + + public void writeRecipes(PacketBuffer buf) { + Map written = new Int2ObjectOpenHashMap<>(); + for (int i = 0; i < memorizedRecipes.length; i++) { + var stack = getRecipeOutputAtIndex(i); + if (stack.isEmpty()) continue; + written.put(i, stack); + } + buf.writeByte(written.size()); + for (var entry : written.entrySet()) { + var recipe = memorizedRecipes[entry.getKey()]; + buf.writeByte(recipe.index); + NetworkUtils.writeItemStack(buf, recipe.recipeResult); + buf.writeInt(recipe.timesUsed); + buf.writeBoolean(recipe.isRecipeLocked()); + } + } + + public void readRecipes(PacketBuffer buf) { + int size = buf.readByte(); + for (int i = 0; i < size; i++) { + int index = buf.readByte(); + if (!hasRecipe(index)) + memorizedRecipes[index] = new MemorizedRecipe(index); + + memorizedRecipes[index].recipeResult = NetworkUtils.readItemStack(buf); + memorizedRecipes[index].timesUsed = buf.readInt(); + memorizedRecipes[index].recipeLocked = buf.readBoolean(); + } + } + + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == UPDATE_RECIPES) { + syncToClient(UPDATE_RECIPES, this::writeRecipes); + } else if (id == MOUSE_CLICK) { + // read mouse data + int index = buf.readByte(); + var data = MouseData.readPacket(buf); + var recipe = getRecipeAtIndex(index); + if (recipe == null) return; + + if (data.shift && data.mouseButton == 0) { + recipe.setRecipeLocked(!recipe.isRecipeLocked()); + } else if (data.mouseButton == 0) { + loadRecipe(index); + } else if (data.mouseButton == 1 && !recipe.isRecipeLocked()) { + removeRecipe(index); + } + } + } + public static class MemorizedRecipe { private final ItemStackHandler craftingMatrix = new ItemStackHandler(9); - private ItemStack recipeResult; + private ItemStack recipeResult = ItemStack.EMPTY; private boolean recipeLocked = false; - private int timesUsed = 0; + public int timesUsed = 0; + public int index; - private MemorizedRecipe() {} + private MemorizedRecipe(int index) { + this.index = index; + } private NBTTagCompound serializeNBT() { NBTTagCompound result = new NBTTagCompound(); @@ -130,8 +296,8 @@ private NBTTagCompound serializeNBT() { return result; } - private static MemorizedRecipe deserializeNBT(NBTTagCompound tagCompound) { - MemorizedRecipe recipe = new MemorizedRecipe(); + private static MemorizedRecipe deserializeNBT(NBTTagCompound tagCompound, int index) { + MemorizedRecipe recipe = new MemorizedRecipe(index); recipe.recipeResult = new ItemStack(tagCompound.getCompoundTag("Result")); recipe.craftingMatrix.deserializeNBT(tagCompound.getCompoundTag("Matrix")); recipe.recipeLocked = tagCompound.getBoolean("Locked"); @@ -139,6 +305,21 @@ private static MemorizedRecipe deserializeNBT(NBTTagCompound tagCompound) { return recipe; } + private void writeToBuffer(PacketBuffer buffer) { + buffer.writeByte(this.index); + buffer.writeInt(this.timesUsed); + buffer.writeBoolean(this.recipeLocked); + NetworkUtils.writeItemStack(buffer, this.recipeResult); + } + + private static @NotNull MemorizedRecipe fromBuffer(PacketBuffer buffer) { + var recipe = new MemorizedRecipe(buffer.readByte()); + recipe.timesUsed = buffer.readInt(); + recipe.recipeLocked = buffer.readBoolean(); + recipe.recipeResult = NetworkUtils.readItemStack(buffer); + return recipe; + } + private void initialize(ItemStack recipeResult) { this.recipeResult = recipeResult.copy(); for (int i = 0; i < this.craftingMatrix.getSlots(); i++) { @@ -166,5 +347,23 @@ public boolean isRecipeLocked() { public void setRecipeLocked(boolean recipeLocked) { this.recipeLocked = recipeLocked; } + + public MemorizedRecipe copy() { + var recipe = new MemorizedRecipe(this.index); + recipe.initialize(this.recipeResult); + recipe.updateCraftingMatrix(this.craftingMatrix); + recipe.recipeLocked = this.recipeLocked; + recipe.timesUsed = this.timesUsed; + return recipe; + } + + @Override + public String toString() { + return String.format("MemorizedRecipe{%dx %s, locked: %s, times used: %d}", + getRecipeResult().getCount(), + getRecipeResult().getDisplayName(), + recipeLocked, + timesUsed); + } } } diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java index 1f00f06818b..3e00c251170 100644 --- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java +++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java @@ -148,8 +148,19 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) for (int i = 0; i < rows; i++) { widgets.add(new ArrayList<>()); for (int j = 0; j < this.rowSize; j++) { - widgets.get(i).add(new ItemSlot().slot(SyncHandlers.itemSlot(inventory, i * rowSize + j) - .slotGroup("item_inv"))); + int index = i * rowSize + j; + widgets.get(i).add(new ItemSlot().slot(SyncHandlers.itemSlot(inventory, index) + .slotGroup("item_inv") + .changeListener((newItem, onlyAmountChanged, client, init) -> { + if (!onlyAmountChanged && !client && !init) { + for (var facing : EnumFacing.VALUES) { + var neighbor = getNeighbor(facing); + if (neighbor instanceof IGregTechTileEntity gregTechTileEntity) { + gregTechTileEntity.getMetaTileEntity().onNeighborChanged(); + } + } + } + }))); } } return GTGuis.createPanel(this, rowSize * 18 + 14, 18 + 4 * 18 + 5 + 14 + 18 * rows) diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityWorkbench.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityWorkbench.java index 44c92f8e11a..32f074426bb 100644 --- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityWorkbench.java +++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityWorkbench.java @@ -1,54 +1,63 @@ package gregtech.common.metatileentities.storage; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; -import gregtech.api.gui.ModularUI.Builder; -import gregtech.api.gui.Widget.ClickData; -import gregtech.api.gui.resources.TextureArea; -import gregtech.api.gui.widgets.AbstractWidgetGroup; -import gregtech.api.gui.widgets.ClickButtonWidget; -import gregtech.api.gui.widgets.CraftingStationInputWidgetGroup; -import gregtech.api.gui.widgets.ImageWidget; -import gregtech.api.gui.widgets.LabelWidget; -import gregtech.api.gui.widgets.SimpleTextWidget; -import gregtech.api.gui.widgets.SlotWidget; -import gregtech.api.gui.widgets.TabGroup; -import gregtech.api.gui.widgets.TabGroup.TabLocation; -import gregtech.api.gui.widgets.WidgetGroup; -import gregtech.api.gui.widgets.tab.ItemTabInfo; +import gregtech.api.capability.GregtechDataCodes; +import gregtech.api.capability.impl.ItemHandlerList; import gregtech.api.items.itemhandlers.GTItemStackHandler; import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; -import gregtech.api.storage.ICraftingStorage; +import gregtech.api.mui.GTGuiTextures; +import gregtech.api.mui.GTGuis; +import gregtech.api.mui.sync.PagedWidgetSyncHandler; import gregtech.api.util.GTUtility; -import gregtech.api.util.Position; import gregtech.client.renderer.texture.Textures; -import gregtech.common.gui.widget.craftingstation.CraftingSlotWidget; -import gregtech.common.gui.widget.craftingstation.ItemListGridWidget; -import gregtech.common.gui.widget.craftingstation.MemorizedRecipeWidget; -import gregtech.common.inventory.IItemList; import gregtech.common.inventory.handlers.SingleItemStackHandler; import gregtech.common.inventory.handlers.ToolItemStackHandler; -import gregtech.common.inventory.itemsource.ItemSources; -import gregtech.common.inventory.itemsource.sources.InventoryItemSource; +import gregtech.common.mui.widget.workbench.CraftingInputSlot; +import gregtech.common.mui.widget.workbench.CraftingOutputSlot; +import gregtech.common.mui.widget.workbench.RecipeMemorySlot; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.I18n; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.ItemStackHandler; import codechicken.lib.render.CCRenderState; import codechicken.lib.render.pipeline.ColourMultiplier; import codechicken.lib.render.pipeline.IVertexOperation; import codechicken.lib.vec.Matrix4; +import com.cleanroommc.modularui.api.drawable.IDrawable; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.drawable.ItemDrawable; +import com.cleanroommc.modularui.factory.PosGuiData; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.value.sync.IntSyncValue; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ItemSlot; +import com.cleanroommc.modularui.widgets.PageButton; +import com.cleanroommc.modularui.widgets.PagedWidget; +import com.cleanroommc.modularui.widgets.SlotGroupWidget; +import com.cleanroommc.modularui.widgets.layout.Flow; +import com.cleanroommc.modularui.widgets.layout.Grid; +import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import com.cleanroommc.modularui.widgets.slot.SlotGroup; import com.google.common.base.Preconditions; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; @@ -56,68 +65,33 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; -public class MetaTileEntityWorkbench extends MetaTileEntity implements ICraftingStorage { +public class MetaTileEntityWorkbench extends MetaTileEntity { + + private static final IDrawable CHEST = new ItemDrawable(new ItemStack(Blocks.CHEST)) + .asIcon().size(16); + + private final IDrawable WORKSTATION = new ItemDrawable(getStackForm()) + .asIcon().size(16); - private final ItemStackHandler internalInventory = new GTItemStackHandler(this, 18); private final ItemStackHandler craftingGrid = new SingleItemStackHandler(9); + private final ItemStackHandler internalInventory = new GTItemStackHandler(this, 18); private final ItemStackHandler toolInventory = new ToolItemStackHandler(9); - private final CraftingRecipeMemory recipeMemory = new CraftingRecipeMemory(9); + private ItemHandlerList combinedInventory; + private ItemHandlerList connectedInventory; + + private final CraftingRecipeMemory recipeMemory = new CraftingRecipeMemory(9, this.craftingGrid); private CraftingRecipeLogic recipeLogic = null; private int itemsCrafted = 0; - private final ArrayList listeners = new ArrayList<>(); - public MetaTileEntityWorkbench(ResourceLocation metaTileEntityId) { super(metaTileEntityId); } - public static AbstractWidgetGroup createWorkbenchTab(CraftingRecipeLogic craftingRecipeLogic, - ItemStackHandler craftingGrid, - CraftingRecipeMemory recipeMemory, - ItemStackHandler toolInventory, - ItemStackHandler internalInventory) { - WidgetGroup widgetGroup = new WidgetGroup(); - widgetGroup.addWidget(new ImageWidget(88 - 13, 44 - 14, 26, 26, GuiTextures.SLOT)); - widgetGroup.addWidget(new CraftingSlotWidget(craftingRecipeLogic, 0, 88 - 9, 44 - 9)); - - // crafting grid - widgetGroup.addWidget(new CraftingStationInputWidgetGroup(4, 7, craftingGrid, craftingRecipeLogic)); - - Supplier textSupplier = () -> Integer.toString(craftingRecipeLogic.getItemsCraftedAmount()); - widgetGroup.addWidget(new SimpleTextWidget(88, 44 + 19, "", textSupplier)); - - Consumer clearAction = (clickData) -> craftingRecipeLogic.clearCraftingGrid(); - widgetGroup.addWidget(new ClickButtonWidget(8 + 18 * 3 + 3, 16, 8, 8, "", clearAction) - .setButtonTexture(GuiTextures.BUTTON_CLEAR_GRID)); - - widgetGroup.addWidget(new ImageWidget(168 - 18 * 3, 44 - 19 * 3 / 2, 18 * 3, 18 * 3, - TextureArea.fullImage("textures/gui/base/darkened_slot.png"))); - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - widgetGroup.addWidget(new MemorizedRecipeWidget(recipeMemory, j + i * 3, craftingGrid, - 168 - 18 * 3 / 2 - 27 + j * 18, 44 - 28 + i * 18)); - } - } - // tool inventory - for (int i = 0; i < 9; i++) { - widgetGroup.addWidget(new SlotWidget(toolInventory, i, 7 + i * 18, 75) - .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.TOOL_SLOT_OVERLAY)); - } - // internal inventory - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < 9; ++j) { - widgetGroup.addWidget(new SlotWidget(internalInventory, j + i * 9, 7 + j * 18, 98 + i * 18) - .setBackgroundTexture(GuiTextures.SLOT)); - } - } - return widgetGroup; - } - @Override public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) { return new MetaTileEntityWorkbench(metaTileEntityId); @@ -141,13 +115,33 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, Textures.CRAFTING_TABLE.renderOriented(renderState, translation, pipeline, getFrontFacing()); } + @Override + public void writeInitialSyncData(@NotNull PacketBuffer buf) { + super.writeInitialSyncData(buf); + buf.writeInt(this.itemsCrafted); + for (int i = 0; i < craftingGrid.getSlots(); i++) { + NetworkUtils.writeItemStack(buf, craftingGrid.getStackInSlot(i)); + } + this.recipeMemory.writeInitialSyncData(buf); + } + + @Override + public void receiveInitialSyncData(@NotNull PacketBuffer buf) { + super.receiveInitialSyncData(buf); + this.itemsCrafted = buf.readInt(); + for (int i = 0; i < craftingGrid.getSlots(); i++) { + craftingGrid.setStackInSlot(i, NetworkUtils.readItemStack(buf)); + } + this.recipeMemory.receiveInitialSyncData(buf); + } + @Override public NBTTagCompound writeToNBT(NBTTagCompound data) { super.writeToNBT(data); data.setTag("CraftingGridInventory", craftingGrid.serializeNBT()); data.setTag("ToolInventory", toolInventory.serializeNBT()); data.setTag("InternalInventory", internalInventory.serializeNBT()); - data.setInteger("ItemsCrafted", recipeLogic == null ? itemsCrafted : recipeLogic.getItemsCraftedAmount()); + data.setInteger("ItemsCrafted", itemsCrafted); data.setTag("RecipeMemory", recipeMemory.serializeNBT()); return data; } @@ -162,33 +156,36 @@ public void readFromNBT(NBTTagCompound data) { this.recipeMemory.deserializeNBT(data.getCompoundTag("RecipeMemory")); } - private void createCraftingRecipeLogic(EntityPlayer entityPlayer) { - if (!getWorld().isRemote) { - if (recipeLogic == null) { - this.recipeLogic = new CraftingRecipeLogic(this); - this.recipeLogic.setItemsCraftedAmount(itemsCrafted); - ItemSources itemSources = this.recipeLogic.getItemSourceList(); - itemSources.addItemHandler(new InventoryItemSource(getWorld(), toolInventory, -2)); - itemSources.addItemHandler(new InventoryItemSource(getWorld(), internalInventory, -1)); - this.recipeLogic.checkNeighbourInventories(getPos()); - } - this.listeners.add(entityPlayer); + public IItemHandlerModifiable getAvailableHandlers() { + var handlers = new ArrayList(); + for (var facing : EnumFacing.VALUES) { + var neighbor = getNeighbor(facing); + if (neighbor == null) continue; + var handler = neighbor.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, facing.getOpposite()); + if (handler != null) handlers.add(handler); } + this.connectedInventory = new ItemHandlerList(handlers); + handlers.clear(); + + handlers.add(this.internalInventory); + handlers.add(this.toolInventory); + handlers.add(this.connectedInventory); + return this.combinedInventory = new ItemHandlerList(handlers); } @Override - public void update() { - super.update(); - if (!getWorld().isRemote) { - if (recipeLogic != null) { - getCraftingRecipeLogic().update(); - } - } + public void onNeighborChanged() { + getCraftingRecipeLogic().updateInventory(getAvailableHandlers()); + writeCustomData(GregtechDataCodes.UPDATE_CLIENT_HANDLER, this::sendHandlerToClient); } - private CraftingRecipeLogic getCraftingRecipeLogic() { + public @NotNull CraftingRecipeLogic getCraftingRecipeLogic() { Preconditions.checkState(getWorld() != null, "getRecipeResolver called too early"); - return recipeLogic; + if (this.recipeLogic == null) { + this.recipeLogic = new CraftingRecipeLogic(getWorld(), getAvailableHandlers(), getCraftingGrid()); + writeCustomData(GregtechDataCodes.UPDATE_CLIENT_HANDLER, this::sendHandlerToClient); + } + return this.recipeLogic; } @Override @@ -198,34 +195,246 @@ public void clearMachineInventory(@NotNull List<@NotNull ItemStack> itemBuffer) clearInventory(itemBuffer, toolInventory); } - private AbstractWidgetGroup createItemListTab() { - WidgetGroup widgetGroup = new WidgetGroup(); - widgetGroup.addWidget(new LabelWidget(5, 20, "gregtech.machine.workbench.storage_note_1")); - widgetGroup.addWidget(new LabelWidget(5, 30, "gregtech.machine.workbench.storage_note_2")); - CraftingRecipeLogic recipeResolver = getCraftingRecipeLogic(); - IItemList itemList = recipeResolver == null ? null : recipeResolver.getItemSourceList(); - widgetGroup.addWidget(new ItemListGridWidget(11, 45, 8, 5, itemList)); - return widgetGroup; + @Override + public boolean usesMui2() { + return true; } @Override - protected ModularUI createUI(EntityPlayer entityPlayer) { - createCraftingRecipeLogic(entityPlayer); + public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager) { + getCraftingRecipeLogic().updateCurrentRecipe(); + this.recipeLogic.clearSlotMap(); + + syncManager.syncValue("recipe_logic", this.recipeLogic); + syncManager.syncValue("recipe_memory", this.recipeMemory); + + var controller = new PagedWidget.Controller(); + syncManager.syncValue("page_controller", new PagedWidgetSyncHandler(controller)); + + return GTGuis.createPanel(this, 176, 224) + .child(Flow.row() + .debugName("tab row") + .widthRel(1f) + .leftRel(0.5f) + .margin(3, 0) + .coverChildrenHeight() + .topRel(0f, 3, 1f) + .child(new PageButton(0, controller) + .tab(GuiTextures.TAB_TOP, 0) + .addTooltipLine(IKey.lang("gregtech.machine.workbench.tab.workbench")) + .overlay(WORKSTATION)) + .child(new PageButton(1, controller) + .tab(GuiTextures.TAB_TOP, 0) + .addTooltipLine(IKey.lang("gregtech.machine.workbench.tab.item_list")) + .addTooltipLine(IKey.lang("gregtech.machine.workbench.storage_note") + .style(TextFormatting.DARK_GRAY)) + .overlay(CHEST))) + .child(IKey.lang(getMetaFullName()) + .asWidget() + .top(7).left(7)) + .child(new PagedWidget<>() + .top(22) + .margin(7) + .widthRel(0.9f) + .controller(controller) + .coverChildrenHeight() + // workstation page + .addPage(Flow.column() + .debugName("crafting page") + .coverChildrenWidth() + .child(Flow.row() + .debugName("crafting row") + .coverChildrenHeight() + .widthRel(1f) + // crafting grid + .child(createCraftingGrid()) + // crafting output slot + .child(createCraftingOutput(guiData, syncManager)) + // recipe memory + .child(createRecipeMemoryGrid(syncManager))) + // tool inventory + .child(createToolInventory(syncManager)) + // internal inventory + .child(createInternalInventory(syncManager))) + // storage page + .addPage(createInventoryPage(syncManager))) + .bindPlayerInventory(); + } + + private ModularSlot trackSlot(IItemHandler handler, int slot) { + int offset = combinedInventory.getIndexOffset(handler); + if (offset == -1) throw new NullPointerException("handler cannot be found"); + this.recipeLogic.updateSlotMap(offset, slot); + return new ModularSlot(handler, slot); + } + + public IWidget createToolInventory(PanelSyncManager syncManager) { + var toolSlots = new SlotGroup("tool_slots", 9, -120, true); + syncManager.registerSlotGroup(toolSlots); + + return SlotGroupWidget.builder() + .row("XXXXXXXXX") + .key('X', i -> new ItemSlot() + .background(GTGuiTextures.SLOT, GTGuiTextures.TOOL_SLOT_OVERLAY) + .slot(trackSlot(this.toolInventory, i) + .slotGroup(toolSlots))) + .build().marginTop(2); + } - Builder builder = ModularUI.builder(GuiTextures.BACKGROUND, 176, 221) - .bindPlayerInventory(entityPlayer.inventory, 138); - builder.label(5, 5, getMetaFullName()); + public IWidget createInternalInventory(PanelSyncManager syncManager) { + var inventory = new SlotGroup("internal_slots", 9, -100, true); + syncManager.registerSlotGroup(inventory); + + return SlotGroupWidget.builder() + .row("XXXXXXXXX") + .row("XXXXXXXXX") + .key('X', i -> new ItemSlot() + .slot(trackSlot(this.internalInventory, i) + .slotGroup(inventory))) + .build().marginTop(2); + } + + public IWidget createCraftingGrid() { + return SlotGroupWidget.builder() + .matrix("XXX", + "XXX", + "XXX") + .key('X', i -> CraftingInputSlot.create(this.recipeLogic, this.craftingGrid, i) + .changeListener((newItem, onlyAmountChanged, client, init) -> { + if (!init) { + this.recipeLogic.updateCurrentRecipe(); + } + }) + .background(GTGuiTextures.SLOT)) + .build() + .child(new ButtonWidget<>() + .margin(2) + .size(8) + .topRel(0f) + .rightRel(0f, 0, 1f) + .background(GTGuiTextures.BUTTON_CLEAR_GRID) + .addTooltipLine(IKey.lang("gregtech.machine.workbench.clear_grid")) + .disableHoverBackground() + .onMousePressed(mouseButton -> { + this.recipeLogic.clearCraftingGrid(); + return true; + })); + } + + public IWidget createCraftingOutput(PosGuiData guiData, PanelSyncManager syncManager) { + var amountCrafted = new IntSyncValue(this::getItemsCrafted, this::setItemsCrafted); + syncManager.syncValue("amount_crafted", amountCrafted); + amountCrafted.updateCacheFromSource(true); // todo remove on mui2 rc3 + + return Flow.column() + .size(54) + .child(new CraftingOutputSlot(amountCrafted, this) + .marginTop(18) + .background(GTGuiTextures.SLOT.asIcon().size(22)) + .marginBottom(4)) + .child(IKey.dynamic(amountCrafted::getStringValue) + .alignment(Alignment.Center) + .asWidget().widthRel(1f)); + } + + public IWidget createRecipeMemoryGrid(PanelSyncManager syncManager) { + return SlotGroupWidget.builder() + .matrix("XXX", + "XXX", + "XXX") + .key('X', i -> new RecipeMemorySlot(this.recipeMemory, i) + .background(GTGuiTextures.SLOT)) + .build().right(0); + } + + public IWidget createInventoryPage(PanelSyncManager syncManager) { + if (this.connectedInventory.getSlots() == 0) { + return Flow.column() + .debugName("inventory page - empty") + .leftRel(0.5f) + .padding(2) + .height(18 * 6) + .width(18 * 8 + 4) + .background(GTGuiTextures.DISPLAY); + } + + // this is actually supposed to include the tool and storage inventory + // but that causes problems + List list = new ArrayList<>(this.connectedInventory.getSlots()); + + int rowSize = Math.min(this.connectedInventory.getSlots(), 8); + var connected = new SlotGroup("connected_inventory", rowSize, true) + .setAllowSorting(false); + syncManager.registerSlotGroup(connected); + + for (int i = 0; i < this.connectedInventory.getSlots(); i++) { + list.add(new ItemSlot() + .setEnabledIf(itemSlot -> { + int slot = itemSlot.getSlot().getSlotIndex(); + return slot < this.connectedInventory.getSlots(); + }) + .slot(trackSlot(this.connectedInventory, i) + .slotGroup(connected))); + } + + // sort list + list.sort((o1, o2) -> { + var left = o1.getSlot().getStack(); + var right = o2.getSlot().getStack(); + + if (!left.isEmpty() && !right.isEmpty()) return 0; + if (left.isEmpty() && right.isEmpty()) return 0; + + return right.isEmpty() ? -1 : 1; + }); + + return Flow.column() + .debugName("inventory page") + .padding(2) + .leftRel(0.5f) + .coverChildren() + .background(GTGuiTextures.DISPLAY) + .child(new Grid() + .scrollable(new VerticalScrollData()) + .width(18 * 8 + 4) + .height(18 * 6) + .mapTo(rowSize, list)); + } + + public void sendHandlerToClient(PacketBuffer buffer) { + buffer.writeVarInt(this.connectedInventory.getSlots()); + } + + public void readHandler(PacketBuffer buf) { + int connected = buf.readVarInt(); + + // set connected inventory + this.connectedInventory = new ItemHandlerList(Collections.singletonList(new ItemStackHandler(connected))); + + // set combined inventory + this.combinedInventory = new ItemHandlerList(Arrays.asList( + this.internalInventory, + this.toolInventory, + this.connectedInventory)); + + getCraftingRecipeLogic() + .updateInventory(this.combinedInventory); + } + + @Override + public void receiveCustomData(int dataId, @NotNull PacketBuffer buf) { + super.receiveCustomData(dataId, buf); + if (dataId == GregtechDataCodes.UPDATE_CLIENT_HANDLER) { + readHandler(buf); + } + } - TabGroup tabGroup = new TabGroup<>(TabLocation.HORIZONTAL_TOP_LEFT, Position.ORIGIN); - tabGroup.addTab(new ItemTabInfo("gregtech.machine.workbench.tab.workbench", - new ItemStack(Blocks.CRAFTING_TABLE)), - createWorkbenchTab(recipeLogic, craftingGrid, recipeMemory, toolInventory, internalInventory)); - tabGroup.addTab(new ItemTabInfo("gregtech.machine.workbench.tab.item_list", - new ItemStack(Blocks.CHEST)), createItemListTab()); - builder.widget(tabGroup); - builder.bindCloseListener(() -> discardRecipeResolver(entityPlayer)); + public int getItemsCrafted() { + return this.itemsCrafted; + } - return builder.build(getHolder(), entityPlayer); + public void setItemsCrafted(int itemsCrafted) { + this.itemsCrafted = itemsCrafted; } @Override @@ -234,17 +443,6 @@ public void addInformation(ItemStack stack, @Nullable World world, List tooltip.add(I18n.format("gregtech.machine.workbench.tooltip2")); } - public void discardRecipeResolver(EntityPlayer entityPlayer) { - this.listeners.remove(entityPlayer); - if (listeners.isEmpty()) { - if (!getWorld().isRemote && recipeLogic != null) { - itemsCrafted = recipeLogic.getItemsCraftedAmount(); - this.markDirty(); - } - recipeLogic = null; - } - } - public ItemStackHandler getCraftingGrid() { return craftingGrid; } diff --git a/src/main/java/gregtech/common/mui/widget/workbench/CraftingInputSlot.java b/src/main/java/gregtech/common/mui/widget/workbench/CraftingInputSlot.java new file mode 100644 index 00000000000..0d45bdd1355 --- /dev/null +++ b/src/main/java/gregtech/common/mui/widget/workbench/CraftingInputSlot.java @@ -0,0 +1,224 @@ +package gregtech.common.mui.widget.workbench; + +import gregtech.api.util.GTUtility; +import gregtech.client.utils.RenderUtil; +import gregtech.common.metatileentities.storage.CraftingRecipeLogic; + +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.items.ItemHandlerHelper; + +import com.cleanroommc.modularui.api.widget.IGuiAction; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.integration.jei.JeiGhostIngredientSlot; +import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widgets.slot.IOnSlotChanged; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CraftingInputSlot extends Widget implements Interactable, + JeiGhostIngredientSlot, + JeiIngredientProvider { + + private final InputSyncHandler syncHandler; + public boolean hasIngredients = true; + private static boolean dragging = false; + + private CraftingInputSlot(IItemHandlerModifiable handler, int index) { + this.syncHandler = new InputSyncHandler(handler, index); + setSyncHandler(this.syncHandler); + tooltipAutoUpdate(true); + tooltipBuilder(tooltip -> { + if (!isSynced()) return; + ItemStack stack = this.syncHandler.getStack(); + if (stack.isEmpty()) return; + tooltip.addFromItem(stack); + }); + + listenGuiAction((IGuiAction.MouseDrag) (m, t) -> { + if (isHovering() && dragging && syncHandler.isValid()) { + var player = syncHandler.getSyncManager().getCursorItem(); + if (!ItemHandlerHelper.canItemStacksStack(player, getStack())) + syncHandler.syncStack(); + return true; + } + return false; + }); + + listenGuiAction((IGuiAction.MouseReleased) mouseButton -> { + dragging = false; + return true; + }); + } + + public static CraftingInputSlot create(CraftingRecipeLogic logic, IItemHandlerModifiable handler, int index) { + var slot = new CraftingInputSlot(handler, index); + logic.setInputSlot(slot, index); + return slot; + } + + @Override + public boolean isValidSyncHandler(SyncHandler syncHandler) { + return syncHandler instanceof InputSyncHandler; + } + + @Override + public void onInit() { + getContext().getJeiSettings().addJeiGhostIngredientSlot(this); + } + + public CraftingInputSlot changeListener(IOnSlotChanged listener) { + this.syncHandler.listener = listener; + return this; + } + + @NotNull + @Override + public Result onMousePressed(int mouseButton) { + if (!this.syncHandler.isValid() || dragging) + return Result.IGNORE; + + this.syncHandler.syncStack(); + return Result.SUCCESS; + } + + @Override + public void onMouseDrag(int mouseButton, long timeSinceClick) { + if (!dragging && timeSinceClick > 100) { + dragging = true; + } + } + + @Override + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { + ItemStack itemstack = this.syncHandler.getStack(); + if (itemstack.isEmpty()) return; + + if (!this.hasIngredients) { + RenderUtil.renderRect(0, 0, 18, 18, 200, 0x80FF0000); + } + + RenderUtil.renderItem(itemstack, 1, 1, 16, 16); + } + + @Override + public void drawForeground(ModularGuiContext context) { + RichTooltip tooltip = getTooltip(); + if (tooltip != null && isHoveringFor(tooltip.getShowUpTimer())) { + tooltip.draw(getContext(), this.syncHandler.getStack()); + } + } + + @Override + public void setGhostIngredient(@NotNull ItemStack ingredient) { + syncHandler.setStack(ingredient, true); + } + + @Override + public @Nullable ItemStack castGhostIngredientIfValid(@NotNull Object ingredient) { + return areAncestorsEnabled() && ingredient instanceof ItemStack ? (ItemStack) ingredient : null; + } + + @Override + public @NotNull ItemStack getIngredient() { + return this.getStack(); + } + + public ItemStack getStack() { + return syncHandler.getStack(); + } + + public int getIndex() { + return syncHandler.index; + } + + public void setStack(ItemStack stack) { + this.syncHandler.setStack(stack, true); + } + + protected static class InputSyncHandler extends SyncHandler { + + public static final int SLOT_CHANGED = 1; + + private final IItemHandlerModifiable handler; + private final int index; + private ItemStack lastStoredItem; + + private IOnSlotChanged listener = IOnSlotChanged.DEFAULT; + + public InputSyncHandler(IItemHandlerModifiable handler, int index) { + this.handler = handler; + this.index = index; + } + + @Override + public void init(String key, PanelSyncManager syncHandler) { + super.init(key, syncHandler); + this.lastStoredItem = this.handler.getStackInSlot(this.index).copy(); + } + + @Override + public void readOnClient(int id, PacketBuffer buf) { + if (id == SLOT_CHANGED) { + var stack = NetworkUtils.readItemStack(buf); + setStack(stack, false); + } + } + + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == SLOT_CHANGED) { + var stack = NetworkUtils.readItemStack(buf); + this.setStack(stack, false); + } + } + + @Override + public void detectAndSendChanges(boolean init) { + ItemStack itemStack = getStack(); + if (itemStack.isEmpty() && this.lastStoredItem.isEmpty()) return; + boolean onlyAmountChanged = false; + if (init || + !ItemHandlerHelper.canItemStacksStack(this.lastStoredItem, itemStack) || + (onlyAmountChanged = itemStack.getCount() != this.lastStoredItem.getCount())) { + this.listener.onChange(itemStack, onlyAmountChanged, false, init); + if (onlyAmountChanged) { + this.lastStoredItem.setCount(itemStack.getCount()); + } else { + this.lastStoredItem = itemStack.isEmpty() ? ItemStack.EMPTY : itemStack.copy(); + } + syncToClient(SLOT_CHANGED, buffer -> NetworkUtils.writeItemStack(buffer, itemStack)); + } + } + + public void syncStack() { + final var cursorStack = GTUtility.copy(1, getSyncManager().getCursorItem()); + setStack(cursorStack, true); + } + + public ItemStack getStack() { + return this.handler.getStackInSlot(this.index); + } + + /** + * Sets the stack in this slot and calls the onChange listener. + * + * @param stack stack to put into this slot + */ + public void setStack(ItemStack stack, boolean sync) { + var old = getStack(); + boolean onlyAmt = ItemHandlerHelper.canItemStacksStackRelaxed(stack, old); + this.handler.setStackInSlot(this.index, stack); + this.listener.onChange(stack, onlyAmt, getSyncManager().isClient(), false); + if (sync) syncToServer(SLOT_CHANGED, buffer -> NetworkUtils.writeItemStack(buffer, getStack())); + } + } +} diff --git a/src/main/java/gregtech/common/mui/widget/workbench/CraftingOutputSlot.java b/src/main/java/gregtech/common/mui/widget/workbench/CraftingOutputSlot.java new file mode 100644 index 00000000000..da3c8d21dbc --- /dev/null +++ b/src/main/java/gregtech/common/mui/widget/workbench/CraftingOutputSlot.java @@ -0,0 +1,354 @@ +package gregtech.common.mui.widget.workbench; + +import gregtech.client.utils.RenderUtil; +import gregtech.common.metatileentities.storage.CraftingRecipeLogic; +import gregtech.common.metatileentities.storage.CraftingRecipeMemory; +import gregtech.common.metatileentities.storage.MetaTileEntityWorkbench; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.items.ItemHandlerHelper; + +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.MouseData; +import com.cleanroommc.modularui.value.sync.IntSyncValue; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import com.cleanroommc.modularui.widgets.slot.SlotGroup; +import com.google.common.collect.Lists; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class CraftingOutputSlot extends Widget implements Interactable, JeiIngredientProvider { + + private static final int MOUSE_CLICK = 2; + private static final int SYNC_STACK = 5; + private final CraftingSlotSH syncHandler; + + public CraftingOutputSlot(IntSyncValue syncValue, MetaTileEntityWorkbench workbench) { + this.syncHandler = new CraftingSlotSH( + new CraftingOutputMS( + workbench.getCraftingRecipeLogic().getCraftingResultInventory(), + syncValue, workbench)); + setSyncHandler(this.syncHandler); + tooltipAutoUpdate(true); + tooltipBuilder(tooltip -> { + if (!isSynced()) return; + ItemStack stack = this.syncHandler.getOutputStack(); + if (stack.isEmpty()) return; + tooltip.addFromItem(stack); + }); + } + + @Override + public boolean isValidSyncHandler(SyncHandler syncHandler) { + return syncHandler instanceof CraftingSlotSH; + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + MouseData mouseData = MouseData.create(mouseButton); + this.syncHandler.syncToServer(MOUSE_CLICK, mouseData::writeToPacket); + return Result.SUCCESS; + } + + @Override + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { + ItemStack itemstack = this.syncHandler.getOutputStack(); + if (itemstack.isEmpty()) return; + + RenderUtil.renderItem(itemstack, 1, 1, 16, 16); + } + + @Override + public void drawForeground(ModularGuiContext context) { + RichTooltip tooltip = getTooltip(); + if (tooltip != null && isHoveringFor(tooltip.getShowUpTimer())) { + tooltip.draw(getContext(), this.syncHandler.getOutputStack()); + } + } + + @Override + public @Nullable ItemStack getIngredient() { + return this.syncHandler.getOutputStack(); + } + + protected static class CraftingSlotSH extends SyncHandler { + + private final CraftingRecipeLogic recipeLogic; + private final CraftingOutputMS slot; + + private final List shiftClickSlots = new ArrayList<>(); + + public CraftingSlotSH(CraftingOutputMS slot) { + this.slot = slot; + this.recipeLogic = slot.recipeLogic; + } + + @Override + public void init(String key, PanelSyncManager syncManager) { + super.init(key, syncManager); + getSyncManager().getSlotGroups().stream() + .filter(SlotGroup::allowShiftTransfer) + .sorted(Comparator.comparingInt(SlotGroup::getShiftClickPriority)) + .collect(Collectors.toList()) + .forEach(slotGroup -> { + for (Slot slot : slotGroup.getSlots()) { + if (slot instanceof ModularSlot modularSlot) { + this.shiftClickSlots.add(modularSlot); + } + } + }); + } + + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == MOUSE_CLICK) { + ForgeHooks.setCraftingPlayer(getSyncManager().getPlayer()); + var data = MouseData.readPacket(buf); + + if (recipeLogic.isRecipeValid() && this.slot.canTakeStack(getSyncManager().getPlayer())) { + ItemStack cursorStack = getSyncManager().getCursorItem(); + ItemStack outputStack = getOutputStack(); + boolean hasSpace; + if (data.shift) { + hasSpace = quickTransfer(getOutputStack(), true); + } else { + hasSpace = cursorStack.isEmpty() || + ItemHandlerHelper.canItemStacksStack(cursorStack, outputStack); + } + if (hasSpace && recipeLogic.performRecipe()) { + handleItemCraft(outputStack, getSyncManager().getPlayer()); + + if (data.shift) { + ItemStack finalStack = outputStack.copy(); + while (quickTransfer(finalStack, true) && + finalStack.getCount() < outputStack.getMaxStackSize()) { + if (!recipeLogic.performRecipe()) break; + finalStack.setCount(finalStack.getCount() + outputStack.getCount()); + handleItemCraft(outputStack, getSyncManager().getPlayer()); + } + quickTransfer(finalStack, false); + } else { + syncToClient(SYNC_STACK, this::syncCraftedStack); + } + } + } + ForgeHooks.setCraftingPlayer(null); + } + } + + private boolean insertStack(ItemStack fromStack, ModularSlot toSlot, boolean simulate) { + ItemStack toStack = toSlot.getStack().copy(); + if (ItemHandlerHelper.canItemStacksStack(fromStack, toStack)) { + int j = toStack.getCount() + fromStack.getCount(); + int maxSize = Math.min(toSlot.getSlotStackLimit(), fromStack.getMaxStackSize()); + + if (j <= maxSize) { + if (simulate) return true; + fromStack.setCount(0); + toStack.setCount(j); + toSlot.putStack(toStack); + } else if (toStack.getCount() < maxSize) { + if (simulate) return true; + fromStack.shrink(maxSize - toStack.getCount()); + toStack.setCount(maxSize); + toSlot.putStack(toStack); + } + + return fromStack.isEmpty(); + } else if (toStack.isEmpty()) { + if (simulate) return true; + int maxSize = Math.max(toSlot.getSlotStackLimit(), fromStack.getCount()); + toSlot.putStack(fromStack.splitStack(maxSize)); + return fromStack.isEmpty(); + } + return false; + } + + public boolean quickTransfer(ItemStack fromStack, boolean simulate) { + List emptySlots = new ArrayList<>(); + for (ModularSlot toSlot : this.shiftClickSlots) { + if (toSlot.isEnabled() && toSlot.isItemValid(fromStack)) { + if (toSlot.getStack().isEmpty()) { + emptySlots.add(toSlot); + continue; + } + + if (insertStack(fromStack, toSlot, simulate)) { + if (simulate || fromStack.isEmpty()) return true; + } + } + } + for (ModularSlot emptySlot : emptySlots) { + ItemStack itemstack = emptySlot.getStack(); + if (emptySlot.isEnabled() && itemstack.isEmpty() && emptySlot.isItemValid(fromStack)) { + if (insertStack(fromStack, emptySlot, simulate)) { + if (simulate || fromStack.isEmpty()) return true; + } + } + } + return false; + } + + @Override + public void readOnClient(int id, PacketBuffer buf) { + if (id == SYNC_STACK) { + getSyncManager().setCursorItem(NetworkUtils.readItemStack(buf)); + } + } + + private void syncCraftedStack(PacketBuffer buf) { + ItemStack curStack = getSyncManager().getCursorItem(); + ItemStack outStack = this.slot.getStack(); + ItemStack toSync = outStack.copy(); + if (ItemHandlerHelper.canItemStacksStack(curStack, outStack)) { + int combined = curStack.getCount() + outStack.getCount(); + if (combined <= outStack.getMaxStackSize()) { + toSync.setCount(curStack.getCount() + outStack.getCount()); + } else { + toSync.setCount(outStack.getMaxStackSize()); + } + } else if (!curStack.isEmpty()) { + toSync = curStack; + } + NetworkUtils.writeItemStack(buf, toSync); + } + + public ItemStack getOutputStack() { + return slot.getStack(); + } + + public void handleItemCraft(ItemStack craftedStack, EntityPlayer player) { + craftedStack.onCrafting(player.world, player, 1); + + var inventoryCrafting = recipeLogic.getCraftingMatrix(); + + // if we're not simulated, fire the event, unlock recipe and add crafted items, and play sounds + FMLCommonHandler.instance().firePlayerCraftingEvent(player, craftedStack, inventoryCrafting); + + var cachedRecipe = recipeLogic.getCachedRecipe(); + if (cachedRecipe != null && !cachedRecipe.isDynamic()) { + player.unlockRecipes(Lists.newArrayList(cachedRecipe)); + } + if (cachedRecipe != null) { + ItemStack resultStack = cachedRecipe.getCraftingResult(inventoryCrafting); + this.slot.notifyRecipePerformed(resultStack); + } + } + } + + protected static class CraftingOutputMS extends ModularSlot { + + private final IntSyncValue syncValue; + private final CraftingRecipeLogic recipeLogic; + private final CraftingRecipeMemory recipeMemory; + private final IItemHandler craftingGrid; + + public CraftingOutputMS(IInventory craftingInventory, IntSyncValue syncValue, + MetaTileEntityWorkbench workbench) { + super(new InventoryWrapper(craftingInventory, workbench.getCraftingRecipeLogic()), 0, true); + this.syncValue = syncValue; + this.recipeLogic = workbench.getCraftingRecipeLogic(); + this.recipeMemory = workbench.getRecipeMemory(); + this.craftingGrid = workbench.getCraftingGrid(); + } + + @Override + public boolean canTakeStack(EntityPlayer playerIn) { + ItemStack curStack = playerIn.inventory.getItemStack(); + if (curStack.isEmpty()) return true; + + ItemStack outStack = recipeLogic.getCachedRecipe().getRecipeOutput(); + if (curStack.getItem() == outStack.getItem() && + curStack.getMetadata() == outStack.getMetadata() && + ItemStack.areItemStackTagsEqual(curStack, outStack)) { + + int combined = curStack.getCount() + outStack.getCount(); + return combined <= outStack.getMaxStackSize(); + } else { + return false; + } + } + + public void notifyRecipePerformed(ItemStack stack) { + this.syncValue.setValue(this.syncValue.getValue() + stack.getCount(), true, true); + this.recipeMemory.notifyRecipePerformed(this.craftingGrid, stack); + } + + @Override + public void putStack(@NotNull ItemStack stack) { + super.putStack(getStack()); + } + + @Override + public @NotNull ItemStack decrStackSize(int amount) { + return getStack(); + } + } + + private static class InventoryWrapper implements IItemHandlerModifiable { + + private final IInventory inventory; + private final CraftingRecipeLogic recipeLogic; + + private InventoryWrapper(IInventory inventory, CraftingRecipeLogic recipeLogic) { + this.inventory = inventory; + this.recipeLogic = recipeLogic; + } + + @Override + public int getSlots() { + return inventory.getSizeInventory(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return inventory.getStackInSlot(slot).copy(); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return inventory.getStackInSlot(slot); + } + + @Override + public int getSlotLimit(int slot) { + return inventory.getInventoryStackLimit(); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + if (!recipeLogic.isRecipeValid()) { + inventory.setInventorySlotContents(slot, ItemStack.EMPTY); + } + + if (!stack.isEmpty()) + inventory.setInventorySlotContents(slot, stack); + } + } +} diff --git a/src/main/java/gregtech/common/mui/widget/workbench/RecipeMemorySlot.java b/src/main/java/gregtech/common/mui/widget/workbench/RecipeMemorySlot.java new file mode 100644 index 00000000000..03d9d6728ec --- /dev/null +++ b/src/main/java/gregtech/common/mui/widget/workbench/RecipeMemorySlot.java @@ -0,0 +1,98 @@ +package gregtech.common.mui.widget.workbench; + +import gregtech.api.mui.GTGuiTextures; +import gregtech.client.utils.RenderUtil; +import gregtech.common.metatileentities.storage.CraftingRecipeMemory; + +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.TextFormatting; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.MouseData; +import com.cleanroommc.modularui.widget.Widget; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class RecipeMemorySlot extends Widget implements Interactable, JeiIngredientProvider { + + private final CraftingRecipeMemory memory; + private final int index; + + public RecipeMemorySlot(CraftingRecipeMemory memory, int index) { + this.memory = memory; + this.index = index; + tooltipAutoUpdate(true); + tooltipBuilder(tooltip -> { + var recipe = memory.getRecipeAtIndex(this.index); + if (recipe == null) return; + + tooltip.addFromItem(recipe.getRecipeResult()); + + tooltip.spaceLine(2); + tooltip.addLine(IKey.lang("gregtech.recipe_memory_widget.tooltip.1")); + tooltip.addLine(IKey.lang("gregtech.recipe_memory_widget.tooltip.2")); + tooltip.addLine(IKey.lang("gregtech.recipe_memory_widget.tooltip.0", recipe.timesUsed) + .style(TextFormatting.WHITE)); + }); + } + + @Override + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { + ItemStack itemstack = this.memory.getRecipeOutputAtIndex(this.index); + if (itemstack.isEmpty()) return; + + int cachedCount = itemstack.getCount(); + itemstack.setCount(1); // required to not render the amount overlay + RenderUtil.renderItem(itemstack, 1, 1, 16, 16); + itemstack.setCount(cachedCount); + + // noinspection DataFlowIssue + if (this.memory.getRecipeAtIndex(this.index).isRecipeLocked()) { + GlStateManager.disableDepth(); + GTGuiTextures.RECIPE_LOCK.draw(context, 10, 1, 8, 8, widgetTheme); + GlStateManager.enableDepth(); + } + } + + @Override + public void drawForeground(ModularGuiContext context) { + RichTooltip tooltip = getTooltip(); + if (tooltip != null && isHoveringFor(tooltip.getShowUpTimer())) { + tooltip.draw(getContext(), this.memory.getRecipeOutputAtIndex(this.index)); + } + } + + @NotNull + @Override + public Result onMousePressed(int mouseButton) { + var recipe = memory.getRecipeAtIndex(this.index); + if (recipe == null) + return Result.IGNORE; + + var data = MouseData.create(mouseButton); + this.memory.syncToServer(CraftingRecipeMemory.MOUSE_CLICK, buffer -> { + buffer.writeByte(this.index); + data.writeToPacket(buffer); + }); + + if (data.shift && data.mouseButton == 0) { + recipe.setRecipeLocked(!recipe.isRecipeLocked()); + } else if (data.mouseButton == 1 && !recipe.isRecipeLocked()) { + this.memory.removeRecipe(index); + } + + return Result.ACCEPT; + } + + @Override + public @Nullable Object getIngredient() { + if (!this.memory.hasRecipe(this.index)) return null; + return this.memory.getRecipeOutputAtIndex(this.index); + } +} diff --git a/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java b/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java index 5a182dbadde..1dd161878ed 100644 --- a/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java +++ b/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java @@ -12,6 +12,7 @@ import gregtech.api.metatileentity.SteamMetaTileEntity; import gregtech.api.metatileentity.registry.MTERegistry; import gregtech.api.modules.GregTechModule; +import gregtech.api.mui.GregTechGuiTransferHandler; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.category.GTRecipeCategory; @@ -27,7 +28,6 @@ import gregtech.api.worldgen.config.OreDepositDefinition; import gregtech.api.worldgen.config.WorldGenRegistry; import gregtech.common.blocks.MetaBlocks; -import gregtech.common.gui.widget.craftingstation.CraftingSlotWidget; import gregtech.common.items.MetaItems; import gregtech.common.items.ToolItems; import gregtech.common.metatileentities.MetaTileEntities; @@ -154,7 +154,6 @@ public void register(IModRegistry registry) { // register transfer handler for all categories, but not for the crafting station ModularUIGuiHandler modularUIGuiHandler = new ModularUIGuiHandler(jeiHelpers.recipeTransferHandlerHelper()); - modularUIGuiHandler.setValidHandlers(widget -> !(widget instanceof CraftingSlotWidget)); modularUIGuiHandler.blacklistCategory( IntCircuitCategory.UID, GTValues.MODID + ":material_tree", @@ -163,13 +162,12 @@ public void register(IModRegistry registry) { registry.getRecipeTransferRegistry().addRecipeTransferHandler(modularUIGuiHandler, Constants.UNIVERSAL_RECIPE_TRANSFER_UID); + // register transfer handler for crafting recipes + registry.getRecipeTransferRegistry().addRecipeTransferHandler(new GregTechGuiTransferHandler( + jeiHelpers.recipeTransferHandlerHelper()), VanillaRecipeCategoryUid.CRAFTING); + registry.addAdvancedGuiHandlers(modularUIGuiHandler); registry.addGhostIngredientHandler(modularUIGuiHandler.getGuiContainerClass(), modularUIGuiHandler); - // register transfer handler for crafting recipes - ModularUIGuiHandler craftingStationGuiHandler = new ModularUIGuiHandler( - jeiHelpers.recipeTransferHandlerHelper()); - registry.getRecipeTransferRegistry().addRecipeTransferHandler(craftingStationGuiHandler, - VanillaRecipeCategoryUid.CRAFTING); for (RecipeMap recipeMap : RecipeMap.getRecipeMaps()) { if (recipeMap.getRecipeMapUI().isJEIVisible()) { diff --git a/src/main/java/gregtech/mixins/jei/DragManagerAccessor.java b/src/main/java/gregtech/mixins/jei/DragManagerAccessor.java new file mode 100644 index 00000000000..7af21bad31e --- /dev/null +++ b/src/main/java/gregtech/mixins/jei/DragManagerAccessor.java @@ -0,0 +1,14 @@ +package gregtech.mixins.jei; + +import mezz.jei.gui.ghost.GhostIngredientDragManager; +import mezz.jei.gui.overlay.IngredientListOverlay; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +// todo remove on next mui2 update +@Mixin(value = IngredientListOverlay.class, remap = false) +public interface DragManagerAccessor { + + @Accessor("ghostIngredientDragManager") + GhostIngredientDragManager getManager(); +} diff --git a/src/main/java/gregtech/mixins/jei/GhostDragAccessor.java b/src/main/java/gregtech/mixins/jei/GhostDragAccessor.java new file mode 100644 index 00000000000..4293992f63c --- /dev/null +++ b/src/main/java/gregtech/mixins/jei/GhostDragAccessor.java @@ -0,0 +1,14 @@ +package gregtech.mixins.jei; + +import mezz.jei.gui.ghost.GhostIngredientDrag; +import mezz.jei.gui.ghost.GhostIngredientDragManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +// todo remove on next mui2 update +@Mixin(value = GhostIngredientDragManager.class, remap = false) +public interface GhostDragAccessor { + + @Accessor("ghostIngredientDrag") + GhostIngredientDrag getDrag(); +} diff --git a/src/main/java/gregtech/mixins/mui2/InputMixin.java b/src/main/java/gregtech/mixins/mui2/InputMixin.java new file mode 100644 index 00000000000..0e1d3febacf --- /dev/null +++ b/src/main/java/gregtech/mixins/mui2/InputMixin.java @@ -0,0 +1,64 @@ +package gregtech.mixins.mui2; + +import gregtech.api.mui.InputAccessor; + +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.screen.viewport.LocatedWidget; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +// todo remove on next mui2 update +@Mixin(targets = "com.cleanroommc.modularui.screen.ModularPanel$Input", remap = false) +public abstract class InputMixin implements InputAccessor { + + @Shadow + private boolean held; + + @Shadow + private long timeHeld; + + @Shadow + private @Nullable LocatedWidget lastPressed; + + @Shadow + private int lastButton; + + @Shadow + protected abstract void addAcceptedInteractable(Interactable interactable); + + @Override + public boolean held() { + return this.held; + } + + @Override + public void held(boolean held) { + this.held = held; + } + + @Override + public void timeHeld(long a) { + timeHeld = a; + } + + @Override + public LocatedWidget lastPressed() { + return this.lastPressed; + } + + @Override + public void lastPressed(LocatedWidget last) { + this.lastPressed = last; + } + + @Override + public void lastButton(int b) { + this.lastButton = b; + } + + @Override + public void addInteractable(Interactable i) { + addAcceptedInteractable(i); + } +} diff --git a/src/main/java/gregtech/mixins/mui2/ModularPanelMixin.java b/src/main/java/gregtech/mixins/mui2/ModularPanelMixin.java new file mode 100644 index 00000000000..05d287c9610 --- /dev/null +++ b/src/main/java/gregtech/mixins/mui2/ModularPanelMixin.java @@ -0,0 +1,167 @@ +package gregtech.mixins.mui2; + +import gregtech.api.mui.InputAccessor; +import gregtech.api.util.Mods; +import gregtech.integration.jei.JustEnoughItemsModule; +import gregtech.mixins.jei.DragManagerAccessor; +import gregtech.mixins.jei.GhostDragAccessor; + +import net.minecraft.client.Minecraft; + +import com.cleanroommc.modularui.api.layout.IViewport; +import com.cleanroommc.modularui.api.widget.IFocusedWidget; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.integration.jei.JeiGhostIngredientSlot; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.viewport.LocatedWidget; +import com.cleanroommc.modularui.utils.ObjectList; +import com.cleanroommc.modularui.widget.ParentWidget; +import mezz.jei.gui.ghost.GhostIngredientDrag; +import mezz.jei.gui.ghost.GhostIngredientDragManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(value = ModularPanel.class, remap = false) +public abstract class ModularPanelMixin extends ParentWidget implements IViewport { + + @Shadow + @Final + private ObjectList hovering; + + @Shadow + public abstract boolean closeOnOutOfBoundsClick(); + + @Shadow + public abstract void animateClose(); + + @Unique + InputAccessor gregTech$mouse = null; + + /** + * @author Ghzdude - GTCEu + * @reason Implement fixes to phantom slot handling from Mui2 master + */ + // this looks really cursed in mixin.out, but it works + @Overwrite + private boolean lambda$onMousePressed$3(int mouseButton) { + LocatedWidget pressed = LocatedWidget.EMPTY; + boolean result = false; + + if (gregTech$mouse == null) { + // reflection because the type is a private inner class + try { + gregTech$mouse = (InputAccessor) ModularPanel.class.getDeclaredField("mouse").get(this); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + if (this.hovering.isEmpty()) { + if (closeOnOutOfBoundsClick()) { + animateClose(); + result = true; + } + } else { + loop: + for (LocatedWidget widget : this.hovering) { + widget.applyMatrix(getContext()); + if (widget.getElement() instanceof JeiGhostIngredientSlotghostSlot && + Mods.JustEnoughItems.isModLoaded()) { + GhostIngredientDrag drag = gregTech$getGhostDrag(); + if (drag != null && gregTech$insertGhostIngredient(drag, ghostSlot)) { + gregTech$stopDrag(); + pressed = LocatedWidget.EMPTY; + result = true; + widget.unapplyMatrix(getContext()); + break; + } + } + if (widget.getElement() instanceof Interactable interactable) { + switch (interactable.onMousePressed(mouseButton)) { + case IGNORE: + break; + case ACCEPT: { + if (!gregTech$mouse.held()) { + gregTech$mouse.addInteractable(interactable); + } + pressed = widget; + // result = false; + break; + } + case STOP: { + pressed = LocatedWidget.EMPTY; + result = true; + widget.unapplyMatrix(getContext()); + break loop; + } + case SUCCESS: { + if (!gregTech$mouse.held()) { + gregTech$mouse.addInteractable(interactable); + } + pressed = widget; + result = true; + widget.unapplyMatrix(getContext()); + break loop; + } + } + } + if (getContext().onHoveredClick(mouseButton, widget)) { + pressed = LocatedWidget.EMPTY; + result = true; + widget.unapplyMatrix(getContext()); + break; + } + widget.unapplyMatrix(getContext()); + if (widget.getElement().canHover()) { + result = true; + break; + } + } + } + + if (result && pressed.getElement() instanceof IFocusedWidget) { + getContext().focus(pressed); + } else { + getContext().removeFocus(); + } + if (!gregTech$mouse.held()) { + gregTech$mouse.lastPressed(pressed); + if (gregTech$mouse.lastPressed().getElement() != null) { + gregTech$mouse.timeHeld(Minecraft.getSystemTime()); + } + gregTech$mouse.lastButton(mouseButton); + gregTech$mouse.held(true); + } + return result; + } + + @Unique + private static GhostIngredientDrag gregTech$getGhostDrag() { + GhostIngredientDragManager manager = ((DragManagerAccessor) JustEnoughItemsModule.jeiRuntime + .getIngredientListOverlay()).getManager(); + return ((GhostDragAccessor) manager).getDrag(); + } + + @Unique + @SuppressWarnings("rawtypes") + private static boolean gregTech$insertGhostIngredient(GhostIngredientDrag drag, + JeiGhostIngredientSlot slot) { + Object object = slot.castGhostIngredientIfValid(drag.getIngredient()); + if (object != null) { + // noinspection unchecked + slot.setGhostIngredient(object); + return true; + } + return false; + } + + @Unique + private static void gregTech$stopDrag() { + GhostIngredientDragManager manager = ((DragManagerAccessor) JustEnoughItemsModule.jeiRuntime + .getIngredientListOverlay()).getManager(); + manager.stopDrag(); + } +} diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 56a674d293b..85657dc9953 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -1211,6 +1211,7 @@ metaitem.blacklight.tooltip=Long-Wave §dUltraviolet§7 light source gui.widget.incrementButton.default_tooltip=Hold Shift, Ctrl or both to change the amount gui.widget.recipeProgressWidget.default_tooltip=Show Recipes +gregtech.recipe_memory_widget.tooltip.0=Times used: %d gregtech.recipe_memory_widget.tooltip.1=§7Left click to automatically input this recipe into the crafting grid gregtech.recipe_memory_widget.tooltip.2=§7Shift click to lock/unlock this recipe @@ -2831,8 +2832,8 @@ gregtech.machine.workbench.tooltip1=Better than Forestry gregtech.machine.workbench.tooltip2=Has Item Storage, Tool Storage, pulls from adjacent Inventories, and saves Recipes. gregtech.machine.workbench.tab.workbench=Crafting gregtech.machine.workbench.tab.item_list=Storage -gregtech.machine.workbench.storage_note_1=(Available items from connected -gregtech.machine.workbench.storage_note_2=inventories usable for crafting) +gregtech.machine.workbench.storage_note=(Available items from connected\ninventories usable for crafting) +gregtech.machine.workbench.clear_grid=Clear Crafting Grid gregtech.item_list.item_stored=§7Stored: %,d gregtech.machine.workbench.tab.crafting=Crafting diff --git a/src/main/resources/mixins.gregtech.jei.json b/src/main/resources/mixins.gregtech.jei.json index 107771e5f35..3df5ef9885e 100644 --- a/src/main/resources/mixins.gregtech.jei.json +++ b/src/main/resources/mixins.gregtech.jei.json @@ -6,6 +6,8 @@ "compatibilityLevel": "JAVA_8", "mixins": [ "BasicRecipeTransferHandlerServerMixin", + "DragManagerAccessor", + "GhostDragAccessor", "IngredientGridMixin", "JEITooltipMixin", "StackHelperMixin" diff --git a/src/main/resources/mixins.gregtech.mui2.json b/src/main/resources/mixins.gregtech.mui2.json index 427113e6ac8..41f64f92ed4 100644 --- a/src/main/resources/mixins.gregtech.mui2.json +++ b/src/main/resources/mixins.gregtech.mui2.json @@ -7,7 +7,10 @@ "injectors": { "maxShiftBy": 10 }, - "mixins": [], + "mixins": [ + "InputMixin", + "ModularPanelMixin" + ], "client": [ "LangKeyMixin" ],