From 78c7235bea96db0dd376d4806861dfb24a2afc64 Mon Sep 17 00:00:00 2001 From: Serenibyss <10861407+serenibyss@users.noreply.github.com> Date: Fri, 24 May 2024 06:15:15 -0500 Subject: [PATCH] Improve ME Interface Terminal UI (#368) Co-authored-by: NotMyWing --- .../java/appeng/api/config/ActionItems.java | 7 +- .../java/appeng/client/gui/AEBaseGui.java | 2 +- .../implementations/GuiInterfaceTerminal.java | 544 +++++++++--------- .../client/gui/widgets/GuiImgButton.java | 5 +- .../gui/widgets/MEGuiTooltipTextField.java | 236 ++++++++ .../core/localization/ButtonToolTips.java | 11 +- .../core/localization/PlayerMessages.java | 10 +- .../reporting/PartInterfaceTerminal.java | 8 - .../appliedenergistics2/lang/en_us.lang | 16 +- .../textures/guis/newinterfaceterminal.png | Bin 11806 -> 3098 bytes .../textures/guis/states.png | Bin 13804 -> 14483 bytes 11 files changed, 563 insertions(+), 276 deletions(-) create mode 100644 src/main/java/appeng/client/gui/widgets/MEGuiTooltipTextField.java diff --git a/src/api/java/appeng/api/config/ActionItems.java b/src/api/java/appeng/api/config/ActionItems.java index 0d8ea51c924..0b988205a6c 100644 --- a/src/api/java/appeng/api/config/ActionItems.java +++ b/src/api/java/appeng/api/config/ActionItems.java @@ -38,8 +38,11 @@ public enum ActionItems DIVIDE_BY_THREE, DECREASE_BY_ONE, MAX_COUNT, - FREE_MOLECULAR_SLOT_SHORTCUT, + MOLECULAR_ASSEMBLERS_ON, + MOLECULAR_ASSEMBLERS_OFF, TOGGLE_SHOW_FULL_INTERFACES_ON, TOGGLE_SHOW_FULL_INTERFACES_OFF, + TOGGLE_SHOW_ONLY_INVALID_PATTERNS_ON, + TOGGLE_SHOW_ONLY_INVALID_PATTERNS_OFF, HIGHLIGHT_INTERFACE -} \ No newline at end of file +} diff --git a/src/main/java/appeng/client/gui/AEBaseGui.java b/src/main/java/appeng/client/gui/AEBaseGui.java index ea695d16641..7b76eed1f30 100644 --- a/src/main/java/appeng/client/gui/AEBaseGui.java +++ b/src/main/java/appeng/client/gui/AEBaseGui.java @@ -258,7 +258,7 @@ protected void drawGuiSlot(GuiCustomSlot slot, int mouseX, int mouseY, float par } } - private void drawTooltip(ITooltip tooltip, int mouseX, int mouseY) { + protected void drawTooltip(ITooltip tooltip, int mouseX, int mouseY) { final int x = tooltip.xPos(); // ((GuiImgButton) c).x; int y = tooltip.yPos(); // ((GuiImgButton) c).y; diff --git a/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java b/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java index 4e8f75794cf..826370af0db 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java +++ b/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java @@ -27,14 +27,18 @@ import appeng.client.gui.AEBaseGui; import appeng.client.gui.widgets.GuiImgButton; import appeng.client.gui.widgets.GuiScrollbar; -import appeng.client.gui.widgets.MEGuiTextField; +import appeng.client.gui.widgets.MEGuiTooltipTextField; import appeng.client.me.ClientDCInternalInv; import appeng.client.me.SlotDisconnected; import appeng.container.implementations.ContainerInterfaceTerminal; import appeng.container.slot.AppEngSlot; import appeng.core.AEConfig; +import appeng.core.AppEng; +import appeng.core.localization.ButtonToolTips; import appeng.core.localization.GuiText; +import appeng.core.localization.PlayerMessages; import appeng.helpers.DualityInterface; +import appeng.helpers.PatternHelper; import appeng.parts.reporting.PartInterfaceTerminal; import appeng.util.BlockPosUtils; import appeng.util.Platform; @@ -42,13 +46,12 @@ import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.entity.player.InventoryPlayer; -import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTUtil; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.World; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.common.util.Constants; import net.minecraftforge.fml.common.Loader; @@ -71,15 +74,12 @@ public class GuiInterfaceTerminal extends AEBaseGui { - private int rows = 6; - - // TODO: copied from GuiMEMonitorable. It looks not changed, maybe unneeded? - private final int offsetX = 21; - private int maxRows = Integer.MAX_VALUE; - - protected int jeiOffset = Loader.isModLoaded("jei") ? 24 : 0; + private static final int OFFSET_X = 21; + private static final int MAGIC_HEIGHT_NUMBER = 52 + 99; + private static final String MOLECULAR_ASSEMBLER = "molecular assembler"; - private int reservedSpace = 0; + private final boolean jeiEnabled; + private final int jeiButtonPadding; private final HashMap byId = new HashMap<>(); private final HashMultimap byName = HashMultimap.create(); @@ -89,99 +89,116 @@ public class GuiInterfaceTerminal extends AEBaseGui { private final ArrayList names = new ArrayList<>(); private final ArrayList lines = new ArrayList<>(); private final Set matchedStacks = new HashSet<>(); - private final Set matchedInterfaces = new HashSet<>(); private final Map> cachedSearches = new WeakHashMap<>(); + private final Map dimHashMap = new HashMap<>(); + + private final MEGuiTooltipTextField searchFieldOutputs; + private final MEGuiTooltipTextField searchFieldInputs; + private final MEGuiTooltipTextField searchFieldNames; + + private final GuiImgButton guiButtonHideFull; + private final GuiImgButton guiButtonAssemblersOnly; + private final GuiImgButton guiButtonBrokenRecipes; + private final GuiImgButton terminalStyleBox; private boolean refreshList = false; - private MEGuiTextField searchFieldOutputs; - private MEGuiTextField searchFieldInputs; - private final PartInterfaceTerminal partInterfaceTerminal; - private GuiButton guiButtonHide; - private GuiButton guiButtonNextAssembler; - private GuiImgButton terminalStyleBox; - private final HashMap dimHashMap = new HashMap<>(); - private int drawnRows = 0; + /* These are worded so that the intended default is false */ + private boolean onlyShowWithSpace = false; + private boolean onlyMolecularAssemblers = false; + private boolean onlyBrokenRecipes = false; + private int rows = 6; public GuiInterfaceTerminal(final InventoryPlayer inventoryPlayer, final PartInterfaceTerminal te) { super(new ContainerInterfaceTerminal(inventoryPlayer, te)); - this.partInterfaceTerminal = te; final GuiScrollbar scrollbar = new GuiScrollbar(); this.setScrollBar(scrollbar); this.xSize = 208; this.ySize = 255; - this.setReservedSpace(82); + this.jeiEnabled = Loader.isModLoaded("jei"); + this.jeiButtonPadding = jeiEnabled ? 22 : 0; + + searchFieldInputs = createTextField(86, 12, ButtonToolTips.SearchFieldInputs.getLocal()); + searchFieldOutputs = createTextField(86, 12, ButtonToolTips.SearchFieldOutputs.getLocal()); + searchFieldNames = createTextField(71, 12, ButtonToolTips.SearchFieldNames.getLocal()); + searchFieldNames.setFocused(true); + + guiButtonAssemblersOnly = new GuiImgButton(0, 0, Settings.ACTIONS, null); + guiButtonHideFull = new GuiImgButton(0, 0, Settings.ACTIONS, null); + guiButtonBrokenRecipes = new GuiImgButton(0, 0, Settings.ACTIONS, null); + terminalStyleBox = new GuiImgButton(0, 0, Settings.TERMINAL_STYLE, null); } - @Override - public void initGui() { - this.maxRows = AEConfig.instance() - .getConfigManager() - .getSetting( - Settings.TERMINAL_STYLE) != TerminalStyle.TALL ? 6 : Integer.MAX_VALUE; + private MEGuiTooltipTextField createTextField(final int width, final int height, final String tooltip) { + MEGuiTooltipTextField textField = new MEGuiTooltipTextField(width, height, tooltip) { + @Override + public void onTextChange(String oldText) { + refreshList(); + } + }; + textField.setEnableBackgroundDrawing(false); + textField.setMaxStringLength(25); + textField.setTextColor(0xFFFFFF); + textField.setCursorPositionZero(); + return textField; + } - final int magicNumber = 82 + 14; - final int extraSpace = this.height - magicNumber - this.reservedSpace; + private void setScrollBar() { + this.getScrollBar().setTop(52).setLeft(189).setHeight(this.rows * 18 - 2); + this.getScrollBar().setRange(0, this.lines.size() - 1, 1); + } - this.rows = (int) Math.floor(extraSpace / 18); - if (this.rows > this.maxRows) { - this.rows = this.maxRows; - } + private int calculateRowsCount() { + final int maxRows = getMaxRows(); + final int jeiPadding = jeiEnabled ? 22 + 18 : 0; + final int extraSpace = this.height - MAGIC_HEIGHT_NUMBER - jeiPadding; - if (this.rows < 6) { - this.rows = 6; - } + return Math.max(6, Math.min(maxRows, extraSpace / 18)); + } - this.ySize = magicNumber + this.rows * 18 + this.reservedSpace; - // this.guiTop = top; - final int unusedSpace = this.height - this.ySize; - this.guiTop = (int) Math.floor(unusedSpace / (unusedSpace < 0 ? 3.8f : 2.0f)); + @Override + public void initGui() { + this.rows = calculateRowsCount(); super.initGui(); - this.getScrollBar().setLeft(189); - this.getScrollBar().setHeight(106); - this.getScrollBar().setTop(51); - - this.searchFieldInputs = new MEGuiTextField(this.fontRenderer, this.guiLeft + Math.max(32, this.offsetX), this.guiTop + 25, 65, 12); - this.searchFieldInputs.setEnableBackgroundDrawing(false); - this.searchFieldInputs.setMaxStringLength(25); - this.searchFieldInputs.setTextColor(0xFFFFFF); - this.searchFieldInputs.setVisible(true); - this.searchFieldInputs.setFocused(false); - - this.searchFieldOutputs = new MEGuiTextField(this.fontRenderer, this.guiLeft + Math.max(32, this.offsetX), this.guiTop + 38, 65, 12); - this.searchFieldOutputs.setEnableBackgroundDrawing(false); - this.searchFieldOutputs.setMaxStringLength(25); - this.searchFieldOutputs.setTextColor(0xFFFFFF); - this.searchFieldOutputs.setVisible(true); - this.searchFieldOutputs.setFocused(true); - - this.searchFieldInputs.setText(partInterfaceTerminal.in); - this.searchFieldOutputs.setText(partInterfaceTerminal.out); - - this.fontRenderer.drawString(this.getGuiDisplayName(GuiText.InterfaceTerminal.getLocal()), 8, 6, 4210752); - this.fontRenderer.drawString(GuiText.inventory.getLocal(), this.offsetX + 2, this.ySize - 96 + 3, 4210752); - - } - - protected void repositionSlot(final AppEngSlot s) { - this.ySize = 82 + 14 + this.drawnRows * 18 + this.reservedSpace; + this.ySize = MAGIC_HEIGHT_NUMBER + this.rows * 18; + final int unusedSpace = this.height - this.ySize; + this.guiTop = (int) Math.floor(unusedSpace / (unusedSpace < 0 ? 3.8f : 2.0f)); - s.yPos = s.getY() + this.ySize - 112; - s.xPos = s.getX() + 14; + searchFieldInputs.x = guiLeft + 32; + searchFieldInputs.y = guiTop + 25; + searchFieldOutputs.x = guiLeft + 32; + searchFieldOutputs.y = guiTop + 38; + searchFieldNames.x = guiLeft + 32 + 99; + searchFieldNames.y = guiTop + 38; + + terminalStyleBox.x = guiLeft - 18; + terminalStyleBox.y = guiTop + 8 + jeiButtonPadding; + guiButtonBrokenRecipes.x = guiLeft - 18; + guiButtonBrokenRecipes.y = terminalStyleBox.y + 20; + guiButtonHideFull.x = guiLeft - 18; + guiButtonHideFull.y = guiButtonBrokenRecipes.y + 20; + guiButtonAssemblersOnly.x = guiLeft - 18; + guiButtonAssemblersOnly.y = guiButtonHideFull.y + 20; + + this.setScrollBar(); + this.repositionSlots(); } - @Override - public void onGuiClosed() { - partInterfaceTerminal.saveSearchStrings(this.searchFieldInputs.getText().toLowerCase(), this.searchFieldOutputs.getText().toLowerCase()); - super.onGuiClosed(); + protected void repositionSlots() { + for (final Object obj : this.inventorySlots.inventorySlots) { + if (obj instanceof AppEngSlot slot) { + slot.yPos = this.ySize + slot.getY() - 78 - 7; + slot.xPos = slot.getX() + 14; + } + } } @Override public List getJEIExclusionArea() { - Rectangle tallButton = new Rectangle(this.guiLeft - 18, this.guiTop + 24 + jeiOffset, 18, 18); + Rectangle tallButton = new Rectangle(this.guiLeft - 18, this.guiTop + 24 + 24, 18, 18); List area = new ArrayList<>(); area.add(tallButton); return area; @@ -189,93 +206,98 @@ public List getJEIExclusionArea() { @Override public void drawFG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { - this.buttonList.clear(); + this.fontRenderer.drawString(this.getGuiDisplayName(GuiText.InterfaceTerminal.getLocal()), OFFSET_X + 2, 6, 4210752); + this.fontRenderer.drawString(GuiText.inventory.getLocal(), OFFSET_X + 2, this.ySize - 96, 4210752); final int currentScroll = this.getScrollBar().getCurrentScroll(); - this.guiButtonNextAssembler = new GuiImgButton(guiLeft + 123, guiTop + 25, Settings.ACTIONS, ActionItems.FREE_MOLECULAR_SLOT_SHORTCUT); - this.buttonList.add(guiButtonNextAssembler); - - guiButtonHide = new GuiImgButton(guiLeft + 141, guiTop + 25, Settings.ACTIONS, this.partInterfaceTerminal.onlyInterfacesWithFreeSlots ? ActionItems.TOGGLE_SHOW_FULL_INTERFACES_OFF : ActionItems.TOGGLE_SHOW_FULL_INTERFACES_ON); - this.buttonList.add(guiButtonHide); - - this.buttonList.add(this.terminalStyleBox = new GuiImgButton(this.guiLeft - 18, guiTop + 24 + jeiOffset, Settings.TERMINAL_STYLE, AEConfig.instance() - .getConfigManager() - .getSetting(Settings.TERMINAL_STYLE))); - - this.inventorySlots.inventorySlots.removeIf(slot -> slot instanceof SlotDisconnected); - - for (final Slot s : this.inventorySlots.inventorySlots) { - if (s instanceof AppEngSlot) { - if (s.xPos < 197) { - this.repositionSlot((AppEngSlot) s); - } - } - } - - int offset = 52; + int offset = 51; int linesDraw = 0; for (int x = 0; x < rows && linesDraw < rows && currentScroll + x < this.lines.size(); x++) { final Object lineObj = this.lines.get(currentScroll + x); - if (lineObj instanceof ClientDCInternalInv) { - final ClientDCInternalInv inv = (ClientDCInternalInv) lineObj; - - GuiButton guiButton = new GuiImgButton(guiLeft + 4, guiTop + offset, Settings.ACTIONS, ActionItems.HIGHLIGHT_INTERFACE); - guiButtonHashMap.put(guiButton, inv); - this.buttonList.add(guiButton); - int extraLines = numUpgradesMap.get(inv); + if (lineObj instanceof ClientDCInternalInv inv) { + final int extraLines = numUpgradesMap.get(inv); for (int row = 0; row < 1 + extraLines && linesDraw < rows; ++row) { for (int z = 0; z < 9; z++) { - this.inventorySlots.inventorySlots.add(new SlotDisconnected(inv, z + (row * 9), (z * 18 + 22), offset)); if (this.matchedStacks.contains(inv.getInventory().getStackInSlot(z + (row * 9)))) { - drawRect(z * 18 + 22, offset, z * 18 + 22 + 16, offset + 16, 0x8A00FF00); - } else if (!matchedInterfaces.contains(inv)) { - drawRect(z * 18 + 22, offset, z * 18 + 22 + 16, offset + 16, 0x6A000000); + drawRect(z * 18 + 22, 1 + offset, z * 18 + 22 + 16, 1 + offset + 16, 0x2A00FF00); } } linesDraw++; offset += 18; } - } else if (lineObj instanceof String) { - String name = (String) lineObj; + } else if (lineObj instanceof String name) { final int rows = this.byName.get(name).size(); if (rows > 1) { name = name + " (" + rows + ')'; } - while (name.length() > 2 && this.fontRenderer.getStringWidth(name) > 155) { + while (name.length() > 2 && this.fontRenderer.getStringWidth(name) > 158) { name = name.substring(0, name.length() - 1); } - this.fontRenderer.drawString(name, this.offsetX + 2, 5 + offset, 4210752); + this.fontRenderer.drawString(name, OFFSET_X + 3, 6 + offset, 4210752); linesDraw++; offset += 18; } } + } - if (searchFieldInputs.isMouseIn(mouseX, mouseY)) { - drawTooltip(Mouse.getEventX() * this.width / this.mc.displayWidth - offsetX, mouseY - guiTop, "Inputs OR names"); - } else if (searchFieldOutputs.isMouseIn(mouseX, mouseY)) { - drawTooltip(Mouse.getEventX() * this.width / this.mc.displayWidth - offsetX, mouseY - guiTop, "Outputs OR names"); + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + buttonList.clear(); + guiButtonHashMap.clear(); + inventorySlots.inventorySlots.removeIf(slot -> slot instanceof SlotDisconnected); + + guiButtonAssemblersOnly.set(onlyMolecularAssemblers ? ActionItems.MOLECULAR_ASSEMBLERS_ON : ActionItems.MOLECULAR_ASSEMBLERS_OFF); + guiButtonHideFull.set(onlyShowWithSpace ? ActionItems.TOGGLE_SHOW_FULL_INTERFACES_OFF : ActionItems.TOGGLE_SHOW_FULL_INTERFACES_ON); + guiButtonBrokenRecipes.set(onlyBrokenRecipes ? ActionItems.TOGGLE_SHOW_ONLY_INVALID_PATTERNS_ON : ActionItems.TOGGLE_SHOW_ONLY_INVALID_PATTERNS_OFF); + terminalStyleBox.set(AEConfig.instance().getConfigManager().getSetting(Settings.TERMINAL_STYLE)); + + buttonList.add(guiButtonAssemblersOnly); + buttonList.add(guiButtonHideFull); + buttonList.add(guiButtonBrokenRecipes); + buttonList.add(terminalStyleBox); + + int offset = 51; + final int currentScroll = this.getScrollBar().getCurrentScroll(); + int linesDraw = 0; + + for (int x = 0; x < rows && linesDraw < rows && currentScroll + x < this.lines.size(); x++) { + final Object lineObj = this.lines.get(currentScroll + x); + if (lineObj instanceof ClientDCInternalInv inv) { + + GuiButton guiButton = new GuiImgButton(guiLeft + 4, guiTop + offset + 1, Settings.ACTIONS, ActionItems.HIGHLIGHT_INTERFACE); + guiButtonHashMap.put(guiButton, inv); + this.buttonList.add(guiButton); + + final int extraLines = numUpgradesMap.get(inv); + for (int row = 0; row < 1 + extraLines && linesDraw < rows; ++row) { + for (int z = 0; z < 9; z++) { + this.inventorySlots.inventorySlots.add(new SlotDisconnected(inv, z + (row * 9), z * 18 + 22, 1+ offset)); + } + linesDraw++; + offset += 18; + } + + } else if (lineObj instanceof String) { + linesDraw++; + offset += 18; + } } + super.drawScreen(mouseX, mouseY, partialTicks); + + drawTooltip(searchFieldInputs, mouseX, mouseY); + drawTooltip(searchFieldOutputs, mouseX, mouseY); + drawTooltip(searchFieldNames, mouseX, mouseY); } @Override protected void mouseClicked(final int xCoord, final int yCoord, final int btn) throws IOException { this.searchFieldInputs.mouseClicked(xCoord, yCoord, btn); - - if (btn == 1 && this.searchFieldInputs.isMouseIn(xCoord, yCoord)) { - this.searchFieldInputs.setText(""); - this.refreshList(); - } - this.searchFieldOutputs.mouseClicked(xCoord, yCoord, btn); - - if (btn == 1 && this.searchFieldOutputs.isMouseIn(xCoord, yCoord)) { - this.searchFieldOutputs.setText(""); - this.refreshList(); - } + this.searchFieldNames.mouseClicked(xCoord, yCoord, btn); super.mouseClicked(xCoord, yCoord, btn); } @@ -289,54 +311,37 @@ protected void actionPerformed(final GuiButton btn) throws IOException { int interfaceDim = dimHashMap.get(guiButtonHashMap.get(this.selectedButton)); if (playerDim != interfaceDim) { try { - mc.player.sendStatusMessage(new TextComponentString("Interface located at dimension: " + interfaceDim + " [" + DimensionManager.getWorld(interfaceDim).provider.getDimensionType().getName() + "] and cant be highlighted"), false); + mc.player.sendStatusMessage(PlayerMessages.InterfaceInOtherDimParam.get(interfaceDim, DimensionManager.getWorld(interfaceDim).provider.getDimensionType().getName()), false); } catch (Exception e) { - mc.player.sendStatusMessage(new TextComponentString("Interface is located in another dimension and cannot be highlighted"), false); + mc.player.sendStatusMessage(PlayerMessages.InterfaceInOtherDim.get(), false); } } else { hilightBlock(blockPos, System.currentTimeMillis() + 500 * BlockPosUtils.getDistance(blockPos, blockPos2), playerDim); - mc.player.sendStatusMessage(new TextComponentString("The interface is now highlighted at " + "X: " + blockPos.getX() + " Y: " + blockPos.getY() + " Z: " + blockPos.getZ()), false); + mc.player.sendStatusMessage(PlayerMessages.InterfaceHighlighted.get(blockPos.getX(), blockPos.getY(), blockPos.getZ()), false); } mc.player.closeScreen(); - } - - if (btn instanceof GuiImgButton) { - final boolean backwards = Mouse.isButtonDown(1); - - final GuiImgButton iBtn = (GuiImgButton) btn; + } else if (btn == guiButtonHideFull) { + onlyShowWithSpace = !onlyShowWithSpace; + this.refreshList(); + } else if (btn == guiButtonAssemblersOnly) { + onlyMolecularAssemblers = !onlyMolecularAssemblers; + this.refreshList(); + } else if (btn == guiButtonBrokenRecipes) { + onlyBrokenRecipes = !onlyBrokenRecipes; + this.refreshList(); + } else if (btn instanceof GuiImgButton iBtn) { if (iBtn.getSetting() != Settings.ACTIONS) { - final Enum cv = iBtn.getCurrentValue(); - final Enum next = Platform.rotateEnum(cv, backwards, iBtn.getSetting().getPossibleValues()); + final Enum cv = iBtn.getCurrentValue(); + final boolean backwards = Mouse.isButtonDown(1); + final Enum next = Platform.rotateEnum(cv, backwards, iBtn.getSetting().getPossibleValues()); if (btn == this.terminalStyleBox) { AEConfig.instance().getConfigManager().putSetting(iBtn.getSetting(), next); - } - - iBtn.set(next); - - if (next.getClass() == TerminalStyle.class) { this.reinitalize(); } + iBtn.set(next); } } - if (btn == guiButtonHide) { - partInterfaceTerminal.onlyInterfacesWithFreeSlots = !partInterfaceTerminal.onlyInterfacesWithFreeSlots; - this.refreshList(); - } - - if (btn == guiButtonNextAssembler) { - // Set Search to "Molecular Assembler" and set "Only Free Interface" - boolean currentOnlyInterfacesWithFreeSlots = this.partInterfaceTerminal.onlyInterfacesWithFreeSlots; - String currentSearchText = this.searchFieldOutputs.getText(); - - this.partInterfaceTerminal.onlyInterfacesWithFreeSlots = true; - this.searchFieldOutputs.setText("Molecular Assembler"); - - this.refreshList(); - - this.partInterfaceTerminal.onlyInterfacesWithFreeSlots = currentOnlyInterfacesWithFreeSlots; - this.searchFieldOutputs.setText(currentSearchText); - } } private void reinitalize() { @@ -347,74 +352,66 @@ private void reinitalize() { @Override public void drawBG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { this.bindTexture("guis/newinterfaceterminal.png"); - //split the texture for the top - this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, 166); + + // draw the top portion of the background, above the interface list + this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, 53); + + for (int x = 0; x < this.rows; x++) { + // draw the background of the rows in the interface list + this.drawTexturedModalRect(offsetX, offsetY + 53 + x * 18, 0, 52, this.xSize, 18); + } int offset = 51; final int ex = this.getScrollBar().getCurrentScroll(); int linesDraw = 0; - for (int x = 0; x < rows && linesDraw < rows && ex + x < this.lines.size(); x++) { + for (int x = 0; x < this.rows && linesDraw < rows && ex + x < this.lines.size(); x++) { final Object lineObj = this.lines.get(ex + x); if (lineObj instanceof ClientDCInternalInv) { GlStateManager.color(1, 1, 1, 1); - final int width = 9 * 18; - int extraLines = numUpgradesMap.get(lineObj); + final int width = 9 * 18; + final int extraLines = numUpgradesMap.get(lineObj); + // draw the slot backgrounds for (int row = 0; row < 1 + extraLines && linesDraw < rows; ++row) { - if (linesDraw == 6) { - this.drawTexturedModalRect(offsetX, offsetY + offset + 7, 0, 166, 190, 7); - this.drawTexturedModalRect(offsetX, offsetY + offset + 14, 0, 166, 190, 4); - } else if (linesDraw >= 7) { - this.drawTexturedModalRect(offsetX, offsetY + offset, 0, 173, 190, 18); - } this.drawTexturedModalRect(offsetX + 20, offsetY + offset, 20, 173, width, 18); offset += 18; linesDraw++; } } else { - if (linesDraw == 6) { - this.drawTexturedModalRect(offsetX, offsetY + offset + 7, 0, 166, 190, 7); - this.drawTexturedModalRect(offsetX, offsetY + offset + 14, 0, 166, 190, 4); - this.drawTexturedModalRect(offsetX, offsetY + offset, 0, 53, 183, 18); - } else if (linesDraw >= 7) { - this.drawTexturedModalRect(offsetX, offsetY + offset, 0, 53, 183, 18); - this.drawTexturedModalRect(offsetX + 183, offsetY + offset, 183, 166, 7, 18); - } offset += 18; linesDraw++; } } - offset = 51 + Math.max(6 * 18, linesDraw * 18); - if (linesDraw > 6) { - this.drawTexturedModalRect(offsetX, offsetY + offset, 0, 166, 190, 7); - } - this.drawTexturedModalRect(offsetX, offsetY + offset + 7, 0, 166, this.xSize, 90); - - this.drawnRows = Math.max(6, linesDraw); - if (this.searchFieldInputs != null) { - this.searchFieldInputs.drawTextBox(); - } + // draw the background below the interface list + this.drawTexturedModalRect(offsetX, offsetY + 50 + this.rows * 18, 0, 158, this.xSize, 99); - if (this.searchFieldOutputs != null) { - this.searchFieldOutputs.drawTextBox(); - } + // draw the text boxes + this.searchFieldInputs.drawTextBox(); + this.searchFieldOutputs.drawTextBox(); + this.searchFieldNames.drawTextBox(); } @Override protected void keyTyped(final char character, final int key) throws IOException { if (!this.checkHotbarKeys(key)) { - if (character == ' ' && this.searchFieldInputs.getText().isEmpty() && this.searchFieldInputs.isFocused()) { - return; - } - - if (character == ' ' && this.searchFieldOutputs.getText().isEmpty() && this.searchFieldOutputs.isFocused()) { - return; + if (character == ' ') { + if ((this.searchFieldInputs.getText().isEmpty() && this.searchFieldInputs.isFocused()) + || (this.searchFieldOutputs.getText().isEmpty() && this.searchFieldOutputs.isFocused()) + || (this.searchFieldNames.getText().isEmpty() && this.searchFieldNames.isFocused())) { + return; + } + } else if (character == '\t') { + if (handleTab()) { + return; + } } - if (this.searchFieldInputs.textboxKeyTyped(character, key) || this.searchFieldOutputs.textboxKeyTyped(character, key)) { + if (this.searchFieldInputs.textboxKeyTyped(character, key) + || this.searchFieldOutputs.textboxKeyTyped(character, key) + || this.searchFieldNames.textboxKeyTyped(character, key)) { this.refreshList(); } else { super.keyTyped(character, key); @@ -422,6 +419,27 @@ protected void keyTyped(final char character, final int key) throws IOException } } + /** Cycle to the next search bar if tab is pressed, going in reverse if shift is held. */ + private boolean handleTab() { + if (searchFieldInputs.isFocused()) { + searchFieldInputs.setFocused(false); + if (isShiftKeyDown()) searchFieldNames.setFocused(true); + else searchFieldOutputs.setFocused(true); + return true; + } else if (searchFieldOutputs.isFocused()) { + searchFieldOutputs.setFocused(false); + if (isShiftKeyDown()) searchFieldInputs.setFocused(true); + else searchFieldNames.setFocused(true); + return true; + } else if (searchFieldNames.isFocused()) { + searchFieldNames.setFocused(false); + if (isShiftKeyDown()) searchFieldOutputs.setFocused(true); + else searchFieldInputs.setFocused(true); + return true; + } + return false; + } + public void postUpdate(final NBTTagCompound in) { if (in.getBoolean("clear")) { this.byId.clear(); @@ -467,12 +485,13 @@ private void refreshList() { this.byName.clear(); this.buttonList.clear(); this.matchedStacks.clear(); - this.matchedInterfaces.clear(); final String searchFieldInputs = this.searchFieldInputs.getText().toLowerCase(); final String searchFieldOutputs = this.searchFieldOutputs.getText().toLowerCase(); + final String searchFieldNames = this.searchFieldNames.getText().toLowerCase(); - final Set cachedSearch = this.getCacheForSearchTerm("IN:" + searchFieldInputs + " OUT:" + searchFieldOutputs + partInterfaceTerminal.onlyInterfacesWithFreeSlots); + final Set cachedSearch = this.getCacheForSearchTerm("IN:" + searchFieldInputs + " OUT:" + searchFieldOutputs + + "NAME:" + searchFieldNames + onlyShowWithSpace + onlyMolecularAssemblers + onlyBrokenRecipes); final boolean rebuild = cachedSearch.isEmpty(); for (final ClientDCInternalInv entry : this.byId.values()) { @@ -483,78 +502,100 @@ private void refreshList() { // Shortcut to skip any filter if search term is ""/empty - boolean found = (searchFieldInputs.isEmpty() && searchFieldOutputs.isEmpty() && !partInterfaceTerminal.onlyInterfacesWithFreeSlots); + boolean found = searchFieldInputs.isEmpty() && searchFieldOutputs.isEmpty(); boolean interfaceHasFreeSlots = false; + boolean interfaceHasBrokenRecipes = false; // Search if the current inventory holds a pattern containing the search term. - if (!found) { + if (!found || onlyShowWithSpace || onlyBrokenRecipes) { int slot = 0; for (final ItemStack itemStack : entry.getInventory()) { if (slot > 8 + numUpgradesMap.get(entry) * 9) { break; } - if (!searchFieldInputs.isEmpty() && !searchFieldOutputs.isEmpty()) { - if (this.itemStackMatchesSearchTerm(itemStack, searchFieldInputs, 0) || this.itemStackMatchesSearchTerm(itemStack, searchFieldOutputs, 1)) { - found = true; - matchedStacks.add(itemStack); - } - } else if (!searchFieldInputs.isEmpty()) { - if (this.itemStackMatchesSearchTerm(itemStack, searchFieldInputs, 0)) { - found = true; - matchedStacks.add(itemStack); - } - } else if (!searchFieldOutputs.isEmpty()) { - if (this.itemStackMatchesSearchTerm(itemStack, searchFieldOutputs, 1)) { - found = true; - matchedStacks.add(itemStack); - } - } - // If only Interfaces with empty slots should be shown, check that here + if (itemStack.isEmpty()) { interfaceHasFreeSlots = true; } + + if (onlyBrokenRecipes && recipeIsBroken(itemStack)) { + interfaceHasBrokenRecipes = true; + } + + if ((!searchFieldInputs.isEmpty() && itemStackMatchesSearchTerm(itemStack, searchFieldInputs, 0)) + || (!searchFieldOutputs.isEmpty() && itemStackMatchesSearchTerm(itemStack, searchFieldOutputs, 1))) { + found = true; + matchedStacks.add(itemStack); + } + slot++; } } - // if found, filter skipped or machine name matching the search term, add it - if ((searchFieldInputs.isEmpty() && searchFieldOutputs.isEmpty()) || - !searchFieldInputs.isEmpty() && entry.getName().toLowerCase().contains(searchFieldInputs) || - (!searchFieldOutputs.isEmpty() && entry.getName().toLowerCase().contains(searchFieldOutputs))) { - this.matchedInterfaces.add(entry); - found = true; + + // Exit if not found + if (!found) { + cachedSearch.remove(entry); + continue; } - if (found) { - if (!partInterfaceTerminal.onlyInterfacesWithFreeSlots) { - this.byName.put(entry.getName(), entry); - cachedSearch.add(entry); - } else if (interfaceHasFreeSlots) { - this.byName.put(entry.getName(), entry); - cachedSearch.add(entry); - } - } else { + // Exit if the interface does not match the name search + if (!entry.getName().toLowerCase().contains(searchFieldNames)) { + cachedSearch.remove(entry); + continue; + } + // Exit if molecular assembler filter is on and this is not a molecular assembler + if (onlyMolecularAssemblers && !entry.getName().toLowerCase().contains(MOLECULAR_ASSEMBLER)) { + cachedSearch.remove(entry); + continue; + } + // Exit if we are only showing interfaces with free slots and there are none free in this interface + if (onlyShowWithSpace && !interfaceHasFreeSlots) { + cachedSearch.remove(entry); + continue; + } + // Exit if we are only showing interfaces with broken patterns and there are no broken patterns in this interface + if (onlyBrokenRecipes && !interfaceHasBrokenRecipes) { cachedSearch.remove(entry); + continue; } + + // Successful search + this.byName.put(entry.getName(), entry); + cachedSearch.add(entry); } this.names.clear(); this.names.addAll(this.byName.keySet()); - Collections.sort(this.names); this.lines.clear(); - this.lines.ensureCapacity(this.getMaxRows()); + this.lines.ensureCapacity(this.names.size() + this.byId.size()); for (final String n : this.names) { this.lines.add(n); - - final ArrayList clientInventories = new ArrayList<>(); - clientInventories.addAll(this.byName.get(n)); - + final ArrayList clientInventories = new ArrayList<>(this.byName.get(n)); Collections.sort(clientInventories); this.lines.addAll(clientInventories); } - this.getScrollBar().setRange(0, this.lines.size() - 1, 1); + this.setScrollBar(); + } + + private boolean recipeIsBroken(final ItemStack stack) { + if (stack == null) return false; + if (stack.isEmpty()) return false; + + final NBTTagCompound encodedValue = stack.getTagCompound(); + if (encodedValue == null) return true; + + final World w = AppEng.proxy.getWorld(); + if (w == null) return false; + + try { + new PatternHelper(stack, w); + return false; + } catch (Throwable ignored) { + return true; + } } private boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final String searchTerm, int pass) { @@ -624,13 +665,8 @@ private Set getCacheForSearchTerm(final String searchTerm) { return cache; } - /** - * The max amount of unique names and each inv row. Not affected by the filtering. - * - * @return max amount of unique names and each inv row - */ private int getMaxRows() { - return this.names.size() + this.byId.size(); + return AEConfig.instance().getConfigManager().getSetting(Settings.TERMINAL_STYLE) != TerminalStyle.TALL ? 6 : Integer.MAX_VALUE; } private ClientDCInternalInv getById(final long id, final long sortBy, final String string) { @@ -643,12 +679,4 @@ private ClientDCInternalInv getById(final long id, final long sortBy, final Stri return o; } - - int getReservedSpace() { - return this.reservedSpace; - } - - void setReservedSpace(final int reservedSpace) { - this.reservedSpace = reservedSpace; - } } diff --git a/src/main/java/appeng/client/gui/widgets/GuiImgButton.java b/src/main/java/appeng/client/gui/widgets/GuiImgButton.java index a72ac2e9bda..638cf5681a5 100644 --- a/src/main/java/appeng/client/gui/widgets/GuiImgButton.java +++ b/src/main/java/appeng/client/gui/widgets/GuiImgButton.java @@ -116,10 +116,13 @@ public GuiImgButton(final int x, final int y, final Enum idx, final Enum val) { this.registerApp(11 + 4 * 16, Settings.ACTIONS, ActionItems.DECREASE_BY_ONE, ButtonToolTips.DecreaseByOne, ButtonToolTips.DecreaseByOneDesc); this.registerApp(12 + 4 * 16, Settings.ACTIONS, ActionItems.MAX_COUNT, ButtonToolTips.MaxCount, ButtonToolTips.MaxCountDesc); - this.registerApp(6 + 5 * 16, Settings.ACTIONS, ActionItems.FREE_MOLECULAR_SLOT_SHORTCUT, ButtonToolTips.FreeMolecularSlotShortcut, ButtonToolTips.FreeMolecularSlotShortcutDesc); + this.registerApp(6 + 5 * 16, Settings.ACTIONS, ActionItems.MOLECULAR_ASSEMBLERS_ON, ButtonToolTips.ToggleMolecularAssemblers, ButtonToolTips.ToggleMolecularAssemblersOnDesc); this.registerApp(7 + 5 * 16, Settings.ACTIONS, ActionItems.TOGGLE_SHOW_FULL_INTERFACES_ON, ButtonToolTips.ToggleShowFullInterfaces, ButtonToolTips.ToggleShowFullInterfacesOnDesc); this.registerApp(8 + 5 * 16, Settings.ACTIONS, ActionItems.TOGGLE_SHOW_FULL_INTERFACES_OFF, ButtonToolTips.ToggleShowFullInterfaces, ButtonToolTips.ToggleShowFullInterfacesOffDesc); + this.registerApp(9 + 5 * 16, Settings.ACTIONS, ActionItems.MOLECULAR_ASSEMBLERS_OFF, ButtonToolTips.ToggleMolecularAssemblers, ButtonToolTips.ToggleMolecularAssemblersOffDesc); this.registerApp(6 + 6 * 16, Settings.ACTIONS, ActionItems.HIGHLIGHT_INTERFACE, ButtonToolTips.HighlightInterface, ""); + this.registerApp(4 + 5 * 16, Settings.ACTIONS, ActionItems.TOGGLE_SHOW_ONLY_INVALID_PATTERNS_OFF, ButtonToolTips.ToggleShowOnlyInvalidInterface, ButtonToolTips.ToggleShowOnlyInvalidInterfaceOffDesc); + this.registerApp(5 + 5 * 16, Settings.ACTIONS, ActionItems.TOGGLE_SHOW_ONLY_INVALID_PATTERNS_ON, ButtonToolTips.ToggleShowOnlyInvalidInterface, ButtonToolTips.ToggleShowOnlyInvalidInterfaceOnDesc); this.registerApp(8, Settings.ACTIONS, ActionItems.ENCODE, ButtonToolTips.Encode, ButtonToolTips.EncodeDescription); this.registerApp(4 + 3 * 16, Settings.ACTIONS, ItemSubstitution.ENABLED, ButtonToolTips.Substitutions, ButtonToolTips.SubstitutionsDescEnabled); diff --git a/src/main/java/appeng/client/gui/widgets/MEGuiTooltipTextField.java b/src/main/java/appeng/client/gui/widgets/MEGuiTooltipTextField.java new file mode 100644 index 00000000000..6a247928f64 --- /dev/null +++ b/src/main/java/appeng/client/gui/widgets/MEGuiTooltipTextField.java @@ -0,0 +1,236 @@ +package appeng.client.gui.widgets; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiTextField; +import org.lwjgl.input.Keyboard; + +/** + * Different implementation of a text field that wraps instead of extends + * MC's {@link GuiTextField}. This is necessary because of deobfuscated name + * collision between {@link ITooltip} and GuiTextField, which would cause + * crashes in an obfuscated environment. Additionally, since we are not extending that + * class, we can construct this object differently and allow its position to be + * mutable like most other widgets. + */ +public class MEGuiTooltipTextField implements ITooltip { + + protected GuiTextField field; + + private static final int PADDING = 2; + private static boolean previousKeyboardRepeatEnabled; + private static MEGuiTooltipTextField previousKeyboardRepeatEnabledField; + private String tooltip; + private int fontPad; + + public int x; + public int y; + public int w; + public int h; + + /** + * Uses the values to instantiate a padded version of a text field. Pays attention to the '_' caret. + * + * @param width absolute width + * @param height absolute height + * @param tooltip tooltip message + */ + public MEGuiTooltipTextField(final int width, final int height, final String tooltip) { + final FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + field = new GuiTextField(0, fontRenderer, 0, 0, 0, 0); + + w = width; + h = height; + + setMessage(tooltip); + + this.fontPad = fontRenderer.getCharWidth('_'); + + setDimensionsAndColor(); + } + + public MEGuiTooltipTextField(final int width, final int height) { + this(width, height, ""); + } + + public MEGuiTooltipTextField() { + this(0, 0); + } + + protected void setDimensionsAndColor() { + field.x = this.x + PADDING; + field.y = this.y + PADDING; + field.width = this.w - PADDING * 2 - this.fontPad; + field.height = this.h - PADDING * 2; + } + + public void onTextChange(final String oldText) {} + + public void mouseClicked(final int xPos, final int yPos, final int button) { + + if (!this.isMouseIn(xPos, yPos)) { + setFocused(false); + return; + } + + field.setCanLoseFocus(false); + setFocused(true); + + if (button == 1) { + setText(""); + } else { + field.mouseClicked(xPos, yPos, button); + } + + field.setCanLoseFocus(true); + } + + /** + * Checks if the mouse is within the element + * + * @param xCoord current x coord of the mouse + * @param yCoord current y coord of the mouse + * @return true if mouse position is within the getText field area + */ + public boolean isMouseIn(final int xCoord, final int yCoord) { + final boolean withinXRange = this.x <= xCoord && xCoord < this.x + this.w; + final boolean withinYRange = this.y <= yCoord && yCoord < this.y + this.h; + + return withinXRange && withinYRange; + } + + public boolean textboxKeyTyped(final char keyChar, final int keyID) { + if (!isFocused()) { + return false; + } + + final String oldText = getText(); + boolean handled = field.textboxKeyTyped(keyChar, keyID); + + if (!handled && (keyID == Keyboard.KEY_RETURN || keyID == Keyboard.KEY_NUMPADENTER + || keyID == Keyboard.KEY_ESCAPE)) { + setFocused(false); + } + + if (handled) { + onTextChange(oldText); + } + + return handled; + } + + public void drawTextBox() { + if (field.getVisible()) { + setDimensionsAndColor(); + GuiTextField.drawRect( + this.x + 1, + this.y + 1, + this.x + this.w - 1, + this.y + this.h - 1, + isFocused() ? 0xFF606060 : 0xFFA8A8A8); + field.drawTextBox(); + } + } + + public void setText(String text, boolean ignoreTrigger) { + final String oldText = getText(); + + int currentCursorPos = field.getCursorPosition(); + field.setText(text); + field.setCursorPosition(currentCursorPos); + + if (!ignoreTrigger) { + onTextChange(oldText); + } + } + + public void setText(String text) { + setText(text, false); + } + + public void setCursorPositionEnd() { + field.setCursorPositionEnd(); + } + + public void setFocused(boolean focus) { + if (field.isFocused() == focus) { + return; + } + + field.setFocused(focus); + + if (focus) { + + if (previousKeyboardRepeatEnabledField == null) { + previousKeyboardRepeatEnabled = Keyboard.areRepeatEventsEnabled(); + } + + previousKeyboardRepeatEnabledField = this; + Keyboard.enableRepeatEvents(true); + } else { + + if (previousKeyboardRepeatEnabledField == this) { + previousKeyboardRepeatEnabledField = null; + Keyboard.enableRepeatEvents(previousKeyboardRepeatEnabled); + } + } + } + + public void setMaxStringLength(final int size) { + field.setMaxStringLength(size); + } + + public void setEnableBackgroundDrawing(final boolean b) { + field.setEnableBackgroundDrawing(b); + } + + public void setTextColor(final int color) { + field.setTextColor(color); + } + + public void setCursorPositionZero() { + field.setCursorPositionZero(); + } + + public boolean isFocused() { + return field.isFocused(); + } + + public String getText() { + return field.getText(); + } + + public void setMessage(String t) { + tooltip = t; + } + + @Override + public String getMessage() { + return tooltip; + } + + @Override + public boolean isVisible() { + return field.getVisible(); + } + + @Override + public int xPos() { + return x; + } + + @Override + public int yPos() { + return y; + } + + @Override + public int getWidth() { + return w; + } + + @Override + public int getHeight() { + return h; + } +} diff --git a/src/main/java/appeng/core/localization/ButtonToolTips.java b/src/main/java/appeng/core/localization/ButtonToolTips.java index fbbdd3223d2..42f65e46a0e 100644 --- a/src/main/java/appeng/core/localization/ButtonToolTips.java +++ b/src/main/java/appeng/core/localization/ButtonToolTips.java @@ -150,13 +150,20 @@ public enum ButtonToolTips { DecreaseByOneDesc, MaxCount, MaxCountDesc, - FreeMolecularSlotShortcut, - FreeMolecularSlotShortcutDesc, + ToggleMolecularAssemblers, + ToggleMolecularAssemblersOnDesc, + ToggleMolecularAssemblersOffDesc, ToggleShowFullInterfaces, ToggleShowFullInterfacesOnDesc, ToggleShowFullInterfacesOffDesc, + ToggleShowOnlyInvalidInterface, + ToggleShowOnlyInvalidInterfaceOnDesc, + ToggleShowOnlyInvalidInterfaceOffDesc, HighlightInterface, HighlightInterfaceDesc, + SearchFieldInputs, + SearchFieldOutputs, + SearchFieldNames, // Used in the tooltips of the items in the terminal, when moused over ItemsStored, diff --git a/src/main/java/appeng/core/localization/PlayerMessages.java b/src/main/java/appeng/core/localization/PlayerMessages.java index dff07f64c2f..5a3b3b3384e 100644 --- a/src/main/java/appeng/core/localization/PlayerMessages.java +++ b/src/main/java/appeng/core/localization/PlayerMessages.java @@ -41,7 +41,15 @@ public enum PlayerMessages { DeviceNotLinked, StationCanNotBeLocated, SettingCleared, - MissingPatternsToEncode; + MissingPatternsToEncode, + InterfaceInOtherDimParam, + InterfaceInOtherDim, + InterfaceHighlighted, + ; + + public ITextComponent get(Object... params) { + return new TextComponentTranslation(this.getName(), params); + } public ITextComponent get() { return new TextComponentTranslation(this.getName()); diff --git a/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java b/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java index dd72e071ef3..c507a1c0a0e 100644 --- a/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java +++ b/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java @@ -42,9 +42,6 @@ public class PartInterfaceTerminal extends AbstractPartDisplay { public static final IPartModel MODELS_OFF = new PartModel(MODEL_BASE, MODEL_OFF, MODEL_STATUS_OFF); public static final IPartModel MODELS_ON = new PartModel(MODEL_BASE, MODEL_ON, MODEL_STATUS_ON); public static final IPartModel MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, MODEL_ON, MODEL_STATUS_HAS_CHANNEL); - public String in = ""; - public String out = ""; - public boolean onlyInterfacesWithFreeSlots = false; public PartInterfaceTerminal(final ItemStack is) { super(is); @@ -64,9 +61,4 @@ public boolean onPartActivate(final EntityPlayer player, final EnumHand hand, fi public IPartModel getStaticModels() { return this.selectModel(MODELS_OFF, MODELS_ON, MODELS_HAS_CHANNEL); } - - public void saveSearchStrings(String in, String out) { - this.in = in; - this.out = out; - } } diff --git a/src/main/resources/assets/appliedenergistics2/lang/en_us.lang b/src/main/resources/assets/appliedenergistics2/lang/en_us.lang index 74ac79e39ce..71726f2ff61 100644 --- a/src/main/resources/assets/appliedenergistics2/lang/en_us.lang +++ b/src/main/resources/assets/appliedenergistics2/lang/en_us.lang @@ -90,6 +90,9 @@ chat.appliedenergistics2.ResetSettings=New device configuration created and copi chat.appliedenergistics2.AmmoDepleted=Ammo Depleted. chat.appliedenergistics2.isNowLocked=Monitor is now Locked. chat.appliedenergistics2.isNowUnlocked=Monitor is now Unlocked. +chat.appliedenergistics2.InterfaceInOtherDimParam=Interface located at dimension: %d [ %s ] and cannot be highlighted +chat.appliedenergistics2.InterfaceInOtherDim=Interface is located in another dimension and cannot be highlighted +chat.appliedenergistics2.InterfaceHighlighted=The interface is now highlighted at X: %s Y: %s Z: %s // Creative Tabs itemGroup.appliedenergistics2=Applied Energistics 2 @@ -374,12 +377,19 @@ gui.tooltips.appliedenergistics2.DecreaseByOne=Decrease one gui.tooltips.appliedenergistics2.DecreaseByOneDesc=Will try to add decrease the items set in the crafting slots by one DOES NOT MAINTAIN INGREDIENT RATIO gui.tooltips.appliedenergistics2.MaxCount=Maximize slot count gui.tooltips.appliedenergistics2.MaxCountDesc=Will maximize the output maintaing the input ratio of the items set in the crafting slots -gui.tooltips.appliedenergistics2.FreeMolecularSlotShortcut=Show Crafting interfaces with free slots -gui.tooltips.appliedenergistics2.FreeMolecularSlotShortcutDesc=Shortcut that toggles interfaces with free slots and searches for molecular assemblers +gui.tooltips.appliedenergistics2.ToggleMolecularAssemblers=Toggles showing crafting interfaces only +gui.tooltips.appliedenergistics2.ToggleMolecularAssemblersOffDesc=Showing all interfaces +gui.tooltips.appliedenergistics2.ToggleMolecularAssemblersOnDesc=Showing only interfaces on molecular assemblers gui.tooltips.appliedenergistics2.ToggleShowFullInterfaces=Toggles showing interfaces with full slots gui.tooltips.appliedenergistics2.ToggleShowFullInterfacesOnDesc=Showing all interfaces gui.tooltips.appliedenergistics2.ToggleShowFullInterfacesOffDesc=Showing only interfaces with free slots -gui.tooltips.appliedenergistics2.HighlightInterface=Highlight interface +gui.tooltips.appliedenergistics2.ToggleShowOnlyInvalidInterface=Toggles showing interfaces with invalid patterns +gui.tooltips.appliedenergistics2.ToggleShowOnlyInvalidInterfaceOffDesc=Showing all interfaces +gui.tooltips.appliedenergistics2.ToggleShowOnlyInvalidInterfaceOnDesc=Showing only interfaces with invalid patterns +gui.tooltips.appliedenergistics2.HighlightInterface=Highlight Interface +gui.tooltips.appliedenergistics2.SearchFieldInputs=Recipe Inputs +gui.tooltips.appliedenergistics2.SearchFieldOutputs=Recipe Outputs +gui.tooltips.appliedenergistics2.SearchFieldNames=Interface Names gui.tooltips.appliedenergistics2.PartialTransfer=Move stored items into the crafting grid gui.tooltips.appliedenergistics2.MissingItem=§cMissing item gui.tooltips.appliedenergistics2.CraftableItem=§9Craftable item diff --git a/src/main/resources/assets/appliedenergistics2/textures/guis/newinterfaceterminal.png b/src/main/resources/assets/appliedenergistics2/textures/guis/newinterfaceterminal.png index 9d0f70c424c92fd16c4c774154fd03713e4d8ba3..6a194d58e02e3908fce5c6bd33e50c947caa0468 100644 GIT binary patch literal 3098 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