/* * Copyright (C) 2022-2023 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * * NotEnoughUpdates is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * NotEnoughUpdates is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see . */ package io.github.moulberry.notenoughupdates.profileviewer; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.core.util.StringUtils; import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils; import io.github.moulberry.notenoughupdates.profileviewer.weight.weight.Weight; import io.github.moulberry.notenoughupdates.util.Constants; import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.ResourceLocation; import org.apache.commons.lang3.text.WordUtils; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class ExtraPage extends GuiProfileViewerPage { private static final ResourceLocation pv_extra = new ResourceLocation("notenoughupdates:pv_extra.png"); private static final List skills = Arrays.asList( "taming", "mining", "foraging", "enchanting", "farming", "combat", "fishing", "alchemy", "carpentry" ); // TODO: why is this hardcoded private TreeMap> topKills = null; private TreeMap> topDeaths = null; private int deathScroll = 0; private int killScroll = 0; private boolean clickedLoadGuildInfoButton = false; public ExtraPage(GuiProfileViewer instance) { super(instance); getInstance().killDeathSearchTextField.setSize(80, 12); } @Override public void keyTyped(char typedChar, int keyCode) throws IOException { super.keyTyped(typedChar, keyCode); if (getInstance().killDeathSearchTextField.getFocus()) { getInstance().killDeathSearchTextField.keyTyped(typedChar, keyCode); killScroll = 0; deathScroll = 0; } } @Override public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { super.mouseClicked(mouseX, mouseY, mouseButton); // Dimensions: X: guiLeft + xStart + xOffset * 3, Y: guiTop + yStartBottom + 77, Width: 80, Height: 12 if (mouseX >= GuiProfileViewer.getGuiLeft() + 22 + 103 * 3 && mouseX <= GuiProfileViewer.getGuiLeft() + 22 + 103 * 3 + 80 && mouseY >= GuiProfileViewer.getGuiTop() + 105 + 77 && mouseY <= GuiProfileViewer.getGuiTop() + 105 + 77 + 12) { getInstance().killDeathSearchTextField.mouseClicked(mouseX, mouseY, mouseButton); getInstance().playerNameTextField.otherComponentClick(); return true; } getInstance().killDeathSearchTextField.otherComponentClick(); return false; } public void drawEssence( JsonObject profileInfo, float xStart, float yStartTop, float xOffset, float yOffset, float mouseX, float mouseY ) { if (Constants.PARENTS == null || !Constants.PARENTS.has("ESSENCE_WITHER")) { Utils.showOutdatedRepoNotification("parents.json or missing ESSENCE_WITHER"); return; } int guiLeft = GuiProfileViewer.getGuiLeft(); int guiTop = GuiProfileViewer.getGuiTop(); yStartTop += 77; JsonArray essenceArray = new JsonArray(); essenceArray.addAll(Constants.PARENTS.get("ESSENCE_WITHER").getAsJsonArray()); //add wither essence since it's not part of the parents array essenceArray.add(new JsonPrimitive("ESSENCE_WITHER")); for (int i = 0; i < essenceArray.size(); i++) { JsonElement jsonElement = essenceArray.get(i); String essenceName = jsonElement.getAsString(); TreeMap itemInformation = NotEnoughUpdates.INSTANCE.manager.getItemInformation(); if (!itemInformation.containsKey(essenceName)) { Utils.showOutdatedRepoNotification(essenceName); return; } String displayName = itemInformation.get(essenceName).getAsJsonObject().get("displayname").getAsString(); int essenceNumber = profileInfo.has(essenceName.toLowerCase()) ? profileInfo.get(essenceName.toLowerCase()).getAsInt() : 0; Utils.renderAlignedString( EnumChatFormatting.GOLD + displayName, EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(essenceNumber, 0), guiLeft + xStart + xOffset, guiTop + yStartTop + (yOffset - 1) * i, 76 ); if (Constants.ESSENCESHOPS == null) return; JsonObject essenceShops = Constants.ESSENCESHOPS; if (mouseX >= guiLeft + xStart + xOffset && mouseX <= guiLeft + xStart + xOffset + 76 && mouseY >= guiTop + yStartTop + (yOffset - 1) * i && mouseY <= guiTop + yStartTop + (yOffset - 1) * i + 10) { getInstance().tooltipToDisplay = new ArrayList<>(); if (essenceShops.get(essenceName) == null) continue; for (Map.Entry entry : essenceShops.get(essenceName).getAsJsonObject().entrySet()) { int perkTier = (profileInfo.has("perks") && profileInfo.get("perks").getAsJsonObject().has(entry.getKey()) ? profileInfo .get("perks") .getAsJsonObject() .get(entry.getKey()) .getAsInt() : 0); int max = entry.getValue().getAsJsonObject().get("costs").getAsJsonArray().size(); EnumChatFormatting formatting = perkTier == max ? EnumChatFormatting.GREEN : EnumChatFormatting.AQUA; String name = entry.getValue().getAsJsonObject().get("name").getAsString(); getInstance().tooltipToDisplay.add(EnumChatFormatting.GOLD + name + ": " + formatting + perkTier + "/" + max); } } } } @Override public void drawPage(int mouseX, int mouseY, float partialTicks) { int guiLeft = GuiProfileViewer.getGuiLeft(); int guiTop = GuiProfileViewer.getGuiTop(); Minecraft.getMinecraft().getTextureManager().bindTexture(pv_extra); Utils.drawTexturedRect(guiLeft, guiTop, getInstance().sizeX, getInstance().sizeY, GL11.GL_NEAREST); SkyblockProfiles.SkyblockProfile selectedProfile = getSelectedProfile(); if (selectedProfile == null) { return; } JsonObject profileInfo = selectedProfile.getProfileJson(); Map skyblockInfo = selectedProfile.getLevelingInfo(); float xStart = 22; float xOffset = 103; float yStartTop = 27; float yStartBottom = 105; float yOffset = 10; float bankBalance = Utils.getElementAsFloat(Utils.getElement(selectedProfile.getOuterProfileJson(), "banking.balance"), 0); float purseBalance = Utils.getElementAsFloat(Utils.getElement(profileInfo, "coin_purse"), 0); Utils.renderAlignedString( EnumChatFormatting.GOLD + "Bank Balance", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(bankBalance), guiLeft + xStart, guiTop + yStartTop, 76 ); Utils.renderAlignedString( EnumChatFormatting.GOLD + "Purse", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(purseBalance), guiLeft + xStart, guiTop + yStartTop + yOffset, 76 ); { String first_join = getTimeSinceString(profileInfo, "first_join"); if (first_join != null) { Utils.renderAlignedString( EnumChatFormatting.AQUA + "Joined", EnumChatFormatting.WHITE + first_join, guiLeft + xStart, guiTop + yStartTop + yOffset * 2, 76 ); } } JsonObject guildInfo; if (GuiProfileViewer.getProfile().isPlayerInGuild()) { guildInfo = clickedLoadGuildInfoButton ? GuiProfileViewer.getProfile().getOrLoadGuildInformation(null) : null; } else { guildInfo = new JsonObject(); guildInfo.add("name", new JsonPrimitive("N/A")); } boolean shouldRenderGuild = guildInfo != null && guildInfo.has("name"); // Render the info when the button has been clicked if (shouldRenderGuild) { Utils.renderAlignedString( EnumChatFormatting.AQUA + "Guild", EnumChatFormatting.WHITE + guildInfo.get("name").getAsString(), guiLeft + xStart, guiTop + yStartTop + yOffset * 3, 76 ); } else { // Render a button to click to load the guild info Rectangle buttonRect = new Rectangle( (int) (guiLeft + xStart - 1), (int) (guiTop + yStartTop + yOffset * 3), 78, 12 ); RenderUtils.drawFloatingRectWithAlpha(buttonRect.getX(), buttonRect.getY(), buttonRect.getWidth(), buttonRect.getHeight(), 100, true ); Utils.renderShadowedString( clickedLoadGuildInfoButton ? EnumChatFormatting.AQUA + "Loading..." : EnumChatFormatting.WHITE + "Load Guild Info", guiLeft + xStart + 38, guiTop + yStartTop + yOffset * 3 + 2, 70 ); if (Mouse.getEventButtonState() && Utils.isWithinRect(mouseX, mouseY, buttonRect)) { clickedLoadGuildInfoButton = true; } } GuiProfileViewer.pronouns.peekValue().flatMap(it -> it).ifPresent(choice -> Utils.renderAlignedString( EnumChatFormatting.GREEN + "Pronouns", EnumChatFormatting.WHITE + String.join(" / ", choice.render()), guiLeft + xStart, guiTop + yStartTop + yOffset * 4 + (shouldRenderGuild ? 0 : 5), 76 )); float fairySouls = Utils.getElementAsFloat(Utils.getElement(profileInfo, "fairy_souls_collected"), 0); int fairySoulMax = 227; if (Constants.FAIRYSOULS != null && Constants.FAIRYSOULS.has("Max Souls")) { fairySoulMax = Constants.FAIRYSOULS.get("Max Souls").getAsInt(); } Utils.renderAlignedString( EnumChatFormatting.LIGHT_PURPLE + "Fairy Souls", EnumChatFormatting.WHITE.toString() + (int) fairySouls + "/" + fairySoulMax, guiLeft + xStart, guiTop + yStartBottom, 76 ); if (skyblockInfo != null) { float totalSkillLVL = 0; float totalTrueSkillLVL = 0; float totalSlayerLVL = 0; float totalSkillCount = 0; float totalSlayerCount = 0; float totalSlayerXP = 0; for (Map.Entry entry : skyblockInfo.entrySet()) { if (skills.contains(entry.getKey())) { totalSkillLVL += entry.getValue().level; totalTrueSkillLVL += Math.floor(entry.getValue().level); totalSkillCount++; } else if (Weight.SLAYER_NAMES.contains(entry.getKey())) { totalSlayerLVL += entry.getValue().level; totalSlayerCount++; totalSlayerXP += entry.getValue().totalXp; } } float avgSkillLVL = totalSkillLVL / totalSkillCount; float avgTrueSkillLVL = totalTrueSkillLVL / totalSkillCount; float avgSlayerLVL = totalSlayerLVL / totalSlayerCount; Utils.renderAlignedString( EnumChatFormatting.RED + "AVG Skill LVL", selectedProfile.skillsApiEnabled() ? EnumChatFormatting.WHITE.toString() + Math.floor(avgSkillLVL * 10) / 10 : EnumChatFormatting.RED + "API OFF!", guiLeft + xStart, guiTop + yStartBottom + yOffset, 76 ); Utils.renderAlignedString( EnumChatFormatting.RED + "True AVG Skill LVL", selectedProfile.skillsApiEnabled() ? EnumChatFormatting.WHITE.toString() + Math.floor(avgTrueSkillLVL * 10) / 10 : EnumChatFormatting.RED + "API OFF!", guiLeft + xStart, guiTop + yStartBottom + yOffset * 2, 76 ); Utils.renderAlignedString( EnumChatFormatting.RED + "AVG Slayer LVL", EnumChatFormatting.WHITE.toString() + Math.floor(avgSlayerLVL * 10) / 10, guiLeft + xStart, guiTop + yStartBottom + yOffset * 3, 76 ); Utils.renderAlignedString( EnumChatFormatting.RED + "Total Slayer XP", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(totalSlayerXP), guiLeft + xStart, guiTop + yStartBottom + yOffset * 4, 76 ); } float auctions_bids = Utils.getElementAsFloat(Utils.getElement(profileInfo, "stats.auctions_bids"), 0); float auctions_highest_bid = Utils.getElementAsFloat( Utils.getElement(profileInfo, "stats.auctions_highest_bid"), 0 ); float auctions_won = Utils.getElementAsFloat(Utils.getElement(profileInfo, "stats.auctions_won"), 0); float auctions_created = Utils.getElementAsFloat(Utils.getElement(profileInfo, "stats.auctions_created"), 0); float auctions_gold_spent = Utils.getElementAsFloat(Utils.getElement(profileInfo, "stats.auctions_gold_spent"), 0); float auctions_gold_earned = Utils.getElementAsFloat( Utils.getElement(profileInfo, "stats.auctions_gold_earned"), 0 ); Utils.renderAlignedString( EnumChatFormatting.DARK_PURPLE + "Auction Bids", EnumChatFormatting.WHITE.toString() + (int) auctions_bids, guiLeft + xStart + xOffset, guiTop + yStartTop, 76 ); Utils.renderAlignedString( EnumChatFormatting.DARK_PURPLE + "Highest Bid", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(auctions_highest_bid), guiLeft + xStart + xOffset, guiTop + yStartTop + yOffset, 76 ); Utils.renderAlignedString( EnumChatFormatting.DARK_PURPLE + "Auctions Won", EnumChatFormatting.WHITE.toString() + (int) auctions_won, guiLeft + xStart + xOffset, guiTop + yStartTop + yOffset * 2, 76 ); Utils.renderAlignedString( EnumChatFormatting.DARK_PURPLE + "Auctions Created", EnumChatFormatting.WHITE.toString() + (int) auctions_created, guiLeft + xStart + xOffset, guiTop + yStartTop + yOffset * 3, 76 ); Utils.renderAlignedString( EnumChatFormatting.DARK_PURPLE + "Gold Spent", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(auctions_gold_spent), guiLeft + xStart + xOffset, guiTop + yStartTop + yOffset * 4, 76 ); Utils.renderAlignedString( EnumChatFormatting.DARK_PURPLE + "Gold Earned", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(auctions_gold_earned), guiLeft + xStart + xOffset, guiTop + yStartTop + yOffset * 5, 76 ); float pet_milestone_ores_mined = Utils.getElementAsFloat(Utils.getElement( profileInfo, "stats.pet_milestone_ores_mined" ), 0); float pet_milestone_sea_creatures_killed = Utils.getElementAsFloat( Utils.getElement(profileInfo, "stats.pet_milestone_sea_creatures_killed"), 0 ); float items_fished = Utils.getElementAsFloat(Utils.getElement(profileInfo, "stats.items_fished"), 0); float items_fished_treasure = Utils.getElementAsFloat( Utils.getElement(profileInfo, "stats.items_fished_treasure"), 0 ); float items_fished_large_treasure = Utils.getElementAsFloat(Utils.getElement( profileInfo, "stats.items_fished_large_treasure" ), 0); Utils.renderAlignedString( EnumChatFormatting.GREEN + "Ores Mined", EnumChatFormatting.WHITE.toString() + (int) pet_milestone_ores_mined, guiLeft + xStart + xOffset * 2, guiTop + yStartTop, 76 ); Utils.renderAlignedString( EnumChatFormatting.GREEN + "Sea Creatures Killed", EnumChatFormatting.WHITE.toString() + (int) pet_milestone_sea_creatures_killed, guiLeft + xStart + xOffset * 2, guiTop + yStartTop + yOffset, 76 ); Utils.renderAlignedString( EnumChatFormatting.GREEN + "Items Fished", EnumChatFormatting.WHITE.toString() + (int) items_fished, guiLeft + xStart + xOffset * 2, guiTop + yStartTop + yOffset * 3, 76 ); Utils.renderAlignedString( EnumChatFormatting.GREEN + "Treasures Fished", EnumChatFormatting.WHITE.toString() + (int) items_fished_treasure, guiLeft + xStart + xOffset * 2, guiTop + yStartTop + yOffset * 4, 76 ); Utils.renderAlignedString( EnumChatFormatting.GREEN + "Large Treasures", EnumChatFormatting.WHITE.toString() + (int) items_fished_large_treasure, guiLeft + xStart + xOffset * 2, guiTop + yStartTop + yOffset * 5, 76 ); drawEssence(profileInfo, xStart, yStartTop, xOffset, yOffset, mouseX, mouseY); if (topKills == null) { topKills = new TreeMap<>(); JsonObject stats = profileInfo.get("stats").getAsJsonObject(); for (Map.Entry entry : stats.entrySet()) { if (entry.getKey().startsWith("kills_")) { if (entry.getValue().isJsonPrimitive()) { JsonPrimitive prim = (JsonPrimitive) entry.getValue(); if (prim.isNumber()) { String name = WordUtils.capitalizeFully(entry.getKey().substring("kills_".length()).replace("_", " ")); Set kills = topKills.computeIfAbsent(prim.getAsInt(), k -> new HashSet<>()); kills.add(name); } } } } } if (topDeaths == null) { topDeaths = new TreeMap<>(); JsonObject stats = profileInfo.get("stats").getAsJsonObject(); for (Map.Entry entry : stats.entrySet()) { if (entry.getKey().startsWith("deaths_")) { if (entry.getValue().isJsonPrimitive()) { JsonPrimitive prim = (JsonPrimitive) entry.getValue(); if (prim.isNumber()) { String name = WordUtils.capitalizeFully(entry.getKey().substring("deaths_".length()).replace("_", " ")); Set deaths = topDeaths.computeIfAbsent(prim.getAsInt(), k -> new HashSet<>()); deaths.add(name); } } } } } getInstance().killDeathSearchTextField.render( (int) (guiLeft + xStart + xOffset * 3), (int) (guiTop + yStartBottom + 77) ); float killDeathX = guiLeft + xStart + xOffset * 3; int index = 0; int skipCount = 0; int renderedKills = 0; for (int killCount : topKills.descendingKeySet()) { Set kills = topKills.get(killCount); for (String killType : kills) { boolean isSearch = getInstance().killDeathSearchTextField.getText().isEmpty() || killType.toLowerCase(Locale.ROOT).contains( getInstance().killDeathSearchTextField.getText().toLowerCase(Locale.ROOT)); float killY = guiTop + yStartTop + yOffset * ((index - skipCount) - killScroll); if (!isSearch) skipCount++; if (isSearch && killY + 6 < guiTop + yStartTop + 65 && killY >= guiTop + yStartTop) { renderedKills++; Utils.renderAlignedString( EnumChatFormatting.YELLOW + "K: " + killType, EnumChatFormatting.WHITE.toString() + killCount, killDeathX, killY, 76 ); } index++; } } index = 0; skipCount = 0; int renderedDeaths = 0; for (int deathCount : topDeaths.descendingKeySet()) { Set deaths = topDeaths.get(deathCount); for (String deathType : deaths) { boolean isSearch = getInstance().killDeathSearchTextField.getText().isEmpty() || deathType.toLowerCase(Locale.ROOT).contains( getInstance().killDeathSearchTextField.getText().toLowerCase(Locale.ROOT)); float deathY = guiTop + yStartBottom + yOffset * ((index - skipCount) - deathScroll); if (!isSearch) skipCount++; if (isSearch && deathY + 6 < guiTop + yStartBottom + 65 && deathY >= guiTop + yStartBottom) { renderedDeaths++; Utils.renderAlignedString( EnumChatFormatting.YELLOW + "D: " + deathType, EnumChatFormatting.WHITE.toString() + deathCount, killDeathX, deathY, 76 ); } index++; } } int mouseDWheel = Mouse.getDWheel(); if (mouseX >= killDeathX && mouseX <= killDeathX + 76) { if (mouseY >= guiTop + yStartTop && mouseY <= guiTop + yStartTop + 65) { if (mouseDWheel > 0) { killScroll -= 1; } else if (mouseDWheel < 0) { killScroll += 1; } if (killScroll < 0) { killScroll = 0; } } else if (mouseY >= guiTop + yStartBottom && mouseY <= guiTop + yStartBottom + 65) { if (mouseDWheel > 0) { deathScroll -= 1; } else if (mouseDWheel < 0) { deathScroll += 1; } if (deathScroll < 0) { deathScroll = 0; } } } if (killScroll > renderedDeaths) { killScroll = renderedDeaths; } if (deathScroll > renderedKills) { deathScroll = renderedKills; } } private String getTimeSinceString(JsonObject profileInfo, String path) { JsonElement lastSaveElement = Utils.getElement(profileInfo, path); if (lastSaveElement != null && lastSaveElement.isJsonPrimitive()) { return Utils.timeSinceMillisecond(lastSaveElement.getAsLong()); } return null; } @Override public void resetCache() { topDeaths = null; topKills = null; } }