-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
557 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
|
||
|
||
} |
140 changes: 140 additions & 0 deletions
140
src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.