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