diff options
author | Linnea Gräf <nea@nea.moe> | 2024-01-15 00:32:43 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-01-17 21:10:51 +0100 |
commit | ac151c8ebc4c5546795cdbf5b0c179183e2c71d1 (patch) | |
tree | 52141110008ba6809d0dde5bc4456fc37e6a665a /src/main/kotlin/moe | |
parent | c49b65835d37266508561e60782bda36275fb8ae (diff) | |
download | Firmament-ac151c8ebc4c5546795cdbf5b0c179183e2c71d1.tar.gz Firmament-ac151c8ebc4c5546795cdbf5b0c179183e2c71d1.tar.bz2 Firmament-ac151c8ebc4c5546795cdbf5b0c179183e2c71d1.zip |
Add Pristine Profit Tracker
Diffstat (limited to 'src/main/kotlin/moe')
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() +} |