Skip to content

Commit

Permalink
Add Pristine Profit Tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
nea89o committed Jan 17, 2024
1 parent c49b658 commit ac151c8
Show file tree
Hide file tree
Showing 16 changed files with 557 additions and 15 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ mixinextras = "0.2.0-rc.5"
jarvis = "1.1.1"
nealisp = "1.0.0"
explosiveenhancement = "1.2.1-1.20.x"
moulconfig = "2.4.0"
moulconfig = "9999.9999.9999"


[libraries]
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import moe.nea.firmament.features.inventory.SaveCursorPosition
import moe.nea.firmament.features.inventory.SlotLocking
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay
import moe.nea.firmament.features.mining.PristineProfitTracker
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures
import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.features.world.Waypoints
Expand Down Expand Up @@ -55,6 +56,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
// TODO: loadFeature(FishingWarning)
loadFeature(SlotLocking)
loadFeature(StorageOverlay)
loadFeature(PristineProfitTracker)
loadFeature(CraftingOverlay)
loadFeature(PowerUserTools)
loadFeature(Waypoints)
Expand Down
86 changes: 86 additions & 0 deletions src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <[email protected]>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package moe.nea.firmament.features.mining

import java.util.*
import kotlin.time.Duration
import moe.nea.firmament.util.TimeMark

class Histogram<T>(
val maxSize: Int,
val maxDuration: Duration,
) {

data class OrderedTimestamp(val timestamp: TimeMark, val order: Int) : Comparable<OrderedTimestamp> {
override fun compareTo(other: OrderedTimestamp): Int {
val o = timestamp.compareTo(other.timestamp)
if (o != 0) return o
return order.compareTo(other.order)
}
}

val size: Int get() = dataPoints.size
private val dataPoints: NavigableMap<OrderedTimestamp, T> = TreeMap()

private var order = Int.MIN_VALUE

fun record(entry: T, timestamp: TimeMark = TimeMark.now()) {
dataPoints[OrderedTimestamp(timestamp, order++)] = entry
trim()
}

fun oldestUpdate(): TimeMark {
trim()
return if (dataPoints.isEmpty()) TimeMark.now() else dataPoints.firstKey().timestamp
}

fun latestUpdate(): TimeMark {
trim()
return if (dataPoints.isEmpty()) TimeMark.farPast() else dataPoints.lastKey().timestamp
}

fun averagePer(valueExtractor: (T) -> Double, perDuration: Duration): Double? {
return aggregate(
seed = 0.0,
operator = { accumulator, entry, _ -> accumulator + valueExtractor(entry) },
finish = { sum, beginning, end ->
val timespan = end - beginning
if (timespan > perDuration)
sum / (timespan / perDuration)
else null
})
}

fun <V, R> aggregate(
seed: V,
operator: (V, T, TimeMark) -> V,
finish: (V, TimeMark, TimeMark) -> R
): R? {
trim()
var accumulator = seed
var min: TimeMark? = null
var max: TimeMark? = null
dataPoints.forEach { (key, value) ->
max = key.timestamp
if (min == null)
min = key.timestamp
accumulator = operator(accumulator, value, key.timestamp)
}
if (min == null)
return null
return finish(accumulator, min!!, max!!)
}

private fun trim() {
while (maxSize < dataPoints.size) {
dataPoints.pollFirstEntry()
}
dataPoints.headMap(OrderedTimestamp(TimeMark.ago(maxDuration), Int.MAX_VALUE)).clear()
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <[email protected]>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package moe.nea.firmament.features.mining

import io.github.moulberry.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.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.formatCurrency
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.formattedString
import moe.nea.firmament.util.parseIntWithComma
import moe.nea.firmament.util.useMatch

object PristineProfitTracker : FirmamentFeature {
override val identifier: String
get() = "pristine-profit"

enum class GemstoneKind(
val label: String,
val flawedId: SkyblockId,
) {
SAPPHIRE("Sapphire", SkyblockId("FLAWED_SAPPHIRE_GEM")),
RUBY("Ruby", SkyblockId("FLAWED_RUBY_GEM")),
AMETHYST("Amethyst", SkyblockId("FLAWED_AMETHYST_GEM")),
AMBER("Amber", SkyblockId("FLAWED_AMBER_GEM")),
TOPAZ("Topaz", SkyblockId("FLAWED_TOPAZ_GEM")),
JADE("Jade", SkyblockId("FLAWED_JADE_GEM")),
JASPER("Jasper", SkyblockId("FLAWED_JASPER_GEM")),
OPAL("Opal", SkyblockId("FLAWED_OPAL_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) {
val timeout by duration("timeout", 0.seconds, 120.seconds) { 30.seconds }
val gui by position("position", 80, 30) { Point(0.05, 0.2) }
}

val sellingStrategy = BazaarPriceStrategy.SELL_ORDER

val pristineRegex =
"PRISTINE! You found . Flawed (?<kind>${
GemstoneKind.values().joinToString("|") { it.label }
}) Gemstone x(?<count>[0-9,]+)!".toPattern()

val collectionHistogram = Histogram<Double>(10000, 180.seconds)
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

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.translatable(
"firmament.pristine-profit.collection",
formatCurrency(collectionPerSecond * SECONDS_PER_HOUR, 1)
).formattedString()
ProfitHud.moneyCurrent = moneyPerSecond
ProfitHud.moneyText = Text.translatable(
"firmament.pristine-profit.money",
formatCurrency(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)
}
}


override fun onLoad() {
ProcessChatEvent.subscribe {
pristineRegex.useMatch(it.unformattedString) {
val gemstoneKind = GemstoneKind.valueOf(group("kind").uppercase())
val flawedCount = parseIntWithComma(group("count"))
val moneyAmount = sellingStrategy.getSellPrice(gemstoneKind.flawedId) * flawedCount
moneyHistogram.record(moneyAmount)
val collectionAmount = flawedCount * ROUGHS_PER_FLAWED
collectionHistogram.record(collectionAmount.toDouble())
updateUi()
}
}
}
}
113 changes: 113 additions & 0 deletions src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <[email protected]>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package moe.nea.firmament.gui

import com.mojang.blaze3d.systems.RenderSystem
import io.github.cottonmc.cotton.gui.client.ScreenDrawing
import io.github.cottonmc.cotton.gui.widget.data.Texture
import io.github.moulberry.moulconfig.common.MyResourceLocation
import io.github.moulberry.moulconfig.common.RenderContext
import io.github.moulberry.moulconfig.gui.GuiComponent
import io.github.moulberry.moulconfig.gui.GuiImmediateContext
import io.github.moulberry.moulconfig.observer.GetSetter
import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
import me.shedaniel.math.Color
import net.minecraft.client.gui.DrawContext
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament

class BarComponent(
val progress: GetSetter<Double>, val total: GetSetter<Double>,
val fillColor: Color,
val emptyColor: Color,
) : GuiComponent() {
override fun getWidth(): Int {
return 80
}

override fun getHeight(): Int {
return 8
}

companion object {
val resource = Firmament.identifier("textures/gui/bar.png")
val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F)
val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F)
val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F)
val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F)
}

