aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--FEATURES.md3
-rw-r--r--build.gradle.kts19
-rw-r--r--gradle.properties2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/About.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/Misc.java52
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt35
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt112
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordRPCManager.kt183
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordStatus.kt149
11 files changed, 558 insertions, 8 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd6ebf128..6d7d5f4e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
+ Added Arachne to damage indicator.
+ Added **Arachne Minis Hider** - Hides the nametag above arachne minis.
+ Added **Arachne Boss Highlighter** - Highlight the arachne boss in red and mini bosses and orange.
++ Added **Discord RCP** - Showing stats like Location, Purse, Bits, Purse or Held Item at Discord Rich Presence. - (contributed by NetheriteMiner)
### Garden Features
+ Added **Copper Price** - Show copper to coin prices inside the Sky Mart inventory.
diff --git a/FEATURES.md b/FEATURES.md
index 200165d73..526fcaebd 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -247,4 +247,5 @@
+ Chicken Head Timer.
+ **rancher boots** speed display.
+ **CH Join** - Helps buy a Pass for accessing the Crystal Hollows if needed.
-+ **Estimated Item Value** - Displays an estimated item value for the item you hover over. \ No newline at end of file
++ **Estimated Item Value** - Displays an estimated item value for the item you hover over.
++ **Discord RCP** - Showing stats like Location, Purse, Bits, Purse or Held Item at Discord Rich Presence. - (contributed by NetheriteMiner) \ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index d3f3c1b10..90703e330 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -21,11 +21,16 @@ sourceSets.main {
output.setResourcesDir(file("$buildDir/classes/java/main"))
}
+// Taken from SBA code to support Discord RPC dependency
+configurations.create("bundle")
+configurations.implementation.extendsFrom(configurations.named("bundle").get())
+
// Dependencies:
repositories {
mavenCentral()
mavenLocal()
+ maven("https://maven.notenoughupdates.org/releases")
maven("https://repo.spongepowered.org/maven/")
// If you don't want to log in with your real minecraft account, remove this line
maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1")
@@ -55,6 +60,14 @@ dependencies {
mappings("de.oceanlabs.mcp:mcp_stable:22-1.8.9")
forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9")
+ "bundle"("com.github.ILikePlayingGames:DiscordIPC:-SNAPSHOT") {
+ exclude(module = "log4j")
+ because("Different version conflicts with Minecraft's Log4J")
+ exclude(module = "gson")
+ because("Different version conflicts with Minecraft's Log4j")
+ } // Discord RPC client
+
+
// If you don't want mixins, remove these lines
shadowImpl("org.spongepowered:mixin:0.7.11-SNAPSHOT") {
isTransitive = false
@@ -72,8 +85,8 @@ dependencies {
implementation("com.github.hannibal002:notenoughupdates:4957f0b:all")
devenvMod("com.github.hannibal002:notenoughupdates:4957f0b:all")
- shadowModImpl("com.github.notenoughupdates:moulconfig:ac39e63")
- devenvMod("com.github.notenoughupdates:moulconfig:ac39e63:test")
+ shadowModImpl("org.notenoughupdates.moulconfig:MoulConfig:1.1.0")
+ devenvMod("org.notenoughupdates.moulconfig:MoulConfig:1.1.0:test")
shadowImpl("moe.nea:libautoupdate:1.0.3")
}
@@ -161,4 +174,4 @@ compileKotlin.kotlinOptions {
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
-}
+} \ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 7a79fe1af..065574755 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,2 +1,2 @@
loom.platform=forge
-org.gradle.jvmargs=-Xmx2g
+org.gradle.jvmargs=-Xmx2g \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java
index 46fcd2e54..cf6ff22b9 100644
--- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java
+++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java
@@ -43,6 +43,7 @@ import at.hannibal2.skyhanni.features.itemabilities.abilitycooldown.ItemAbilityC
import at.hannibal2.skyhanni.features.minion.MinionCollectLogic;
import at.hannibal2.skyhanni.features.minion.MinionFeatures;
import at.hannibal2.skyhanni.features.misc.*;
+import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager;
import at.hannibal2.skyhanni.features.misc.tiarelay.TiaRelayHelper;
import at.hannibal2.skyhanni.features.misc.tiarelay.TiaRelayWaypoints;
import at.hannibal2.skyhanni.features.misc.update.UpdateManager;
@@ -149,6 +150,7 @@ public class SkyHanniMod {
loadModule(new CropAccessoryData());
loadModule(new MayorElection());
loadModule(new GardenComposterUpgradesData());
+ loadModule(new ActionBarStatsData());
// APIs
loadModule(new BazaarApi());
@@ -273,6 +275,7 @@ public class SkyHanniMod {
loadModule(new AshfangMinisNametagHider());
loadModule(new GardenTeleportPadInventoryNumber());
loadModule(new ComposterOverlay());
+ loadModule(new DiscordRPCManager());
loadModule(new GardenCropMilestoneFix());
loadModule(new GardenBurrowingSporesNotifier());
loadModule(new WildStrawberryDyeNotification());
@@ -325,4 +328,4 @@ public class SkyHanniMod {
System.out.println("consoleLog: (" + message + ")");
}
}
-}
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/About.java b/src/main/java/at/hannibal2/skyhanni/config/features/About.java
index c67e2f7ee..5a97b2410 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/About.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/About.java
@@ -73,5 +73,8 @@ public class About {
@ConfigEditorButton(buttonText = "Source")
public Runnable mixin = () -> OSUtils.openBrowser("https://github.com/SpongePowered/Mixin/");
+ @ConfigOption(name = "DiscordIPC", desc = "DiscordIPC is available under the Apache License 2.0")
+ @ConfigEditorButton(buttonText = "GitHub")
+ public Runnable discordRPC = () -> OSUtils.openBrowser("https://github.com/jagrosh/DiscordIPC");
}
-}
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/Misc.java b/src/main/java/at/hannibal2/skyhanni/config/features/Misc.java
index 12642106b..23a9824a9 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/Misc.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/Misc.java
@@ -231,6 +231,56 @@ public class Misc {
@ConfigAccordionId(id = 11)
public boolean estimatedIemValueAlwaysEnabled = true;
+ @ConfigOption(name = "Discord Rich Presence", desc = "")
+ @Accordion
+ @Expose
+ public DiscordRPC discordRPC = new DiscordRPC();
+
+ public static class DiscordRPC {
+
+ @Expose
+ @ConfigOption(name = "Enable Discord RPC", desc = "Details about your Skyblock session displayed through Discord.")
+ @ConfigEditorBoolean
+ public Property<Boolean> enabled = Property.of(false);
+
+ @Expose
+ @ConfigOption(name = "First Line", desc = "Decide what to show in the first line.")
+ @ConfigEditorDropdown(values = {
+ "Nothing",
+ "Location",
+ "Purse",
+ "Bits",
+ "Stats",
+ "Held Item",
+ "Skyblock Date",
+ "Profile (Fruit)",
+ "Slayer",
+ "Custom"
+ })
+ public Property<Integer> firstLine = Property.of(0);
+
+ @Expose
+ @ConfigOption(name = "Second Line", desc = "Decide what to show in the second line.")
+ @ConfigEditorDropdown(values = {
+ "Nothing",
+ "Location",
+ "Purse",
+ "Bits",
+ "Stats",
+ "Held Item",
+ "Skyblock Date",
+ "Profile (Fruit)",
+ "Slayer",
+ "Custom"
+ })
+ public Property<Integer> secondLine = Property.of(0);
+
+ @Expose
+ @ConfigOption(name = "Custom", desc = "What should be displayed if you select \"Custom\" above.")
+ @ConfigEditorText
+ public Property<String> customText = Property.of("");
+ }
+
@Expose
public Position itemPriceDataPos = new Position(140, 90, false, true);
@@ -279,4 +329,4 @@ public class Misc {
@Expose
public Position inventoryLoadPos = new Position(394, 124, false, true);
-}
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt b/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt
new file mode 100644
index 000000000..5130e7080
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt
@@ -0,0 +1,35 @@
+package at.hannibal2.skyhanni.data
+
+import at.hannibal2.skyhanni.events.LorenzActionBarEvent
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+class ActionBarStatsData {
+ private val pattern =
+ Pattern.compile("..((?:\\d|,)*)\\/(?:\\d|,)*(.) *..((?:\\d|,)*)..(. \\w*) *..((?:\\d|,)*)\\/(?:\\d|,)*(.*)")
+// Sample input: §c2,817/2,817❤ §a703§a❈ Defense §b3,479/3,479✎ Mana
+// Returns the following groups: 1 = 2,817; 2 = ❤; 3 = 703; 4 = ❈ Defense; 5 = 3,479; 6 = ✎ Mana
+
+ companion object {
+ var groups = listOf<String>()
+ }
+
+ @SubscribeEvent
+ fun onActionBar(event: LorenzActionBarEvent) {
+ groups = readGroups(event.message)
+ }
+
+ private fun readGroups(message: String): List<String> {
+ if (!LorenzUtils.inSkyBlock) return emptyList()
+
+ val matcher = pattern.matcher(message)
+ if (!matcher.matches()) return emptyList()
+
+ val list = mutableListOf<String>()
+ for (i in 1..matcher.groupCount()) {
+ list.add(matcher.group(i))
+ }
+ return list
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt
new file mode 100644
index 000000000..8fbfadbd0
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt
@@ -0,0 +1,112 @@
+package at.hannibal2.skyhanni.features.misc.discordrpc
+
+class DiscordLocationKey {
+
+ private val normalRPC = setOf(
+ "auction-house",
+ "bank",
+ "canvas-room",
+ "coal-mine",
+ "colosseum",
+ "farm",
+ "fashion-shop",
+ "flower-house",
+ "forest",
+ "graveyard",
+ "library",
+ "mountain",
+ "ruins",
+ "tavern",
+ "village",
+ "wilderness",
+ "wizard-tower",
+ "birch-park",
+ "spruce-woods",
+ "savanna-woodland",
+ "dark-thicket",
+ "jungle-island",
+ "gold-mine",
+ "slimehill",
+ "diamond-reserve",
+ "obsidian-sanctuary",
+ "the-barn",
+ "mushroom-desert",
+ "the-end"
+ )
+ // list of tokens where the name can just be lowercased and spaces can be replaced with dashes
+
+ private val specialRPC = mapOf(
+ "Fisherman's Hut" to "fishermans-hut", "Unincorporated" to "high-level",
+ "Dragon's Nest" to "dragons-nest", "Void Sepulture" to "the-end", "Void Slate" to "the-end",
+ "Zealot Bruiser Hideout" to "the-end", "Desert Settlement" to "mushroom-desert",
+ "Oasis" to "mushroom-desert", "Desert Mountain" to "mushroom-desert", "Jake's House" to "mushroom-desert",
+ "Trapper's Den" to "mushroom-desert", "Mushroom Gorge" to "mushroom-desert",
+ "Glowing Mushroom Cave" to "mushroom-desert", "Overgrown Mushroom Cave" to "mushroom-desert",
+ "Shepherd's Keep" to "mushroom-desert", "Treasure Hunter Camp" to "mushroom-desert",
+ "Windmill" to "the-barn", "Spider's Den" to "spiders-den", "Arachne's Burrow" to "spiders-den",
+ "Arachne's Sanctuary" to "spiders-den", "Archaeologist's Camp" to "spiders-den",
+ "Grandma's House" to "spiders-den", "Gravel Mines" to "spiders-den", "Spider Mound" to "spiders-den",
+ "Melody's Plateau" to "forest", "Viking Longhouse" to "forest", "Lonely Island" to "forest",
+ "Howling Cave" to "forest"
+ ) // maps locations that do have a token, but have parentheses or a legacy key
+
+ private val specialNetherRPC = arrayOf(
+ "Aura's Lab",
+ "Barbarian Outpost",
+ "Belly of the Beast",
+ "Blazing Volcano",
+ "Burning Desert",
+ "Cathedral",
+ "Chief's Hut",
+ "Courtyard",
+ "Crimson Fields",
+ "Crimson Isle",
+ "Dojo",
+ "Dragontail Auction House",
+ "Dragontail Bank",
+ "Dragontail Bazaar",
+ "Dragontail Blacksmith",
+ "Dragontail Townsquare",
+ "Dragontail",
+ "Forgotten Skull",
+ "Igrupan's Chicken Coop",
+ "Igrupan's House",
+ "Mage Council",
+ "Mage Outpost",
+ "Magma Chamber",
+ "Matriarch's Lair",
+ "Minion Shop",
+ "Mystic Marsh",
+ "Odger's Hut",
+ "Plhlegblast Pool",
+ "Ruins of Ashfang",
+ "Scarleton Auction House",
+ "Scarleton Bank",
+ "Scarleton Bazaar",
+ "Scarleton Blacksmith",
+ "Scarleton Minion Shop",
+ "Scarleton Plaza",
+ "Scarleton",
+ "Smoldering Tomb",
+ "Stronghold",
+ "The Bastion",
+ "The Dukedom",
+ "The Wasteland",
+ "Throne Room"
+ )
+ // list of nether locations because there are sooo many (truncated some according to scoreboard)
+
+ fun getDiscordIconKey(location: String): String {
+ val keyIfNormal = location.lowercase().replace(' ', '-')
+
+ return if (normalRPC.contains(keyIfNormal)) {
+ keyIfNormal
+ } else if (specialRPC.containsKey(location)) {
+ specialRPC[location]!!
+ } else if (specialNetherRPC.contains(location)) {
+ "blazing-fortress"
+ } else {
+ "skyblock" // future proofing since we can't update the images anymore :(
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordRPCManager.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordRPCManager.kt
new file mode 100644
index 000000000..c5dfcdff2
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordRPCManager.kt
@@ -0,0 +1,183 @@
+package at.hannibal2.skyhanni.features.misc.discordrpc
+
+// This entire file was taken from SkyblockAddons code, ported to SkyHanni
+
+import at.hannibal2.skyhanni.SkyHanniMod.*
+import at.hannibal2.skyhanni.events.ConfigLoadEvent
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import com.google.gson.JsonObject
+import com.jagrosh.discordipc.IPCClient
+import com.jagrosh.discordipc.IPCListener
+import com.jagrosh.discordipc.entities.RichPresence
+import io.github.moulberry.moulconfig.observer.Property
+import kotlinx.coroutines.launch
+import net.minecraftforge.event.world.WorldEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import net.minecraftforge.fml.common.network.FMLNetworkEvent
+import java.util.*
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
+
+class DiscordRPCManager : IPCListener {
+ private val applicationID = 653443797182578707L
+ private val updatePeriod = 4200L
+
+ private val config get() = feature.misc.discordRPC
+
+ private var client: IPCClient? = null
+ private lateinit var secondLine: DiscordStatus
+ private lateinit var firstLine: DiscordStatus
+ private var startTimestamp: Long? = null
+ private var startOnce = false
+
+ private var updateTimer: Timer? = null
+ private var connected = false
+
+ private val DiscordLocationKey = DiscordLocationKey()
+
+ private fun start() {
+ coroutineScope.launch {
+ try {
+ if (isActive()) {
+ return@launch
+ }
+ consoleLog("Starting Discord RPC...")
+
+ firstLine = getStatusByConfigId(config.firstLine.get())
+ secondLine = getStatusByConfigId(config.secondLine.get())
+ startTimestamp = System.currentTimeMillis()
+ client = IPCClient(applicationID)
+ client?.setListener(this@DiscordRPCManager) // why must kotlin be this way
+
+ try {
+ client?.connect()
+ } catch (ex: Exception) {
+ consoleLog("Warn: Failed to connect to RPC!")
+ consoleLog(ex.toString())
+ }
+ } catch (ex: Throwable) {
+ consoleLog("Warn: Discord RPC has thrown an unexpected error while trying to start...")
+ consoleLog(ex.toString())
+ }
+ }
+ }
+
+ private fun stop() {
+ coroutineScope.launch {
+ if (isActive()) {
+ connected = false
+ client?.close()
+ startOnce = false
+ }
+ }
+ }
+
+ private fun isActive() = client != null && connected
+
+ @SubscribeEvent
+ fun onConfigLoad(event: ConfigLoadEvent) {
+ for (property in listOf(
+ config.firstLine,
+ config.secondLine,
+ config.customText,
+ )) {
+ property.whenChangedWithDifference {
+ if (isActive()) {
+ updatePresence()
+ }
+ }
+ }
+ config.enabled.whenChanged { _, new ->
+ if (new) {
+// start()
+ } else {
+ stop()
+ }
+ }
+ }
+
+ fun Property<*>.whenChangedWithDifference(run: () -> (Unit)) {
+ whenChanged { old, new -> if (old != new) run() }
+ }
+
+ fun updatePresence() {
+ val location = LorenzUtils.skyBlockArea
+ val discordIconKey = DiscordLocationKey.getDiscordIconKey(location)
+
+ secondLine = getStatusByConfigId(config.secondLine.get())
+ firstLine = getStatusByConfigId(config.firstLine.get())
+ val presence: RichPresence = RichPresence.Builder()
+ .setDetails(firstLine.getDisplayString())
+ .setState(secondLine.getDisplayString())
+ .setStartTimestamp(startTimestamp!!)
+ .setLargeImage(discordIconKey, location)
+ .build()
+ client?.sendRichPresence(presence)
+ }
+
+ override fun onReady(client: IPCClient) {
+ consoleLog("Discord RPC Started.")
+ connected = true
+ updateTimer = Timer()
+ updateTimer?.schedule(object : TimerTask() {
+ override fun run() {
+ updatePresence()
+ }
+ }, 0, updatePeriod)
+ }
+
+ override fun onClose(client: IPCClient, json: JsonObject) {
+ consoleLog("Discord RPC closed.")
+ this.client = null
+ connected = false
+ cancelTimer()
+ }
+
+ override fun onDisconnect(client: IPCClient?, t: Throwable?) {
+ consoleLog("Discord RPC disconnected.")
+ this.client = null
+ connected = false
+ cancelTimer()
+ }
+
+ private fun cancelTimer() {
+ updateTimer?.let {
+ it.cancel()
+ updateTimer = null
+ }
+ }
+
+ private fun getStatusByConfigId(id: Int) = DiscordStatus.values().getOrElse(id) { DiscordStatus.NONE }
+
+ private fun isEnabled() = config.enabled.get()
+
+ @SubscribeEvent
+ fun onTick(event: TickEvent.ClientTickEvent) {
+ if (startOnce || !isEnabled()) return // the mod has already started the connection process. this variable is my way of running a function when the player joins skyblock but only running it again once they join and leave.
+ if (LorenzUtils.inSkyBlock) {
+ start()
+ startOnce = true
+ }
+ }
+
+ @SubscribeEvent
+ fun onWorldChange(event: WorldEvent.Load) {
+ val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
+ executor.schedule(
+ {
+ if (!LorenzUtils.inSkyBlock) {
+ stop()
+ }
+ },
+ 5,
+ TimeUnit.SECONDS
+ ) // wait 5 seconds to check if the new world is skyblock or not before stopping the function
+ }
+
+ @SubscribeEvent
+ fun onDisconnect(event: FMLNetworkEvent.ClientDisconnectionFromServerEvent) {
+ stop()
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordStatus.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordStatus.kt
new file mode 100644
index 000000000..ac73c33cf
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordStatus.kt
@@ -0,0 +1,149 @@
+package at.hannibal2.skyhanni.features.misc.discordrpc
+
+// SkyblockAddons code, adapted for SkyHanni
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.ActionBarStatsData
+import at.hannibal2.skyhanni.data.HypixelData
+import at.hannibal2.skyhanni.data.ScoreboardData
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.StringUtils.firstLetterUppercase
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import io.github.moulberry.notenoughupdates.util.SkyBlockTime
+import java.util.function.Supplier
+import java.util.regex.Pattern
+
+enum class DiscordStatus(private val displayMessageSupplier: Supplier<String>?) {
+ // implements "ButtonSelect:SelectItem". no idea how to translate that into skyhanni
+
+ NONE(null),
+
+ LOCATION({
+ val location = LorenzUtils.skyBlockArea
+ if (location == "Your Island") {
+ "Private Island"
+ } else {
+ location
+ /**
+ * looks slightly weird if visiting someone else's island,
+ * I was thinking of using LorenzUtils . skyblockIsland to determine if they're visiting,
+ * but it takes too long to load, so we 'd have to put in some sort of artificial delay
+ * like what I did in DiscordRPCManager.onWorldChange.
+ * after that, use the tab-list "Owner:" line to get the person we're visiting, but I don't know if
+ * that'll work with coops, and you'd have to deal with color codes as well
+ * as again, I'm pretty sure sba had "'s Island" without the name filled in this entire time,
+ * so I 'd rather have [RANK] NameThatGetsCutOff for example than 's Island
+ */
+ }
+ }),
+
+ PURSE({
+ val scoreboard = ScoreboardData.sidebarLinesFormatted
+ var coins = ""
+
+ for (line in scoreboard) {
+ if (line.startsWith("Purse: ") || line.startsWith("Piggy: ")) {
+ coins = line.subSequence(9 until line.length).toString()
+ }
+ }
+
+ if (coins == "1") "1 Coin" else "$coins Coins"
+ }),
+
+ BITS({
+ var bits = ""
+ for (line in ScoreboardData.sidebarLinesFormatted) {
+ if (line.startsWith("Bits: ")) {
+ bits = line.subSequence(8 until line.length).toString()
+ }
+ }
+
+ when (bits) {
+ "1" -> "1 Bit"
+ "" -> "0 Bits"
+ else -> "$bits Bits"
+ }
+ }),
+
+ STATS({
+ val groups = ActionBarStatsData.groups
+ var statString = ""
+ for (item in groups.indices) {
+ when (groups[item]) {
+ "❤" -> statString = "❤${groups[item - 1]} "
+ "❈ Defense" -> statString = "$statString❈${groups[item - 1]} "
+ "✎ Mana" -> statString = "$statString✎${groups[item - 1]} "
+ }
+ }
+ statString
+ }),
+
+ ITEM({
+ val player: net.minecraft.client.entity.EntityPlayerSP = net.minecraft.client.Minecraft.getMinecraft().thePlayer
+ if (player.heldItem != null) {
+ String.format("Holding ${player.heldItem.displayName.removeColor()}")
+ } else {
+ "No item in hand"
+ }
+ }),
+
+ TIME({
+ fun formatNum(num: Int): Int {
+ val rem = num % 10
+ var returnNum = num - rem // floor()
+ if (returnNum == 0) {
+ returnNum = "0$num".toInt()
+ /**
+ * and this is so that if the minute value is ever
+ * a single digit (0 after being floored), it displays as 00 because 12:0pm just looks bad
+ */
+ }
+ return returnNum
+ }
+
+ val date: SkyBlockTime = SkyBlockTime.now()
+ val hour = if (date.hour > 12) date.hour - 12 else date.hour
+ val timeOfDay = if (date.hour > 11) "pm" else "am" // hooray for 12-hour clocks
+ "${SkyBlockTime.monthName(date.month)} ${date.day}${SkyBlockTime.daySuffix(date.day)}, $hour:${formatNum(date.minute)}$timeOfDay" // Early Winter 1st, 12:00pm
+ }),
+
+ PROFILE({
+ HypixelData.profileName.firstLetterUppercase()
+ }),
+
+ SLAYER({
+ var slayerName = ""
+ var slayerLevel = ""
+ var bossAlive = "spawning"
+ val slayerRegex =
+ Pattern.compile("((?:\\w| )*) ([IV]+)") // Samples: Revenant Horror I; Tarantula Broodfather IV
+
+ for (line in ScoreboardData.sidebarLinesFormatted) {
+ val noColorLine = line.removeColor()
+ val match = slayerRegex.matcher(noColorLine)
+ if (match.matches()) {
+ slayerName = match.group(1)
+ slayerLevel = match.group(2)
+ } else if (noColorLine == "Slay the boss!") bossAlive = "slaying"
+ else if (noColorLine == "Boss slain!") bossAlive = "slain"
+ }
+
+ if (slayerLevel == "") "Planning to do a slayer quest"// selected slayer in rpc but hasn't started a quest
+ else if (bossAlive == "spawning") "Spawning a $slayerName $slayerLevel boss."
+ else if (bossAlive == "slaying") "Slaying a $slayerName $slayerLevel boss."
+ else if (bossAlive == "slain") "Finished slaying a $slayerName $slayerLevel boss."
+ else "Something went wrong with slayer detection!"
+ }),
+
+ CUSTOM({
+ SkyHanniMod.feature.misc.discordRPC.customText.get() // custom field in the config
+ })
+ ;
+
+ fun getDisplayString(): String {
+ if (displayMessageSupplier != null) {
+ return displayMessageSupplier.get()
+ }
+ return ""
+ }
+} \ No newline at end of file