diff options
| author | Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> | 2024-06-03 19:38:55 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-03 19:38:55 +0200 |
| commit | 0d288498a60718e2a165666ebd69b10aef658833 (patch) | |
| tree | 7898767bcf970e77321a806afd9337e971d186e5 /src/main/java/at/hannibal2/skyhanni/data/model | |
| parent | 960009ef6883b49fa13293f05567bd3d7e4977a3 (diff) | |
| download | skyhanni-0d288498a60718e2a165666ebd69b10aef658833.tar.gz skyhanni-0d288498a60718e2a165666ebd69b10aef658833.tar.bz2 skyhanni-0d288498a60718e2a165666ebd69b10aef658833.zip | |
Backend: Widget abstractions (#1150)
Co-authored-by: Cal <cwolfson58@gmail.com>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/data/model')
| -rw-r--r-- | src/main/java/at/hannibal2/skyhanni/data/model/TabWidget.kt | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/data/model/TabWidget.kt b/src/main/java/at/hannibal2/skyhanni/data/model/TabWidget.kt new file mode 100644 index 000000000..d459e2a9e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/model/TabWidget.kt @@ -0,0 +1,438 @@ +package at.hannibal2.skyhanni.data.model + +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.events.TabListUpdateEvent +import at.hannibal2.skyhanni.events.WidgetUpdateEvent +import at.hannibal2.skyhanni.utils.CollectionUtils.editCopy +import at.hannibal2.skyhanni.utils.CollectionUtils.getOrNull +import at.hannibal2.skyhanni.utils.ConditionalUtils.transformIf +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.regex.Matcher +import java.util.regex.Pattern + +private val repoGroup by RepoPattern.exclusiveGroup("tab.widget.enum") + +/** + * This class defines various widgets within the tab list, specifically focusing on the reading of the values. + * Each enum value corresponds to a distinct widget in the tab list, ensuring no overlap between them. + * The general info widget is broken up into multiple smaller ones. + * The class facilitates access to the lines associated with each widget and triggers events when a widget undergoes changes or becomes invisible. + */ +enum class TabWidget( + pattern0: String, +) { + PLAYER_LIST( + // language=RegExp + "(?:§.)*Players (?:§.)*\\(\\d+\\)" + ), + + /** This line holds no information, only here because every widget must be present */ + INFO( + // language=RegExp + "(?:§.)*Info" + ), + AREA( + // language=RegExp + "(?:§.)*(Area|Dungeon): (?:§.)*(?<island>.*)" + ), + SERVER( + // language=RegExp + "Server: (?:§.)*(?<serverid>.*)" + ), + GEMS( + // language=RegExp + "Gems: (?:§.)*(?<gems>.*)" + ), + FAIRY_SOULS( + // language=RegExp + "Fairy Souls: (?:§.)*(?<got>\\d+)(?:§.)*\\/(?:§.)*(?<max>\\d+)" + ), + PROFILE( + // language=RegExp + "(?:§.)*Profile: (?:§.)*(?<profile>\\S+).*" + ), + SB_LEVEL( + // language=RegExp + "SB Level(?:§.)*: (?:§.)*\\[(?:§.)*(?<level>\\d+)(?:§.)*\\] (?:§.)*(?<xp>\\d+).*" + ), + BANK( + // language=RegExp + "Bank: (?:§.)*(?<amount>[^§]+)(?:(?:§.)* \\/ (?:§.)*(?<personal>.*))?" + ), + INTEREST( + // language=RegExp + "Interest: (?:§.)*(?<time>[^§]+)(?:§.)* \\((?<amount>[^)]+)\\)" + ), + SOULFLOW( + // language=RegExp + "Soulflow: (?:§.)*(?<amount>.*)" + ), + PET( + // language=RegExp + "(?:§.)*Pet:" + ), + PET_TRANING( + // language=RegExp + "(?:§.)*Pet Training:" + ), + PET_SITTER( + // language=RegExp + "Kat: .*" + ), + FIRE_SALE( + // language=RegExp + "(?:§.)*Fire Sales: .*" + ), + ELECTION( + // language=RegExp + "(?:§.)*Election: (?:§.)*(?<time>.*)" + ), + EVENT( + // language=RegExp + "(?:§.)*Event: (?:§.)*(?<event>.*)" + ), + SKILLS( + // language=RegExp + "(?:§.)*Skills: ?(?:§.)*(?<avg>[\\d.]*)" + ), + STATS( + // language=RegExp + "(?:§.)*Stats:" + ), + GUESTS( + // language=RegExp + "(?:§.)*Guests (?:§.)*.*" + ), + COOP( + // language=RegExp + "(?:§.)*Coop (?:§.)*.*" + ), + MINION( + // language=RegExp + "(?:§.)*Minions: (?:§.)*(?<used>\\d+)(?:§.)*/(?:§.)*(?<max>\\d+)" + ), + JERRY_ISLAND_CLOSING( + // language=RegExp + "Island closes in: (?:§.)*(?<time>.*)" + ), + NORTH_STARS( + // language=RegExp + "North Stars: (?:§.)*(?<amount>\\d+)" + ), + COLLECTION( + // language=RegExp + "(?:§.)*Collection:" + ), + JACOB_CONTEST( + // language=RegExp + "(?:§.)*Jacob's Contest:.*" + ), + SLAYER( + // language=RegExp + "(?:§.)*Slayer:" + ), + DAILY_QUESTS( + // language=RegExp + "(?:§.)*Daily Quests:" + ), + ACTIVE_EFFECTS( + // language=RegExp + "(?:§.)*Active Effects: (?:§.)*\\((?<amount>\\d+)\\)" + ), + BESTIARY( + // language=RegExp + "(?:§.)*Bestiary:" + ), + ESSENCE( + // language=RegExp + "(?:§.)*Essence:.*" + ), + FORGE( + // language=RegExp + "(?:§.)*Forges:" + ), + TIMERS( + // language=RegExp + "(?:§.)*Timers:" + ), + DUNGEON_STATS( + // language=RegExp + "Opened Rooms: (?:§.)*(?<opend>\\d+)" + ), + PARTY( + // language=RegExp + "(?:§.)*Party:.*" + ), + TRAPPER( + // language=RegExp + "(?:§.)*Trapper:" + ), + COMMISSIONS( + // language=RegExp + "(?:§.)*Commissions:" + ), + POWDER( + // language=RegExp + "(?:§.)*Powders:" + ), + CRYSTAL( + // language=RegExp + "(?:§.)*Crystals:" + ), + UNCLAIMED_CHESTS( + // language=RegExp + "Unclaimed chests: (?:§.)*(?<amount>\\d+)" + ), + RAIN( + // language=RegExp + "(?<type>Thunder|Rain): (?:§.)*(?<time>.*)" + ), + BROODMOTHER( + // language=RegExp + "Broodmother: (?:§.)*(?<time>.*)" + ), + EYES_PLACED( + // language=RegExp + "Eyes placed: (?:§.)*(?<amount>\\d).*|(?:§.)*Dragon spawned!|(?:§.)*Egg respawning!" + ), + PROTECTOR( + // language=RegExp + "Protector: (?:§.)*(?<time>.*)" + ), + DRAGON( + // language=RegExp + "(?:§.)*Dragon: (?:§.)*\\((?<type>[^)])\\)" + ), + VOLCANO( + // language=RegExp + "Volcano: (?:§.)*(?<time>.*)" + ), + REPUTATION( + // language=RegExp + "(?:§.)*(Barbarian|Mage) Reputation:" + ), + FACTION_QUESTS( + // language=RegExp + "(?:§.)*Faction Quests:" + ), + TROPHY_FISH( + // language=RegExp + "(?:§.)*Trophy Fish:" + ), + RIFT_INFO( + // language=RegExp + "(?:§.)*Good to know:" + ), + RIFT_SHEN( + // language=RegExp + "(?:§.)*Shen: (?:§.)*\\((?<time>[^)])\\)" + ), + RIFT_BARRY( + // language=RegExp + "(?:§.)*Advertisement:" + ), + COMPOSTER( + // language=RegExp + "(?:§.)*Composter:" + ), + GARDEN_LEVEL( + // language=RegExp + "Garden Level: (?:§.)*(?<level>.*)" + ), + COPPER( + // language=RegExp + "Copper: (?:§.)*(?<amount>\\d+)" + ), + PESTS( + // language=RegExp + "(?:§.)*Pests:" + ), + VISITORS( + // language=RegExp + "(?:§.)*Visitors: (?:§.)*\\((?<count>\\d+)\\)" + ), + CROP_MILESTONE( + // language=RegExp + "(?:§.)*Crop Milestones:" + ), + PRIVATE_ISLAND_CRYSTALS( + // language=RegExp + "Crystals: (?:§.)*(?<count>\\d+)" + ), + OLD_PET_SITTER( + // language=RegExp + "Pet Sitter:.*" + ), + DUNGEON_HUB_PROGRESS( + // language=RegExp + "(?:§.)*Dungeons:" + ), + DUNGEON_PUZZLE( + // language=RegExp + "(?:§.)*Puzzles: (?:§.)*\\((?<amount>\\d+)\\)" + ), + DUNGEON_PARTY( + // language=RegExp + "(?:§.)*Party (?:§.)*\\(\\d+\\)" + ), + DUNGEON_PLAYER_STATS( + // language=RegExp + "(?:§.)*Player Stats" + ), + DUNGEON_SKILLS_AND_STATS( + // language=RegExp + "(?:§.)*Skills: (?:§.)*\\w+ \\d+: (?:§.)*[\\d.]+%" + ), + + /** This line holds no information, only here because every widget must be present */ + DUNGEON_ACCOUNT_INFO_LINE( + // language=RegExp + "(?:§.)*Account Info" + ), + DUNGEON_STATS_LINE( + // language=RegExp + "(?:§.)*Dungeon Stats" + ), + FROZEN_CORPSES( + // language=RegExp + "§b§lFrozen Corpses:" + ), + + ; + + /** The pattern for the first line of the widget*/ + val pattern by repoGroup.pattern(name.replace("_", ".").lowercase(), "\\s*$pattern0") + + /** The current active information from tab list. + * + * When the widget isn't visible it will be empty + * */ + var lines: List<String> = emptyList() + private set + + /** Both are inclusive */ + var boundary = -1 to -1 + + /** Is this widget currently visible in the tab list */ + var isActive: Boolean = false + private set + + /** Internal value for the checking to set [isActive] */ + private var gotChecked = false + + /** A [matchMatcher] for the first line using the pattern from the widget*/ + inline fun <T> matchMatcherFirstLine(consumer: Matcher.() -> T) = + if (isActive) + pattern.matchMatcher(lines.first(), consumer) + else null + + private fun postNewEvent(lines: List<String>) { + // Prevent Post if lines are equal + if (lines == this.lines) return + this.lines = lines + isActive = true + WidgetUpdateEvent(this, lines).postAndCatch() + } + + private fun postClearEvent() { + lines = emptyList() + WidgetUpdateEvent(this, lines).postAndCatch() + } + + /** Update the state of the widget, posts the clear if [isActive] == true && [gotChecked] == false */ + private fun updateIsActive() { + if (isActive == gotChecked) return + isActive = gotChecked + if (!gotChecked) { + postClearEvent() + } + } + + companion object { + + /** The index for the start of each Widget (inclusive) */ + private val separatorIndexes = mutableListOf<Pair<Int, TabWidget?>>() + + /** Patterns that where loaded from a future version*/ + private var extraPatterns: List<Pattern> = emptyList() + + init { + entries.forEach { it.pattern } + } + + @SubscribeEvent(priority = EventPriority.HIGH) + fun onTabListUpdate(event: TabListUpdateEvent) { + if (!LorenzUtils.inSkyBlock) { + if (separatorIndexes.isNotEmpty()) { + separatorIndexes.forEach { it.second?.updateIsActive() } + separatorIndexes.clear() + } + return + } + + val tabList = filterTabList(event.tabList) + + separatorIndexes.clear() + + for ((index, line) in tabList.withIndex()) { + val match = entries.firstOrNull { it.pattern.matches(line) } + ?: if (extraPatterns.any { it.matches(line) }) null else continue + separatorIndexes.add(index to match) + } + separatorIndexes.add(tabList.size to null) + + separatorIndexes.zipWithNext { (firstIndex, widget), (secondIndex, _) -> + widget?.boundary = firstIndex to secondIndex - 1 + widget?.gotChecked = true + widget?.postNewEvent(tabList.subList(firstIndex, secondIndex).filter { it.isNotEmpty() }) + } + + entries.forEach { it.updateIsActive() } + + separatorIndexes.forEach { + it.second?.gotChecked = false + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + fun onRepoReload(event: RepositoryReloadEvent) { + extraPatterns = repoGroup.getUnusedPatterns() + } + + private fun filterTabList(tabList: List<String>): List<String> { + var playerListFound = false + var infoFound = false + + val headers = generateSequence(0) { it + 20 }.take(4).map { it to tabList.getOrNull(it) } + + val removeIndexes = mutableListOf<Int>() + + for ((index, header) in headers) when { + PLAYER_LIST.pattern.matches(header) -> if (playerListFound) removeIndexes.add(index - removeIndexes.size) else playerListFound = + true + + INFO.pattern.matches(header) -> if (infoFound) removeIndexes.add(index - removeIndexes.size) else infoFound = + true + } + + return tabList.transformIf({ size > 81 }, { dropLast(size - 80) }).editCopy { + removeIndexes.forEach { + removeAt(it) + } + } + } + + fun reSendEvents() = entries.forEach { + if (it.isActive) { + it.postNewEvent(it.lines) + } else { + it.postClearEvent() + } + } + } +} |
