package codechicken.nei; import static codechicken.nei.NEIClientConfig.canCheatItem; import static codechicken.nei.NEIClientConfig.canPerformAction; import static codechicken.nei.NEIClientConfig.getStringArrSetting; import static codechicken.nei.NEIClientConfig.getStringSetting; import static codechicken.nei.NEIClientConfig.hasSMPCounterPart; import static codechicken.nei.NEIClientConfig.invCreativeMode; import static codechicken.nei.NEIClientConfig.world; import java.awt.Color; import java.io.PrintWriter; import java.io.StringWriter; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.MessageFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.Container; import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.IChatComponent; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fluids.FluidStack; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.GL11; import com.google.common.collect.Iterables; import com.gtnewhorizon.gtnhlib.util.numberformatting.NumberFormatUtil; import codechicken.lib.gui.GuiDraw; import codechicken.lib.inventory.InventoryRange; import codechicken.lib.inventory.InventoryUtils; import codechicken.lib.util.LangProxy; import codechicken.lib.vec.Rectangle4i; import codechicken.nei.api.GuiInfo; import codechicken.nei.api.IInfiniteItemHandler; import codechicken.nei.api.ItemInfo; import codechicken.nei.recipe.GuiOverlayButton.ItemOverlayState; import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.NEIKeyboardUtils; public class NEIClientUtils extends NEIServerUtils { public static class Alignment { public final int x, y; public static final Alignment TopLeft = new Alignment(-1, -1); public static final Alignment TopCenter = new Alignment(0, -1); public static final Alignment TopRight = new Alignment(1, -1); public static final Alignment CenterLeft = new Alignment(-1, 0); public static final Alignment Center = new Alignment(0, 0); public static final Alignment CenterRight = new Alignment(1, 0); public static final Alignment BottomLeft = new Alignment(-1, 1); public static final Alignment BottomCenter = new Alignment(0, 1); public static final Alignment BottomRight = new Alignment(1, 1); public Alignment(int x, int y) { this.x = x; this.y = y; } public float getX(float parentWidth, float childWidth) { final float x = (this.x + 1) / 2f; return parentWidth * x - childWidth * x; } public float getY(float parentHeight, float childHeight) { final float y = (this.y + 1) / 2f; return parentHeight * y - childHeight * y; } } public static LangProxy lang = new LangProxy("nei"); /** Formats a number with group separator and at most 2 fraction digits. */ private static final Map decimalFormatters = new HashMap<>(); private static final DecimalFormat CHANCE_FORMAT = new DecimalFormat("##0.##%"); public static final int ALT_HASH = NEIKeyboardUtils.ALT_HASH; public static final int SHIFT_HASH = NEIKeyboardUtils.SHIFT_HASH; public static final int CTRL_HASH = NEIKeyboardUtils.CTRL_HASH; public static Minecraft mc() { return Minecraft.getMinecraft(); } public static String translate(String key, Object... params) { return lang.format(key, params); } public static void printChatMessage(IChatComponent msg) { if (mc().ingameGUI != null) mc().ingameGUI.getChatGUI().printChatMessage(msg); } private static DecimalFormat getDecimalFormat() { return decimalFormatters.computeIfAbsent(Locale.getDefault(Locale.Category.FORMAT), locale -> { DecimalFormat numberFormat = new DecimalFormat(); // uses the necessary locale inside anyway numberFormat.setGroupingUsed(true); numberFormat.setMaximumFractionDigits(2); numberFormat.setRoundingMode(RoundingMode.HALF_UP); DecimalFormatSymbols decimalFormatSymbols = numberFormat.getDecimalFormatSymbols(); decimalFormatSymbols.setGroupingSeparator(','); // Use sensible separator for best clarity. numberFormat.setDecimalFormatSymbols(decimalFormatSymbols); return numberFormat; }); } public static String formatNumber(Number aNumber) { if (NEIModContainer.isGTNHLibLoaded()) { return NumberFormatUtil.formatNumber(aNumber); } return getDecimalFormat().format(aNumber); } public static String formatFluid(Number aNumber) { if (NEIModContainer.isGTNHLibLoaded()) { return NumberFormatUtil.formatFluid(aNumber); } return getDecimalFormat().format(aNumber) + " L"; } public static String formatChance(float chance) { return CHANCE_FORMAT.format(chance); } public static void deleteHeldItem() { deleteSlotStack(-999); } public static void dropHeldItem() { mc().playerController .windowClick(((GuiContainer) mc().currentScreen).inventorySlots.windowId, -999, 0, 0, mc().thePlayer); } public static void deleteSlotStack(int slotNumber) { setSlotContents(slotNumber, null, true); } public static void decreaseSlotStack(int slotNumber) { ItemStack stack = slotNumber == -999 ? getHeldItem() : mc().thePlayer.openContainer.getSlot(slotNumber).getStack(); if (stack == null) return; if (stack.stackSize == 1) deleteSlotStack(slotNumber); else { stack = stack.copy(); stack.stackSize--; setSlotContents(slotNumber, stack, true); } } public static void deleteEverything() { NEICPH.sendDeleteAllItems(); } public static void deleteItemsOfType(ItemStack type) { Container c = getGuiContainer().inventorySlots; for (int i = 0; i < c.inventorySlots.size(); i++) { Slot slot = c.getSlot(i); if (slot == null) continue; ItemStack stack = slot.getStack(); if (stack != null && stack.getItem() == type.getItem() && stack.getItemDamage() == type.getItemDamage()) { setSlotContents(i, null, true); slot.putStack(null); } } } public static ItemStack getHeldItem() { return mc().thePlayer.inventory.getItemStack(); } public static void setSlotContents(int slot, ItemStack item, boolean containerInv) { NEICPH.sendSetSlot(slot, item, containerInv); if (slot == -999) mc().thePlayer.inventory.setItemStack(item); } /** * @param mode -1 = normal cheats, 0 = no infinites, 1 = replenish stack */ public static void cheatItem(ItemStack stack, int button, int mode) { if (!canCheatItem(stack)) return; if (mode == -1 && button == 0 && controlKey() && NEIClientConfig.hasSMPCounterPart()) { for (IInfiniteItemHandler handler : ItemInfo.infiniteHandlers) { if (!handler.canHandleItem(stack)) continue; ItemStack inf = handler.getInfiniteItem(stack); if (inf != null) { giveStack(inf, inf.stackSize, true); return; } } cheatItem(stack, button, 0); } else if (button == 1) { giveStack(stack, 1); } else { if (mode == 1 && stack.stackSize < stack.getMaxStackSize()) { giveStack(stack, stack.getMaxStackSize() - stack.stackSize); } else { giveStack(stack); } } } public static void giveStack(ItemStack itemstack) { giveStack(itemstack, itemstack.stackSize); } public static void giveStack(ItemStack itemstack, int i) { giveStack(itemstack, i, false); } public static void giveStack(ItemStack base, int i, boolean infinite) { ItemStack stack = copyStack(base, i); if (hasSMPCounterPart()) { ItemStack typestack = copyStack(stack, 1); if (!infinite && !canItemFitInInventory(mc().thePlayer, stack) && (mc().currentScreen instanceof GuiContainer)) { GuiContainer gui = getGuiContainer(); final List> handlerSlots; try { GuiInfo.readLock.lock(); handlerSlots = GuiInfo.guiHandlers.stream() .map(handler -> handler.getItemSpawnSlots(gui, typestack)).filter(x -> x != null) .collect(Collectors.toCollection(LinkedList::new)); } finally { GuiInfo.readLock.unlock(); } int increment = typestack.getMaxStackSize(); int given = 0; for (int slotNo : Iterables.concat(handlerSlots)) { Slot slot = gui.inventorySlots.getSlot(slotNo); if (!slot.isItemValid(typestack) || !InventoryUtils.canStack(slot.getStack(), typestack)) continue; int qty = Math.min(stack.stackSize - given, increment); int current = slot.getHasStack() ? slot.getStack().stackSize : 0; qty = Math.min(qty, slot.getSlotStackLimit() - current); ItemStack newStack = copyStack(typestack, qty + current); slot.putStack(newStack); setSlotContents(slotNo, newStack, true); given += qty; if (given >= stack.stackSize) break; } if (given > 0) NEICPH.sendGiveItem(copyStack(typestack, given), false, false); } else NEICPH.sendGiveItem(stack, infinite, true); } else { for (int given = 0; given < stack.stackSize;) { int qty = Math.min(stack.stackSize - given, stack.getMaxStackSize()); sendCommand( getStringSetting("command.item"), mc().thePlayer.getCommandSenderName(), Item.getIdFromItem(stack.getItem()), qty, stack.getItemDamage(), stack.hasTagCompound() ? stack.getTagCompound().toString() : "", Item.itemRegistry.getNameForObject(stack.getItem())); given += qty; } } } public static boolean canItemFitInInventory(EntityPlayer player, ItemStack itemstack) { return InventoryUtils.getInsertibleQuantity(new InventoryRange(player.inventory, 0, 36), itemstack) > 0; } public static List presenceOverlay(List ingredients, List invStacks) { final List states = new ArrayList<>(); final Map remainingFluid = new IdentityHashMap<>(); for (PositionedStack stack : ingredients) { boolean found; if (StackInfo.isFluidDisplayItem(stack.item)) { final FluidStack requiredFluid = StackInfo.getFluid(stack.item); found = requiredFluid != null && consumeFluid(invStacks, remainingFluid, requiredFluid); } else { found = false; for (ItemStack is : invStacks) { if (is.stackSize > 0 && stack.contains(is)) { is.stackSize--; found = true; break; } } } states.add(new ItemOverlayState(stack, found)); } return states; } private static boolean consumeFluid(List invStacks, Map remainingFluid, FluidStack requiredFluid) { long need = requiredFluid.amount; final List matched = new ArrayList<>(); for (ItemStack is : invStacks) { if (need <= 0) { break; } if (is.stackSize <= 0) { continue; } final FluidStack invFluid = StackInfo.getFluid(is); if (invFluid == null || !invFluid.isFluidEqual(requiredFluid)) { continue; } final long available = remainingFluid.computeIfAbsent(is, k -> (long) invFluid.amount * is.stackSize); if (available <= 0) { continue; } matched.add(is); need -= available; } if (need > 0) { return false; } long remainingNeed = requiredFluid.amount; for (ItemStack is : matched) { final long available = remainingFluid.get(is); final long consumed = Math.min(available, remainingNeed); remainingFluid.put(is, available - consumed); remainingNeed -= consumed; if (remainingNeed <= 0) { break; } } return true; } public static boolean shiftKey() { return Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT); } public static boolean controlKey() { if (Minecraft.isRunningOnMac) { return Keyboard.isKeyDown(Keyboard.KEY_LMETA) || Keyboard.isKeyDown(Keyboard.KEY_RMETA); } return Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL); } public static int getGamemode() { if (invCreativeMode()) return 2; else if (mc().playerController.isInCreativeMode()) return 1; else if (mc().playerController.currentGameType.isAdventure()) return 3; else return 0; } public static boolean isValidGamemode(String s) { return s.equals("survival") || canPerformAction(s) && Arrays.asList(getStringArrSetting("inventory.gamemodes")).contains(s); } public static int getNextGamemode() { int mode = getGamemode(); int nmode = mode; while (true) { nmode = (nmode + 1) % NEIActions.gameModes.length; if (nmode == mode || isValidGamemode(NEIActions.gameModes[nmode])) break; } return nmode; } public static void cycleGamemode() { int mode = getGamemode(); int nmode = getNextGamemode(); if (mode == nmode) return; if (hasSMPCounterPart()) NEICPH.sendGamemode(nmode); else sendCommand( getStringSetting("command.creative"), getGameType(nmode), mc().thePlayer.getCommandSenderName()); } public static long getTime() { return mc().theWorld.getWorldInfo().getWorldTime(); } public static void setTime(long l) { mc().theWorld.getWorldInfo().setWorldTime(l); } public static void setHourForward(int hour) { long day = (getTime() / 24000L) * 24000L; long newTime = day + 24000L + hour * 1000; if (hasSMPCounterPart()) NEICPH.sendSetTime(hour); else sendCommand(getStringSetting("command.time"), newTime); } public static void sendCommand(String command, Object... args) { if (command.isEmpty()) return; NumberFormat numberformat = NumberFormat.getIntegerInstance(); numberformat.setGroupingUsed(false); MessageFormat messageformat = new MessageFormat(command); for (int i = 0; i < args.length; i++) if (args[i] instanceof Integer || args[i] instanceof Long) messageformat.setFormatByArgumentIndex(i, numberformat); mc().thePlayer.sendChatMessage(messageformat.format(args)); } public static boolean isRaining() { return mc().theWorld.getWorldInfo().isRaining(); } public static void toggleRaining() { if (hasSMPCounterPart()) NEICPH.sendToggleRain(); else sendCommand(getStringSetting("command.rain"), isRaining() ? 0 : 1); } public static void healPlayer() { if (hasSMPCounterPart()) NEICPH.sendHeal(); else sendCommand(getStringSetting("command.heal"), mc().thePlayer.getCommandSenderName()); } public static void sendChatItemLink(NBTTagCompound payload) { if (hasSMPCounterPart()) NEICPH.sendChatLink(payload); } public static void toggleMagnetMode() { if (hasSMPCounterPart()) NEICPH.sendToggleMagnetMode(); } public static ArrayList concatIntegersToRanges(List damages) { ArrayList ranges = new ArrayList<>(); if (damages.size() == 0) return ranges; Collections.sort(damages); int start = -1; int next = 0; for (Integer i : damages) { if (start == -1) { start = next = i; continue; } if (next + 1 != i) { ranges.add(new int[] { start, next }); start = next = i; continue; } next = i; } ranges.add(new int[] { start, next }); return ranges; } public static ArrayList addIntegersToRanges(List ranges, List damages) { for (int[] range : ranges) for (int integer = range[0]; integer <= range[1]; integer++) damages.add(integer); return concatIntegersToRanges(damages); } public static String cropText(FontRenderer fontRenderer, String text, int containerWidth) { int textWidth = fontRenderer.getStringWidth(text); if (textWidth > containerWidth) { int dots = fontRenderer.getStringWidth("..."); if (containerWidth <= dots) { return "..."; } return fontRenderer.trimStringToWidth(text, containerWidth - dots) + "..."; } return text; } public static boolean safeKeyDown(int keyCode) { try { return Keyboard.isKeyDown(keyCode); } catch (IndexOutOfBoundsException e) { return false; } } public static void setItemQuantity(int i) { world.nbt.setInteger("quantity", i); } public static GuiContainer getGuiContainer() { if (mc().currentScreen instanceof GuiContainer) return (GuiContainer) mc().currentScreen; return null; } public static boolean altKey() { return Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU); } public static String getKeyName(int keyBind, int mouseBind) { return KeyManager.getKeyName(keyBind, mouseBind); } public static void playClickSound() { mc().getSoundHandler() .playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); } public static void reportErrorBuffered(Throwable e, Set buffer, String cause) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); String stackTrace = cause + sw; if (buffer.contains(stackTrace)) return; System.err.println("Error while rendering: " + cause + " (" + e.getMessage() + ")"); e.printStackTrace(); buffer.add(stackTrace); } public static void reportErrorBuffered(Throwable e, Set buffer, ItemStack cause) { if (cause != null) { reportErrorBuffered(e, buffer, cause.toString()); } else { reportErrorBuffered(e, buffer, "null"); } } public static void drawRect(double left, double top, double width, double height, Color color) { final boolean is2DTexture = GL11.glGetBoolean(GL11.GL_TEXTURE_2D); final boolean isBlend = GL11.glGetBoolean(GL11.GL_BLEND); final Tessellator tessellator = Tessellator.instance; GL11.glEnable(GL11.GL_BLEND); GL11.glDisable(GL11.GL_TEXTURE_2D); OpenGlHelper.glBlendFunc(770, 771, 1, 0); GL11.glColor4f(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); tessellator.startDrawingQuads(); tessellator.addVertex(left, top + height, 0.0D); tessellator.addVertex(left + width, top + height, 0.0D); tessellator.addVertex(left + width, top, 0.0D); tessellator.addVertex(left, top, 0.0D); tessellator.draw(); GL11.glColor4f(1, 1, 1, 1); if (is2DTexture) GL11.glEnable(GL11.GL_TEXTURE_2D); if (!isBlend) GL11.glDisable(GL11.GL_BLEND); } public static void drawNEIOverlayText(String text, Rectangle4i rect, float scale, int color, boolean shadow, Alignment alignment) { final float screenScale = mc().currentScreen.width * 1f / mc().displayWidth; final double smallTextScale = Math .max(screenScale, (Math.max(scale, 1f) * (GuiDraw.fontRenderer.getUnicodeFlag() ? 3F / 4F : 1F / 2F))); NEIClientUtils.gl2DRenderContext(() -> { final int width = GuiDraw.fontRenderer.getStringWidth(text); final float partW = rect.w / 2f; final float partH = rect.h / 2f; final double offsetX = Math .ceil(rect.x + partW + partW * alignment.x - (width / 2f * (alignment.x + 1)) * smallTextScale); final double offsetY = Math.ceil( rect.y + partH + partH * alignment.y - (GuiDraw.fontRenderer.FONT_HEIGHT / 2f * (alignment.y + 1)) * smallTextScale); GL11.glTranslated(offsetX, offsetY, 0); GL11.glScaled(smallTextScale, smallTextScale, 1); GuiDraw.fontRenderer.drawString(text, 0, 0, color, shadow); GL11.glScaled(1 / smallTextScale, 1 / smallTextScale, 1); GL11.glTranslated(-1 * offsetX, -1 * offsetY, 0); }); } public static void drawNEIOverlayText(String text, int x, int y) { drawNEIOverlayText(text, new Rectangle4i(x, y, 16, 16), 0.5f, 0xFDD835, false, Alignment.TopLeft); } public static void gl2DRenderContext(Runnable callback) { boolean isLighting = GL11.glGetBoolean(GL11.GL_LIGHTING); boolean isDepthTest = GL11.glGetBoolean(GL11.GL_DEPTH_TEST); boolean isAlphaTest = GL11.glGetBoolean(GL11.GL_ALPHA_TEST); GL11.glDisable(GL11.GL_LIGHTING); GL11.glEnable(GL11.GL_ALPHA_TEST); GL11.glDisable(GL11.GL_DEPTH_TEST); callback.run(); if (isLighting) GL11.glEnable(GL11.GL_LIGHTING); if (isDepthTest) GL11.glEnable(GL11.GL_DEPTH_TEST); if (!isAlphaTest) GL11.glDisable(GL11.GL_ALPHA_TEST); } }