aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/features
diff options
context:
space:
mode:
authorhannibal2 <24389977+hannibal002@users.noreply.github.com>2024-09-21 13:03:18 +0200
committerGitHub <noreply@github.com>2024-09-21 13:03:18 +0200
commit1052203d26f8ded5d7a1b1eb25a32e0ce2e6c784 (patch)
treebc9c0b23aa5ed85562da4c6f8c30721f5a3d444d /src/main/java/at/hannibal2/skyhanni/features
parent1739a5f13194bc136e169a7870a14c2704174be8 (diff)
downloadskyhanni-1052203d26f8ded5d7a1b1eb25a32e0ce2e6c784.tar.gz
skyhanni-1052203d26f8ded5d7a1b1eb25a32e0ce2e6c784.tar.bz2
skyhanni-1052203d26f8ded5d7a1b1eb25a32e0ce2e6c784.zip
Feature: Carry Tracker (#2185)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/features')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt326
1 files changed, 326 insertions, 0 deletions
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)
+}