diff --git a/dependencies.gradle b/dependencies.gradle index 41bd5d8d032..ccbd52f6fd6 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -27,4 +27,6 @@ dependencies { functionalTestImplementation('com.github.GTNewHorizons:GT5-Unofficial:5.09.44.26:dev') { exclude module: "Applied-Energistics-2-Unofficial" } + + runtimeOnlyNonPublishable("com.github.GTNewHorizons:DuraDisplay:1.1.7:dev") } diff --git a/src/main/java/appeng/api/config/ActionItems.java b/src/main/java/appeng/api/config/ActionItems.java index 4fcc6730af6..bd7347a23e0 100644 --- a/src/main/java/appeng/api/config/ActionItems.java +++ b/src/main/java/appeng/api/config/ActionItems.java @@ -28,5 +28,6 @@ public enum ActionItems { TOGGLE_SHOW_FULL_INTERFACES_OFF, TOGGLE_SHOW_ONLY_INVALID_PATTERN_ON, TOGGLE_SHOW_ONLY_INVALID_PATTERN_OFF, - HIGHLIGHT_INTERFACE + HIGHLIGHT_INTERFACE, + EXTRA_OPTIONS, } diff --git a/src/main/java/appeng/api/features/IInterfaceTerminalRegistry.java b/src/main/java/appeng/api/features/IInterfaceTerminalRegistry.java new file mode 100644 index 00000000000..f950c51faf4 --- /dev/null +++ b/src/main/java/appeng/api/features/IInterfaceTerminalRegistry.java @@ -0,0 +1,14 @@ +package appeng.api.features; + +import appeng.api.util.IInterfaceViewable; + +/** + * Registry for interface terminal support. + */ +public interface IInterfaceTerminalRegistry { + + /** + * Registers a class to be considered supported in interface terminals. + */ + void register(Class clazz); +} diff --git a/src/main/java/appeng/api/features/IRegistryContainer.java b/src/main/java/appeng/api/features/IRegistryContainer.java index d1b922ee9e6..9e47f66808f 100644 --- a/src/main/java/appeng/api/features/IRegistryContainer.java +++ b/src/main/java/appeng/api/features/IRegistryContainer.java @@ -73,6 +73,8 @@ public interface IRegistryContainer { */ IInscriberRegistry inscriber(); + IInterfaceTerminalRegistry interfaceTerminal(); + /** * get access to the locatable registry */ diff --git a/src/main/java/appeng/api/networking/events/MENetworkBootingStatusChange.java b/src/main/java/appeng/api/networking/events/MENetworkBootingStatusChange.java index da45dbe896e..025ad1ce285 100644 --- a/src/main/java/appeng/api/networking/events/MENetworkBootingStatusChange.java +++ b/src/main/java/appeng/api/networking/events/MENetworkBootingStatusChange.java @@ -22,4 +22,13 @@ * Note: Most machines just need to check {@link IGridNode}.isActive() */ public class MENetworkBootingStatusChange extends MENetworkEvent { + + /** + * Indicates whether the network has changed to this state when the event is posted. + */ + public final boolean isBooting; + + public MENetworkBootingStatusChange(boolean isBooting) { + this.isBooting = isBooting; + } } diff --git a/src/main/java/appeng/api/util/IInterfaceViewable.java b/src/main/java/appeng/api/util/IInterfaceViewable.java new file mode 100644 index 00000000000..1eb536b99d9 --- /dev/null +++ b/src/main/java/appeng/api/util/IInterfaceViewable.java @@ -0,0 +1,51 @@ +package appeng.api.util; + +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; + +import appeng.api.networking.IGridHost; + +/** + * Replacement for {@code IInterfaceTerminalSupport} in API. + */ +public interface IInterfaceViewable extends IGridHost { + + DimensionalCoord getLocation(); + + /** + * Number of rows to expect. This is used with {@link #rowSize()} to determine how to render the slots. + */ + int rows(); + + /** + * Number of slots per row. + */ + int rowSize(); + + /** + * Get the patterns. If multiple rows are supported, this is assumed to be tightly packed. Use {@link #rowSize()} + * with {@link #rows()} to determine where a row starts. + */ + IInventory getPatterns(); + + String getName(); + + TileEntity getTileEntity(); + + boolean shouldDisplay(); + + /** + * Self representation + */ + default ItemStack getSelfRep() { + return null; + } + + /** + * "Target" Display representation + */ + default ItemStack getDisplayRep() { + return null; + } +} diff --git a/src/main/java/appeng/client/gui/AEBaseGui.java b/src/main/java/appeng/client/gui/AEBaseGui.java index d64cc3b99f9..ccdd02a88d6 100644 --- a/src/main/java/appeng/client/gui/AEBaseGui.java +++ b/src/main/java/appeng/client/gui/AEBaseGui.java @@ -50,6 +50,7 @@ import appeng.client.me.SlotDisconnected; import appeng.client.me.SlotME; import appeng.client.render.AppEngRenderItem; +import appeng.client.render.TranslatedRenderItem; import appeng.container.AEBaseContainer; import appeng.container.slot.AppEngCraftingSlot; import appeng.container.slot.AppEngSlot; @@ -85,7 +86,8 @@ public abstract class AEBaseGui extends GuiContainer { private final List meSlots = new LinkedList<>(); // drag y private final Set drag_click = new HashSet<>(); - private final AppEngRenderItem aeRenderItem = new AppEngRenderItem(); + public static final AppEngRenderItem aeRenderItem = new AppEngRenderItem(); + public static final TranslatedRenderItem translatedRenderItem = new TranslatedRenderItem(); private GuiScrollbar scrollBar = null; private boolean disableShiftClick = false; private Stopwatch dbl_clickTimer = Stopwatch.createStarted(); @@ -784,8 +786,10 @@ public void drawItem(final int x, final int y, final ItemStack is) { GL11.glEnable(GL11.GL_LIGHTING); GL11.glEnable(GL12.GL_RESCALE_NORMAL); GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glTranslatef(0.0f, 0.0f, 101.0f); RenderHelper.enableGUIStandardItemLighting(); itemRender.renderItemAndEffectIntoGUI(this.fontRendererObj, this.mc.renderEngine, is, x, y); + GL11.glTranslatef(0.0f, 0.0f, -101.0f); GL11.glPopAttrib(); itemRender.zLevel = 0.0F; @@ -817,10 +821,9 @@ private void drawSlot(final Slot s) { RenderItem pIR = this.setItemRender(this.aeRenderItem); try { - this.zLevel = 100.0F; - itemRender.zLevel = 100.0F; - if (!this.isPowered()) { + this.zLevel = 100.0F; + itemRender.zLevel = 100.0F; GL11.glDisable(GL11.GL_LIGHTING); drawRect( s.xDisplayPosition, @@ -829,14 +832,14 @@ private void drawSlot(final Slot s) { 16 + s.yDisplayPosition, GuiColors.ItemSlotOverlayUnpowered.getColor()); GL11.glEnable(GL11.GL_LIGHTING); - } - - this.zLevel = 0.0F; - itemRender.zLevel = 0.0F; + this.zLevel = 0.0F; + itemRender.zLevel = 0.0F; + } else { + this.aeRenderItem.setAeStack(Platform.getAEStackInSlot(s)); - this.aeRenderItem.setAeStack(Platform.getAEStackInSlot(s)); + this.drawAESlot(s); + } - this.safeDrawSlot(s); } catch (final Exception err) { AELog.warn("[AppEng] AE prevented crash while drawing slot: " + err.toString()); } @@ -940,7 +943,7 @@ private void drawSlot(final Slot s) { if (s instanceof AppEngSlot) { ((AppEngSlot) s).setDisplay(true); - this.safeDrawSlot(s); + this.drawMCSlot(s); } else { this.safeDrawSlot(s); } @@ -954,6 +957,39 @@ private void drawSlot(final Slot s) { this.safeDrawSlot(s); } + public void drawMCSlot(Slot slotIn) { + int i = slotIn.xDisplayPosition; + int j = slotIn.yDisplayPosition; + ItemStack itemstack = slotIn.getStack(); + String s = null; + + GL11.glEnable(GL11.GL_DEPTH_TEST); + translatedRenderItem.zLevel = 100.0f; + translatedRenderItem + .renderItemAndEffectIntoGUI(this.fontRendererObj, this.mc.getTextureManager(), itemstack, i, j); + translatedRenderItem.zLevel = 200.0f; + translatedRenderItem + .renderItemOverlayIntoGUI(this.fontRendererObj, this.mc.getTextureManager(), itemstack, i, j, s); + translatedRenderItem.zLevel = 0.0f; + } + + public void drawAESlot(Slot slotIn) { + int i = slotIn.xDisplayPosition; + int j = slotIn.yDisplayPosition; + ItemStack itemstack = slotIn.getStack(); + String s = null; + + this.zLevel = 100.0F; + itemRender.zLevel = 100.0F; + itemRender.renderItemAndEffectIntoGUI(this.fontRendererObj, this.mc.getTextureManager(), itemstack, i, j); + itemRender.zLevel = 0.0F; + + this.zLevel = 0.0F; + GL11.glTranslatef(0.0f, 0.0f, 200.0f); + aeRenderItem.renderItemOverlayIntoGUI(this.fontRendererObj, this.mc.getTextureManager(), itemstack, i, j, s); + GL11.glTranslatef(0.0f, 0.0f, -200.0f); + } + private RenderItem setItemRender(final RenderItem item) { if (IntegrationRegistry.INSTANCE.isEnabled(IntegrationType.NEI)) { return ((INEI) IntegrationRegistry.INSTANCE.getInstance(IntegrationType.NEI)).setItemRender(item); @@ -979,6 +1015,10 @@ public void bindTexture(final String file) { this.mc.getTextureManager().bindTexture(loc); } + public void bindTexture(final ResourceLocation loc) { + mc.getTextureManager().bindTexture(loc); + } + public void func_146977_a(final Slot s) { this.drawSlot(s); } diff --git a/src/main/java/appeng/client/gui/AEBaseMEGui.java b/src/main/java/appeng/client/gui/AEBaseMEGui.java index 67ee5d698c0..ad0dff44a7a 100644 --- a/src/main/java/appeng/client/gui/AEBaseMEGui.java +++ b/src/main/java/appeng/client/gui/AEBaseMEGui.java @@ -25,12 +25,13 @@ import appeng.core.localization.ButtonToolTips; import appeng.util.Platform; -public abstract class AEBaseMEGui extends AEBaseGui { +public abstract class AEBaseMEGui extends AEBaseGui implements IGuiTooltipHandler { public AEBaseMEGui(final Container container) { super(container); } + @Override public List handleItemTooltip(final ItemStack stack, final int mouseX, final int mouseY, final List currentToolTip) { if (stack != null) { diff --git a/src/main/java/appeng/client/gui/IGuiTooltipHandler.java b/src/main/java/appeng/client/gui/IGuiTooltipHandler.java new file mode 100644 index 00000000000..8101a867c41 --- /dev/null +++ b/src/main/java/appeng/client/gui/IGuiTooltipHandler.java @@ -0,0 +1,20 @@ +package appeng.client.gui; + +import java.util.List; + +import net.minecraft.item.ItemStack; + +/** + * Interface for handling tooltips from NEIGuiContainerManager. + */ +public interface IGuiTooltipHandler { + + default List handleItemTooltip(final ItemStack stack, final int mouseX, final int mouseY, + final List currentToolTip) { + return currentToolTip; + } + + default ItemStack getHoveredStack() { + return null; + } +} diff --git a/src/main/java/appeng/client/gui/IInterfaceTerminalPostUpdate.java b/src/main/java/appeng/client/gui/IInterfaceTerminalPostUpdate.java new file mode 100644 index 00000000000..753b2ef337e --- /dev/null +++ b/src/main/java/appeng/client/gui/IInterfaceTerminalPostUpdate.java @@ -0,0 +1,16 @@ +package appeng.client.gui; + +import java.util.List; + +import appeng.core.sync.packets.PacketInterfaceTerminalUpdate.PacketEntry; + +public interface IInterfaceTerminalPostUpdate { + + /** + * Interface to handle updates inside interface terminal + * + * @param updates List of updates + * @param statusFlags status bitflags + */ + void postUpdate(List updates, int statusFlags); +} diff --git a/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java b/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java index f8875d610df..0da04e53d83 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java +++ b/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java @@ -35,6 +35,7 @@ import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; import appeng.client.gui.AEBaseGui; +import appeng.client.gui.IGuiTooltipHandler; import appeng.client.gui.widgets.GuiCraftingCPUTable; import appeng.client.gui.widgets.GuiCraftingTree; import appeng.client.gui.widgets.GuiImgButton; @@ -63,7 +64,7 @@ import appeng.util.Platform; import appeng.util.ReadableNumberConverter; -public class GuiCraftConfirm extends AEBaseGui implements ICraftingCPUTableHolder { +public class GuiCraftConfirm extends AEBaseGui implements ICraftingCPUTableHolder, IGuiTooltipHandler { public static final int TREE_VIEW_TEXTURE_WIDTH = 238; public static final int TREE_VIEW_TEXTURE_HEIGHT = 238; diff --git a/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java b/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java index 4cc9c74db00..cc9677d27b2 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java +++ b/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java @@ -38,6 +38,7 @@ import appeng.api.util.DimensionalCoord; import appeng.api.util.WorldCoord; import appeng.client.gui.AEBaseGui; +import appeng.client.gui.IGuiTooltipHandler; import appeng.client.gui.widgets.GuiScrollbar; import appeng.client.gui.widgets.ISortSource; import appeng.client.gui.widgets.ITooltip; @@ -55,7 +56,7 @@ import appeng.util.Platform; import appeng.util.ReadableNumberConverter; -public class GuiCraftingCPU extends AEBaseGui implements ISortSource { +public class GuiCraftingCPU extends AEBaseGui implements ISortSource, IGuiTooltipHandler { private static final int GUI_HEIGHT = 184; private static final int GUI_WIDTH = 238; @@ -616,6 +617,7 @@ public Enum getSortDisplay() { return ViewItems.ALL; } + @Override public ItemStack getHoveredStack() { return hoveredStack; } diff --git a/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java b/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java index 1ee28f24126..56eec5fe361 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java +++ b/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java @@ -11,25 +11,34 @@ package appeng.client.gui.implementations; 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.NBT; +import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; - -import com.google.common.collect.HashMultimap; +import org.lwjgl.opengl.GL12; import appeng.api.AEApi; import appeng.api.config.ActionItems; @@ -38,42 +47,62 @@ import appeng.api.util.DimensionalCoord; import appeng.api.util.WorldCoord; import appeng.client.gui.AEBaseGui; +import appeng.client.gui.IGuiTooltipHandler; +import appeng.client.gui.IInterfaceTerminalPostUpdate; import appeng.client.gui.widgets.GuiImgButton; import appeng.client.gui.widgets.GuiScrollbar; 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.implementations.ContainerInterfaceTerminal; 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.PacketInterfaceTerminalUpdate; +import appeng.core.sync.packets.PacketInterfaceTerminalUpdate.PacketEntry; +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.parts.reporting.PartInterfaceTerminal; +import appeng.tile.inventory.AppEngInternalInventory; import appeng.util.Platform; - -public class GuiInterfaceTerminal extends AEBaseGui implements IDropToFillTextField { - - 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<>(); - +import appeng.util.item.AEItemStack; +import cpw.mods.fml.common.Loader; + +/** + * Interface Terminal GUI
+ * Most of the interface terminal has been rewritten. You may ask, why are you not using SLOTS? And I asked myself that + * too. I was about to go and rewrite it again until I realized that the interface terminal must support adding/removing + * entries dynamically. And finding the slots, removing them from the slot list - not exactly a huge fan, especially + * considering we are doing this first, and foremost, for performance gains. Also, the whole Z-level thing is very + * frustrating for me. Basically, I've gone rogue and taken the Thaumic Energistics route.
+ * The previous implementation did not use static slots either - instead, in generated new slots dynamically every + * render tick or when slots were clicked. Why do that, when you can write 500 extra lines of code to reimplement a + * version of slots specific to the interface terminal? + * + * @author firenoo + */ +public class GuiInterfaceTerminal extends AEBaseGui + implements IDropToFillTextField, IGuiTooltipHandler, IInterfaceTerminalPostUpdate { + + 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 InterfaceTerminalList masterList = new InterfaceTerminalList(); private final MEGuiTextField searchFieldOutputs; private final MEGuiTextField searchFieldInputs; private final MEGuiTextField searchFieldNames; @@ -81,14 +110,25 @@ public class GuiInterfaceTerminal extends AEBaseGui implements IDropToFillTextFi private final GuiImgButton guiButtonAssemblersOnly; private final GuiImgButton guiButtonBrokenRecipes; private final GuiImgButton terminalStyleBox; - private boolean refreshList = false; private boolean onlyMolecularAssemblers = false; private boolean onlyBrokenRecipes = false; - - // private final IConfigManager configSrc; - private int rows = 3; - - private static final String MOLECULAR_ASSEMBLER = "tile.appliedenergistics2.BlockMolecularAssembler"; + 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 ITEM_STACK_Z = 100.0f; + private static final float SLOT_Z = 0.5f; + private static final float ITEM_STACK_OVERLAY_Z = 200.0f; + private static final float SLOT_HOVER_Z = 310.0f; + private static final float TOOLTIP_Z = 410.0f; + private static final float STEP_Z = 10.0f; + private static final float MAGIC_RENDER_ITEM_Z = 50.0f; public GuiInterfaceTerminal(final InventoryPlayer inventoryPlayer, final PartInterfaceTerminal te) { super(new ContainerInterfaceTerminal(inventoryPlayer, te)); @@ -96,12 +136,13 @@ public GuiInterfaceTerminal(final InventoryPlayer inventoryPlayer, final PartInt 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(); } }; @@ -109,7 +150,7 @@ public void onTextChange(final String oldText) { @Override public void onTextChange(final String oldText) { - refreshList(); + masterList.markDirty(); } }; @@ -117,7 +158,7 @@ public void onTextChange(final String oldText) { @Override public void onTextChange(final String oldText) { - refreshList(); + masterList.markDirty(); } }; searchFieldNames.setFocused(true); @@ -127,46 +168,59 @@ public void onTextChange(final String oldText) { 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()); } 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); + } } @Override public void initGui() { - this.rows = calculateRowsCount(); - super.initGui(); - 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)); - 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; + guiButtonAssemblersOnly.xPosition = guiLeft - 18; + guiButtonAssemblersOnly.yPosition = guiButtonHideFull.yPosition + 18; this.setScrollBar(); this.repositionSlots(); + + buttonList.add(guiButtonAssemblersOnly); + buttonList.add(guiButtonHideFull); + buttonList.add(guiButtonBrokenRecipes); + buttonList.add(terminalStyleBox); } protected void repositionSlots() { @@ -177,13 +231,14 @@ protected void repositionSlots() { } } - 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 NEIPadding = hasNEI ? 22 /* input */ + 18 /* top panel */ : 0; - final int extraSpace = this.height - MAGIC_HEIGHT_NUMBER - NEIPadding; + 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 @@ -195,52 +250,16 @@ public void drawFG(final int offsetX, final int offsetY, final int mouseX, final GuiColors.InterfaceTerminalTitle.getColor()); fontRendererObj.drawString( GuiText.inventory.getLocal(), - GuiInterfaceTerminal.offsetX + 2, + GuiInterfaceTerminal.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 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, - GuiInterfaceTerminal.offsetX + 3, - 6 + offset, - GuiColors.InterfaceTerminalName.getColor()); - } - - offset += 18; + if (!neiPresent && tooltipStack != null) { + renderToolTip(tooltipStack, mouseX, mouseY); } } @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( @@ -253,34 +272,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); - guiButtonHashMap.clear(); - - 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 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); - guiButtonHashMap.put(guiButton, inv); - buttonList.add(guiButton); - } - - offset += 18; - } - super.drawScreen(mouseX, mouseY, btn); handleTooltip(mouseX, mouseY, searchFieldInputs); @@ -294,43 +285,23 @@ protected void mouseClicked(final int xCoord, final int yCoord, final int btn) { searchFieldOutputs.mouseClicked(xCoord, yCoord, btn); searchFieldNames.mouseClicked(xCoord, yCoord, btn); + 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)) { - DimensionalCoord blockPos = blockPosHashMap.get(guiButtonHashMap.get(btn)); - 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(); + masterList.markDirty(); } else if (btn instanceof GuiImgButton iBtn) { if (iBtn.getSetting() != Settings.ACTIONS) { final Enum cv = iBtn.getCurrentValue(); @@ -339,8 +310,7 @@ protected void actionPerformed(final GuiButton btn) { if (btn == this.terminalStyleBox) { AEConfig.instance.settings.putSetting(iBtn.getSetting(), next); - - this.reinitialize(); + initGui(); } iBtn.set(next); @@ -348,41 +318,392 @@ protected void actionPerformed(final GuiButton 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 + InterfaceSection.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(); + } + + /** + * 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()) { + InterfaceSection 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; + } + + section.visible = true; + int advanceY = drawSection(section, viewY, relMouseX, relMouseY); + viewY += advanceY; + entryIdx++; + } + } + + /** + * 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(InterfaceSection 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 + */ + bindTexture(BACKGROUND); + GL11.glTranslatef(0.0f, 0.0f, ITEM_STACK_OVERLAY_Z + ITEM_STACK_Z + STEP_Z); + if (sectionBottom > 0 && sectionBottom < InterfaceSection.TITLE_HEIGHT) { + /* Transition draw */ + title = sectionBottom; + } else if (viewY < 0) { + /* Hidden title draw */ + title = 0; + } else { + /* Normal title draw */ + title = 0; + } + GL11.glTranslatef(0.0f, 0.0f, -(ITEM_STACK_OVERLAY_Z + ITEM_STACK_Z + STEP_Z)); + 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 + InterfaceSection.TITLE_HEIGHT + renderY, + title, + relMouseX, + relMouseY); + } else { + InterfaceTerminalEntry entry = visible.next(); + entry.dispY = -9999; + entry.optionsButton.yPosition = -1; + } + } + /* + * Render title + */ + bindTexture(BACKGROUND); + GL11.glTranslatef(0.0f, 0.0f, ITEM_STACK_OVERLAY_Z + ITEM_STACK_Z + STEP_Z); + if (sectionBottom > 0 && sectionBottom < InterfaceSection.TITLE_HEIGHT) { + /* Transition draw */ + drawTexturedModalRect( + 0, + 0, + VIEW_LEFT, + HEADER_HEIGHT + InterfaceSection.TITLE_HEIGHT - sectionBottom, + VIEW_WIDTH, + sectionBottom); + fontRendererObj.drawString(section.name, 2, sectionBottom - InterfaceSection.TITLE_HEIGHT + 2, fontColor); + } else if (viewY < 0) { + /* Hidden title draw */ + GL11.glDisable(GL11.GL_DEPTH_TEST); + GL11.glTranslatef(0.0f, 0.0f, 100f); + drawTexturedModalRect(0, 0, VIEW_LEFT, HEADER_HEIGHT, VIEW_WIDTH, InterfaceSection.TITLE_HEIGHT); + fontRendererObj.drawString(section.name, 2, 2, fontColor); + GL11.glEnable(GL11.GL_DEPTH_TEST); + } else { + /* Normal title draw */ + drawTexturedModalRect(0, viewY, VIEW_LEFT, HEADER_HEIGHT, VIEW_WIDTH, InterfaceSection.TITLE_HEIGHT); + fontRendererObj.drawString(section.name, 2, viewY + 2, fontColor); + } + GL11.glTranslatef(0.0f, 0.0f, -(ITEM_STACK_OVERLAY_Z + ITEM_STACK_Z + STEP_Z)); - int offset = 51; - final int ex = this.getScrollBar().getCurrentScroll(); + return InterfaceSection.TITLE_HEIGHT + renderY; + } - for (int x = 0; x < this.rows; x++) { - this.drawTexturedModalRect(offsetX, offsetY + 53 + x * 18, 0, 52, this.xSize, 18); + /** + * Draws the entry. In practice, it just draws the slots + items. + * + * @param viewY the gui coordinate z + */ + private int drawEntry(InterfaceTerminalEntry 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.optionsButton.drawButton(mc, relMouseX, relMouseY); + if (entry.optionsButton.getMouseIn() + && relMouseY >= Math.max(InterfaceSection.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; + } + /* 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, InterfaceSection.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, ITEM_STACK_Z); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + RenderHelper.enableGUIStandardItemLighting(); + translatedRenderItem.zLevel = ITEM_STACK_Z - MAGIC_RENDER_ITEM_Z; + translatedRenderItem + .renderItemAndEffectIntoGUI(fontRendererObj, mc.getTextureManager(), toRender, 0, 0); + GL11.glTranslatef(0.0f, 0.0f, ITEM_STACK_OVERLAY_Z - ITEM_STACK_Z); + aeRenderItem.setAeStack(AEItemStack.create(toRender)); + 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 - ITEM_STACK_OVERLAY_Z); + drawRect(0, 0, 16, 16, GuiColors.ItemSlotOverlayInvalid.getColor()); + } else if (entry.filteredRecipes[slotIdx]) { + GL11.glTranslatef(0.0f, 0.0f, ITEM_STACK_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, ITEM_STACK_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; + } - for (int x = 0; x < this.rows && ex + x < this.lines.size(); x++) { + @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 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. + */ + @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 : 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 = 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 @@ -393,18 +714,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); @@ -425,135 +770,70 @@ 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"); - blockPosHashMap.put(current, new DimensionalCoord(X, Y, Z, dim)); - - 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) {} - } + @Override + public void postUpdate(List updates, int statusFlags) { + if ((statusFlags & PacketInterfaceTerminalUpdate.CLEAR_ALL_BIT) + == PacketInterfaceTerminalUpdate.CLEAR_ALL_BIT) { + /* Should clear all client entries. */ + this.masterList.list.clear(); } + /* Should indicate disconnected, so the terminal turns dark. */ + this.online = (statusFlags & PacketInterfaceTerminalUpdate.DISCONNECT_BIT) + != PacketInterfaceTerminalUpdate.DISCONNECT_BIT; - if (this.refreshList) { - this.refreshList = false; - // invalid caches on refresh - this.cachedSearches.clear(); - this.refreshList(); + for (PacketInterfaceTerminalUpdate.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(PacketInterfaceTerminalUpdate.PacketEntry cmd) { + long id = cmd.entryId; + if (cmd instanceof PacketInterfaceTerminalUpdate.PacketAdd addCmd) { + InterfaceTerminalEntry entry = new InterfaceTerminalEntry( + id, + addCmd.name, + addCmd.rows, + addCmd.rowSize, + addCmd.online).setLocation(addCmd.x, addCmd.y, addCmd.z, addCmd.dim) + .setIcons(addCmd.selfRep, addCmd.dispRep).setItems(addCmd.items); + masterList.addEntry(entry); + } else if (cmd instanceof PacketInterfaceTerminalUpdate.PacketRemove) { + masterList.removeEntry(id); + } else if (cmd instanceof PacketInterfaceTerminalUpdate.PacketOverwrite owCmd) { + InterfaceTerminalEntry 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); } } - - 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); + masterList.isDirty = true; + } else if (cmd instanceof PacketInterfaceTerminalUpdate.PacketRename renameCmd) { + InterfaceTerminalEntry entry = masterList.list.get(id); + + 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 static boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final String searchTerm, boolean in) { if (itemStack == null) { return false; } @@ -564,7 +844,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", NBT.TAG_COMPOUND); final boolean containsInvalidDisplayName = GuiText.UnknownItem.getLocal().toLowerCase().contains(searchTerm); for (int i = 0; i < tags.tagCount(); i++) { @@ -586,7 +866,6 @@ private boolean itemStackMatchesSearchTerm(final ItemStack itemStack, final Stri } private boolean recipeIsBroken(final ItemStack itemStack) { - if (itemStack == null) { return false; } @@ -609,61 +888,455 @@ private boolean recipeIsBroken(final ItemStack itemStack) { } } + private int getMaxViewHeight() { + return AEConfig.instance.getConfigManager().getSetting(Settings.TERMINAL_STYLE) == TerminalStyle.SMALL + ? AEConfig.instance.InterfaceTerminalSmallSize * 18 + : Integer.MAX_VALUE; + } + + 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)) { + searchFieldOutputs.setText(displayName); + } else if (searchFieldNames.isMouseIn(mousex, mousey)) { + searchFieldNames.setText(displayName); + } + } + /** - * 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 + * Tracks the list of entries. */ - private Set getCacheForSearchTerm(final String searchTerm) { - if (!this.cachedSearches.containsKey(searchTerm)) { - this.cachedSearches.put(searchTerm, new HashSet<>()); + private class InterfaceTerminalList { + + 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 InterfaceTerminalEntry hoveredEntry; + + InterfaceTerminalList() { + this.isDirty = true; } - final Set cache = this.cachedSearches.get(searchTerm); + /** + * Performs a full update. + */ + private void update() { + height = 0; + visibleSections.clear(); + + for (InterfaceSection section : sections.values()) { + String query = GuiInterfaceTerminal.this.searchFieldNames.getText(); + if (!query.isEmpty() && !section.name.toLowerCase().contains(query.toLowerCase())) { + continue; + } - if (cache.isEmpty() && searchTerm.length() > 1) { - cache.addAll(this.getCacheForSearchTerm(searchTerm.substring(0, searchTerm.length() - 1))); - return cache; + section.isDirty = true; + if (section.getVisible().hasNext()) { + height += section.getHeight(); + visibleSections.add(section); + } + } + isDirty = false; } - return cache; - } + public void markDirty() { + this.isDirty = true; + setScrollBar(); + } - private int getMaxRows() { - return AEConfig.instance.getConfigManager().getSetting(Settings.TERMINAL_STYLE) == TerminalStyle.SMALL - ? AEConfig.instance.InterfaceTerminalSmallSize - : Integer.MAX_VALUE; - } + public int getHeight() { + if (isDirty) { + update(); + } + return height; + } - private ClientDCInternalInv getById(final long id, final long sortBy, final String unlocalizedName, - final int sizeInit) { - ClientDCInternalInv o = this.byId.get(id); + /** + * 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 (InterfaceSection section : sections) { + if (y > viewY) { + result = true; + scrollbar.setCurrentScroll(y); + break; + } + y += section.getHeight(); + } + } + return result; + } + + public void addEntry(InterfaceTerminalEntry entry) { + InterfaceSection section = sections.get(entry.dispName); + + if (section == null) { + section = new InterfaceSection(entry.dispName); + sections.put(entry.dispName, section); + } + section.addEntry(entry); + list.put(entry.id, entry); + isDirty = true; + } + + public void removeEntry(long id) { + InterfaceTerminalEntry entry = list.remove(id); - if (o == null) { - this.byId.put(id, o = new ClientDCInternalInv(sizeInit, id, sortBy, unlocalizedName)); - this.refreshList = true; + if (entry != null) { + entry.section.removeEntry(entry); + } } - return o; + 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 (InterfaceSection section : getVisibleSections()) { + if (section.mouseClicked(relMouseX, relMouseY, btn)) { + return true; + } + } + return false; + } } - public boolean isOverTextField(final int mousex, final int mousey) { - return searchFieldInputs.isMouseIn(mousex, mousey) || searchFieldOutputs.isMouseIn(mousex, mousey) - || searchFieldNames.isMouseIn(mousex, mousey); + /** + * A section holds all the interface entries with the same name. + */ + private class InterfaceSection { + + 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; + + InterfaceSection(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 (InterfaceTerminalEntry entry : visibleEntries) { + height += entry.guiHeight; + } + } + isDirty = false; + } + + public void refreshVisible() { + visibleEntries.clear(); + String input = GuiInterfaceTerminal.this.searchFieldInputs.getText().toLowerCase(); + String output = GuiInterfaceTerminal.this.searchFieldOutputs.getText().toLowerCase(); + + for (InterfaceTerminalEntry 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(InterfaceTerminalEntry entry) { + this.entries.add(entry); + entry.section = this; + this.isDirty = true; + } + + public void removeEntry(InterfaceTerminalEntry 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; + } } - public void setTextFieldValue(final String displayName, final int mousex, final int mousey, final ItemStack stack) { + /** + * This class keeps track of an entry and its widgets. + */ + private class InterfaceTerminalEntry { + + String dispName; + AppEngInternalInventory inv; + GuiImgButton optionsButton; + /** Nullable - icon that represents the interface */ + ItemStack selfRep; + /** Nullable - icon that represents the interface's "target" */ + ItemStack dispRep; + InterfaceSection section; + long id; + int x, y, z, dim; + 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; + + InterfaceTerminalEntry(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.guiHeight = 18 * rows + 1; + this.brokenRecipes = new boolean[rows * rowSize]; + this.filteredRecipes = new boolean[rows * rowSize]; + } - if (searchFieldInputs.isMouseIn(mousex, mousey)) { - searchFieldInputs.setText(displayName); - } else if (searchFieldOutputs.isMouseIn(mousex, mousey)) { - searchFieldOutputs.setText(displayName); - } else if (searchFieldNames.isMouseIn(mousex, mousey)) { - searchFieldNames.setText(displayName); + InterfaceTerminalEntry setLocation(int x, int y, int z, int dim) { + this.x = x; + this.y = y; + this.z = z; + this.dim = dim; + + return this; + } + + InterfaceTerminalEntry 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; + } + + InterfaceTerminalEntry 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, InterfaceSection.TITLE_HEIGHT) + && mouseY <= Math.min(optionsButton.yPosition + optionsButton.height, viewHeight)) { + optionsButton.func_146113_a(mc.getSoundHandler()); + DimensionalCoord blockPos = new DimensionalCoord(x, y, z, dim); + /* 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, InterfaceSection.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/appeng/client/gui/widgets/GuiImgButton.java b/src/main/java/appeng/client/gui/widgets/GuiImgButton.java index f254e1bd8f8..bf28fc67d26 100644 --- a/src/main/java/appeng/client/gui/widgets/GuiImgButton.java +++ b/src/main/java/appeng/client/gui/widgets/GuiImgButton.java @@ -635,6 +635,7 @@ public GuiImgButton(final int x, final int y, final Enum idx, final Enum val) { CraftingMode.IGNORE_MISSING, ButtonToolTips.CraftingModeIgnoreMissing, ButtonToolTips.CraftingModeIgnoreMissingDesc); + this.registerApp(16 * 6 + 5, Settings.ACTIONS, ActionItems.EXTRA_OPTIONS, ButtonToolTips.ExtraOptions, ""); } } @@ -798,6 +799,10 @@ public boolean isVisible() { return this.visible; } + public boolean getMouseIn() { + return this.field_146123_n; + } + public void set(final Enum e) { if (this.currentValue != e) { this.currentValue = e; diff --git a/src/main/java/appeng/client/gui/widgets/GuiScrollbar.java b/src/main/java/appeng/client/gui/widgets/GuiScrollbar.java index 3d85e05841e..a696a58e9ac 100644 --- a/src/main/java/appeng/client/gui/widgets/GuiScrollbar.java +++ b/src/main/java/appeng/client/gui/widgets/GuiScrollbar.java @@ -30,6 +30,7 @@ public class GuiScrollbar implements IScrollSource { private int maxScroll = 0; private int minScroll = 0; private int currentScroll = 0; + private boolean visible = true; public void setTexture(final String base, final String file, final int shiftX, final int shiftY) { txtBase = base; @@ -39,6 +40,9 @@ public void setTexture(final String base, final String file, final int shiftX, f } public void draw(final AEBaseGui g) { + if (!visible) { + return; + } g.bindTexture(txtBase, txtFile); GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); @@ -50,6 +54,10 @@ public void draw(final AEBaseGui g) { } } + public void setVisible(boolean visible) { + this.visible = visible; + } + private int getRange() { return this.maxScroll - this.minScroll; } diff --git a/src/main/java/appeng/client/render/AppEngRenderItem.java b/src/main/java/appeng/client/render/AppEngRenderItem.java index 7db9653b0c0..c5623a7cc28 100644 --- a/src/main/java/appeng/client/render/AppEngRenderItem.java +++ b/src/main/java/appeng/client/render/AppEngRenderItem.java @@ -79,7 +79,6 @@ public void renderItemOverlayIntoGUI(final FontRenderer fontRenderer, final Text final int k = (int) Math.round(255.0D - health * 255.0D); GL11.glDisable(GL11.GL_LIGHTING); - GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glDisable(GL11.GL_ALPHA_TEST); GL11.glDisable(GL11.GL_BLEND); @@ -95,7 +94,6 @@ public void renderItemOverlayIntoGUI(final FontRenderer fontRenderer, final Text GL11.glEnable(GL11.GL_ALPHA_TEST); GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_LIGHTING); - GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); } @@ -105,7 +103,6 @@ public void renderItemOverlayIntoGUI(final FontRenderer fontRenderer, final Text : GuiText.SmallFontCraft.getLocal(); GL11.glDisable(GL11.GL_LIGHTING); - GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glPushMatrix(); GL11.glScaled(scaleFactor, scaleFactor, scaleFactor); @@ -117,7 +114,6 @@ public void renderItemOverlayIntoGUI(final FontRenderer fontRenderer, final Text GL11.glPopMatrix(); GL11.glEnable(GL11.GL_LIGHTING); - GL11.glEnable(GL11.GL_DEPTH_TEST); } final long amount = this.aeStack != null ? this.aeStack.getStackSize() : is.stackSize; @@ -126,7 +122,6 @@ public void renderItemOverlayIntoGUI(final FontRenderer fontRenderer, final Text final String stackSize = this.getToBeRenderedStackSize(amount); GL11.glDisable(GL11.GL_LIGHTING); - GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glPushMatrix(); GL11.glScaled(scaleFactor, scaleFactor, scaleFactor); @@ -138,24 +133,12 @@ public void renderItemOverlayIntoGUI(final FontRenderer fontRenderer, final Text GL11.glPopMatrix(); GL11.glEnable(GL11.GL_LIGHTING); - GL11.glEnable(GL11.GL_DEPTH_TEST); } fontRenderer.setUnicodeFlag(unicodeFlag); } } - private void renderQuad(final Tessellator par1Tessellator, final int par2, final int par3, final int par4, - final int par5, final int par6) { - par1Tessellator.startDrawingQuads(); - par1Tessellator.setColorOpaque_I(par6); - par1Tessellator.addVertex(par2, par3, 0.0D); - par1Tessellator.addVertex(par2, par3 + par5, 0.0D); - par1Tessellator.addVertex(par2 + par4, par3 + par5, 0.0D); - par1Tessellator.addVertex(par2 + par4, par3, 0.0D); - par1Tessellator.draw(); - } - private String getToBeRenderedStackSize(final long originalSize) { if (AEConfig.instance.useTerminalUseLargeFont()) { return SLIM_CONVERTER.toSlimReadableForm(originalSize); diff --git a/src/main/java/appeng/client/render/TranslatedRenderItem.java b/src/main/java/appeng/client/render/TranslatedRenderItem.java new file mode 100644 index 00000000000..122e3c9747f --- /dev/null +++ b/src/main/java/appeng/client/render/TranslatedRenderItem.java @@ -0,0 +1,74 @@ +package appeng.client.render; + +import static appeng.client.render.AppEngRenderItem.POST_HOOKS; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.item.ItemStack; + +import org.lwjgl.opengl.GL11; + +import appeng.api.storage.IItemDisplayRegistry.ItemRenderHook; + +/** + * Uses translations instead of depth test to perform rendering. + */ +public class TranslatedRenderItem extends RenderItem { + + @Override + public void renderItemOverlayIntoGUI(FontRenderer font, TextureManager texManager, ItemStack stack, int x, int y, + String customText) { + if (stack != null) { + boolean skip = false; + boolean showDurabilitybar = true; + boolean showStackSize = true; + boolean showCraftLabelText = true; + for (ItemRenderHook hook : POST_HOOKS) { + skip |= hook.renderOverlay(font, texManager, stack, x, y); + showDurabilitybar &= hook.showDurability(stack); + showStackSize &= hook.showStackSize(stack); + showCraftLabelText &= hook.showCraftLabelText(stack); + } + if (skip) { + return; + } + GL11.glPushMatrix(); + if ((showStackSize && stack.stackSize > 1) || (showCraftLabelText && customText != null)) { + GL11.glTranslatef(0.0f, 0.0f, this.zLevel); + String s1 = customText == null ? String.valueOf(stack.stackSize) : customText; + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_BLEND); + font.drawStringWithShadow(s1, x + 19 - 2 - font.getStringWidth(s1), y + 6 + 3, 16777215); + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glTranslatef(0.0f, 0.0f, -this.zLevel); + } + + if (showDurabilitybar && stack.getItem().showDurabilityBar(stack)) { + GL11.glTranslatef(0.0f, 0.0f, this.zLevel - 1f); + double health = stack.getItem().getDurabilityForDisplay(stack); + int j1 = (int) Math.round(13.0D - health * 13.0D); + int k = (int) Math.round(255.0D - health * 255.0D); + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_ALPHA_TEST); + GL11.glDisable(GL11.GL_BLEND); + Tessellator tessellator = Tessellator.instance; + int l = 255 - k << 16 | k << 8; + int i1 = (255 - k) / 4 << 16 | 16128; + this.renderQuad(tessellator, x + 2, y + 13, 13, 2, 0); + this.renderQuad(tessellator, x + 2, y + 13, 12, 1, i1); + this.renderQuad(tessellator, x + 2, y + 13, j1, 1, l); + // GL11.glEnable(GL11.GL_BLEND); // Forge: Disable Bled because it screws with a lot of things down the + // line. + GL11.glEnable(GL11.GL_ALPHA_TEST); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + GL11.glTranslatef(0.0f, 0.0f, -(this.zLevel - 1f)); + } + GL11.glPopMatrix(); + } + } +} diff --git a/src/main/java/appeng/container/implementations/ContainerInterfaceTerminal.java b/src/main/java/appeng/container/implementations/ContainerInterfaceTerminal.java index 7cfb9467501..339b372f229 100644 --- a/src/main/java/appeng/container/implementations/ContainerInterfaceTerminal.java +++ b/src/main/java/appeng/container/implementations/ContainerInterfaceTerminal.java @@ -1,3 +1,4 @@ + /* * This file is part of Applied Energistics 2. Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. Applied * Energistics 2 is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General @@ -10,62 +11,70 @@ package appeng.container.implementations; -import java.io.IOException; -import java.util.Collection; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +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.minecraft.nbt.NBTTagList; import net.minecraftforge.common.util.ForgeDirection; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; +import com.google.common.primitives.Ints; import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; import appeng.api.networking.security.IActionHost; import appeng.api.util.DimensionalCoord; +import appeng.api.util.IInterfaceViewable; import appeng.container.AEBaseContainer; +import appeng.core.features.registries.InterfaceTerminalRegistry; import appeng.core.sync.network.NetworkHandler; -import appeng.core.sync.packets.PacketCompressedNBT; -import appeng.helpers.IInterfaceTerminalSupport; -import appeng.helpers.IInterfaceTerminalSupport.PatternsConfiguration; -import appeng.helpers.InterfaceTerminalSupportedClassProvider; +import appeng.core.sync.packets.PacketInterfaceTerminalUpdate; import appeng.helpers.InventoryAction; import appeng.items.misc.ItemEncodedPattern; +import appeng.parts.AEBasePart; import appeng.parts.reporting.PartInterfaceTerminal; -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 final class ContainerInterfaceTerminal extends AEBaseContainer { - /** - * this stuff is all server side.. - */ - private static long autoBase = Long.MIN_VALUE; + private int nextId = 0; - private final Multimap supportedInterfaces = HashMultimap.create(); - private final Map byId = new HashMap<>(); + private final Map tracked = new HashMap<>(); + private final Map trackedById = new HashMap<>(); + private PacketInterfaceTerminalUpdate dirty; + private boolean isDirty; private IGrid grid; - private NBTTagCompound data = new NBTTagCompound(); + private IActionHost anchor; + private boolean wasOff; - public ContainerInterfaceTerminal(final InventoryPlayer ip, final PartInterfaceTerminal anchor) { + public ContainerInterfaceTerminal(final InventoryPlayer ip, final IActionHost anchor) { super(ip, anchor); - + assert anchor != null; + this.anchor = anchor; if (Platform.isServer()) { this.grid = anchor.getActionableNode().getGrid(); + dirty = this.updateList(); + if (dirty != null) { + this.isDirty = true; + } else { + dirty = new PacketInterfaceTerminalUpdate(); + } } - - this.bindPlayerInventory(ip, 14, 0); + this.bindPlayerInventory(ip, 14, 3); } @Override @@ -80,154 +89,128 @@ public void detectAndSendChanges() { 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()) { - for (var clz : InterfaceTerminalSupportedClassProvider.getSupportedClasses()) { - 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; - } - } - } + final IGridNode agn = this.anchor.getActionableNode(); - if (total != this.supportedInterfaces.size() || missing) { - this.regenList(this.data); - } else { - for (final 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 (!agn.isActive()) { + /* + * Should turn off the terminal. However, there's no need to remove all the entries from the client. + * Continue tracking on the server just in case the system comes back online. Just don't send any new + * updates. This prevents DoSing the player if their network is flickering. + */ + if (!this.wasOff) { + PacketInterfaceTerminalUpdate update = new PacketInterfaceTerminalUpdate(); + + update.setDisconnect(); + this.wasOff = true; + NetworkHandler.instance.sendTo(update, (EntityPlayerMP) this.getPlayerInv().player); } + return; } + this.wasOff = false; - if (!this.data.hasNoTags()) { - try { - NetworkHandler.instance - .sendTo(new PacketCompressedNBT(this.data), (EntityPlayerMP) this.getPlayerInv().player); - } catch (final IOException e) { - // :P + if (anchor instanceof PartInterfaceTerminal terminal && terminal.needsUpdate()) { + PacketInterfaceTerminalUpdate update = this.updateList(); + if (update != null) { + update.encode(); + NetworkHandler.instance.sendTo(update, (EntityPlayerMP) this.getPlayerInv().player); } - - this.data = new NBTTagCompound(); + } else if (isDirty) { + this.dirty.encode(); + NetworkHandler.instance.sendTo(this.dirty, (EntityPlayerMP) this.getPlayerInv().player); + this.dirty = new PacketInterfaceTerminalUpdate(); + this.isDirty = false; } } @Override public void doAction(final EntityPlayerMP player, final InventoryAction action, final int slot, final long id) { - final InvTracker inv = this.byId.get(id); + final InvTracker inv = this.trackedById.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 PatternInvSlot(inv.server); + final ItemStack handStack = player.inventory.getItemStack(); - final IInventory theSlot = slotInv.getWrapper(slot + inv.offset); - final InventoryAdaptor interfaceSlot = new AdaptorIInventory(theSlot); + if (handStack != null && !(handStack.getItem() instanceof ItemEncodedPattern)) { + // Why even bother if we're not dealing with an encoded pattern in hand + return; + } - IInventory interfaceHandler = inv.server; - boolean canInsert = true; + final ItemStack slotStack = inv.patterns.getStackInSlot(slot); + final InventoryAdaptor playerHand = new AdaptorPlayerHand(player); switch (action) { + /* Set down/pickup. This is the same as SPLIT_OR_PLACE_SINGLE as our max stack sizes are 1 in slots. */ 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 (handStack != null) { + for (int s = 0; s < inv.patterns.getSizeInventory(); s++) { + /* Is there a duplicate pattern here? */ + if (Platform.isSameItemPrecise(inv.patterns.getStackInSlot(s), handStack)) { + /* We're done here - dupe found. */ + return; } } - 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 (slotStack == null) { + /* Insert to container, if valid */ + if (handStack == null) { + /* Nothing happens */ + return; } - if (extra != null) { - interfaceSlot.addItems(extra); + inv.patterns.setInventorySlotContents(slot, playerHand.removeItems(1, null, null)); + } else { + /* Exchange? */ + if (handStack != null && handStack.stackSize > 1) { + /* Exchange is impossible, abort */ + return; } + inv.patterns.setInventorySlotContents(slot, playerHand.removeItems(1, null, null)); + playerHand.addItems(slotStack.copy()); } + syncIfaceSlot(inv, id, slot, inv.patterns.getStackInSlot(slot)); } + /* Shift click from slot -> player. Player -> slot is not supported. */ 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))); + InventoryAdaptor playerInv = InventoryAdaptor.getAdaptor(player.inventory, ForgeDirection.UNKNOWN); + ItemStack leftOver = mergeToPlayerInventory(playerInv, slotStack); + + if (leftOver == null) { + inv.patterns.setInventorySlotContents(slot, null); + syncIfaceSlot(inv, id, slot, null); + } } + /* Move all blank patterns -> player */ 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))); + final InventoryAdaptor playerInv = InventoryAdaptor.getAdaptor(player, ForgeDirection.UNKNOWN); + List valid = new ArrayList<>(); + + for (int i = 0; i < inv.patterns.getSizeInventory(); i++) { + ItemStack toExtract = inv.patterns.getStackInSlot(i); + + if (toExtract == null) { + continue; + } + + ItemStack leftOver = mergeToPlayerInventory(playerInv, toExtract); + + if (leftOver != null) { + break; + } else { + inv.patterns.setInventorySlotContents(i, null); + } + valid.add(i); + } + if (valid.size() > 0) { + int[] validIndices = Ints.toArray(valid); + NBTTagList tag = new NBTTagList(); + for (int i = 0; i < valid.size(); ++i) { + tag.appendTag(new NBTTagCompound()); + } + dirty.addOverwriteEntry(id).setItems(validIndices, tag); + isDirty = true; } } case CREATIVE_DUPLICATE -> { - if (player.capabilities.isCreativeMode && !hasItemInHand) { - player.inventory.setItemStack(is == null ? null : is.copy()); + if (player.capabilities.isCreativeMode) { + playerHand.addItems(handStack); } } default -> { @@ -239,6 +222,32 @@ public void doAction(final EntityPlayerMP player, final InventoryAction action, } } + /** + * Since we are not using "slots" like MC and instead taking the Thaumic Energistics route, this is used to + * eventually send a packet to the client to indicate that a slot has changed.
+ * Why weren't slots used? Because at first I was more concerned about performance issues, and by the time I + * realized I was perhaps making a huge mistake in not using the notion of a "slot", I had already finished + * implementing my own version of a "slot". Because of this everything in this GUI is basically hand written. Also, + * the previous implementation was seemingly allocating slots.
+ * // rant over + */ + private void syncIfaceSlot(InvTracker inv, long id, int slot, ItemStack stack) { + int[] validIndices = { slot }; + NBTTagList list = new NBTTagList(); + NBTTagCompound item = new NBTTagCompound(); + + if (stack != null) { + stack.writeToNBT(item); + } + list.appendTag(item); + inv.updateNBT(); + this.dirty.addOverwriteEntry(id).setItems(validIndices, list); + this.isDirty = true; + } + + /** + * Merge from slot -> player inv. Returns the items not added. + */ private ItemStack mergeToPlayerInventory(InventoryAdaptor playerInv, ItemStack stack) { if (stack == null) return null; for (ItemSlot slot : playerInv) { @@ -252,36 +261,76 @@ private ItemStack mergeToPlayerInventory(InventoryAdaptor playerInv, ItemStack s 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()) { - for (var clz : InterfaceTerminalSupportedClassProvider.getSupportedClasses()) { - 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(); + /** + * Finds out whether any updates are needed, and if so, incrementally updates the list. + */ + private PacketInterfaceTerminalUpdate updateList() { + PacketInterfaceTerminalUpdate update = null; + var supported = InterfaceTerminalRegistry.instance().getSupportedClasses(); + Set visited = new HashSet<>(); + + for (Class c : supported) { + for (IGridNode node : grid.getMachines(c)) { + IInterfaceViewable machine = (IInterfaceViewable) node.getMachine(); + /* First check if we are already tracking this node */ + if (tracked.containsKey(machine)) { + /* Check for updates */ + InvTracker known = tracked.get(machine); + + /* Name changed? */ + String name = machine.getName(); + + if (!Objects.equals(known.name, name)) { + if (update == null) update = new PacketInterfaceTerminalUpdate(); + update.addRenamedEntry(known.id, name); + known.name = name; + } - for (int i = 0; i < configurations.length; ++i) { - this.supportedInterfaces - .put(terminalSupport, new InvTracker(terminalSupport, configurations[i], i)); - } + /* Status changed? */ + boolean isActive = node.isActive() || machine.shouldDisplay(); + + if (!known.online && isActive) { + /* Node offline -> online */ + known.online = true; + if (update == null) update = new PacketInterfaceTerminalUpdate(); + known.updateNBT(); + update.addOverwriteEntry(known.id).setOnline(true).setItems(new int[0], known.invNbt); + } else if (known.online && !isActive) { + /* Node online -> offline */ + known.online = false; + if (update == null) update = new PacketInterfaceTerminalUpdate(); + update.addOverwriteEntry(known.id).setOnline(false); } + } else { + /* Add a new entry */ + if (update == null) update = new PacketInterfaceTerminalUpdate(); + InvTracker entry = new InvTracker(nextId++, machine, node.isActive()); + update.addNewEntry(entry.id, entry.name, entry.online) + .setLoc(entry.x, entry.y, entry.z, entry.dim, entry.side.ordinal()) + .setItems(entry.rows, entry.rowSize, entry.invNbt) + .setReps(machine.getSelfRep(), machine.getDisplayRep()); + tracked.put(machine, entry); + trackedById.put(entry.id, entry); } + visited.add(machine); } } - data.setBoolean("clear", true); + /* Now find any entries that we need to remove */ + Iterator> it = tracked.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + if (visited.contains(entry.getKey())) { + continue; + } + + if (update == null) update = new PacketInterfaceTerminalUpdate(); - for (final InvTracker inv : this.supportedInterfaces.values()) { - this.byId.put(inv.which, inv); - this.addItems(data, inv, 0, inv.client.getSizeInventory()); + trackedById.remove(entry.getValue().id); + it.remove(); + update.addRemovalEntry(entry.getValue().id); } + return update; } private boolean isDifferent(final ItemStack a, final ItemStack b) { @@ -296,89 +345,71 @@ private boolean isDifferent(final ItemStack a, final ItemStack b) { return !ItemStack.areItemStacksEqual(a, b); } - private void addItems(final NBTTagCompound data, final 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("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 long id; + private String name; + private final IInventory patterns; + private final int rowSize; + private final int rows; + private final int x; + private final int y; + private final int z; private final int dim; - - public InvTracker(final DimensionalCoord coord, long sortValue, final IInventory patterns, - final String unlocalizedName, int offset, int size) { - this(coord.x, coord.y, coord.z, coord.getDimension(), sortValue, patterns, unlocalizedName, offset, size); - } - - public InvTracker(int x, int y, int z, int dim, long sortValue, final IInventory patterns, - final String unlocalizedName, int offset, int size) { - 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; - } - - public InvTracker(IInterfaceTerminalSupport terminalSupport, PatternsConfiguration configuration, int index) { - this( - terminalSupport.getLocation(), - terminalSupport.getSortValue(), - terminalSupport.getPatterns(index), - terminalSupport.getName(), - configuration.offset, - configuration.size); + private final ForgeDirection side; + private boolean online; + private NBTTagList invNbt; + + InvTracker(long id, IInterfaceViewable machine, boolean online) { + DimensionalCoord location = machine.getLocation(); + + this.id = id; + this.name = machine.getName(); + this.patterns = machine.getPatterns(); + this.rowSize = machine.rowSize(); + this.rows = machine.rows(); + this.x = location.x; + this.y = location.y; + this.z = location.z; + this.dim = location.getDimension(); + this.side = machine instanceof AEBasePart hasSide ? hasSide.getSide() : ForgeDirection.UNKNOWN; + this.online = online; + this.invNbt = new NBTTagList(); + updateNBT(); } - } - - private static class PatternInvSlot extends WrapperInvSlot { - public PatternInvSlot(final IInventory inv) { - super(inv); + /** + * Refresh nbt items in the row, item idx. + */ + private void updateNBT(int row, int idx) { + ItemStack stack = this.patterns.getStackInSlot(row * this.rowSize + idx); + + if (stack != null) { + NBTTagCompound itemNbt = this.invNbt.getCompoundTagAt(idx); + stack.writeToNBT(itemNbt); + } else { + // replace + this.invNbt.func_150304_a(idx, new NBTTagCompound()); + } } - @Override - public boolean isItemValid(final ItemStack itemstack) { - return itemstack != null && itemstack.getItem() instanceof ItemEncodedPattern; + /** + * Refreshes all nbt tags. + */ + private void updateNBT() { + this.invNbt = new NBTTagList(); + for (int i = 0; i < this.rows; ++i) { + for (int j = 0; j < this.rowSize; ++j) { + final int offset = this.rowSize * i; + ItemStack stack = this.patterns.getStackInSlot(offset + j); + + if (stack != null) { + this.invNbt.appendTag(stack.writeToNBT(new NBTTagCompound())); + } else { + this.invNbt.appendTag(new NBTTagCompound()); + } + } + } } } } diff --git a/src/main/java/appeng/core/features/registries/InterfaceTerminalRegistry.java b/src/main/java/appeng/core/features/registries/InterfaceTerminalRegistry.java new file mode 100644 index 00000000000..4bd4f280044 --- /dev/null +++ b/src/main/java/appeng/core/features/registries/InterfaceTerminalRegistry.java @@ -0,0 +1,45 @@ +package appeng.core.features.registries; + +import java.util.HashSet; +import java.util.Set; + +import appeng.api.features.IInterfaceTerminalRegistry; +import appeng.api.util.IInterfaceViewable; +import appeng.parts.misc.PartInterface; +import appeng.parts.p2p.PartP2PInterface; +import appeng.tile.misc.TileInterface; + +/** + * Interface Terminal Registry impl for registering viewable instances. + */ +public class InterfaceTerminalRegistry implements IInterfaceTerminalRegistry { + + private final Set> supportedClasses = new HashSet<>(); + private static InterfaceTerminalRegistry INSTANCE; + + /** + * Singleton, do not instantiate more than once. + */ + public InterfaceTerminalRegistry() { + supportedClasses.add(TileInterface.class); + supportedClasses.add(PartInterface.class); + supportedClasses.add(PartP2PInterface.class); + INSTANCE = this; + } + + /** + * Get all supported classes that were registered during startup + */ + public Set> getSupportedClasses() { + return supportedClasses; + } + + @Override + public void register(Class clazz) { + supportedClasses.add(clazz); + } + + public static InterfaceTerminalRegistry instance() { + return INSTANCE; + } +} diff --git a/src/main/java/appeng/core/features/registries/RegistryContainer.java b/src/main/java/appeng/core/features/registries/RegistryContainer.java index 7c1d4086b50..6561d49b176 100644 --- a/src/main/java/appeng/core/features/registries/RegistryContainer.java +++ b/src/main/java/appeng/core/features/registries/RegistryContainer.java @@ -12,6 +12,7 @@ import appeng.api.features.IGrinderRegistry; import appeng.api.features.IInscriberRegistry; +import appeng.api.features.IInterfaceTerminalRegistry; import appeng.api.features.ILocatableRegistry; import appeng.api.features.IMatterCannonAmmoRegistry; import appeng.api.features.IP2PTunnelRegistry; @@ -42,6 +43,7 @@ public class RegistryContainer implements IRegistryContainer { private final IExternalStorageRegistry storage = new ExternalStorageRegistry(); private final ICellRegistry cell = new CellRegistry(); private final IItemDisplayRegistry itemDisplay = new ItemDisplayRegistry(); + private final IInterfaceTerminalRegistry interfaceTerminalRegistry = new InterfaceTerminalRegistry(); private final ILocatableRegistry locatable = new LocatableRegistry(); private final ISpecialComparisonRegistry comparison = new SpecialComparisonRegistry(); private final IWirelessTermRegistry wireless = new WirelessRegistry(); @@ -97,6 +99,11 @@ public IInscriberRegistry inscriber() { return this.inscriber; } + @Override + public IInterfaceTerminalRegistry interfaceTerminal() { + return this.interfaceTerminalRegistry; + } + @Override public ILocatableRegistry locatable() { return this.locatable; diff --git a/src/main/java/appeng/core/localization/ButtonToolTips.java b/src/main/java/appeng/core/localization/ButtonToolTips.java index 7e385799778..35b644d661f 100644 --- a/src/main/java/appeng/core/localization/ButtonToolTips.java +++ b/src/main/java/appeng/core/localization/ButtonToolTips.java @@ -184,7 +184,8 @@ public enum ButtonToolTips { CraftingModeStandard, CraftingModeStandardDesc, CraftingModeIgnoreMissing, - CraftingModeIgnoreMissingDesc; + CraftingModeIgnoreMissingDesc, + ExtraOptions; private final String root; diff --git a/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java b/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java index 4102a1f6eb1..b5af54ce0ca 100644 --- a/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java +++ b/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java @@ -26,6 +26,7 @@ import appeng.core.sync.packets.PacketCraftingItemInterface; import appeng.core.sync.packets.PacketCraftingRemainingOperations; import appeng.core.sync.packets.PacketCraftingTreeData; +import appeng.core.sync.packets.PacketInterfaceTerminalUpdate; import appeng.core.sync.packets.PacketInventoryAction; import appeng.core.sync.packets.PacketLightning; import appeng.core.sync.packets.PacketMEInventoryUpdate; @@ -110,7 +111,8 @@ public enum PacketTypes { PACKET_CRAFTING_REMAINING_OPERATIONS(PacketCraftingRemainingOperations.class), PACKET_CRAFTING_ITEM_INTERFACE(PacketCraftingItemInterface.class), PACKET_CRAFTING_TREE_DATA(PacketCraftingTreeData.class), - PACKET_NEI_BOOKMARK(PacketNEIBookmark.class); + PACKET_NEI_BOOKMARK(PacketNEIBookmark.class), + PACKET_INTERFACE_TERMINAL_UPDATE(PacketInterfaceTerminalUpdate.class),; private final Class packetClass; private final Constructor packetConstructor; diff --git a/src/main/java/appeng/core/sync/packets/PacketCompressedNBT.java b/src/main/java/appeng/core/sync/packets/PacketCompressedNBT.java index 1f34d5f4a8a..b551814008b 100644 --- a/src/main/java/appeng/core/sync/packets/PacketCompressedNBT.java +++ b/src/main/java/appeng/core/sync/packets/PacketCompressedNBT.java @@ -18,13 +18,10 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; -import appeng.client.gui.implementations.GuiInterfaceTerminal; import appeng.core.sync.AppEngPacket; import appeng.core.sync.network.INetworkInfo; import cpw.mods.fml.relauncher.Side; @@ -86,11 +83,5 @@ public void write(final int value) throws IOException { @Override @SideOnly(Side.CLIENT) - public void clientPacketData(final INetworkInfo network, final AppEngPacket packet, final EntityPlayer player) { - final GuiScreen gs = Minecraft.getMinecraft().currentScreen; - - if (gs instanceof GuiInterfaceTerminal) { - ((GuiInterfaceTerminal) gs).postUpdate(this.in); - } - } + public void clientPacketData(final INetworkInfo network, final AppEngPacket packet, final EntityPlayer player) {} } diff --git a/src/main/java/appeng/core/sync/packets/PacketInterfaceTerminalUpdate.java b/src/main/java/appeng/core/sync/packets/PacketInterfaceTerminalUpdate.java new file mode 100644 index 00000000000..b884d836b5c --- /dev/null +++ b/src/main/java/appeng/core/sync/packets/PacketInterfaceTerminalUpdate.java @@ -0,0 +1,594 @@ +package appeng.core.sync.packets; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraftforge.common.util.Constants.NBT; +import net.minecraftforge.common.util.ForgeDirection; + +import appeng.client.gui.IInterfaceTerminalPostUpdate; +import appeng.core.AEConfig; +import appeng.core.AELog; +import appeng.core.features.AEFeature; +import appeng.core.sync.AppEngPacket; +import appeng.core.sync.network.INetworkInfo; +import appeng.helpers.Reflected; +import cpw.mods.fml.common.network.ByteBufUtils; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; + +/** + * Packet used for interface terminal updates. Packet allows the server to send an array of command packets, which are + * then processed in order. This allows for chaining commands to produce the desired update. + */ +public class PacketInterfaceTerminalUpdate extends AppEngPacket { + + public static final int CLEAR_ALL_BIT = 1; + public static final int DISCONNECT_BIT = 2; + + private final List commands = new ArrayList<>(); + private int statusFlags; + + @Reflected + public PacketInterfaceTerminalUpdate(final ByteBuf buf) throws IOException { + decode(buf); + } + + public PacketInterfaceTerminalUpdate() {} + + private void decode(ByteBuf buf) { + this.statusFlags = buf.readByte(); + int numEntries = buf.readInt(); + + for (int i = 0; i < numEntries; ++i) { + try { + int packetType = buf.readByte(); + PacketType type = PacketType.valueOf(packetType); + switch (type) { + case ADD -> this.commands.add(new PacketAdd(buf)); + case REMOVE -> this.commands.add(new PacketRemove(buf)); + case OVERWRITE -> this.commands.add(new PacketOverwrite(buf)); + case RENAME -> this.commands.add(new PacketRename(buf)); + } + } catch (ArrayIndexOutOfBoundsException e) { + if (AEConfig.instance.isFeatureEnabled(AEFeature.PacketLogging)) { + AELog.info( + "Corrupted packet commands: (" + i + + ") of (" + + numEntries + + ") -> " + + this.commands.size() + + " : " + + this.commands.stream().map(packetEntry -> packetEntry.getClass().getSimpleName()) + .collect(Collectors.groupingBy(String::new, Collectors.counting()))); + if (AEConfig.instance.isFeatureEnabled(AEFeature.DebugLogging)) { + AELog.info(" <- Parsed content: " + this.commands); + } + } + AELog.debug(e); + return; + } catch (IOException e) { + AELog.error(e); + break; + } + } + if (AEConfig.instance.isFeatureEnabled(AEFeature.PacketLogging)) { + AELog.info( + " <- Received commands " + this.commands.size() + + " : " + + this.commands.stream().map(packetEntry -> packetEntry.getClass().getSimpleName()) + .collect(Collectors.groupingBy(String::new, Collectors.counting()))); + } + } + + public void encode() { + try { + if (AEConfig.instance.isFeatureEnabled(AEFeature.PacketLogging)) { + AELog.info( + " -> Sent commands " + this.commands.size() + + " : " + + this.commands.stream().map(packetEntry -> packetEntry.getClass().getSimpleName()) + .collect(Collectors.groupingBy(String::new, Collectors.counting()))); + if (AEConfig.instance.isFeatureEnabled(AEFeature.DebugLogging)) { + AELog.info(" -> Sent commands: " + this.commands); + } + } + + ByteBuf buf = Unpooled.buffer(2048); + buf.writeInt(this.getPacketID()); + buf.writeByte(this.statusFlags); + buf.writeInt(commands.size()); + for (PacketEntry entry : commands) { + entry.write(buf); + } + super.configureWrite(buf); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Remove all entries on the terminal. This is done BEFORE any entries are processed, so you can set this to clear + * old entries, and add new ones after in one packet. + */ + public void setClear() { + this.statusFlags |= CLEAR_ALL_BIT; + } + + /** + * The terminal disconnected. Entries are still processed, but indicates for the GUI to darken/turn off the + * terminal. No further updates will arrive until the terminal reconnects. + */ + public void setDisconnect() { + this.statusFlags |= DISCONNECT_BIT; + } + + /** + * Adds a new entry. Fill out the rest of the command using the {@link PacketAdd#setItems(int, int, NBTTagList)} and + * {@link PacketAdd#setLoc(int, int, int, int, int)}. + * + * @return the packet, which needs to have information filled out. + */ + public PacketAdd addNewEntry(long id, String name, boolean online) { + PacketAdd packet = new PacketAdd(id, name, online); + + commands.add(packet); + return packet; + } + + /** + * Remove the entry with the id from the terminal. + */ + public void addRemovalEntry(long id) { + commands.add(new PacketRemove(id)); + } + + /** + * Rename the entry + */ + public void addRenamedEntry(long id, String newName) { + commands.add(new PacketRename(id, newName)); + } + + /** + * Overwrite the entry with new items or new status + */ + public PacketOverwrite addOverwriteEntry(long id) { + PacketOverwrite packet = new PacketOverwrite(id); + + commands.add(packet); + return packet; + } + + @Override + @SideOnly(Side.CLIENT) + public void clientPacketData(final INetworkInfo network, final AppEngPacket packet, final EntityPlayer player) { + final GuiScreen gs = Minecraft.getMinecraft().currentScreen; + + if (gs instanceof IInterfaceTerminalPostUpdate hasPostUpdate) { + hasPostUpdate.postUpdate(this.commands, this.statusFlags); + } + } + + enum PacketType { + + ADD, + REMOVE, + OVERWRITE, + RENAME; + + public static final PacketType[] TYPES = PacketType.values(); + + /** + * Get the indexed value of packet type, throws on invalid index + */ + public static PacketType valueOf(int idx) throws ArrayIndexOutOfBoundsException { + return TYPES[idx]; + } + } + + /** + * A packet for updating an entry. + */ + public abstract static class PacketEntry { + + public final long entryId; + + protected PacketEntry(long entryId) { + this.entryId = entryId; + } + + protected PacketEntry(ByteBuf buf) throws IOException { + this.entryId = buf.readLong(); + read(buf); + } + + /** + * Needs to write the packet id. + */ + protected abstract void write(ByteBuf buf) throws IOException; + + /** + * Reading the entry id is not needed. + */ + protected abstract void read(ByteBuf buf) throws IOException; + } + + /** + * A command for sending a new entry. + */ + public static class PacketAdd extends PacketEntry { + + public String name; + public int x, y, z, dim, side; + public int rows, rowSize; + public boolean online; + public ItemStack selfRep, dispRep; + public NBTTagList items; + + PacketAdd(long id, String name, boolean online) { + super(id); + this.name = name; + this.online = online; + } + + PacketAdd(ByteBuf buf) throws IOException { + super(buf); + } + + public PacketAdd setLoc(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; + } + + public PacketAdd setItems(int rows, int rowSize, NBTTagList items) { + this.rows = rows; + this.rowSize = rowSize; + this.items = items; + + return this; + } + + /** + * Set representatives for the interface + * + * @param selfRep the stack representing me + * @param dispRep the stack representing my target + */ + public PacketAdd setReps(ItemStack selfRep, ItemStack dispRep) { + this.selfRep = selfRep; + this.dispRep = dispRep; + + return this; + } + + @Override + protected void write(ByteBuf buf) throws IOException { + buf.writeByte(PacketType.ADD.ordinal()); + buf.writeLong(entryId); + ByteBufUtils.writeUTF8String(buf, this.name); + buf.writeInt(x); + buf.writeInt(y); + buf.writeInt(z); + buf.writeInt(dim); + buf.writeByte(side); + buf.writeInt(rows); + buf.writeInt(rowSize); + + ByteBuf tempBuf = Unpooled.directBuffer(256); + try { + try (ByteBufOutputStream stream = new ByteBufOutputStream(tempBuf)) { + + NBTTagCompound wrapper = new NBTTagCompound(); + + if (selfRep != null) { + wrapper.setTag("self", selfRep.writeToNBT(new NBTTagCompound())); + } + if (dispRep != null) { + wrapper.setTag("disp", dispRep.writeToNBT(new NBTTagCompound())); + } + wrapper.setTag("data", items); + CompressedStreamTools.writeCompressed(wrapper, stream); + } + buf.writeInt(tempBuf.readableBytes()); + buf.writeBytes(tempBuf); + } finally { + tempBuf.release(); + } + } + + @Override + protected void read(ByteBuf buf) throws IOException { + this.name = ByteBufUtils.readUTF8String(buf); + this.x = buf.readInt(); + this.y = buf.readInt(); + this.z = buf.readInt(); + this.dim = buf.readInt(); + this.side = buf.readInt(); + this.rows = buf.readByte(); + this.rowSize = buf.readInt(); + int payloadSize = buf.readInt(); + try (ByteBufInputStream stream = new ByteBufInputStream(buf, payloadSize)) { + NBTTagCompound payload = CompressedStreamTools.readCompressed(stream); + int available = stream.available(); + if (available > 0) { + byte[] left = new byte[available]; + int read = stream.read(left); + if (AEConfig.instance.isFeatureEnabled(AEFeature.PacketLogging)) { + AELog.info( + "Unread bytes detected (" + read + + "): " + + Arrays.toString(left) + + " at " + + dim + + "#(" + + x + + ":" + + y + + ":" + + z + + ")@" + + ForgeDirection.getOrientation(side)); + } + } + if (payload.hasKey("self", NBT.TAG_COMPOUND)) { + this.selfRep = ItemStack.loadItemStackFromNBT(payload.getCompoundTag("self")); + } + + if (payload.hasKey("disp", NBT.TAG_COMPOUND)) { + this.dispRep = ItemStack.loadItemStackFromNBT(payload.getCompoundTag("disp")); + } + + this.items = payload.getTagList("data", NBT.TAG_COMPOUND); + } + } + + @Override + public String toString() { + return "PacketAdd{" + "name='" + + name + + '\'' + + ", x=" + + x + + ", y=" + + y + + ", z=" + + z + + ", dim=" + + dim + + ", side=" + + side + + ", rows=" + + rows + + ", rowSize=" + + rowSize + + ", online=" + + online + + ", selfRep=" + + selfRep + + ", dispRep=" + + dispRep + + ", items=" + + items + + ", entryId=" + + entryId + + '}'; + } + } + + public static class PacketRemove extends PacketEntry { + + PacketRemove(long id) { + super(id); + } + + PacketRemove(ByteBuf buf) throws IOException { + super(buf); + } + + @Override + protected void write(ByteBuf buf) { + buf.writeByte(PacketType.REMOVE.ordinal()); + buf.writeLong(entryId); + } + + @Override + protected void read(ByteBuf buf) {} + + @Override + public String toString() { + return "PacketRemove{" + "entryId=" + entryId + '}'; + } + } + + /** + * Overwrite online status or inventory of the entry. + */ + public static class PacketOverwrite extends PacketEntry { + + public static final int ONLINE_BIT = 1; + public static final int ONLINE_VALID = 1 << 1; + public static final int ITEMS_VALID = 1 << 2; + public static final int ALL_ITEM_UPDATE_BIT = 1 << 3; + public boolean onlineValid; + public boolean online; + public boolean itemsValid; + public boolean allItemUpdate; + public int[] validIndices; + public NBTTagList items; + + protected PacketOverwrite(long id) { + super(id); + } + + protected PacketOverwrite(ByteBuf buf) throws IOException { + super(buf); + } + + public PacketOverwrite setOnline(boolean online) { + this.onlineValid = true; + this.online = online; + return this; + } + + public PacketOverwrite setItems(int[] validIndices, NBTTagList items) { + this.itemsValid = true; + this.allItemUpdate = validIndices == null || validIndices.length == 0; + this.validIndices = validIndices; + this.items = items; + + return this; + } + + @Override + protected void write(ByteBuf buf) throws IOException { + buf.writeByte(PacketType.OVERWRITE.ordinal()); + buf.writeLong(entryId); + + int flags = 0; + + if (onlineValid) { + flags |= ONLINE_VALID; + flags |= online ? ONLINE_BIT : 0; + } + if (itemsValid) { + flags |= ITEMS_VALID; + if (allItemUpdate) { + flags |= ALL_ITEM_UPDATE_BIT; + buf.writeByte(flags); + } else { + buf.writeByte(flags); + buf.writeInt(validIndices.length); + for (int validIndex : validIndices) { + buf.writeInt(validIndex); + } + } + ByteBuf tempBuf = Unpooled.directBuffer(256); + try { + try (ByteBufOutputStream stream = new ByteBufOutputStream(tempBuf)) { + + NBTTagCompound wrapper = new NBTTagCompound(); + + wrapper.setTag("data", items); + CompressedStreamTools.writeCompressed(wrapper, stream); + } + buf.writeInt(tempBuf.readableBytes()); + buf.writeBytes(tempBuf); + } finally { + tempBuf.release(); + } + + } else { + buf.writeByte(flags); + } + } + + @Override + protected void read(ByteBuf buf) throws IOException { + int flags = buf.readByte(); + + /* Decide whether to use online flag or not */ + if ((flags & ONLINE_VALID) == ONLINE_VALID) { + this.onlineValid = true; + this.online = (flags & ONLINE_BIT) == ONLINE_BIT; + } + /* Decide whether to read item list or not */ + if ((flags & ITEMS_VALID) == ITEMS_VALID) { + if ((flags & ALL_ITEM_UPDATE_BIT) == ALL_ITEM_UPDATE_BIT) { + this.allItemUpdate = true; + } else { + int numItems = buf.readInt(); + this.itemsValid = true; + this.validIndices = new int[numItems]; + for (int i = 0; i < numItems; ++i) { + this.validIndices[i] = buf.readInt(); + } + } + + int payloadSize = buf.readInt(); + try (ByteBufInputStream stream = new ByteBufInputStream(buf, payloadSize)) { + this.items = CompressedStreamTools.readCompressed(stream).getTagList("data", NBT.TAG_COMPOUND); + int available = stream.available(); + if (available > 0) { + byte[] left = new byte[available]; + int read = stream.read(left); + if (AEConfig.instance.isFeatureEnabled(AEFeature.PacketLogging)) { + AELog.info("Unread bytes detected (" + read + "): " + Arrays.toString(left)); + } + } + } + } + } + + @Override + public String toString() { + return "PacketOverwrite{" + "onlineValid=" + + onlineValid + + ", online=" + + online + + ", itemsValid=" + + itemsValid + + ", allItemUpdate=" + + allItemUpdate + + ", validIndices=" + + Arrays.toString(validIndices) + + ", items=" + + items + + ", entryId=" + + entryId + + '}'; + } + } + + /** + * Rename the entry. + */ + public static class PacketRename extends PacketEntry { + + public String newName; + + protected PacketRename(long id, String newName) { + super(id); + this.newName = newName; + } + + protected PacketRename(ByteBuf buf) throws IOException { + super(buf); + } + + @Override + protected void write(ByteBuf buf) { + buf.writeByte(PacketType.RENAME.ordinal()); + buf.writeLong(entryId); + ByteBufUtils.writeUTF8String(buf, newName); + } + + @Override + protected void read(ByteBuf buf) { + newName = ByteBufUtils.readUTF8String(buf); + } + + @Override + public String toString() { + return "PacketRename{" + "newName='" + newName + '\'' + ", entryId=" + entryId + '}'; + } + } +} diff --git a/src/main/java/appeng/helpers/DualityInterface.java b/src/main/java/appeng/helpers/DualityInterface.java index 31ef4fe3b92..68d4d6c5329 100644 --- a/src/main/java/appeng/helpers/DualityInterface.java +++ b/src/main/java/appeng/helpers/DualityInterface.java @@ -1155,11 +1155,6 @@ public String getTermName() { } } - public long getSortValue() { - final TileEntity te = this.iHost.getTileEntity(); - return (te.zCoord << 24) ^ (te.xCoord << 8) ^ te.yCoord; - } - public BaseActionSource getActionSource() { return interfaceRequestSource; } diff --git a/src/main/java/appeng/helpers/IInterfaceHost.java b/src/main/java/appeng/helpers/IInterfaceHost.java index 2cdcb0ec67f..acf0525656c 100644 --- a/src/main/java/appeng/helpers/IInterfaceHost.java +++ b/src/main/java/appeng/helpers/IInterfaceHost.java @@ -10,11 +10,10 @@ package appeng.helpers; -import java.util.ArrayList; import java.util.EnumSet; -import java.util.List; import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraftforge.common.util.ForgeDirection; @@ -24,9 +23,9 @@ import appeng.api.implementations.IUpgradeableHost; import appeng.api.networking.crafting.ICraftingProvider; import appeng.api.networking.crafting.ICraftingRequester; +import appeng.api.util.IInterfaceViewable; -public interface IInterfaceHost - extends ICraftingProvider, IUpgradeableHost, ICraftingRequester, IInterfaceTerminalSupport { +public interface IInterfaceHost extends ICraftingProvider, IUpgradeableHost, ICraftingRequester, IInterfaceViewable { DualityInterface getInterfaceDuality(); @@ -37,17 +36,7 @@ public interface IInterfaceHost void saveChanges(); @Override - default PatternsConfiguration[] getPatternsConfigurations() { - DualityInterface dual = getInterfaceDuality(); - List patterns = new ArrayList<>(); - for (int i = 0; i <= dual.getInstalledUpgrades(Upgrades.PATTERN_CAPACITY); ++i) { - patterns.add(new PatternsConfiguration(i * 9, 9)); - } - return patterns.toArray(new PatternsConfiguration[0]); - } - - @Override - default IInventory getPatterns(int index) { + default IInventory getPatterns() { return getInterfaceDuality().getPatterns(); } @@ -62,7 +51,17 @@ default String getName() { } @Override - default long getSortValue() { - return getInterfaceDuality().getSortValue(); + default int rows() { + return getInterfaceDuality().getInstalledUpgrades(Upgrades.PATTERN_CAPACITY) + 1; + } + + @Override + default int rowSize() { + return DualityInterface.NUMBER_OF_PATTERN_SLOTS; + } + + @Override + default ItemStack getDisplayRep() { + return getInterfaceDuality().getCrafterIcon(); } } diff --git a/src/main/java/appeng/helpers/IInterfaceTerminalSupport.java b/src/main/java/appeng/helpers/IInterfaceTerminalSupport.java index ffc4fcd7b0b..40d3aa78cff 100644 --- a/src/main/java/appeng/helpers/IInterfaceTerminalSupport.java +++ b/src/main/java/appeng/helpers/IInterfaceTerminalSupport.java @@ -5,11 +5,20 @@ import appeng.api.networking.IGridHost; import appeng.api.util.DimensionalCoord; - +import appeng.api.util.IInterfaceViewable; + +/** + * Refactoring this class into API, and renaming. + * + * @see IInterfaceViewable + */ +@Deprecated public interface IInterfaceTerminalSupport extends IGridHost { class PatternsConfiguration { + /** This property should be used in the terminal level and items tightly packed. */ + @Deprecated public int offset; public int size; @@ -23,12 +32,14 @@ public PatternsConfiguration(int offset, int size) { PatternsConfiguration[] getPatternsConfigurations(); + @Deprecated IInventory getPatterns(int index); String getName(); TileEntity getTileEntity(); + @Deprecated default long getSortValue() { var te = getTileEntity(); return ((long) te.zCoord << 24) ^ ((long) te.xCoord << 8) ^ te.yCoord; diff --git a/src/main/java/appeng/helpers/InterfaceTerminalSupportedClassProvider.java b/src/main/java/appeng/helpers/InterfaceTerminalSupportedClassProvider.java index 01746b750ce..6f6e95f5565 100644 --- a/src/main/java/appeng/helpers/InterfaceTerminalSupportedClassProvider.java +++ b/src/main/java/appeng/helpers/InterfaceTerminalSupportedClassProvider.java @@ -1,27 +1,20 @@ package appeng.helpers; -import java.util.HashSet; import java.util.Set; -import appeng.parts.misc.PartInterface; -import appeng.parts.p2p.PartP2PInterface; -import appeng.tile.misc.TileInterface; - +/** + * Interface Terminal Support handler. + * + * @deprecated This is being refactored to the API. + */ +@Deprecated public class InterfaceTerminalSupportedClassProvider { - private static final Set> supportedClasses = new HashSet<>(); - - static { - supportedClasses.add(TileInterface.class); - supportedClasses.add(PartInterface.class); - supportedClasses.add(PartP2PInterface.class); - } - + @Deprecated public static Set> getSupportedClasses() { - return supportedClasses; + return null; } - public static void register(Class clazz) { - supportedClasses.add(clazz); - } + @Deprecated + public static void register(Class clazz) {} } diff --git a/src/main/java/appeng/integration/modules/NEI.java b/src/main/java/appeng/integration/modules/NEI.java index 7e5264a531a..cc415b886eb 100644 --- a/src/main/java/appeng/integration/modules/NEI.java +++ b/src/main/java/appeng/integration/modules/NEI.java @@ -22,7 +22,7 @@ import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; -import appeng.client.gui.AEBaseMEGui; +import appeng.client.gui.IGuiTooltipHandler; import appeng.client.gui.implementations.GuiCraftConfirm; import appeng.client.gui.implementations.GuiCraftingCPU; import appeng.client.gui.implementations.GuiCraftingTerm; @@ -210,8 +210,8 @@ public List handleItemDisplayName(final GuiContainer arg0, final ItemSta @Override public List handleItemTooltip(final GuiContainer guiScreen, final ItemStack stack, final int mouseX, final int mouseY, final List currentToolTip) { - if (guiScreen instanceof AEBaseMEGui) { - return ((AEBaseMEGui) guiScreen).handleItemTooltip(stack, mouseX, mouseY, currentToolTip); + if (guiScreen instanceof IGuiTooltipHandler) { + return ((IGuiTooltipHandler) guiScreen).handleItemTooltip(stack, mouseX, mouseY, currentToolTip); } return currentToolTip; @@ -228,8 +228,9 @@ public void load(GuiContainer gui) {} @Override public ItemStack getStackUnderMouse(GuiContainer gui, int mousex, int mousey) { - if (gui instanceof GuiCraftConfirm) return ((GuiCraftConfirm) gui).getHoveredStack(); - else if (gui instanceof GuiCraftingCPU) return ((GuiCraftingCPU) gui).getHoveredStack(); + if (gui instanceof IGuiTooltipHandler) { + return ((IGuiTooltipHandler) gui).getHoveredStack(); + } return null; } diff --git a/src/main/java/appeng/me/cache/PathGridCache.java b/src/main/java/appeng/me/cache/PathGridCache.java index a51f347be60..73d7f277909 100644 --- a/src/main/java/appeng/me/cache/PathGridCache.java +++ b/src/main/java/appeng/me/cache/PathGridCache.java @@ -85,7 +85,7 @@ public void onUpdateTick() { if (this.updateNetwork) { if (!this.booting) { - this.myGrid.postEvent(new MENetworkBootingStatusChange()); + this.myGrid.postEvent(new MENetworkBootingStatusChange(true)); } this.booting = true; @@ -186,7 +186,7 @@ public void onUpdateTick() { this.booting = false; this.setChannelPowerUsage(this.getChannelsByBlocks() / 128.0); - this.myGrid.postEvent(new MENetworkBootingStatusChange()); + this.myGrid.postEvent(new MENetworkBootingStatusChange(false)); } } } diff --git a/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java b/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java index 9c5ec725d07..317ceba6707 100644 --- a/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java +++ b/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java @@ -66,6 +66,7 @@ import appeng.api.storage.data.IAEStack; import appeng.api.storage.data.IItemList; import appeng.api.util.DimensionalCoord; +import appeng.api.util.IInterfaceViewable; import appeng.api.util.WorldCoord; import appeng.container.ContainerNull; import appeng.core.AELog; @@ -75,7 +76,6 @@ import appeng.crafting.CraftingWatcher; import appeng.crafting.MECraftingInventory; import appeng.helpers.DualityInterface; -import appeng.helpers.IInterfaceTerminalSupport; import appeng.me.cache.CraftingGridCache; import appeng.me.cluster.IAECluster; import appeng.tile.AEBaseTile; @@ -1243,8 +1243,8 @@ private TileEntity getTile(ICraftingMedium craftingProvider) { return ((DualityInterface) craftingProvider).getHost().getTile(); } else if (craftingProvider instanceof AEBaseTile) { return ((AEBaseTile) craftingProvider).getTile(); - } else if (craftingProvider instanceof IInterfaceTerminalSupport interfaceTerminalSupport) { - return interfaceTerminalSupport.getTileEntity(); + } else if (craftingProvider instanceof IInterfaceViewable interfaceViewable) { + return interfaceViewable.getTileEntity(); } try { Method method = craftingProvider.getClass().getMethod("getTile"); diff --git a/src/main/java/appeng/parts/misc/PartInterface.java b/src/main/java/appeng/parts/misc/PartInterface.java index 23704196044..d8a7f1064f8 100644 --- a/src/main/java/appeng/parts/misc/PartInterface.java +++ b/src/main/java/appeng/parts/misc/PartInterface.java @@ -386,4 +386,9 @@ public int getPriority() { public void setPriority(final int newValue) { this.duality.setPriority(newValue); } + + @Override + public ItemStack getSelfRep() { + return this.getItemStack(); + } } diff --git a/src/main/java/appeng/parts/p2p/PartP2PInterface.java b/src/main/java/appeng/parts/p2p/PartP2PInterface.java index 8ae7facbc7c..f5e9a2d25c7 100644 --- a/src/main/java/appeng/parts/p2p/PartP2PInterface.java +++ b/src/main/java/appeng/parts/p2p/PartP2PInterface.java @@ -390,4 +390,9 @@ public void onTunnelNetworkChange() { public boolean shouldDisplay() { return IInterfaceHost.super.shouldDisplay() && !isOutput(); } + + @Override + public ItemStack getSelfRep() { + return this.getItemStack(); + } } diff --git a/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java b/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java index f914fa4309f..d3f1744f6ee 100644 --- a/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java +++ b/src/main/java/appeng/parts/reporting/PartInterfaceTerminal.java @@ -14,6 +14,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.Vec3; +import appeng.api.networking.events.MENetworkBootingStatusChange; +import appeng.api.networking.events.MENetworkEventSubscribe; import appeng.client.texture.CableBusTextures; import appeng.core.sync.GuiBridge; import appeng.util.Platform; @@ -23,6 +25,7 @@ public class PartInterfaceTerminal extends AbstractPartDisplay { private static final CableBusTextures FRONT_BRIGHT_ICON = CableBusTextures.PartInterfaceTerm_Bright; private static final CableBusTextures FRONT_DARK_ICON = CableBusTextures.PartInterfaceTerm_Dark; private static final CableBusTextures FRONT_COLORED_ICON = CableBusTextures.PartInterfaceTerm_Colored; + private boolean needsUpdate; public PartInterfaceTerminal(final ItemStack is) { super(is); @@ -59,4 +62,21 @@ public CableBusTextures getFrontColored() { public CableBusTextures getFrontDark() { return FRONT_DARK_ICON; } + + /** + * Indicates that the network has changed. Calling this will reset the flag. + */ + public boolean needsUpdate() { + boolean ret = needsUpdate; + + needsUpdate = false; + return ret; + } + + @MENetworkEventSubscribe + public void onNetworkBootingChanged(MENetworkBootingStatusChange event) { + if (!event.isBooting) { + this.needsUpdate = true; + } + } } diff --git a/src/main/java/appeng/tile/misc/TileInterface.java b/src/main/java/appeng/tile/misc/TileInterface.java index 8409a7a6fbe..d3628bcf1b8 100644 --- a/src/main/java/appeng/tile/misc/TileInterface.java +++ b/src/main/java/appeng/tile/misc/TileInterface.java @@ -319,4 +319,9 @@ public boolean isActive() { public boolean isBooting() { return (clientFlags & BOOTING_FLAG) == BOOTING_FLAG; } + + @Override + public ItemStack getSelfRep() { + return this.getItemFromTile(this); + } } diff --git a/src/main/resources/META-INF/appeng_at.cfg b/src/main/resources/META-INF/appeng_at.cfg index 6018bae349e..489b3d1948f 100644 --- a/src/main/resources/META-INF/appeng_at.cfg +++ b/src/main/resources/META-INF/appeng_at.cfg @@ -3,3 +3,5 @@ public net.minecraft.client.gui.inventory.GuiContainer func_146977_a(Lnet/minecr public net.minecraft.client.gui.GuiTextField func_146188_c(IIII)V # drawSelectionBox #Pattern Value Setting public net.minecraft.client.gui.inventory.GuiContainer field_147006_u #theSlot +# RenderItem +public net.minecraft.client.renderer.entity.RenderItem func_77017_a(Lnet/minecraft/client/renderer/Tessellator;IIIII)V #renderQuad diff --git a/src/main/resources/assets/appliedenergistics2/lang/en_US.lang b/src/main/resources/assets/appliedenergistics2/lang/en_US.lang index 21620440996..eeba5e10a32 100644 --- a/src/main/resources/assets/appliedenergistics2/lang/en_US.lang +++ b/src/main/resources/assets/appliedenergistics2/lang/en_US.lang @@ -445,6 +445,8 @@ gui.tooltips.appliedenergistics2.CraftingModeStandardDesc=Only starts craft if a gui.tooltips.appliedenergistics2.CraftingModeIgnoreMissing=Ignore Missing Crafting Mode gui.tooltips.appliedenergistics2.CraftingModeIgnoreMissingDesc=Starts craft even if ingredients are missing from the ME System. +gui.tooltips.appliedenergistics2.ExtraOptions=Extra Options + # Units gui.appliedenergistics2.units.appliedenergstics=AE gui.appliedenergistics2.units.ic2=Energy Units diff --git a/src/main/resources/assets/appliedenergistics2/textures/guis/newinterfaceterminal.png b/src/main/resources/assets/appliedenergistics2/textures/guis/newinterfaceterminal.png index 187e3d02a6d..6dd010c5ae7 100644 Binary files a/src/main/resources/assets/appliedenergistics2/textures/guis/newinterfaceterminal.png and b/src/main/resources/assets/appliedenergistics2/textures/guis/newinterfaceterminal.png differ diff --git a/src/main/resources/assets/appliedenergistics2/textures/guis/states.png b/src/main/resources/assets/appliedenergistics2/textures/guis/states.png index 434498a7ade..fef9c0f46df 100644 Binary files a/src/main/resources/assets/appliedenergistics2/textures/guis/states.png and b/src/main/resources/assets/appliedenergistics2/textures/guis/states.png differ