Refactor source layout

Introduce compat source sets and move all kotlin sources to the main directory

[no changelog]
This commit is contained in:
Linnea Gräf
2024-08-28 19:04:24 +02:00
parent a690630816
commit d2f240ff0c
251 changed files with 295 additions and 38 deletions

View File

@@ -0,0 +1,81 @@
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()
}
}

View File

@@ -0,0 +1,176 @@
package moe.nea.firmament.features.mining
import java.util.regex.Pattern
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import net.minecraft.item.ItemStack
import net.minecraft.util.DyeColor
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HudRenderEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.SlotClickEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.DurabilityBarEvent
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.TIME_PATTERN
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.extraAttributes
import moe.nea.firmament.util.item.displayNameAccordingToNbt
import moe.nea.firmament.util.item.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.parseTimePattern
import moe.nea.firmament.util.render.RenderCircleProgress
import moe.nea.firmament.util.render.lerp
import moe.nea.firmament.util.toShedaniel
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
object PickaxeAbility : FirmamentFeature {
override val identifier: String
get() = "pickaxe-info"
object TConfig : ManagedConfig(identifier) {
val cooldownEnabled by toggle("ability-cooldown") { true }
val cooldownScale by integer("ability-scale", 16, 64) { 16 }
val drillFuelBar by toggle("fuel-bar") { true }
}
var lobbyJoinTime = TimeMark.farPast()
var lastUsage = mutableMapOf<String, TimeMark>()
var abilityOverride: String? = null
var defaultAbilityDurations = mutableMapOf<String, Duration>(
"Mining Speed Boost" to 120.seconds,
"Pickobulus" to 110.seconds,
"Gemstone Infusion" to 140.seconds,
"Hazardous Miner" to 140.seconds,
"Maniac Miner" to 59.seconds,
"Vein Seeker" to 60.seconds
)
override val config: ManagedConfig
get() = TConfig
fun getCooldownPercentage(name: String, cooldown: Duration): Double {
val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE
if (sinceLastUsage < cooldown)
return sinceLastUsage / cooldown
val sinceLobbyJoin = lobbyJoinTime.passedTime()
val halfCooldown = cooldown / 2
if (sinceLobbyJoin < halfCooldown) {
return (sinceLobbyJoin / halfCooldown)
}
return 1.0
}
@Subscribe
fun onSlotClick(it: SlotClickEvent) {
if (MC.screen?.title?.unformattedString == "Heart of the Mountain") {
val name = it.stack.displayNameAccordingToNbt?.unformattedString ?: return
val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull {
cooldownPattern.useMatch(it.unformattedString) {
parseTimePattern(group("cooldown"))
}
} ?: return
defaultAbilityDurations[name] = cooldown
}
}
@Subscribe
fun onDurabilityBar(it: DurabilityBarEvent) {
if (!TConfig.drillFuelBar) return
val lore = it.item.loreAccordingToNbt
if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return
val maxFuel = lore.firstNotNullOfOrNull {
fuelPattern.useMatch(it.unformattedString) {
parseShortNumber(group("maxFuel"))
}
} ?: return
val extra = it.item.extraAttributes
if (!extra.contains("drill_fuel")) return
val fuel = extra.getInt("drill_fuel")
val percentage = fuel / maxFuel.toFloat()
it.barOverride = DurabilityBarEvent.DurabilityBar(
lerp(
DyeColor.RED.toShedaniel(),
DyeColor.GREEN.toShedaniel(),
percentage
), percentage
)
}
@Subscribe
fun onChatMessage(it: ProcessChatEvent) {
abilityUsePattern.useMatch(it.unformattedString) {
lastUsage[group("name")] = TimeMark.now()
}
abilitySwitchPattern.useMatch(it.unformattedString) {
abilityOverride = group("ability")
}
}
@Subscribe
fun onWorldReady(event: WorldReadyEvent) {
lastUsage.clear()
lobbyJoinTime = TimeMark.now()
abilityOverride = null
}
val abilityUsePattern = Pattern.compile("You used your (?<name>.*) Pickaxe Ability!")
val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)")
data class PickaxeAbilityData(
val name: String,
val cooldown: Duration,
)
fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? {
val lore = itemStack.loreAccordingToNbt
if (!lore.any { it.unformattedString.contains("Breaking Power") == true })
return null
val cooldown = lore.firstNotNullOfOrNull {
cooldownPattern.useMatch(it.unformattedString) {
parseTimePattern(group("cooldown"))
}
} ?: return null
val name = lore.firstNotNullOfOrNull {
abilityPattern.useMatch(it.unformattedString) {
group("name")
}
} ?: return null
return PickaxeAbilityData(name, cooldown)
}
val cooldownPattern = Pattern.compile("Cooldown: (?<cooldown>$TIME_PATTERN)")
val abilityPattern = Pattern.compile("Ability: (?<name>.*) {2}RIGHT CLICK")
val abilitySwitchPattern =
Pattern.compile("You selected (?<ability>.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!")
@Subscribe
fun renderHud(event: HudRenderEvent) {
if (!TConfig.cooldownEnabled) return
var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return
defaultAbilityDurations[ability.name] = ability.cooldown
val ao = abilityOverride
if (ao != ability.name && ao != null) {
ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds)
}
event.context.matrices.push()
event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F)
event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F)
RenderCircleProgress.renderCircle(
event.context, Identifier.of("firmament", "textures/gui/circle.png"),
getCooldownPercentage(ability.name, ability.cooldown).toFloat(),
0f, 1f, 0f, 1f
)
event.context.matrices.pop()
}
}

View File

@@ -0,0 +1,133 @@
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.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.entries.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.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 = sellingStrategy.getSellPrice(gemstoneKind.flawedId) * flawedCount
moneyHistogram.record(moneyAmount)
val collectionAmount = flawedCount * ROUGHS_PER_FLAWED
collectionHistogram.record(collectionAmount.toDouble())
updateUi()
}
}
}