Skip to content

Commit 766681c

Browse files
authored
Fix: Break Manager (#138)
This introduces a more immutable style system for item swapping logic. Each tick a new SwapInfo is created for all active break infos. This also fixes an issue with redundancy spam due to the build tasks request logic. Along with the fixes, ive added a new "Server Swap" setting for the user to edit how many ticks the server should be given to recognise the selected tool. This is hard limited to a minimum of 2 ticks for secondary breaks to allow the user to select 0 for servers < 1.21.5 that dont apply certain enchantments, like efficiency, as player attributes
1 parent cc4fd43 commit 766681c

File tree

8 files changed

+145
-96
lines changed

8 files changed

+145
-96
lines changed

src/main/kotlin/com/lambda/config/groups/BreakSettings.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ class BreakSettings(
5050

5151
// Fixes / Delays
5252
override val breakThreshold by c.setting("Break Threshold", 0.70f, 0.1f..1.0f, 0.01f, "The break amount at which the block is considered broken", visibility = vis).group(groupPath, Group.General)
53-
override val fudgeFactor by c.setting("Fudge Factor", 2, 0..5, 1, "The amount of ticks to give double, aka secondary breaks extra for the server to recognise the break", visibility = vis).group(groupPath, Group.General)
53+
override val fudgeFactor by c.setting("Fudge Factor", 1, 0..5, 1, "The number of ticks to add to the break time, usually to account for server lag", visibility = vis).group(groupPath, Group.General)
54+
override val serverSwapTicks by c.setting("Server Swap", 2, 0..5, 1, "The number of ticks to give the server time to recognize the player attributes on the swapped item", " tick(s)", visibility = vis).group(groupPath, Group.General)
5455
// override val desyncFix by c.setting("Desync Fix", false, "Predicts if the players breaking will be slowed next tick as block break packets are processed using the players next position") { vis() && page == Page.General }
55-
override val breakDelay by c.setting("Break Delay", 0, 0..6, 1, "The delay between breaking blocks", " ticks", visibility = vis).group(groupPath, Group.General)
56+
override val breakDelay by c.setting("Break Delay", 0, 0..6, 1, "The delay between breaking blocks", " tick(s)", visibility = vis).group(groupPath, Group.General)
5657

5758
// Timing
5859
override val breakStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post, TickEvent.Player.Post), description = "The sub-tick timing at which break actions can be performed", visibility = vis).group(groupPath, Group.General)

