aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/inventory
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/features/inventory')
-rw-r--r--src/main/kotlin/features/inventory/CraftingOverlay.kt33
-rw-r--r--src/main/kotlin/features/inventory/ItemHotkeys.kt10
-rw-r--r--src/main/kotlin/features/inventory/ItemRarityCosmetics.kt50
-rw-r--r--src/main/kotlin/features/inventory/JunkHighlighter.kt30
-rw-r--r--src/main/kotlin/features/inventory/PetFeatures.kt551
-rw-r--r--src/main/kotlin/features/inventory/PriceData.kt149
-rw-r--r--src/main/kotlin/features/inventory/REIDependencyWarner.kt32
-rw-r--r--src/main/kotlin/features/inventory/SaveCursorPosition.kt113
-rw-r--r--src/main/kotlin/features/inventory/SlotLocking.kt384
-rw-r--r--src/main/kotlin/features/inventory/TimerInLore.kt152
-rw-r--r--src/main/kotlin/features/inventory/WardrobeKeybinds.kt80
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButton.kt162
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt231
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt5
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButtons.kt158
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt24
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt105
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt84
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt370
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt82
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt108
21 files changed, 2133 insertions, 780 deletions
diff --git a/src/main/kotlin/features/inventory/CraftingOverlay.kt b/src/main/kotlin/features/inventory/CraftingOverlay.kt
index d2c79fd..5241f54 100644
--- a/src/main/kotlin/features/inventory/CraftingOverlay.kt
+++ b/src/main/kotlin/features/inventory/CraftingOverlay.kt
@@ -1,20 +1,20 @@
package moe.nea.firmament.features.inventory
import io.github.moulberry.repo.data.NEUCraftingRecipe
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.item.ItemStack
-import net.minecraft.util.Formatting
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.world.item.ItemStack
+import net.minecraft.ChatFormatting
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.skyblockId
-object CraftingOverlay : FirmamentFeature {
+object CraftingOverlay {
- private var screen: GenericContainerScreen? = null
+ private var screen: ContainerScreen? = null
private var recipe: NEUCraftingRecipe? = null
private var useNextScreen = false
private val craftingOverlayIndices = listOf(
@@ -24,7 +24,7 @@ object CraftingOverlay : FirmamentFeature {
)
val CRAFTING_SCREEN_NAME = "Craft Item"
- fun setOverlay(screen: GenericContainerScreen?, recipe: NEUCraftingRecipe) {
+ fun setOverlay(screen: ContainerScreen?, recipe: NEUCraftingRecipe) {
this.screen = screen
if (screen == null) {
useNextScreen = true
@@ -34,7 +34,7 @@ object CraftingOverlay : FirmamentFeature {
@Subscribe
fun onScreenChange(event: ScreenChangeEvent) {
- if (useNextScreen && event.new is GenericContainerScreen
+ if (useNextScreen && event.new is ContainerScreen
&& event.new.title?.string == "Craft Item"
) {
useNextScreen = false
@@ -42,18 +42,19 @@ object CraftingOverlay : FirmamentFeature {
}
}
- override val identifier: String
+ val identifier: String
get() = "crafting-overlay"
+ @OptIn(ExpensiveItemCacheApi::class)
@Subscribe
fun onSlotRender(event: SlotRenderEvents.After) {
val slot = event.slot
val recipe = this.recipe ?: return
- if (slot.inventory != screen?.screenHandler?.inventory) return
- val recipeIndex = craftingOverlayIndices.indexOf(slot.index)
+ if (slot.container != screen?.menu?.container) return
+ val recipeIndex = craftingOverlayIndices.indexOf(slot.containerSlot)
if (recipeIndex < 0) return
val expectedItem = recipe.inputs[recipeIndex]
- val actualStack = slot.stack ?: ItemStack.EMPTY!!
+ val actualStack = slot.item ?: ItemStack.EMPTY!!
val actualEntry = SBItemStack(actualStack)
if ((actualEntry.skyblockId != expectedItem.skyblockId || actualEntry.getStackSize() < expectedItem.amount)
&& expectedItem.amount.toInt() != 0
@@ -66,15 +67,15 @@ object CraftingOverlay : FirmamentFeature {
0x80FF0000.toInt()
)
}
- if (!slot.hasStack()) {
+ if (!slot.hasItem()) {
val itemStack = SBItemStack(expectedItem)?.asImmutableItemStack() ?: return
- event.context.drawItem(itemStack, event.slot.x, event.slot.y)
- event.context.drawStackOverlay(
+ event.context.renderItem(itemStack, event.slot.x, event.slot.y)
+ event.context.renderItemDecorations(
MC.font,
itemStack,
event.slot.x,
event.slot.y,
- "${Formatting.RED}${expectedItem.amount.toInt()}"
+ "${ChatFormatting.RED}${expectedItem.amount.toInt()}"
)
}
}
diff --git a/src/main/kotlin/features/inventory/ItemHotkeys.kt b/src/main/kotlin/features/inventory/ItemHotkeys.kt
index 4aa8202..e9d0631 100644
--- a/src/main/kotlin/features/inventory/ItemHotkeys.kt
+++ b/src/main/kotlin/features/inventory/ItemHotkeys.kt
@@ -2,22 +2,26 @@ package moe.nea.firmament.features.inventory
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
-import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.HypixelStaticData
-import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.ItemCache.isBroken
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.asBazaarStock
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.skyblock.SBItemUtil.getSearchName
object ItemHotkeys {
+ @Config
object TConfig : ManagedConfig("item-hotkeys", Category.INVENTORY) {
val openGlobalTradeInterface by keyBindingWithDefaultUnbound("global-trade-interface")
}
+ @OptIn(ExpensiveItemCacheApi::class)
@Subscribe
fun onHandledInventoryPress(event: HandledScreenKeyPressedEvent) {
if (!event.matches(TConfig.openGlobalTradeInterface)) {
@@ -26,7 +30,7 @@ object ItemHotkeys {
var item = event.screen.focusedItemStack ?: return
val skyblockId = item.skyBlockId ?: return
item = RepoManager.getNEUItem(skyblockId)?.asItemStack()?.takeIf { !it.isBroken } ?: item
- if (HypixelStaticData.hasBazaarStock(skyblockId)) {
+ if (HypixelStaticData.hasBazaarStock(skyblockId.asBazaarStock)) {
MC.sendCommand("bz ${item.getSearchName()}")
} else if (HypixelStaticData.hasAuctionHouseOffers(skyblockId)) {
MC.sendCommand("ahs ${item.getSearchName()}")
diff --git a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
index d2c555b..9712067 100644
--- a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
+++ b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
@@ -1,56 +1,38 @@
package moe.nea.firmament.features.inventory
import java.awt.Color
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.item.ItemStack
-import net.minecraft.util.Formatting
-import net.minecraft.util.Identifier
+import net.minecraft.client.renderer.RenderPipelines
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.world.item.ItemStack
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HotbarItemRenderEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
-import moe.nea.firmament.util.collections.lastNotNullOfOrNull
-import moe.nea.firmament.util.collections.memoizeIdentity
-import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.skyblock.Rarity
-import moe.nea.firmament.util.unformattedString
-object ItemRarityCosmetics : FirmamentFeature {
- override val identifier: String
+object ItemRarityCosmetics {
+ val identifier: String
get() = "item-rarity-cosmetics"
+ @Config
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val showItemRarityBackground by toggle("background") { false }
val showItemRarityInHotbar by toggle("background-hotbar") { false }
}
- override val config: ManagedConfig
- get() = TConfig
-
- private val rarityToColor = mapOf(
- Rarity.COMMON to Formatting.WHITE,
- Rarity.UNCOMMON to Formatting.GREEN,
- Rarity.RARE to Formatting.BLUE,
- Rarity.EPIC to Formatting.DARK_PURPLE,
- Rarity.LEGENDARY to Formatting.GOLD,
- Rarity.MYTHIC to Formatting.LIGHT_PURPLE,
- Rarity.DIVINE to Formatting.AQUA,
- Rarity.SPECIAL to Formatting.RED,
- Rarity.VERY_SPECIAL to Formatting.RED,
- Rarity.SUPREME to Formatting.DARK_RED,
- ).mapValues {
- val c = Color(it.value.colorValue!!)
+ private val rarityToColor = Rarity.colourMap.mapValues {
+ val c = Color(it.value.color!!)
c.rgb
}
- fun drawItemStackRarity(drawContext: DrawContext, x: Int, y: Int, item: ItemStack) {
+ fun drawItemStackRarity(drawContext: GuiGraphics, x: Int, y: Int, item: ItemStack) {
val rarity = Rarity.fromItem(item) ?: return
val rgb = rarityToColor[rarity] ?: 0xFF00FF80.toInt()
- drawContext.drawGuiTexture(
- RenderLayer::getGuiTextured,
- Identifier.of("firmament:item_rarity_background"),
+ drawContext.blitSprite(
+ RenderPipelines.GUI_TEXTURED,
+ ResourceLocation.parse("firmament:item_rarity_background"),
x, y,
16, 16,
rgb
@@ -61,7 +43,7 @@ object ItemRarityCosmetics : FirmamentFeature {
@Subscribe
fun onRenderSlot(it: SlotRenderEvents.Before) {
if (!TConfig.showItemRarityBackground) return
- val stack = it.slot.stack ?: return
+ val stack = it.slot.item ?: return
drawItemStackRarity(it.context, it.slot.x, it.slot.y, stack)
}
diff --git a/src/main/kotlin/features/inventory/JunkHighlighter.kt b/src/main/kotlin/features/inventory/JunkHighlighter.kt
new file mode 100644
index 0000000..15bdcfa
--- /dev/null
+++ b/src/main/kotlin/features/inventory/JunkHighlighter.kt
@@ -0,0 +1,30 @@
+package moe.nea.firmament.features.inventory
+
+import org.lwjgl.glfw.GLFW
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.SlotRenderEvents
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.skyblock.SBItemUtil.getSearchName
+import moe.nea.firmament.util.useMatch
+
+object JunkHighlighter {
+ val identifier: String
+ get() = "junk-highlighter"
+
+ @Config
+ object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
+ val junkRegex by string("regex") { "" }
+ val highlightBind by keyBinding("highlight") { GLFW.GLFW_KEY_LEFT_CONTROL }
+ }
+
+ @Subscribe
+ fun onDrawSlot(event: SlotRenderEvents.After) {
+ if (!TConfig.highlightBind.isPressed() || TConfig.junkRegex.isEmpty()) return
+ val junkRegex = TConfig.junkRegex.toPattern()
+ val slot = event.slot
+ junkRegex.useMatch(slot.item.getSearchName()) {
+ event.context.fill(slot.x, slot.y, slot.x + 16, slot.y + 16, 0xffff0000.toInt())
+ }
+ }
+}
diff --git a/src/main/kotlin/features/inventory/PetFeatures.kt b/src/main/kotlin/features/inventory/PetFeatures.kt
index 5ca10f7..e0bb4b1 100644
--- a/src/main/kotlin/features/inventory/PetFeatures.kt
+++ b/src/main/kotlin/features/inventory/PetFeatures.kt
@@ -1,40 +1,561 @@
package moe.nea.firmament.features.inventory
-import net.minecraft.util.Identifier
+import java.util.regex.Matcher
+import org.joml.Vector2i
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Component
+import net.minecraft.ChatFormatting
+import net.minecraft.util.StringRepresentable
+import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.HudRenderEvent
+import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.events.ProfileSwitchEvent
+import moe.nea.firmament.events.SlotClickEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.jarvis.JarvisIntegration
+import moe.nea.firmament.repo.ExpLadders
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
+import moe.nea.firmament.repo.ItemCache.asItemStack
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.FirmFormatters.formatPercent
+import moe.nea.firmament.util.FirmFormatters.shortFormat
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.formattedString
+import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.petData
import moe.nea.firmament.util.render.drawGuiTexture
+import moe.nea.firmament.util.skyblock.Rarity
+import moe.nea.firmament.util.skyblock.TabListAPI
+import moe.nea.firmament.util.skyblockUUID
+import moe.nea.firmament.util.titleCase
+import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
+import moe.nea.firmament.util.withColor
-object PetFeatures : FirmamentFeature {
- override val identifier: String
+object PetFeatures {
+ val identifier: String
get() = "pets"
- override val config: ManagedConfig?
- get() = TConfig
-
+ @Config
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val highlightEquippedPet by toggle("highlight-pet") { true }
+ val petOverlay by toggle("pet-overlay") { false }
+ val petOverlayHud by position("pet-overlay-hud", 80, 10) {
+ Vector2i()
+ }
+ val petOverlayHudStyle by choice("pet-overlay-hud-style") { PetOverlayHudStyles.PLAIN_NO_BACKGROUND }
}
- val petMenuTitle = "Pets(?: \\([0-9]+/[0-9]+\\))?".toPattern()
+ enum class PetOverlayHudStyles : StringRepresentable {
+ PLAIN_NO_BACKGROUND,
+ COLOUR_NO_BACKGROUND,
+ PLAIN_BACKGROUND,
+ COLOUR_BACKGROUND,
+ ICON_ONLY;
+
+ override fun getSerializedName() : String {
+ return name
+ }
+ }
+
+ private val petMenuTitle = "Pets(?: \\([0-9]+/[0-9]+\\))?".toPattern()
+ private val autopetPattern =
+ "§cAutopet §eequipped your §7\\[Lvl (\\d{1,3})\\] §([fa956d])([\\w\\s]+)§e! §aVIEW RULE".toPattern()
+ private val petItemPattern = "§aYour pet is now holding (§[fa956d][\\w\\s]+)§a.".toPattern()
+ private val petLevelUpPattern = "§aYour §([fa956d])([\\w\\s]+) §aleveled up to level §9(\\d+)§a!".toPattern()
+ private val petMap = HashMap<String, ParsedPet>()
+ private var currentPetUUID: String = ""
+ private var tempTabPet: ParsedPet? = null
+ private var tempChatPet: ParsedPet? = null
+
+ @Subscribe
+ fun onProfileSwitch(event: ProfileSwitchEvent) {
+ petMap.clear()
+ currentPetUUID = ""
+ tempTabPet = null
+ tempChatPet = null
+ }
@Subscribe
fun onSlotRender(event: SlotRenderEvents.Before) {
- if (!TConfig.highlightEquippedPet) return
- val stack = event.slot.stack
- if (stack.petData?.active == true)
- petMenuTitle.useMatch(MC.screenName ?: return) {
+ // Cache pets
+ petMenuTitle.useMatch(MC.screenName ?: return) {
+ val stack = event.slot.item
+ if (!stack.isEmpty) cachePet(stack)
+ if (stack.petData?.active == true) {
+ if (currentPetUUID == "") currentPetUUID = stack.skyblockUUID.toString()
+ // Highlight active pet feature
+ if (!TConfig.highlightEquippedPet) return
event.context.drawGuiTexture(
- event.slot.x, event.slot.y, 0, 16, 16,
- Identifier.of("firmament:selected_pet_background")
+ Firmament.identifier("selected_pet_background"),
+ event.slot.x, event.slot.y, 16, 16,
)
}
+ }
+ }
+
+ private fun cachePet(stack: ItemStack) {
+ // Cache information about a pet
+ if (stack.skyblockUUID == null) return
+ if (petMap.containsKey(stack.skyblockUUID.toString()) &&
+ petMap[stack.skyblockUUID.toString()]?.isComplete == true) return
+
+ val pet = PetParser.parsePetMenuSlot(stack) ?: return
+ petMap[stack.skyblockUUID.toString()] = pet
+ }
+
+ @Subscribe
+ fun onSlotClick(event: SlotClickEvent) {
+ // Check for switching/removing pet manually
+ petMenuTitle.useMatch(MC.screenName ?: return) {
+ if (event.slot.container is Inventory) return
+ if (event.button != 0 && event.button != 1) return
+ val petData = event.stack.petData ?: return
+ if (petData.active == true) {
+ currentPetUUID = "None"
+ return
+ }
+ if (event.button != 0) return
+ if (!petMap.containsKey(event.stack.skyblockUUID.toString())) cachePet(event.stack)
+ currentPetUUID = event.stack.skyblockUUID.toString()
+ }
}
+ @Subscribe
+ fun onChatEvent(event: ProcessChatEvent) {
+ // Handle AutoPet
+ var matcher = autopetPattern.matcher(event.text.formattedString())
+ if (matcher.matches()) {
+ val tempMap = petMap.filter { (uuid, pet) ->
+ pet.name == matcher.group(3) &&
+ pet.rarity == PetParser.reversePetColourMap[matcher.group(2)] &&
+ pet.level <= matcher.group(1).toInt()
+ }
+ if (tempMap.isNotEmpty()) {
+ currentPetUUID = tempMap.keys.first()
+ } else {
+ tempChatPet = PetParser.parsePetChatMessage(matcher.group(3), matcher.group(2), matcher.group(1).toInt())
+ currentPetUUID = ""
+ }
+ tempTabPet = null
+ return
+ }
+ // Handle changing pet item
+ // This is needed for when pet item can't be found in tab list
+ matcher = petItemPattern.matcher(event.text.formattedString())
+ if (matcher.matches()) {
+ petMap[currentPetUUID]?.petItem = matcher.group(1)
+ tempTabPet?.petItem = matcher.group(1)
+ tempChatPet?.petItem = matcher.group(1)
+ // TODO: Handle tier boost pet items if required
+ // I'm not rich enough to be able to test tier boosts
+ }
+ // Handle pet levelling up
+ // This is needed for when pet level can't be found in tab list
+ matcher = petLevelUpPattern.matcher(event.text.formattedString())
+ if (matcher.matches()) {
+ val tempPet =
+ PetParser.parsePetChatMessage(matcher.group(2), matcher.group(1), matcher.group(3).toInt()) ?: return
+ val tempMap = petMap.filter { (uuid, pet) ->
+ pet.name == tempPet.name &&
+ pet.rarity == tempPet.rarity &&
+ pet.level <= tempPet.level
+ }
+ if (tempMap.isNotEmpty()) petMap[tempMap.keys.first()]?.update(tempPet)
+ if (tempTabPet?.name == tempPet.name && tempTabPet?.rarity == tempPet.rarity) {
+ tempTabPet?.update(tempPet)
+ }
+ if (tempChatPet?.name == tempPet.name && tempChatPet?.rarity == tempPet.rarity) tempChatPet?.update(tempPet)
+ }
+ }
+ private fun renderLinesAndBackground(it: HudRenderEvent, lines: List<Component>) {
+ // Render background for the hud
+ if (TConfig.petOverlayHudStyle == PetOverlayHudStyles.PLAIN_BACKGROUND ||
+ TConfig.petOverlayHudStyle == PetOverlayHudStyles.COLOUR_BACKGROUND) {
+ var maxWidth = 0
+ lines.forEach { if (MC.font.width(it) > maxWidth) maxWidth = MC.font.width(it.unformattedString) }
+ val height = if (MC.font.lineHeight * lines.size > 32) MC.font.lineHeight * lines.size else 32
+ it.context.fill(0, -3, 40 + maxWidth, height + 2, 0x80000000.toInt())
+ }
+
+ // Render text for the hud
+ lines.forEachIndexed { index, line ->
+ it.context.drawString(
+ MC.font,
+ line.copy().withColor(ChatFormatting.GRAY),
+ 36,
+ MC.font.lineHeight * index,
+ -1,
+ true
+ )
+ }
+ }
+
+ @Subscribe
+ fun onRenderHud(it: HudRenderEvent) {
+ if (!TConfig.petOverlay || !SBData.isOnSkyblock) return
+
+ // Possibly handle Montezuma as a future feature? Could track how many pieces have been found etc
+ // Would likely need to be a separate config toggle though since that has
+ // very different usefulness/purpose to the pet hud outside of rift
+ if (SBData.skyblockLocation == SkyBlockIsland.RIFT) return
+
+ // Initial data
+ var pet: ParsedPet? = null
+ // Do not render the HUD if there is no pet active
+ if (currentPetUUID == "None") return
+ // Get active pet from cache
+ if (currentPetUUID != "") pet = petMap[currentPetUUID]
+ // Parse tab widget for pet data
+ val tabPet = PetParser.parseTabWidget(TabListAPI.getWidgetLines(TabListAPI.WidgetName.PET))
+ if (pet == null && tabPet == null && tempTabPet == null && tempChatPet == null) {
+ // No data on current pet
+ it.context.pose().pushMatrix()
+ TConfig.petOverlayHud.applyTransformations(JarvisIntegration.jarvis, it.context.pose())
+ val lines = mutableListOf<Component>()
+ lines.add(Component.literal("" + ChatFormatting.WHITE + "Unknown Pet"))
+ lines.add(Component.literal("Open Pets Menu To Fix"))
+ renderLinesAndBackground(it, lines)
+ it.context.pose().popMatrix()
+ return
+ }
+ if (pet == null) {
+ // Pet is only known through tab widget or chat message, potentially saved from tab widget elsewhere
+ // (e.g. another server or before removing the widget from the tab list)
+ pet = tabPet ?: tempTabPet ?: tempChatPet ?: return
+ if (tempTabPet == null) tempTabPet = tabPet
+ }
+
+ // Update pet based on tab widget if needed
+ if (tabPet != null && pet.name == tabPet.name && pet.rarity == tabPet.rarity) {
+ if (tabPet.level > pet.level) {
+ // Level has increased since caching
+ pet.level = tabPet.level
+ pet.currentExp = tabPet.currentExp
+ pet.expForNextLevel = tabPet.expForNextLevel
+ pet.totalExp = tabPet.totalExp
+ } else if (tabPet.currentExp > pet.currentExp) {
+ // Exp has increased since caching, level has not
+ pet.currentExp = tabPet.currentExp
+ pet.totalExp = tabPet.totalExp
+ }
+ if (tabPet.petItem != pet.petItem && tabPet.petItem != "Unknown") {
+ // Pet item has changed since caching
+ pet.petItem = tabPet.petItem
+ pet.petItemStack = tabPet.petItemStack
+ }
+ }
+
+ // Set the text for the HUD
+
+ val lines = mutableListOf<Component>()
+
+ if (TConfig.petOverlayHudStyle == PetOverlayHudStyles.COLOUR_NO_BACKGROUND ||
+ TConfig.petOverlayHudStyle == PetOverlayHudStyles.COLOUR_BACKGROUND) {
+ // Colour Style
+ lines.add(Component.literal("[Lvl ${pet.level}] ").append(Component.literal(pet.name)
+ .withColor((Rarity.colourMap[pet.rarity]) ?: ChatFormatting.WHITE)))
+
+ lines.add(Component.literal(pet.petItem))
+ if (pet.level != pet.maxLevel) {
+ // Exp data
+ lines.add(
+ Component.literal(
+ "" + ChatFormatting.YELLOW + "Required L${pet.level + 1}: ${shortFormat(pet.currentExp)}" +
+ ChatFormatting.GOLD + "/" + ChatFormatting.YELLOW +
+ "${shortFormat(pet.expForNextLevel)} " + ChatFormatting.GOLD +
+ "(${formatPercent(pet.currentExp / pet.expForNextLevel)})"
+ )
+ )
+ lines.add(
+ Component.literal(
+ "" + ChatFormatting.YELLOW + "Required L100: ${shortFormat(pet.totalExp)}" +
+ ChatFormatting.GOLD + "/" + ChatFormatting.YELLOW +
+ "${shortFormat(pet.expForMax)} " + ChatFormatting.GOLD +
+ "(${formatPercent(pet.totalExp / pet.expForMax)})"
+ )
+ )
+ } else {
+ // Overflow Exp data
+ lines.add(Component.literal(
+ "" + ChatFormatting.AQUA + ChatFormatting.BOLD + "MAX LEVEL"
+ ))
+ lines.add(Component.literal(
+ "" + ChatFormatting.GOLD + "+" + ChatFormatting.YELLOW + "${shortFormat(pet.overflowExp)} XP"
+ ))
+ }
+ } else if (TConfig.petOverlayHudStyle == PetOverlayHudStyles.PLAIN_NO_BACKGROUND ||
+ TConfig.petOverlayHudStyle == PetOverlayHudStyles.PLAIN_BACKGROUND) {
+ // Plain Style
+ lines.add(Component.literal("[Lvl ${pet.level}] ").append(Component.literal(pet.name)
+ .withColor((Rarity.colourMap[pet.rarity]) ?: ChatFormatting.WHITE)))
+
+ lines.add(Component.literal(if (pet.petItem != "None" && pet.petItem != "Unknown")
+ pet.petItem.substring(2) else pet.petItem))
+ if (pet.level != pet.maxLevel) {
+ // Exp data
+ lines.add(
+ Component.literal(
+ "Required L${pet.level + 1}: ${shortFormat(pet.currentExp)}/" +
+ "${shortFormat(pet.expForNextLevel)} " +
+ "(${formatPercent(pet.currentExp / pet.expForNextLevel)})"
+ )
+ )
+ lines.add(
+ Component.literal(
+ "Required L100: ${shortFormat(pet.totalExp)}/${shortFormat(pet.expForMax)} " +
+ "(${formatPercent(pet.totalExp / pet.expForMax)})"
+ )
+ )
+ } else {
+ // Overflow Exp data
+ lines.add(Component.literal(
+ "MAX LEVEL"
+ ))
+ lines.add(Component.literal(
+ "+${shortFormat(pet.overflowExp)} XP"
+ ))
+ }
+ }
+
+ // Render HUD
+
+ it.context.pose().pushMatrix()
+ TConfig.petOverlayHud.applyTransformations(JarvisIntegration.jarvis, it.context.pose())
+
+ renderLinesAndBackground(it, lines)
+
+ // Draw the ItemStack
+ it.context.pose().pushMatrix()
+ it.context.pose().translate(-0.5F, -0.5F)
+ it.context.pose().scale(2f, 2f)
+ it.context.renderItem(pet.petItemStack.value, 0, 0)
+ it.context.pose().popMatrix()
+
+ it.context.pose().popMatrix()
+ }
+}
+
+object PetParser {
+ private val petNamePattern = " §7\\[Lvl (\\d{1,3})] §([fa956d])([\\w\\s]+)".toPattern()
+ private val petItemPattern = " (§[fa956dbc4][\\s\\w]+)".toPattern()
+ private val petExpPattern = " §e((?:\\d{1,3}[,.]?)+\\d*[kM]?)§6\\/§e((?:\\d{1,3}[,.]?)+\\d*[kM]?) XP §6\\(\\d+(?:.\\d+)?%\\)".toPattern()
+ private val petOverflowExpPattern = " §6\\+§e((?:\\d{1,3}[,.])+\\d*[kM]?) XP".toPattern()
+ private val katPattern = " Kat:.*".toPattern()
+
+ val reversePetColourMap = mapOf(
+ "f" to Rarity.COMMON,
+ "a" to Rarity.UNCOMMON,
+ "9" to Rarity.RARE,
+ "5" to Rarity.EPIC,
+ "6" to Rarity.LEGENDARY,
+ "d" to Rarity.MYTHIC
+ )
+
+ val found = HashMap<String, Matcher>()
+
+ @OptIn(ExpensiveItemCacheApi::class)
+ fun parsePetChatMessage(name: String, rarityCode: String, level: Int) : ParsedPet? {
+ val petId = name.uppercase().replace(" ", "_")
+ val petRarity = reversePetColourMap[rarityCode] ?: Rarity.COMMON
+
+ val neuRarity = petRarity.neuRepoRarity ?: return null
+ val expLadder = ExpLadders.getExpLadder(petId, neuRarity)
+
+ var currentExp = 0.0
+ val expForNextLevel: Double
+ if (found.containsKey("exp")) {
+ currentExp = parseShortNumber(found.getValue("exp").group(1))
+ expForNextLevel = parseShortNumber(found.getValue("exp").group(2))
+ } else {
+ expForNextLevel = expLadder.getPetExpForLevel(level + 1).toDouble() -
+ expLadder.getPetExpForLevel(level).toDouble()
+ }
+
+ val totalExpBeforeLevel = expLadder.getPetExpForLevel(level).toDouble()
+ val totalExp = totalExpBeforeLevel + currentExp
+ val maxLevel = RepoManager.neuRepo.constants.petLevelingData.petLevelingBehaviourOverrides[petId]?.maxLevel ?: 100
+ val expForMax = expLadder.getPetExpForLevel(maxLevel).toDouble()
+ val petItemStack = lazy { RepoManager.neuRepo.items.items[petId + ";" + petRarity.ordinal].asItemStack() }
+
+ return ParsedPet(
+ name,
+ petRarity,
+ level,
+ -1,
+ expLadder,
+ currentExp,
+ expForNextLevel,
+ totalExp,
+ totalExpBeforeLevel,
+ expForMax,
+ 0.0,
+ "Unknown",
+ petItemStack,
+ false
+ )
+ }
+
+ @OptIn(ExpensiveItemCacheApi::class)
+ fun parseTabWidget(lines: List<Component>): ParsedPet? {
+ found.clear()
+ for (line in lines.reversed()) {
+ if (!found.containsKey("kat")) {
+ val matcher = katPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["kat"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("exp")) {
+ val matcher = petExpPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["exp"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("exp")) {
+ val matcher = petOverflowExpPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["overflow"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("item")) {
+ val matcher = petItemPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["item"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("name")) {
+ val matcher = petNamePattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["name"] = matcher
+ continue
+ }
+ }
+ }
+ if (!found.containsKey("name")) return null
+
+ val petName = titleCase(found.getValue("name").group(3))
+ val petRarity = reversePetColourMap.getValue(found.getValue("name").group(2))
+ val petId = petName.uppercase().replace(" ", "_")
+
+ val petLevel = found.getValue("name").group(1).toInt()
+
+ val neuRarity = petRarity.neuRepoRarity ?: return null
+ val expLadder = ExpLadders.getExpLadder(petId, neuRarity)
+
+ var currentExp = 0.0
+ val expForNextLevel: Double
+ if (found.containsKey("exp")) {
+ currentExp = parseShortNumber(found.getValue("exp").group(1))
+ expForNextLevel = parseShortNumber(found.getValue("exp").group(2))
+ } else {
+ expForNextLevel = expLadder.getPetExpForLevel(petLevel + 1).toDouble() -
+ expLadder.getPetExpForLevel(petLevel).toDouble()
+ }
+
+ val overflowExp: Double = if (found.containsKey("overflow"))
+ parseShortNumber(found.getValue("overflow").group(1)) else 0.0
+
+ val totalExpBeforeLevel = expLadder.getPetExpForLevel(petLevel).toDouble()
+ val totalExp = totalExpBeforeLevel + currentExp
+ val maxLevel = RepoManager.neuRepo.constants.petLevelingData.petLevelingBehaviourOverrides[petId]?.maxLevel ?: 100
+ val expForMax = expLadder.getPetExpForLevel(maxLevel).toDouble()
+ val petItemStack = lazy { RepoManager.neuRepo.items.items[petId + ";" + petRarity.ordinal].asItemStack() }
+
+
+ var petItem = "Unknown"
+ if (found.containsKey("item")) {
+ petItem = found.getValue("item").group(1)
+ }
+
+ return ParsedPet(
+ petName,
+ petRarity,
+ petLevel,
+ maxLevel,
+ expLadder,
+ currentExp,
+ expForNextLevel,
+ totalExp,
+ totalExpBeforeLevel,
+ expForMax,
+ overflowExp,
+ petItem,
+ petItemStack,
+ false
+ )
+ }
+
+ fun parsePetMenuSlot(stack: ItemStack) : ParsedPet? {
+ val petData = stack.petData ?: return null
+ val expData = petData.level
+ val overflow = if (expData.expTotal - expData.expRequiredForMaxLevel > 0)
+ (expData.expTotal - expData.expRequiredForMaxLevel).toDouble() else 0.0
+ val petItem = if (stack.petData?.heldItem != null)
+ RepoManager.neuRepo.items.items.getValue(stack.petData?.heldItem).displayName else "None"
+ return ParsedPet(
+ titleCase(petData.type),
+ Rarity.fromNeuRepo(petData.tier) ?: Rarity.COMMON,
+ expData.currentLevel,
+ expData.maxLevel,
+ ExpLadders.getExpLadder(petData.skyblockId.toString(), petData.tier),
+ expData.expInCurrentLevel.toDouble(),
+ expData.expRequiredForNextLevel.toDouble(),
+ expData.expTotal.toDouble(),
+ expData.expTotal.toDouble() - expData.expInCurrentLevel.toDouble(),
+ expData.expRequiredForMaxLevel.toDouble(),
+ overflow,
+ petItem,
+ lazy { stack },
+ true
+ )
+ }
+}
+
+data class ParsedPet(
+ val name: String,
+ val rarity: Rarity,
+ var level: Int,
+ val maxLevel: Int,
+ val expLadder: ExpLadders.ExpLadder?,
+ var currentExp: Double,
+ var expForNextLevel: Double,
+ var totalExp: Double,
+ var totalExpBeforeLevel: Double,
+ val expForMax: Double,
+ var overflowExp: Double,
+