aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/firmament
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/moe/nea/firmament')
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt86
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt140
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt113
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/WBar.kt4
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt62
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt24
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/MC.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt52
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/TimeMark.kt40
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/regex.kt8
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/stringutil.kt11
13 files changed, 532 insertions, 14 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
index 30036b7..3c5ac62 100644
--- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
@@ -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
@@ -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)
diff --git a/src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt b/src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt
new file mode 100644
index 0000000..e897aaa
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/mining/Histogram.kt
@@ -0,0 +1,86 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * 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()
+ }
+
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt b/src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt
new file mode 100644
index 0000000..294c835
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/mining/PristineProfitTracker.kt
@@ -0,0 +1,140 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * 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()
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt
new file mode 100644
index 0000000..f59d6b1
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt
@@ -0,0 +1,113 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * 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)
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/WBar.kt b/src/main/kotlin/moe/nea/firmament/gui/WBar.kt
index 2c37d11..5772aac 100644
--- a/src/main/kotlin/moe/nea/firmament/gui/WBar.kt
+++ b/src/main/kotlin/moe/nea/firmament/gui/WBar.kt
@@ -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")
diff --git a/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt b/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt
new file mode 100644
index 0000000..d56f713
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.gui.hud
+
+import io.github.moulberry.moulconfig.gui.GuiContext
+import io.github.moulberry.moulconfig.gui.component.TextComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
+import net.minecraft.resource.ResourceManager
+import net.minecraft.resource.SynchronousResourceReloader
+import moe.nea.firmament.events.HudRenderEvent
+import moe.nea.firmament.gui.config.HudMeta
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.MoulConfigUtils
+
+abstract class MoulConfigHud(
+ val name: String,
+ val hudMeta: HudMeta,
+) {
+ companion object {
+ private val componentWrapper = object : GuiComponentWrapper(GuiContext(TextComponent("§cERROR"))) {
+ init {
+ this.client = MC.instance
+ }
+ }
+ }
+
+ private var fragment: GuiContext? = null
+
+ open fun shouldRender(): Boolean {
+ return true
+ }
+
+ init {
+ HudRenderEvent.subscribe {
+ if (!shouldRender()) return@subscribe
+ val renderContext = componentWrapper.createContext(it.context)
+ if (fragment == null)
+ loadFragment()
+ it.context.matrices.push()
+ hudMeta.applyTransformations(it.context.matrices)
+ val renderContextTranslated =
+ renderContext.translated(hudMeta.absoluteX, hudMeta.absoluteY, hudMeta.width, hudMeta.height)
+ .scaled(hudMeta.scale)
+ fragment!!.root.render(renderContextTranslated)
+ it.context.matrices.pop()
+ }
+ MC.resourceManager.registerReloader(object : SynchronousResourceReloader {
+ override fun reload(manager: ResourceManager?) {
+ fragment = null
+ }
+ })
+ }
+
+ fun loadFragment() {
+ fragment = MoulConfigUtils.loadGui(name, this)
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt b/src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt
new file mode 100644
index 0000000..8b746cc
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/BazaarPriceStrategy.kt
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.util
+
+import moe.nea.firmament.repo.HypixelStaticData
+
+enum class BazaarPriceStrategy {
+ BUY_ORDER,
+ SELL_ORDER,
+ NPC_SELL;
+
+ fun getSellPrice(skyblockId: SkyblockId): Double {
+ val bazaarEntry = HypixelStaticData.bazaarData[skyblockId] ?: return 0.0
+ return when (this) {
+ BUY_ORDER -> bazaarEntry.quickStatus.sellPrice
+ SELL_ORDER -> bazaarEntry.quickStatus.buyPrice
+ NPC_SELL -> TODO()
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt b/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt
index 4927d65..22756d0 100644
--- a/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt
@@ -91,7 +91,7 @@ abstract class FragmentGuiScreen(
verticalAmount: Double
): Boolean {
return ifPopup {
- it.mouseScrolled(mouseX, mouseY, verticalAmount)
+ it.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/util/MC.kt b/src/main/kotlin/moe/nea/firmament/util/MC.kt
index f3fc754..5c4bf5c 100644
--- a/src/main/kotlin/moe/nea/firmament/util/MC.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/MC.kt
@@ -14,6 +14,7 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.network.message.ArgumentSignatureDataMap
import net.minecraft.network.message.LastSeenMessagesCollector.LastSeenMessages
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
+import net.minecraft.resource.ReloadableResourceManagerImpl
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
import moe.nea.firmament.events.TickEvent
@@ -59,6 +60,7 @@ object MC {
player?.networkHandler?.sendCommand(command)
}
+ inline val resourceManager get() = (MinecraftClient.getInstance().resourceManager as ReloadableResourceManagerImpl)
inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance()
inline val keyboard get() = MinecraftClient.getInstance().keyboard
diff --git a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt
index bea3bc6..9ca0a73 100644
--- a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt
@@ -8,10 +8,60 @@ package moe.nea.firmament.util
import io.github.moulberry.moulconfig.common.MyResourceLocation
import io.github.moulberry.moulconfig.gui.GuiContext
+import io.github.moulberry.moulconfig.xml.ChildCount
+import io.github.moulberry.moulconfig.xml.XMLContext
+import io.github.moulberry.moulconfig.xml.XMLGuiLoader
import io.github.moulberry.moulconfig.xml.XMLUniverse
+import javax.xml.namespace.QName
+import me.shedaniel.math.Color
+import org.w3c.dom.Element
+import moe.nea.firmament.gui.BarComponent
object MoulConfigUtils {
- val universe = XMLUniverse.getDefaultUniverse()
+ val firmUrl = "http://nea.moe/Firmament"
+ val universe = XMLUniverse.getDefaultUniverse().also { uni ->
+ uni.registerMapper(java.awt.Color::class.java) {
+ if (it.startsWith("#")) {
+ val hexString = it.substring(1)
+ val hex = hexString.toInt(16)
+ if (hexString.length == 6) {
+ return@registerMapper java.awt.Color(hex)
+ }
+ if (hexString.length == 8) {
+ return@registerMapper java.awt.Color(hex, true)
+ }
+ error("Hexcolor $it needs to be exactly 6 or 8 hex digits long")
+ }
+ return@registerMapper java.awt.Color(it.toInt(), true)
+ }
+ uni.registerMapper(Color::class.java) {
+ val color = uni.mapXMLObject(it, java.awt.Color::class.java)
+ Color.ofRGBA(color.red, color.green, color.blue, color.alpha)
+ }
+ uni.registerLoader(object : XMLGuiLoader<BarComponent> {
+ override fun getName(): QName {
+ return QName(firmUrl, "Bar")
+ }
+
+ override fun createInstance(context: XMLContext<*>, element: Element): BarComponent {
+ return BarComponent(
+ context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!,
+ context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!,
+ context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(),
+ context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(),
+ )
+ }
+
+ override fun getChildCount(): ChildCount {
+ return ChildCount.NONE
+ }
+
+ override fun getAttributeNames(): Map<String, Boolean> {
+ return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true)
+ }
+ })
+ }
+
fun loadGui(name: String, bindTo: Any): GuiContext {
return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml")))
}
diff --git a/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt b/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt
index f3526be..41a196d 100644
--- a/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/TimeMark.kt
@@ -7,22 +7,42 @@
package moe.nea.firmament.util
import kotlin.time.Duration
-import kotlin.time.ExperimentalTime
-import kotlin.time.TimeSource
+import kotlin.time.Duration.Companion.milliseconds
-@OptIn(ExperimentalTime::class)
-class TimeMark private constructor(private val timeMark: TimeSource.Monotonic.ValueTimeMark?) : Comparable<TimeMark> {
- fun passedTime() = timeMark?.elapsedNow() ?: Duration.INFINITE
+class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> {
+ fun passedTime() = if (timeMark == 0L) Duration.INFINITE else (System.currentTimeMillis() - timeMark).milliseconds
+
+ operator fun minus(other: TimeMark): Duration {
+ if (other.timeMark == timeMark)
+ return 0.milliseconds
+ if (other.timeMark == 0L)
+ return Duration.INFINITE
+ if (timeMark == 0L)
+ return -Duration.INFINITE
+ return (timeMark - other.timeMark).milliseconds
+ }
companion object {
- fun now() = TimeMark(TimeSource.Monotonic.markNow())
- fun farPast() = TimeMark(null)
+ fun now() = TimeMark(System.currentTimeMillis())
+ fun farPast() = TimeMark(0L)
+ fun ago(timeDelta: Duration): TimeMark {
+ if (timeDelta.isFinite()) {
+ return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds)
+ }
+ require(timeDelta.isPositive())
+ return farPast()
+ }
+ }
+
+ override fun hashCode(): Int {
+ return timeMark.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is TimeMark && other.timeMark == timeMark
}
override fun compareTo(other: TimeMark): Int {
- if (this.timeMark == other.timeMark) return 0
- if (this.timeMark == null) return -1
- if (other.timeMark == null) return -1
return this.timeMark.compareTo(other.timeMark)
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/util/regex.kt b/src/main/kotlin/moe/nea/firmament/util/regex.kt
index 97c2797..4cc3f03 100644
--- a/src/main/kotlin/moe/nea/firmament/util/regex.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/regex.kt
@@ -6,5 +6,13 @@
package moe.nea.firmament.util
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
regex.matchEntire(this)?.let(block)
+
+inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? =
+ matcher(string)
+ .takeIf(Matcher::matches)
+ ?.let(block)
diff --git a/src/main/kotlin/moe/nea/firmament/util/stringutil.kt b/src/main/kotlin/moe/nea/firmament/util/stringutil.kt
new file mode 100644
index 0000000..21625d4
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/stringutil.kt
@@ -0,0 +1,11 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.util
+
+fun parseIntWithComma(string: String): Int {
+ return string.replace(",", "").toInt()
+}