private fun drawSection(
context: DrawContext,
texture: Texture,
x: Int,
y: Int,
width: Int,
sectionStart: Double,
sectionEnd: Double
) {
if (sectionEnd < progress.get() && width == 4) {
ScreenDrawing.texturedRect(context, x, y, 4, 8, texture, fillColor.color)
return
}
if (sectionStart > progress.get() && width == 4) {
ScreenDrawing.texturedRect(context, x, y, 4, 8, texture, emptyColor.color)
return
}
val increasePerPixel = (sectionEnd - sectionStart / 4)
var valueAtPixel = sectionStart
for (i in (0 until width)) {
ScreenDrawing.texturedRect(
context, x + i, y, 1, 8,
texture.image, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2,
if (valueAtPixel < progress.get()) fillColor.color else emptyColor.color
)
valueAtPixel += increasePerPixel
}
}

override fun render(context: GuiImmediateContext) {
val renderContext = (context.renderContext as ModernRenderContext).drawContext
var i = 0
val x = 0
val y = 0
while (i < context.width - 4) {
drawSection(
renderContext,
if (i == 0) left else middle,
x + i, y,
(context.width - (i + 4)).coerceAtMost(4),
i * total.get() / context.width, (i + 4) * total.get() / context.width
)
i += 4
}
drawSection(
renderContext,
right,
x + context.width - 4,
y,
4,
(context.width - 4) * total.get() / context.width,
total.get()
)
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)

}

}

fun Identifier.toMoulConfig(): MyResourceLocation {
return MyResourceLocation(this.namespace, this.path)
}

fun RenderContext.color(color: Color) {
color(color.red, color.green, color.blue, color.alpha)
}

fun RenderContext.color(red: Int, green: Int, blue: Int, alpha: Int) {
color(red / 255f, green / 255f, blue / 255f, alpha / 255f)
}
4 changes: 2 additions & 2 deletions src/main/kotlin/moe/nea/firmament/gui/WBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import moe.nea.firmament.Firmament

open class WBar(
var progress: Double,
val total: Double,
var total: Double,
val fillColor: Color,
val emptyColor: Color,
val emptyColor: Color = fillColor.darker(2.0),
) : WWidget() {
companion object {
val resource = Firmament.identifier("textures/gui/bar.png")
Expand Down
Loading

0 comments on commit ac151c8

Please sign in to comment.