feat: Smooth-ish interpolation of breaking progress

This commit is contained in:
Linnea Gräf
2025-03-08 14:09:39 +01:00
parent b4a93bd751
commit c3d10173d2
2 changed files with 53 additions and 6 deletions

View File

@@ -4,11 +4,15 @@ import snownee.jade.api.BlockAccessor
import snownee.jade.api.IBlockComponentProvider
import snownee.jade.api.ITooltip
import snownee.jade.api.config.IPluginConfig
import kotlin.time.DurationUnit
import net.minecraft.block.BlockState
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.tr
object CustomMiningHardnessProvider : IBlockComponentProvider {
@@ -29,13 +33,35 @@ object CustomMiningHardnessProvider : IBlockComponentProvider {
data class BreakingInfo(
val blockPos: BlockPos, val stage: Int,
val state: BlockState?,
val ts: TimeMark = TimeMark.now()
)
var previousBreakingInfo: BreakingInfo? = null
var currentBreakingInfo: BreakingInfo? = null
@Subscribe
fun clearInfoOnStopBreaking(event: TickEvent) {
val isBreakingBlock = MC.interactionManager?.isBreakingBlock ?: false
if (!isBreakingBlock) {
previousBreakingInfo = null
currentBreakingInfo = null
}
}
@JvmStatic
fun setBreakingInfo(blockPos: BlockPos, stage: Int) {
currentBreakingInfo = BreakingInfo(blockPos.toImmutable(), stage, MC.world?.getBlockState(blockPos))
previousBreakingInfo = currentBreakingInfo
val state = MC.world?.getBlockState(blockPos)
if (previousBreakingInfo?.let { it.state != state || it.blockPos != blockPos } ?: false)
previousBreakingInfo == null
currentBreakingInfo = BreakingInfo(blockPos.toImmutable(), stage, state)
// For some reason hypixel initially sends a stage 10 packet, and then fixes it up with a stage 0 packet.
// Ignore the stage 10 packet if we dont have any previous packets for this block.
// This could in theory still have issues if someone perfectly stops breaking a block the tick it finishes and then does not break another block until it respawns, but i deem that to be too much of an edge case.
if (stage == 10 && previousBreakingInfo == null) {
previousBreakingInfo = null
currentBreakingInfo = null
}
}
@JvmStatic
@@ -45,10 +71,26 @@ object CustomMiningHardnessProvider : IBlockComponentProvider {
val info = currentBreakingInfo
if (info?.blockPos != pos || info.state != MC.world?.getBlockState(pos)) {
currentBreakingInfo = null
return 0F
previousBreakingInfo = null
return original
}
// TODO: use linear extrapolation to guess how quickly this progresses between stages.
// This would only be possible after one stage, but should make the remaining stages a bit smoother
return info.stage / 10F
// TODO: improve this interpolation to work across all stages, to alleviate some of the jittery bar.
// Maybe introduce a proper mining API that tracks the actual progress with some sort of FSM
val interpolatedStage = previousBreakingInfo?.let { prev ->
val timeBetweenTicks = (info.ts - prev.ts).toDouble(DurationUnit.SECONDS)
val stagesPerUpdate = (info.stage - prev.stage).toDouble()
if (stagesPerUpdate < 1) return@let null
val stagesPerSecond = stagesPerUpdate / timeBetweenTicks
info.stage + (info.ts.passedTime().toDouble(DurationUnit.SECONDS) * stagesPerSecond)
.coerceAtMost(stagesPerUpdate)
}?.toFloat()
val stage = interpolatedStage ?: info.stage.toFloat()
return stage / 10F
}
@JvmStatic
fun replaceBlockBreakSpeed(original: Float): Float {
if (isOnMiningIsland()) return 0F
return original
}
}

View File

@@ -16,5 +16,10 @@ public class PatchBreakingBarSpeedJade {
private static float replaceBlockBreakingProgress(float original) {
return CustomMiningHardnessProvider.replaceBreakProgress(original);
}
// TODO: given the inherent roughness of the server provided stages, i don't feel the need to also patch the accesses to the delta provided by the block state. if i ever get around to adding the linear extrapolation, i should also patch that extrapolation to use the better one provided by the server stages.
@ModifyExpressionValue(method = "drawBreakingProgress",
at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;calcBlockBreakingDelta(Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F"))
private static float replacePlayerSpecificBreakingProgress(float original) {
return CustomMiningHardnessProvider.replaceBlockBreakSpeed(original);
}
}