From 8c19d1092facdf4d4384f931183795342b09fa94 Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+mukjepscarlet@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:24:41 +0800 Subject: [PATCH 1/3] Add Tracker --- .../net/ccbluex/liquidbounce/LiquidBounce.kt | 2 + .../combat/velocity/mode/VelocityIntave.kt | 30 ++----- .../combat/velocity/mode/VelocityJumpReset.kt | 29 +------ .../combat/velocity/mode/VelocityLag.kt | 6 ++ .../network/LocalPlayerFallDamageTracker.kt | 86 +++++++++++++++++++ .../utils/network/PacketExtensions.kt | 19 ++++ 6 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt b/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt index 34599ec07a0..ddd60b08339 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt @@ -87,6 +87,7 @@ import net.ccbluex.liquidbounce.utils.inventory.InventoryManager import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention.FIRST_PRIORITY import net.ccbluex.liquidbounce.utils.kotlin.Minecraft import net.ccbluex.liquidbounce.utils.mappings.EnvironmentRemapper +import net.ccbluex.liquidbounce.utils.network.LocalPlayerFallDamageTracker import net.minecraft.resources.Identifier import net.minecraft.server.packs.resources.PreparableReloadListener import net.minecraft.server.packs.resources.ReloadableResourceManager @@ -272,6 +273,7 @@ object LiquidBounce : EventListener { // Utility managers RotationManager BlinkManager + LocalPlayerFallDamageTracker InteractionTracker CombatManager FriendManager diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityIntave.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityIntave.kt index bd3d54dedd4..ce02275b405 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityIntave.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityIntave.kt @@ -22,12 +22,11 @@ import net.ccbluex.liquidbounce.config.types.group.ToggleableValueGroup import net.ccbluex.liquidbounce.event.EventListener import net.ccbluex.liquidbounce.event.events.AttackEntityEvent import net.ccbluex.liquidbounce.event.events.MovementInputEvent -import net.ccbluex.liquidbounce.event.events.PacketEvent import net.ccbluex.liquidbounce.event.handler -import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug import net.ccbluex.liquidbounce.utils.math.multiply +import net.ccbluex.liquidbounce.utils.network.LocalPlayerFallDamageTracker import net.minecraft.client.gui.screens.inventory.InventoryScreen -import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket +import kotlin.random.Random object VelocityIntave : VelocityMode("Intave") { @@ -68,13 +67,14 @@ object VelocityIntave : VelocityMode("Intave") { } private val randomize = tree(Randomize()) - private var isFallDamage = false private var currentDelay = 0 private var delayCounter = 0 @Suppress("unused") private val tickJumpHandler = handler { - val shouldJump = Math.random() * 100 < chance && player.hurtTime > 5 && !isFallDamage + val shouldJump = Random.nextInt(100) < chance + && player.hurtTime > 5 + && !LocalPlayerFallDamageTracker.isCurrentFallDamage val canJump = player.onGround() && mc.screen !is InventoryScreen val shouldFinallyJump = shouldJump && canJump @@ -91,26 +91,6 @@ object VelocityIntave : VelocityMode("Intave") { } } - @Suppress("unused") - private val packetHandler = handler { event -> - val packet = event.packet - - if (packet is ClientboundSetEntityMotionPacket && packet.id == player.id) { - val velocityX = packet.movement.x - val velocityY = packet.movement.y - val velocityZ = packet.movement.z - - // Check if the player is taking fall damage - // We set this on every packet, because if the player gets hit afterward, - // we will know that from the velocity. - isFallDamage = velocityX == 0.0 && velocityZ == 0.0 && velocityY < 0 - ModuleDebug.debugParameter(this, "VelocityX", velocityX) - ModuleDebug.debugParameter(this, "VelocityY", velocityY) - ModuleDebug.debugParameter(this, "VelocityZ", velocityZ) - ModuleDebug.debugParameter(this, "IsFallDamage", isFallDamage) - } - } - } init { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityJumpReset.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityJumpReset.kt index c2a823980d7..7d55076cffb 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityJumpReset.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityJumpReset.kt @@ -20,10 +20,9 @@ package net.ccbluex.liquidbounce.features.module.modules.combat.velocity.mode import net.ccbluex.liquidbounce.config.types.group.ToggleableValueGroup import net.ccbluex.liquidbounce.event.events.MovementInputEvent -import net.ccbluex.liquidbounce.event.events.PacketEvent import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug -import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket +import net.ccbluex.liquidbounce.utils.network.LocalPlayerFallDamageTracker import kotlin.random.Random /** @@ -47,8 +46,6 @@ internal object VelocityJumpReset : VelocityMode("JumpReset") { } private var limitUntilJump = 0 - private var isFallDamage = false - private var hitsUntilJump = JumpByReceivedHits.hitsUntilJump.random() private var ticksUntilJump = JumpByDelay.ticksUntilJump.random() @@ -56,8 +53,8 @@ internal object VelocityJumpReset : VelocityMode("JumpReset") { private val movementInputHandler = handler { event -> // To be able to alter velocity when receiving knockback, player must be sprinting. if (player.hurtTime != 9 || !player.onGround() || !player.isSprinting || - isFallDamage || !isCooldownOver() || chance != 100f && Random.nextInt(100) > chance) - { + LocalPlayerFallDamageTracker.isCurrentFallDamage || !isCooldownOver() || + chance != 100f && Random.nextInt(100) > chance) { updateLimit() return@handler } @@ -69,26 +66,6 @@ internal object VelocityJumpReset : VelocityMode("JumpReset") { ticksUntilJump = JumpByDelay.ticksUntilJump.random() } - @Suppress("unused") - private val packetHandler = handler { event -> - val packet = event.packet - - if (packet is ClientboundSetEntityMotionPacket && packet.id == player.id) { - val velocityX = packet.movement.x - val velocityY = packet.movement.y - val velocityZ = packet.movement.z - - // Check if the player is taking fall damage - // We set this on every packet, because if the player gets hit afterward, - // we will know that from the velocity. - isFallDamage = velocityX == 0.0 && velocityZ == 0.0 && velocityY < 0 - ModuleDebug.debugParameter(this, "VelocityX", velocityX) - ModuleDebug.debugParameter(this, "VelocityY", velocityY) - ModuleDebug.debugParameter(this, "VelocityZ", velocityZ) - ModuleDebug.debugParameter(this, "IsFallDamage", isFallDamage) - } - } - private fun isCooldownOver(): Boolean { ModuleDebug.debugParameter(this, "HitsUntilJump", hitsUntilJump) ModuleDebug.debugParameter(this, "UntilJump", ticksUntilJump) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityLag.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityLag.kt index 2def80941c8..0626030ea89 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityLag.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityLag.kt @@ -30,6 +30,7 @@ import net.ccbluex.liquidbounce.event.sequenceHandler import net.ccbluex.liquidbounce.event.waitTicks import net.ccbluex.liquidbounce.features.blink.BlinkManager import net.ccbluex.liquidbounce.features.blink.BlinkManager.Action +import net.ccbluex.liquidbounce.utils.network.LocalPlayerFallDamageTracker import net.ccbluex.liquidbounce.utils.network.isLocalPlayerVelocity import net.minecraft.network.protocol.common.ClientboundKeepAlivePacket @@ -89,6 +90,11 @@ internal object VelocityLag : VelocityMode("Lag") { private val movementInputHandler = handler { event -> // To be able to alter velocity when receiving knockback, player must be sprinting. if (jumpReset && shouldJump && player.onGround() && player.isSprinting) { + if (LocalPlayerFallDamageTracker.isCurrentFallDamage) { + shouldJump = false + return@handler + } + event.jump = true shouldJump = false } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt new file mode 100644 index 00000000000..6520149b8a8 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt @@ -0,0 +1,86 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2026 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + */ + +package net.ccbluex.liquidbounce.utils.network + +import net.ccbluex.liquidbounce.event.EventListener +import net.ccbluex.liquidbounce.event.events.GameTickEvent +import net.ccbluex.liquidbounce.event.events.PacketEvent +import net.ccbluex.liquidbounce.event.events.TransferOrigin +import net.ccbluex.liquidbounce.event.events.WorldChangeEvent +import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.utils.client.mc + +/** + * Tracks whether the local player's current client-side hurt window comes from vanilla fall damage. + * + * The server sends `net.minecraft.network.protocol.game.ClientboundDamageEventPacket` for + * `minecraft:fall` before the later `hurtMarked`-driven motion sync, while the client keeps the + * currently active damage window in `hurtTime`. + * + * @see net.minecraft.world.entity.LivingEntity#hurt + * @see net.minecraft.server.level.ServerLevel#broadcastDamageEvent + * @see net.minecraft.world.entity.LivingEntity#handleDamageEvent + * @see net.minecraft.server.level.ServerEntity#sendChanges + */ +object LocalPlayerFallDamageTracker : EventListener { + + @Volatile + private var currentDamageState = DamageState.NONE + + val isCurrentFallDamage: Boolean + get() = currentDamageState == DamageState.FALL && (mc.player?.hurtTime ?: 0) > 0 + + private fun reset() { + currentDamageState = DamageState.NONE + } + + @Suppress("unused") + private val packetHandler = handler { event -> + if (event.origin != TransferOrigin.INCOMING) { + return@handler + } + + val packet = event.packet + if (!packet.isLocalPlayerDamage()) { + return@handler + } + + currentDamageState = if (packet.isLocalPlayerFallDamageEvent()) DamageState.FALL else DamageState.OTHER + } + + @Suppress("unused") + private val gameTickHandler = handler { + if ((mc.player?.hurtTime ?: 0) <= 0) { + reset() + } + } + + @Suppress("unused") + private val worldChangeHandler = handler { + reset() + } + + private enum class DamageState { + NONE, + FALL, + OTHER, + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt index 33f552f4710..4f65303eb3b 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt @@ -31,7 +31,10 @@ import net.minecraft.network.protocol.game.ServerboundContainerClosePacket import net.minecraft.network.protocol.game.ServerboundContainerSlotStateChangedPacket import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket import net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket +import net.minecraft.world.damagesource.DamageTypes import net.minecraft.world.phys.Vec3 +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun Packet<*>?.isC2SContainerPacket() = this is ServerboundContainerClickPacket || @@ -40,7 +43,12 @@ fun Packet<*>?.isC2SContainerPacket() = this is ServerboundContainerSlotStateChangedPacket || this is ServerboundContainerClosePacket +@OptIn(ExperimentalContracts::class) fun Packet<*>?.isLocalPlayerDamage(): Boolean { + contract { + returns(true) implies (this@isLocalPlayerDamage is ClientboundDamageEventPacket) + } + return this is ClientboundDamageEventPacket && this.entityId == mc.player?.id } @@ -62,3 +70,14 @@ fun ClientboundSetEntityMotionPacket.isMovementYFallDamage(): Boolean { return this.movement.y.toRawBits() == (if (isNewerThanOrEquals1_21_9) -4633060179779189496L else -4633068976409115392L) } + +@OptIn(ExperimentalContracts::class) +fun Packet<*>?.isLocalPlayerFallDamageEvent(): Boolean { + contract { + returns(true) implies (this@isLocalPlayerFallDamageEvent is ClientboundDamageEventPacket) + } + + return this is ClientboundDamageEventPacket + && this.entityId == mc.player?.id + && this.sourceType.`is`(DamageTypes.FALL) +} From dfbea0e7f94b75530d32ca72280610e6d9d0fae3 Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+mukjepscarlet@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:29:26 +0800 Subject: [PATCH 2/3] Refactor --- .../modules/combat/velocity/mode/VelocityReduce.kt | 4 +--- .../utils/network/LocalPlayerFallDamageTracker.kt | 14 ++++++++------ .../liquidbounce/utils/network/PacketExtensions.kt | 12 ------------ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityReduce.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityReduce.kt index 974ae299ec5..c7e7c472e26 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityReduce.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/velocity/mode/VelocityReduce.kt @@ -247,9 +247,7 @@ object VelocityReduce : VelocityMode("Reduce") { if (packet.isLocalPlayerDamage()) { receiveDamage = true - } - - if (packet.isLocalPlayerVelocity() && receiveDamage) { + } else if (packet.isLocalPlayerVelocity() && receiveDamage) { receiveDamage = false if (player.isUsingItem || ModuleScaffold.running) return@handler diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt index 6520149b8a8..f2d6ae966ad 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt @@ -26,12 +26,13 @@ import net.ccbluex.liquidbounce.event.events.TransferOrigin import net.ccbluex.liquidbounce.event.events.WorldChangeEvent import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.utils.client.mc +import net.minecraft.world.damagesource.DamageTypes /** * Tracks whether the local player's current client-side hurt window comes from vanilla fall damage. * - * The server sends `net.minecraft.network.protocol.game.ClientboundDamageEventPacket` for - * `minecraft:fall` before the later `hurtMarked`-driven motion sync, while the client keeps the + * The server sends [net.minecraft.network.protocol.game.ClientboundDamageEventPacket] for + * [DamageTypes.FALL] before the later `hurtMarked`-driven motion sync, while the client keeps the * currently active damage window in `hurtTime`. * * @see net.minecraft.world.entity.LivingEntity#hurt @@ -58,11 +59,12 @@ object LocalPlayerFallDamageTracker : EventListener { } val packet = event.packet - if (!packet.isLocalPlayerDamage()) { - return@handler + when { + packet.isLocalPlayerDamage() -> { + currentDamageState = + if (packet.sourceType.`is`(DamageTypes.FALL)) DamageState.FALL else DamageState.OTHER + } } - - currentDamageState = if (packet.isLocalPlayerFallDamageEvent()) DamageState.FALL else DamageState.OTHER } @Suppress("unused") diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt index 4f65303eb3b..a38db51076c 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/PacketExtensions.kt @@ -31,7 +31,6 @@ import net.minecraft.network.protocol.game.ServerboundContainerClosePacket import net.minecraft.network.protocol.game.ServerboundContainerSlotStateChangedPacket import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket import net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket -import net.minecraft.world.damagesource.DamageTypes import net.minecraft.world.phys.Vec3 import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -70,14 +69,3 @@ fun ClientboundSetEntityMotionPacket.isMovementYFallDamage(): Boolean { return this.movement.y.toRawBits() == (if (isNewerThanOrEquals1_21_9) -4633060179779189496L else -4633068976409115392L) } - -@OptIn(ExperimentalContracts::class) -fun Packet<*>?.isLocalPlayerFallDamageEvent(): Boolean { - contract { - returns(true) implies (this@isLocalPlayerFallDamageEvent is ClientboundDamageEventPacket) - } - - return this is ClientboundDamageEventPacket - && this.entityId == mc.player?.id - && this.sourceType.`is`(DamageTypes.FALL) -} From d9f79ba35cf7296f1923cb37fe7904ca487213b1 Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+mukjepscarlet@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:39:12 +0800 Subject: [PATCH 3/3] Motion check --- .../network/LocalPlayerFallDamageTracker.kt | 67 ++++++++++++++----- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt index f2d6ae966ad..18be1f8fe06 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/network/LocalPlayerFallDamageTracker.kt @@ -26,51 +26,81 @@ import net.ccbluex.liquidbounce.event.events.TransferOrigin import net.ccbluex.liquidbounce.event.events.WorldChangeEvent import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.utils.client.mc +import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention.READ_FINAL_STATE +import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket import net.minecraft.world.damagesource.DamageTypes +import java.util.concurrent.atomic.AtomicInteger /** - * Tracks whether the local player's current client-side hurt window comes from vanilla fall damage. + * Tracks whether the local player's current knockback/hurt cycle was caused by vanilla fall damage. * - * The server sends [net.minecraft.network.protocol.game.ClientboundDamageEventPacket] for - * [DamageTypes.FALL] before the later `hurtMarked`-driven motion sync, while the client keeps the - * currently active damage window in `hurtTime`. + * Vanilla sends the local player's fall [net.minecraft.network.protocol.game.ClientboundDamageEventPacket] + * before the matching `hurtMarked`-driven [ClientboundSetEntityMotionPacket]. We only confirm fall damage + * after seeing that following motion packet. * * @see net.minecraft.world.entity.LivingEntity#hurt * @see net.minecraft.server.level.ServerLevel#broadcastDamageEvent - * @see net.minecraft.world.entity.LivingEntity#handleDamageEvent * @see net.minecraft.server.level.ServerEntity#sendChanges */ object LocalPlayerFallDamageTracker : EventListener { @Volatile - private var currentDamageState = DamageState.NONE + private var state = State.NONE + + private val activeTicks = AtomicInteger() + private val pendingTicks = AtomicInteger() val isCurrentFallDamage: Boolean - get() = currentDamageState == DamageState.FALL && (mc.player?.hurtTime ?: 0) > 0 + get() = state == State.CONFIRMED_FALL_DAMAGE && activeTicks.get() > 0 private fun reset() { - currentDamageState = DamageState.NONE + state = State.NONE + activeTicks.set(0) + pendingTicks.set(0) } @Suppress("unused") - private val packetHandler = handler { event -> - if (event.origin != TransferOrigin.INCOMING) { + private val packetHandler = handler(READ_FINAL_STATE) { event -> + if (event.origin != TransferOrigin.INCOMING || !event.original || event.isCancelled) { return@handler } val packet = event.packet when { packet.isLocalPlayerDamage() -> { - currentDamageState = - if (packet.sourceType.`is`(DamageTypes.FALL)) DamageState.FALL else DamageState.OTHER + activeTicks.set(0) + pendingTicks.set(0) + state = if (packet.sourceType.`is`(DamageTypes.FALL)) State.AWAITING_FALL_DAMAGE_MOTION else State.NONE + } + + packet is ClientboundSetEntityMotionPacket && packet.id == mc.player?.id -> { + if (state == State.AWAITING_FALL_DAMAGE_MOTION) { + state = State.CONFIRMED_FALL_DAMAGE + activeTicks.set(DAMAGE_WINDOW_TICKS) + } } } } @Suppress("unused") private val gameTickHandler = handler { - if ((mc.player?.hurtTime ?: 0) <= 0) { - reset() + when (state) { + State.AWAITING_FALL_DAMAGE_MOTION -> { + if (pendingTicks.incrementAndGet() > FOLLOWING_MOTION_TIMEOUT_TICKS) { + reset() + } + } + + State.CONFIRMED_FALL_DAMAGE -> { + val remainingTicks = activeTicks.updateAndGet { ticks -> + if (ticks > 0) ticks - 1 else 0 + } + if (remainingTicks <= 0) { + reset() + } + } + + State.NONE -> {} } } @@ -79,10 +109,13 @@ object LocalPlayerFallDamageTracker : EventListener { reset() } - private enum class DamageState { + private enum class State { NONE, - FALL, - OTHER, + AWAITING_FALL_DAMAGE_MOTION, + CONFIRMED_FALL_DAMAGE, } + private const val FOLLOWING_MOTION_TIMEOUT_TICKS = 1 + private const val DAMAGE_WINDOW_TICKS = 10 + }