aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util/skyblock
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/util/skyblock')
-rw-r--r--src/main/kotlin/util/skyblock/AbilityUtils.kt12
-rw-r--r--src/main/kotlin/util/skyblock/DungeonUtil.kt4
-rw-r--r--src/main/kotlin/util/skyblock/ItemType.kt8
-rw-r--r--src/main/kotlin/util/skyblock/PartyUtil.kt210
-rw-r--r--src/main/kotlin/util/skyblock/Rarity.kt34
-rw-r--r--src/main/kotlin/util/skyblock/SBItemUtil.kt4
-rw-r--r--src/main/kotlin/util/skyblock/SackUtil.kt33
-rw-r--r--src/main/kotlin/util/skyblock/ScreenIdentification.kt52
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt11
-rw-r--r--src/main/kotlin/util/skyblock/TabListAPI.kt41
10 files changed, 369 insertions, 40 deletions
diff --git a/src/main/kotlin/util/skyblock/AbilityUtils.kt b/src/main/kotlin/util/skyblock/AbilityUtils.kt
index 0d7d2b7..9ba182d 100644
--- a/src/main/kotlin/util/skyblock/AbilityUtils.kt
+++ b/src/main/kotlin/util/skyblock/AbilityUtils.kt
@@ -1,8 +1,8 @@
package moe.nea.firmament.util.skyblock
import kotlin.time.Duration
-import net.minecraft.item.ItemStack
-import net.minecraft.text.Text
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Component
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -17,7 +17,7 @@ object AbilityUtils {
val hasPowerScroll: Boolean,
val activation: AbilityActivation,
val manaCost: Int?,
- val descriptionLines: List<Text>,
+ val descriptionLines: List<Component>,
val cooldown: Duration?,
)
@@ -40,7 +40,7 @@ object AbilityUtils {
}
private val abilityNameRegex = "Ability: (?<name>.*?) *".toPattern()
- private fun findAbility(iterator: ListIterator<Text>): ItemAbility? {
+ private fun findAbility(iterator: ListIterator<Component>): ItemAbility? {
if (!iterator.hasNext()) {
return null
}
@@ -72,7 +72,7 @@ object AbilityUtils {
return null
}
if (abilityName == null) return null
- val descriptionLines = mutableListOf<Text>()
+ val descriptionLines = mutableListOf<Component>()
var manaCost: Int? = null
var cooldown: Duration? = null
while (iterator.hasNext()) {
@@ -121,7 +121,7 @@ object AbilityUtils {
)
}
- fun getAbilities(lore: List<Text>): List<ItemAbility> {
+ fun getAbilities(lore: List<Component>): List<ItemAbility> {
val iterator = lore.listIterator()
val abilities = mutableListOf<ItemAbility>()
while (iterator.hasNext()) {
diff --git a/src/main/kotlin/util/skyblock/DungeonUtil.kt b/src/main/kotlin/util/skyblock/DungeonUtil.kt
index 488b158..c4eb169 100644
--- a/src/main/kotlin/util/skyblock/DungeonUtil.kt
+++ b/src/main/kotlin/util/skyblock/DungeonUtil.kt
@@ -7,7 +7,7 @@ import moe.nea.firmament.util.TIME_PATTERN
object DungeonUtil {
val isInDungeonIsland get() = SBData.skyblockLocation == SkyBlockIsland.DUNGEON
- private val timeElapsedRegex = "Time Elapsed: $TIME_PATTERN".toRegex()
+ private val timeElapsedRegex = "Time Elapsed: (?:$TIME_PATTERN\\s*)+".toRegex()
val isInActiveDungeon get() = isInDungeonIsland && ScoreboardUtil.simplifiedScoreboardLines.any { it.matches(
timeElapsedRegex) }
@@ -21,7 +21,7 @@ object DungeonUtil {
' §7♲ §7Ironman'
' '
'Keys: §c■ §c✗ §8■ §a1x'
-'Time Elapsed: §a46s'
+'Time Elapsed: §a1m 46s'
'Cleared: §660% §8(105)'
' '
'§e[B] §b151_Dragon §e2,062§c❤'
diff --git a/src/main/kotlin/util/skyblock/ItemType.kt b/src/main/kotlin/util/skyblock/ItemType.kt
index 7a776b5..887edef 100644
--- a/src/main/kotlin/util/skyblock/ItemType.kt
+++ b/src/main/kotlin/util/skyblock/ItemType.kt
@@ -1,13 +1,12 @@
package moe.nea.firmament.util.skyblock
-import net.minecraft.item.ItemStack
+import net.minecraft.world.item.ItemStack
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.petData
-@JvmInline
-value class ItemType private constructor(val name: String) {
+data class ItemType private constructor(val name: String) {
companion object {
fun ofName(name: String): ItemType {
return ItemType(name)
@@ -41,6 +40,7 @@ value class ItemType private constructor(val name: String) {
val SWORD = ofName("SWORD")
val DRILL = ofName("DRILL")
val PICKAXE = ofName("PICKAXE")
+ val AXE = ofName("AXE")
val GAUNTLET = ofName("GAUNTLET")
val LONGSWORD = ofName("LONG SWORD")
val EQUIPMENT = ofName("EQUIPMENT")
@@ -57,6 +57,8 @@ value class ItemType private constructor(val name: String) {
val LEGGINGS = ofName("LEGGINGS")
val HELMET = ofName("HELMET")
val BOOTS = ofName("BOOTS")
+ val SHOVEL = ofName("SHOVEL")
+
val NIL = ofName("__NIL")
/**
diff --git a/src/main/kotlin/util/skyblock/PartyUtil.kt b/src/main/kotlin/util/skyblock/PartyUtil.kt
new file mode 100644
index 0000000..46e1aa3
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/PartyUtil.kt
@@ -0,0 +1,210 @@
+package moe.nea.firmament.util.skyblock
+
+import java.util.UUID
+import net.hypixel.modapi.HypixelModAPI
+import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket
+import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket.PartyRole
+import net.hypixel.modapi.packet.impl.serverbound.ServerboundPartyInfoPacket
+import org.intellij.lang.annotations.Language
+import kotlinx.coroutines.launch
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.apis.Routes
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.events.WorldReadyEvent
+import moe.nea.firmament.features.debug.DeveloperFeatures
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.bold
+import moe.nea.firmament.util.boolColour
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.useMatch
+
+object PartyUtil {
+ object Internal {
+ val hma = HypixelModAPI.getInstance()
+
+ val handler = hma.createHandler(ClientboundPartyInfoPacket::class.java) { clientboundPartyInfoPacket ->
+ Firmament.coroutineScope.launch {
+ party = Party(clientboundPartyInfoPacket.memberMap.values.map {
+ PartyMember.fromUuid(it.uuid, it.role)
+ })
+ }
+ }
+
+ fun sendSyncPacket() {
+ hma.sendPacket(ServerboundPartyInfoPacket())
+ }
+
+ @Subscribe
+ fun onDevCommand(event: CommandEvent.SubCommand) {
+ event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
+ thenLiteral("party") {
+ thenLiteral("refresh") {
+ thenExecute {
+ sendSyncPacket()
+ source.sendFeedback(tr("firmament.dev.partyinfo.refresh", "Refreshing party info"))
+ }
+ }
+ thenExecute {
+ val p = party
+ val text = Component.empty()
+ text.append(
+ tr("firmament.dev.partyinfo", "Party Info: ")
+ .boolColour(p != null)
+ )
+ if (p == null) {
+ text.append(tr("firmament.dev.partyinfo.empty", "Empty Party").grey())
+ } else {
+ text.append(tr("firmament.dev.partyinfo.count", "${p.members.size} members").grey())
+ p.members.forEach {
+ text.append("\n")
+ .append(Component.literal(" - ${it.name}"))
+ .append(" (")
+ .append(
+ when (it.role) {
+ PartyRole.LEADER -> tr("firmament.dev.partyinfo.leader", "Leader").bold()
+ PartyRole.MOD -> tr("firmament.dev.partyinfo.mod", "Moderator")
+ PartyRole.MEMBER -> tr("firmament.dev.partyinfo.member", "Member")
+ }
+ )
+ .append(")")
+ }
+ }
+ source.sendFeedback(text)
+ }
+ }
+ }
+ }
+
+ object Regexes {
+ @Language("RegExp")
+ val NAME = "(\\[[^\\]]+\\] )?(?<name>[a-zA-Z0-9_]{2,16})"
+ val NAME_SECONDARY = NAME.replace("name", "name2")
+ val joinSelf = "You have joined $NAME's? party!".toPattern()
+ val joinOther = "$NAME joined the party\\.".toPattern()
+ val leaveSelf = "You left the party\\.".toPattern()
+ val disbandedEmpty =
+ "The party was disbanded because all invites expired and the party was empty\\.".toPattern()
+ val leaveOther = "$NAME has left the party\\.".toPattern()
+ val kickedOther = "$NAME has been removed from the party\\.".toPattern()
+ val kickedOtherOffline = "Kicked $NAME because they were offline\\.".toPattern()
+ val disconnectedOther = "$NAME was removed from your party because they disconnected\\.".toPattern()
+ val transferLeave = "The party was transferred to $NAME because $NAME_SECONDARY left\\.?".toPattern()
+ val transferVoluntary = "The party was transferred to $NAME by $NAME_SECONDARY\\.?".toPattern()
+ val disbanded = "$NAME has disbanded the party!".toPattern()
+ val kickedSelf = "You have been kicked from the party by $NAME ?\\.?".toPattern()
+ val partyFinderJoin = "Party Finder > $NAME joined the .* group!.*".toPattern()
+ }
+
+ fun modifyParty(
+ allowEmpty: Boolean = false,
+ modifier: (MutableList<PartyMember>) -> Unit
+ ) {
+ val oldList = party?.members ?: emptyList()
+ if (oldList.isEmpty() && !allowEmpty) return
+ party = Party(oldList.toMutableList().also(modifier))
+ }
+
+ fun MutableList<PartyMember>.modifyMember(name: String, mod: (PartyMember) -> PartyMember) {
+ val idx = indexOfFirst { it.name == name }
+ val member = if (idx < 0) {
+ PartyMember(name, PartyRole.MEMBER)
+ } else {
+ removeAt(idx)
+ }
+ add(mod(member))
+ }
+
+ fun addMemberToParty(name: String) {
+ modifyParty(true) {
+ if (it.isEmpty())
+ it.add(PartyMember(MC.playerName, PartyRole.LEADER))
+ it.add(PartyMember(name, PartyRole.MEMBER))
+ }
+ }
+
+ @Subscribe
+ fun onJoinServer(event: WorldReadyEvent) { // This event isn't perfect... Hypixel isn't ready yet when we join the server. We should probably just listen to the mod api hello packet and go from there, but this works (since you join and leave servers quite often).
+ if (party == null)
+ sendSyncPacket()
+ }
+
+ @Subscribe
+ fun onPartyRelatedMessage(event: ProcessChatEvent) {
+ Regexes.joinSelf.useMatch(event.unformattedString) {
+ sendSyncPacket()
+ }
+ Regexes.joinOther.useMatch(event.unformattedString) {
+ addMemberToParty(group("name"))
+ }
+ Regexes.leaveOther.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.leaveSelf.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.disbandedEmpty.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.kickedOther.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.kickedOtherOffline.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.disconnectedOther.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.transferLeave.useMatch(event.unformattedString) {
+ modifyParty {
+ it.modifyMember(group("name")) { it.copy(role = PartyRole.LEADER) }
+ it.removeIf { it.name == group("name2") }
+ }
+ }
+ Regexes.transferVoluntary.useMatch(event.unformattedString) {
+ modifyParty {
+ it.modifyMember(group("name")) { it.copy(role = PartyRole.LEADER) }
+ it.modifyMember(group("name2")) { it.copy(role = PartyRole.MOD) }
+ }
+ }
+ Regexes.disbanded.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.kickedSelf.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.partyFinderJoin.useMatch(event.unformattedString) {
+ addMemberToParty(group("name"))
+ }
+ }
+ }
+
+ data class Party(
+ val members: List<PartyMember>
+ )
+
+ data class PartyMember(
+ val name: String,
+ val role: PartyRole
+ ) {
+ companion object {
+ suspend fun fromUuid(uuid: UUID, role: PartyRole = PartyRole.MEMBER): PartyMember {
+ return PartyMember(
+ ErrorUtil.notNullOr(
+ Routes.getPlayerNameForUUID(uuid),
+ "Could not find username for player $uuid"
+ ) { "Ghost" },
+ role
+ )
+ }
+ }
+ }
+
+ var party: Party? = null
+}
diff --git a/src/main/kotlin/util/skyblock/Rarity.kt b/src/main/kotlin/util/skyblock/Rarity.kt
index b19f371..95e5d87 100644
--- a/src/main/kotlin/util/skyblock/Rarity.kt
+++ b/src/main/kotlin/util/skyblock/Rarity.kt
@@ -7,10 +7,10 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import net.minecraft.item.ItemStack
-import net.minecraft.text.Style
-import net.minecraft.text.Text
-import net.minecraft.util.Formatting
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Style
+import net.minecraft.network.chat.Component
+import net.minecraft.ChatFormatting
import moe.nea.firmament.util.StringUtil.words
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -31,6 +31,7 @@ enum class Rarity(vararg altNames: String) {
SUPREME,
SPECIAL,
VERY_SPECIAL,
+ ULTIMATE,
UNKNOWN
;
@@ -48,22 +49,23 @@ enum class Rarity(vararg altNames: String) {
}
val names = setOf(name) + altNames
- val text: Text get() = Text.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this]))
+ val text: Component get() = Component.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this]))
val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name }
companion object {
// TODO: inline those formattings as fields
val colourMap = 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,
+ Rarity.COMMON to ChatFormatting.WHITE,
+ Rarity.UNCOMMON to ChatFormatting.GREEN,
+ Rarity.RARE to ChatFormatting.BLUE,
+ Rarity.EPIC to ChatFormatting.DARK_PURPLE,
+ Rarity.LEGENDARY to ChatFormatting.GOLD,
+ Rarity.MYTHIC to ChatFormatting.LIGHT_PURPLE,
+ Rarity.DIVINE to ChatFormatting.AQUA,
+ Rarity.SPECIAL to ChatFormatting.RED,
+ Rarity.VERY_SPECIAL to ChatFormatting.RED,
+ Rarity.SUPREME to ChatFormatting.DARK_RED,
+ Rarity.ULTIMATE to ChatFormatting.DARK_RED,
)
val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap()
val fromNeuRepo = entries.associateBy { it.neuRepoRarity }
@@ -87,7 +89,7 @@ enum class Rarity(vararg altNames: String) {
fun fromPetItem(itemStack: ItemStack): Rarity? =
itemStack.petData?.tier?.let(::fromNeuRepo)
- fun fromLore(lore: List<Text>): Rarity? =
+ fun fromLore(lore: List<Component>): Rarity? =
lore.lastNotNullOfOrNull {
it.unformattedString.words()
.firstNotNullOfOrNull(::fromString)
diff --git a/src/main/kotlin/util/skyblock/SBItemUtil.kt b/src/main/kotlin/util/skyblock/SBItemUtil.kt
index 3901b60..619a10b 100644
--- a/src/main/kotlin/util/skyblock/SBItemUtil.kt
+++ b/src/main/kotlin/util/skyblock/SBItemUtil.kt
@@ -1,12 +1,12 @@
package moe.nea.firmament.util.skyblock
-import net.minecraft.item.ItemStack
+import net.minecraft.world.item.ItemStack
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.unformattedString
object SBItemUtil {
fun ItemStack.getSearchName(): String {
- val name = this.name.unformattedString
+ val name = this.hoverName.unformattedString
if (name.contains("Enchanted Book")) {
val enchant = loreAccordingToNbt.firstOrNull()?.unformattedString
if (enchant != null) return enchant
diff --git a/src/main/kotlin/util/skyblock/SackUtil.kt b/src/main/kotlin/util/skyblock/SackUtil.kt
index fd67c44..a69309b 100644
--- a/src/main/kotlin/util/skyblock/SackUtil.kt
+++ b/src/main/kotlin/util/skyblock/SackUtil.kt
@@ -2,15 +2,18 @@ package moe.nea.firmament.util.skyblock
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.text.HoverEvent
-import net.minecraft.text.Text
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.network.chat.HoverEvent
+import net.minecraft.network.chat.Component
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ChestInventoryUpdateEvent
import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.gui.config.storage.ConfigFixEvent
+import moe.nea.firmament.gui.config.storage.ConfigStorageClass
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.data.Config
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.iterableView
@@ -28,18 +31,26 @@ object SackUtil {
// val sackTypes:
)
- object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "Sacks", ::SackContents)
+ @Config
+ object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "sacks", ::SackContents)
+
+ @Subscribe
+ fun onConfigFix(event: ConfigFixEvent) {
+ event.on(996, ConfigStorageClass.PROFILE) {
+ move("Sacks", "sacks")
+ }
+ }
val items get() = Store.data?.contents ?: mutableMapOf()
val storedRegex = "^Stored: (?<stored>$SHORT_NUMBER_FORMAT)/(?<max>$SHORT_NUMBER_FORMAT)$".toPattern()
@Subscribe
fun storeDataFromInventory(event: ChestInventoryUpdateEvent) {
- val screen = event.inventory as? GenericContainerScreen ?: return
+ val screen = event.inventory as? ContainerScreen ?: return
if (!screen.title.unformattedString.endsWith(" Sack")) return
- val inv = screen.screenHandler?.inventory ?: return
- if (inv.size() < 18) return
- val backSlot = inv.getStack(inv.size() - 5)
+ val inv = screen.menu?.container ?: return
+ if (inv.containerSize < 18) return
+ val backSlot = inv.getItem(inv.containerSize - 5)
if (backSlot.displayNameAccordingToNbt.unformattedString != "Go Back") return
if (backSlot.loreAccordingToNbt.map { it.unformattedString } != listOf("To Sack of Sacks")) return
for (itemStack in inv.iterableView) {
@@ -63,7 +74,7 @@ object SackUtil {
getUpdatesFromMessage(event.text)
}
- fun getUpdatesFromMessage(text: Text): List<SackUpdate> {
+ fun getUpdatesFromMessage(text: Component): List<SackUpdate> {
val update = ChatUpdate()
text.siblings.forEach(update::updateFromHoverText)
return update.updates
@@ -91,9 +102,9 @@ object SackUtil {
}
}
- fun updateFromHoverText(text: Text) {
+ fun updateFromHoverText(text: Component) {
text.siblings.forEach(::updateFromHoverText)
- val hoverText = text.style.hoverEvent?.getValue(HoverEvent.Action.SHOW_TEXT) ?: return
+ val hoverText = (text.style.hoverEvent as? HoverEvent.ShowText)?.value ?: return
val cleanedText = hoverText.unformattedString
if (cleanedText.startsWith("Added items:\n")) {
if (!foundAdded) {
diff --git a/src/main/kotlin/util/skyblock/ScreenIdentification.kt b/src/main/kotlin/util/skyblock/ScreenIdentification.kt
new file mode 100644
index 0000000..ff725fa
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/ScreenIdentification.kt
@@ -0,0 +1,52 @@
+package moe.nea.firmament.util.skyblock
+
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.unformattedString
+
+
+object ScreenIdentification {
+ private var lastScreen: Screen? = null
+ private var lastScreenType: ScreenType? = null
+
+ fun getType(screen: Screen?): ScreenType? {
+ if (screen == null) return null
+ if (screen !== lastScreen) {
+ lastScreenType = ScreenType.entries
+ .find { it.detector(screen) }
+ lastScreen = screen
+ }
+ return lastScreenType
+ }
+}
+
+enum class ScreenType(val detector: (Screen) -> Boolean) {
+ BAZAAR_ANY({
+ it is ContainerScreen && (
+ it.menu.getSlot(it.menu.rowCount * 9 - 4)
+ .item
+ .displayNameAccordingToNbt
+ .unformattedString == "Manage Orders"
+ || it.menu.getSlot(it.menu.rowCount * 9 - 5)
+ .item
+ .loreAccordingToNbt
+ .any {
+ it.unformattedString == "To Bazaar"
+ })
+ }),
+ ENCHANTMENT_GUIDE({
+ it.title.unformattedString.endsWith("Enchantments Guide")
+ }),
+ SUPER_PAIRS({
+ it.title.unformattedString.startsWith("Superpairs")
+ }),
+ EXPERIMENTATION_RNG_METER({
+ it.title.unformattedString.contains("Experimentation Table RNG")
+ }),
+ DYE_COMPENDIUM({
+ it.title.unformattedString.contains("Dye Compendium")
+ })
+}
+
diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
index ca2b17b..5f4acd4 100644
--- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt
+++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
@@ -3,14 +3,25 @@ package moe.nea.firmament.util.skyblock
import moe.nea.firmament.util.SkyblockId
object SkyBlockItems {
+ val COINS = SkyblockId("SKYBLOCK_COIN")
val ROTTEN_FLESH = SkyblockId("ROTTEN_FLESH")
val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND")
val DIAMOND = SkyblockId("DIAMOND")
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
+ val ARCHAIC_SPADE = SkyblockId("ARCHAIC_SPADE")
+ val DEIFIC_SPADE = SkyblockId("DEIFIC_SPADE")
val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL")
val SLICE_OF_BLUEBERRY_CAKE = SkyblockId("SLICE_OF_BLUEBERRY_CAKE")
val SLICE_OF_CHEESECAKE = SkyblockId("SLICE_OF_CHEESECAKE")
val SLICE_OF_GREEN_VELVET_CAKE = SkyblockId("SLICE_OF_GREEN_VELVET_CAKE")
val SLICE_OF_RED_VELVET_CAKE = SkyblockId("SLICE_OF_RED_VELVET_CAKE")
val SLICE_OF_STRAWBERRY_SHORTCAKE = SkyblockId("SLICE_OF_STRAWBERRY_SHORTCAKE")
+ val ASPECT_OF_THE_VOID = SkyblockId("ASPECT_OF_THE_VOID")
+ val ASPECT_OF_THE_END = SkyblockId("ASPECT_OF_THE_END")
+ val BONE_BOOMERANG = SkyblockId("BONE_BOOMERANG")
+ val STARRED_BONE_BOOMERANG = SkyblockId("STARRED_BONE_BOOMERANG")
+ val TRIBAL_SPEAR = SkyblockId("TRIBAL_SPEAR")
+ val BLOCK_ZAPPER = SkyblockId("BLOCK_ZAPPER")
+ val HUNTING_TOOLKIT = SkyblockId("HUNTING_TOOLKIT")
+ val ETHERWARP_CONDUIT = SkyblockId("ETHERWARP_CONDUIT")
}
diff --git a/src/main/kotlin/util/skyblock/TabListAPI.kt b/src/main/kotlin/util/skyblock/TabListAPI.kt
new file mode 100644
index 0000000..43722e0
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/TabListAPI.kt
@@ -0,0 +1,41 @@
+package moe.nea.firmament.util.skyblock
+
+import org.intellij.lang.annotations.Language
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.util.StringUtil.title
+import moe.nea.firmament.util.StringUtil.unwords
+import moe.nea.firmament.util.mc.MCTabListAPI
+import moe.nea.firmament.util.unformattedString
+
+object TabListAPI {
+
+ fun getWidgetLines(widgetName: WidgetName, includeTitle: Boolean = false, from: MCTabListAPI.CurrentTabList = MCTabListAPI.currentTabList): List<Component> {
+ return from.body
+ .dropWhile { !widgetName.matchesTitle(it) }
+ .takeWhile { it.string.isNotBlank() && !it.string.startsWith(" ") }
+ .let { if (includeTitle) it else it.drop(1) }
+ }
+
+ enum class WidgetName(regex: Regex?) {
+ COMMISSIONS,
+ SKILLS("Skills:( .*)?"),
+ PROFILE("Profile: (.*)"),
+ COLLECTION,
+ ESSENCE,
+ PET
+ ;
+
+ fun matchesTitle(it: Component): Boolean {
+ return regex.matches(it.unformattedString)
+ }
+
+ constructor() : this(null)
+ constructor(@Language("RegExp") regex: String) : this(Regex(regex))
+
+ val label =
+ name.split("_").map { it.lowercase().title() }.unwords()
+ val regex = regex ?: Regex.fromLiteral("$label:")
+
+ }
+
+}