package moe.nea.firmament.features.mining import io.github.notenoughupdates.moulconfig.xml.Bind import moe.nea.jarvis.api.Point import kotlinx.serialization.Serializable import kotlinx.serialization.serializer import kotlin.time.Duration.Companion.seconds import net.minecraft.text.Text import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ProcessChatEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.hud.MoulConfigHud import moe.nea.firmament.util.BazaarPriceStrategy import moe.nea.firmament.util.FirmFormatters.formatCommas import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.data.ProfileSpecificDataHolder import moe.nea.firmament.util.formattedString import moe.nea.firmament.util.StringUtil.parseIntWithComma import moe.nea.firmament.util.useMatch object PristineProfitTracker : FirmamentFeature { override val identifier: String get() = "pristine-profit" enum class GemstoneKind( val label: String, ) { SAPPHIRE("Sapphire"), RUBY("Ruby"), AMETHYST("Amethyst"), AMBER("Amber"), TOPAZ("Topaz"), JADE("Jade"), JASPER("Jasper"), OPAL("Opal"), ; val flawedId: SkyblockId = SkyblockId("FLAWED_${name}_GEM") val fineId: SkyblockId = SkyblockId("FINE_${name}_GEM") } @Serializable data class Data( var maxMoneyPerSecond: Double = 1.0, var maxCollectionPerSecond: Double = 1.0, ) object DConfig : ProfileSpecificDataHolder<Data>(serializer(), identifier, ::Data) override val config: ManagedConfig? get() = TConfig object TConfig : ManagedConfig(identifier, Category.MINING) { val timeout by duration("timeout", 0.seconds, 120.seconds) { 30.seconds } val gui by position("position", 80, 30) { Point(0.05, 0.2) } val useFineGemstones by toggle("fine-gemstones") { false } } val sellingStrategy = BazaarPriceStrategy.SELL_ORDER val pristineRegex = "PRISTINE! You found . Flawed (?<kind>${ GemstoneKind.entries.joinToString("|") { it.label } }) Gemstone x(?<count>[0-9,]+)!".toPattern() val collectionHistogram = Histogram<Double>(10000, 180.seconds) /** * Separate histogram for money, since money changes based on gemstone, therefore we cannot calculate money from collection. */ val moneyHistogram = Histogram<Double>(10000, 180.seconds) object ProfitHud : MoulConfigHud("pristine_profit", TConfig.gui) { @field:Bind var moneyCurrent: Double = 0.0 @field:Bind var moneyMax: Double = 1.0 @field:Bind var moneyText = "" @field:Bind var collectionCurrent = 0.0 @field:Bind var collectionMax = 1.0 @field:Bind var collectionText = "" override fun shouldRender(): Boolean = collectionHistogram.latestUpdate().passedTime() < TConfig.timeout } val SECONDS_PER_HOUR = 3600 val ROUGHS_PER_FLAWED = 80 val FLAWED_PER_FINE = 80 val ROUGHS_PER_FINE = ROUGHS_PER_FLAWED * FLAWED_PER_FINE fun updateUi() { val collectionPerSecond = collectionHistogram.averagePer({ it }, 1.seconds) val moneyPerSecond = moneyHistogram.averagePer({ it }, 1.seconds) if (collectionPerSecond == null || moneyPerSecond == null) return ProfitHud.collectionCurrent = collectionPerSecond ProfitHud.collectionText = Text.stringifiedTranslatable("firmament.pristine-profit.collection", formatCommas(collectionPerSecond * SECONDS_PER_HOUR, 1)).formattedString() ProfitHud.moneyCurrent = moneyPerSecond ProfitHud.moneyText = Text.stringifiedTranslatable("firmament.pristine-profit.money", formatCommas(moneyPerSecond * SECONDS_PER_HOUR, 1)) .formattedString() val data = DConfig.data if (data != null) { if (data.maxCollectionPerSecond < collectionPerSecond && collectionHistogram.oldestUpdate() .passedTime() > 30.seconds ) { data.maxCollectionPerSecond = collectionPerSecond DConfig.markDirty() } if (data.maxMoneyPerSecond < moneyPerSecond && moneyHistogram.oldestUpdate().passedTime() > 30.seconds) { data.maxMoneyPerSecond = moneyPerSecond DConfig.markDirty() } ProfitHud.collectionMax = maxOf(data.maxCollectionPerSecond, collectionPerSecond) ProfitHud.moneyMax = maxOf(data.maxMoneyPerSecond, moneyPerSecond) } } @Subscribe fun onMessage(it: ProcessChatEvent) { pristineRegex.useMatch(it.unformattedString) { val gemstoneKind = GemstoneKind.valueOf(group("kind").uppercase()) val flawedCount = parseIntWithComma(group("count")) val moneyAmount = if (TConfig.useFineGemstones) sellingStrategy.getSellPrice(gemstoneKind.fineId) * flawedCount / FLAWED_PER_FINE else sellingStrategy.getSellPrice(gemstoneKind.flawedId) * flawedCount moneyHistogram.record(moneyAmount) val collectionAmount = flawedCount * ROUGHS_PER_FLAWED collectionHistogram.record(collectionAmount.toDouble()) updateUi() } } }