src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import com.lambda.interaction.material.StackSelection.Companion.selectStack
3838
import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial
3939
import com.lambda.interaction.material.container.MaterialContainer
4040
import com.lambda.interaction.request.breaking.BreakConfig
41-
import com.lambda.interaction.request.breaking.BreakManager
4241
import com.lambda.interaction.request.inventory.InventoryConfig
4342
import com.lambda.interaction.request.placing.PlaceConfig
4443
import com.lambda.interaction.request.rotating.Rotation.Companion.rotation
@@ -850,7 +849,6 @@ object BuildSimulator {
850849
val swapStack = swapCandidates.map { it.matchingStacks(stackSelection) }
851850
.asSequence()
852851
.flatten()
853-
.filter { BreakManager.currentStackSelection.filterStack(it) }
854852
.let { containerStacks ->
855853
var bestStack = ItemStack.EMPTY
856854
var bestBreakDelta = -1f

src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ import java.awt.Color
2828
interface BreakConfig : RequestConfig {
2929
val breakMode: BreakMode
3030
val sorter: SortMode
31-
val breakThreshold: Float
3231
val rebreak: Boolean
3332

3433
val doubleBreak: Boolean
3534
val unsafeCancels: Boolean
3635

36+
val breakThreshold: Float
3737
val fudgeFactor: Int
38+
val serverSwapTicks: Int
3839
//ToDo: Needs a more advanced player simulation implementation to predict the next ticks onGround / submerged status
3940
// abstract val desyncFix: Boolean
4041
val breakDelay: Int

src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package com.lambda.interaction.request.breaking
1919

2020
import com.lambda.interaction.construction.context.BreakContext
2121
import com.lambda.interaction.request.ActionInfo
22+
import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Primary
23+
import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Rebreak
2224
import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
2325
import com.lambda.util.Describable
2426
import com.lambda.util.NamedEnum
@@ -31,7 +33,6 @@ import net.minecraft.entity.player.PlayerEntity
3133
import net.minecraft.item.ItemStack
3234
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
3335
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action
34-
import net.minecraft.world.BlockView
3536

3637
data class BreakInfo(
3738
override var context: BreakContext,
@@ -44,10 +45,9 @@ data class BreakInfo(
4445

4546
// Pre Processing
4647
var shouldProgress = false
47-
var couldReBreak by OneSetPerTick(value = RebreakManager.RebreakPotential.None, throwOnLimitBreach = true)
48-
var shouldSwap by OneSetPerTick(value = false, throwOnLimitBreach = true)
48+
var rebreakPotential by OneSetPerTick(value = RebreakManager.RebreakPotential.None, throwOnLimitBreach = true)
49+
var swapInfo by OneSetPerTick(value = SwapInfo.EMPTY, throwOnLimitBreach = true)
4950
var swapStack: ItemStack by OneSetPerTick(ItemStack.EMPTY, true)
50-
var minSwapTicks by OneSetPerTick(0, true)
5151

5252
// BreakInfo Specific
5353
var updatedThisTick by OneSetPerTick(false, resetAfterTick = true).apply { set(true) }
@@ -60,7 +60,7 @@ data class BreakInfo(
6060
var breakingTicks by OneSetPerTick(0, true)
6161
var soundsCooldown by OneSetPerTick(0f, true)
6262
var vanillaInstantBreakable = false
63-
val rebreakable get() = !vanillaInstantBreakable && type == BreakType.Primary
63+
val rebreakable get() = !vanillaInstantBreakable && type == Primary
6464

6565
enum class BreakType(
6666
override val displayName: String,
@@ -70,12 +70,6 @@ data class BreakInfo(
7070
Secondary("Secondary", "A second block broken at the same time (when double‑break is enabled)."),
7171
RedundantSecondary("Redundant Secondary", "A previously started secondary break that’s now ignored/monitored only (no new actions)."),
7272
Rebreak("Rebreak", "A previously broken block which new breaks in the same position can compound progression on. Often rebreaking instantly.");
73-
74-
fun getBreakThreshold(breakConfig: BreakConfig) =
75-
when (this) {
76-
Primary -> breakConfig.breakThreshold
77-
else -> 1.0f
78-
}
7973
}
8074

8175
// Post Processing
@@ -113,20 +107,6 @@ data class BreakInfo(
113107
item = null
114108
}
115109

116-
fun shouldSwap(player: ClientPlayerEntity, world: BlockView): Boolean {
117-
val breakDelta = context.cachedState.calcItemBlockBreakingDelta(player, world, context.blockPos, swapStack)
118-
val breakProgress = breakDelta * (breakingTicks + 1)
119-
return if (couldReBreak == RebreakManager.RebreakPotential.Instant)
120-
breakConfig.swapMode.isEnabled()
121-
else when (breakConfig.swapMode) {
122-
BreakConfig.SwapMode.None -> false
123-
BreakConfig.SwapMode.Start -> !breaking
124-
BreakConfig.SwapMode.End -> breakProgress >= getBreakThreshold()
125-
BreakConfig.SwapMode.StartAndEnd -> !breaking || breakProgress >= getBreakThreshold()
126-
BreakConfig.SwapMode.Constant -> true
127-
}
128-
}
129-
130110
fun setBreakingTextureStage(
131111
player: ClientPlayerEntity,
132112
world: ClientWorld,
@@ -144,7 +124,12 @@ data class BreakInfo(
144124
return if (progress > 0.0f) (progress * 10.0f).toInt().coerceAtMost(9) else -1
145125
}
146126

147-
fun getBreakThreshold() = type.getBreakThreshold(breakConfig)
127+
fun getBreakThreshold() =
128+
when (type) {
129+
Primary,
130+
Rebreak-> breakConfig.breakThreshold
131+
else -> 1.0f
132+
}
148133

149134
fun startBreakPacket(world: ClientWorld, interaction: ClientPlayerInteractionManager) =
150135
breakPacket(Action.START_DESTROY_BLOCK, world, interaction)

src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import com.lambda.interaction.request.breaking.BrokenBlockHandler.destroyBlock
6565
import com.lambda.interaction.request.breaking.BrokenBlockHandler.pendingActions
6666
import com.lambda.interaction.request.breaking.BrokenBlockHandler.setPendingConfigs
6767
import com.lambda.interaction.request.breaking.BrokenBlockHandler.startPending
68+
import com.lambda.interaction.request.breaking.SwapInfo.Companion.getSwapInfo
6869
import com.lambda.interaction.request.interacting.InteractionManager
6970
import com.lambda.interaction.request.placing.PlaceManager
7071
import com.lambda.interaction.request.rotating.RotationRequest
@@ -138,15 +139,15 @@ object BreakManager : RequestHandler<BreakRequest>(
138139
private var rotationRequest: RotationRequest? = null
139140
private val rotated get() = rotationRequest?.done != false
140141

141-
var swappedThisTick = false
142-
var heldTicks = 0
143-
var swappedStack: ItemStack = ItemStack.EMPTY
142+
var currentStack: ItemStack = ItemStack.EMPTY
144143
set(value) {
145144
if (value != field)
146145
heldTicks = 0
147146
swappedThisTick = true
148147
field = value
149148
}
149+
var heldTicks = 0
150+
var swappedThisTick = false
150151
private var breakCooldown = 0
151152
var breaksThisTick = 0
152153
private var maxBreaksThisTick = 0
@@ -173,7 +174,7 @@ object BreakManager : RequestHandler<BreakRequest>(
173174

174175
listen<TickEvent.Post>(priority = Int.MIN_VALUE) {
175176
if (!swappedThisTick) {
176-
swappedStack = player.mainHandStack
177+
currentStack = player.mainHandStack
177178
}
178179
swappedThisTick = false
179180
heldTicks++
@@ -314,9 +315,12 @@ object BreakManager : RequestHandler<BreakRequest>(
314315
* @see updateBreakProgress
315316
*/
316317
private fun SafeContext.processRequest(breakRequest: BreakRequest?) {
318+
breakRequest?.let { request ->
319+
if (request.fresh) populateFrom(request)
320+
}
321+
317322
repeat(2) {
318323
breakRequest?.let { request ->
319-
if (request.fresh) populateFrom(request)
320324
if (performInstantBreaks(request)) {
321325
processNewBreaks(request)
322326
}
@@ -421,12 +425,12 @@ object BreakManager : RequestHandler<BreakRequest>(
421425
infos.forEach { it.updatePreProcessing(player, world) }
422426

423427
infos.firstOrNull()?.let { info ->
424-
infos.lastOrNull { it.shouldSwap && it.shouldProgress }?.let { last ->
425-
val minSwapTicks = max(info.minSwapTicks, last.minSwapTicks)
428+
infos.lastOrNull { it.swapInfo.swap && it.shouldProgress }?.let { last ->
429+
val minSwapTicks = max(info.swapInfo.minKeepTicks, last.swapInfo.minKeepTicks)
426430
if (!info.context.requestSwap(info.request, minSwapTicks))
427431
return false
428432
if (minSwapTicks > 0)
429-
swappedStack = info.swapStack
433+
currentStack = info.swapStack
430434
}
431435
}
432436
}
@@ -603,31 +607,8 @@ object BreakManager : RequestHandler<BreakRequest>(
603607
updatedPreProcessingThisTick = true
604608

605609
swapStack = player.inventory.getStack(context.hotbarIndex)
606-
couldReBreak = RebreakManager.couldRebreak(this, player, world)
607-
shouldSwap = shouldSwap(player, world)
608-
609-
val cachedState = context.cachedState
610-
611-
val breakTicks = (breakingTicks + 1 - breakConfig.fudgeFactor).coerceAtLeast(1)
612-
val breakAmount = cachedState.calcBreakDelta(
613-
player,
614-
world,
615-
context.blockPos,
616-
breakConfig,
617-
swapStack
618-
) * breakTicks
619-
val breakAmountNoEfficiency = cachedState.calcBreakDelta(
620-
player,
621-
world,
622-
context.blockPos,
623-
breakConfig,
624-
swapStack,
625-
ignoreEfficiency = true
626-
) * breakTicks
627-
628-
minSwapTicks = if ((breakAmount >= getBreakThreshold() || couldReBreak == RebreakManager.RebreakPotential.Instant) &&
629-
(breakAmountNoEfficiency < getBreakThreshold() || type == Secondary)) 1
630-
else 0
610+
rebreakPotential = RebreakManager.getRebreakPotential(this, player, world)
611+
swapInfo = getSwapInfo(this, player, world)
631612
}
632613

633614
/**
@@ -737,8 +718,6 @@ object BreakManager : RequestHandler<BreakRequest>(
737718
config
738719
) * (info.breakingTicks - config.fudgeFactor)
739720

740-
val overBreakThreshold = progress >= info.getBreakThreshold()
741-
742721
if (config.sounds) {
743722
if (info.soundsCooldown % 4.0f == 0.0f) {
744723
val blockSoundGroup = blockState.soundGroup
@@ -765,7 +744,7 @@ object BreakManager : RequestHandler<BreakRequest>(
765744
}
766745

767746
val swing = config.swing
768-
if (overBreakThreshold && heldTicks + 1 >= info.breakConfig.fudgeFactor) {
747+
if (progress >= info.getBreakThreshold() && info.swapInfo.canCompleteBreak) {
769748
if (info.type == Primary) {
770749
onBlockBreak(info)
771750
info.stopBreakPacket(world, interaction)
@@ -791,7 +770,7 @@ object BreakManager : RequestHandler<BreakRequest>(
791770
private fun SafeContext.startBreaking(info: BreakInfo): Boolean {
792771
val ctx = info.context
793772

794-
if (info.couldReBreak.isPossible()) {
773+
if (info.rebreakPotential.isPossible()) {
795774
when (val rebreakResult = RebreakManager.handleUpdate(info.context, info.request)) {
796775
is RebreakResult.StillBreaking -> {
797776
primaryBreak = rebreakResult.breakInfo.apply {
@@ -801,6 +780,7 @@ object BreakManager : RequestHandler<BreakRequest>(
801780
}
802781

803782
primaryBreak?.let { primary ->
783+
if (!handlePreProcessing()) return false
804784
updateBreakProgress(primary)
805785
}
806786
return true
@@ -832,14 +812,13 @@ object BreakManager : RequestHandler<BreakRequest>(
832812
lastPosStarted = ctx.blockPos
833813

834814
val blockState = blockState(ctx.blockPos)
835-
val notEmpty = blockState.isNotEmpty
836-
if (notEmpty && info.breakingTicks == 0) {
815+
if (info.breakingTicks == 0) {
837816
blockState.onBlockBreakStart(world, ctx.blockPos, player)
838817
}
839818

840-
val breakDelta = blockState.calcBreakDelta(player, world, ctx.blockPos, info.breakConfig)
841-
info.vanillaInstantBreakable = breakDelta >= 1
842-
if (notEmpty && breakDelta >= info.getBreakThreshold() && heldTicks + 1 >= info.breakConfig.fudgeFactor) {
819+
val progress = blockState.calcBreakDelta(player, world, ctx.blockPos, info.breakConfig)
820+
info.vanillaInstantBreakable = progress >= 1 && info.swapInfo.canCompleteBreak
821+
if (progress >= info.getBreakThreshold() && info.swapInfo.canCompleteBreak) {
843822
onBlockBreak(info)
844823
if (!info.vanillaInstantBreakable) breakCooldown = info.breakConfig.breakDelay
845824
} else {
@@ -859,7 +838,7 @@ object BreakManager : RequestHandler<BreakRequest>(
859838

860839
info.startBreakPacket(world, interaction)
861840

862-
if (info.type == Secondary || (!info.vanillaInstantBreakable && breakDelta >= info.breakConfig.breakThreshold)) {
841+
if (info.type == Secondary || (!info.vanillaInstantBreakable && progress >= info.breakConfig.breakThreshold)) {
863842
info.stopBreakPacket(world, interaction)
864843
}
865844

src/main/kotlin/com/lambda/interaction/request/breaking/RebreakManager.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ object RebreakManager {
3535
var rebreak: BreakInfo? = null
3636

3737
init {
38-
listen<TickEvent.Post>(priority = Int.MIN_VALUE) {
38+
listen<TickEvent.Post>(priority = Int.MIN_VALUE + 1) {
3939
rebreak?.run {
4040
if (!progressedThisTick) {
4141
breakingTicks++
@@ -64,7 +64,7 @@ object RebreakManager {
6464
rebreak = null
6565
}
6666

67-
fun couldRebreak(info: BreakInfo, player: ClientPlayerEntity, world: BlockView) =
67+
fun getRebreakPotential(info: BreakInfo, player: ClientPlayerEntity, world: BlockView) =
6868
rebreak?.let { reBreak ->
6969
val stack = if (info.breakConfig.swapMode.isEnabled())
7070
info.swapStack
@@ -89,7 +89,8 @@ object RebreakManager {
8989

9090
val context = reBreak.context
9191
val breakDelta = context.cachedState.calcBreakDelta(player, world, context.blockPos, reBreak.breakConfig)
92-
return@runSafe if ((reBreak.breakingTicks - reBreak.breakConfig.fudgeFactor) * breakDelta >= reBreak.breakConfig.breakThreshold) {
92+
val breakTicks = reBreak.breakingTicks - reBreak.breakConfig.fudgeFactor
93+
return@runSafe if (breakTicks * breakDelta >= reBreak.getBreakThreshold() && reBreak.swapInfo.canCompleteBreak) {
9394
if (reBreak.breakConfig.breakConfirmation != BreakConfig.BreakConfirmationMode.AwaitThenBreak) {
9495
destroyBlock(reBreak)
9596
}
@@ -109,6 +110,6 @@ object RebreakManager {
109110
PartialProgress,
110111
None;
111112

112-
fun isPossible() = this == Instant || this == PartialProgress
113+
fun isPossible() = this != None
113114
}
114115
}

0 commit comments

Comments
 (0)