Add pickaxe ability timer

This commit is contained in:
Linnea Gräf
2024-04-18 20:10:16 +02:00
parent fbe94315a4
commit c264ca9e8f
10 changed files with 345 additions and 2 deletions

View File

@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
));
}
}
}

View File

@@ -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>()
}

View File

@@ -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)

View File

@@ -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()
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

View File

@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2024 june_hibiscus
SPDX-License-Identifier: CC-BY-4.0