From 5935206fd2a20eb8d9d17e68ca4e5d5c754f6bd0 Mon Sep 17 00:00:00 2001 From: jani270 <69345714+jani270@users.noreply.github.com> Date: Tue, 26 Dec 2023 10:25:52 +0100 Subject: Added null check for bestiary and moved the files (#982) --- .../profileviewer/GuiProfileViewer.java | 2 +- .../profileviewer/SkyblockProfiles.java | 2 +- .../profileviewer/bestiary/BestiaryData.kt | 273 ------------- .../profileviewer/bestiary/BestiaryPage.kt | 424 --------------------- .../profileviewer/bestiary/BestiaryData.kt | 277 ++++++++++++++ .../profileviewer/bestiary/BestiaryPage.kt | 424 +++++++++++++++++++++ 6 files changed, 703 insertions(+), 699 deletions(-) delete mode 100644 src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryData.kt delete mode 100644 src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryPage.kt create mode 100644 src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryData.kt create mode 100644 src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryPage.kt diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java index 824e1995..21bdfa43 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java @@ -23,7 +23,7 @@ import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.core.util.StringUtils; import io.github.moulberry.notenoughupdates.cosmetics.ShaderManager; import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField; -import io.github.moulberry.notenoughupdates.profileviewer.bestiary.BestiaryPage; +import io.github.moulberry.notenoughupdates.miscfeatures.profileviewer.bestiary.BestiaryPage; import io.github.moulberry.notenoughupdates.profileviewer.rift.RiftPage; import io.github.moulberry.notenoughupdates.profileviewer.trophy.TrophyFishPage; import io.github.moulberry.notenoughupdates.profileviewer.weight.weight.DungeonsWeight; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/SkyblockProfiles.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/SkyblockProfiles.java index aac33cfa..f5e22e0a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/SkyblockProfiles.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/SkyblockProfiles.java @@ -26,7 +26,7 @@ import com.google.gson.JsonPrimitive; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.events.ProfileDataLoadedEvent; import io.github.moulberry.notenoughupdates.miscfeatures.PetInfoOverlay; -import io.github.moulberry.notenoughupdates.profileviewer.bestiary.BestiaryData; +import io.github.moulberry.notenoughupdates.miscfeatures.profileviewer.bestiary.BestiaryData; import io.github.moulberry.notenoughupdates.profileviewer.weight.senither.SenitherWeight; import io.github.moulberry.notenoughupdates.profileviewer.weight.weight.Weight; import io.github.moulberry.notenoughupdates.util.Constants; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryData.kt b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryData.kt deleted file mode 100644 index a548678e..00000000 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryData.kt +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 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.bestiary - -import com.google.gson.JsonObject -import io.github.moulberry.notenoughupdates.util.Constants -import io.github.moulberry.notenoughupdates.util.ItemUtils -import io.github.moulberry.notenoughupdates.util.Utils -import io.github.moulberry.notenoughupdates.util.roundToDecimals -import kotlin.math.min - -object BestiaryData { - private val categoriesToParse = listOf( - "dynamic", - "hub", - "farming_1", - "garden", - "combat_1", - "combat_3", - "crimson_isle", - "mining_2", - "mining_3", - "crystal_hollows", - "foraging_1", - "spooky_festival", - "mythological_creatures", - "jerry", - "kuudra", - "catacombs", - "fishing" - ) - - /** - * Calculates the sum of all individual tiers for this profile - * - * @param computedCategories List of parsed categories - * @see BestiaryPage.parseBestiaryData - */ - @JvmStatic - fun calculateTotalBestiaryTiers(computedCategories: List): Int { - var tiers = 0.0 - computedCategories.forEach { - tiers += countTotalLevels(it) - } - return tiers.toInt() - } - - /** - * Calculate the skyblock xp awarded for the given bestiary progress - */ - @JvmStatic - fun calculateBestiarySkyblockXp(profileInfo: JsonObject): Int { - val totalTiers = calculateTotalBestiaryTiers(parseBestiaryData(profileInfo)) - var skyblockXp = 0 - - val slayingTask = Constants.SBLEVELS.getAsJsonObject("slaying_task") ?: return 0 - val xpPerTier = (slayingTask.get("bestiary_family_xp") ?: return 0).asInt - val xpPerMilestone = slayingTask.get("bestiary_milestone_xp").asInt - val maxXp = slayingTask.get("bestiary_progress").asInt - - skyblockXp += totalTiers * xpPerTier - - val milestones = (totalTiers / 100) - skyblockXp += milestones * xpPerMilestone - - return min(skyblockXp, maxXp) - } - - private fun countTotalLevels(category: Category): Int { - var levels = 0 - for (mob in category.mobs) { - levels += mob.mobLevelData.level - } - category.subCategories.forEach { levels += countTotalLevels(it) } - return levels - } - - /** - * Checks if a user profile has migrated. - * - * @param profileInfo skyblock profile information - */ - fun hasMigrated(profileInfo: JsonObject): Boolean { - val bestiaryObject = profileInfo.getAsJsonObject("bestiary") ?: return false - - return (bestiaryObject.get("migration") ?: return false).asBoolean - } - - /** - * Parse the bestiary data for the profile. Categories are taken from the `constants/bestiary.json` repo file - * - * @param profileInfo the JsonObject containing the bestiary data - */ - @JvmStatic - fun parseBestiaryData(profileInfo: JsonObject): MutableList { - if (!hasMigrated(profileInfo) || Constants.BESTIARY == null) { - return mutableListOf() - } - - val parsedCategories = mutableListOf() - - val apiKills = profileInfo.getAsJsonObject("bestiary")!!.getAsJsonObject("kills") ?: return mutableListOf() - val apiDeaths = profileInfo.getAsJsonObject("bestiary").getAsJsonObject("deaths") ?: return mutableListOf() - val killsMap: HashMap = HashMap() - for (entry in apiKills.entrySet()) { - killsMap[entry.key] = entry.value.asInt - } - val deathsMap: HashMap = HashMap() - for (entry in apiDeaths.entrySet()) { - deathsMap[entry.key] = entry.value.asInt - } - - for (categoryId in categoriesToParse) { - val categoryData = Constants.BESTIARY.getAsJsonObject(categoryId)!! - parsedCategories.add(parseCategory(categoryData, categoryId, killsMap, deathsMap)) - } - - return parsedCategories - } - - /** - * Parse one individual category, including potential subcategories - */ - private fun parseCategory( - categoryData: JsonObject, - categoryId: String, - killsMap: HashMap, - deathsMap: HashMap - ): Category { - val categoryName = categoryData["name"].asString - val computedMobs: MutableList = mutableListOf() - val categoryIconData = categoryData["icon"].asJsonObject - - val categoryIcon = if (categoryIconData.has("skullOwner")) { - Utils.createSkull( - categoryName, categoryIconData["skullOwner"].asString, categoryIconData["texture"].asString - ) - } else { - ItemUtils.createItemStackFromId(categoryIconData["item"].asString, categoryName) - } - if (categoryData.has("hasSubcategories")) { // It must have some subcategories - val subCategories: MutableList = mutableListOf() - - val reserved = listOf("name", "icon", "hasSubcategories") - for (entry in categoryData.entrySet()) { - if (!reserved.contains(entry.key)) { - subCategories.add( - parseCategory( - entry.value.asJsonObject, - "${categoryId}_${entry.key}", - killsMap, - deathsMap - ) - ) - } - } - return Category(categoryId, categoryName, categoryIcon, emptyList(), subCategories, calculateFamilyDataOfSubcategories(subCategories)) - } else { - val categoryMobs = categoryData["mobs"].asJsonArray.map { it.asJsonObject } - - for (mobData in categoryMobs) { - val mobName = mobData["name"].asString - val mobIcon = if (mobData.has("skullOwner")) { - Utils.createSkull( - mobName, mobData["skullOwner"].asString, mobData["texture"].asString - ) - } else { - ItemUtils.createItemStackFromId(mobData["item"].asString, mobName) - } - - val cap = mobData["cap"].asDouble - val bracket = mobData["bracket"].asInt - - var kills = 0.0 - var deaths = 0.0 - - // The mobs array contains the individual names returned by the API - val mobsArray = mobData["mobs"].asJsonArray.map { it.asString } - for (s in mobsArray) { - kills += killsMap.getOrDefault(s, 0) - deaths += deathsMap.getOrDefault(s, 0) - } - - val levelData = calculateLevel(bracket, kills, cap) - computedMobs.add(Mob(mobName, mobIcon, kills, deaths, levelData)) - } - return Category(categoryId, categoryName, categoryIcon, computedMobs, emptyList(), calculateFamilyData(computedMobs)) - } - } - - /** - * Calculates the level for a given mob - * - * @param bracket applicable bracket number - * @param kills number of kills the player has on that mob type - * @param cap maximum kill limit for the mob - */ - private fun calculateLevel(bracket: Int, kills: Double, cap: Double): MobLevelData { - val bracketData = - Constants.BESTIARY["brackets"].asJsonObject[bracket.toString()].asJsonArray.map { it.asDouble } - var maxLevel = false - var progress = 0.0 - var effKills = 0.0 - var effReq = 0.0 - - val effectiveKills = if (kills >= cap) { - maxLevel = true - cap - } else { - kills - } - - val totalProgress = (effectiveKills/cap*100).roundToDecimals(1) - - var level = 0 - for (requiredKills in bracketData) { - if (effectiveKills >= requiredKills) { - level++ - } else { - val prevTierKills = if (level != 0) bracketData[(level - 1).coerceAtLeast(0)].toInt() else 0 - effKills = kills - prevTierKills - effReq = requiredKills - prevTierKills - progress = (effKills / effReq * 100).roundToDecimals(1) - break - } - } - return MobLevelData(level, maxLevel, progress, totalProgress, MobKillData(effKills, effReq, effectiveKills, cap)) - } - - private fun calculateFamilyData(mobs: List): FamilyData { - var found = 0 - var completed = 0 - - for (mob in mobs) { - if(mob.kills > 0) found++ - if(mob.mobLevelData.maxLevel) completed++ - } - - return FamilyData(found, completed, mobs.size) - } - - private fun calculateFamilyDataOfSubcategories(subCategories: List): FamilyData { - var found = 0 - var completed = 0 - var total = 0 - - for (category in subCategories) { - val data = category.familyData - found += data.found - completed += data.completed - total += data.total - } - - return FamilyData(found, completed, total) - } -} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryPage.kt b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryPage.kt deleted file mode 100644 index c25cbe34..00000000 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/bestiary/BestiaryPage.kt +++ /dev/null @@ -1,424 +0,0 @@ -/* - * 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.bestiary - -import io.github.moulberry.notenoughupdates.core.util.StringUtils -import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewer -import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewerPage -import io.github.moulberry.notenoughupdates.profileviewer.bestiary.BestiaryData.calculateTotalBestiaryTiers -import io.github.moulberry.notenoughupdates.profileviewer.bestiary.BestiaryData.hasMigrated -import io.github.moulberry.notenoughupdates.profileviewer.bestiary.BestiaryData.parseBestiaryData -import io.github.moulberry.notenoughupdates.util.Constants -import io.github.moulberry.notenoughupdates.util.Utils -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.ScaledResolution -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.client.renderer.RenderHelper -import net.minecraft.item.ItemStack -import net.minecraft.util.EnumChatFormatting -import net.minecraft.util.ResourceLocation -import org.lwjgl.input.Mouse -import org.lwjgl.opengl.GL11 -import java.awt.Color - -/** - * Individual mob entry in the Bestiary - */ -data class Mob( - val name: String, val icon: ItemStack, val kills: Double, val deaths: Double, val mobLevelData: MobLevelData -) - -/** - * A Bestiary category as defined in `constants/bestiary.json` - */ -data class Category( - val id: String, - val name: String, - val icon: ItemStack, - val mobs: List, - val subCategories: List, - val familyData: FamilyData -) - -/** - * Level data for one specific mob - */ -data class MobLevelData( - val level: Int, val maxLevel: Boolean, val progress: Double, val totalProgress: Double, val mobKillData: MobKillData -) - -/** - * Kills data for one specific mob - */ -data class MobKillData( - val tierKills: Double, val tierReq: Double, val cappedKills: Double, val cap: Double -) - -/** - * Family data for one specific category - */ -data class FamilyData( - val found: Int, val completed: Int, val total: Int -) - -class BestiaryPage(instance: GuiProfileViewer?) : GuiProfileViewerPage(instance) { - private var selectedCategory = "dynamic" - private var selectedSubCategory = "" - private var tooltipToDisplay: MutableList = mutableListOf() - private var bestiaryLevel = 0.0 - private var computedCategories: MutableList = mutableListOf() - private var lastProfileName = "" - - private val bestiaryTexture = ResourceLocation("notenoughupdates:pv_bestiary_tab.png") - private val mobListXCount = 9 - private val mobListYCount = 5 - private val mobListXPadding = (240 - mobListXCount * 20) / (mobListXCount + 1).toFloat() - private val mobListYPadding = (202 - mobListYCount * 20) / (mobListYCount + 1).toFloat() - - override fun drawPage(mouseX: Int, mouseY: Int, partialTicks: Float) { - val guiLeft = GuiProfileViewer.getGuiLeft() - val guiTop = GuiProfileViewer.getGuiTop() - - val selectedProfile = GuiProfileViewer.getSelectedProfile() ?: return - val profileInfo = selectedProfile.profileJson - val profileName = GuiProfileViewer.getProfileName() - - if (!hasMigrated(profileInfo) || Constants.BESTIARY == null) { - Utils.drawStringCentered( - "${EnumChatFormatting.RED}No valid bestiary data!", - guiLeft + 431 / 2f, - (guiTop + 101).toFloat(), - true, - 0 - ) - lastProfileName = profileName - return - } - // Do the initial parsing only once or on profile switch - if (computedCategories.isEmpty() || lastProfileName != profileName) { - computedCategories = parseBestiaryData(profileInfo) - bestiaryLevel = calculateTotalBestiaryTiers(computedCategories).toDouble() - } - - if (computedCategories.isEmpty() || Constants.BESTIARY == null) { - Utils.drawStringCentered( - "${EnumChatFormatting.RED}No valid bestiary data!", - guiLeft + 431 / 2f, - (guiTop + 101).toFloat(), - true, - 0 - ) - lastProfileName = profileName - return - } - lastProfileName = profileName - - val bestiarySize = computedCategories.size - val bestiaryXSize = (350f / (bestiarySize - 1 + 0.0000001f)).toInt() - - - // Render the category list - for ((categoryXIndex, category) in computedCategories.withIndex()) { - Minecraft.getMinecraft().textureManager.bindTexture(GuiProfileViewer.pv_elements) - - if (mouseX > guiLeft + 30 + bestiaryXSize * categoryXIndex && - mouseX < guiLeft + 30 + bestiaryXSize * categoryXIndex + 20 - && mouseY > guiTop + 10 && mouseY < guiTop + 10 + 20 - ) { - tooltipToDisplay.add(EnumChatFormatting.GRAY.toString() + category.name) - if (Mouse.getEventButtonState() && selectedCategory != category.id) { - selectedCategory = category.id - Utils.playPressSound() - } - } - - - if (category.id == selectedCategory) { - Utils.drawTexturedRect( - (guiLeft + 30 + bestiaryXSize * categoryXIndex).toFloat(), - (guiTop + 10).toFloat(), - 20f, - 20f, - 20 / 256f, - 0f, - 20 / 256f, - 0f, - GL11.GL_NEAREST - ) - } else { - Utils.drawTexturedRect( - (guiLeft + 30 + bestiaryXSize * categoryXIndex).toFloat(), - (guiTop + 10).toFloat(), - 20f, - 20f, - 0f, - 20 / 256f, - 0f, - 20 / 256f, - GL11.GL_NEAREST - ) - } - Utils.drawItemStack(category.icon, guiLeft + 32 + bestiaryXSize * categoryXIndex, guiTop + 12) - } - - val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) - val width = scaledResolution.scaledWidth - val height = scaledResolution.scaledHeight - - Minecraft.getMinecraft().textureManager.bindTexture(bestiaryTexture) - Utils.drawTexturedRect(guiLeft.toFloat(), guiTop.toFloat(), 431f, 202f, GL11.GL_NEAREST) - GlStateManager.color(1f, 1f, 1f, 1f) - val color = Color(128, 128, 128, 255) - Utils.renderAlignedString( - EnumChatFormatting.RED.toString() + "Milestone: ", - "${EnumChatFormatting.GRAY}${(bestiaryLevel / 10)}", - (guiLeft + 280).toFloat(), - (guiTop + 50).toFloat(), - 110 - ) - - // Render the subcategories in the bottom right corner, if applicable - val selectedCategory = computedCategories.first { it.id == selectedCategory } - if (selectedCategory.subCategories.isNotEmpty()) { - if (selectedSubCategory == "") { - selectedSubCategory = selectedCategory.subCategories.first().id - } - - Utils.renderShadowedString( - "${EnumChatFormatting.RED}Subcategories", (guiLeft + 317).toFloat(), (guiTop + 165).toFloat(), 1000 - ) - GlStateManager.color(1f, 1f, 1f, 1f) - - val xStart = (guiLeft + 280).toFloat() - val y = (guiTop + 175).toFloat() - for ((i, subCategory) in selectedCategory.subCategories.withIndex()) { - Minecraft.getMinecraft().textureManager.bindTexture(GuiProfileViewer.pv_elements) - - if (subCategory.id == selectedSubCategory) { - Utils.drawTexturedRect( - (xStart + 24 * i.toFloat()), - y, - 20f, - 20f, - 20 / 256f, - 0f, - 20 / 256f, - 0f, - GL11.GL_NEAREST - ) - } else { - Utils.drawTexturedRect( - (xStart + 24 * i.toFloat()), - y, - 20f, - 20f, - 0f, - 20 / 256f, - 0f, - 20 / 256f, - GL11.GL_NEAREST - ) - } - Utils.drawItemStack(subCategory.icon, (xStart + 24 * i + 2).toInt(), y.toInt() + 2) - if (mouseX > xStart + 24 * i - && mouseX < xStart + 24 * (i + 1) - && mouseY > y - && mouseY < y + 16 - ) { - tooltipToDisplay.add(subCategory.name) - if (Mouse.getEventButtonState() && selectedSubCategory != subCategory.id) { - selectedSubCategory = subCategory.id - Utils.playPressSound() - } - } - } - } else { - selectedSubCategory = "" - } - - // Render family information - var catData = selectedCategory.familyData - Utils.renderAlignedString( - EnumChatFormatting.RED.toString() + "Families Found:", - (if (catData.found == catData.total) "§6" else "§7") + "${catData.found}/${catData.total}", - guiLeft + 280F, - guiTop + 70F, - 110 - ) - if (catData.found == catData.total) { - instance.renderGoldBar(guiLeft + 280F, guiTop + 80F, 112F) - } else { - instance.renderBar(guiLeft + 280F, guiTop + 80F, 112F, catData.found / catData.total.toFloat()) - } - - Utils.renderAlignedString( - EnumChatFormatting.RED.toString() + "Families Completed:", - (if (catData.completed == catData.total) "§6" else "§7") + "${catData.completed}/${catData.total}", - guiLeft + 280F, - guiTop + 90F, - 110 - ) - if (catData.completed == catData.total) { - instance.renderGoldBar(guiLeft + 280F, guiTop + 100F, 112F) - } else { - instance.renderBar(guiLeft + 280F, guiTop + 100F, 112F, catData.completed / catData.total.toFloat()) - } - - // Render subcategory family information, if possible - if (selectedSubCategory != "") { - catData = selectedCategory.subCategories.find { it.id == selectedSubCategory }!!.familyData - Utils.renderAlignedString( - EnumChatFormatting.RED.toString() + "Families Found:", - (if (catData.found == catData.total) "§6" else "§7") + "${catData.found}/${catData.total}", - guiLeft + 280F, - guiTop + 120F, - 110 - ) - if (catData.found == catData.total) { - instance.renderGoldBar(guiLeft + 280F, guiTop + 130F, 112F) - } else { - instance.renderBar(guiLeft + 280F, guiTop + 130F, 112F, catData.found / catData.total.toFloat()) - } - - Utils.renderAlignedString( - EnumChatFormatting.RED.toString() + "Families Completed:", - (if (catData.completed == catData.total) "§6" else "§7") + "${catData.completed}/${catData.total}", - guiLeft + 280F, - guiTop + 140F, - 110 - ) - if (catData.completed == catData.total) { - instance.renderGoldBar(guiLeft + 280F, guiTop + 150F, 112F) - } else { - instance.renderBar(guiLeft + 280F, guiTop + 150F, 112F, catData.completed / catData.total.toFloat()) - } - } - - // Determine which mobs should be displayed - val mobs = if (selectedSubCategory != "") { - selectedCategory.subCategories.first { it.id == selectedSubCategory }.mobs - } else { - selectedCategory.mobs - } - - // Render the mob list - for ((i, mob) in mobs.withIndex()) { - val stack = mob.icon - val xIndex = i % mobListXCount - val yIndex = i / mobListXCount - val x = 23 + mobListXPadding + (mobListXPadding + 20) * xIndex - val y = 30 + mobListYPadding + (mobListYPadding + 20) * yIndex - - GlStateManager.disableLighting() - RenderHelper.enableGUIStandardItemLighting() - GlStateManager.color(1f, 1f, 1f, 1f) - Minecraft.getMinecraft().textureManager.bindTexture(GuiProfileViewer.pv_elements) - Utils.drawTexturedRect( - guiLeft + x, - guiTop + y, - 20f, - 20f, - 0f, - 20 / 256f, - 0f, - 20f / 256f, - GL11.GL_NEAREST - ) - Utils.drawItemStack(stack, guiLeft + x.toInt() + 2, guiTop + y.toInt() + 2) - val kills = mob.kills - val deaths = mob.deaths - - if (mouseX > guiLeft + x.toInt() + 2 && mouseX < guiLeft + x.toInt() + 18) { - if (mouseY > guiTop + y.toInt() + 2 && mouseY < guiTop + y.toInt() + 18) { - tooltipToDisplay = ArrayList() - tooltipToDisplay.add( - "${mob.name} ${mob.mobLevelData.level}" - ) - tooltipToDisplay.add( - EnumChatFormatting.GRAY.toString() + "Kills: " + EnumChatFormatting.GREEN + - StringUtils.formatNumber(kills) - ) - tooltipToDisplay.add( - EnumChatFormatting.GRAY.toString() + "Deaths: " + EnumChatFormatting.GREEN + - StringUtils.formatNumber(deaths) - ) - tooltipToDisplay.add("") - - if (!mob.mobLevelData.maxLevel) { - tooltipToDisplay.add( - EnumChatFormatting.GRAY.toString() + "Progress to Tier ${mob.mobLevelData.level + 1}: " + - EnumChatFormatting.AQUA + "${mob.mobLevelData.progress}%" - ) - - var bar = "§3§l§m" - for (j in 1..14) { - var col = "" - if (mob.mobLevelData.progress < j * (100 / 14)) col = "§f§l§m" - bar += "$col " - } - tooltipToDisplay.add( - "${bar}§r§b ${StringUtils.formatNumber(mob.mobLevelData.mobKillData.tierKills)}/${ - StringUtils.formatNumber( - mob.mobLevelData.mobKillData.tierReq - ) - }" - ) - tooltipToDisplay.add("") - } - - tooltipToDisplay.add( - EnumChatFormatting.GRAY.toString() + "Overall Progress: " + EnumChatFormatting.AQUA + "${mob.mobLevelData.totalProgress}%" + - if (mob.mobLevelData.maxLevel) " §7(§c§lMAX!§r§7)" else "" - ) - var bar = "§3§l§m" - for (j in 1..14) { - var col = "" - if (mob.mobLevelData.totalProgress < j * (100 / 14)) col = "§f§l§m" - bar += "$col " - } - tooltipToDisplay.add( - "${bar}§r§b ${StringUtils.formatNumber(mob.mobLevelData.mobKillData.cappedKills)}/${ - StringUtils.formatNumber( - mob.mobLevelData.mobKillData.cap - ) - }" - ) - } - } - GlStateManager.color(1f, 1f, 1f, 1f) - Utils.drawStringCentered( - if (mob.mobLevelData.maxLevel) { - "${EnumChatFormatting.GOLD}${mob.mobLevelData.level}" - } else { - mob.mobLevelData.level.toString() - }, guiLeft + x + 10, guiTop + y + 26, true, color.rgb - ) - } - - // Render the accumulated tooltip, if applicable - if (tooltipToDisplay.isNotEmpty()) { - val grayTooltip: MutableList = ArrayList(tooltipToDisplay.size) - for (line in tooltipToDisplay) { - grayTooltip.add(EnumChatFormatting.GRAY.toString() + line) - } - Utils.drawHoveringText(grayTooltip, mouseX, mouseY, width, height, -1) - tooltipToDisplay.clear() - } - } -} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryData.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryData.kt new file mode 100644 index 00000000..69688fcc --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryData.kt @@ -0,0 +1,277 @@ +/* + * Copyright (C) 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.miscfeatures.profileviewer.bestiary + +import com.google.gson.JsonObject +import io.github.moulberry.notenoughupdates.util.Constants +import io.github.moulberry.notenoughupdates.util.ItemUtils +import io.github.moulberry.notenoughupdates.util.Utils +import io.github.moulberry.notenoughupdates.util.roundToDecimals +import kotlin.math.min + +object BestiaryData { + private val categoriesToParse = listOf( + "dynamic", + "hub", + "farming_1", + "garden", + "combat_1", + "combat_3", + "crimson_isle", + "mining_2", + "mining_3", + "crystal_hollows", + "foraging_1", + "spooky_festival", + "mythological_creatures", + "jerry", + "kuudra", + "catacombs", + "fishing" + ) + + /** + * Calculates the sum of all individual tiers for this profile + * + * @param computedCategories List of parsed categories + * @see BestiaryPage.parseBestiaryData + */ + @JvmStatic + fun calculateTotalBestiaryTiers(computedCategories: List): Int { + var tiers = 0.0 + computedCategories.forEach { + tiers += countTotalLevels(it) + } + return tiers.toInt() + } + + /** + * Calculate the skyblock xp awarded for the given bestiary progress + */ + @JvmStatic + fun calculateBestiarySkyblockXp(profileInfo: JsonObject): Int { + val totalTiers = calculateTotalBestiaryTiers(parseBestiaryData(profileInfo)) + var skyblockXp = 0 + + val slayingTask = Constants.SBLEVELS.getAsJsonObject("slaying_task") ?: return 0 + val xpPerTier = (slayingTask.get("bestiary_family_xp") ?: return 0).asInt + val xpPerMilestone = slayingTask.get("bestiary_milestone_xp").asInt + val maxXp = slayingTask.get("bestiary_progress").asInt + + skyblockXp += totalTiers * xpPerTier + + val milestones = (totalTiers / 100) + skyblockXp += milestones * xpPerMilestone + + return min(skyblockXp, maxXp) + } + + private fun countTotalLevels(category: Category): Int { + var levels = 0 + for (mob in category.mobs) { + levels += mob.mobLevelData.level + } + category.subCategories.forEach { levels += countTotalLevels(it) } + return levels + } + + /** + * Checks if a user profile has migrated. + * + * @param profileInfo skyblock profile information + */ + fun hasMigrated(profileInfo: JsonObject): Boolean { + val bestiaryObject = profileInfo.getAsJsonObject("bestiary") ?: return false + + return (bestiaryObject.get("migration") ?: return false).asBoolean + } + + /** + * Parse the bestiary data for the profile. Categories are taken from the `constants/bestiary.json` repo file + * + * @param profileInfo the JsonObject containing the bestiary data + */ + @JvmStatic + fun parseBestiaryData(profileInfo: JsonObject): MutableList { + if (!hasMigrated(profileInfo) || Constants.BESTIARY == null) { + return mutableListOf() + } + + val parsedCategories = mutableListOf() + + val apiKills = profileInfo.getAsJsonObject("bestiary")!!.getAsJsonObject("kills") ?: return mutableListOf() + val apiDeaths = profileInfo.getAsJsonObject("bestiary").getAsJsonObject("deaths") ?: return mutableListOf() + val killsMap: HashMap = HashMap() + for (entry in apiKills.entrySet()) { + killsMap[entry.key] = entry.value.asInt + } + val deathsMap: HashMap = HashMap() + for (entry in apiDeaths.entrySet()) { + deathsMap[entry.key] = entry.value.asInt + } + + for (categoryId in categoriesToParse) { + val categoryData = Constants.BESTIARY.getAsJsonObject(categoryId) + if (categoryData != null) { + parsedCategories.add(parseCategory(categoryData, categoryId, killsMap, deathsMap)) + } else { + Utils.showOutdatedRepoNotification("bestiary.json missing or outdated") + } + } + + return parsedCategories + } + + /** + * Parse one individual category, including potential subcategories + */ + private fun parseCategory( + categoryData: JsonObject, + categoryId: String, + killsMap: HashMap, + deathsMap: HashMap + ): Category { + val categoryName = categoryData["name"].asString + val computedMobs: MutableList = mutableListOf() + val categoryIconData = categoryData["icon"].asJsonObject + + val categoryIcon = if (categoryIconData.has("skullOwner")) { + Utils.createSkull( + categoryName, categoryIconData["skullOwner"].asString, categoryIconData["texture"].asString + ) + } else { + ItemUtils.createItemStackFromId(categoryIconData["item"].asString, categoryName) + } + if (categoryData.has("hasSubcategories")) { // It must have some subcategories + val subCategories: MutableList = mutableListOf() + + val reserved = listOf("name", "icon", "hasSubcategories") + for (entry in categoryData.entrySet()) { + if (!reserved.contains(entry.key)) { + subCategories.add( + parseCategory( + entry.value.asJsonObject, + "${categoryId}_${entry.key}", + killsMap, + deathsMap + ) + ) + } + } + return Category(categoryId, categoryName, categoryIcon, emptyList(), subCategories, calculateFamilyDataOfSubcategories(subCategories)) + } else { + val categoryMobs = categoryData["mobs"].asJsonArray.map { it.asJsonObject } + + for (mobData in categoryMobs) { + val mobName = mobData["name"].asString + val mobIcon = if (mobData.has("skullOwner")) { + Utils.createSkull( + mobName, mobData["skullOwner"].asString, mobData["texture"].asString + ) + } else { + ItemUtils.createItemStackFromId(mobData["item"].asString, mobName) + } + + val cap = mobData["cap"].asDouble + val bracket = mobData["bracket"].asInt + + var kills = 0.0 + var deaths = 0.0 + + // The mobs array contains the individual names returned by the API + val mobsArray = mobData["mobs"].asJsonArray.map { it.asString } + for (s in mobsArray) { + kills += killsMap.getOrDefault(s, 0) + deaths += deathsMap.getOrDefault(s, 0) + } + + val levelData = calculateLevel(bracket, kills, cap) + computedMobs.add(Mob(mobName, mobIcon, kills, deaths, levelData)) + } + return Category(categoryId, categoryName, categoryIcon, computedMobs, emptyList(), calculateFamilyData(computedMobs)) + } + } + + /** + * Calculates the level for a given mob + * + * @param bracket applicable bracket number + * @param kills number of kills the player has on that mob type + * @param cap maximum kill limit for the mob + */ + private fun calculateLevel(bracket: Int, kills: Double, cap: Double): MobLevelData { + val bracketData = + Constants.BESTIARY["brackets"].asJsonObject[bracket.toString()].asJsonArray.map { it.asDouble } + var maxLevel = false + var progress = 0.0 + var effKills = 0.0 + var effReq = 0.0 + + val effectiveKills = if (kills >= cap) { + maxLevel = true + cap + } else { + kills + } + + val totalProgress = (effectiveKills/cap*100).roundToDecimals(1) + + var level = 0 + for (requiredKills in bracketData) { + if (effectiveKills >= requiredKills) { + level++ + } else { + val prevTierKills = if (level != 0) bracketData[(level - 1).coerceAtLeast(0)].toInt() else 0 + effKills = kills - prevTierKills + effReq = requiredKills - prevTierKills + progress = (effKills / effReq * 100).roundToDecimals(1) + break + } + } + return MobLevelData(level, maxLevel, progress, totalProgress, MobKillData(effKills, effReq, effectiveKills, cap)) + } + + private fun calculateFamilyData(mobs: List): FamilyData { + var found = 0 + var completed = 0 + + for (mob in mobs) { + if(mob.kills > 0) found++ + if(mob.mobLevelData.maxLevel) completed++ + } + + return FamilyData(found, completed, mobs.size) + } + + private fun calculateFamilyDataOfSubcategories(subCategories: List): FamilyData { + var found = 0 + var completed = 0 + var total = 0 + + for (category in subCategories) { + val data = category.familyData + found += data.found + completed += data.completed + total += data.total + } + + return FamilyData(found, completed, total) + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryPage.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryPage.kt new file mode 100644 index 00000000..d2dfe910 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/profileviewer/bestiary/BestiaryPage.kt @@ -0,0 +1,424 @@ +/* + * 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.miscfeatures.profileviewer.bestiary + +import io.github.moulberry.notenoughupdates.core.util.StringUtils +import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewer +import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewerPage +import io.github.moulberry.notenoughupdates.miscfeatures.profileviewer.bestiary.BestiaryData.calculateTotalBestiaryTiers +import io.github.moulberry.notenoughupdates.miscfeatures.profileviewer.bestiary.BestiaryData.hasMigrated +import io.github.moulberry.notenoughupdates.miscfeatures.profileviewer.bestiary.BestiaryData.parseBestiaryData +import io.github.moulberry.notenoughupdates.util.Constants +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.RenderHelper +import net.minecraft.item.ItemStack +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.ResourceLocation +import org.lwjgl.input.Mouse +import org.lwjgl.opengl.GL11 +import java.awt.Color + +/** + * Individual mob entry in the Bestiary + */ +data class Mob( + val name: String, val icon: ItemStack, val kills: Double, val deaths: Double, val mobLevelData: MobLevelData +) + +/** + * A Bestiary category as defined in `constants/bestiary.json` + */ +data class Category( + val id: String, + val name: String, + val icon: ItemStack, + val mobs: List, + val subCategories: List, + val familyData: FamilyData +) + +/** + * Level data for one specific mob + */ +data class MobLevelData( + val level: Int, val maxLevel: Boolean, val progress: Double, val totalProgress: Double, val mobKillData: MobKillData +) + +/** + * Kills data for one specific mob + */ +data class MobKillData( + val tierKills: Double, val tierReq: Double, val cappedKills: Double, val cap: Double +) + +/** + * Family data for one specific category + */ +data class FamilyData( + val found: Int, val completed: Int, val total: Int +) + +class BestiaryPage(instance: GuiProfileViewer?) : GuiProfileViewerPage(instance) { + private var selectedCategory = "dynamic" + private var selectedSubCategory = "" + private var tooltipToDisplay: MutableList = mutableListOf() + private var bestiaryLevel = 0.0 + private var computedCategories: MutableList = mutableListOf() + private var lastProfileName = "" + + private val bestiaryTexture = ResourceLocation("notenoughupdates:pv_bestiary_tab.png") + private val mobListXCount = 9 + private val mobListYCount = 5 + private val mobListXPadding = (240 - mobListXCount * 20) / (mobListXCount + 1).toFloat() + private val mobListYPadding = (202 - mobListYCount * 20) / (mobListYCount + 1).toFloat() + + override fun drawPage(mouseX: Int, mouseY: Int, partialTicks: Float) { + val guiLeft = GuiProfileViewer.getGuiLeft() + val guiTop = GuiProfileViewer.getGuiTop() + + val selectedProfile = GuiProfileViewer.getSelectedProfile() ?: return + val profileInfo = selectedProfile.profileJson + val profileName = GuiProfileViewer.getProfileName() + + if (!hasMigrated(profileInfo) || Constants.BESTIARY == null) { + Utils.drawStringCentered( + "${EnumChatFormatting.RED}No valid bestiary data!", + guiLeft + 431 / 2f, + (guiTop + 101).toFloat(), + true, + 0 + ) + lastProfileName = profileName + return + } + // Do the initial parsing only once or on profile switch + if (computedCategories.isEmpty() || lastProfileName != profileName) { + computedCategories = parseBestiaryData(profileInfo) + bestiaryLevel = calculateTotalBestiaryTiers(computedCategories).toDouble() + } + + if (computedCategories.isEmpty() || Constants.BESTIARY == null) { + Utils.drawStringCentered( + "${EnumChatFormatting.RED}No valid bestiary data!", + guiLeft + 431 / 2f, + (guiTop + 101).toFloat(), + true, + 0 + ) + lastProfileName = profileName + return + } + lastProfileName = profileName + + val bestiarySize = computedCategories.size + val bestiaryXSize = (350f / (bestiarySize - 1 + 0.0000001f)).toInt() + + + // Render the category list + for ((categoryXIndex, category) in computedCategories.withIndex()) { + Minecraft.getMinecraft().textureManager.bindTexture(GuiProfileViewer.pv_elements) + + if (mouseX > guiLeft + 30 + bestiaryXSize * categoryXIndex && + mouseX < guiLeft + 30 + bestiaryXSize * categoryXIndex + 20 + && mouseY > guiTop + 10 && mouseY < guiTop + 10 + 20 + ) { + tooltipToDisplay.add(EnumChatFormatting.GRAY.toString() + category.name) + if (Mouse.getEventButtonState() && selectedCategory != category.id) { + selectedCategory = category.id + Utils.playPressSound() + } + } + + + if (category.id == selectedCategory) { + Utils.drawTexturedRect( + (guiLeft + 30 + bestiaryXSize * categoryXIndex).toFloat(), + (guiTop + 10).toFloat(), + 20f, + 20f, + 20 / 256f, + 0f, + 20 / 256f, + 0f, + GL11.GL_NEAREST + ) + } else { + Utils.drawTexturedRect( + (guiLeft + 30 + bestiaryXSize * categoryXIndex).toFloat(), + (guiTop + 10).toFloat(), + 20f, + 20f, + 0f, + 20 / 256f, + 0f, + 20 / 256f, + GL11.GL_NEAREST + ) + } + Utils.drawItemStack(category.icon, guiLeft + 32 + bestiaryXSize * categoryXIndex, guiTop + 12) + } + + val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) + val width = scaledResolution.scaledWidth + val height = scaledResolution.scaledHeight + + Minecraft.getMinecraft().textureManager.bindTexture(bestiaryTexture) + Utils.drawTexturedRect(guiLeft.toFloat(), guiTop.toFloat(), 431f, 202f, GL11.GL_NEAREST) + GlStateManager.color(1f, 1f, 1f, 1f) + val color = Color(128, 128, 128, 255) + Utils.renderAlignedString( + EnumChatFormatting.RED.toString() + "Milestone: ", + "${EnumChatFormatting.GRAY}${(bestiaryLevel / 10)}", + (guiLeft + 280).toFloat(), + (guiTop + 50).toFloat(), + 110 + ) + + // Render the subcategories in the bottom right corner, if applicable + val selectedCategory = computedCategories.first { it.id == selectedCategory } + if (selectedCategory.subCategories.isNotEmpty()) { + if (selectedSubCategory == "") { + selectedSubCategory = selectedCategory.subCategories.first().id + } + + Utils.renderShadowedString( + "${EnumChatFormatting.RED}Subcategories", (guiLeft + 317).toFloat(), (guiTop + 165).toFloat(), 1000 + ) + GlStateManager.color(1f, 1f, 1f, 1f) + + val xStart = (guiLeft + 280).toFloat() + val y = (guiTop + 175).toFloat() + for ((i, subCategory) in selectedCategory.subCategories.withIndex()) { + Minecraft.getMinecraft().textureManager.bindTexture(GuiProfileViewer.pv_elements) + + if (subCategory.id == selectedSubCategory) { + Utils.drawTexturedRect( + (xStart + 24 * i.toFloat()), + y, + 20f, + 20f, + 20 / 256f, + 0f, + 20 / 256f, + 0f, + GL11.GL_NEAREST + ) + } else { + Utils.drawTexturedRect( + (xStart + 24 * i.toFloat()), + y, + 20f, + 20f, + 0f, + 20 / 256f, + 0f, + 20 / 256f, + GL11.GL_NEAREST + ) + } + Utils.drawItemStack(subCategory.icon, (xStart + 24 * i + 2).toInt(), y.toInt() + 2) + if (mouseX > xStart + 24 * i + && mouseX < xStart + 24 * (i + 1) + && mouseY > y + && mouseY < y + 16 + ) { + tooltipToDisplay.add(subCategory.name) + if (Mouse.getEventButtonState() && selectedSubCategory != subCategory.id) { + selectedSubCategory = subCategory.id + Utils.playPressSound() + } + } + } + } else { + selectedSubCategory = "" + } + + // Render family information + var catData = selectedCategory.familyData + Utils.renderAlignedString( + EnumChatFormatting.RED.toString() + "Families Found:", + (if (catData.found == catData.total) "§6" else "§7") + "${catData.found}/${catData.total}", + guiLeft + 280F, + guiTop + 70F, + 110 + ) + if (catData.found == catData.total) { + instance.renderGoldBar(guiLeft + 280F, guiTop + 80F, 112F) + } else { + instance.renderBar(guiLeft + 280F, guiTop + 80F, 112F, catData.found / catData.total.toFloat()) + } + + Utils.renderAlignedString( + EnumChatFormatting.RED.toString() + "Families Completed:", + (if (catData.completed == catData.total) "§6" else "§7") + "${catData.completed}/${catData.total}", + guiLeft + 280F, + guiTop + 90F, + 110 + ) + if (catData.completed == catData.total) { + instance.renderGoldBar(guiLeft + 280F, guiTop + 100F, 112F) + } else { + instance.renderBar(guiLeft + 280F, guiTop + 100F, 112F, catData.completed / catData.total.toFloat()) + } + + // Render subcategory family information, if possible + if (selectedSubCategory != "") { + catData = selectedCategory.subCategories.find { it.id == selectedSubCategory }!!.familyData + Utils.renderAlignedString( + EnumChatFormatting.RED.toString() + "Families Found:", + (if (catData.found == catData.total) "§6" else "§7") + "${catData.found}/${catData.total}", + guiLeft + 280F, + guiTop + 120F, + 110 + ) + if (catData.found == catData.total) { + instance.renderGoldBar(guiLeft + 280F, guiTop + 130F, 112F) + } else { + instance.renderBar(guiLeft + 280F, guiTop + 130F, 112F, catData.found / catData.total.toFloat()) + } + + Utils.renderAlignedString( + EnumChatFormatting.RED.toString() + "Families Completed:", + (if (catData.completed == catData.total) "§6" else "§7") + "${catData.completed}/${catData.total}", + guiLeft + 280F, + guiTop + 140F, + 110 + ) + if (catData.completed == catData.total) { + instance.renderGoldBar(guiLeft + 280F, guiTop + 150F, 112F) + } else { + instance.renderBar(guiLeft + 280F, guiTop + 150F, 112F, catData.completed / catData.total.toFloat()) + } + } + + // Determine which mobs should be displayed + val mobs = if (selectedSubCategory != "") { + selectedCategory.subCategories.first { it.id == selectedSubCategory }.mobs + } else { + selectedCategory.mobs + } + + // Render the mob list + for ((i, mob) in mobs.withIndex()) { + val stack = mob.icon + val xIndex = i % mobListXCount + val yIndex = i / mobListXCount + val x = 23 + mobListXPadding + (mobListXPadding + 20) * xIndex + val y = 30 + mobListYPadding + (mobListYPadding + 20) * yIndex + + GlStateManager.disableLighting() + RenderHelper.enableGUIStandardItemLighting() + GlStateManager.color(1f, 1f, 1f, 1f) + Minecraft.getMinecraft().textureManager.bindTexture(GuiProfileViewer.pv_elements) + Utils.drawTexturedRect( + guiLeft + x, + guiTop + y, + 20f, + 20f, + 0f, + 20 / 256f, + 0f, + 20f / 256f, + GL11.GL_NEAREST + ) + Utils.drawItemStack(stack, guiLeft + x.toInt() + 2, guiTop + y.toInt() + 2) + val kills = mob.kills + val deaths = mob.deaths + + if (mouseX > guiLeft + x.toInt() + 2 && mouseX < guiLeft + x.toInt() + 18) { + if (mouseY > guiTop + y.toInt() + 2 && mouseY < guiTop + y.toInt() + 18) { + tooltipToDisplay = ArrayList() + tooltipToDisplay.add( + "${mob.name} ${mob.mobLevelData.level}" + ) + tooltipToDisplay.add( + EnumChatFormatting.GRAY.toString() + "Kills: " + EnumChatFormatting.GREEN + + StringUtils.formatNumber(kills) + ) + tooltipToDisplay.add( + EnumChatFormatting.GRAY.toString() + "Deaths: " + EnumChatFormatting.GREEN + + StringUtils.formatNumber(deaths) + ) + tooltipToDisplay.add("") + + if (!mob.mobLevelData.maxLevel) { + tooltipToDisplay.add( + EnumChatFormatting.GRAY.toString() + "Progress to Tier ${mob.mobLevelData.level + 1}: " + + EnumChatFormatting.AQUA + "${mob.mobLevelData.progress}%" + ) + + var bar = "§3§l§m" + for (j in 1..14) { + var col = "" + if (mob.mobLevelData.progress < j * (100 / 14)) col = "§f§l§m" + bar += "$col " + } + tooltipToDisplay.add( + "${bar}§r§b ${StringUtils.formatNumber(mob.mobLevelData.mobKillData.tierKills)}/${ + StringUtils.formatNumber( + mob.mobLevelData.mobKillData.tierReq + ) + }" + ) + tooltipToDisplay.add("") + } + + tooltipToDisplay.add( + EnumChatFormatting.GRAY.toString() + "Overall Progress: " + EnumChatFormatting.AQUA + "${mob.mobLevelData.totalProgress}%" + + if (mob.mobLevelData.maxLevel) " §7(§c§lMAX!§r§7)" else "" + ) + var bar = "§3§l§m" + for (j in 1..14) { + var col = "" + if (mob.mobLevelData.totalProgress < j * (100 / 14)) col = "§f§l§m" + bar += "$col " + } + tooltipToDisplay.add( + "${bar}§r§b ${StringUtils.formatNumber(mob.mobLevelData.mobKillData.cappedKills)}/${ + StringUtils.formatNumber( + mob.mobLevelData.mobKillData.cap + ) + }" + ) + } + } + GlStateManager.color(1f, 1f, 1f, 1f) + Utils.drawStringCentered( + if (mob.mobLevelData.maxLevel) { + "${EnumChatFormatting.GOLD}${mob.mobLevelData.level}" + } else { + mob.mobLevelData.level.toString() + }, guiLeft + x + 10, guiTop + y + 26, true, color.rgb + ) + } + + // Render the accumulated tooltip, if applicable + if (tooltipToDisplay.isNotEmpty()) { + val grayTooltip: MutableList = ArrayList(tooltipToDisplay.size) + for (line in tooltipToDisplay) { + grayTooltip.add(EnumChatFormatting.GRAY.toString() + line) + } + Utils.drawHoveringText(grayTooltip, mouseX, mouseY, width, height, -1) + tooltipToDisplay.clear() + } + } +} -- cgit