/* * Copyright (C) 2022-2024 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * * NotEnoughUpdates is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * NotEnoughUpdates is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see . */ package io.github.moulberry.notenoughupdates.profileviewer; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.miscfeatures.PetInfoOverlay; import io.github.moulberry.notenoughupdates.profileviewer.info.QuiverInfo; import io.github.moulberry.notenoughupdates.util.Constants; import io.github.moulberry.notenoughupdates.util.PetLeveling; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.EnumChatFormatting; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PlayerStats { // Combat stats public static final String HEALTH = "health"; public static final String DEFENCE = "defence"; public static final String STRENGTH = "strength"; public static final String INTELLIGENCE = "intelligence"; public static final String CRIT_CHANCE = "crit_chance"; public static final String CRIT_DAMAGE = "crit_damage"; public static final String BONUS_ATTACK_SPEED = "bonus_attack_speed"; public static final String ABILITY_DAMAGE = "ability_damage"; public static final String TRUE_DEFENSE = "true_defense"; public static final String FEROCITY = "ferocity"; public static final String HEALTH_REGEN = "health_regen"; public static final String VITALITY = "vitality"; public static final String MENDING = "mending"; public static final String SWING_RANGE = "swing_range"; // Gathering stats public static final String MINING_SPEED = "mining_speed"; public static final String MINING_FORTUNE = "mining_fortune"; public static final String FARMING_FORTUNE = "farming_fortune"; public static final String FORAGING_FORTUNE = "foraging_fortune"; public static final String BREAKING_POWER = "breaking_power"; public static final String PRISTINE = "pristine"; public static final String WHEAT_FORTUNE = "wheat_fortune"; public static final String CARROT_FORTUNE = "carrot_fortune"; public static final String POTATO_FORTUNE = "potato_fortune"; public static final String PUMPKIN_FORTUNE = "pumpkin_fortune"; public static final String MELON_FORTUNE = "melon_fortune"; public static final String MUSHROOM_FORTUNE = "mushroom_fortune"; public static final String CACTUS_FORTUNE = "cactus_fortune"; public static final String SUGAR_CANE_FORTUNE = "sugar_cane_fortune"; public static final String NETHER_WART_FORTUNE = "nether_wart_fortune"; public static final String COCOA_BEANS_FORTUNE = "cocoa_beans_fortune"; // Wisdom stats public static final String COMBAT_WISDOM = "combat_wisdom"; public static final String MINING_WISDOM = "mining_wisdom"; public static final String FARMING_WISDOM = "farming_wisdom"; public static final String FORAGING_WISDOM = "foraging_wisdom"; public static final String FISHING_WISDOM = "fishing_wisdom"; public static final String ENCHANTING_WISDOM = "enchanting_wisdom"; public static final String ALCHEMY_WISDOM = "alchemy_wisdom"; public static final String CARPENTRY_WISDOM = "carpentry_wisdom"; public static final String RUNECRAFTING_WISDOM = "runecrafting_wisdom"; public static final String SOCIAL_WISDOM = "social_wisdom"; public static final String TAMING_WISDOM = "taming_wisdom"; // Misc stats public static final String SPEED = "speed"; public static final String MAGIC_FIND = "magic_find"; public static final String PET_LUCK = "pet_luck"; public static final String SEA_CREATURE_CHANCE = "sea_creature_chance"; public static final String DOUBLE_HOOK_CHANCE = "double_hook_chance"; public static final String FISHING_SPEED = "fishing_speed"; public static final String COLD_RESISTANCE = "cold_resistance"; public static final String BONUS_PEST_CHANCE = "bonus_pest_chance"; public static final String[] defaultStatNames = new String[]{ HEALTH, DEFENCE, STRENGTH, INTELLIGENCE, CRIT_CHANCE, CRIT_DAMAGE, BONUS_ATTACK_SPEED, ABILITY_DAMAGE, TRUE_DEFENSE, FEROCITY, HEALTH_REGEN, VITALITY, MENDING, SWING_RANGE, MINING_SPEED, MINING_FORTUNE, FARMING_FORTUNE, FORAGING_FORTUNE, BREAKING_POWER, PRISTINE, WHEAT_FORTUNE, CARROT_FORTUNE, POTATO_FORTUNE, PUMPKIN_FORTUNE, MELON_FORTUNE, MUSHROOM_FORTUNE, CACTUS_FORTUNE, SUGAR_CANE_FORTUNE, NETHER_WART_FORTUNE, COCOA_BEANS_FORTUNE, COMBAT_WISDOM, MINING_WISDOM, FARMING_WISDOM, FORAGING_WISDOM, FISHING_WISDOM, ENCHANTING_WISDOM, ALCHEMY_WISDOM, CARPENTRY_WISDOM, RUNECRAFTING_WISDOM, SOCIAL_WISDOM, TAMING_WISDOM, SPEED, MAGIC_FIND, PET_LUCK, SEA_CREATURE_CHANCE, DOUBLE_HOOK_CHANCE, FISHING_SPEED, COLD_RESISTANCE, BONUS_PEST_CHANCE, }; public static final String[] defaultStatNamesPretty = new String[]{ EnumChatFormatting.RED + "❤ Health", EnumChatFormatting.GREEN + "❈ Defence", EnumChatFormatting.RED + "❁ Strength", EnumChatFormatting.AQUA + "✎ Intelligence", EnumChatFormatting.BLUE + "☣ Crit Chance", EnumChatFormatting.BLUE + "☠ Crit Damage", EnumChatFormatting.YELLOW + "⚔ Bonus Attack Speed", EnumChatFormatting.RED + "๑ Ability Damage", EnumChatFormatting.WHITE + "❂ True Defense", EnumChatFormatting.RED + "⫽ Ferocity", EnumChatFormatting.RED + "❣ Health Regen", EnumChatFormatting.DARK_RED + "♨ Vitality", EnumChatFormatting.GREEN + "☄ Mending", EnumChatFormatting.YELLOW + "Ⓢ Swing Range", EnumChatFormatting.GOLD + "⸕ Mining Speed", EnumChatFormatting.GOLD + "☘ Mining Fortune", EnumChatFormatting.GOLD + "☘ Farming Fortune", EnumChatFormatting.GOLD + "☘ Foraging Fortune", EnumChatFormatting.DARK_GREEN + "Ⓟ Breaking Power", EnumChatFormatting.DARK_PURPLE + "✧ Pristine", EnumChatFormatting.GOLD + "☘ Wheat Fortune", EnumChatFormatting.GOLD + "☘ Carrot Fortune", EnumChatFormatting.GOLD + "☘ Potato Fortune", EnumChatFormatting.GOLD + "☘ Pumpkin Fortune", EnumChatFormatting.GOLD + "☘ Melon Fortune", EnumChatFormatting.GOLD + "☘ Mushroom Fortune", EnumChatFormatting.GOLD + "☘ Cactus Fortune", EnumChatFormatting.GOLD + "☘ Sugar Cane Fortune", EnumChatFormatting.GOLD + "☘ Nether Wart Fortune", EnumChatFormatting.GOLD + "☘ Cocoa Beans Fortune", EnumChatFormatting.DARK_AQUA + "☯ Combat Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Mining Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Farming Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Foraging Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Fishing Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Enchanting Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Alchemy Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Carpentry Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Runecrafting Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Social Wisdom", EnumChatFormatting.DARK_AQUA + "☯ Taming Wisdom", EnumChatFormatting.WHITE + "✦ Speed", EnumChatFormatting.AQUA + "✯ Magic Find", EnumChatFormatting.LIGHT_PURPLE + "♣ Pet Luck", EnumChatFormatting.DARK_AQUA + "α Sea Creature Chance", EnumChatFormatting.BLUE + "⚓ Double Hook Chance", EnumChatFormatting.AQUA + "☂ Fishing Speed", EnumChatFormatting.AQUA + "❄ Cold Resistance", EnumChatFormatting.DARK_GREEN + "ൠ Bonus Pest Chance" }; public static final HashMap STAT_PATTERN_MAP = new HashMap() { { put(HEALTH, Pattern.compile("^Health" + STAT_PATTERN_END)); put(DEFENCE, Pattern.compile("^Defence" + STAT_PATTERN_END)); put(STRENGTH, Pattern.compile("^Strength" + STAT_PATTERN_END)); put(INTELLIGENCE, Pattern.compile("^Intelligence" + STAT_PATTERN_END)); put(CRIT_CHANCE, Pattern.compile("^Crit Chance" + STAT_PATTERN_END)); put(CRIT_DAMAGE, Pattern.compile("^Crit Damage" + STAT_PATTERN_END)); put(BONUS_ATTACK_SPEED, Pattern.compile("^Bonus Attack Speed" + STAT_PATTERN_END)); put(ABILITY_DAMAGE, Pattern.compile("^Ability Damage" + STAT_PATTERN_END)); put(TRUE_DEFENSE, Pattern.compile("^True Defense" + STAT_PATTERN_END)); put(FEROCITY, Pattern.compile("^Ferocity" + STAT_PATTERN_END)); put(HEALTH_REGEN, Pattern.compile("^Health Regen" + STAT_PATTERN_END)); put(VITALITY, Pattern.compile("^Vitality" + STAT_PATTERN_END)); put(MENDING, Pattern.compile("^Mending" + STAT_PATTERN_END)); put(SWING_RANGE, Pattern.compile("^Swing Range" + STAT_PATTERN_END)); put(MINING_SPEED, Pattern.compile("^Mining Speed" + STAT_PATTERN_END)); put(MINING_FORTUNE, Pattern.compile("^Mining Fortune" + STAT_PATTERN_END)); put(FARMING_FORTUNE, Pattern.compile("^Farming Fortune" + STAT_PATTERN_END)); put(FORAGING_FORTUNE, Pattern.compile("^Foraging Fortune" + STAT_PATTERN_END)); put(BREAKING_POWER, Pattern.compile("^Breaking Power" + STAT_PATTERN_END)); put(PRISTINE, Pattern.compile("^Pristine" + STAT_PATTERN_END)); put(WHEAT_FORTUNE, Pattern.compile("^Wheat Fortune" + STAT_PATTERN_END)); put(CARROT_FORTUNE, Pattern.compile("^Carrot Fortune" + STAT_PATTERN_END)); put(POTATO_FORTUNE, Pattern.compile("^Potato Fortune" + STAT_PATTERN_END)); put(PUMPKIN_FORTUNE, Pattern.compile("^Pumpkin Fortune" + STAT_PATTERN_END)); put(MELON_FORTUNE, Pattern.compile("^Melon Fortune" + STAT_PATTERN_END)); put(MUSHROOM_FORTUNE, Pattern.compile("^Mushroom Fortune" + STAT_PATTERN_END)); put(CACTUS_FORTUNE, Pattern.compile("^Cactus Fortune" + STAT_PATTERN_END)); put(SUGAR_CANE_FORTUNE, Pattern.compile("^Sugar Cane Fortune" + STAT_PATTERN_END)); put(NETHER_WART_FORTUNE, Pattern.compile("^Nether Wart Fortune" + STAT_PATTERN_END)); put(COCOA_BEANS_FORTUNE, Pattern.compile("^Cocoa Beans Fortune" + STAT_PATTERN_END)); put(COMBAT_WISDOM, Pattern.compile("^Combat Wisdom" + STAT_PATTERN_END)); put(MINING_WISDOM, Pattern.compile("^Mining Wisdom" + STAT_PATTERN_END)); put(FARMING_WISDOM, Pattern.compile("^Farming Wisdom" + STAT_PATTERN_END)); put(FORAGING_WISDOM, Pattern.compile("^Foraging Wisdom" + STAT_PATTERN_END)); put(FISHING_WISDOM, Pattern.compile("^Fishing Wisdom" + STAT_PATTERN_END)); put(ENCHANTING_WISDOM, Pattern.compile("^Enchanting Wisdom" + STAT_PATTERN_END)); put(ALCHEMY_WISDOM, Pattern.compile("^Alchemy Wisdom" + STAT_PATTERN_END)); put(CARPENTRY_WISDOM, Pattern.compile("^Carpentry Wisdom" + STAT_PATTERN_END)); put(RUNECRAFTING_WISDOM, Pattern.compile("^Runecrafting Wisdom" + STAT_PATTERN_END)); put(SOCIAL_WISDOM, Pattern.compile("^Social Wisdom" + STAT_PATTERN_END)); put(TAMING_WISDOM, Pattern.compile("^Taming Wisdom" + STAT_PATTERN_END)); put(SPEED, Pattern.compile("^Speed" + STAT_PATTERN_END)); put(MAGIC_FIND, Pattern.compile("^Magic Find" + STAT_PATTERN_END)); put(PET_LUCK, Pattern.compile("^Pet Luck" + STAT_PATTERN_END)); put(SEA_CREATURE_CHANCE, Pattern.compile("^Sea Creature Chance" + STAT_PATTERN_END)); put(DOUBLE_HOOK_CHANCE, Pattern.compile("^Double Hook Chance" + STAT_PATTERN_END)); put(FISHING_SPEED, Pattern.compile("^Fishing Speed" + STAT_PATTERN_END)); put(COLD_RESISTANCE, Pattern.compile("^Cold Resistance" + STAT_PATTERN_END)); put(BONUS_PEST_CHANCE, Pattern.compile("^Bonus Pest Chance" + STAT_PATTERN_END)); } }; public static final String STAT_PATTERN_END = ": ((?:\\+|-)([0-9]+(\\.[0-9]+)?))%?"; public static Stats getBaseStats() { JsonObject misc = Constants.MISC; if (misc == null) return null; Stats stats = new Stats(); for (String statName : defaultStatNames) { stats.addStat(statName, Utils.getElementAsFloat(Utils.getElement(misc, "base_stats." + statName), 0)); } return stats; } private static Stats getFairyBonus(int fairyExchanges) { Stats bonus = new Stats(); bonus.addStat(SPEED, fairyExchanges / 10); for (int i = 0; i < fairyExchanges; i++) { bonus.addStat(STRENGTH, (i + 1) % 5 == 0 ? 2 : 1); bonus.addStat(DEFENCE, (i + 1) % 5 == 0 ? 2 : 1); bonus.addStat(HEALTH, 3 + i / 2); } return bonus; } private static Stats getSkillBonus(Map skyblockInfo) { JsonObject bonuses = Constants.BONUSES; if (bonuses == null) return null; Stats skillBonus = new Stats(); for (Map.Entry entry : skyblockInfo.entrySet()) { JsonElement element = Utils.getElement(bonuses, "bonus_stats." + entry.getKey()); if (element != null && element.isJsonObject()) { JsonObject skillStatMap = element.getAsJsonObject(); Stats currentBonus = new Stats(); for (int i = 1; i <= entry.getValue().level; i++) { if (skillStatMap.has("" + i)) { currentBonus = new Stats(); for (Map.Entry entry2 : skillStatMap.get("" + i).getAsJsonObject().entrySet()) { currentBonus.addStat(entry2.getKey(), entry2.getValue().getAsFloat()); } } skillBonus.add(currentBonus); } } } return skillBonus; } public static int getPetScore(JsonObject profile) { JsonObject bonuses = Constants.BONUSES; if (bonuses == null) { Utils.showOutdatedRepoNotification("bonuses.json"); return 0; } JsonElement petsElement = Utils.getElement(profile, "pets"); if (petsElement == null) return 0; JsonArray pets = petsElement.getAsJsonArray(); HashMap highestRarityMap = new HashMap<>(); for (int i = 0; i < pets.size(); i++) { JsonObject pet = pets.get(i).getAsJsonObject(); highestRarityMap.put(pet.get("type").getAsString(), pet.get("tier").getAsString()); } int petScore = 0; for (String value : highestRarityMap.values()) { petScore += Utils.getElementAsFloat(Utils.getElement(bonuses, "pet_value." + value.toUpperCase(Locale.ROOT)), 0); } return petScore; } private static Stats getTamingBonus(JsonObject profile) { JsonObject bonuses = Constants.BONUSES; if (bonuses == null) return null; JsonElement petRewardsElement = Utils.getElement(bonuses, "pet_rewards"); if (petRewardsElement == null) return null; JsonObject petRewards = petRewardsElement.getAsJsonObject(); int petScore = getPetScore(profile); Stats petBonus = new Stats(); for (int i = 0; i <= petScore; i++) { if (petRewards.has("" + i)) { petBonus = new Stats(); for (Map.Entry entry : petRewards.get("" + i).getAsJsonObject().entrySet()) { petBonus.addStat(entry.getKey(), entry.getValue().getAsFloat()); } } } return petBonus; } private static float harpBonus(JsonObject profile) { String talk_to_melody = Utils.getElementAsString( Utils.getElement(profile, "objectives.talk_to_melody.status"), "INCOMPLETE" ); if (talk_to_melody.equalsIgnoreCase("COMPLETE")) { return 26; } else { return 0; } } private static float hotmFortune(JsonObject profile, Map skyblockInfo) { int miningLevelFortune = (int) (4 * (float) Math.floor(skyblockInfo.get("mining").level)); int miningFortuneStat = ((Utils.getElementAsInt(Utils.getElement(profile, "mining_core.nodes.mining_fortune"), 0)) * 5); int miningFortune2Stat = ((Utils.getElementAsInt( Utils.getElement(profile, "mining_core.nodes.mining_fortune_2"), 0 )) * 5); return miningFortuneStat + miningFortune2Stat + miningLevelFortune; } private static float hotmSpeed(JsonObject profile) { int miningSpeedStat = ((Utils.getElementAsInt(Utils.getElement(profile, "mining_core.nodes.mining_speed"), 0)) * 20); int miningSpeed2Stat = ((Utils.getElementAsInt(Utils.getElement(profile, "mining_core.nodes.mining_speed_2"), 0)) * 40); return miningSpeedStat + miningSpeed2Stat; } public static Stats getPassiveBonuses(Map skyblockInfo, JsonObject profile) { Stats passiveBonuses = new Stats(); Stats fairyBonus = getFairyBonus((int) Utils.getElementAsFloat(Utils.getElement( profile, "fairy_soul.fairy_exchanges" ), 0)); Stats skillBonus = getSkillBonus(skyblockInfo); Stats petBonus = getTamingBonus(profile); if (skillBonus == null || petBonus == null) { return null; } passiveBonuses.add(fairyBonus); passiveBonuses.add(skillBonus); passiveBonuses.addStat(INTELLIGENCE, harpBonus(profile)); passiveBonuses.add(petBonus); return passiveBonuses; } public static Stats getHOTMBonuses(Map skyblockInfo, JsonObject profile) { Stats hotmBonuses = new Stats(); hotmBonuses.addStat(MINING_FORTUNE, hotmFortune(profile, skyblockInfo)); hotmBonuses.addStat(MINING_SPEED, hotmSpeed(profile)); return hotmBonuses; } private static String getFullset(JsonArray armor, int ignore) { String fullset = null; for (int i = 0; i < armor.size(); i++) { if (i == ignore) continue; JsonElement itemElement = armor.get(i); if (itemElement == null || !itemElement.isJsonObject()) { fullset = null; break; } JsonObject item = itemElement.getAsJsonObject(); String internalname = item.get("internalname").getAsString(); String[] split = internalname.split("_"); split[split.length - 1] = ""; String armorname = StringUtils.join(split, "_"); if (fullset == null) { fullset = armorname; } else if (!fullset.equalsIgnoreCase(armorname)) { fullset = null; break; } } return fullset; } private static Stats getSetBonuses( Stats stats, Map inventoryInfo, Map skyblockInfo, JsonObject profile ) { JsonArray armor = inventoryInfo.get("inv_armor"); Stats bonuses = new Stats(); String fullset = getFullset(armor, -1); if (fullset != null) { // TODO @nea: repo based stat delivery? (with lisp) switch (fullset) { case "LAPIS_ARMOR_": bonuses.addStat(HEALTH, 60); break; case "FAIRY_": bonuses.addStat(HEALTH, Utils.getElementAsFloat(Utils.getElement(profile, "fairy_souls_collected"), 0)); break; case "SPEEDSTER_": bonuses.addStat(SPEED, 20); break; case "YOUNG_DRAGON_": bonuses.addStat(SPEED, 70); break; case "MASTIFF_": bonuses.addStat(HEALTH, 50 * Math.round(stats.get(CRIT_DAMAGE))); break; case "ANGLER_": bonuses.addStat(HEALTH, 10 * (float) Math.floor(skyblockInfo.get("fishing").level)); bonuses.addStat(SEA_CREATURE_CHANCE, 4); break; case "ARMOR_OF_MAGMA_": int bonus = (int) Math.min( 200, Math.floor(Utils.getElementAsFloat(Utils.getElement(profile, "stats.kills_magma_cube"), 0) / 10) ); bonuses.addStat(HEALTH, bonus); bonuses.addStat(INTELLIGENCE, bonus); case "OLD_DRAGON_": bonuses.addStat(HEALTH, 200); bonuses.addStat(DEFENCE, 40); break; } } JsonElement chestplateElement = armor.get(2); if (chestplateElement != null && chestplateElement.isJsonObject()) { JsonObject chestplate = chestplateElement.getAsJsonObject(); if (chestplate.get("internalname").getAsString().equals("OBSIDIAN_CHESTPLATE")) { JsonArray inventory = inventoryInfo.get("inv_contents"); for (int i = 0; i < inventory.size(); i++) { JsonElement itemElement = inventory.get(i); if (itemElement != null && itemElement.isJsonObject()) { JsonObject item = itemElement.getAsJsonObject(); if (item.get("internalname").getAsString().equals("OBSIDIAN")) { int count = 1; if (item.has("count")) { count = item.get("count").getAsInt(); } bonuses.addStat(SPEED, count / 20); } } } } } return bonuses; } private static Stats getStatForItem(String internalname, JsonObject item, JsonArray lore) { Stats stats = new Stats(); for (int i = 0; i < lore.size(); i++) { String line = lore.get(i).getAsString(); for (Map.Entry entry : STAT_PATTERN_MAP.entrySet()) { Matcher matcher = entry.getValue().matcher(Utils.cleanColour(line)); if (matcher.find()) { int bonus = Integer.parseInt(matcher.group(1)); stats.addStat(entry.getKey(), bonus); } } } if (internalname.equals("DAY_CRYSTAL") || internalname.equals("NIGHT_CRYSTAL")) { stats.addStat(STRENGTH, 2.5f); stats.addStat(DEFENCE, 2.5f); } if (internalname.equals("NEW_YEAR_CAKE_BAG") && item.has("item_contents")) { JsonArray bytesArr = item.get("item_contents").getAsJsonArray(); byte[] bytes = new byte[bytesArr.size()]; for (int i = 0; i < bytesArr.size(); i++) { bytes[i] = bytesArr.get(i).getAsByte(); } try { NBTTagCompound contents_nbt = CompressedStreamTools.readCompressed(new ByteArrayInputStream(bytes)); NBTTagList items = contents_nbt.getTagList("i", 10); HashSet cakes = new HashSet<>(); for (int j = 0; j < items.tagCount(); j++) { if (items.getCompoundTagAt(j).getKeySet().size() > 0) { NBTTagCompound nbt = items.getCompoundTagAt(j).getCompoundTag("tag"); if (nbt != null && nbt.hasKey("ExtraAttributes", 10)) { NBTTagCompound ea = nbt.getCompoundTag("ExtraAttributes"); if (ea.hasKey("new_years_cake")) { cakes.add(ea.getInteger("new_years_cake")); } } } } stats.addStat(HEALTH, cakes.size()); } catch (IOException e) { e.printStackTrace(); return stats; } } return stats; } private static Stats getItemBonuses(boolean talismanOnly, JsonArray... inventories) { JsonObject misc = Constants.MISC; if (misc == null) return null; JsonElement talisman_upgrades_element = misc.get("talisman_upgrades"); if (talisman_upgrades_element == null) return null; JsonObject talisman_upgrades = talisman_upgrades_element.getAsJsonObject(); HashMap itemBonuses = new HashMap<>(); for (JsonArray inventory : inventories) { for (int i = 0; i < inventory.size(); i++) { JsonElement itemElement = inventory.get(i); if (itemElement != null && itemElement.isJsonObject()) { JsonObject item = itemElement.getAsJsonObject(); String internalname = item.get("internalname").getAsString(); if (itemBonuses.containsKey(internalname)) { continue; } if (!talismanOnly || Utils.checkItemType( item.get("lore").getAsJsonArray(), true, "ACCESSORY", "HATCESSORY" ) >= 0) { Stats itemBonus = getStatForItem(internalname, item, item.get("lore").getAsJsonArray()); itemBonuses.put(internalname, itemBonus); for (Map.Entry talisman_upgrades_item : talisman_upgrades.entrySet()) { JsonArray upgrades = talisman_upgrades_item.getValue().getAsJsonArray(); for (int j = 0; j < upgrades.size(); j++) { String upgrade = upgrades.get(j).getAsString(); if (upgrade.equals(internalname)) { itemBonuses.put(talisman_upgrades_item.getKey(), new Stats()); break; } } } } } } } Stats itemBonusesStats = new Stats(); for (Stats stats : itemBonuses.values()) { itemBonusesStats.add(stats); } return itemBonusesStats; } public static Stats getPetStatBonuses(JsonObject petsInfo) { JsonObject petsJson = Constants.PETS; JsonObject petnums = Constants.PETNUMS; if (petsJson == null || petnums == null) return new Stats(); if ( petsInfo != null && petsInfo.has("active_pet") && petsInfo.get("active_pet") != null && petsInfo.get("active_pet").isJsonObject() ) { JsonObject pet = petsInfo.get("active_pet").getAsJsonObject(); if ( pet.has("type") && pet.get("type") != null && pet.has("tier") && pet.get("tier") != null && pet.has("exp") && pet.get("exp") != null ) { String petname = pet.get("type").getAsString(); String tier = pet.get("tier").getAsString(); String heldItem = Utils.getElementAsString(pet.get("heldItem"), null); if (!petnums.has(petname)) { return new Stats(); } String tierNum = GuiProfileViewer.RARITY_TO_NUM.get(tier); float exp = pet.get("exp").getAsFloat(); if (tierNum == null) return new Stats(); if ( pet.has("heldItem") && !pet.get("heldItem").isJsonNull() && pet.get("heldItem").getAsString().equals("PET_ITEM_TIER_BOOST") ) { tierNum = "" + (Integer.parseInt(tierNum) + 1); } PetLeveling.PetLevel levelObj = PetLeveling.getPetLevelingForPet( petname, PetInfoOverlay.Rarity.valueOf(tier) ).getPetLevel(exp); float level = levelObj.getCurrentLevel(); float currentLevelRequirement = levelObj.getExpRequiredForNextLevel(); float maxXP = levelObj.getMaxLevel(); pet.addProperty("level", level); pet.addProperty("currentLevelRequirement", currentLevelRequirement); pet.addProperty("maxXP", maxXP); JsonObject petItem = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(petname + ";" + tierNum); if (petItem == null) return new Stats(); Stats stats = new Stats(); JsonObject petInfo = petnums.get(petname).getAsJsonObject(); if (petInfo.has(tier)) { JsonObject petInfoTier = petInfo.get(tier).getAsJsonObject(); if (petInfoTier == null || !petInfoTier.has("1") || !petInfoTier.has("100")) { return new Stats(); } JsonObject min = petInfoTier.get("1").getAsJsonObject(); JsonObject max = petInfoTier.get("100").getAsJsonObject(); float minMix = (100 - level) / 99f; float maxMix = (level - 1) / 99f; for (Map.Entry entry : max.get("statNums").getAsJsonObject().entrySet()) { float statMax = entry.getValue().getAsFloat(); float statMin = min.get("statNums").getAsJsonObject().get(entry.getKey()).getAsFloat(); float val = statMin * minMix + statMax * maxMix; stats.addStat(entry.getKey().toLowerCase(Locale.ROOT), (int) Math.floor(val)); } } if (heldItem != null) { HashMap petStatBoots = GuiProfileViewer.PET_STAT_BOOSTS.get(heldItem); HashMap petStatBootsMult = GuiProfileViewer.PET_STAT_BOOSTS_MULT.get(heldItem); if (petStatBoots != null) { for (Map.Entry entryBoost : petStatBoots.entrySet()) { String key = entryBoost.getKey().toLowerCase(Locale.ROOT); try { stats.addStat(key, entryBoost.getValue()); } catch (Exception ignored) { } } } if (petStatBootsMult != null) { for (Map.Entry entryBoost : petStatBootsMult.entrySet()) { String key = entryBoost.getKey().toLowerCase(Locale.ROOT); try { stats.scale(key, entryBoost.getValue()); } catch (Exception ignored) { } } } } return stats; } } return new Stats(); } private static float getStatMult(Map inventoryInfo) { float mult = 1f; JsonArray armor = inventoryInfo.get("inv_armor"); String fullset = getFullset(armor, -1); if (fullset != null && fullset.equals("SUPERIOR_DRAGON_")) { mult *= 1.05f; } for (int i = 0; i < armor.size(); i++) { JsonElement itemElement = armor.get(i); if (itemElement == null || !itemElement.isJsonObject()) continue; JsonObject item = itemElement.getAsJsonObject(); String reforge = Utils.getElementAsString(Utils.getElement(item, "ExtraAttributes.modifier"), ""); if (reforge.equals("renowned")) { mult *= 1.01f; } } return mult; } private static void applyLimits(Stats stats, Map inventoryInfo) { //>0 JsonArray armor = inventoryInfo.get("inv_armor"); String fullset = getFullset(armor, 3); if (fullset != null) { switch (fullset) { case "CHEAP_TUXEDO_": stats.statsJson.add(HEALTH, new JsonPrimitive(Math.min(75, stats.get(HEALTH)))); case "FANCY_TUXEDO_": stats.statsJson.add(HEALTH, new JsonPrimitive(Math.min(150, stats.get(HEALTH)))); case "ELEGANT_TUXEDO_": stats.statsJson.add(HEALTH, new JsonPrimitive(Math.min(250, stats.get(HEALTH)))); } } for (Map.Entry statEntry : stats.statsJson.entrySet()) { if ( statEntry.getKey().equals(CRIT_DAMAGE) || statEntry.getKey().equals(INTELLIGENCE) || statEntry.getKey().equals(BONUS_ATTACK_SPEED) ) continue; stats.statsJson.add(statEntry.getKey(), new JsonPrimitive(Math.max(0, statEntry.getValue().getAsFloat()))); } } public static Stats getStats( Map levelingInfo, Map inventoryInfo, JsonObject petsInfo, JsonObject profile ) { if (levelingInfo == null || inventoryInfo == null || profile == null) return null; JsonArray armor = inventoryInfo.get("inv_armor"); JsonArray inventory = inventoryInfo.get("inv_contents"); JsonArray talisman_bag = inventoryInfo.get("talisman_bag"); Stats passiveBonuses = getPassiveBonuses(levelingInfo, profile); Stats hotmBonuses = getHOTMBonuses(levelingInfo, profile); Stats armorBonuses = getItemBonuses(false, armor); Stats talismanBonuses = getItemBonuses(true, inventory, talisman_bag); if (passiveBonuses == null || armorBonuses == null || talismanBonuses == null) { return null; } Stats stats = getBaseStats(); if (stats == null) { return null; } Stats petBonus = getPetStatBonuses(petsInfo); stats = stats.add(passiveBonuses).add(armorBonuses).add(talismanBonuses).add(petBonus).add(hotmBonuses); stats.add(getSetBonuses(stats, inventoryInfo, levelingInfo, profile)); stats.scaleAll(getStatMult(inventoryInfo)); applyLimits(stats, inventoryInfo); return stats; } /** * Finds the Magical Power the player selected if applicable * * @param profileInfo profile information object * @return selected magical power as a String or null * @see SkyblockProfiles.SkyblockProfile#getLevelingInfo() */ public static @Nullable String getSelectedMagicalPower(JsonObject profileInfo) { String abs = "accessory_bag_storage"; if ( profileInfo == null || !profileInfo.has(abs) || !profileInfo.get(abs).isJsonObject() || !profileInfo.get(abs).getAsJsonObject().has("selected_power") ) { return null; } String selectedPower = profileInfo.get(abs).getAsJsonObject().get("selected_power").getAsString(); return selectedPower.substring(0, 1).toUpperCase(Locale.ROOT) + selectedPower.substring(1); } public static @Nullable QuiverInfo getQuiverInfo(Map inventoryInfo, JsonObject profileInfo) { if (inventoryInfo == null || !inventoryInfo.containsKey("quiver")) { return null; } QuiverInfo quiverInfo = new QuiverInfo(); quiverInfo.arrows = new HashMap<>(); JsonArray quiver = inventoryInfo.get("quiver"); for (JsonElement quiverEntry : quiver) { if (quiverEntry == null || quiverEntry.isJsonNull() || !quiverEntry.isJsonObject()) { continue; } JsonObject stack = quiverEntry.getAsJsonObject(); if (!stack.has("internalname") || !stack.has("count")) { continue; } String internalName = stack.get("internalname").getAsString(); int count = stack.get("count").getAsInt(); quiverInfo.arrows.computeIfPresent(internalName, (key, existing) -> existing + count); quiverInfo.arrows.putIfAbsent(internalName, count); } quiverInfo.selectedArrow = Utils.getElementAsString( Utils.getElement(profileInfo, "item_data.favorite_arrow"), null ); return quiverInfo; } public static class Stats { JsonObject statsJson = new JsonObject(); /*public float health; public float defence; public float strength; public float speed; public float crit_chance; public float crit_damage; public float bonus_attack_speed; public float intelligence; public float sea_creature_chance; public float magic_find; public float pet_luck;*/ public Stats(Stats... statses) { for (Stats stats : statses) { add(stats); } } /*@Override public String toString() { return String.format("{health=%s,defence=%s,strength=%s,speed=%s,crit_chance=%s,crit_damage=%s," + "bonus_attack_speed=%s,intelligence=%s,sea_creature_chance=%s,magic_find=%s,pet_luck=%s}", stats.get("health"), defence, strength, speed, crit_chance, crit_damage, bonus_attack_speed, intelligence, sea_creature_chance, magic_find, pet_luck ); }*/ public int size() { return statsJson.entrySet().size(); } public float get(String statName) { if (statsJson.has(statName)) { return statsJson.get(statName).getAsFloat(); } else { return 0; } } public Stats add(Stats stats) { for (Map.Entry statEntry : stats.statsJson.entrySet()) { if (statEntry.getValue().isJsonPrimitive() && ((JsonPrimitive) statEntry.getValue()).isNumber()) { if (!statsJson.has(statEntry.getKey())) { statsJson.add(statEntry.getKey(), statEntry.getValue()); } else { JsonPrimitive e = statsJson.get(statEntry.getKey()).getAsJsonPrimitive(); float statNum = e.getAsFloat() + statEntry.getValue().getAsFloat(); statsJson.add(statEntry.getKey(), new JsonPrimitive(statNum)); } } } return this; } public void scale(String statName, float scale) { if (statsJson.has(statName)) { statsJson.add(statName, new JsonPrimitive(statsJson.get(statName).getAsFloat() * scale)); } } public void scaleAll(float scale) { for (Map.Entry statEntry : statsJson.entrySet()) { statsJson.add(statEntry.getKey(), new JsonPrimitive(statEntry.getValue().getAsFloat() * scale)); } } public void addStat(String statName, float amount) { if (!statsJson.has(statName)) { statsJson.add(statName, new JsonPrimitive(amount)); } else { JsonPrimitive e = statsJson.get(statName).getAsJsonPrimitive(); statsJson.add(statName, new JsonPrimitive(e.getAsFloat() + amount)); } } } }