package slimeknights.tconstruct.library.client; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import org.lwjgl.opengl.GL11; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.VertexBuffer; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.item.ItemStack; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import slimeknights.tconstruct.TConstruct; import slimeknights.tconstruct.common.TinkerNetwork; import slimeknights.tconstruct.library.TinkerRegistry; import slimeknights.tconstruct.library.Util; import slimeknights.tconstruct.library.materials.Material; import slimeknights.tconstruct.library.smeltery.CastingRecipe; import slimeknights.tconstruct.library.smeltery.ICastingRecipe; import slimeknights.tconstruct.library.smeltery.SmelteryTank; import slimeknights.tconstruct.smeltery.TinkerSmeltery; import slimeknights.tconstruct.smeltery.client.SmelteryRenderer; import slimeknights.tconstruct.smeltery.network.SmelteryFluidClicked; public class GuiUtil { private GuiUtil() {} protected static Minecraft mc = Minecraft.getMinecraft(); /** Renders the given texture tiled into a GUI */ public static void renderTiledTextureAtlas(int x, int y, int width, int height, float depth, TextureAtlasSprite sprite) { Tessellator tessellator = Tessellator.getInstance(); VertexBuffer worldrenderer = tessellator.getBuffer(); worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); mc.renderEngine.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); putTiledTextureQuads(worldrenderer, x, y, width, height, depth, sprite); tessellator.draw(); } public static void renderTiledFluid(int x, int y, int width, int height, float depth, FluidStack fluidStack) { TextureAtlasSprite fluidSprite = mc.getTextureMapBlocks().getAtlasSprite(fluidStack.getFluid().getStill(fluidStack).toString()); RenderUtil.setColorRGBA(fluidStack.getFluid().getColor(fluidStack)); renderTiledTextureAtlas(x, y, width, height, depth, fluidSprite); } /** Adds a quad to the rendering pipeline. Call startDrawingQuads beforehand. You need to call draw() yourself. */ public static void putTiledTextureQuads(VertexBuffer renderer, int x, int y, int width, int height, float depth, TextureAtlasSprite sprite) { float u1 = sprite.getMinU(); float v1 = sprite.getMinV(); // tile vertically do { int renderHeight = Math.min(sprite.getIconHeight(), height); height -= renderHeight; float v2 = sprite.getInterpolatedV((16f * renderHeight) / (float) sprite.getIconHeight()); // we need to draw the quads per width too int x2 = x; int width2 = width; // tile horizontally do { int renderWidth = Math.min(sprite.getIconWidth(), width2); width2 -= renderWidth; float u2 = sprite.getInterpolatedU((16f * renderWidth) / (float) sprite.getIconWidth()); renderer.pos(x2, y, depth).tex(u1, v1).endVertex(); renderer.pos(x2, y + renderHeight, depth).tex(u1, v2).endVertex(); renderer.pos(x2 + renderWidth, y + renderHeight, depth).tex(u2, v2).endVertex(); renderer.pos(x2 + renderWidth, y, depth).tex(u2, v1).endVertex(); x2 += renderWidth; } while(width2 > 0); y += renderHeight; } while(height > 0); } /* GUI Tanks */ /** @deprecated Will be removed next version */ @Deprecated public static List<String> drawTankTooltip(SmelteryTank tank, int mouseX, int mouseY, int xmin, int ymin, int xmax, int ymax) { return getTankTooltip(tank, mouseX, mouseY, xmin, ymin, xmax, ymax); } public static List<String> getTankTooltip(SmelteryTank tank, int mouseX, int mouseY, int xmin, int ymin, int xmax, int ymax) { // Liquids if(xmin <= mouseX && mouseX < xmax && ymin <= mouseY && mouseY < ymax) { FluidStack hovered = getFluidHovered(tank, ymax - mouseY - 1, ymax - ymin); List<String> text = Lists.newArrayList(); Consumer<Integer> stringFn = Util.isShiftKeyDown() ? (i) -> GuiUtil.amountToString(i, text) : (i) -> GuiUtil.amountToIngotString(i, text); if(hovered == null) { int usedCap = tank.getFluidAmount(); int maxCap = tank.getCapacity(); text.add(TextFormatting.WHITE + Util.translate("gui.smeltery.capacity")); stringFn.accept(maxCap); text.add(Util.translateFormatted("gui.smeltery.capacity_available")); stringFn.accept(maxCap - usedCap); text.add(Util.translateFormatted("gui.smeltery.capacity_used")); stringFn.accept(usedCap); if(!Util.isShiftKeyDown()) { text.add(""); text.add(Util.translate("tooltip.tank.holdShift")); } } else { text.add(TextFormatting.WHITE + hovered.getLocalizedName()); GuiUtil.liquidToString(hovered, text); } return text; } return null; } private static FluidStack getFluidHovered(SmelteryTank tank, int y, int height) { int[] heights = calcLiquidHeights(tank.getFluids(), tank.getCapacity(), height); for(int i = 0; i < heights.length; i++) { if(y < heights[i]) { return tank.getFluids().get(i); } y -= heights[i]; } return null; } private static int[] calcLiquidHeights(List<FluidStack> liquids, int capacity, int height) { return SmelteryRenderer.calcLiquidHeights(liquids, capacity, height, 3); } public static void drawGuiTank(SmelteryTank liquids, int x, int y, int w, int height, float zLevel) { // draw liquids if(liquids.getFluidAmount() > 0) { int capacity = Math.max(liquids.getFluidAmount(), liquids.getCapacity()); int[] heights = calcLiquidHeights(liquids.getFluids(), capacity, height); int bottom = y + w; for(int i = 0; i < heights.length; i++) { int h = heights[i]; FluidStack liquid = liquids.getFluids().get(i); renderTiledFluid(x, bottom - h, w, h, zLevel, liquid); bottom -= h; } } } public static FluidStack getFluidStackAtPosition(SmelteryTank tank, int mouseX, int mouseY, int xmin, int ymin, int xmax, int ymax) { return getFluidStackIndexAtPosition(tank, mouseX, mouseY, xmin, ymin, xmax, ymax).map(tank.getFluids()::get).orElse(null); } public static void handleTankClick(SmelteryTank tank, int mouseX, int mouseY, int xmin, int ymin, int xmax, int ymax) { getFluidStackIndexAtPosition(tank, mouseX, mouseY, xmin, ymin, xmax, ymax).ifPresent( i -> TinkerNetwork.sendToServer(new SmelteryFluidClicked(i)) ); } public static Optional<Integer> getFluidStackIndexAtPosition(SmelteryTank tank, int mouseX, int mouseY, int xmin, int ymin, int xmax, int ymax) { if(xmin <= mouseX && mouseX < xmax && ymin <= mouseY && mouseY < ymax) { int[] heights = calcLiquidHeights(tank.getFluids(), tank.getCapacity(), ymax - ymin); int y = ymax - mouseY - 1; for(int i = 0; i < heights.length; i++) { if(y < heights[i]) { return Optional.of(i); } y -= heights[i]; } } return Optional.empty(); } /* Fluid amount displays */ private static Map<Fluid, List<FluidGuiEntry>> fluidGui = Maps.newHashMap(); private static boolean smelteryLoaded = TConstruct.pulseManager.isPulseLoaded(TinkerSmeltery.PulseId); /** * Adds information for the tooltip based on the fluid stacks size. * * @param fluid Input fluid stack * @param text Text to add information to. */ public static void liquidToString(FluidStack fluid, List<String> text) { int amount = fluid.amount; if(smelteryLoaded && !Util.isShiftKeyDown()) { List<FluidGuiEntry> entries = fluidGui.get(fluid.getFluid()); if(entries == null) { entries = calcFluidGuiEntries(fluid.getFluid()); fluidGui.put(fluid.getFluid(), entries); } for(FluidGuiEntry entry : entries) { amount = calcLiquidText(amount, entry.amount, entry.getText(), text); } } // standard display stuff: bucket amounts amountToString(amount, text); } public static void amountToIngotString(int amount, List<String> text) { amount = calcLiquidText(amount, Material.VALUE_Ingot, Util.translate("gui.smeltery.liquid.ingot"), text); amountToString(amount, text); } /** * Adds information to the tooltip based on the fluid amount * @param amount Fluid amount * @param text Text to add information to. */ public static void amountToString(int amount, List<String> text) { amount = calcLiquidText(amount, 1000000, Util.translate("gui.smeltery.liquid.kilobucket"), text); amount = calcLiquidText(amount, 1000, Util.translate("gui.smeltery.liquid.bucket"), text); calcLiquidText(amount, 1, Util.translate("gui.smeltery.liquid.millibucket"), text); } private static List<FluidGuiEntry> calcFluidGuiEntries(Fluid fluid) { List<FluidGuiEntry> list = Lists.newArrayList(); // go through all casting recipes for the fluids and check for known "units" like blocks, ingots,... for(ICastingRecipe irecipe : TinkerRegistry.getAllBasinCastingRecipes()) { if(irecipe instanceof CastingRecipe) { CastingRecipe recipe = (CastingRecipe) irecipe; // search for a block recipe if(recipe.getFluid().getFluid() == fluid && recipe.cast == null) { // it's a block that is cast solely from the material, using no cast, therefore it's a block made out of the material list.add(new FluidGuiEntry(recipe.getFluid().amount, "gui.smeltery.liquid.block")); } } } // table casting for(ICastingRecipe irecipe : TinkerRegistry.getAllTableCastingRecipes()) { if(irecipe instanceof CastingRecipe) { CastingRecipe recipe = (CastingRecipe) irecipe; if(recipe.getFluid().getFluid() == fluid && recipe.cast != null) { // nugget if(recipe.cast.matches(new ItemStack[]{TinkerSmeltery.castNugget}) != null) { list.add(new FluidGuiEntry(recipe.getFluid().amount, "gui.smeltery.liquid.nugget")); } // ingot if(recipe.cast.matches(new ItemStack[]{TinkerSmeltery.castIngot}) != null) { list.add(new FluidGuiEntry(recipe.getFluid().amount, "gui.smeltery.liquid.ingot")); } // gem if(recipe.cast.matches(new ItemStack[]{TinkerSmeltery.castGem}) != null) { list.add(new FluidGuiEntry(recipe.getFluid().amount, "gui.smeltery.liquid.gem")); } } } } // sort by amount descending because the order in which they're accessed is important since it changes the remaining value during processing Collections.sort(list, new Comparator<FluidGuiEntry>() { @Override public int compare(FluidGuiEntry o1, FluidGuiEntry o2) { return o2.amount - o1.amount; } }); return ImmutableList.copyOf(list); } private static int calcLiquidText(int amount, int divider, String unit, List<String> text) { int full = amount / divider; if(full > 0) { text.add(String.format("%s%d %s", TextFormatting.GRAY, full, unit)); } return amount % divider; } private static class FluidGuiEntry { public final int amount; public final String unlocName; private FluidGuiEntry(int amount, String unlocName) { this.amount = amount; this.unlocName = unlocName; } public String getText() { return Util.translate(unlocName); } } }