diff options
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) +} |