diff --git a/dependencies.gradle b/dependencies.gradle index f2e495b0d..5bc837c28 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,7 +2,7 @@ dependencies { api('com.github.GTNewHorizons:NotEnoughItems:2.4.3-GTNH:dev') - api('com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-263-GTNH-pre:dev') + api('com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-266-GTNH-pre:dev') api('curse.maven:cofh-core-69162:2388751') api('com.github.GTNewHorizons:waila:1.6.0:dev') api("com.github.GTNewHorizons:WirelessCraftingTerminal:1.10.0:dev") diff --git a/src/main/java/com/glodblock/github/client/gui/GuiInterfaceWireless.java b/src/main/java/com/glodblock/github/client/gui/GuiInterfaceWireless.java index d4d3ade44..099f7f153 100644 --- a/src/main/java/com/glodblock/github/client/gui/GuiInterfaceWireless.java +++ b/src/main/java/com/glodblock/github/client/gui/GuiInterfaceWireless.java @@ -1,97 +1,111 @@ package com.glodblock.github.client.gui; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.WeakHashMap; +import java.util.TreeMap; +import java.util.TreeSet; +import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants; import net.minecraftforge.common.util.ForgeDirection; +import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; import com.glodblock.github.FluidCraft; import com.glodblock.github.client.gui.base.FCBaseMEGui; import com.glodblock.github.client.gui.container.ContainerInterfaceWireless; -import com.glodblock.github.inventory.InventoryHandler; -import com.glodblock.github.inventory.gui.GuiType; import com.glodblock.github.inventory.item.IWirelessTerminal; import com.glodblock.github.network.CPacketRenamer; -import com.glodblock.github.util.ModAndClassUtil; -import com.glodblock.github.util.Util; -import com.google.common.collect.HashMultimap; import appeng.api.AEApi; import appeng.api.config.ActionItems; import appeng.api.config.Settings; import appeng.api.config.TerminalStyle; -import appeng.api.config.YesNo; +import appeng.api.util.DimensionalCoord; import appeng.api.util.WorldCoord; +import appeng.client.gui.IInterfaceTerminalPostUpdate; import appeng.client.gui.widgets.GuiImgButton; import appeng.client.gui.widgets.GuiScrollbar; -import appeng.client.gui.widgets.GuiTabButton; import appeng.client.gui.widgets.IDropToFillTextField; import appeng.client.gui.widgets.MEGuiTextField; -import appeng.client.me.ClientDCInternalInv; -import appeng.client.me.SlotDisconnected; import appeng.client.render.BlockPosHighlighter; import appeng.container.slot.AppEngSlot; import appeng.core.AEConfig; +import appeng.core.AppEng; import appeng.core.CommonHelper; import appeng.core.localization.ButtonToolTips; import appeng.core.localization.GuiColors; import appeng.core.localization.GuiText; import appeng.core.localization.PlayerMessages; +import appeng.core.sync.network.NetworkHandler; +import appeng.core.sync.packets.PacketIfaceTermUpdate; +import appeng.core.sync.packets.PacketInventoryAction; +import appeng.helpers.InventoryAction; import appeng.helpers.PatternHelper; import appeng.integration.IntegrationRegistry; import appeng.integration.IntegrationType; +import appeng.items.misc.ItemEncodedPattern; +import appeng.tile.inventory.AppEngInternalInventory; import appeng.util.Platform; +import cpw.mods.fml.common.Loader; -public class GuiInterfaceWireless extends FCBaseMEGui implements IDropToFillTextField { +public class GuiInterfaceWireless extends FCBaseMEGui implements IDropToFillTextField, IInterfaceTerminalPostUpdate { - protected int offsetY; - private static final int MAGIC_HEIGHT_NUMBER = 52 + 99; - private static final int offsetX = 21; - - private final HashMap byId = new HashMap<>(); - private final HashMultimap byName = HashMultimap.create(); - private final HashMap blockPosHashMap = new HashMap<>(); - private final HashMap guiButtonHashMap = new HashMap<>(); - private final ArrayList names = new ArrayList<>(); - private final ArrayList lines = new ArrayList<>(); - private final Set matchedStacks = new HashSet<>(); - - private final Map> cachedSearches = new WeakHashMap<>(); - - protected static String searchFieldOutputsText = ""; - protected static String searchFieldInputsText = ""; - protected static String searchFieldNamesText = ""; + public static final int HEADER_HEIGHT = 52; + public static final int INV_HEIGHT = 98; + public static final int VIEW_WIDTH = 174; + public static final int VIEW_LEFT = 10; + protected static final ResourceLocation BACKGROUND = new ResourceLocation( + AppEng.MOD_ID, + "textures/guis/newinterfaceterminal.png"); + + private final IfaceList masterList = new IfaceList(); private final MEGuiTextField searchFieldOutputs; private final MEGuiTextField searchFieldInputs; private final MEGuiTextField searchFieldNames; private final GuiImgButton guiButtonHideFull; private final GuiImgButton guiButtonAssemblersOnly; private final GuiImgButton guiButtonBrokenRecipes; - private GuiImgButton searchStringSave; - private GuiImgButton terminalStyleBox; - private boolean refreshList = false; - protected static boolean onlyMolecularAssemblers = false; - protected static boolean onlyBrokenRecipes = false; - protected GuiTabButton craftingStatusBtn; - - private int rows = 3; + private final GuiImgButton terminalStyleBox; + private boolean onlyMolecularAssemblers = false; + private boolean onlyBrokenRecipes = false; + private boolean online; + /** The height of the viewport. */ + private int viewHeight; + private final List extraOptionsText; + private ItemStack tooltipStack; + private final boolean neiPresent; + /* + * Z-level Map (FLOATS) 0.0 - BACKGROUND 1.0 - ItemStacks 2.0 - Slot color overlays 20.0 - ItemStack overlays 21.0 - + * Slot mouse hover overlay 200.0 - Tooltips + */ + private static final float ITEMSTACK_Z = 1.0f; + private static final float SLOT_Z = 0.5f; + private static final float ITEMSTACK_OVERLAY_Z = 20.0f; + private static final float SLOT_HOVER_Z = 31.0f; + private static final float TOOLTIP_Z = 200.0f; - private static final String MOLECULAR_ASSEMBLER = "tile.appliedenergistics2.BlockMolecularAssembler"; + protected int offsetY; public GuiInterfaceWireless(final InventoryPlayer inventoryPlayer, final IWirelessTerminal te) { super(inventoryPlayer, new ContainerInterfaceWireless(inventoryPlayer, te)); @@ -99,12 +113,13 @@ public GuiInterfaceWireless(final InventoryPlayer inventoryPlayer, final IWirele this.setScrollBar(new GuiScrollbar()); this.xSize = 208; this.ySize = 255; + this.neiPresent = Loader.isModLoaded("NotEnoughItems"); searchFieldInputs = new MEGuiTextField(86, 12, ButtonToolTips.SearchFieldInputs.getLocal()) { @Override public void onTextChange(final String oldText) { - refreshList(); + masterList.markDirty(); } }; @@ -112,7 +127,7 @@ public void onTextChange(final String oldText) { @Override public void onTextChange(final String oldText) { - refreshList(); + masterList.markDirty(); } }; @@ -120,7 +135,7 @@ public void onTextChange(final String oldText) { @Override public void onTextChange(final String oldText) { - refreshList(); + masterList.markDirty(); } }; searchFieldNames.setFocused(true); @@ -128,6 +143,11 @@ public void onTextChange(final String oldText) { 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); + + this.extraOptionsText = new ArrayList<>(2); + extraOptionsText.add(ButtonToolTips.HighlightInterface.getLocal()); } @Override @@ -141,102 +161,76 @@ public void setOffsetY(int y) { } private void setScrollBar() { - this.getScrollBar().setTop(52).setLeft(189).setHeight(this.rows * 18 - 2); - this.getScrollBar().setRange(0, this.lines.size() - this.rows, 2); + int maxScroll = this.masterList.getHeight() - this.viewHeight - 1; + if (maxScroll <= 0) { + this.getScrollBar().setTop(52).setLeft(189).setHeight(this.viewHeight).setRange(0, 0, 1); + } else { + this.getScrollBar().setTop(52).setLeft(189).setHeight(this.viewHeight).setRange(0, maxScroll, 12); + } } + @SuppressWarnings("unchecked") @Override public void initGui() { super.initGui(); - this.rows = calculateRowsCount(); - this.ySize = MAGIC_HEIGHT_NUMBER + this.rows * 18; + + this.buttonList.clear(); + this.viewHeight = calculateViewHeight(); + this.ySize = HEADER_HEIGHT + INV_HEIGHT + this.viewHeight; + final int unusedSpace = this.height - this.ySize; this.guiTop = (int) Math.floor(unusedSpace / (unusedSpace < 0 ? 3.8f : 2.0f)); - this.offsetY = this.guiTop + 8; - this.buttonList.add( - this.terminalStyleBox = new GuiImgButton( - this.guiLeft - 18, - this.offsetY, - Settings.TERMINAL_STYLE, - AEConfig.instance.settings.getSetting(Settings.TERMINAL_STYLE))); - this.offsetY += 20; - this.buttonList.add( - this.searchStringSave = new GuiImgButton( - this.guiLeft - 18, - this.offsetY, - Settings.SAVE_SEARCH, - AEConfig.instance.settings.getSetting(Settings.SAVE_SEARCH))); - this.offsetY += 20; - - searchFieldInputs.x = guiLeft + Math.max(32, offsetX); + + searchFieldInputs.x = guiLeft + Math.max(32, VIEW_LEFT); searchFieldInputs.y = guiTop + 25; - searchFieldOutputs.x = guiLeft + Math.max(32, offsetX); + searchFieldOutputs.x = guiLeft + Math.max(32, VIEW_LEFT); searchFieldOutputs.y = guiTop + 38; - searchFieldNames.x = guiLeft + Math.max(32, offsetX) + 99; + searchFieldNames.x = guiLeft + Math.max(32, VIEW_LEFT) + 99; searchFieldNames.y = guiTop + 38; - guiButtonAssemblersOnly.xPosition = guiLeft + Math.max(32, offsetX) + 99; - guiButtonAssemblersOnly.yPosition = guiTop + 20; + terminalStyleBox.xPosition = guiLeft - 18; + terminalStyleBox.yPosition = guiTop + 8; - guiButtonHideFull.xPosition = guiButtonAssemblersOnly.xPosition + 18; - guiButtonHideFull.yPosition = guiTop + 20; + guiButtonBrokenRecipes.xPosition = guiLeft - 18; + guiButtonBrokenRecipes.yPosition = terminalStyleBox.yPosition + 18; - guiButtonBrokenRecipes.xPosition = guiButtonHideFull.xPosition + 18; - guiButtonBrokenRecipes.yPosition = guiTop + 20; + guiButtonHideFull.xPosition = guiLeft - 18; + guiButtonHideFull.yPosition = guiButtonBrokenRecipes.yPosition + 18; - terminalStyleBox.xPosition = guiLeft - 18; - terminalStyleBox.yPosition = guiTop + 8; - this.buttonList.add( - this.craftingStatusBtn = new GuiTabButton( - this.guiLeft + this.xSize - 24, - this.guiTop - 4, - 2 + 11 * 16, - GuiText.CraftingStatus.getLocal(), - itemRender)); - this.craftingStatusBtn.setHideEdge(13); // GuiTabButton implementation // + guiButtonAssemblersOnly.xPosition = guiLeft - 18; + guiButtonAssemblersOnly.yPosition = guiButtonHideFull.yPosition + 18; - if (ModAndClassUtil.isSearchBar && (AEConfig.instance.preserveSearchBar || this.isSubGui())) { - setSearchString(); - } + offsetY = 103; this.setScrollBar(); this.repositionSlots(); - this.initGuiDone(); - } - - @Override - public void onGuiClosed() { - super.onGuiClosed(); - searchFieldOutputsText = this.searchFieldOutputs.getText(); - searchFieldInputsText = this.searchFieldInputs.getText(); - searchFieldNamesText = this.searchFieldNames.getText(); - } - public void setSearchString() { - this.searchFieldOutputs.setText(searchFieldOutputsText); - this.searchFieldInputs.setText(searchFieldInputsText); - this.searchFieldNames.setText(searchFieldNamesText); + buttonList.add(guiButtonAssemblersOnly); + buttonList.add(guiButtonHideFull); + buttonList.add(guiButtonBrokenRecipes); + buttonList.add(terminalStyleBox); + initGuiDone(); + addSwitchGuiBtns(); } protected void repositionSlots() { for (final Object obj : this.inventorySlots.inventorySlots) { if (obj instanceof final AppEngSlot slot) { - slot.yDisplayPosition = this.ySize + slot.getY() - 78 - 7; + slot.yDisplayPosition = this.ySize + slot.getY() - 78 - 4; } } } - protected int calculateRowsCount() { - final int maxRows = this.getMaxRows(); + protected int calculateViewHeight() { + final int maxViewHeight = this.getMaxViewHeight(); final boolean hasNEI = IntegrationRegistry.INSTANCE.isEnabled(IntegrationType.NEI); - final int input = 22; - final int topPanel = 18; - final int NEIPadding = hasNEI ? input + topPanel : 0; - final int extraSpace = this.height - MAGIC_HEIGHT_NUMBER - NEIPadding; + final int NEIPadding = hasNEI ? 22 /* input */ + 18 /* top panel */ : 0; + final int availableSpace = this.height - HEADER_HEIGHT - INV_HEIGHT - NEIPadding; - return Math.max(3, Math.min(maxRows, extraSpace / 18)); + // screen should use 95% of the space it can, 5% margins + return Math.min((int) (availableSpace * 0.95), maxViewHeight); } @Override @@ -248,53 +242,16 @@ public void drawFG(final int offsetX, final int offsetY, final int mouseX, final GuiColors.InterfaceTerminalTitle.getColor()); fontRendererObj.drawString( GuiText.inventory.getLocal(), - GuiInterfaceWireless.offsetX + 2, + VIEW_LEFT + 2, this.ySize - 96, GuiColors.InterfaceTerminalInventory.getColor()); - - int offset = 51; - final int ex = getScrollBar().getCurrentScroll(); - for (int x = 0; x < this.rows && ex + x < this.lines.size(); x++) { - final Object lineObj = this.lines.get(ex + x); - if (lineObj instanceof final ClientDCInternalInv inv) { - for (int z = 0; z < inv.getInventory().getSizeInventory(); z++) { - if (this.matchedStacks.contains(inv.getInventory().getStackInSlot(z))) drawRect( - z * 18 + 22, - 1 + offset, - z * 18 + 22 + 16, - 1 + offset + 16, - GuiColors.InterfaceTerminalMatch.getColor()); - } - } else if (lineObj instanceof String name) { - final int rows = this.byName.get(name).size(); - String postfix = ""; - - if (rows > 1) { - postfix = " (" + rows + ')'; - } - - while (name.length() > 2 && this.fontRendererObj.getStringWidth(name + postfix) > 158) { - name = name.substring(0, name.length() - 1); - } - - this.fontRendererObj.drawString( - name + postfix, - GuiInterfaceWireless.offsetX + 3, - 6 + offset, - GuiColors.InterfaceTerminalName.getColor()); - } - - offset += 18; + if (!neiPresent && tooltipStack != null) { + renderToolTip(tooltipStack, mouseX, mouseY); } } - @SuppressWarnings("unchecked") @Override public void drawScreen(final int mouseX, final int mouseY, final float btn) { - - buttonList.clear(); - inventorySlots.inventorySlots.removeIf(slot -> slot instanceof SlotDisconnected); - guiButtonAssemblersOnly.set( onlyMolecularAssemblers ? ActionItems.MOLECULAR_ASSEMBLEERS_ON : ActionItems.MOLECULAR_ASSEMBLEERS_OFF); guiButtonHideFull.set( @@ -307,43 +264,6 @@ public void drawScreen(final int mouseX, final int mouseY, final float btn) { terminalStyleBox.set(AEConfig.instance.settings.getSetting(Settings.TERMINAL_STYLE)); - buttonList.add(guiButtonAssemblersOnly); - buttonList.add(guiButtonHideFull); - buttonList.add(guiButtonBrokenRecipes); - - buttonList.add(terminalStyleBox); - buttonList.add(searchStringSave); - buttonList.add(craftingStatusBtn); - this.guiButtonHashMap.clear(); - addSwitchGuiBtns(); - - int offset = 51; - final int ex = this.getScrollBar().getCurrentScroll(); - for (int x = 0; x < this.rows && ex + x < this.lines.size(); x++) { - final Object lineObj = this.lines.get(ex + x); - if (lineObj instanceof final ClientDCInternalInv inv) { - for (int z = 0; z < inv.getInventory().getSizeInventory(); z++) { - inventorySlots.inventorySlots.add(new SlotDisconnected(inv, z, z * 18 + 22, 1 + offset)); - } - - GuiButton guiButton = new GuiImgButton( - guiLeft + 4, - guiTop + offset + 1, - Settings.ACTIONS, - ActionItems.HIGHLIGHT_INTERFACE); - GuiFCImgButton editButton = new GuiFCImgButton(guiLeft + 4, guiTop + offset + 1, "EDIT", "YES"); - guiButtonHashMap.put(guiButton, inv); - guiButtonHashMap.put(editButton, inv); - if (isShiftKeyDown()) { - buttonList.add(editButton); - } else { - buttonList.add(guiButton); - } - } - - offset += 18; - } - super.drawScreen(mouseX, mouseY, btn); handleTooltip(mouseX, mouseY, searchFieldInputs); @@ -353,132 +273,420 @@ public void drawScreen(final int mouseX, final int mouseY, final float btn) { @Override protected void mouseClicked(final int xCoord, final int yCoord, final int btn) { - boolean focusIn = searchFieldInputs.isFocused(); - boolean focusOut = searchFieldOutputs.isFocused(); - boolean focusName = searchFieldNames.isFocused(); - searchFieldInputs.mouseClicked(xCoord, yCoord, btn); searchFieldOutputs.mouseClicked(xCoord, yCoord, btn); searchFieldNames.mouseClicked(xCoord, yCoord, btn); - if (focusIn && !searchFieldInputs.isFocused()) { - searchFieldInputsText = searchFieldInputs.getText(); - } else if (focusOut && !searchFieldOutputs.isFocused()) { - searchFieldOutputsText = searchFieldOutputs.getText(); - } else if (focusName && !searchFieldNames.isFocused()) { - searchFieldNamesText = searchFieldNames.getText(); + if (masterList.mouseClicked(xCoord - guiLeft - VIEW_LEFT, yCoord - guiTop - HEADER_HEIGHT, btn)) { + return; } super.mouseClicked(xCoord, yCoord, btn); } @Override protected void actionPerformed(final GuiButton btn) { - if (guiButtonHashMap.containsKey(btn)) { - Util.DimensionalCoordSide blockPos = blockPosHashMap.get(guiButtonHashMap.get(btn)); - if (btn instanceof GuiFCImgButton) { - FluidCraft.proxy.netHandler.sendToServer( - new CPacketRenamer( - blockPos.x, - blockPos.y, - blockPos.z, - blockPos.getDimension(), - blockPos.getSide())); - } else { - WorldCoord blockPos2 = new WorldCoord( - (int) mc.thePlayer.posX, - (int) mc.thePlayer.posY, - (int) mc.thePlayer.posZ); - if (mc.theWorld.provider.dimensionId != blockPos.getDimension()) { - mc.thePlayer.addChatMessage( - new ChatComponentTranslation( - PlayerMessages.InterfaceInOtherDim.getName(), - blockPos.getDimension())); - } else { - BlockPosHighlighter.highlightBlock( - blockPos, - System.currentTimeMillis() + 500 * WorldCoord.getTaxicabDistance(blockPos, blockPos2)); - mc.thePlayer.addChatMessage( - new ChatComponentTranslation( - PlayerMessages.InterfaceHighlighted.getName(), - blockPos.x, - blockPos.y, - blockPos.z)); - } - mc.thePlayer.closeScreen(); - } + if (btn == guiButtonAssemblersOnly) { + onlyMolecularAssemblers = !onlyMolecularAssemblers; + masterList.markDirty(); } else if (btn == guiButtonHideFull) { AEConfig.instance.showOnlyInterfacesWithFreeSlotsInInterfaceTerminal = !AEConfig.instance.showOnlyInterfacesWithFreeSlotsInInterfaceTerminal; - this.refreshList(); - } else if (btn == guiButtonAssemblersOnly) { - onlyMolecularAssemblers = !onlyMolecularAssemblers; - this.refreshList(); + masterList.markDirty(); } else if (btn == guiButtonBrokenRecipes) { onlyBrokenRecipes = !onlyBrokenRecipes; - this.refreshList(); - } else if (ModAndClassUtil.isSaveText && btn == searchStringSave) { - final boolean backwards = Mouse.isButtonDown(1); - final GuiImgButton iBtn = (GuiImgButton) btn; - final Enum cv = iBtn.getCurrentValue(); - final Enum next = Platform.rotateEnum(cv, backwards, iBtn.getSetting().getPossibleValues()); - AEConfig.instance.preserveSearchBar = next == YesNo.YES; - AEConfig.instance.settings.putSetting(Settings.SAVE_SEARCH, next); - this.searchStringSave.set(next); - - } else if (btn == craftingStatusBtn) { - InventoryHandler.switchGui(GuiType.CRAFTING_STATUS); - } else if (btn instanceof final GuiImgButton iBtn) { + masterList.markDirty(); + } else if (btn instanceof GuiImgButton iBtn) { if (iBtn.getSetting() != Settings.ACTIONS) { - final Enum cv = iBtn.getCurrentValue(); + final Enum cv = iBtn.getCurrentValue(); final boolean backwards = Mouse.isButtonDown(1); - final Enum next = Platform.rotateEnum(cv, backwards, iBtn.getSetting().getPossibleValues()); + final Enum next = Platform.rotateEnum(cv, backwards, iBtn.getSetting().getPossibleValues()); if (btn == this.terminalStyleBox) { AEConfig.instance.settings.putSetting(iBtn.getSetting(), next); - - this.reinitialize(); + initGui(); } iBtn.set(next); } + } else { + super.actionPerformed(btn); } - super.actionPerformed(btn); - } - - private void reinitialize() { - this.buttonList.clear(); - this.initGui(); } @Override public void drawBG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { - this.bindTexture("guis/newinterfaceterminal.png"); - this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, 53); + this.bindTexture(BACKGROUND); + /* Draws the top part. */ + this.drawTexturedModalRect(offsetX, offsetY, 0, 0, xSize, HEADER_HEIGHT); + /* Draws the middle part. */ + Tessellator.instance.startDrawingQuads(); + addTexturedRectToTesselator( + offsetX, + offsetY + HEADER_HEIGHT, + offsetX + xSize, + offsetY + HEADER_HEIGHT + viewHeight + 1, + 0.0f, + 0.0f, + (HEADER_HEIGHT + IfaceSection.TITLE_HEIGHT + 1.0f) / 256.0f, + this.xSize / 256.0f, + (HEADER_HEIGHT + 106.0f) / 256.0f); + Tessellator.instance.draw(); + /* Draw the bottom part */ + this.drawTexturedModalRect(offsetX, offsetY + HEADER_HEIGHT + viewHeight, 0, 158, xSize, INV_HEIGHT); + if (online) { + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + /* (0,0) => viewPort's (0,0) */ + GL11.glPushMatrix(); + GL11.glTranslatef(offsetX + VIEW_LEFT, offsetY + HEADER_HEIGHT, 0); + tooltipStack = null; + masterList.hoveredEntry = null; + drawViewport(mouseX - offsetX - VIEW_LEFT, mouseY - offsetY - HEADER_HEIGHT - 1); + GL11.glPopMatrix(); + GL11.glPopAttrib(); + } + searchFieldInputs.drawTextBox(); + searchFieldOutputs.drawTextBox(); + searchFieldNames.drawTextBox(); + } - int offset = 51; - final int ex = this.getScrollBar().getCurrentScroll(); + /** + * Draws the viewport area + */ + private void drawViewport(int relMouseX, int relMouseY) { + /* Viewport Magic */ + final int scroll = this.getScrollBar().getCurrentScroll(); + int viewY = -scroll; // current y in viewport coordinates + int entryIdx = 0; + List visibleSections = this.masterList.getVisibleSections(); + + final float guiScaleX = (float) mc.displayWidth / width; + final float guiScaleY = (float) mc.displayHeight / height; + GL11.glScissor( + (int) ((guiLeft + VIEW_LEFT) * guiScaleX), + (int) ((height - (guiTop + HEADER_HEIGHT + viewHeight)) * guiScaleY), + (int) (VIEW_WIDTH * guiScaleX), + (int) (this.viewHeight * guiScaleY)); + GL11.glEnable(GL11.GL_SCISSOR_TEST); + + /* + * Render each section + */ + while (viewY < this.viewHeight && entryIdx < visibleSections.size()) { + IfaceSection section = visibleSections.get(entryIdx); + int sectionHeight = section.getHeight(); + + /* Is it viewable/in the viewport at all? */ + if (viewY + sectionHeight < 0) { + entryIdx++; + viewY += sectionHeight; + section.visible = false; + continue; + } - for (int x = 0; x < this.rows; x++) { - this.drawTexturedModalRect(offsetX, offsetY + 53 + x * 18, 0, 52, this.xSize, 18); + section.visible = true; + int advanceY = drawSection(section, viewY, relMouseX, relMouseY); + viewY += advanceY; + entryIdx++; } + } - for (int x = 0; x < this.rows && ex + x < this.lines.size(); x++) { + /** + * Render the section (if it is visible) + * + * @param section the section to render + * @param viewY current y coordinate relative to gui + * @param relMouseX transformed mouse coords relative to viewport + * @param relMouseY transformed mouse coords relative to viewport + * @return the height of the section rendered in viewport coordinates, max of viewHeight. + */ + private int drawSection(IfaceSection section, int viewY, int relMouseX, int relMouseY) { + int title; + int renderY = 0; + final int sectionBottom = viewY + section.getHeight() - 1; + final int fontColor = GuiColors.InterfaceTerminalInventory.getColor(); + /* + * Render title + */ + GL11.glTranslatef(0.0f, 0.0f, 50f); + bindTexture(BACKGROUND); + if (sectionBottom > 0 && sectionBottom < IfaceSection.TITLE_HEIGHT) { + /* Transition draw */ + drawTexturedModalRect( + 0, + 0, + VIEW_LEFT, + HEADER_HEIGHT + IfaceSection.TITLE_HEIGHT - sectionBottom, + VIEW_WIDTH, + sectionBottom); + fontRendererObj.drawString(section.name, 2, sectionBottom - IfaceSection.TITLE_HEIGHT + 2, fontColor); + title = sectionBottom; + } else if (viewY < 0) { + /* Hidden title draw */ + drawTexturedModalRect(0, 0, VIEW_LEFT, HEADER_HEIGHT, VIEW_WIDTH, IfaceSection.TITLE_HEIGHT); + fontRendererObj.drawString(section.name, 2, 2, fontColor); + title = 0; + } else { + /* Normal title draw */ + drawTexturedModalRect(0, viewY, VIEW_LEFT, HEADER_HEIGHT, VIEW_WIDTH, IfaceSection.TITLE_HEIGHT); + fontRendererObj.drawString(section.name, 2, viewY + 2, fontColor); + title = 0; + } + GL11.glTranslatef(0.0f, 0.0f, -50f); + GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + Iterator visible = section.getVisible(); + while (visible.hasNext()) { + if (viewY < viewHeight) { + renderY += drawEntry( + visible.next(), + viewY + IfaceSection.TITLE_HEIGHT + renderY, + title, + relMouseX, + relMouseY); + } else { + IfaceEntry entry = visible.next(); + entry.dispY = -9999; + entry.optionsButton.yPosition = -1; + } + } + return IfaceSection.TITLE_HEIGHT + renderY; + } + + /** + * Draws the entry. In practice it just draws the slots + items. + * + * @param viewY the gui coordinate z + */ + private int drawEntry(IfaceEntry entry, int viewY, int titleBottom, int relMouseX, int relMouseY) { + bindTexture(BACKGROUND); + Tessellator.instance.startDrawingQuads(); + int relY = 0; + final int slotLeftMargin = (VIEW_WIDTH - entry.rowSize * 18); + + entry.dispY = viewY; + /* PASS 1: BG */ + for (int row = 0; row < entry.rows; ++row) { + final int rowYTop = row * 18; + final int rowYBot = rowYTop + 18; + + relY += 18; + /* Is the slot row in view? */ + if (viewY + rowYBot <= titleBottom) { + continue; + } + for (int col = 0; col < entry.rowSize; ++col) { + addTexturedRectToTesselator( + col * 18 + slotLeftMargin, + viewY + rowYTop, + 18 * col + 18 + slotLeftMargin, + viewY + rowYBot, + 0, + 21 / 256f, + 173 / 256f, + (21 + 18) / 256f, + (173 + 18) / 256f); + } + } + Tessellator.instance.draw(); + /* Draw button */ + if (viewY + entry.optionsButton.height > 0 && viewY < viewHeight) { + entry.optionsButton.yPosition = viewY + 5; + entry.renameButton.yPosition = viewY + 5; + if (isShiftKeyDown()) { + entry.renameButton.drawButton(mc, relMouseX, relMouseY); + } else { + entry.optionsButton.drawButton(mc, relMouseX, relMouseY); + } + if (entry.optionsButton.getMouseIn() + && relMouseY >= Math.max(IfaceSection.TITLE_HEIGHT, entry.optionsButton.yPosition)) { + // draw a tooltip + GL11.glTranslatef(0f, 0f, TOOLTIP_Z); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + drawHoveringText(extraOptionsText, relMouseX, relMouseY); + GL11.glTranslatef(0f, 0f, -TOOLTIP_Z); + GL11.glEnable(GL11.GL_SCISSOR_TEST); + } + } else { + entry.optionsButton.yPosition = -1; + entry.renameButton.yPosition = -1; + } + /* PASS 2: Items */ + for (int row = 0; row < entry.rows; ++row) { + final int rowYTop = row * 18; + final int rowYBot = rowYTop + 18; + /* Is the slot row in view? */ + if (viewY + rowYBot <= titleBottom) { + continue; + } + AppEngInternalInventory inv = entry.getInventory(); + + for (int col = 0; col < entry.rowSize; ++col) { + final int colLeft = col * 18 + slotLeftMargin + 1; + final int colRight = colLeft + 18 + 1; + final int slotIdx = row * entry.rowSize + col; + ItemStack stack = inv.getStackInSlot(slotIdx); + + boolean tooltip = relMouseX > colLeft - 1 && relMouseX < colRight - 1 + && relMouseY >= Math.max(viewY + rowYTop, IfaceSection.TITLE_HEIGHT) + && relMouseY < Math.min(viewY + rowYBot, viewHeight); + if (stack != null) { + final ItemEncodedPattern iep = (ItemEncodedPattern) stack.getItem(); + final ItemStack toRender = iep.getOutput(stack); + + GL11.glPushMatrix(); + GL11.glTranslatef(colLeft, viewY + rowYTop + 1, ITEMSTACK_Z); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + RenderHelper.enableGUIStandardItemLighting(); + translatedRenderItem.zLevel = 3.0f - 50.0f; + translatedRenderItem + .renderItemAndEffectIntoGUI(fontRendererObj, mc.getTextureManager(), toRender, 0, 0); + GL11.glTranslatef(0.0f, 0.0f, ITEMSTACK_OVERLAY_Z - ITEMSTACK_Z); + aeRenderItem.renderItemOverlayIntoGUI(fontRendererObj, mc.getTextureManager(), toRender, 0, 0); + aeRenderItem.zLevel = 0.0f; + RenderHelper.disableStandardItemLighting(); + if (!tooltip) { + if (entry.brokenRecipes[slotIdx]) { + GL11.glTranslatef(0.0f, 0.0f, SLOT_Z - ITEMSTACK_OVERLAY_Z); + drawRect(0, 0, 16, 16, GuiColors.ItemSlotOverlayInvalid.getColor()); + } else if (entry.filteredRecipes[slotIdx]) { + GL11.glTranslatef(0.0f, 0.0f, ITEMSTACK_OVERLAY_Z); + drawRect(0, 0, 16, 16, GuiColors.ItemSlotOverlayUnpowered.getColor()); + } + } else { + tooltipStack = stack; + } + GL11.glPopMatrix(); + } else if (entry.filteredRecipes[slotIdx]) { + GL11.glPushMatrix(); + GL11.glTranslatef(colLeft, viewY + rowYTop + 1, ITEMSTACK_OVERLAY_Z); + drawRect(0, 0, 16, 16, GuiColors.ItemSlotOverlayUnpowered.getColor()); + GL11.glPopMatrix(); + } + if (tooltip) { + // overlay highlight + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glTranslatef(0.0f, 0.0f, SLOT_HOVER_Z); + drawRect(colLeft, viewY + 1 + rowYTop, -2 + colRight, viewY - 1 + rowYBot, 0x77FFFFFF); + GL11.glTranslatef(0.0f, 0.0f, -SLOT_HOVER_Z); + masterList.hoveredEntry = entry; + entry.hoveredSlotIdx = slotIdx; + } + GL11.glDisable(GL11.GL_LIGHTING); + } + } + GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + return relY + 1; + } + + @Override + public List handleItemTooltip(ItemStack stack, int mouseX, int mouseY, List currentToolTip) { + return currentToolTip; + } - final Object lineObj = this.lines.get(ex + x); - if (lineObj instanceof final ClientDCInternalInv inv) { + @Override + public ItemStack getHoveredStack() { + return tooltipStack; + } - GL11.glColor4f(1, 1, 1, 1); - final int width = inv.getInventory().getSizeInventory() * 18; - this.drawTexturedModalRect(offsetX + 20, offsetY + offset, 20, 173, width, 18); + /** + * A copy of super method, but modified to allow for depth testing. + */ + @SuppressWarnings("unchecked") + @Override + public void drawHoveringText(List textLines, int x, int y, FontRenderer font) { + if (!textLines.isEmpty()) { + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + RenderHelper.disableStandardItemLighting(); + int maxStrWidth = 0; + + // is this more efficient than doing 1 pass, then doing a translate before drawing the text? + for (String s : (List) textLines) { + int width = font.getStringWidth(s); + + if (width > maxStrWidth) { + maxStrWidth = width; + } } - offset += 18; - } + // top left corner + int curX = x + 12; + int curY = y - 12; + int totalHeight = 8; - this.drawTexturedModalRect(offsetX, offsetY + 50 + this.rows * 18, 0, 158, this.xSize, 99); + if (textLines.size() > 1) { + totalHeight += 2 + (textLines.size() - 1) * 10; + } - searchFieldInputs.drawTextBox(); - searchFieldOutputs.drawTextBox(); - searchFieldNames.drawTextBox(); + /* String is too long? Display on the left side */ + if (curX + maxStrWidth > this.width) { + curX -= 28 + maxStrWidth; + } + + /* String is too tall? move it up */ + if (curY + totalHeight + 6 > this.height) { + curY = this.height - totalHeight - 6; + } + + int borderColor = -267386864; + // drawing the border... + this.drawGradientRect(curX - 3, curY - 4, curX + maxStrWidth + 3, curY - 3, borderColor, borderColor); + this.drawGradientRect( + curX - 3, + curY + totalHeight + 3, + curX + maxStrWidth + 3, + curY + totalHeight + 4, + borderColor, + borderColor); + this.drawGradientRect( + curX - 3, + curY - 3, + curX + maxStrWidth + 3, + curY + totalHeight + 3, + borderColor, + borderColor); + this.drawGradientRect(curX - 4, curY - 3, curX - 3, curY + totalHeight + 3, borderColor, borderColor); + this.drawGradientRect( + curX + maxStrWidth + 3, + curY - 3, + curX + maxStrWidth + 4, + curY + totalHeight + 3, + borderColor, + borderColor); + int color1 = 1347420415; + int color2 = (color1 & 16711422) >> 1 | color1 & -16777216; + this.drawGradientRect(curX - 3, curY - 3 + 1, curX - 3 + 1, curY + totalHeight + 3 - 1, color1, color2); + this.drawGradientRect( + curX + maxStrWidth + 2, + curY - 3 + 1, + curX + maxStrWidth + 3, + curY + totalHeight + 3 - 1, + color1, + color2); + this.drawGradientRect(curX - 3, curY - 3, curX + maxStrWidth + 3, curY - 3 + 1, color1, color1); + this.drawGradientRect( + curX - 3, + curY + totalHeight + 2, + curX + maxStrWidth + 3, + curY + totalHeight + 3, + color2, + color2); + + for (int i = 0; i < textLines.size(); ++i) { + String line = (String) textLines.get(i); + font.drawStringWithShadow(line, curX, curY, -1); + + if (i == 0) { + // gap between name and lore text + curY += 2; + } + + curY += 10; + } + + RenderHelper.enableGUIStandardItemLighting(); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + } } @Override @@ -489,18 +697,42 @@ protected void keyTyped(final char character, final int key) { || (searchFieldOutputs.getText().isEmpty() && searchFieldOutputs.isFocused()) || (searchFieldNames.getText().isEmpty() && searchFieldNames.isFocused())) return; - } else if (character == '\t') { - if (handleTab()) return; + } else if (character == '\t' && handleTab()) { + return; } if (searchFieldInputs.textboxKeyTyped(character, key) || searchFieldOutputs.textboxKeyTyped(character, key) || searchFieldNames.textboxKeyTyped(character, key)) { - refreshList(); + return; + } + super.keyTyped(character, key); + } + } + + @Override + protected boolean mouseWheelEvent(int mouseX, int mouseY, int wheel) { + boolean isMouseInViewport = isMouseInViewport(mouseX, mouseY); + GuiScrollbar scrollbar = getScrollBar(); + if (isMouseInViewport && isCtrlKeyDown()) { + if (wheel < 0) { + scrollbar.setCurrentScroll(masterList.getHeight()); } else { - super.keyTyped(character, key); + getScrollBar().setCurrentScroll(0); } + return true; + } else if (isMouseInViewport && isShiftKeyDown()) { + // advance to the next section + return masterList.scrollNextSection(wheel > 0); + } else { + return super.mouseWheelEvent(mouseX, mouseY, wheel); } } + private boolean isMouseInViewport(int mouseX, int mouseY) { + return mouseX > guiLeft + VIEW_LEFT && mouseX < guiLeft + VIEW_LEFT + VIEW_WIDTH + && mouseY > guiTop + HEADER_HEIGHT + && mouseY < guiTop + HEADER_HEIGHT + viewHeight; + } + private boolean handleTab() { if (searchFieldInputs.isFocused()) { searchFieldInputs.setFocused(false); @@ -521,138 +753,64 @@ private boolean handleTab() { return false; } - public void postUpdate(final NBTTagCompound in) { - if (in.getBoolean("clear")) { - this.byId.clear(); - this.refreshList = true; - } - - for (final Object oKey : in.func_150296_c()) { - final String key = (String) oKey; - if (key.startsWith("=")) { - try { - final long id = Long.parseLong(key.substring(1), Character.MAX_RADIX); - final NBTTagCompound invData = in.getCompoundTag(key); - int size = invData.getInteger("size"); - final ClientDCInternalInv current = this - .getById(id, invData.getLong("sortBy"), invData.getString("un"), size); - int X = invData.getInteger("x"); - int Y = invData.getInteger("y"); - int Z = invData.getInteger("z"); - int dim = invData.getInteger("dim"); - ForgeDirection side = ForgeDirection.getOrientation(invData.getInteger("side")); - blockPosHashMap.put( - current, - new Util.DimensionalCoordSide(X, Y, Z, dim, side, current.getUnlocalizedName())); - - for (int x = 0; x < current.getInventory().getSizeInventory(); x++) { - final String which = Integer.toString(x); - if (invData.hasKey(which)) { - current.getInventory().setInventorySlotContents( - x, - ItemStack.loadItemStackFromNBT(invData.getCompoundTag(which))); - } - } - } catch (final NumberFormatException ignored) {} - } + public void postUpdate(List updates, int statusFlags) { + if ((statusFlags & PacketIfaceTermUpdate.CLEAR_ALL_BIT) == PacketIfaceTermUpdate.CLEAR_ALL_BIT) { + /* Should clear all client entries. */ + this.masterList.list.clear(); } + /* Should indicate disconnected, so the terminal turns dark. */ + this.online = (statusFlags & PacketIfaceTermUpdate.DISCONNECT_BIT) != PacketIfaceTermUpdate.DISCONNECT_BIT; - if (this.refreshList) { - this.refreshList = false; - // invalidate caches on refresh - this.cachedSearches.clear(); - this.refreshList(); + for (PacketIfaceTermUpdate.PacketEntry cmd : updates) { + parsePacketCmd(cmd); } + this.masterList.markDirty(); + } - /** - * Rebuilds the list of interfaces. - *

- * Respects a search term if present (ignores case) and adding only matching patterns. - */ - private void refreshList() { - this.byName.clear(); - this.buttonList.clear(); - this.matchedStacks.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 - + "NAME:" - + searchFieldNames - + AEConfig.instance.showOnlyInterfacesWithFreeSlotsInInterfaceTerminal - + onlyMolecularAssemblers - + onlyBrokenRecipes); - final boolean rebuild = cachedSearch.isEmpty(); - - for (final ClientDCInternalInv entry : this.byId.values()) { - // ignore inventory if not doing a full rebuild and cache already marks it as miss. - if (!rebuild && !cachedSearch.contains(entry)) { - continue; + private void parsePacketCmd(PacketIfaceTermUpdate.PacketEntry cmd) { + long id = cmd.entryId; + if (cmd instanceof PacketIfaceTermUpdate.PacketAdd addCmd) { + IfaceEntry entry = new IfaceEntry(id, addCmd.name, addCmd.rows, addCmd.rowSize, addCmd.online) + .setLocation(addCmd.x, addCmd.y, addCmd.z, addCmd.dim, addCmd.side) + .setIcons(addCmd.selfRep, addCmd.dispRep).setItems(addCmd.items); + masterList.addEntry(entry); + } else if (cmd instanceof PacketIfaceTermUpdate.PacketRemove) { + masterList.removeEntry(id); + } else if (cmd instanceof PacketIfaceTermUpdate.PacketOverwrite owCmd) { + IfaceEntry entry = masterList.list.get(id); + + if (entry == null) { + return; } - // Shortcut to skip any filter if search term is ""/empty - 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 || AEConfig.instance.showOnlyInterfacesWithFreeSlotsInInterfaceTerminal || onlyBrokenRecipes) { - for (final ItemStack itemStack : entry.getInventory()) { - // If only Interfaces with empty slots should be shown, check that here - if (itemStack == null) { - interfaceHasFreeSlots = true; - continue; - } - - if (onlyBrokenRecipes && recipeIsBroken(itemStack)) { - interfaceHasBrokenRecipes = true; - } + if (owCmd.onlineValid) { + entry.online = owCmd.online; + } - if ((!searchFieldInputs.isEmpty() && itemStackMatchesSearchTerm(itemStack, searchFieldInputs, 0)) - || (!searchFieldOutputs.isEmpty() - && itemStackMatchesSearchTerm(itemStack, searchFieldOutputs, 1))) { - found = true; - matchedStacks.add(itemStack); - } + if (owCmd.itemsValid) { + if (owCmd.allItemUpdate) { + entry.fullItemUpdate(owCmd.items, owCmd.validIndices.length); + } else { + entry.partialItemUpdate(owCmd.items, owCmd.validIndices); } } + masterList.isDirty = true; + } else if (cmd instanceof PacketIfaceTermUpdate.PacketRename renameCmd) { + IfaceEntry entry = masterList.list.get(id); - if ((found && entry.getName().toLowerCase().contains(searchFieldNames)) - && (!onlyMolecularAssemblers || entry.getUnlocalizedName().contains(MOLECULAR_ASSEMBLER)) - && (!AEConfig.instance.showOnlyInterfacesWithFreeSlotsInInterfaceTerminal || interfaceHasFreeSlots) - && (!onlyBrokenRecipes || interfaceHasBrokenRecipes)) { - this.byName.put(entry.getName(), entry); - cachedSearch.add(entry); - } else { - cachedSearch.remove(entry); + if (entry != null) { + if (StatCollector.canTranslate(renameCmd.newName)) { + entry.dispName = StatCollector.translateToLocal(renameCmd.newName); + } else { + entry.dispName = StatCollector.translateToFallback(renameCmd.newName); + } } + masterList.isDirty = true; } - - this.names.clear(); - this.names.addAll(this.byName.keySet()); - - Collections.sort(this.names); - - this.lines.clear(); - this.lines.ensureCapacity(this.names.size() + this.byId.size()); - - for (final String n : this.names) { - this.lines.add(n); - final ArrayList clientInventories = new ArrayList<>(this.byName.get(n)); - Collections.sort(clientInventories); - this.lines.addAll(clientInventories); - } - - this.setScrollBar(); } - private boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final String searchTerm, int pass) { + private boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final String searchTerm, boolean in) { if (itemStack == null) { return false; } @@ -663,7 +821,7 @@ private boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final Stri return false; } - final NBTTagList tags = encodedValue.getTagList(pass == 0 ? "in" : "out", 10); + final NBTTagList tags = encodedValue.getTagList(in ? "in" : "out", Constants.NBT.TAG_COMPOUND); final boolean containsInvalidDisplayName = GuiText.UnknownItem.getLocal().toLowerCase().contains(searchTerm); for (int i = 0; i < tags.tagCount(); i++) { @@ -682,10 +840,10 @@ private boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final Stri } return false; + } private boolean recipeIsBroken(final ItemStack itemStack) { - if (itemStack == null) { return false; } @@ -708,55 +866,18 @@ private boolean recipeIsBroken(final ItemStack itemStack) { } } - /** - * Tries to retrieve a cache for a with search term as keyword. - *

- * If this cache should be empty, it will populate it with an earlier cache if available or at least the cache for - * the empty string. - * - * @param searchTerm the corresponding search - * @return a Set matching a superset of the search term - */ - private Set getCacheForSearchTerm(final String searchTerm) { - if (!this.cachedSearches.containsKey(searchTerm)) { - this.cachedSearches.put(searchTerm, new HashSet<>()); - } - - final Set cache = this.cachedSearches.get(searchTerm); - - if (cache.isEmpty() && searchTerm.length() > 1) { - cache.addAll(this.getCacheForSearchTerm(searchTerm.substring(0, searchTerm.length() - 1))); - return cache; - } - - return cache; - } - - private int getMaxRows() { + private int getMaxViewHeight() { return AEConfig.instance.getConfigManager().getSetting(Settings.TERMINAL_STYLE) == TerminalStyle.SMALL - ? AEConfig.instance.InterfaceTerminalSmallSize + ? AEConfig.instance.InterfaceTerminalSmallSize * 18 : Integer.MAX_VALUE; } - private ClientDCInternalInv getById(final long id, final long sortBy, final String unlocalizedName, - final int sizeInit) { - ClientDCInternalInv o = this.byId.get(id); - - if (o == null) { - this.byId.put(id, o = new ClientDCInternalInv(sizeInit, id, sortBy, unlocalizedName)); - this.refreshList = true; - } - - return o; - } - public boolean isOverTextField(final int mousex, final int mousey) { return searchFieldInputs.isMouseIn(mousex, mousey) || searchFieldOutputs.isMouseIn(mousex, mousey) || searchFieldNames.isMouseIn(mousex, mousey); } public void setTextFieldValue(final String displayName, final int mousex, final int mousey, final ItemStack stack) { - if (searchFieldInputs.isMouseIn(mousex, mousey)) { searchFieldInputs.setText(displayName); } else if (searchFieldOutputs.isMouseIn(mousex, mousey)) { @@ -766,4 +887,450 @@ public void setTextFieldValue(final String displayName, final int mousex, final } } + /** + * Tracks the list of entries. + */ + private class IfaceList { + + private final Map list = new HashMap<>(); + private final Map sections = new TreeMap<>(); + private final List visibleSections = new ArrayList<>(); + private boolean isDirty; + private int height; + private IfaceEntry hoveredEntry; + + IfaceList() { + this.isDirty = true; + } + + /** + * Performs a full update. + */ + private void update() { + height = 0; + visibleSections.clear(); + + for (IfaceSection section : sections.values()) { + String query = GuiInterfaceWireless.this.searchFieldNames.getText(); + if (!query.isEmpty() && !section.name.toLowerCase().contains(query.toLowerCase())) { + continue; + } + + section.isDirty = true; + if (section.getVisible().hasNext()) { + height += section.getHeight(); + visibleSections.add(section); + } + } + isDirty = false; + } + + public void markDirty() { + this.isDirty = true; + setScrollBar(); + } + + public int getHeight() { + if (isDirty) { + update(); + } + return height; + } + + /** + * Jump between sections. + */ + private boolean scrollNextSection(boolean up) { + GuiScrollbar scrollbar = getScrollBar(); + int viewY = scrollbar.getCurrentScroll(); + var sections = getVisibleSections(); + boolean result = false; + + if (up) { + int y = masterList.getHeight(); + int i = sections.size() - 1; + + while (y > 0 && i >= 0) { + y -= sections.get(i).getHeight(); + i -= 1; + if (y < viewY) { + result = true; + scrollbar.setCurrentScroll(y); + break; + } + } + } else { + int y = 0; + + for (IfaceSection section : sections) { + if (y > viewY) { + result = true; + scrollbar.setCurrentScroll(y); + break; + } + y += section.getHeight(); + } + } + return result; + } + + public void addEntry(IfaceEntry entry) { + IfaceSection section = sections.get(entry.dispName); + + if (section == null) { + section = new IfaceSection(entry.dispName); + sections.put(entry.dispName, section); + } + section.addEntry(entry); + list.put(entry.id, entry); + isDirty = true; + } + + public void removeEntry(long id) { + IfaceEntry entry = list.remove(id); + + if (entry != null) { + entry.section.removeEntry(entry); + } + } + + public List getVisibleSections() { + if (isDirty) { + update(); + } + return visibleSections; + } + + /** + * Mouse button click. + * + * @param relMouseX viewport coords mouse X + * @param relMouseY viewport coords mouse Y + * @param btn button code + */ + public boolean mouseClicked(int relMouseX, int relMouseY, int btn) { + if (relMouseX < 0 || relMouseX >= VIEW_WIDTH || relMouseY < 0 || relMouseY >= viewHeight) { + return false; + } + for (IfaceSection section : getVisibleSections()) { + if (section.mouseClicked(relMouseX, relMouseY, btn)) { + return true; + } + } + return false; + } + } + + /** + * A section holds all the interface entries with the same name. + */ + private class IfaceSection { + + public static final int TITLE_HEIGHT = 12; + + String name; + List entries = new ArrayList<>(); + Set visibleEntries = new TreeSet<>(Comparator.comparing(e -> { + if (e.dispRep != null) { + return e.dispRep.getDisplayName() + e.id; + } else { + return String.valueOf(e.id); + } + })); + int height; + private boolean isDirty = true; + boolean visible = false; + + IfaceSection(String name) { + this.name = name; + } + + /** + * Gets the height. Includes title. + */ + public int getHeight() { + if (isDirty) { + update(); + } + return height; + } + + private void update() { + refreshVisible(); + if (visibleEntries.isEmpty()) { + height = 0; + } else { + height = TITLE_HEIGHT; + for (IfaceEntry entry : visibleEntries) { + height += entry.guiHeight; + } + } + isDirty = false; + } + + public void refreshVisible() { + visibleEntries.clear(); + String input = GuiInterfaceWireless.this.searchFieldInputs.getText().toLowerCase(); + String output = GuiInterfaceWireless.this.searchFieldOutputs.getText().toLowerCase(); + + for (IfaceEntry entry : entries) { + var moleAss = AEApi.instance().definitions().blocks().molecularAssembler().maybeStack(1); + entry.dispY = -9999; + if (onlyMolecularAssemblers + && (!moleAss.isPresent() || !Platform.isSameItem(moleAss.get(), entry.dispRep))) { + continue; + } + if (AEConfig.instance.showOnlyInterfacesWithFreeSlotsInInterfaceTerminal + && entry.numItems == entry.rows * entry.rowSize) { + continue; + } + if (onlyBrokenRecipes && entry.numBrokenRecipes == 0) { + continue; + } + // Find search terms + if (!input.isEmpty() || !output.isEmpty()) { + AppEngInternalInventory inv = entry.inv; + boolean shouldAdd = false; + + for (int i = 0; i < inv.getSizeInventory(); ++i) { + ItemStack stack = inv.getStackInSlot(i); + if (itemStackMatchesSearchTerm(stack, input, true) + && itemStackMatchesSearchTerm(stack, output, false)) { + shouldAdd = true; + entry.filteredRecipes[i] = false; + } else { + entry.filteredRecipes[i] = true; + } + } + if (!shouldAdd) { + continue; + } + } else { + Arrays.fill(entry.filteredRecipes, false); + } + visibleEntries.add(entry); + } + } + + public void addEntry(IfaceEntry entry) { + this.entries.add(entry); + entry.section = this; + this.isDirty = true; + } + + public void removeEntry(IfaceEntry entry) { + this.entries.remove(entry); + entry.section = null; + this.isDirty = true; + } + + public Iterator getVisible() { + if (isDirty) { + update(); + } + return visibleEntries.iterator(); + } + + public boolean mouseClicked(int relMouseX, int relMouseY, int btn) { + Iterator it = getVisible(); + boolean ret = false; + + while (it.hasNext() && !ret) { + ret = it.next().mouseClicked(relMouseX, relMouseY, btn); + } + + return ret; + } + } + + /** + * This class keeps track of an entry and its widgets. + */ + private class IfaceEntry { + + String dispName; + AppEngInternalInventory inv; + GuiImgButton optionsButton; + GuiFCImgButton renameButton; + + /** Nullable - icon that represents the interface */ + ItemStack selfRep; + /** Nullable - icon that represents the interface's "target" */ + ItemStack dispRep; + IfaceSection section; + long id; + int x, y, z, dim, side; + int rows, rowSize; + int guiHeight; + int dispY = -9999; + boolean online; + int numBrokenRecipes; + boolean[] brokenRecipes; + int numItems = 0; + /** Should recipe be filtered out/grayed out? */ + boolean[] filteredRecipes; + private int hoveredSlotIdx = -1; + + IfaceEntry(long id, String name, int rows, int rowSize, boolean online) { + this.id = id; + if (StatCollector.canTranslate(name)) { + this.dispName = StatCollector.translateToLocal(name); + } else { + String fallback = name + ".name"; // its whatever. save some bytes on network but looks ugly + if (StatCollector.canTranslate(fallback)) { + this.dispName = StatCollector.translateToLocal(fallback); + } else { + this.dispName = StatCollector.translateToFallback(name); + } + } + this.inv = new AppEngInternalInventory(null, rows * rowSize, 1); + this.rows = rows; + this.rowSize = rowSize; + this.online = online; + this.optionsButton = new GuiImgButton(2, 0, Settings.ACTIONS, ActionItems.HIGHLIGHT_INTERFACE); + this.optionsButton.setHalfSize(true); + this.renameButton = new GuiFCImgButton(2, 0, "EDIT", "YES"); + this.renameButton.setHalfSize(true); + this.guiHeight = 18 * rows + 1; + this.brokenRecipes = new boolean[rows * rowSize]; + this.filteredRecipes = new boolean[rows * rowSize]; + } + + IfaceEntry setLocation(int x, int y, int z, int dim, int side) { + this.x = x; + this.y = y; + this.z = z; + this.dim = dim; + this.side = side; + + return this; + } + + IfaceEntry setIcons(ItemStack selfRep, ItemStack dispRep) { + // Kotlin would make this pretty easy :( + this.selfRep = selfRep; + this.dispRep = dispRep; + + return this; + } + + public void fullItemUpdate(NBTTagList items, int newSize) { + inv = new AppEngInternalInventory(null, newSize); + rows = newSize / rowSize; + brokenRecipes = new boolean[newSize]; + numItems = 0; + + for (int i = 0; i < inv.getSizeInventory(); ++i) { + setItemInSlot(ItemStack.loadItemStackFromNBT(items.getCompoundTagAt(i)), i); + } + this.guiHeight = 18 * rows + 4; + } + + IfaceEntry setItems(NBTTagList items) { + assert items.tagCount() == inv.getSizeInventory(); + + for (int i = 0; i < items.tagCount(); ++i) { + setItemInSlot(ItemStack.loadItemStackFromNBT(items.getCompoundTagAt(i)), i); + } + return this; + } + + public void partialItemUpdate(NBTTagList items, int[] validIndices) { + for (int i = 0; i < validIndices.length; ++i) { + setItemInSlot(ItemStack.loadItemStackFromNBT(items.getCompoundTagAt(i)), validIndices[i]); + } + } + + private void setItemInSlot(ItemStack stack, int idx) { + final int oldBroke = brokenRecipes[idx] ? 1 : 0; + final int newBroke = recipeIsBroken(stack) ? 1 : 0; + final int oldHasItem = inv.getStackInSlot(idx) != null ? 1 : 0; + final int newHasItem = stack != null ? 1 : 0; + + // Update broken recipe count + numBrokenRecipes += newBroke - oldBroke; + brokenRecipes[idx] = newBroke == 1; + inv.setInventorySlotContents(idx, stack); + assert numBrokenRecipes >= 0; + // Update item count + numItems += newHasItem - oldHasItem; + assert numItems >= 0; + } + + public AppEngInternalInventory getInventory() { + return inv; + } + + public boolean mouseClicked(int mouseX, int mouseY, int btn) { + if (!section.visible || btn < 0 || btn > 2) { + return false; + } + if (mouseX >= optionsButton.xPosition && mouseX < 2 + optionsButton.width + && mouseY > Math.max(optionsButton.yPosition, IfaceSection.TITLE_HEIGHT) + && mouseY <= Math.min(optionsButton.yPosition + optionsButton.height, viewHeight)) { + optionsButton.func_146113_a(mc.getSoundHandler()); + DimensionalCoord blockPos = new DimensionalCoord(x, y, z, dim); + + if (isShiftKeyDown()) { + FluidCraft.proxy.netHandler.sendToServer( + new CPacketRenamer( + blockPos.x, + blockPos.y, + blockPos.z, + blockPos.getDimension(), + ForgeDirection.getOrientation(side))); + } else { + /* View in world */ + WorldCoord blockPos2 = new WorldCoord( + (int) mc.thePlayer.posX, + (int) mc.thePlayer.posY, + (int) mc.thePlayer.posZ); + if (mc.theWorld.provider.dimensionId != dim) { + mc.thePlayer.addChatMessage( + new ChatComponentTranslation(PlayerMessages.InterfaceInOtherDim.getName(), dim)); + } else { + BlockPosHighlighter.highlightBlock( + blockPos, + System.currentTimeMillis() + 500 * WorldCoord.getTaxicabDistance(blockPos, blockPos2)); + mc.thePlayer.addChatMessage( + new ChatComponentTranslation( + PlayerMessages.InterfaceHighlighted.getName(), + blockPos.x, + blockPos.y, + blockPos.z)); + } + mc.thePlayer.closeScreen(); + } + return true; + } + + int offsetY = mouseY - dispY; + int offsetX = mouseX - (VIEW_WIDTH - rowSize * 18) - 1; + if (offsetX >= 0 && offsetX < (rowSize * 18) + && mouseY > Math.max(dispY, IfaceSection.TITLE_HEIGHT) + && offsetY < Math.min(viewHeight - dispY, guiHeight)) { + final int col = offsetX / 18; + final int row = offsetY / 18; + final int slotIdx = row * rowSize + col; + + // send packet to server, request an update + // TODO: Client prediction. + PacketInventoryAction packet; + + if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) { + packet = new PacketInventoryAction(InventoryAction.MOVE_REGION, 0, id); + } else if (isShiftKeyDown() && (btn == 0 || btn == 1)) { + packet = new PacketInventoryAction(InventoryAction.SHIFT_CLICK, slotIdx, id); + } else if (btn == 0 || btn == 1) { + packet = new PacketInventoryAction(InventoryAction.PICKUP_OR_SET_DOWN, slotIdx, id); + } else { + packet = new PacketInventoryAction(InventoryAction.CREATIVE_DUPLICATE, slotIdx, id); + } + NetworkHandler.instance.sendToServer(packet); + return true; + } + + return false; + } + } } diff --git a/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java b/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java index f79a1c4bc..8b7b01dbe 100644 --- a/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java @@ -18,6 +18,7 @@ import net.minecraftforge.common.util.ForgeDirection; import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; import com.glodblock.github.FluidCraft; import com.glodblock.github.client.gui.container.ContainerLevelMaintainer; @@ -36,7 +37,6 @@ import com.glodblock.github.network.CPacketLevelMaintainer; import com.glodblock.github.network.CPacketLevelMaintainer.Action; import com.glodblock.github.network.CPacketLevelTerminalCommands; -import com.glodblock.github.util.Ae2ReflectClient; import com.glodblock.github.util.FCGuiColors; import com.glodblock.github.util.NameConst; import com.glodblock.github.util.Util; @@ -45,7 +45,6 @@ import appeng.api.util.DimensionalCoord; import appeng.client.gui.AEBaseGui; import appeng.client.gui.widgets.GuiTabButton; -import appeng.client.render.AppEngRenderItem; import appeng.container.AEBaseContainer; import appeng.container.slot.SlotFake; import appeng.core.sync.network.NetworkHandler; @@ -63,7 +62,6 @@ public class GuiLevelMaintainer extends AEBaseGui implements INEIGuiHandler { private final ContainerLevelMaintainer cont; private final Component[] component = new Component[TileLevelMaintainer.REQ_COUNT]; private final MouseRegionManager mouseRegions = new MouseRegionManager(this); - private final AppEngRenderItem stackSizeRenderer = Ae2ReflectClient.getStackSizeRenderer(this); private FCGuiTextField input; private int lastWorkingTick; private int refreshTick; @@ -226,13 +224,17 @@ public boolean drawSlot0(Slot slot) { } else { fake.setStackSize(0); } - stackSizeRenderer.setAeStack(fake); - stackSizeRenderer.renderItemOverlayIntoGUI( + float lastZLevel = this.zLevel; + this.zLevel = 0f; + GL11.glTranslatef(0.0f, 0.0f, 200.0f); + aeRenderItem.renderItemOverlayIntoGUI( fontRendererObj, mc.getTextureManager(), fake.getItemStack(), slot.xDisplayPosition, slot.yDisplayPosition); + GL11.glTranslatef(0.0f, 0.0f, -200.0f); + this.zLevel = lastZLevel; return false; } return true; @@ -322,7 +324,6 @@ public void switchGui() { originalBlockPos.z, originalBlockPos.getDimension(), originalBlockPos.getSide())); - // InventoryHandler.switchGui(originalGui); } @Override diff --git a/src/main/java/com/glodblock/github/client/gui/GuiLevelTerminal.java b/src/main/java/com/glodblock/github/client/gui/GuiLevelTerminal.java index 65bd5cb68..6de3f44f3 100644 --- a/src/main/java/com/glodblock/github/client/gui/GuiLevelTerminal.java +++ b/src/main/java/com/glodblock/github/client/gui/GuiLevelTerminal.java @@ -51,7 +51,6 @@ import com.glodblock.github.network.CPacketLevelTerminalCommands; import com.glodblock.github.network.CPacketLevelTerminalCommands.Action; import com.glodblock.github.network.CPacketRenamer; -import com.glodblock.github.util.Ae2ReflectClient; import com.glodblock.github.util.FCGuiColors; import com.glodblock.github.util.ModAndClassUtil; import com.glodblock.github.util.NameConst; @@ -68,7 +67,6 @@ import appeng.client.gui.widgets.GuiTabButton; import appeng.client.gui.widgets.IDropToFillTextField; import appeng.client.gui.widgets.MEGuiTextField; -import appeng.client.render.AppEngRenderItem; import appeng.client.render.BlockPosHighlighter; import appeng.container.AEBaseContainer; import appeng.container.slot.AppEngSlot; @@ -94,7 +92,6 @@ public class GuiLevelTerminal extends FCBaseMEGui implements IDropToFillTextFiel private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/level_terminal.png"); protected int offsetY; private static final int offsetX = 21; - private final AppEngRenderItem stackSizeRenderer = Ae2ReflectClient.getStackSizeRenderer(this); protected static String searchFieldOutputsText = ""; protected static String searchFieldNamesText = ""; protected static String currentMode = "OFF"; @@ -496,6 +493,7 @@ private int drawEntry(LevelTerminalEntry entry, int viewY, int titleBottom, int Tessellator.instance.startDrawingQuads(); int relY = 0; final int slotLeftMargin = (VIEW_WIDTH - entry.rowSize * 18); + float lastZLevel = aeRenderItem.zLevel; entry.dispY = viewY; /* PASS 1: BG */ @@ -571,6 +569,9 @@ private int drawEntry(LevelTerminalEntry entry, int viewY, int titleBottom, int if (stack != null) { NBTTagCompound data = stack.getTagCompound(); + ItemStack itemStack = data.hasKey(TLMTags.Stack.tagName) + ? ItemStack.loadItemStackFromNBT(data.getCompoundTag(TLMTags.Stack.tagName)) + : stack.copy(); long quantity = data.getLong(TLMTags.Quantity.tagName); long batch = data.getLong(TLMTags.Batch.tagName); State state = State.values()[data.getInteger(TLMTags.State.tagName)]; @@ -580,16 +581,16 @@ private int drawEntry(LevelTerminalEntry entry, int viewY, int titleBottom, int GL11.glTranslatef(colLeft, viewY + rowYTop + 1, ITEM_STACK_Z); GL11.glEnable(GL12.GL_RESCALE_NORMAL); RenderHelper.enableGUIStandardItemLighting(); - stackSizeRenderer.zLevel = 3.0f - 50.0f; - stackSizeRenderer.renderItemAndEffectIntoGUI(fontRendererObj, mc.getTextureManager(), stack, 0, 0); - stackSizeRenderer.zLevel = 0.0f; + aeRenderItem.zLevel = 3.0f - 50.0f; + aeRenderItem.renderItemAndEffectIntoGUI(fontRendererObj, mc.getTextureManager(), itemStack, 0, 0); + aeRenderItem.zLevel = 0.0f; GL11.glTranslatef(0.0f, 0.0f, ITEM_STACK_OVERLAY_Z - ITEM_STACK_Z); - stack.stackSize = (int) quantity; - stackSizeRenderer.renderItemOverlayIntoGUI(fontRendererObj, mc.getTextureManager(), stack, 0, 0); + itemStack.stackSize = (int) quantity; + aeRenderItem.renderItemOverlayIntoGUI(fontRendererObj, mc.getTextureManager(), itemStack, 0, 0); if (batch > 0) { - stack.stackSize = (int) batch; - stackSizeRenderer - .renderItemOverlayIntoGUI(fontRendererObj, mc.getTextureManager(), stack, 0, -11); + itemStack.stackSize = (int) batch; + aeRenderItem + .renderItemOverlayIntoGUI(fontRendererObj, mc.getTextureManager(), itemStack, 0, -11); } int color = switch (state) { case Idle -> FCGuiColors.StateIdle.getColor(); @@ -634,6 +635,7 @@ private int drawEntry(LevelTerminalEntry entry, int viewY, int titleBottom, int } } GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + aeRenderItem.zLevel = lastZLevel; return relY + 1; } diff --git a/src/main/java/com/glodblock/github/client/gui/container/ContainerInterfaceWireless.java b/src/main/java/com/glodblock/github/client/gui/container/ContainerInterfaceWireless.java index 794de8c5b..02bed9910 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/ContainerInterfaceWireless.java +++ b/src/main/java/com/glodblock/github/client/gui/container/ContainerInterfaceWireless.java @@ -1,59 +1,23 @@ package com.glodblock.github.client.gui.container; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.InventoryPlayer; -import net.minecraft.inventory.IInventory; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraftforge.common.util.ForgeDirection; import com.glodblock.github.client.gui.container.base.FCBaseContainer; import com.glodblock.github.inventory.item.IWirelessTerminal; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import appeng.api.networking.IGrid; -import appeng.api.networking.IGridNode; -import appeng.api.networking.security.IActionHost; -import appeng.api.util.DimensionalCoord; -import appeng.core.sync.network.NetworkHandler; -import appeng.core.sync.packets.PacketCompressedNBT; -import appeng.helpers.IInterfaceTerminalSupport; -import appeng.helpers.InterfaceTerminalSupportedClassProvider; +import appeng.container.implementations.ContainerInterfaceTerminal; import appeng.helpers.InventoryAction; -import appeng.items.misc.ItemEncodedPattern; -import appeng.parts.AEBasePart; -import appeng.tile.inventory.AppEngInternalInventory; -import appeng.util.InventoryAdaptor; import appeng.util.Platform; -import appeng.util.inv.AdaptorIInventory; -import appeng.util.inv.AdaptorPlayerHand; -import appeng.util.inv.ItemSlot; -import appeng.util.inv.WrapperInvSlot; public class ContainerInterfaceWireless extends FCBaseContainer { - /** - * this stuff is all server side - */ - private static long autoBase = Long.MIN_VALUE; - - private final Multimap supportedInterfaces = HashMultimap.create(); - private final Map byId = new HashMap<>(); - private IGrid grid; - private NBTTagCompound data = new NBTTagCompound(); + ContainerInterfaceTerminal delegateContainer; public ContainerInterfaceWireless(final InventoryPlayer ip, final IWirelessTerminal monitorable) { super(ip, monitorable); - if (Platform.isServer()) { - this.grid = monitorable.getActionableNode().getGrid(); - } + + delegateContainer = new ContainerInterfaceTerminal(ip, monitorable); this.bindPlayerInventory(ip, 14, 0); } @@ -65,340 +29,16 @@ public void detectAndSendChanges() { } super.detectAndSendChanges(); - if (this.grid == null) { - return; - } - - int total = 0; - boolean missing = false; - - final IActionHost host = this.getActionHost(); - if (host != null) { - final IGridNode agn = host.getActionableNode(); - if (agn != null && agn.isActive()) { - Set> supportedClasses = InterfaceTerminalSupportedClassProvider - .getSupportedClasses(); - if (supportedClasses == null) { - return; - } - for (var clz : supportedClasses) { - for (final IGridNode gn : this.grid.getMachines(clz)) { - final IInterfaceTerminalSupport interfaceTerminalSupport = (IInterfaceTerminalSupport) gn - .getMachine(); - if (!gn.isActive() || !interfaceTerminalSupport.shouldDisplay()) continue; - - final Collection t = supportedInterfaces.get(interfaceTerminalSupport); - final String name = interfaceTerminalSupport.getName(); - missing = t.isEmpty() || t.stream().anyMatch(it -> !it.unlocalizedName.equals(name)); - total += interfaceTerminalSupport.getPatternsConfigurations().length; - if (missing) break; - } - // we can stop if any is missing. The value of `total` is not important if `missing == true` - if (missing) break; - } - } - } - - if (total != this.supportedInterfaces.size() || missing) { - this.regenList(this.data); - } else { - for (final ContainerInterfaceWireless.InvTracker inv : supportedInterfaces.values()) { - for (int x = 0; x < inv.client.getSizeInventory(); x++) { - if (this.isDifferent(inv.server.getStackInSlot(inv.offset + x), inv.client.getStackInSlot(x))) { - this.addItems(this.data, inv, x, 1); - } - } - } - } - - if (!this.data.hasNoTags()) { - try { - NetworkHandler.instance - .sendTo(new PacketCompressedNBT(this.data), (EntityPlayerMP) this.getPlayerInv().player); - } catch (final IOException ignored) {} - - this.data = new NBTTagCompound(); - } + delegateContainer.detectAndSendChanges(); } @Override public void doAction(final EntityPlayerMP player, final InventoryAction action, final int slot, final long id) { - final ContainerInterfaceWireless.InvTracker inv = this.byId.get(id); - if (inv != null) { - final ItemStack is = inv.server.getStackInSlot(slot + inv.offset); - final boolean hasItemInHand = player.inventory.getItemStack() != null; - - final InventoryAdaptor playerHand = new AdaptorPlayerHand(player); - - final WrapperInvSlot slotInv = new ContainerInterfaceWireless.PatternInvSlot(inv.server); - - final IInventory theSlot = slotInv.getWrapper(slot + inv.offset); - final InventoryAdaptor interfaceSlot = new AdaptorIInventory(theSlot); - - IInventory interfaceHandler = inv.server; - boolean canInsert = true; - - switch (action) { - case PICKUP_OR_SET_DOWN -> { - if (hasItemInHand) { - for (int s = 0; s < interfaceHandler.getSizeInventory(); s++) { - if (Platform.isSameItemPrecise( - interfaceHandler.getStackInSlot(s), - player.inventory.getItemStack())) { - canInsert = false; - break; - } - } - if (canInsert) { - ItemStack inSlot = theSlot.getStackInSlot(0); - if (inSlot == null) { - player.inventory.setItemStack(interfaceSlot.addItems(player.inventory.getItemStack())); - } else { - inSlot = inSlot.copy(); - final ItemStack inHand = player.inventory.getItemStack().copy(); - - theSlot.setInventorySlotContents(0, null); - player.inventory.setItemStack(null); - - player.inventory.setItemStack(interfaceSlot.addItems(inHand.copy())); - - if (player.inventory.getItemStack() == null) { - player.inventory.setItemStack(inSlot); - } else { - player.inventory.setItemStack(inHand); - theSlot.setInventorySlotContents(0, inSlot); - } - } - } - } else { - final IInventory mySlot = slotInv.getWrapper(slot + inv.offset); - mySlot.setInventorySlotContents(0, playerHand.addItems(mySlot.getStackInSlot(0))); - } - } - case SPLIT_OR_PLACE_SINGLE -> { - if (hasItemInHand) { - for (int s = 0; s < interfaceHandler.getSizeInventory(); s++) { - if (Platform.isSameItemPrecise( - interfaceHandler.getStackInSlot(s), - player.inventory.getItemStack())) { - canInsert = false; - break; - } - } - if (canInsert) { - ItemStack extra = playerHand.removeItems(1, null, null); - if (extra != null && !interfaceSlot.containsItems()) { - extra = interfaceSlot.addItems(extra); - } - if (extra != null) { - playerHand.addItems(extra); - } - } - } else if (is != null) { - ItemStack extra = interfaceSlot.removeItems((is.stackSize + 1) / 2, null, null); - if (extra != null) { - extra = playerHand.addItems(extra); - } - if (extra != null) { - interfaceSlot.addItems(extra); - } - } - } - case SHIFT_CLICK -> { - final IInventory mySlot = slotInv.getWrapper(slot + inv.offset); - final InventoryAdaptor playerInv = InventoryAdaptor.getAdaptor(player, ForgeDirection.UNKNOWN); - mySlot.setInventorySlotContents(0, mergeToPlayerInventory(playerInv, mySlot.getStackInSlot(0))); - } - case MOVE_REGION -> { - final InventoryAdaptor playerInvAd = InventoryAdaptor.getAdaptor(player, ForgeDirection.UNKNOWN); - for (int x = 0; x < inv.client.getSizeInventory(); x++) { - inv.server.setInventorySlotContents( - x + inv.offset, - mergeToPlayerInventory(playerInvAd, inv.server.getStackInSlot(x + inv.offset))); - } - } - case CREATIVE_DUPLICATE -> { - if (player.capabilities.isCreativeMode && !hasItemInHand) { - player.inventory.setItemStack(is == null ? null : is.copy()); - } - } - default -> { - return; - } - } - - this.updateHeld(player); - } - } - - private ItemStack mergeToPlayerInventory(InventoryAdaptor playerInv, ItemStack stack) { - if (stack == null) return null; - for (ItemSlot slot : playerInv) { - if (Platform.isSameItemPrecise(slot.getItemStack(), stack)) { - if (slot.getItemStack().stackSize < slot.getItemStack().getMaxStackSize()) { - ++slot.getItemStack().stackSize; - return null; - } - } - } - return playerInv.addItems(stack); - } - - private void regenList(final NBTTagCompound data) { - this.byId.clear(); - this.supportedInterfaces.clear(); - - final IActionHost host = this.getActionHost(); - if (host != null) { - final IGridNode agn = host.getActionableNode(); - if (agn != null && agn.isActive()) { - Set> supportedClasses = InterfaceTerminalSupportedClassProvider - .getSupportedClasses(); - if (supportedClasses == null) { - return; - } - for (var clz : supportedClasses) { - for (final IGridNode gn : this.grid.getMachines(clz)) { - final IInterfaceTerminalSupport terminalSupport = (IInterfaceTerminalSupport) gn.getMachine(); - if (!gn.isActive() || !terminalSupport.shouldDisplay()) continue; - - final var configurations = terminalSupport.getPatternsConfigurations(); - - for (int i = 0; i < configurations.length; ++i) { - this.supportedInterfaces.put( - terminalSupport, - new ContainerInterfaceWireless.InvTracker(terminalSupport, configurations[i], i)); - } - } - } - } - } - - data.setBoolean("clear", true); - - for (final ContainerInterfaceWireless.InvTracker inv : this.supportedInterfaces.values()) { - this.byId.put(inv.which, inv); - this.addItems(data, inv, 0, inv.client.getSizeInventory()); - } + delegateContainer.doAction(player, action, slot, id); } @Override protected boolean isWirelessTerminal() { return true; } - - private boolean isDifferent(final ItemStack a, final ItemStack b) { - if (a == null && b == null) { - return false; - } - - if (a == null || b == null) { - return true; - } - - return !ItemStack.areItemStacksEqual(a, b); - } - - private void addItems(final NBTTagCompound data, final ContainerInterfaceWireless.InvTracker inv, final int offset, - final int length) { - final String name = '=' + Long.toString(inv.which, Character.MAX_RADIX); - final NBTTagCompound tag = data.getCompoundTag(name); - - if (tag.hasNoTags()) { - tag.setLong("sortBy", inv.sortBy); - tag.setString("un", inv.unlocalizedName); - tag.setInteger("x", inv.X); - tag.setInteger("y", inv.Y); - tag.setInteger("z", inv.Z); - tag.setInteger("dim", inv.dim); - tag.setInteger("side", inv.side.ordinal()); - tag.setInteger("size", inv.client.getSizeInventory()); - } - - for (int x = 0; x < length; x++) { - final NBTTagCompound itemNBT = new NBTTagCompound(); - - final ItemStack is = inv.server.getStackInSlot(x + offset + inv.offset); - - // "update" client side. - inv.client.setInventorySlotContents(offset + x, is == null ? null : is.copy()); - - if (is != null) { - is.writeToNBT(itemNBT); - } - - tag.setTag(Integer.toString(x + offset), itemNBT); - } - - data.setTag(name, tag); - } - - private static class InvTracker { - - private final long sortBy; - private final long which = autoBase++; - private final String unlocalizedName; - private final IInventory client; - private final IInventory server; - private final int offset; - private final int X; - private final int Y; - private final int Z; - private final int dim; - private final ForgeDirection side; - - public InvTracker(final DimensionalCoord coord, long sortValue, final IInventory patterns, - final String unlocalizedName, int offset, int size, ForgeDirection side) { - this( - coord.x, - coord.y, - coord.z, - coord.getDimension(), - sortValue, - patterns, - unlocalizedName, - offset, - size, - side); - } - - public InvTracker(int x, int y, int z, int dim, long sortValue, final IInventory patterns, - final String unlocalizedName, int offset, int size, ForgeDirection side) { - this.server = patterns; - this.client = new AppEngInternalInventory(null, size); - this.unlocalizedName = unlocalizedName; - this.sortBy = sortValue + offset << 16; - this.offset = offset; - this.X = x; - this.Y = y; - this.Z = z; - this.dim = dim; - this.side = side; - } - - public InvTracker(IInterfaceTerminalSupport terminalSupport, - IInterfaceTerminalSupport.PatternsConfiguration configuration, int index) { - this( - terminalSupport.getLocation(), - terminalSupport.getSortValue(), - terminalSupport.getPatterns(index), - terminalSupport.getName(), - configuration.offset, - configuration.size, - terminalSupport instanceof AEBasePart ? ((AEBasePart) terminalSupport).getSide() - : ForgeDirection.UNKNOWN); - } - } - - private static class PatternInvSlot extends WrapperInvSlot { - - public PatternInvSlot(final IInventory inv) { - super(inv); - } - - @Override - public boolean isItemValid(final ItemStack itemstack) { - return itemstack != null && itemstack.getItem() instanceof ItemEncodedPattern; - } - } } diff --git a/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java b/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java index f469ca6cd..030d68a5a 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java @@ -126,12 +126,21 @@ public void detectAndSendChanges() { public static ItemStack createLevelValues(ItemStack itemStack) { var data = itemStack.hasTagCompound() ? itemStack.getTagCompound() : new NBTTagCompound(); - var itemStackTag = new NBTTagCompound(); - itemStack.writeToNBT(itemStackTag); - data.setTag(TLMTags.Stack.tagName, itemStackTag); - data.setLong(TLMTags.Quantity.tagName, itemStack.stackSize); - data.setLong(TLMTags.Batch.tagName, 0); - data.setBoolean(TLMTags.Enable.tagName, false); + if (!data.hasKey(TLMTags.Stack.tagName)) { + var itemStackTag = new NBTTagCompound(); + itemStack.copy().writeToNBT(itemStackTag); + data.setTag(TLMTags.Stack.tagName, itemStackTag); + } + if (!data.hasKey(TLMTags.Quantity.tagName)) { + data.setLong(TLMTags.Quantity.tagName, itemStack.stackSize); + } + if (!data.hasKey(TLMTags.Batch.tagName)) { + data.setLong(TLMTags.Batch.tagName, 0); + } + if (!data.hasKey(TLMTags.Enable.tagName)) { + data.setBoolean(TLMTags.Enable.tagName, false); + } + data.setInteger(TLMTags.State.tagName, State.None.ordinal()); itemStack.setTagCompound(data); diff --git a/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelTerminal.java b/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelTerminal.java index 5ea23e17b..652ae95ca 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelTerminal.java +++ b/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelTerminal.java @@ -205,7 +205,7 @@ public void doAction(final EntityPlayerMP player, final InventoryAction action, } valid.add(i); } - if (valid.size() > 0) { + if (!valid.isEmpty()) { int[] validIndices = Ints.toArray(valid); NBTTagList tag = new NBTTagList(); for (int i = 0; i < valid.size(); ++i) { diff --git a/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java b/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java index 7a8b3000a..83d2ffb7c 100644 --- a/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java @@ -8,6 +8,8 @@ import net.minecraft.tileentity.TileEntity; import net.minecraftforge.common.util.ForgeDirection; +import org.jetbrains.annotations.Nullable; + import com.glodblock.github.api.registries.ILevelViewable; import com.glodblock.github.common.Config; import com.glodblock.github.common.item.ItemFluidDrop; @@ -49,6 +51,7 @@ import appeng.tile.grid.AENetworkTile; import appeng.tile.inventory.IAEAppEngInventory; import appeng.tile.inventory.InvOperation; +import appeng.util.Platform; import appeng.util.item.AEItemStack; import io.netty.buffer.ByteBuf; @@ -296,6 +299,27 @@ public void writeToNBTEvent(NBTTagCompound data) { public void readFromNBTEvent(NBTTagCompound data) { if (data.hasKey(TLMTags.RequestStacks.tagName)) { requests.requestStacks.readFromNbt(data, TLMTags.RequestStacks.tagName); + if (Platform.isServer()) { + for (int i = 0; i < REQ_COUNT; i++) { + if (requests.requestStacks.getStack(i) != null) { + ItemStack storageStack = requests.requestStacks.getStack(i).getItemStack(); + ItemStack itemStack = loadItemStackFromTag(storageStack); + ItemStack craftStack = removeRecursion(storageStack); + if (!ItemStack.areItemStacksEqual(itemStack, craftStack)) { + requests.updateStack(i, craftStack); + AELog.info( + "[TileLevelMaintainer] Replace craft stack from: " + itemStack.toString() + + ":" + + (itemStack.hasTagCompound() ? itemStack.getTagCompound() : "{no tags}") + + "; with: " + + craftStack + + ":" + + (craftStack.hasTagCompound() ? craftStack.getTagCompound() + : "{no tags}")); + } + } + } + } } else { // Migration from old data storage long[] batches = new long[REQ_COUNT]; @@ -323,6 +347,18 @@ public void readFromNBTEvent(NBTTagCompound data) { } } + private ItemStack removeRecursion(ItemStack itemStack) { + if (itemStack.hasTagCompound() && itemStack.getTagCompound().hasKey(TLMTags.Stack.tagName)) { + return removeRecursion(loadItemStackFromTag(itemStack)); + } + return itemStack; + } + + @Nullable + private static ItemStack loadItemStackFromTag(ItemStack itemStack) { + return ItemStack.loadItemStackFromNBT(itemStack.getTagCompound().getCompoundTag(TLMTags.Stack.tagName)); + } + @TileEvent(TileEventType.NETWORK_READ) public boolean readFromStream(final ByteBuf data) { final boolean oldPower = isPowered; @@ -571,8 +607,7 @@ public long getBatchSize(int idx) { public IAEItemStack getCraftItem(int idx) { IAEItemStack is = requestStacks.getStack(idx); if (is == null) return null; - ItemStack qis = ItemStack - .loadItemStackFromNBT(is.getItemStack().getTagCompound().getCompoundTag(TLMTags.Stack.tagName)); + ItemStack qis = loadItemStackFromTag(is.getItemStack()); IAEItemStack qais = AEItemStack.create(qis); qais.setStackSize(getBatchSize(idx)); diff --git a/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java b/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java index 4f13a13a5..f43c8ebf6 100644 --- a/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java +++ b/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java @@ -12,7 +12,6 @@ import com.glodblock.github.coremod.transform.ExternalStorageRegistryTransformer; import com.glodblock.github.coremod.transform.GuiCraftingTransformer; import com.glodblock.github.coremod.transform.NEITransformer; -import com.glodblock.github.coremod.transform.PacketCompressedNBTTransformer; public class FCClassTransformer implements IClassTransformer { @@ -26,7 +25,6 @@ public byte[] transform(String name, String transformedName, byte[] code) { case "appeng.client.gui.implementations.GuiCraftingCPU", "appeng.client.gui.implementations.GuiCraftConfirm", "net.p455w0rd.wirelesscraftingterminal.client.gui.GuiCraftConfirm", "appeng.client.gui.widgets.GuiCraftingTree" -> tform = GuiCraftingTransformer.INSTANCE; case "appeng.integration.modules.NEI" -> tform = NEITransformer.INSTANCE; case "appeng.core.features.registries.ExternalStorageRegistry" -> tform = ExternalStorageRegistryTransformer.INSTANCE; - case "appeng.core.sync.packets.PacketCompressedNBT" -> tform = PacketCompressedNBTTransformer.INSTANCE; default -> { return code; } diff --git a/src/main/java/com/glodblock/github/coremod/hooker/CoreModHooksClient.java b/src/main/java/com/glodblock/github/coremod/hooker/CoreModHooksClient.java index 0b86e5d1a..bf94e47fb 100644 --- a/src/main/java/com/glodblock/github/coremod/hooker/CoreModHooksClient.java +++ b/src/main/java/com/glodblock/github/coremod/hooker/CoreModHooksClient.java @@ -1,19 +1,9 @@ package com.glodblock.github.coremod.hooker; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.nbt.NBTTagCompound; - -import com.glodblock.github.client.gui.GuiInterfaceWireless; - import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @SideOnly(Side.CLIENT) public class CoreModHooksClient { - public static void clientPacketData(NBTTagCompound data) { - GuiScreen gs = Minecraft.getMinecraft().currentScreen; - if (gs instanceof GuiInterfaceWireless terminal) terminal.postUpdate(data); - } } diff --git a/src/main/java/com/glodblock/github/coremod/registries/adapters/PartLevelEmitterAdapter.java b/src/main/java/com/glodblock/github/coremod/registries/adapters/PartLevelEmitterAdapter.java index a8bdb45dd..af4779816 100644 --- a/src/main/java/com/glodblock/github/coremod/registries/adapters/PartLevelEmitterAdapter.java +++ b/src/main/java/com/glodblock/github/coremod/registries/adapters/PartLevelEmitterAdapter.java @@ -33,11 +33,13 @@ public ILevelViewable adapt(IGridHost gridHost) { }; @NotNull - static public IInventory getPatchedInventory(IInventory inventory, long value, State state) { + static public IInventory getPatchedInventory(IInventory inventory, long quantity, State state) { ItemStack itemStack = inventory.getStackInSlot(SLOT_IN).copy(); NBTTagCompound data = !itemStack.hasTagCompound() ? new NBTTagCompound() : itemStack.getTagCompound(); - data.setLong(TLMTags.Quantity.tagName, value); - data.setInteger(TLMTags.State.tagName, state.ordinal()); + if (!data.hasKey(TLMTags.Quantity.tagName) || data.getLong(TLMTags.Quantity.tagName) != quantity) + data.setLong(TLMTags.Quantity.tagName, quantity); + if (!data.hasKey(TLMTags.State.tagName) || data.getInteger(TLMTags.State.tagName) != state.ordinal()) + data.setInteger(TLMTags.State.tagName, state.ordinal()); itemStack.setTagCompound(data); inventory.setInventorySlotContents(SLOT_IN, itemStack); diff --git a/src/main/java/com/glodblock/github/coremod/transform/PacketCompressedNBTTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/PacketCompressedNBTTransformer.java deleted file mode 100644 index 0d92f73a3..000000000 --- a/src/main/java/com/glodblock/github/coremod/transform/PacketCompressedNBTTransformer.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.glodblock.github.coremod.transform; - -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -import com.glodblock.github.coremod.FCClassTransformer; - -public class PacketCompressedNBTTransformer extends FCClassTransformer.ClassMapper { - - public static final PacketCompressedNBTTransformer INSTANCE = new PacketCompressedNBTTransformer(); - - private PacketCompressedNBTTransformer() { - // NO-OP - } - - @Override - protected ClassVisitor getClassMapper(ClassVisitor downstream) { - return new TransformPacketCompressedNBT(Opcodes.ASM5, downstream); - } - - private static class TransformPacketCompressedNBT extends ClassVisitor { - - TransformPacketCompressedNBT(int api, ClassVisitor cv) { - super(api, cv); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - if (name.equals("clientPacketData")) { - return new TransformClientPacketData(api, super.visitMethod(access, name, desc, signature, exceptions)); - } - return super.visitMethod(access, name, desc, signature, exceptions); - - } - } - - private static class TransformClientPacketData extends MethodVisitor { - - TransformClientPacketData(int api, MethodVisitor mv) { - super(api, mv); - } - - @Override - public void visitInsn(int opcode) { - super.visitVarInsn(Opcodes.ALOAD, 0); - super.visitFieldInsn( - Opcodes.GETFIELD, - "appeng/core/sync/packets/PacketCompressedNBT", - "in", - "Lnet/minecraft/nbt/NBTTagCompound;"); - super.visitMethodInsn( - Opcodes.INVOKESTATIC, - "com/glodblock/github/coremod/hooker/CoreModHooksClient", - "clientPacketData", - "(Lnet/minecraft/nbt/NBTTagCompound;)V", - false); - super.visitInsn(opcode); - } - } -}