aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/moe')
-rw-r--r--src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt20
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt154
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt7
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt23
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt99
6 files changed, 304 insertions, 1 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt b/src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt
new file mode 100644
index 0000000..d935adb
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.events
+
+import net.minecraft.item.ItemStack
+import net.minecraft.screen.slot.Slot
+import net.minecraft.screen.slot.SlotActionType
+
+data class SlotClickEvent(
+ val slot: Slot,
+ val stack: ItemStack,
+ val button: Int,
+ val actionType: SlotActionType,
+) : FirmamentEvent() {
+ companion object : FirmamentEventBus<SlotClickEvent>()
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
index 5e7f612..a8daf63 100644
--- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
@@ -28,6 +28,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.PickaxeAbility
import moe.nea.firmament.features.mining.PristineProfitTracker
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures
import moe.nea.firmament.features.world.FairySouls
@@ -73,6 +74,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(Fixes)
loadFeature(DianaWaypoints)
loadFeature(ItemRarityCosmetics)
+ loadFeature(PickaxeAbility)
if (Firmament.DEBUG) {
loadFeature(DeveloperFeatures)
loadFeature(DebugView)
diff --git a/src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt b/src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt
new file mode 100644
index 0000000..4c23e04
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.regex.Pattern
+import org.intellij.lang.annotations.Language
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.item.ItemStack
+import net.minecraft.util.Hand
+import net.minecraft.util.Identifier
+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.MC
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.item.displayNameAccordingToNbt
+import moe.nea.firmament.util.item.loreAccordingToNbt
+import moe.nea.firmament.util.render.RenderCircleProgress
+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 }
+ }
+
+ 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
+ }
+
+ override fun onLoad() {
+ HudRenderEvent.subscribe(this::renderHud)
+ WorldReadyEvent.subscribe {
+ lastUsage.clear()
+ lobbyJoinTime = TimeMark.now()
+ abilityOverride = null
+ }
+ ProcessChatEvent.subscribe {
+ pattern.useMatch(it.unformattedString) {
+ lastUsage[group("name")] = TimeMark.now()
+ }
+ abilitySwitchPattern.useMatch(it.unformattedString) {
+ abilityOverride = group("ability")
+ }
+ }
+ SlotClickEvent.subscribe {
+ if (MC.screen?.title?.unformattedString == "Heart of the Mountain") {
+ val name = it.stack.displayNameAccordingToNbt?.unformattedString ?: return@subscribe
+ val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull {
+ cooldownPattern.useMatch(it.value?.unformattedString ?: return@firstNotNullOfOrNull null) {
+ parseTimePattern(group("cooldown"))
+ }
+ } ?: return@subscribe
+ defaultAbilityDurations[name] = cooldown
+ }
+ }
+ }
+
+ val pattern = Pattern.compile("You used your (?<name>.*) Pickaxe Ability!")
+
+ data class PickaxeAbilityData(
+ val name: String,
+ val cooldown: Duration,
+ )
+
+ fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? {
+ val lore = itemStack.loreAccordingToNbt
+ if (!lore.any { it.value?.unformattedString?.contains("Breaking Power") == true })
+ return null
+ val cooldown = lore.firstNotNullOfOrNull {
+ cooldownPattern.useMatch(it.value?.unformattedString ?: return@firstNotNullOfOrNull null) {
+ parseTimePattern(group("cooldown"))
+ }
+ } ?: return null
+ val name = lore.firstNotNullOfOrNull {
+ abilityPattern.useMatch(it.value?.unformattedString ?: return@firstNotNullOfOrNull null) {
+ group("name")
+ }
+ } ?: return null
+ return PickaxeAbilityData(name, cooldown)
+ }
+
+ @Language("RegExp")
+ val TIME_PATTERN = "[0-9]+[ms]"
+ 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!")
+
+ fun parseTimePattern(text: String): Duration {
+ val length = text.dropLast(1).toInt()
+ return when (text.last()) {
+ 'm' -> length.minutes
+ 's' -> length.seconds
+ else -> error("Invalid pattern for time $text")
+ }
+ }
+
+ private 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("firmament", "textures/gui/circle.png"),
+ getCooldownPercentage(ability.name, ability.cooldown).toFloat(),
+ 0f, 1f, 0f, 1f
+ )
+ event.context.matrices.pop()
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt
index d226da0..80628ae 100644
--- a/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -17,7 +18,11 @@ fun textFromNbt() {
val ItemStack.loreAccordingToNbt
get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).getList(ItemStack.LORE_KEY, NbtElement.STRING_TYPE.toInt())
- .map { lazy(LazyThreadSafetyMode.NONE) { Text.Serialization.fromJson((it as NbtString).asString()) } }
+ .map {
+ lazy(LazyThreadSafetyMode.NONE) {
+ Text.Serialization.fromJson((it as NbtString).asString())
+ }
+ }
val ItemStack.displayNameAccordingToNbt
get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).let {
diff --git a/src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt b/src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt
new file mode 100644
index 0000000..a979f8d
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.util.render
+
+val pi = Math.PI
+val tau = Math.PI * 2
+fun lerpAngle(a: Float, b: Float, progress: Float): Float {
+ // TODO: there is at least 10 mods to many in here lol
+ val shortestAngle = ((((b.mod(tau) - a.mod(tau)).mod(tau)) + tau + pi).mod(tau)) - pi
+ return ((a + (shortestAngle) * progress).mod(tau)).toFloat()
+}
+
+fun lerp(a: Float, b: Float, progress: Float): Float {
+ return a + (b - a) * progress
+}
+
+fun ilerp(a: Float, b: Float, value: Float): Float {
+ return (value - a) / (b - a)
+}
diff --git a/src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt b/src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt
new file mode 100644
index 0000000..a8245ca
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt
@@ -0,0 +1,99 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.util.render
+
+import com.mojang.blaze3d.systems.RenderSystem
+import org.joml.Matrix4f
+import org.joml.Vector2f
+import kotlin.math.atan2
+import kotlin.math.tan
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.render.BufferRenderer
+import net.minecraft.client.render.GameRenderer
+import net.minecraft.client.render.Tessellator
+import net.minecraft.client.render.VertexFormat.DrawMode
+import net.minecraft.client.render.VertexFormats
+import net.minecraft.util.Identifier
+
+object RenderCircleProgress {
+
+ fun renderCircle(
+ drawContext: DrawContext,
+ texture: Identifier,
+ progress: Float,
+ u1: Float,
+ u2: Float,
+ v1: Float,
+ v2: Float,
+ ) {
+ RenderSystem.setShaderTexture(0, texture)
+ RenderSystem.setShader { GameRenderer.getPositionColorTexProgram() }
+ RenderSystem.enableBlend()
+ val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
+ val bufferBuilder = Tessellator.getInstance().buffer
+ bufferBuilder.begin(DrawMode.TRIANGLES, VertexFormats.POSITION_COLOR_TEXTURE)
+ bufferBuilder.fixedColor(255, 255, 255, 255)
+
+ val corners = listOf(
+ Vector2f(0F, -1F),
+ Vector2f(1F, -1F),
+ Vector2f(1F, 0F),
+ Vector2f(1F, 1F),
+ Vector2f(0F, 1F),
+ Vector2f(-1F, 1F),
+ Vector2f(-1F, 0F),
+ Vector2f(-1F, -1F),
+ )
+
+ for (i in (0 until 8)) {
+ if (progress < i / 8F) {
+ break
+ }
+ val second = corners[(i + 1) % 8]
+ val first = corners[i]
+ if (progress <= (i + 1) / 8F) {
+ val internalProgress = 1 - (progress - i / 8F) * 8F
+ val angle = lerpAngle(
+ atan2(second.y, second.x),
+ atan2(first.y, first.x),
+ internalProgress
+ )
+ if (angle < tau / 8 || angle >= tau * 7 / 8) {
+ second.set(1F, tan(angle))
+ } else if (angle < tau * 3 / 8) {
+ second.set(1 / tan(angle), 1F)
+ } else if (angle < tau * 5 / 8) {
+ second.set(-1F, -tan(angle))
+ } else {
+ second.set(-1 / tan(angle), -1F)
+ }
+ }
+
+ fun ilerp(f: Float): Float =
+ ilerp(-1f, 1f, f)
+
+ bufferBuilder
+ .vertex(matrix, second.x, second.y, 0F)
+ .texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y)))
+ .next()
+ bufferBuilder
+ .vertex(matrix, first.x, first.y, 0F)
+ .texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y)))
+ .next()
+ bufferBuilder
+ .vertex(matrix, 0F, 0F, 0F)
+ .texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
+ .next()
+ }
+ bufferBuilder.unfixColor()
+ BufferRenderer.drawWithGlobalProgram(bufferBuilder.end())
+ RenderSystem.disableBlend()
+ }
+
+
+
+}