aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt1
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/OtherPlayersSlayerAPI.kt36
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/CarryTrackerJson.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/entity/slayer/SlayerDeathEvent.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt326
7 files changed, 391 insertions, 12 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
index 4ac3f7925..bf0f2ba0a 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
@@ -104,6 +104,7 @@ class ConfigManager {
// commands
"features.garden.GardenConfig.cropSpeedMeterPos",
"features.misc.MiscConfig.collectionCounterPos",
+ "features.misc.MiscConfig.carryPosition",
"features.misc.MiscConfig.lockedMouseDisplay",
// debug features
diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
index 9b85c2cf9..cf4679e39 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
@@ -64,6 +64,7 @@ import at.hannibal2.skyhanni.features.mining.fossilexcavator.ExcavatorProfitTrac
import at.hannibal2.skyhanni.features.mining.glacitemineshaft.CorpseTracker
import at.hannibal2.skyhanni.features.mining.powdertracker.PowderTracker
import at.hannibal2.skyhanni.features.minion.MinionFeatures
+import at.hannibal2.skyhanni.features.misc.CarryTracker
import at.hannibal2.skyhanni.features.misc.CollectionTracker
import at.hannibal2.skyhanni.features.misc.LockMouseLook
import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager
@@ -183,7 +184,6 @@ object Commands {
registerCommand("shwords", "Opens the config list for modifying visual words") { openVisualWords() }
}
-
private fun usersNormal() {
registerCommand(
"shmarkplayer",
@@ -192,8 +192,7 @@ object Commands {
registerCommand("shtrackcollection", "Tracks your collection gain over time") { CollectionTracker.command(it) }
registerCommand(
"shcroptime",
- "Calculates with your current crop per second speed " +
- "how long you need to farm a crop to collect this amount of items",
+ "Calculates with your current crop per second speed how long you need to farm a crop to collect this amount of items",
) { GardenCropTimeCommand.onCommand(it) }
registerCommand(
"shcropsin",
@@ -250,8 +249,7 @@ object Commands {
) { FarmingWeightDisplay.lookUpCommand(it) }
registerCommand(
"shcopytranslation",
- "Copy the English translation of a message in another language to the clipboard.\n" +
- "Uses a 2 letter language code that can be found at the end of a translation message.",
+ "Copy the English translation of a message in another language to the clipboard.\n" + "Uses a 2 letter language code that can be found at the end of a translation message.",
) { Translator.fromEnglish(it) }
registerCommand(
"shtranslate",
@@ -367,8 +365,12 @@ object Commands {
) { ColorFormattingHelper.printColorCodeList() }
registerCommand(
"shtps",
- "Informs in chat about the server ticks per second (TPS)."
+ "Informs in chat about the server ticks per second (TPS).",
) { TpsCounter.tpsCommand() }
+ registerCommand(
+ "shcarry",
+ "Keep track of carries you do.",
+ ) { CarryTracker.onCommand(it) }
}
private fun usersBugFix() {
@@ -490,7 +492,7 @@ object Commands {
) { SkyBlockIslandTest.onCommand(it) }
registerCommand(
"shdebugprice",
- "Debug different price sources for an item."
+ "Debug different price sources for an item.",
) { ItemPriceUtils.debugItemPrice(it) }
registerCommand(
"shdebugscoreboard",
@@ -585,9 +587,7 @@ object Commands {
) { TitleManager.command(it) }
registerCommand(
"shresetconfig",
- "Reloads the config manager and rendering processors of MoulConfig. " +
- "This §cWILL RESET §7your config, but also updating the java config files " +
- "(names, description, orderings and stuff).",
+ "Reloads the config manager and rendering processors of MoulConfig. " + "This §cWILL RESET §7your config, but also updating the java config files " + "(names, description, orderings and stuff).",
) { SkyHanniDebugsAndTests.resetConfigCommand() }
registerCommand(
"shreadcropmilestonefromclipboard",
@@ -664,8 +664,7 @@ object Commands {
else -> currentStream
}
- val switchingToBeta = updateStream == UpdateStream.BETA &&
- (currentStream != UpdateStream.BETA || !UpdateManager.isCurrentlyBeta())
+ val switchingToBeta = updateStream == UpdateStream.BETA && (currentStream != UpdateStream.BETA || !UpdateManager.isCurrentlyBeta())
if (switchingToBeta) {
ChatUtils.clickableChat(
"Are you sure you want to switch to beta? These versions may be less stable.",
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java
index 03b0bf779..9d503c398 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java
@@ -145,6 +145,9 @@ public class MiscConfig {
public Position collectionCounterPos = new Position(10, 10, false, true);
@Expose
+ public Position carryPosition = new Position(10, 10, false, true);
+
+ @Expose
@ConfigOption(name = "Brewing Stand Overlay", desc = "Display the item names directly inside the Brewing Stand.")
@ConfigEditorBoolean
@FeatureToggle
diff --git a/src/main/java/at/hannibal2/skyhanni/data/OtherPlayersSlayerAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/OtherPlayersSlayerAPI.kt
new file mode 100644
index 000000000..0401746c4
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/OtherPlayersSlayerAPI.kt
@@ -0,0 +1,36 @@
+package at.hannibal2.skyhanni.data
+
+import at.hannibal2.skyhanni.data.mob.Mob
+import at.hannibal2.skyhanni.events.MobEvent
+import at.hannibal2.skyhanni.events.entity.slayer.SlayerDeathEvent
+import at.hannibal2.skyhanni.features.slayer.SlayerType
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.test.command.ErrorManager
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+@SkyHanniModule
+object OtherPlayersSlayerAPI {
+
+ @SubscribeEvent
+ fun onMobDespawn(event: MobEvent.DeSpawn.SkyblockMob) {
+ val mob = event.mob
+
+ // no death, rather despawn because too far away
+ if (mob.baseEntity.health != 0f) return
+
+ if (mob.mobType != Mob.Type.SLAYER) return
+
+ val owner = mob.owner?.ownerName
+ val tier = mob.levelOrTier
+ val name = mob.name
+ val slayerType = SlayerType.getByName(name) ?: run {
+ ErrorManager.logErrorStateWithData(
+ "Unknown slayer type found", "unknown slayer",
+ "name" to name,
+ )
+ return
+ }
+
+ SlayerDeathEvent(slayerType, tier, owner).post()
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/CarryTrackerJson.kt b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/CarryTrackerJson.kt
new file mode 100644
index 000000000..1819b36b9
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/CarryTrackerJson.kt
@@ -0,0 +1,8 @@
+package at.hannibal2.skyhanni.data.jsonobjects.repo
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+data class CarryTrackerJson(
+ @Expose @SerializedName("slayer_names") val slayerNames: Map<String, List<String>>,
+)
diff --git a/src/main/java/at/hannibal2/skyhanni/events/entity/slayer/SlayerDeathEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/entity/slayer/SlayerDeathEvent.kt
new file mode 100644
index 000000000..620e4fb3e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/events/entity/slayer/SlayerDeathEvent.kt
@@ -0,0 +1,6 @@
+package at.hannibal2.skyhanni.events.entity.slayer
+
+import at.hannibal2.skyhanni.api.event.SkyHanniEvent
+import at.hannibal2.skyhanni.features.slayer.SlayerType
+
+class SlayerDeathEvent(val slayerType: SlayerType, val tier: Int, val owner: String?) : SkyHanniEvent()
diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt
new file mode 100644
index 000000000..f70fa58b8
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt
@@ -0,0 +1,326 @@
+package at.hannibal2.skyhanni.features.misc
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.api.event.HandleEvent
+import at.hannibal2.skyhanni.data.jsonobjects.repo.CarryTrackerJson
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.events.RepositoryReloadEvent
+import at.hannibal2.skyhanni.events.entity.slayer.SlayerDeathEvent
+import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboardUtils.formatNum
+import at.hannibal2.skyhanni.features.slayer.SlayerType
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.ChatUtils
+import at.hannibal2.skyhanni.utils.CollectionUtils.addString
+import at.hannibal2.skyhanni.utils.HypixelCommands
+import at.hannibal2.skyhanni.utils.KeyboardManager
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble
+import at.hannibal2.skyhanni.utils.NumberUtil.formatDoubleOrUserError
+import at.hannibal2.skyhanni.utils.NumberUtil.formatIntOrUserError
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables
+import at.hannibal2.skyhanni.utils.StringUtils.cleanPlayerName
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * TODO more carry features
+ * save on restart
+ * support for Dungeon, Kuudra, crimson minibosses
+ * average spawn time per slayer customer
+ * change customer name color if offline, onlilne, on your island
+ * show time since last boss died next to slayer customer name
+ * highlight slayer bosses for slayer customers
+ * automatically mark customers with /shmarkplaayers
+ * show a line behind them
+ */
+
+@SkyHanniModule
+object CarryTracker {
+ private val config get() = SkyHanniMod.feature.misc
+
+ private val customers = mutableListOf<Customer>()
+ private val carryTypes = mutableMapOf<String, CarryType>()
+ private var slayerNames = emptyMap<SlayerType, List<String>>()
+
+ private var display = listOf<Renderable>()
+
+ private val patternGroup = RepoPattern.group("carry")
+
+ /**
+ * REGEX-TEST:
+ * §6Trade completed with §r§b[MVP§r§c+§r§b] ClachersHD§r§f§r§6!
+ */
+ private val tradeCompletedPattern by patternGroup.pattern(
+ "trade.completed",
+ "§6Trade completed with (?<name>.*)§r§6!",
+ )
+
+ /**
+ * REGEX-TEST:
+ * §r§a§l+ §r§6500k coins
+ */
+ private val rawNamePattern by patternGroup.pattern(
+ "trade.coins.gained",
+ " §r§a§l\\+ §r§6(?<coins>.*) coins",
+ )
+
+ @HandleEvent
+ fun onSlayerDeath(event: SlayerDeathEvent) {
+ val slayerType = event.slayerType
+ val tier = event.tier
+ val owner = event.owner
+ for (customer in customers) {
+ if (!customer.name.equals(owner, ignoreCase = true)) continue
+ for (carry in customer.carries) {
+ val type = carry.type as? SlayerCarryType ?: return
+ if (type.slayerType != slayerType) continue
+ if (type.tier != tier) continue
+ carry.done++
+ if (carry.done == carry.requested) {
+ ChatUtils.chat("Carry done for ${customer.name}!")
+ LorenzUtils.sendTitle("§eCarry done!", 3.seconds)
+ }
+ update()
+ }
+ }
+ }
+
+ // TODO create trade event with player name, coins and items
+ var lastTradedPlayer = ""
+
+ @SubscribeEvent
+ fun onChat(event: LorenzChatEvent) {
+ tradeCompletedPattern.matchMatcher(event.message) {
+ lastTradedPlayer = group("name").cleanPlayerName()
+ }
+
+ rawNamePattern.matchMatcher(event.message) {
+ val coinsGained = group("coins").formatDouble()
+ getCustomer(lastTradedPlayer).alreadyPaid += coinsGained
+ update()
+ }
+ }
+
+ @SubscribeEvent
+ fun onRepoReload(event: RepositoryReloadEvent) {
+ val data = event.getConstant<CarryTrackerJson>("CarryTracker")
+ slayerNames = data.slayerNames.mapKeys { SlayerType.valueOf(it.key) }
+ }
+
+ @SubscribeEvent
+ fun onRenderOverlay(event: GuiRenderEvent) {
+ if (!LorenzUtils.inSkyBlock) return
+
+ config.carryPosition.renderRenderables(display, posLabel = "Carry Tracker")
+ }
+
+ fun onCommand(args: Array<String>) {
+ if (args.size < 2 || args.size > 3) {
+ ChatUtils.userError("Usage:\n§c/shcarry <customer name> <type> <amount requested>\n§c/shcarry <type> <price per>")
+ return
+ }
+ if (args.size == 2) {
+ setPrice(args[0], args[1])
+ return
+ }
+
+ val customerName = args[0]
+
+ val rawType = args[1]
+ val carryType = getCarryType(rawType) ?: return
+
+ val amountRequested = args[2].formatIntOrUserError() ?: return
+
+ val newCarry = Carry(carryType, amountRequested)
+
+ for (customer in customers) {
+ if (!customer.name.equals(customerName, ignoreCase = true)) continue
+ val carries = customer.carries
+ for (carry in carries.toList()) {
+ if (!newCarry.type.sameType(carry.type)) continue
+ val newAmountRequested = carry.requested + amountRequested
+ if (newAmountRequested < 1) {
+ ChatUtils.userError("New carry amount requested must be positive!")
+ return
+ }
+ carries.remove(carry)
+ val updatedCarry = Carry(carryType, newAmountRequested)
+ updatedCarry.done = carry.done
+ carries.add(updatedCarry)
+ update()
+ ChatUtils.chat("Updated carry: §b$customerName §8x$newAmountRequested ${newCarry.type}")
+ return
+ }
+ }
+ if (amountRequested < 1) {
+ ChatUtils.userError("Carry amount requested must be positive!")
+ return
+ }
+
+ val customer = getCustomer(customerName)
+ customer.carries.add(newCarry)
+ update()
+ ChatUtils.chat("Started carry: §b$customerName §8x$amountRequested ${newCarry.type}")
+ }
+
+ private fun getCarryType(rawType: String): CarryType? = carryTypes.getOrPut(rawType) {
+ createCarryType(rawType) ?: run {
+ ChatUtils.userError("Unknown carry type: '$rawType'! Use e.g. rev5, sven4, eman3, blaze2..")
+ return null
+ }
+ }
+
+ private fun setPrice(rawType: String, rawPrice: String) {
+ val carryType = getCarryType(rawType) ?: return
+
+ val price = rawPrice.formatDoubleOrUserError() ?: return
+ carryType.pricePer = price
+ update()
+ ChatUtils.chat("Set carry price for $carryType §eto §6${price.formatNum()} coins.")
+ }
+
+ private fun getCustomer(customerName: String): Customer {
+ for (customer in customers) {
+ if (customer.name.equals(customerName, ignoreCase = true)) {
+ return customer
+ }
+ }
+ val customer = Customer(customerName)
+ customers.add(customer)
+ return customer
+ }
+
+ private fun CarryType.sameType(other: CarryType): Boolean = name == other.name && tier == other.tier
+
+ private fun update() {
+ val list = mutableListOf<Renderable>()
+ if (customers.none { it.carries.isNotEmpty() }) {
+ display = emptyList()
+ return
+ }
+ list.addString("§c§lCarries")
+ for (customer in customers) {
+ if (customer.carries.isEmpty()) continue
+ addCustomerName(customer, list)
+
+ val carries = customer.carries
+ for (carry in carries) {
+ val requested = carry.requested
+ val done = carry.done
+ val missing = requested - done
+
+ val color = if (done > requested) "§c" else if (done == requested) "§a" else "§e"
+ val cost = formatCost(carry.type.pricePer?.let { it * requested })
+ val text = "$color$done§8/$color$requested $cost"
+ list.add(
+ Renderable.clickAndHover(
+ Renderable.string(" ${carry.type} $text"),
+ tips = buildList {
+ add("§b${customer.name}' ${carry.type} §cCarry")
+ add("")
+ add("§7Requested: §e$requested")
+ add("§7Done: §e$done")
+ add("§7Missing: §e$missing")
+ add("")
+ if (cost != "") {
+ add("§7Total cost: §e${cost}")
+ add("§7Cost per carry: §e${formatCost(carry.type.pricePer)}")
+ } else {
+ add("§cNo price set for this carry!")
+ add("§7Set a price with §e/shcarry <type> <price>")
+ }
+ add("")
+ add("§eClick to send current progress in the party chat!")
+ add("§eControl-click to remove this carry!")
+ },
+ onClick = {
+ if (KeyboardManager.isModifierKeyDown()) {
+ carries.remove(carry)
+ update()
+ } else {
+ HypixelCommands.partyChat(
+ "${customer.name} ${carry.type.toString().removeColor()} carry: $done/$requested",
+ )
+ }
+ },
+ ),
+ )
+ }
+ }
+ display = list
+ }
+
+ private fun addCustomerName(customer: Customer, list: MutableList<Renderable>) {
+ val customerName = customer.name
+ val totalCost = customer.carries.sumOf { it.getCost() ?: 0.0 }
+ val totalCostFormat = formatCost(totalCost)
+ if (totalCostFormat != "") {
+ val paidFormat = "§6${customer.alreadyPaid.formatNum()}"
+ val missingFormat = formatCost(totalCost - customer.alreadyPaid)
+ list.add(
+ Renderable.clickAndHover(
+ Renderable.string("§b$customerName $paidFormat§8/${totalCostFormat}"),
+ tips = listOf(
+ "§7Carries for §b$customerName",
+ "",
+ "§7Total cost: $totalCostFormat",
+ "§7Already paid: $paidFormat",
+ "§7Still missing: $missingFormat",
+ "",
+ "§eClick to send missing coins in party chat!",
+ ),
+ onClick = {
+ HypixelCommands.partyChat(
+ "$customerName Carry: already paid: ${paidFormat.removeColor()}, " + "still missing: ${missingFormat.removeColor()}",
+ )
+ },
+ ),
+ )
+
+ } else {
+ list.addString("§b$customerName$totalCostFormat")
+ }
+ }
+
+ private fun Carry.getCost(): Double? {
+ return type.pricePer?.let {
+ requested * it
+ }?.takeIf { it != 0.0 }
+ }
+
+ private fun formatCost(totalCost: Double?): String = if (totalCost == 0.0 || totalCost == null) "" else "§6${totalCost.formatNum()}"
+
+ private fun createCarryType(input: String): CarryType? {
+ if (input.length == 1) return null
+ val rawName = input.dropLast(1).lowercase()
+ val tier = input.last().digitToIntOrNull() ?: return null
+
+ getSlayerType(rawName)?.let {
+ return SlayerCarryType(it, tier)
+ }
+
+ return null
+ }
+
+ private fun getSlayerType(name: String): SlayerType? = slayerNames.entries.find { name in it.value }?.key
+
+ class Customer(
+ val name: String,
+ var alreadyPaid: Double = 0.0,
+ val carries: MutableList<Carry> = mutableListOf(),
+ )
+
+ class Carry(val type: CarryType, val requested: Int, var done: Int = 0)
+
+ abstract class CarryType(val name: String, val tier: Int, var pricePer: Double? = null) {
+ override fun toString(): String = "§d$name $tier"
+ }
+
+ class SlayerCarryType(val slayerType: SlayerType, tier: Int) : CarryType(slayerType.displayName, tier)
+// class DungeonCarryType(val floor: DungeonFloor, masterMode: Boolean) : CarryType(floor.name, tier)
+}