Skip to content

Commit 09b96de

Browse files
committed
move almost all swap data and logic into the SwapInfo class, and add new serverSwap setting.
this seems to be on par with 1.20.4 consistency but there are improvements to be made regarding speed
1 parent 4f0b4e4 commit 09b96de

File tree

6 files changed

+123
-73
lines changed

6 files changed

+123
-73
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", 1, 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/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: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ 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
2223
import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
2324
import com.lambda.util.Describable
2425
import com.lambda.util.NamedEnum
@@ -31,7 +32,6 @@ import net.minecraft.entity.player.PlayerEntity
3132
import net.minecraft.item.ItemStack
3233
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
3334
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action
34-
import net.minecraft.world.BlockView
3535

3636
data class BreakInfo(
3737
override var context: BreakContext,
@@ -44,10 +44,9 @@ data class BreakInfo(
4444

4545
// Pre Processing
4646
var shouldProgress = false
47-
var couldReBreak by OneSetPerTick(value = RebreakManager.RebreakPotential.None, throwOnLimitBreach = true)
48-
var shouldSwap by OneSetPerTick(value = false, throwOnLimitBreach = true)
47+
var rebreakPotential by OneSetPerTick(value = RebreakManager.RebreakPotential.None, throwOnLimitBreach = true)
48+
var swapInfo by OneSetPerTick(value = SwapInfo.EMPTY, throwOnLimitBreach = true)
4949
var swapStack: ItemStack by OneSetPerTick(ItemStack.EMPTY, true)
50-
var minSwapTicks by OneSetPerTick(0, true)
5150

5251
// BreakInfo Specific
5352
var updatedThisTick by OneSetPerTick(false, resetAfterTick = true).apply { set(true) }
@@ -60,7 +59,7 @@ data class BreakInfo(
6059
var breakingTicks by OneSetPerTick(0, true)
6160
var soundsCooldown by OneSetPerTick(0f, true)
6261
var vanillaInstantBreakable = false
63-
val rebreakable get() = !vanillaInstantBreakable && type == BreakType.Primary
62+
val rebreakable get() = !vanillaInstantBreakable && type == Primary
6463

6564
enum class BreakType(
6665
override val displayName: String,
@@ -70,12 +69,6 @@ data class BreakInfo(
7069
Secondary("Secondary", "A second block broken at the same time (when double‑break is enabled)."),
7170
RedundantSecondary("Redundant Secondary", "A previously started secondary break that’s now ignored/monitored only (no new actions)."),
7271
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-
}
7972
}
8073

8174
// Post Processing
@@ -113,20 +106,6 @@ data class BreakInfo(
113106
item = null
114107
}
115108

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-
130109
fun setBreakingTextureStage(
131110
player: ClientPlayerEntity,
132111
world: ClientWorld,
@@ -144,7 +123,11 @@ data class BreakInfo(
144123
return if (progress > 0.0f) (progress * 10.0f).toInt().coerceAtMost(9) else -1
145124
}
146125

147-
fun getBreakThreshold() = type.getBreakThreshold(breakConfig)
126+
fun getBreakThreshold() =
127+
when (type) {
128+
Primary -> breakConfig.breakThreshold
129+
else -> 1.0f
130+
}
148131

149132
fun startBreakPacket(world: ClientWorld, interaction: ClientPlayerInteractionManager) =
150133
breakPacket(Action.START_DESTROY_BLOCK, world, interaction)

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

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

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

173174
listen<TickEvent.Post>(priority = Int.MIN_VALUE) {
174175
if (!swappedThisTick) {
175-
swappedStack = player.mainHandStack
176+
currentStack = player.mainHandStack
176177
}
177178
swappedThisTick = false
178179
heldTicks++
@@ -416,12 +417,12 @@ object BreakManager : RequestHandler<BreakRequest>(
416417
infos.forEach { it.updatePreProcessing(player, world) }
417418

418419
infos.firstOrNull()?.let { info ->
419-
infos.lastOrNull { it.shouldSwap && it.shouldProgress }?.let { last ->
420-
val minSwapTicks = max(info.minSwapTicks, last.minSwapTicks)
420+
infos.lastOrNull { it.swapInfo.swap && it.shouldProgress }?.let { last ->
421+
val minSwapTicks = max(info.swapInfo.minKeepTicks, last.swapInfo.minKeepTicks)
421422
if (!info.context.requestSwap(info.request, minSwapTicks))
422423
return false
423424
if (minSwapTicks > 0)
424-
swappedStack = info.swapStack
425+
currentStack = info.swapStack
425426
}
426427
}
427428
}
@@ -598,31 +599,8 @@ object BreakManager : RequestHandler<BreakRequest>(
598599
updatedPreProcessingThisTick = true
599600

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

628606
/**
@@ -732,8 +710,6 @@ object BreakManager : RequestHandler<BreakRequest>(
732710
config
733711
) * (info.breakingTicks - config.fudgeFactor)
734712

735-
val overBreakThreshold = progress >= info.getBreakThreshold()
736-
737713
if (config.sounds) {
738714
if (info.soundsCooldown % 4.0f == 0.0f) {
739715
val blockSoundGroup = blockState.soundGroup
@@ -760,7 +736,7 @@ object BreakManager : RequestHandler<BreakRequest>(
760736
}
761737

762738
val swing = config.swing
763-
if (overBreakThreshold && heldTicks + 1 >= info.breakConfig.fudgeFactor) {
739+
if (progress >= info.getBreakThreshold() && info.swapInfo.canCompleteBreak) {
764740
if (info.type == Primary) {
765741
onBlockBreak(info)
766742
info.stopBreakPacket(world, interaction)
@@ -786,7 +762,7 @@ object BreakManager : RequestHandler<BreakRequest>(
786762
private fun SafeContext.startBreaking(info: BreakInfo): Boolean {
787763
val ctx = info.context
788764

789-
if (info.couldReBreak.isPossible()) {
765+
if (info.rebreakPotential.isPossible()) {
790766
when (val rebreakResult = RebreakManager.handleUpdate(info.context, info.request)) {
791767
is RebreakResult.StillBreaking -> {
792768
primaryBreak = rebreakResult.breakInfo.apply {
@@ -827,14 +803,13 @@ object BreakManager : RequestHandler<BreakRequest>(
827803
lastPosStarted = ctx.blockPos
828804

829805
val blockState = blockState(ctx.blockPos)
830-
val notEmpty = blockState.isNotEmpty
831-
if (notEmpty && info.breakingTicks == 0) {
806+
if (info.breakingTicks == 0) {
832807
blockState.onBlockBreakStart(world, ctx.blockPos, player)
833808
}
834809

835-
val breakDelta = blockState.calcBreakDelta(player, world, ctx.blockPos, info.breakConfig)
836-
info.vanillaInstantBreakable = breakDelta >= 1
837-
if (notEmpty && breakDelta >= info.getBreakThreshold() && heldTicks + 1 >= info.breakConfig.fudgeFactor) {
810+
val progress = blockState.calcBreakDelta(player, world, ctx.blockPos, info.breakConfig)
811+
info.vanillaInstantBreakable = progress >= 1 && info.swapInfo.canCompleteBreak
812+
if (progress >= info.getBreakThreshold() && info.swapInfo.canCompleteBreak) {
838813
onBlockBreak(info)
839814
if (!info.vanillaInstantBreakable) breakCooldown = info.breakConfig.breakDelay
840815
} else {
@@ -854,7 +829,7 @@ object BreakManager : RequestHandler<BreakRequest>(
854829

855830
info.startBreakPacket(world, interaction)
856831

857-
if (info.type == Secondary || (!info.vanillaInstantBreakable && breakDelta >= info.breakConfig.breakThreshold)) {
832+
if (info.type == Secondary || (!info.vanillaInstantBreakable && progress >= info.breakConfig.breakThreshold)) {
858833
info.stopBreakPacket(world, interaction)
859834
}
860835

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -109,6 +109,6 @@ object RebreakManager {
109109
PartialProgress,
110110
None;
111111

112-
fun isPossible() = this == Instant || this == PartialProgress
112+
fun isPossible() = this != None
113113
}
114114
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.interaction.request.breaking
19+
20+
import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Primary
21+
import com.lambda.interaction.request.breaking.BreakManager.currentStack
22+
import com.lambda.module.modules.client.TaskFlowModule
23+
import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
24+
import net.minecraft.client.network.ClientPlayerEntity
25+
import net.minecraft.item.ItemStack
26+
import net.minecraft.world.BlockView
27+
28+
data class SwapInfo(
29+
val breakConfig: BreakConfig = TaskFlowModule.build.breaking,
30+
val swap: Boolean = false,
31+
val minKeepTicks: Int = 0,
32+
) {
33+
val canCompleteBreak
34+
get() = BreakManager.heldTicks >= breakConfig.serverSwapTicks
35+
36+
companion object {
37+
val EMPTY = SwapInfo()
38+
39+
fun getSwapInfo(
40+
info: BreakInfo,
41+
player: ClientPlayerEntity,
42+
world: BlockView
43+
): SwapInfo = with(info) {
44+
val breakDelta = context.cachedState
45+
.calcItemBlockBreakingDelta(player, world, context.blockPos, swapStack)
46+
val breakDeltaNoEfficiency = context.cachedState
47+
.calcItemBlockBreakingDelta(player, world, context.blockPos, swapStack, ignoreEfficiency = true)
48+
val breakTicks = (if (rebreakPotential.isPossible()) RebreakManager.rebreak?.breakingTicks
49+
?: throw IllegalStateException("Rebreak was null when rebreak was considered possible")
50+
else breakingTicks).let {
51+
// Plus one as this is calculated before this ticks progress is calculated and the breakingTicks are incremented
52+
(it + 1) - breakConfig.fudgeFactor
53+
}
54+
val threshold = getBreakThreshold()
55+
56+
val minKeepTicks = run {
57+
val swapTickProgress = breakDelta * (breakTicks + breakConfig.serverSwapTicks)
58+
val withinPrimarySwapRange = swapTickProgress >= threshold
59+
if (type == Primary) {
60+
val withoutEfficiency = breakDeltaNoEfficiency * breakTicks >= threshold
61+
if (withinPrimarySwapRange && !withoutEfficiency && swapStack.heldTicks < breakConfig.serverSwapTicks) 1
62+
else 0
63+
} else {
64+
val withinSecondarySwapRange = withinPrimarySwapRange ||
65+
(breakDelta * breakTicks >= threshold && breakDelta * (breakTicks - 1) < threshold)
66+
if (withinSecondarySwapRange) 1 else 0
67+
}
68+
}
69+
70+
val swapAtEnd = breakDelta * breakTicks >= threshold || minKeepTicks > 0
71+
72+
val swap = if (rebreakPotential == RebreakManager.RebreakPotential.Instant)
73+
breakConfig.swapMode.isEnabled()
74+
else when (breakConfig.swapMode) {
75+
BreakConfig.SwapMode.None -> false
76+
BreakConfig.SwapMode.Start -> !breaking
77+
BreakConfig.SwapMode.End -> swapAtEnd
78+
BreakConfig.SwapMode.StartAndEnd -> !breaking || swapAtEnd
79+
BreakConfig.SwapMode.Constant -> true
80+
}
81+
82+
return SwapInfo(breakConfig, swap, minKeepTicks)
83+
}
84+
85+
private val ItemStack.heldTicks
86+
get() = if (currentStack == this)
87+
BreakManager.heldTicks
88+
else 0
89+
}
90+
}

0 commit comments

Comments
 (0)