Add pickaxe ability timer
This commit is contained in:
@@ -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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt
Normal file
20
src/main/kotlin/moe/nea/firmament/events/SlotClickEvent.kt
Normal 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>()
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import moe.nea.firmament.features.inventory.SaveCursorPosition
|
|||||||
import moe.nea.firmament.features.inventory.SlotLocking
|
import moe.nea.firmament.features.inventory.SlotLocking
|
||||||
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
|
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
|
||||||
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay
|
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.mining.PristineProfitTracker
|
||||||
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures
|
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures
|
||||||
import moe.nea.firmament.features.world.FairySouls
|
import moe.nea.firmament.features.world.FairySouls
|
||||||
@@ -73,6 +74,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
|
|||||||
loadFeature(Fixes)
|
loadFeature(Fixes)
|
||||||
loadFeature(DianaWaypoints)
|
loadFeature(DianaWaypoints)
|
||||||
loadFeature(ItemRarityCosmetics)
|
loadFeature(ItemRarityCosmetics)
|
||||||
|
loadFeature(PickaxeAbility)
|
||||||
if (Firmament.DEBUG) {
|
if (Firmament.DEBUG) {
|
||||||
loadFeature(DeveloperFeatures)
|
loadFeature(DeveloperFeatures)
|
||||||
loadFeature(DebugView)
|
loadFeature(DebugView)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
|
* 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
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
@@ -17,7 +18,11 @@ fun textFromNbt() {
|
|||||||
|
|
||||||
val ItemStack.loreAccordingToNbt
|
val ItemStack.loreAccordingToNbt
|
||||||
get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).getList(ItemStack.LORE_KEY, NbtElement.STRING_TYPE.toInt())
|
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
|
val ItemStack.displayNameAccordingToNbt
|
||||||
get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).let {
|
get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).let {
|
||||||
|
|||||||
23
src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt
Normal file
23
src/main/kotlin/moe/nea/firmament/util/render/LerpUtils.kt
Normal 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)
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -164,5 +164,8 @@
|
|||||||
"firmament.quick-commands.join.success": "Joining: %s",
|
"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.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-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"
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/main/resources/assets/firmament/textures/gui/circle.png
Normal file
BIN
src/main/resources/assets/firmament/textures/gui/circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 340 B |
@@ -0,0 +1,3 @@
|
|||||||
|
SPDX-FileCopyrightText: 2024 june_hibiscus
|
||||||
|
|
||||||
|
SPDX-License-Identifier: CC-BY-4.0
|
||||||
Reference in New Issue
Block a user