/* * 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.miscfeatures.profileviewer.HoppityPage; import io.github.moulberry.notenoughupdates.profileviewer.data.APIDataJson; 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 lombok.var; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; 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.LinkedHashMap; 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; private boolean onHoppityPage; private final HoppityPage hoppityPage; public static final ItemStack hoppitySkull = Utils.createSkull( "calmwolfs", "d7ac85e6-bd40-359e-a2c5-86082959309e", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvO" + "WE4MTUzOThlN2RhODliMWJjMDhmNjQ2Y2FmYzhlN2I4MTNkYTBiZTBlZWMwY2NlNmQzZWZmNTIwNzgwMTAyNiJ9fX0=" ); private static final LinkedHashMap pageModeIcon = new LinkedHashMap() { { put( "stats", Utils.editItemStackInfo( new ItemStack(Items.book), EnumChatFormatting.GRAY + "Stats", true ) ); put( "hoppity", Utils.editItemStackInfo( hoppitySkull, EnumChatFormatting.GRAY + "Hoppity", true ) ); } }; public ExtraPage(GuiProfileViewer instance) { super(instance); this.hoppityPage = new HoppityPage(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 { int guiLeft = GuiProfileViewer.getGuiLeft(); int guiTop = GuiProfileViewer.getGuiTop(); super.mouseClicked(mouseX, mouseY, mouseButton); int i = ProfileViewerUtils.onSlotToChangePage(mouseX, mouseY, guiLeft, guiTop); switch (i) { case 1: onHoppityPage = false; break; case 2: onHoppityPage = true; break; default: break; } if (onHoppityPage) { return hoppityPage.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 = Utils.getElementAsInt(Utils.getElement( profileInfo, "currencies.essence." + essenceName.replace("ESSENCE_", "") + ".current" ), 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 = Utils.getElementAsInt(Utils.getElement(profileInfo, "player_data.perks." + entry.getKey()), 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(); drawSideButtons(mouseX, mouseY); if (onHoppityPage) { hoppityPage.drawPage(mouseX, mouseY, partialTicks); return; } 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(); APIDataJson data = selectedProfile.getAPIDataJson(); if (data == null) { return; } var player_stats = data.player_stats; var auctions = player_stats.auctions; 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 = data.currencies.coin_purse; float personalBankBalance = data.profile.bank_account; Utils.renderAlignedString( EnumChatFormatting.GOLD + "Bank", EnumChatFormatting.WHITE + StringUtils.shortNumberFormat(bankBalance) + "/" + StringUtils.shortNumberFormat(personalBankBalance), 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, "profile.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 )); int fairySouls = data.fairy_soul.total_collected; boolean cookieBuff = data.profile.cookie_buff_active; 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() + 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 ); Utils.renderAlignedString( EnumChatFormatting.GOLD + "Cookie Buff", (cookieBuff) ? EnumChatFormatting.LIGHT_PURPLE + "Active" : EnumChatFormatting.RED + "Inactive", guiLeft + xStart, guiTop + yStartBottom + yOffset * 5, 76 ); } float auctions_bids = auctions.bids; float auctions_highest_bid = auctions.highest_bid; float auctions_won = auctions.won; float auctions_created = auctions.created; float auctions_gold_spent = auctions.gold_spent; float auctions_gold_earned = auctions.gold_earned; 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 = player_stats.pets.milestone.ores_mined; float pet_milestone_sea_creatures_killed = player_stats.pets.milestone.sea_creatures_killed; float items_fished = player_stats.items_fished.total; float items_fished_treasure = player_stats.items_fished.treasure; float items_fished_large_treasure = player_stats.items_fished.large_treasure; 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(selectedProfile.getProfileJson(), xStart, yStartTop, xOffset, yOffset, mouseX, mouseY); if (topKills == null) { topKills = new TreeMap<>(); JsonObject stats = Utils .getElementOrDefault(profileInfo, "player_stats.kills", new JsonObject()) .getAsJsonObject(); for (Map.Entry entry : stats.entrySet()) { if (entry.getValue().isJsonPrimitive()) { JsonPrimitive prim = (JsonPrimitive) entry.getValue(); if (prim.isNumber()) { String name = WordUtils.capitalizeFully(entry.getKey().replace("_", " ")); Set kills = topKills.computeIfAbsent(prim.getAsInt(), k -> new HashSet<>()); kills.add(name); } } } } if (topDeaths == null) { topDeaths = new TreeMap<>(); JsonObject stats = Utils .getElementOrDefault(profileInfo, "player_stats.deaths", new JsonObject()) .getAsJsonObject(); for (Map.Entry entry : stats.entrySet()) { if (entry.getValue().isJsonPrimitive()) { JsonPrimitive prim = (JsonPrimitive) entry.getValue(); if (prim.isNumber()) { String name = WordUtils.capitalizeFully(entry.getKey().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 indexKills = 0; int skipCountKills = 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 * ((indexKills - skipCountKills) - killScroll); if (!isSearch) skipCountKills++; if (isSearch && killY + 6 < guiTop + yStartTop + 75 && killY >= guiTop + yStartTop) { renderedKills++; Utils.renderAlignedString( EnumChatFormatting.YELLOW + "K: " + killType, EnumChatFormatting.WHITE.toString() + killCount, killDeathX, killY, 76 ); } indexKills++; } } int indexDeaths = 0; int skipCountDeaths = 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 * ((indexDeaths - skipCountDeaths) - deathScroll); if (!isSearch) skipCountDeaths++; if (isSearch && deathY + 6 < guiTop + yStartBottom + 75 && deathY >= guiTop + yStartBottom) { renderedDeaths++; Utils.renderAlignedString( EnumChatFormatting.YELLOW + "D: " + deathType, EnumChatFormatting.WHITE.toString() + deathCount, killDeathX, deathY, 76 ); } indexDeaths++; } } 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; } } } int killsMaxScroll = (indexKills - skipCountKills) - renderedKills; if (killScroll > killsMaxScroll) { killScroll = killsMaxScroll; } int deathsMaxScroll = (indexDeaths - skipCountDeaths) - renderedDeaths; if (deathScroll > deathsMaxScroll) { deathScroll = deathsMaxScroll; } } 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; } private void drawSideButtons(int mouseX, int mouseY) { GlStateManager.enableDepth(); GlStateManager.translate(0, 0, 5); if (onHoppityPage) { Utils.drawPvSideButton(1, pageModeIcon.get("hoppity"), true, getInstance(), mouseX, mouseY); } else { Utils.drawPvSideButton(0, pageModeIcon.get("stats"), true, getInstance(), mouseX, mouseY); } GlStateManager.translate(0, 0, -3); GlStateManager.translate(0, 0, -2); if (!onHoppityPage) { Utils.drawPvSideButton(1, pageModeIcon.get("hoppity"), false, getInstance(), mouseX, mouseY); } else { Utils.drawPvSideButton(0, pageModeIcon.get("stats"), false, getInstance(), mouseX, mouseY); } GlStateManager.disableDepth(); } }