From c264ca9e8f9f2b0aed457753c43bb4e25edb0ef1 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 18 Apr 2024 20:10:16 +0200 Subject: Add pickaxe ability timer --- .../nea/firmament/mixins/SlotClickEventPatch.java | 34 +++++ .../moe/nea/firmament/events/SlotClickEvent.kt | 20 +++ .../moe/nea/firmament/features/FeatureManager.kt | 2 + .../firmament/features/mining/PickaxeAbility.kt | 154 +++++++++++++++++++++ .../moe/nea/firmament/util/item/NbtItemData.kt | 7 +- .../moe/nea/firmament/util/render/LerpUtils.kt | 23 +++ .../firmament/util/render/RenderCircleProgress.kt | 99 +++++++++++++ .../resources/assets/firmament/lang/en_us.json | 5 +- .../assets/firmament/textures/gui/circle.png | Bin 0 -> 340 bytes .../firmament/textures/gui/circle.png.license | 3 + 10 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/SlotClickEventPatch.java create mode 100644 src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/mining/PickaxeAbility.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/render/RenderCircleProgress.kt create mode 100644 src/main/resources/assets/firmament/textures/gui/circle.png create mode 100644 src/main/resources/assets/firmament/textures/gui/circle.png.license diff --git a/src/main/java/moe/nea/firmament/mixins/SlotClickEventPatch.java b/src/main/java/moe/nea/firmament/mixins/SlotClickEventPatch.java new file mode 100644 index 0000000..4e6d766 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/SlotClickEventPatch.java @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import com.llamalad7.mixinextras.sugar.Local; +import moe.nea.firmament.events.SlotClickEvent; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.SlotActionType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayerInteractionManager.class) +public class SlotClickEventPatch { + + @Inject(method = "clickSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V")) + private void onSlotClick(int syncId, int slotId, int button, SlotActionType actionType, PlayerEntity player, CallbackInfo ci, @Local ScreenHandler handler) { + if (0 <= slotId && slotId < handler.slots.size()) { + SlotClickEvent.Companion.publish(new SlotClickEvent( + handler.getSlot(slotId), + handler.getSlot(slotId).getStack(), + button, + actionType + )); + } + } +} 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 + * + * 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() +} 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(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 + * + * 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() + var abilityOverride: String? = null + var defaultAbilityDurations = mutableMapOf( + "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 (?.*) 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: (?$TIME_PATTERN)") + val abilityPattern = Pattern.compile("Ability: (?.*) {2}RIGHT CLICK") + val abilitySwitchPattern = + Pattern.compile("You selected (?.*) 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 + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * 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 + * + * 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 + * + * 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() + } + + + +} diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index bc0e205..37df38e 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -164,5 +164,8 @@ "firmament.quick-commands.join.success": "Joining: %s", "firmament.quick-commands.join.explain": "Join a dungeon or kuudra floor by using commands like /join f1, /join m7, /join fe or /join khot", "firmament.quick-commands.join.unknown-kuudra": "Unknown kuudra floor %s", - "firmament.quick-commands.join.unknown-catacombs": "Unknown catacombs floor %s" + "firmament.quick-commands.join.unknown-catacombs": "Unknown catacombs floor %s", + "firmament.config.pickaxe-info": "Pickaxes", + "firmament.config.pickaxe-info.ability-cooldown": "Pickaxe Ability Cooldown", + "firmament.config.pickaxe-info.ability-scale": "Ability Cooldown Scale" } diff --git a/src/main/resources/assets/firmament/textures/gui/circle.png b/src/main/resources/assets/firmament/textures/gui/circle.png new file mode 100644 index 0000000..ffd3fab Binary files /dev/null and b/src/main/resources/assets/firmament/textures/gui/circle.png differ diff --git a/src/main/resources/assets/firmament/textures/gui/circle.png.license b/src/main/resources/assets/firmament/textures/gui/circle.png.license new file mode 100644 index 0000000..5d26548 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/circle.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 june_hibiscus + +SPDX-License-Identifier: CC-BY-4.0 -- cgit