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.IBlockComponentProvider
import snownee.jade.api.ITooltip import snownee.jade.api.ITooltip
import snownee.jade.api.config.IPluginConfig import snownee.jade.api.config.IPluginConfig
import kotlin.time.DurationUnit
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import moe.nea.firmament.Firmament 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.MC
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.tr import moe.nea.firmament.util.tr
object CustomMiningHardnessProvider : IBlockComponentProvider { object CustomMiningHardnessProvider : IBlockComponentProvider {
@@ -29,13 +33,35 @@ object CustomMiningHardnessProvider : IBlockComponentProvider {
data class BreakingInfo( data class BreakingInfo(
val blockPos: BlockPos, val stage: Int, val blockPos: BlockPos, val stage: Int,
val state: BlockState?, val state: BlockState?,
val ts: TimeMark = TimeMark.now()
) )
var previousBreakingInfo: BreakingInfo? = null
var currentBreakingInfo: BreakingInfo? = null var currentBreakingInfo: BreakingInfo? = null
@Subscribe
fun clearInfoOnStopBreaking(event: TickEvent) {
val isBreakingBlock = MC.interactionManager?.isBreakingBlock ?: false
if (!isBreakingBlock) {
previousBreakingInfo = null
currentBreakingInfo = null
}
}
@JvmStatic @JvmStatic
fun setBreakingInfo(blockPos: BlockPos, stage: Int) { 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 @JvmStatic
@@ -45,10 +71,26 @@ object CustomMiningHardnessProvider : IBlockComponentProvider {
val info = currentBreakingInfo val info = currentBreakingInfo
if (info?.blockPos != pos || info.state != MC.world?.getBlockState(pos)) { if (info?.blockPos != pos || info.state != MC.world?.getBlockState(pos)) {
currentBreakingInfo = null currentBreakingInfo = null
return 0F previousBreakingInfo = null
return original
} }
// TODO: use linear extrapolation to guess how quickly this progresses between stages. // TODO: improve this interpolation to work across all stages, to alleviate some of the jittery bar.
// This would only be possible after one stage, but should make the remaining stages a bit smoother // Maybe introduce a proper mining API that tracks the actual progress with some sort of FSM
return info.stage / 10F 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) { private static float replaceBlockBreakingProgress(float original) {
return CustomMiningHardnessProvider.replaceBreakProgress(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);
}
} }