From 9315d3f0805a3cb6122e6dafb78a5043ba972e69 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Mon, 10 Nov 2025 21:05:32 +0000 Subject: [PATCH 1/8] break manager documentation --- .../request/breaking/BreakManager.kt | 187 ++++++++++-------- 1 file changed, 109 insertions(+), 78 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt index 3ef12f5ca..a7e49aeb9 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt @@ -50,18 +50,25 @@ import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Rebreak import com.lambda.interaction.request.breaking.BreakInfo.BreakType.RedundantSecondary import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.request.breaking.BreakManager.abandonedBreak import com.lambda.interaction.request.breaking.BreakManager.activeInfos import com.lambda.interaction.request.breaking.BreakManager.activeRequest import com.lambda.interaction.request.breaking.BreakManager.breakInfos import com.lambda.interaction.request.breaking.BreakManager.breaks import com.lambda.interaction.request.breaking.BreakManager.canAccept import com.lambda.interaction.request.breaking.BreakManager.checkForCancels +import com.lambda.interaction.request.breaking.BreakManager.handlePreProcessing +import com.lambda.interaction.request.breaking.BreakManager.hotbarRequest import com.lambda.interaction.request.breaking.BreakManager.initNewBreak import com.lambda.interaction.request.breaking.BreakManager.maxBreaksThisTick +import com.lambda.interaction.request.breaking.BreakManager.nullify +import com.lambda.interaction.request.breaking.BreakManager.populateFrom import com.lambda.interaction.request.breaking.BreakManager.processNewBreak import com.lambda.interaction.request.breaking.BreakManager.processRequest +import com.lambda.interaction.request.breaking.BreakManager.rotationRequest import com.lambda.interaction.request.breaking.BreakManager.simulateAbandoned import com.lambda.interaction.request.breaking.BreakManager.updateBreakProgress +import com.lambda.interaction.request.breaking.BreakManager.updatePreProcessing import com.lambda.interaction.request.breaking.BrokenBlockHandler.destroyBlock import com.lambda.interaction.request.breaking.BrokenBlockHandler.pendingActions import com.lambda.interaction.request.breaking.BrokenBlockHandler.setPendingConfigs @@ -96,6 +103,14 @@ import net.minecraft.util.math.Box import kotlin.math.max import kotlin.math.min +/** + * This manager is responsible for breaking blocks in the most efficient manner possible. It can be accessed + * from anywhere through a [BreakRequest], although it is not designed in the image of thread safety. + * + * If configured with the right options enabled, this manager can break two blocks simultaneously, even if the two breaks come from + * different requests. Each break will be handled using its own config, and just like the other managers, priority is a first-come, first-served + * style system. + */ object BreakManager : RequestHandler( 0, TickEvent.Pre, @@ -291,8 +306,8 @@ object BreakManager : RequestHandler( } /** - * Attempts to accept and process the request, if there is not already an [activeRequest]. - * If the request is processed and all breaks completed, the [activeRequest] is cleared. + * Attempts to accept and process the request, if there is not already an [activeRequest] and the + * [BreakRequest.contexts] collection is not empty. * * @see processRequest */ @@ -304,13 +319,12 @@ object BreakManager : RequestHandler( } /** - * If the request is fresh, local variables are populated through the [processRequest] method. - * It then attempts to perform as many breaks within this tick as possible from the [instantBreaks] collection. - * The [breakInfos] are then updated if the dependencies are present, E.G. if the user has rotations enabled, - * or the player needs to swap to a different hotbar slot. + * Handles populating the manager, updating break progresses, and clearing the active request + * when all breaks are complete. * - * @see performInstantBreaks + * @see populateFrom * @see processNewBreak + * @see handlePreProcessing * @see updateBreakProgress */ private fun SafeContext.processRequest(request: BreakRequest?) { @@ -318,16 +332,14 @@ object BreakManager : RequestHandler( request?.let { request -> logger.debug("Processing request", request) - if (request.fresh) request.runSafeAutomated { populateFrom(request) } + if (request.fresh) populateFrom(request) } var noNew: Boolean var noProgression: Boolean while (true) { - noNew = request?.let { - !request.runSafeAutomated { processNewBreak(request) } - } != false + noNew = request?.let { !processNewBreak(request) } != false // Reversed so that the breaking order feels natural to the user as the primary break is always the // last break to be started @@ -340,9 +352,7 @@ object BreakManager : RequestHandler( if (isEmpty()) true else { forEach { breakInfo -> - breakInfo.request.runSafeAutomated { - updateBreakProgress(breakInfo) - } + updateBreakProgress(breakInfo) } false } @@ -362,13 +372,14 @@ object BreakManager : RequestHandler( /** * Filters the requests [BreakContext]s, and iterates over the [breakInfos] collection looking for matches - * in positions. If a match is found, the [BreakInfo] is updated with the new context. Otherwise, the break is cancelled. - * The [instantBreaks] and [breaks] collections are then populated with the new appropriate contexts, and the [maxBreaksThisTick] + * in positions. If a match is found, the [BreakInfo] is updated with the new context. + * The [breaks] collection is then populated with the new appropriate contexts, and the [maxBreaksThisTick] * value is set. * * @see canAccept + * @see BreakInfo.updateInfo */ - private fun AutomatedSafeContext.populateFrom(request: BreakRequest) { + private fun SafeContext.populateFrom(request: BreakRequest) = request.runSafeAutomated { logger.debug("Populating from request", request) // Sanitize the new breaks @@ -381,24 +392,25 @@ object BreakManager : RequestHandler( breakInfos .filterNotNull() .forEach { info -> - newBreaks - .find { ctx -> ctx.blockPos == info.context.blockPos } - ?.let { ctx -> - if ((!info.updatedThisTick || info.type == RedundantSecondary) || info.abandoned) { - logger.debug("Updating info", info, ctx) - if (info.type == RedundantSecondary) - info.request.onStart?.invoke(this, info.context.blockPos) - else if (info.abandoned) { - info.abandoned = false - info.request.onStart?.invoke(this, info.context.blockPos) - } else - info.request.onUpdate?.invoke(this, info.context.blockPos) - - info.updateInfo(ctx, request) - } - newBreaks.remove(ctx) - return@forEach + val ctx = newBreaks.find { ctx -> + ctx.blockPos == info.context.blockPos + } ?: return@forEach + + newBreaks.remove(ctx) + + if (info.updatedThisTick && info.type != RedundantSecondary && !info.abandoned) return@forEach + + logger.debug("Updating info", info, ctx) + when { + info.type == RedundantSecondary -> info.request.onStart?.invoke(this, info.context.blockPos) + info.abandoned -> { + info.abandoned = false + info.request.onStart?.invoke(this, info.context.blockPos) } + else -> info.request.onUpdate?.invoke(this, info.context.blockPos) + } + + info.updateInfo(ctx, request) } breaks = newBreaks @@ -428,52 +440,58 @@ object BreakManager : RequestHandler( return blockState.isNotEmpty && hardness != 600f && hardness != -1f } + /** + * Updates the pre-processing for [BreakInfo] elements within [activeInfos] as long as they've been updated this tick. + * This method also populates [rotationRequest] and [hotbarRequest]. + * + * @see updatePreProcessing + */ private fun SafeContext.handlePreProcessing() { + if (activeInfos.isEmpty()) return + activeInfos .filter { it.updatedThisTick } .let { infos -> - rotationRequest = infos.firstOrNull { info -> info.breakConfig.rotateForBreak } - ?.let { info -> - val rotation = info.context.rotationRequest - logger.debug("Requesting rotation", rotation) - rotation.submit(false) - } - - if (activeInfos.isEmpty()) return + rotationRequest = infos.lastOrNull { info -> + info.breakConfig.rotateForBreak + }?.let { info -> + val rotation = info.context.rotationRequest + logger.debug("Requesting rotation", rotation) + rotation.submit(false) + } infos.forEach { it.updatePreProcessing() } - infos.firstOrNull()?.let { info -> - infos.lastOrNull { it.swapInfo.swap && it.shouldProgress }?.let { last -> - val minKeepTicks = if (info.swapInfo.longSwap || last.swapInfo.longSwap) 1 else 0 - val serverSwapTicks = max(info.breakConfig.serverSwapTicks, last.breakConfig.serverSwapTicks) - hotbarRequest = with(info) { - HotbarRequest( - context.hotbarIndex, - request, - request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks), - request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1) - ).submit(false) - } - logger.debug("Submitted hotbar request", hotbarRequest) - return - } + val first = infos.firstOrNull() ?: return@let + val last = infos.lastOrNull { it.swapInfo.swap && it.shouldProgress } ?: return@let + + val minKeepTicks = if (first.swapInfo.longSwap || last.swapInfo.longSwap) 1 else 0 + val serverSwapTicks = max(first.breakConfig.serverSwapTicks, last.breakConfig.serverSwapTicks) + + hotbarRequest = with(last) { + HotbarRequest( + context.hotbarIndex, + request, + request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks), + request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1) + ).submit(false) } + + logger.debug("Submitted hotbar request", hotbarRequest) + return } hotbarRequest = null - - return } /** * Attempts to start breaking as many [BreakContext]'s from the [breaks] collection as possible. * - * @return false if a context cannot be started or the maximum active breaks has been reached. + * @return false if a context cannot be started or the maximum active breaks have been reached. * * @see initNewBreak */ - private fun AutomatedSafeContext.processNewBreak(request: BreakRequest): Boolean { + private fun SafeContext.processNewBreak(request: BreakRequest): Boolean = request.runSafeAutomated { breaks.forEach { ctx -> if (breaksThisTick >= maxBreaksThisTick) return false if (!currentStackSelection.filterStack(player.inventory.getStack(ctx.hotbarIndex))) return@forEach @@ -488,7 +506,15 @@ object BreakManager : RequestHandler( /** * Attempts to accept the [requestCtx] into the [breakInfos]. * - * @return the [BreakInfo] or null if the break context wasn't accepted. + * If a primary [BreakInfo] is active, as long as the tick stage is valid, it is transformed + * into a secondary break, so a new primary can be initialized. This means sending a + * [net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action] with action: [net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action.STOP_DESTROY_BLOCK] + * packet to the server to start the automated breaking server side. + * + * If there is no way to keep both breaks, and the primary break hasn't been updated yet, + * the primary break is canceled. Otherwise, the break cannot be started. + * + * @return the [BreakInfo], or null, if the break context wasn't accepted. */ private fun AutomatedSafeContext.initNewBreak( requestCtx: BreakContext, @@ -521,8 +547,11 @@ object BreakManager : RequestHandler( return primaryBreak } + /** + * Simulates and updates the [abandonedBreak]. + */ private fun SafeContext.simulateAbandoned() { - // Cancelled but double breaking so requires break manager to continue the simulation + // Canceled but double breaking so requires break manager to continue the simulation val abandonedInfo = abandonedBreak ?: return abandonedInfo.request.runSafeAutomated { @@ -530,7 +559,6 @@ object BreakManager : RequestHandler( .toStructure(TargetState.Empty) .toBlueprint() .simulate() - .asSequence() .filterIsInstance() .filter { canAccept(it.context) } .sorted() @@ -540,10 +568,13 @@ object BreakManager : RequestHandler( } } + /** + * Checks if any active [BreakInfo]s are not updated this tick, and are within the timeframe of a valid tick stage. + * If so, the [BreakInfo] is either canceled, or progressed if the break is redundant. + */ private fun SafeContext.checkForCancels() { breakInfos .filterNotNull() - .asSequence() .filter { !it.updatedThisTick && tickStage in it.breakConfig.tickStageMask } .forEach { info -> if (info.type == RedundantSecondary && !info.progressedThisTick) { @@ -575,6 +606,7 @@ object BreakManager : RequestHandler( * * @see destroyBlock * @see startPending + * @see nullify */ private fun AutomatedSafeContext.onBlockBreak(info: BreakInfo) { info.request.onStop?.invoke(this, info.context.blockPos) @@ -619,9 +651,9 @@ object BreakManager : RequestHandler( /** * Attempts to cancel the break. * - * Secondary blocks are monitored by the server, and keep breaking regardless of the clients actions. - * This means that the break cannot be completely stopped, instead, it must be monitored as we can't start - * more secondary break infos until the previous has broken or its state has turned to air. + * Secondary blocks are monitored by the server and keep breaking regardless of the clients' actions. + * This means that the break cannot be completely stopped. Instead, it must be monitored as we can't start + * another secondary [BreakInfo] until the previous has broken or its state has become empty. * * If the user has [BreakConfig.unsafeCancels] enabled, the info is made redundant, and mostly ignored. * If not, the break continues. @@ -652,9 +684,6 @@ object BreakManager : RequestHandler( } } - /** - * Nullifies the break. If the block is not broken, the [BreakInfo.internalOnCancel] callback gets triggered - */ private fun BreakInfo.nullify() = when (type) { Primary, Rebreak -> primaryBreak = null @@ -662,13 +691,11 @@ object BreakManager : RequestHandler( } /** - * A modified version of the vanilla updateBlockBreakingProgress method. + * A modified version of the vanilla [net.minecraft.client.network.ClientPlayerInteractionManager.updateBlockBreakingProgress] method. * * @return if the update was successful. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.updateBlockBreakingProgress */ - private fun AutomatedSafeContext.updateBreakProgress(info: BreakInfo) { + private fun SafeContext.updateBreakProgress(info: BreakInfo): Unit = info.request.runSafeAutomated { val ctx = info.context info.progressedThisTick = true @@ -756,11 +783,9 @@ object BreakManager : RequestHandler( } /** - * A modified version of the minecraft attackBlock method. + * A modified version of the minecraft [net.minecraft.client.network.ClientPlayerInteractionManager.attackBlock] method. * * @return if the block started breaking successfully. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.attackBlock */ private fun AutomatedSafeContext.startBreaking(info: BreakInfo): Boolean { val ctx = info.context @@ -852,6 +877,9 @@ object BreakManager : RequestHandler( return true } + /** + * Wrapper method for calculating block-breaking delta. + */ context(automatedSafeContext: AutomatedSafeContext) fun BlockState.calcBreakDelta( pos: BlockPos, @@ -884,6 +912,9 @@ object BreakManager : RequestHandler( return inRange && correctMaterial } + /** + * Interpolates the give [box] using the [BreakConfig]'s animation mode. + */ private fun interpolateBox(box: Box, progress: Double, animationMode: BreakConfig.AnimationMode): Box { val boxCenter = Box(box.center, box.center) return when (animationMode) { From 4a6bd8910cf085d46d65353a0ebca62d2599a88c Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Mon, 10 Nov 2025 22:04:18 +0000 Subject: [PATCH 2/8] other breaking-related documentation --- .../interaction/request/breaking/BreakInfo.kt | 12 ++--- .../request/breaking/BreakRequest.kt | 12 +++++ .../request/breaking/BrokenBlockHandler.kt | 50 ++++++++----------- .../request/breaking/RebreakHandler.kt | 23 +++++++++ .../interaction/request/breaking/SwapInfo.kt | 10 +++- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt index b1edef638..95d93c4e6 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt @@ -37,6 +37,9 @@ import net.minecraft.item.ItemStack import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action +/** + * A data class that holds all the information required to process and continue a break. + */ data class BreakInfo( override var context: BreakContext, var type: BreakType, @@ -140,16 +143,13 @@ data class BreakInfo( } context(_: SafeContext) - fun startBreakPacket() = - breakPacket(Action.START_DESTROY_BLOCK) + fun startBreakPacket() = breakPacket(Action.START_DESTROY_BLOCK) context(_: SafeContext) - fun stopBreakPacket() = - breakPacket(Action.STOP_DESTROY_BLOCK) + fun stopBreakPacket() = breakPacket(Action.STOP_DESTROY_BLOCK) context(_: SafeContext) - fun abortBreakPacket() = - breakPacket(Action.ABORT_DESTROY_BLOCK) + fun abortBreakPacket() = breakPacket(Action.ABORT_DESTROY_BLOCK) context(safeContext: SafeContext) private fun breakPacket(action: Action) = diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt index ba7795963..e6a0bd3ca 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt @@ -24,12 +24,24 @@ import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.request.LogContext import com.lambda.interaction.request.LogContext.Companion.LogContextBuilder import com.lambda.interaction.request.Request +import com.lambda.interaction.request.breaking.BreakRequest.Companion.breakRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty import net.minecraft.entity.ItemEntity import net.minecraft.util.math.BlockPos +/** + * Contains the information necessary for initializing and continuing breaks within the [BreakManager]. + * + * The callbacks can be used to keep track of the break progress. + * + * The class has a private constructor to force use of the cleaner [BreakRequestDsl] builder. This is + * accessed through the [breakRequest] method. + * + * @param contexts A collection of [BreakContext]'s gathered from the [com.lambda.interaction.construction.simulation.BuildSimulator]. + * @param pendingInteractions A mutable, concurrent list to store the pending actions. + */ data class BreakRequest private constructor( val contexts: Collection, val pendingInteractions: MutableCollection, diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt index b85c565aa..3817cb872 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt @@ -42,8 +42,8 @@ import net.minecraft.entity.ItemEntity import net.minecraft.util.math.ChunkSectionPos /** - * This object is designed to handle blocks that have been broken client side, yet are awaiting - * confirmation from the server, and / or an item drop. + * Designed to handle blocks that are deemed broken, yet are awaiting + * confirmation from the server, and/or an item drop. * * @see BreakManager */ @@ -105,7 +105,7 @@ object BrokenBlockHandler : PostActionHandler() { if (pending.breakConfig.breakConfirmation == BreakConfirmationMode.AwaitThenBreak || (pending.type == BreakInfo.BreakType.Rebreak && !pending.breakConfig.rebreak) - ) { + ) { destroyBlock(pending) } pending.internalOnBreak() @@ -121,44 +121,38 @@ object BrokenBlockHandler : PostActionHandler() { listen(priority = Int.MIN_VALUE) { if (it.entity !is ItemEntity) return@listen - run { + val pending = pendingActions.firstOrNull { info -> matchesBlockItem(info, it.entity) } ?: rebreak?.let { info -> - return@run if (matchesBlockItem(info, it.entity)) info - else null - } - }?.let { pending -> - pending.internalOnItemDrop(it.entity) - if (pending.callbacksCompleted) { - pending.stopPending() - if (lastPosStarted == pending.context.blockPos) { - RebreakHandler.offerRebreak(pending) - } + if (matchesBlockItem(info, it.entity)) info + else return@listen + } ?: return@listen + + pending.internalOnItemDrop(it.entity) + if (pending.callbacksCompleted) { + pending.stopPending() + if (lastPosStarted == pending.context.blockPos) { + RebreakHandler.offerRebreak(pending) } - return@listen } } } /** - * A modified version of the minecraft breakBlock method. + * A modified version of the minecraft [net.minecraft.client.world.ClientWorld.breakBlock] method. * - * Performs the actions required to display break particles, sounds, texture overlay, etc. - * based on the users settings. - * - * @return if the blocks state was set or not. - * - * @see net.minecraft.client.world.ClientWorld.breakBlock + * Performs the actions required to display breaking particles, sounds, texture overlay, etc. + * based on the user's settings. */ - fun SafeContext.destroyBlock(info: BreakInfo): Boolean { + fun SafeContext.destroyBlock(info: BreakInfo) { val ctx = info.context - if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return false - if (!player.mainHandStack.canMine(ctx.cachedState, world, ctx.blockPos, player)) return false + if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return + if (!player.mainHandStack.canMine(ctx.cachedState, world, ctx.blockPos, player)) return val block = ctx.cachedState.block - if (block is OperatorBlock && !player.isCreativeLevelTwoOp) return false - if (ctx.cachedState.isEmpty) return false + if (block is OperatorBlock && !player.isCreativeLevelTwoOp) return + if (ctx.cachedState.isEmpty) return block.onBreak(world, ctx.blockPos, ctx.cachedState, player) val fluidState = fluidState(ctx.blockPos) @@ -166,7 +160,5 @@ object BrokenBlockHandler : PostActionHandler() { if (setState) block.onBroken(world, ctx.blockPos, ctx.cachedState) if (info.breakConfig.breakingTexture) info.setBreakingTextureStage(player, world, -1) - - return setState } } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt index f6f9af58d..ccec4316e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt @@ -25,10 +25,15 @@ import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.request.breaking.BreakManager.calcBreakDelta import com.lambda.interaction.request.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.request.breaking.RebreakHandler.rebreak import com.lambda.threading.runSafeAutomated import com.lambda.util.player.swingHand import net.minecraft.util.Hand +/** + * Designed to track the latest primary-broken [BreakInfo] in order to exploit a flaw in Minecraft's code that allows + * the user to break any block placed in said position using the progress from the previously broken block. + */ object RebreakHandler { var rebreak: BreakInfo? = null @@ -47,6 +52,10 @@ object RebreakHandler { } } + /** + * Tests to see if the [BreakInfo] can be accepted. If not, nothing happens. Otherwise, + * the [rebreak] is set, and the [BreakRequest.onReBreakStart] callback is invoked. + */ context(safeContext: SafeContext) fun offerRebreak(info: BreakInfo) { if (!info.rebreakable) return @@ -63,6 +72,15 @@ object RebreakHandler { rebreak = null } + /** + * [RebreakPotential.None] if it cannot be rebroken at all. + * + * [RebreakPotential.PartialProgress] if some progress would be added to the break. + * + * [RebreakPotential.Instant] if the block can be instantly rebroken. + * + * @return In what way this block can be rebroken. + */ context(_: SafeContext) fun BreakInfo.getRebreakPotential() = request.runSafeAutomated { rebreak?.let { reBreak -> @@ -81,6 +99,11 @@ object RebreakHandler { } ?: RebreakPotential.None } + /** + * Updates the current [rebreak] with a fresh [BreakContext], and attempts to rebreak the block if possible. + * + * @return A [RebreakResult] to indicate how the update has been processed. + */ context(_: SafeContext) fun handleUpdate(ctx: BreakContext, breakRequest: BreakRequest) = breakRequest.runSafeAutomated { val reBreak = this@RebreakHandler.rebreak ?: return@runSafeAutomated RebreakResult.Ignored diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/SwapInfo.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/SwapInfo.kt index 9ffd890be..c313245cf 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/SwapInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/SwapInfo.kt @@ -27,6 +27,9 @@ import com.lambda.interaction.request.breaking.BreakInfo.BreakType.Secondary import com.lambda.interaction.request.breaking.BreakManager.calcBreakDelta import com.lambda.threading.runSafeAutomated +/** + * A simple data class to store info about when the [BreakManager] should swap tool. + */ data class SwapInfo( private val type: BreakInfo.BreakType, private val automated: Automated, @@ -43,13 +46,18 @@ data class SwapInfo( companion object { val EMPTY = SwapInfo(Primary, AutomationConfig) + /** + * Calculates the contents and returns a [SwapInfo]. + * + * + */ context(_: SafeContext) fun BreakInfo.getSwapInfo() = request.runSafeAutomated { val breakDelta = context.cachedState.calcBreakDelta(context.blockPos, swapStack) val threshold = getBreakThreshold() - // Plus one as this is calculated before this ticks progress is calculated and the breakingTicks are incremented + // Plus one as this is calculated before this ticks' progress is calculated and the breakingTicks are incremented val breakTicks = (if (rebreakPotential.isPossible()) RebreakHandler.rebreak?.breakingTicks ?: throw IllegalStateException("Rebreak BreakInfo was null when rebreak was considered possible") else breakingTicks) + 1 - breakConfig.fudgeFactor From 43bf8e8a31dffc234c664c6eb280377688a05526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emy=20=F0=9F=92=9C?= Date: Mon, 10 Nov 2025 17:12:34 -0500 Subject: [PATCH 3/8] utils ktdoc cleanup --- .../com/lambda/util/EnchantmentUtils.kt | 12 -- src/main/kotlin/com/lambda/util/FileUtils.kt | 83 +------------- .../kotlin/com/lambda/util/FolderRegister.kt | 6 - src/main/kotlin/com/lambda/util/KeyCode.kt | 11 -- src/main/kotlin/com/lambda/util/Nameables.kt | 3 - .../kotlin/com/lambda/util/PacketUtils.kt | 6 - src/main/kotlin/com/lambda/util/Pointer.kt | 5 - .../kotlin/com/lambda/util/StringUtils.kt | 55 ++------- src/main/kotlin/com/lambda/util/Timer.kt | 52 --------- .../kotlin/com/lambda/util/WindowUtils.kt | 21 +--- .../com/lambda/util/collections/Cacheable.kt | 12 ++ .../util/collections/LimitedDecayQueue.kt | 5 - .../lambda/util/collections/UpdatableLazy.kt | 35 ------ .../com/lambda/util/combat/CombatUtils.kt | 1 + .../com/lambda/util/combat/DamageUtils.kt | 13 +-- .../kotlin/com/lambda/util/extension/Nbt.kt | 3 - .../com/lambda/util/item/ItemStackUtils.kt | 5 - .../kotlin/com/lambda/util/math/Linear.kt | 92 +-------------- .../util/player/prediction/PredictionTick.kt | 14 --- .../kotlin/com/lambda/util/world/Position.kt | 106 ------------------ .../com/lambda/util/world/StructureUtils.kt | 10 -- .../kotlin/com/lambda/util/world/WorldDsl.kt | 75 +------------ .../com/lambda/util/world/WorldUtils.kt | 50 ++------- src/test/kotlin/RangeTest.kt | 10 +- 24 files changed, 51 insertions(+), 634 deletions(-) diff --git a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt index a7d2541e1..4308ba7f2 100644 --- a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt +++ b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt @@ -27,9 +27,6 @@ import net.minecraft.registry.RegistryKey import net.minecraft.registry.entry.RegistryEntry object EnchantmentUtils { - /** - * Returns the list of enchantments from a given [ItemStack] - */ val ItemStack.enchantments: ItemEnchantmentsComponent get() = getOrDefault(DataComponentTypes.ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT) @@ -40,21 +37,12 @@ object EnchantmentUtils { get() = !getOrDefault(DataComponentTypes.ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT).isEmpty || !getOrDefault(DataComponentTypes.STORED_ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT).isEmpty - /** - * Returns the given enchantment level from a [net.minecraft.item.ItemStack] - */ fun ItemStack.getEnchantment(key: RegistryKey) = enchantments.enchantmentEntries.find { it.key?.matchesKey(key) == true }?.intValue ?: 0 - /** - * Iterates over all the enchantments for the given [ItemStack] - */ fun ItemStack.forEachEnchantment(block: (RegistryEntry, Int) -> T) = enchantments.enchantmentEntries.map { block(it.key, it.intValue) } - /** - * Iterates over all the enchantments of the given [net.minecraft.entity.LivingEntity]'s [EquipmentSlot] - */ fun LivingEntity.forEachSlot( vararg slots: EquipmentSlot, block: (entry: RegistryEntry, level: Int) -> T diff --git a/src/main/kotlin/com/lambda/util/FileUtils.kt b/src/main/kotlin/com/lambda/util/FileUtils.kt index d474825b9..39a337240 100644 --- a/src/main/kotlin/com/lambda/util/FileUtils.kt +++ b/src/main/kotlin/com/lambda/util/FileUtils.kt @@ -61,34 +61,17 @@ object FileUtils { return path } - /** - * Executes the [block] if the file is older than the given [duration] - */ inline fun File.isOlderThan(duration: Duration, block: (File) -> Unit) = ifExists { if (duration.inWholeMilliseconds < System.currentTimeMillis() - lastModified()) block(this) } - /** - * Returns whether the receiver file is older than [duration] - */ fun File.isOlderThan(duration: Duration) = duration.inWholeMilliseconds < System.currentTimeMillis() - lastModified() - /** - * Executes the [block] if the receiver file exists and is not empty - */ inline fun File.ifExists(block: (File) -> Unit): File { if (length() > 0) block(this) return this } - /** - * Ensures the current file exists by creating it if it does not. - * - * If the file already exists, it will not be recreated. The necessary - * parent directories will be created if they do not exist. - * - * @param block Lambda executed if the file doesn't exist or the file is empty - */ inline fun File.createIfNotExists(block: (File) -> Unit = {}): File { if (length() == 0L) { parentFile.mkdirs() @@ -100,24 +83,15 @@ object FileUtils { return this } - /** - * Executes the [block] if the receiver file does not exist or is empty. - */ inline fun File.ifNotExists(block: (File) -> Unit): File { if (length() == 0L) block(this) return this } /** - * Modifies the receiver file if the downloaded file compare check succeeds - * - * @receiver The destination file to write the bytes to - * - * @param url The url to download the file from - * @param compare Compare method. -1 if remote is larger. 0 if both file have the same size. 1 if local is larger - * @param block Configuration block for the request - * - * @return An exception or the file + * Changes the local file if [compare]: + * - is -1 and the remote is larger + * - is 1 and local is larger */ suspend fun File.downloadCompare( url: String, @@ -133,72 +107,21 @@ object FileUtils { } } - /** - * Downloads the given file url if the file is not present - * - * @receiver The destination file to write the bytes to - * - * @param url The url to download the file from - * @param block Configuration block for the request - * - * @return An exception or the file - */ suspend fun File.downloadIfNotPresent( url: String, block: HttpRequestBuilder.() -> Unit = {}, ) = runCatching { createIfNotExists { LambdaHttp.download(url, this, block) } } - /** - * Downloads the given file url if the file is not present - * - * @receiver The url to download the file from - * - * @param file The destination file to write the bytes to - * @param block Configuration block for the request - * - * @return An exception or the file - */ suspend fun String.downloadIfNotPresent( file: File, block: HttpRequestBuilder.() -> Unit = {}, ) = runCatching { file.createIfNotExists { LambdaHttp.download(this, file, block) } } - /** - * Lambda that downloads the given file url if the file is not present - * - * @receiver The destination file to write the bytes to - * @param block Configuration block for the request - * - * @return A lambda that returns an exception or the file - */ - fun File.downloadIfNotPresent(block: HttpRequestBuilder.() -> Unit = {}): suspend (String) -> Result = - { url -> runCatching { createIfNotExists { LambdaHttp.download(url, this, block) } } } - - /** - * Downloads the given file url if the file is present - * - * @receiver The destination file to write the bytes to - * - * @param url The url to download the file from - * @param block Configuration block for the request - * - * @return An exception or the file - */ suspend fun File.downloadIfPresent( url: String, block: HttpRequestBuilder.() -> Unit = {}, ) = runCatching { ifExists { LambdaHttp.download(url, this, block) } } - /** - * Downloads the given file url if the file is present - * - * @receiver The url to download the file from - * - * @param file The destination file to write the bytes to - * @param block Configuration block for the request - * - * @return An exception or the file - */ suspend fun String.downloadIfPresent( file: File, block: HttpRequestBuilder.() -> Unit = {}, diff --git a/src/main/kotlin/com/lambda/util/FolderRegister.kt b/src/main/kotlin/com/lambda/util/FolderRegister.kt index 43516d223..b94901714 100644 --- a/src/main/kotlin/com/lambda/util/FolderRegister.kt +++ b/src/main/kotlin/com/lambda/util/FolderRegister.kt @@ -31,12 +31,6 @@ import kotlin.io.path.notExists /** * The [FolderRegister] object is responsible for managing the directory structure of the application. - * - * @property minecraft The root directory of the Minecraft client. - * @property lambda The directory for the Lambda client, located within the Minecraft directory. - * @property config The directory for storing configuration files, located within the Lambda directory. - * @property packetLogs The directory for storing packet logs, located within the Lambda directory. - * @property replay The directory for storing replay files, located within the Lambda directory. */ object FolderRegister : Loadable { val minecraft: Path = mc.runDirectory.toPath() diff --git a/src/main/kotlin/com/lambda/util/KeyCode.kt b/src/main/kotlin/com/lambda/util/KeyCode.kt index 8bf126ab5..ad22fea60 100644 --- a/src/main/kotlin/com/lambda/util/KeyCode.kt +++ b/src/main/kotlin/com/lambda/util/KeyCode.kt @@ -155,14 +155,7 @@ enum class KeyCode(val code: Int) { private val keyCodeMap = entries.associateBy { it.code } private val nameMap = entries.associateBy { it.name.lowercase() } - /** - * Returns the KeyCode enum instance from the key code number or [Unbound] if invalid - */ fun fromKeyCode(keyCode: Int) = keyCodeMap[keyCode] ?: Unbound - - /** - * Returns the KeyCode enum instance from the key code name or [Unbound] if invalid - */ fun fromKeyName(name: String) = nameMap[name.lowercase()] ?: Unbound /** @@ -172,10 +165,6 @@ enum class KeyCode(val code: Int) { * If the key corresponds to a printable character or letter, it is mapped * to its corresponding [KeyCode] based on the US layout. * - * @param keyCode The key code to map. - * @param scanCode The scan code of the key. - * @return The corresponding [KeyCode]. - * * @see ImGui impl */ fun virtualMapUS(keyCode: Int, scanCode: Int): KeyCode { diff --git a/src/main/kotlin/com/lambda/util/Nameables.kt b/src/main/kotlin/com/lambda/util/Nameables.kt index ed6cbae06..042d16108 100644 --- a/src/main/kotlin/com/lambda/util/Nameables.kt +++ b/src/main/kotlin/com/lambda/util/Nameables.kt @@ -17,9 +17,6 @@ package com.lambda.util -/** - * @property name represents the name or identifier of the object. - */ interface Nameable { val name: String val commandName get() = name.trim().replace(' ', '_') diff --git a/src/main/kotlin/com/lambda/util/PacketUtils.kt b/src/main/kotlin/com/lambda/util/PacketUtils.kt index f4174d806..ad16c3abb 100644 --- a/src/main/kotlin/com/lambda/util/PacketUtils.kt +++ b/src/main/kotlin/com/lambda/util/PacketUtils.kt @@ -26,8 +26,6 @@ import net.minecraft.network.packet.Packet object PacketUtils { /** * Sends a packet through the regular packet pipeline - * - * @param block Lambda that returns the packet to be sent */ fun ClientPlayNetworkHandler.sendPacket(block: () -> Packet<*>) = connection.send(block()) @@ -35,8 +33,6 @@ object PacketUtils { * Sends a packet to the server without notifying the client. * It bypasses the mixins that would normally intercept the packet * and send it through the client's event bus. - * - * @param packet The packet to send. */ fun ClientPlayNetworkHandler.sendPacketSilently(packet: Packet<*>) { if (!connection.isOpen || connection.packetListener?.accepts(packet) == true) return @@ -49,8 +45,6 @@ object PacketUtils { * Handles a packet without notifying the client. * It bypasses the mixins that would normally intercept the packet * and send it through the client's event bus. - * - * @param packet The packet to handle. */ fun ClientPlayNetworkHandler.handlePacketSilently(packet: Packet<*>) { if (!connection.isOpen || connection.packetListener?.accepts(packet) == false) return diff --git a/src/main/kotlin/com/lambda/util/Pointer.kt b/src/main/kotlin/com/lambda/util/Pointer.kt index f506aae12..8c5e22789 100644 --- a/src/main/kotlin/com/lambda/util/Pointer.kt +++ b/src/main/kotlin/com/lambda/util/Pointer.kt @@ -20,11 +20,6 @@ package com.lambda.util import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -/** - * A class representing a pointer to a value. - * - * It is a high-level abstraction over a mutable variable that allows for easy access to the value. - */ data class Pointer(var value: T? = null) : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): T? = value override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { diff --git a/src/main/kotlin/com/lambda/util/StringUtils.kt b/src/main/kotlin/com/lambda/util/StringUtils.kt index 27c8826b7..e6b1f3bb3 100644 --- a/src/main/kotlin/com/lambda/util/StringUtils.kt +++ b/src/main/kotlin/com/lambda/util/StringUtils.kt @@ -25,49 +25,31 @@ import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi object StringUtils { - /** - * Returns a sanitized file path for both Unix and Linux systems - */ fun String.sanitizeForFilename() = replace(Regex("[\\\\/:*?\"<>|]"), "_") .trim() .take(255) // truncate to 255 characters for Windows compatibility - /** - * Capitalizes the first character of a string using its Unicode mapping - */ fun String.capitalize() = replaceFirstChar { it.titlecase() } - /** - * Returns a Minecraft [net.minecraft.util.Identifier] from a string with the given namespace - */ fun String.toIdentifier(namespace: String = Lambda.MOD_ID): Identifier = Identifier.of(namespace, this) - /** - * Returns a [Lambda] Minecraft [Identifier] from a string - */ val String.asIdentifier: Identifier get() = toIdentifier() /** * Find similar strings in a set of words. * - * @param target The string to compare against. - * @param words The set of words to compare against. - * @param threshold The maximum Levenshtein distance between the target and the words. + * @see Levenshtein distance, threshold: Int, - ) = words.filter { it.levenshteinDistance(target) <= threshold }.toSet() + ) = words.filter { it.levenshteinDistance(this) <= threshold }.toSet() /** - * See [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) - * - * @receiver The string to compare. - * @param rhs The string to compare against. + * @see Levenshtein distance String.json() = gson.fromJson(this, T::class.java) /** @@ -122,39 +99,21 @@ object StringUtils { fun String.base64UrlDecode() = Base64.UrlSafe.decode(this).decodeToString() /** - * See [MessageDigest section](https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms) of the Java Security Standard Algorithm Names Specification - * - * @receiver The string to hash - * @param algorithm The algorithm instance to use - * @param extra Additional data to digest with the string - * - * @return The string representation of the hash + * @see Java Security Standard Algorithm Names Specification */ fun String.hashString(algorithm: String, vararg extra: ByteArray): String = toByteArray().hash(algorithm, *extra) .joinToString(separator = "") { "%02x".format(it) } /** - * See [MessageDigest section](https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms) of the Java Security Standard Algorithm Names Specification - * - * @receiver The byte array to hash - * @param algorithm The algorithm instance to use - * @param extra Additional data to digest with the byte array - * - * @return The string representation of the hash + * @see Java Security Standard Algorithm Names Specification */ fun ByteArray.hashString(algorithm: String, vararg extra: ByteArray): String = hash(algorithm, *extra) .joinToString(separator = "") { "%02x".format(it) } /** - * See [MessageDigest section](https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms) of the Java Security Standard Algorithm Names Specification - * - * @receiver The byte array to hash - * @param algorithm The algorithm instance to use - * @param extra Additional data to digest with the byte array - * - * @return The digested data + * @see Java Security Standard Algorithm Names Specification */ fun ByteArray.hash(algorithm: String, vararg extra: ByteArray): ByteArray = MessageDigest diff --git a/src/main/kotlin/com/lambda/util/Timer.kt b/src/main/kotlin/com/lambda/util/Timer.kt index e92f3c054..204cfa8a4 100644 --- a/src/main/kotlin/com/lambda/util/Timer.kt +++ b/src/main/kotlin/com/lambda/util/Timer.kt @@ -26,39 +26,16 @@ import kotlin.time.TimeSource * A utility class to manage time-based operations, such as delays and periodic tasks. */ class Timer { - /** - * Records the timestamp of the last timing event using a monotonic clock. - */ private var lastTiming = TimeSource.Monotonic.markNow() - /** - * Checks if the specified amount of time has passed since the last timing event. - * - * @param duration the time interval to check. - * @return `true` if the current time exceeds `lastTiming + duration`, `false` otherwise. - */ fun timePassed(duration: Duration): Boolean = lastTiming.elapsedNow() > duration - /** - * Checks if the specified duration has passed and resets the timer if true. - * - * @param duration the time interval to check. - * @return `true` if the duration has passed and the timer was reset, `false` otherwise. - */ fun delayIfPassed(duration: Duration): Boolean = timePassed(duration).apply { if (this) reset() } - /** - * Executes a given block of code if the specified duration has passed since the last timing event. - * Optionally resets the timer after execution. - * - * @param duration the time interval to check. - * @param reset whether to reset the timer after running the block. Defaults to `true`. - * @param block the code block to execute if the time has passed. - */ fun runIfPassed(duration: Duration, reset: Boolean = true, block: () -> Unit) = timePassed(duration).apply { if (!this) return@apply @@ -67,14 +44,6 @@ class Timer { block() } - /** - * Executes a given block of code if the specified duration has not passed yet since the last timing event. - * Optionally resets the timer after execution. - * - * @param duration the time interval to check. - * @param reset whether to reset the timer after running the block. Defaults to `true`. - * @param block the code block to execute if the duration has not passed. - */ fun runIfNotPassed(duration: Duration, reset: Boolean = true, block: () -> Unit) = timePassed(duration).also { passed -> if (passed) return@also @@ -83,14 +52,6 @@ class Timer { block() } - /** - * Executes a given block of code in safe context if the specified duration has passed since the last timing event. - * Optionally resets the timer after execution. - * - * @param duration the time interval to check. - * @param reset whether to reset the timer after running the block. Defaults to `true`. - * @param block the code block to execute if the duration has passed. - */ fun runSafeIfPassed(duration: Duration, reset: Boolean = true, block: SafeContext.() -> Unit) = timePassed(duration).also { passed -> if (!passed) return@also @@ -101,14 +62,6 @@ class Timer { } } - /** - * Executes a given block of code in safe context if the specified duration has not passed yet since the last timing event. - * Optionally resets the timer after execution. - * - * @param duration the time interval to check. - * @param reset whether to reset the timer after running the block. Defaults to `true`. - * @param block the code block to execute if the duration has not passed. - */ fun runSafeIfNotPassed(duration: Duration, reset: Boolean = true, block: SafeContext.() -> Unit) = timePassed(duration).also { passed -> if (passed) return@also @@ -119,11 +72,6 @@ class Timer { } } - /** - * Resets the timer by updating the last timing event to the current time. - * - * @param additionalDelay an additional delay to add to the current time. Defaults to `Duration.ZERO`. - */ fun reset(additionalDelay: Duration = Duration.ZERO) { lastTiming = TimeSource.Monotonic.markNow() + additionalDelay } diff --git a/src/main/kotlin/com/lambda/util/WindowUtils.kt b/src/main/kotlin/com/lambda/util/WindowUtils.kt index 41533189c..4160ca83f 100644 --- a/src/main/kotlin/com/lambda/util/WindowUtils.kt +++ b/src/main/kotlin/com/lambda/util/WindowUtils.kt @@ -34,25 +34,12 @@ import java.nio.ByteBuffer import javax.imageio.ImageIO object WindowUtils { - /** - * Updates the Lambda title for the application window. - * - * Constructs and sets the window title based on the current application symbol, name, version, - * and additional dynamic details (e.g., username if `lambdaTitleAppendixName` is enabled). - */ @JvmStatic fun setLambdaTitle() { val name = if (lambdaTitleAppendixName) " - ${mc.session.username}" else "" mc.window.setTitle("$SYMBOL $MOD_NAME $VERSION - ${mc.windowTitle}$name") } - /** - * Sets the window icon for the application using a predefined set of icon sizes. - * - * This method constructs a list of image resource paths corresponding to different icon sizes - * (16x16, 24x24, 32x32, 48x48, 64x64, 128x128, and 256x256) and applies them as the window icon. - * The function utilizes the `setWindowIcon` function to handle the underlying platform-specific logic. - */ fun setLambdaWindowIcon() { val icons = listOf(16, 24, 32, 48, 64, 128, 256).map { "textures/icon/logo_$it.png" } setWindowIcon(*icons.toTypedArray()) @@ -63,15 +50,15 @@ object WindowUtils { * - On Windows/X11: uses glfwSetWindowIcon with all given sizes. * - On macOS: attempts to set the application Dock icon from the largest image (glfwSetWindowIcon is ignored). * - * Paths are resolved via your readImage() extension (same as your texture system). - * * Example: - * WindowIcons.setWindowIcon( + * ``` + * WindowIcons.setWindowIcon( * "textures/icon16.png", * "textures/icon32.png", * "textures/icon48.png", * "textures/icon128.png", - * ) + * ) + * ``` */ @JvmStatic fun setWindowIcon(vararg iconPaths: String) { diff --git a/src/main/kotlin/com/lambda/util/collections/Cacheable.kt b/src/main/kotlin/com/lambda/util/collections/Cacheable.kt index 94da3b81c..8280c3286 100644 --- a/src/main/kotlin/com/lambda/util/collections/Cacheable.kt +++ b/src/main/kotlin/com/lambda/util/collections/Cacheable.kt @@ -19,6 +19,18 @@ package com.lambda.util.collections import kotlin.reflect.KProperty +/** + * This structure lazily evaluates the [getter] lambda and stores its result in its [cache]. + * + * Example: + * ```kt + * val String.sha256: String by cacheable { it.hashString("SHA-256") } + * + * val hash1 = "aa".sha256 // lazily evaluated and stored + * val hash2 = "aa".sha256 // value fetched from the cache + * val hash3 = "bb.sha256 // lazily evaluated and stored + * ``` + */ class Cacheable private constructor(private val getter: (K) -> V) { private val cache = mutableMapOf() diff --git a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt index 39c50a5ea..4d7902703 100644 --- a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt +++ b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt @@ -23,11 +23,6 @@ import java.util.concurrent.ConcurrentLinkedQueue /** * A thread-safe collection that limits the number of elements it can hold and automatically removes elements * older than a specified time interval. The elements are stored with the timestamp of their addition to the collection. - * - * @param E The type of elements held in this collection. - * @property sizeLimit The maximum number of elements the queue can hold at any given time. - * @property maxAge The activeRequestAge (in milliseconds) after which elements are considered expired and are removed from the queue. - * @property onDecay Lambda function that is executed on decay of element [E]. */ class LimitedDecayQueue( private var sizeLimit: Int, diff --git a/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt b/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt index f70260392..382d2265e 100644 --- a/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt +++ b/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt @@ -19,53 +19,18 @@ package com.lambda.util.collections /** * A lazy-initialized value holder that allows the stored value to be reset and re-initialized on demand. - * - * This class supports lazy initialization for a value of type [T] using a provided initializer function. - * Once the value is accessed, it is initialized and stored. The `update` function can be called to reset - * the value, allowing it to be re-initialized using the same initializer function. - * - * @param T The type of the value being lazily initialized and managed. - * @constructor Accepts an initializer function that defines how the value should be created. */ class UpdatableLazy(private val initializer: () -> T) { private var _value: T? = null - /** - * Lazily initializes and retrieves a value of type [T] using the provided initializer function. - * If the value has not been initialized previously, the initializer function is called - * to generate the value, which is then cached for subsequent accesses. - * - * This property ensures that the value is only initialized when it is first accessed, - * and maintains its state until explicitly updated or reset. - * - * @return The lazily initialized value, or `null` if the initializer function - * is designed to produce a `null` result or has not been called yet. - */ val value: T? get() { if (_value == null) _value = initializer() return _value } - - /** - * Resets the current value to a new value generated by the initializer function. - * - * This function effectively re-initializes the stored value by discarding the existing value, - * if any, and calling the initializer function to compute a new value. - * It is useful for scenarios where the lazily computed value needs to be refreshed or updated - * explicitly. - */ fun update() { _value = initializer() } } -/** - * Provides a lazily-initialized value that can be reset and re-initialized on demand. - * This function utilizes an `UpdatableLazy` to manage the lazy initialization and allow updates. - * - * @param T The type of the value to be lazily initialized. - * @param initializer A lambda function that defines how the value should be computed. - * @return An `UpdatableLazy` instance capable of managing a lazily-initialized value. - */ fun updatableLazy(initializer: () -> T) = UpdatableLazy(initializer) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt b/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt index 0f33c3e07..89dc12435 100644 --- a/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt +++ b/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt @@ -40,6 +40,7 @@ object CombatUtils { /** * Calculates the damage dealt by an explosion to a living entity + * * @param position The position of the explosion * @param entity The entity to calculate the damage for */ diff --git a/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt b/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt index 7753799c6..795f3d830 100644 --- a/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt +++ b/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt @@ -66,7 +66,8 @@ object DamageUtils { player.fullHealth - fallDamage() <= minHealth /** - * Calculates the fall damage for the player at the predicted position + * Calculates the fall damage for the player given its current input values and the predicted + * landing position. */ fun SafeContext.fallDamage(): Double { val prediction = buildPlayerPrediction() @@ -96,13 +97,6 @@ object DamageUtils { return source.scale(world, player, player.fallDamage(distance, multiplier)) } - /** - * Calculates the fall damage for the given entity - * - * @param distance The fall distance - * @param multiplier The fall damage multiplier - * @return The calculated fall damage - */ fun LivingEntity.fallDamage(distance: Double, multiplier: Double): Double { if (type.isIn(FALL_DAMAGE_IMMUNE)) return 0.0 @@ -112,9 +106,6 @@ object DamageUtils { /** * Scales damage up or down based on the player resistances and other variables - * - * @param entity The entity to calculate the damage for - * @param damage The damage to apply */ fun DamageSource.scale(world: ClientWorld, entity: LivingEntity, damage: Double): Double { val blockingItem = entity.blockingItem diff --git a/src/main/kotlin/com/lambda/util/extension/Nbt.kt b/src/main/kotlin/com/lambda/util/extension/Nbt.kt index 0229b1a7b..261f10bc4 100644 --- a/src/main/kotlin/com/lambda/util/extension/Nbt.kt +++ b/src/main/kotlin/com/lambda/util/extension/Nbt.kt @@ -30,9 +30,6 @@ fun NbtCompound.putIntList(key: String, vararg values: Int) { put(key, values.fold(NbtList()) { list, value -> list.add(NbtInt.of(value)); list }) } -/** - * Deletes all the keys in a compound - */ fun NbtCompound.clear() { keys.forEach { remove(it) } } diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index e67ec2c48..68cd4078c 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -116,11 +116,6 @@ object ItemStackUtils { /** * Checks if the given item stacks are equal, including the item count and NBT. - * - * @param other The other item stack to compare with. - * @return `true` if the item stacks are equal, `false` otherwise. - * @see ItemStack.areItemsEqual Checks if the items in two item stacks are equal. - * @see ItemStack.canCombine Checks if two item stacks can be combined into one stack. */ fun ItemStack?.equal(other: ItemStack?) = ItemStack.areEqual(this, other) } diff --git a/src/main/kotlin/com/lambda/util/math/Linear.kt b/src/main/kotlin/com/lambda/util/math/Linear.kt index ca8f43953..1befe0e69 100644 --- a/src/main/kotlin/com/lambda/util/math/Linear.kt +++ b/src/main/kotlin/com/lambda/util/math/Linear.kt @@ -26,54 +26,29 @@ import kotlin.math.min import kotlin.random.Random import kotlin.random.Random.Default.nextDouble -/** - * Iterates over the double range with the specified step. - */ -fun ClosedRange.step(step: Double) = object : DoubleIterator() { +infix fun ClosedRange.step(step: Double) = object : DoubleIterator() { private var next = start override fun hasNext() = next <= endInclusive override fun nextDouble() = next.also { next += step } } -/** - * Iterates over the float range with the specified step. - */ -fun ClosedRange.step(step: Float) = object : FloatIterator() { +infix fun ClosedRange.step(step: Float) = object : FloatIterator() { private var next = start override fun hasNext() = next <= endInclusive override fun nextFloat() = next.also { next += step } } -/** - * Returns a random number within the range. - */ fun ClosedRange.random(random: Random = Random) = start + (endInclusive - start) * random.nextDouble() +fun ClosedRange.random(random: Random = Random) = start + (endInclusive - start) * random.nextDouble() -/** - * Converts a value from one range to a normalized value between 0 and 1. - */ -fun ClosedRange.normalize(value: Double): Double = - transform(value, 0.0, 1.0) - -/** - * Converts a value from one range to a normalized value between 0 and 1. - */ -fun ClosedRange.normalize(value: Float): Float = - transform(value, 0f, 1f) +fun ClosedRange.normalize(value: Double): Double = transform(value, 0.0, 1.0) +fun ClosedRange.normalize(value: Float): Float = transform(value, 0f, 1f) -/** - * Inverts the range. - */ +fun ClosedRange.inv() = endInclusive to start fun ClosedRange.inv() = endInclusive to start /** * Converts a value from one range to another while keeping the ratio using linear interpolation. - * - * @param value The value to convert. - * @param min The minimum of the new range. - * @param max The maximum of the new range. - * - * @return The converted value. */ fun ClosedRange.transform( value: Double, @@ -84,12 +59,6 @@ fun ClosedRange.transform( /** * Converts a value from one range to another while keeping the ratio using linear interpolation. - * - * @param value The value to convert. - * @param min The minimum of the new range. - * @param max The maximum of the new range. - * - * @return The converted value. */ fun ClosedRange.transform( value: Float, @@ -98,12 +67,6 @@ fun ClosedRange.transform( ): Float = transform(value, start, endInclusive, min, max) -/** - * Linear interpolation between two axes-aligned boxes. - * - * @param start The start box. - * @param end The end box. - */ fun lerp(value: Double, start: Box, end: Box) = Box( lerp(value, start.minX, end.minX), @@ -114,21 +77,12 @@ fun lerp(value: Double, start: Box, end: Box) = lerp(value, start.maxZ, end.maxZ), ) -/** - * Linear interpolation between two 2d vectors. - * - * @param start The start vector. - * @param end The end vector. - */ fun lerp(value: Double, start: Vec2d, end: Vec2d) = Vec2d( lerp(value, start.x, end.x), lerp(value, start.y, end.y), ) -/** - * Linear interpolation between two 3d vectors. - */ fun lerp(value: Double, start: Vec3d, end: Vec3d) = Vec3d( lerp(value, start.x, end.x), @@ -136,27 +90,18 @@ fun lerp(value: Double, start: Vec3d, end: Vec3d) = lerp(value, start.z, end.z), ) -/** - * Linear interpolation between two rotations. - */ fun lerp(value: Double, start: Rotation, end: Rotation) = Rotation( lerp(value, start.yaw, end.yaw), lerp(value, start.pitch, end.pitch), ) -/** - * Linear interpolation between two rectangles - */ fun lerp(value: Double, start: Rect, end: Rect) = Rect( lerp(value, start.leftTop, end.leftTop), lerp(value, start.rightBottom, end.rightBottom), ) -/** - * Linear interpolation between two colors. - */ fun lerp(value: Double, start: Color, end: Color) = Color( lerp(value, start.r, end.r).toFloat(), @@ -172,11 +117,6 @@ fun lerp(value: Double, start: Color, end: Color) = * between [start] and [end] based on the interpolation factor [value]. * The interpolation factor [value] is clamped between zero * and one to ensure the result stays within the range of [start] and [end]. - * - * @param start The start value. - * @param end The end value. - * @param value The interpolation factor, typically between 0 (representing [start]) and 1 (representing [end]). - * @return The interpolated value between [start] and [end]. */ fun lerp(value: Double, start: Double, end: Double) = transform(value.coerceIn(0.0, 1.0), 0.0, 1.0, start, end) @@ -188,12 +128,6 @@ fun lerp(value: Double, start: Double, end: Double) = * between [start] and [end] based on the interpolation factor [value]. * The interpolation factor [value] is clamped between zero * and one to ensure the result stays within the range of [start] and [end]. - * - * @param start The start value. - * @param end The end value. - * @param value The interpolation factor, typically between 0 (representing [start]) and 1 (representing [end]). - * - * @return The interpolated value between [start] and [end]. */ fun lerp(value: Float, start: Float, end: Float) = transform(value.coerceIn(0f, 1f), 0f, 1f, start, end) @@ -207,7 +141,6 @@ fun lerp(value: Float, start: Float, end: Float) = * @param nStart The new start value. * @param nEnd The new end value. * - * @return The converted value. * @see Linear Map */ fun transform( @@ -228,7 +161,6 @@ fun transform( * @param nStart The new start value. * @param nEnd The new end value. * - * @return The converted value. * @see Linear Map */ fun transform( @@ -241,23 +173,11 @@ fun transform( nStart + (value - ogStart) * ((nEnd - nStart) / (ogEnd - ogStart)) -/** - * Coerces a value to be within the range. - */ fun ClosedRange.coerceIn(value: Double) = value.coerceIn(start, endInclusive) - -/** - * Coerces a value to be within the range. - */ fun ClosedRange.coerceIn(value: Float) = value.coerceIn(start, endInclusive) /** * Coerces a value to be within a 2d vector. - * - * @param minX The minimum x value. - * @param maxX The maximum x value. - * @param minY The minimum y value. - * @param maxY The maximum y value. */ fun Vec2d.coerceIn( minX: Double, diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt index f0e108d3f..3f84a9d9f 100644 --- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt +++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt @@ -21,17 +21,6 @@ import com.lambda.interaction.request.rotating.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d -/** - * Data class representing a movement prediction tick for an entity. - * - * @property position The current position of the entity. - * @property rotation The current rotation (yaw, pitch) of the entity. - * @property velocity The current velocity vector of the entity. - * @property boundingBox The bounding box that defines the entity's space in the world. - * @property eyePos The position of the entity's eyes, typically used for raycasting or viewing purposes. - * @property onGround Indicates whether the entity is currently touching the ground. - * @property isJumping Indicates whether the entity is currently jumping. - */ data class PredictionTick( val position: Vec3d, val rotation: Rotation, @@ -52,9 +41,6 @@ data class PredictionTick( lastTick } - /** - * Runs the simulation until either [amount] ticks were skipped or [block] is true - */ fun skipUntil(amount: Int = 20, block: (PredictionTick) -> Boolean) = with(predictionEntity) { repeat(amount) { tickMovement() diff --git a/src/main/kotlin/com/lambda/util/world/Position.kt b/src/main/kotlin/com/lambda/util/world/Position.kt index 3935a4447..aafeaebe6 100644 --- a/src/main/kotlin/com/lambda/util/world/Position.kt +++ b/src/main/kotlin/com/lambda/util/world/Position.kt @@ -55,9 +55,6 @@ internal const val MAX_Z = (1L shl Z_BITS - 1) - 1L */ const val F_ONE = 274945015809L -/** - * Creates a new position from the given coordinates. - */ fun fastVectorOf(x: Long, y: Long, z: Long): FastVector { require(x in MIN_X..MAX_X) { "X coordinate out of bounds for $X_BITS bits: $x" } require(z in MIN_Z..MAX_Z) { "Z coordinate out of bounds for $Z_BITS bits: $z" } @@ -65,127 +62,47 @@ fun fastVectorOf(x: Long, y: Long, z: Long): FastVector { return ((x and X_MASK) shl X_SHIFT) or ((z and Z_MASK) shl Z_SHIFT) or (y and Y_MASK) } -/** - * Creates a new position from the given coordinates. - */ fun fastVectorOf(x: Int, y: Int, z: Int): FastVector = fastVectorOf(x.toLong(), y.toLong(), z.toLong()) -/** - * Gets the X coordinate from the position. - */ val FastVector.x: Int get() = ((this shr X_SHIFT and X_MASK).toInt() shl (32 - X_BITS)) shr (32 - X_BITS) -/** - * Gets the Z coordinate from the position. - */ val FastVector.z: Int get() = ((this shr Z_SHIFT and Z_MASK).toInt() shl (32 - Z_BITS)) shr (32 - Z_BITS) -/** - * Gets the Y coordinate from the position. - */ val FastVector.y: Int get() = ((this and Y_MASK).toInt() shl (32 - Y_BITS)) shr (32 - Y_BITS) -/** - * Sets the X coordinate of the position. - */ infix fun FastVector.setX(x: Int): FastVector = bitSetTo(x.toLong(), X_SHIFT, X_BITS) - -/** - * Sets the Y coordinate of the position. - */ infix fun FastVector.setY(y: Int): FastVector = bitSetTo(y.toLong(), 0, Y_BITS) - -/** - * Sets the Z coordinate of the position. - */ infix fun FastVector.setZ(z: Int): FastVector = bitSetTo(z.toLong(), Z_SHIFT, Z_BITS) -/** - * Adds the given value to the X coordinate. - */ infix fun FastVector.addX(value: Int): FastVector = setX(x + value) - -/** - * Adds the given value to the Y coordinate. - */ infix fun FastVector.addY(value: Int): FastVector = setY(y + value) - -/** - * Adds the given value to the Z coordinate. - */ infix fun FastVector.addZ(value: Int): FastVector = setZ(z + value) -/** - * Adds the given vector to the position. - */ infix fun FastVector.plus(vec: FastVector): FastVector = fastVectorOf(x + vec.x, y + vec.y, z + vec.z) - -/** - * Adds the given vector to the position. - */ infix fun FastVector.plus(vec: Vec3i): FastVector = fastVectorOf(x + vec.x, y + vec.y, z + vec.z) - -/** - * Adds the given vector to the position. - */ infix fun FastVector.plus(vec: Vec3d): FastVector = fastVectorOf(x + vec.x.toLong(), y + vec.y.toLong(), z + vec.z.toLong()) -/** - * Subtracts the given vector from the position. - */ infix fun FastVector.minus(vec: FastVector): FastVector = fastVectorOf(x - vec.x, y - vec.y, z - vec.z) - -/** - * Subtracts the given vector from the position. - */ infix fun FastVector.minus(vec: Vec3i): FastVector = fastVectorOf(x - vec.x, y - vec.y, z - vec.z) - -/** - * Subtracts the given vector from the position. - */ infix fun FastVector.minus(vec: Vec3d): FastVector = fastVectorOf(x - vec.x.toLong(), y - vec.y.toLong(), z - vec.z.toLong()) -/** - * Multiplies the position by the given scalar. - */ infix fun FastVector.times(scalar: Int): FastVector = fastVectorOf(x * scalar, y * scalar, z * scalar) - -/** - * Multiplies the position by the given scalar. - */ infix fun FastVector.times(scalar: Double): FastVector = fastVectorOf((x * scalar).toLong(), (y * scalar).toLong(), (z * scalar).toLong()) -/** - * Divides the position by the given scalar. - */ infix fun FastVector.div(scalar: Int): FastVector = fastVectorOf(x / scalar, y / scalar, z / scalar) - -/** - * Divides the position by the given scalar. - */ infix fun FastVector.div(scalar: Double): FastVector = fastVectorOf((x / scalar).toLong(), (y / scalar).toLong(), (z / scalar).toLong()) -/** - * Modulo the position by the given scalar. - */ infix fun FastVector.remainder(scalar: Int): FastVector = fastVectorOf(x % scalar, y % scalar, z % scalar) - -/** - * Modulo the position by the given scalar. - */ infix fun FastVector.remainder(scalar: Double): FastVector = fastVectorOf((x % scalar).toLong(), (y % scalar).toLong(), (z % scalar).toLong()) -/** - * Returns the squared distance between this position and the other. - */ infix fun FastVector.distSq(other: FastVector): Double { val dx = x - other.x val dy = y - other.y @@ -193,9 +110,6 @@ infix fun FastVector.distSq(other: FastVector): Double { return (dx * dx + dy * dy + dz * dz).toDouble() } -/** - * Returns the squared distance between this position and the Vec3i. - */ infix fun FastVector.distSq(other: Vec3i): Double { val dx = x - other.x val dy = y - other.y @@ -203,9 +117,6 @@ infix fun FastVector.distSq(other: Vec3i): Double { return (dx * dx + dy * dy + dz * dz).toDouble() } -/** - * Returns the squared distance between this position and the Vec3d. - */ infix fun FastVector.distSq(other: Vec3d): Double { val dx = x - other.x.toLong() val dy = y - other.y.toLong() @@ -213,30 +124,13 @@ infix fun FastVector.distSq(other: Vec3d): Double { return (dx * dx + dy * dy + dz * dz).toDouble() } -/** - * Adds a [net.minecraft.util.math.Direction] offset to the fast vector - */ fun FastVector.offset(dir: Direction) = fastVectorOf(x + dir.offsetX, y + dir.offsetY, z + dir.offsetZ) -/** - * Converts a [Vec3i] to a [FastVector]. - */ fun Vec3i.toFastVec(): FastVector = fastVectorOf(x.toLong(), y.toLong(), z.toLong()) - -/** - * Converts a [Vec3d] to a [FastVector]. - */ fun Vec3d.toFastVec(): FastVector = fastVectorOf(x.toLong(), y.toLong(), z.toLong()) -/** - * Converts the [FastVector] into a [Vec3d]. - */ fun FastVector.toVec3d(): Vec3d = Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) - -/** - * Converts the [FastVector] into a [BlockPos]. - */ fun FastVector.toBlockPos(): BlockPos = BlockPos(x, y, z) /** diff --git a/src/main/kotlin/com/lambda/util/world/StructureUtils.kt b/src/main/kotlin/com/lambda/util/world/StructureUtils.kt index 76db5144a..a3782a761 100644 --- a/src/main/kotlin/com/lambda/util/world/StructureUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/StructureUtils.kt @@ -41,16 +41,6 @@ object StructureUtils { BlockPos(1, 0, 0) to Blocks.FIRE.defaultState, ) - /** - * Generates a tube of blocks in the specified direction. - * - * @param direction The direction in which the tube should be generated. - * @param width The width of the tube. - * @param height The height of the tube. - * @param leftRightOffset The offset along the X-axis. - * @param heightOffset The offset along the Y-axis. - * @return A set of BlockPos representing the generated tube. - */ fun generateDirectionalTube( direction: EightWayDirection, width: Int, diff --git a/src/main/kotlin/com/lambda/util/world/WorldDsl.kt b/src/main/kotlin/com/lambda/util/world/WorldDsl.kt index bf67605ba..e7a9b8a47 100644 --- a/src/main/kotlin/com/lambda/util/world/WorldDsl.kt +++ b/src/main/kotlin/com/lambda/util/world/WorldDsl.kt @@ -36,10 +36,8 @@ import net.minecraft.util.math.Vec3i annotation class BlockMarker /** - * Searches for blocks in the world - * * Example: - * ```kotlin + * ``` * val blocks = blockSearch(range = Vec3i(10, 10, 10)) { * it.isOf(Blocks.DIAMOND_BLOCK) // Filter out blocks that are not diamond blocks * } @@ -48,13 +46,6 @@ annotation class BlockMarker * println("Found diamond block at: $pos") * } * ``` - * - * @param pos The position around which to search for blocks. Defaults to the player's current position. - * @param range The `x`, `y`, `z` range around the position to search for blocks. - * @param step The `x`, `y`, `z` step intervals at which to check for blocks. - * @param filter The predicate to filter blocks. - * - * @return A map of positions to block states */ @BlockMarker fun SafeContext.blockSearch( @@ -72,10 +63,8 @@ fun SafeContext.blockSearch( }.mapKeys { it.key.toBlockPos() } /** - * Searches for blocks in the world - * * Example: - * ```kotlin + * ``` * val blocks = blockSearch(range = Vec3i(10, 10, 10)) { * it.isOf(Blocks.DIAMOND_BLOCK) // Filter out blocks that are not diamond blocks * } @@ -84,13 +73,6 @@ fun SafeContext.blockSearch( * println("Found diamond block at: $pos") * } * ``` - * - * @param pos The position around which to search for blocks - * @param range The `x`, `y`, `z` range around the position to search for blocks - * @param step The `x`, `y`, `z` step intervals at which to check for blocks - * @param filter The predicate to filter blocks. - * - * @return A map of positions to block states */ @BlockMarker fun SafeContext.blockSearch( @@ -104,17 +86,12 @@ fun SafeContext.blockSearch( annotation class BlockEntityMarker /** - * Search for block entities matching the filter - * - * ```kotlin + * Example + * ``` * val blockEntities = blockEntitySearch(range = 10.0) { * !it.isRemoved // Filter out existing block entities * } * ``` - * - * @param range The range around the position to search for entities - * @param pos The position to start the search from - * @param filter The predicate to filter entities */ @BlockEntityMarker inline fun SafeContext.blockEntitySearch( @@ -127,20 +104,12 @@ inline fun SafeContext.blockEntitySearch( annotation class EntityMarker /** - * Initiates an entity search operation in the world at the specified position - * * Example: * ```kotlin * val closestPlayer = entitySearch(range = 20.0) { * it.isAlive // Filter out dead entities * } * ``` - * - * @param range The range around the position to search for entities - * @param pos The position to start the search from - * @param filter The predicate to filter entities - * - * @return The closest entity to the position [pos] */ @EntityMarker inline fun SafeContext.closestEntity( @@ -152,20 +121,12 @@ inline fun SafeContext.closestEntity( .minByOrNull { pos distSq it.pos } /** - * Initiates an entity search operation in the world at the specified position - * * Example: * ```kotlin * val entities = entitySearch(range = 20.0) { * it.isAlive // Filter out dead entities * } * ``` - * - * @param range The range around the position to search for entities - * @param pos The position to start the search from - * @param filter The predicate to filter entities - * - * @return A list of entity [T] */ @EntityMarker inline fun SafeContext.entitySearch( @@ -175,21 +136,13 @@ inline fun SafeContext.entitySearch( ) = internalGetEntities(pos.toFastVec(), range, filter = filter) /** - * Initiates an optimized entity search operation in the world at the specified position - * * Example: - * ```kotlin + * ``` * val entities = fastEntitySearch(range = 10.0) { * it.isAlive && // Filter out dead entities * it.isGlowing // Filter out entities that are not glowing * } * ``` - * - * @param range The range around the position to search for entities - * @param pos The position to start the search from - * @param filter The predicate to filter entities - * - * @return A sequence of [T] */ @EntityMarker inline fun SafeContext.fastEntitySearch( @@ -202,8 +155,6 @@ inline fun SafeContext.fastEntitySearch( annotation class FluidMarker /** - * Searches for fluids in the world - * * Example: * ```kotlin * val fluids = fluidSearch(range = Vec3i(8.0, 3.0, 8.0)) { // Search for fluids within a box of (8, 3, 8) @@ -214,13 +165,6 @@ annotation class FluidMarker * println("Found still lava at $pos with state $state") * } * ``` - * - * @param range The `x`, `y`, `z` range around the position to search for fluids - * @param pos The position around which to search for fluids - * @param step The `x`, `y`, `z` step intervals at which to check for fluids - * @param filter The predicate to filter fluids. - * - * @return A map of positions to fluid states */ @FluidMarker inline fun SafeContext.fluidSearch( @@ -237,8 +181,6 @@ inline fun SafeContext.fluidSearch( .mapKeys { it.key.toBlockPos() } /** - * Searches for fluids in the world - * * Example: * ```kotlin * val fluids = fluidSearch(range = 8.0) { // Search for fluids in a range of 8 blocks @@ -249,13 +191,6 @@ inline fun SafeContext.fluidSearch( * println("Found still lava at $pos with state $state") * } * ``` - * - * @param range The range around the position to search for fluids - * @param pos The position around which to search for fluids - * @param step The step intervals at which to check for fluids - * @param filter The predicate to filter fluids. - * - * @return A map of positions to fluid states */ @FluidMarker inline fun SafeContext.fluidSearch( diff --git a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index 14adfe70a..0e499c2a7 100644 --- a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -34,24 +34,11 @@ import kotlin.math.ceil import kotlin.sequences.filter object WorldUtils { - fun SafeContext.isLoaded(pos: BlockPos) = - world.chunkManager.isChunkLoaded( - ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z) - ) - /** - * Gets all entities of type [T] within a specified distance from a position. - * - * This function retrieves entities of type [T] within a specified distance from a given position. - * It efficiently queries nearby chunks based on the distance and returns a list of matching entities. - * - * Note: This implementation is optimized for performance at small distances - * For distances larger than 64 blocks, it is recommended to use the [internalGetEntities] function instead + * Returns a sequence of entities. * - * @param pos The position to search from - * @param distance The maximum distance to search for entities - * @param filter The lambda to filter entities - * @return A sequence of [T] + * This implementation is optimized for performance at small distances. + * For distances larger than 64 blocks, it is recommended to use the [internalGetEntities] function instead. * * @see [fastEntitySearch] */ @@ -88,16 +75,9 @@ object WorldUtils { } /** - * Gets all entities of type [T] within a specified distance from a position. - * - * This function retrieves entities of type [T] within a specified distance from a given position. + * Returns a sequence of entities. * Unlike [internalGetFastEntities], it traverses all entities in the world to find matches. * - * @param pos The block position to search from. - * @param distance The maximum distance to search for entities. - * @param filter The lambda to filter entities. - * @return A sequence of [T] - * * @see [entitySearch] */ inline fun SafeContext.internalGetEntities( @@ -114,13 +94,7 @@ object WorldUtils { } /** - * Gets all block entities of type [T] within a specified distance from a position. - * - * @param pos The block position to search from. - * @param distance The maximum distance to search for entities. - * @param filter The lambda to filter block entities. - * @return A sequence of [T]ç - * + * Returns a sequence of block entities. * @see [blockEntitySearch] */ inline fun SafeContext.internalGetBlockEntities( @@ -153,12 +127,7 @@ object WorldUtils { } /** - * Returns a filtered map of block state positions. - * - * @param pos The position to search from. - * @param range The maximum distance to search for entities in each axis. - * @param filter The lambda to filter blocks. - * + * Returns a map of positions to block states. * @see [blockSearch] */ inline fun SafeContext.internalSearchBlocks( @@ -175,12 +144,7 @@ object WorldUtils { /** - * Returns a filtered map of fluid positions. - * - * @param pos The position to search from. - * @param range The maximum distance to search for fluids in each axis. - * @param filter The lambda to filter fluids. - * + * Returns a map of positions to fluid states. * @see [fluidSearch] */ inline fun SafeContext.internalSearchFluids( diff --git a/src/test/kotlin/RangeTest.kt b/src/test/kotlin/RangeTest.kt index 61c961310..2ce93f734 100644 --- a/src/test/kotlin/RangeTest.kt +++ b/src/test/kotlin/RangeTest.kt @@ -50,19 +50,17 @@ class RangeTest { @Test fun `test step over double range`() { - val range = 0.0..10.0 - val iterator = range.step(2.0) + val range = 0.0..10.0 step 2.0 - val result = iterator.asSequence().toList() + val result = range.asSequence().toList() assertEquals(listOf(0.0, 2.0, 4.0, 6.0, 8.0, 10.0), result) } @Test fun `test step over float range`() { - val range = 0.0f..10.0f - val iterator = range.step(2.0f) + val range = 0.0f..10.0f step 2.0f - val result = iterator.asSequence().toList() + val result = range.asSequence().toList() assertEquals(listOf(0.0f, 2.0f, 4.0f, 6.0f, 8.0f, 10.0f), result) } From 6f09bfc58421d8fcfb985ed843aedf11038a42ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emy=20=F0=9F=92=9C?= Date: Mon, 10 Nov 2025 17:51:35 -0500 Subject: [PATCH 4/8] more ktdoc cleanup --- .../com/lambda/module/ModuleRegistry.kt | 3 - .../kotlin/com/lambda/network/CapeManager.kt | 2 - .../network/api/v1/endpoints/GetCape.kt | 2 - .../network/api/v1/endpoints/GetCapes.kt | 4 - .../network/api/v1/endpoints/LinkDiscord.kt | 2 - .../lambda/network/api/v1/endpoints/Login.kt | 2 - .../network/api/v1/endpoints/SetCape.kt | 2 - .../network/api/v1/models/Authentication.kt | 6 -- .../lambda/network/api/v1/models/Player.kt | 6 -- .../com/lambda/network/mojang/GetProfile.kt | 4 - src/main/kotlin/com/lambda/threading/Hook.kt | 54 +----------- .../kotlin/com/lambda/threading/Threading.kt | 87 ++++++------------- 12 files changed, 28 insertions(+), 146 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/ModuleRegistry.kt b/src/main/kotlin/com/lambda/module/ModuleRegistry.kt index 0f570e199..d0d0dd3d4 100644 --- a/src/main/kotlin/com/lambda/module/ModuleRegistry.kt +++ b/src/main/kotlin/com/lambda/module/ModuleRegistry.kt @@ -20,9 +20,6 @@ package com.lambda.module import com.lambda.core.Loadable import com.lambda.util.reflections.getInstances -/** - * The [ModuleRegistry] object is responsible for managing all [Module] instances in the system. - */ object ModuleRegistry : Loadable { override val priority = 1 diff --git a/src/main/kotlin/com/lambda/network/CapeManager.kt b/src/main/kotlin/com/lambda/network/CapeManager.kt index 2a0ea7200..5aa84fb6a 100644 --- a/src/main/kotlin/com/lambda/network/CapeManager.kt +++ b/src/main/kotlin/com/lambda/network/CapeManager.kt @@ -53,7 +53,6 @@ import kotlin.time.Duration.Companion.seconds @Suppress("JavaIoSerializableObjectMustHaveReadResolve") object CapeManager : ConcurrentHashMap(), Loadable { - // We want to cache images to reduce class B requests private val images = capes.walk() .filter { it.extension == "png" } .associate { it.nameWithoutExtension to NativeImageBackedTexture({ it.nameWithoutExtension }, read(it.inputStream())) } @@ -61,7 +60,6 @@ object CapeManager : ConcurrentHashMap(), Loadable { private val fetchQueue = mutableListOf() - // We want to cache the cape list to reduce class B requests val capeList = runBlocking { capes.resolveFile("capes.txt") .isOlderThan(24.hours) { diff --git a/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt b/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt index 97830afae..c5af133cb 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt @@ -30,8 +30,6 @@ import java.util.* * * Example: * - id: ab24f5d6-dcf1-45e4-897e-b50a7c5e7422 - * - * @return results of cape */ suspend fun getCape(uuid: UUID) = runCatching { LambdaHttp.get("$apiUrl/api/$apiVersion/cape?id=$uuid").body() diff --git a/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCapes.kt b/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCapes.kt index 5d6fd4548..e91839b9e 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCapes.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCapes.kt @@ -33,8 +33,6 @@ import java.util.* * - id: ab24f5d6-dcf1-45e4-897e-b50a7c5e7422 * - id: 4f332cd7-cf93-427e-a282-53f45f6bb113 * - id: fdee323e-7f0c-4c15-8d1c-0f277442342a - * - * @return results of capes */ suspend fun getCapes(vararg uuid: UUID) = getCapes(uuid.toList()) @@ -45,8 +43,6 @@ suspend fun getCapes(vararg uuid: UUID) = getCapes(uuid.toList()) * - id: ab24f5d6-dcf1-45e4-897e-b50a7c5e7422 * - id: 4f332cd7-cf93-427e-a282-53f45f6bb113 * - id: fdee323e-7f0c-4c15-8d1c-0f277442342a - * - * @return results of capes */ suspend fun getCapes(uuids: List) = runCatching { LambdaHttp.get("$apiUrl/api/$apiVersion/capes") { diff --git a/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt b/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt index a7399decf..8fe72d773 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt @@ -31,8 +31,6 @@ import io.ktor.http.* * * Example: * - token: OTk1MTU1NzcyMzYxMTQ2NDM4 - * - * @return result of [Authentication] */ suspend fun linkDiscord(discordToken: String) = runCatching { LambdaHttp.post("${apiUrl}/api/$apiVersion/link/discord") { diff --git a/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt b/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt index 2ae778ba5..aafe55eb8 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt @@ -31,8 +31,6 @@ import io.ktor.http.* * Example: * - username: Notch * - hash: 069a79f444e94726a5befca90e38aaf5 - * - * @return result of [Authentication] */ suspend fun login(username: String, hash: String) = runCatching { LambdaHttp.post("${apiUrl}/api/$apiVersion/login") { diff --git a/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt b/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt index 4e8c18725..e2108754e 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt @@ -29,8 +29,6 @@ import io.ktor.http.* * * Example: * - id: galaxy - * - * @return nothing */ suspend fun setCape(id: String) = runCatching { val resp = LambdaHttp.put("$apiUrl/api/$apiVersion/cape?id=$id") { diff --git a/src/main/kotlin/com/lambda/network/api/v1/models/Authentication.kt b/src/main/kotlin/com/lambda/network/api/v1/models/Authentication.kt index 5e69b148a..a8f1eb121 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/models/Authentication.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/models/Authentication.kt @@ -20,18 +20,12 @@ package com.lambda.network.api.v1.models import com.google.gson.annotations.SerializedName data class Authentication( - // The access token to use for the API - // example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c @SerializedName("access_token") val accessToken: String, - // The duration of the token (in seconds). - // example: 3600 @SerializedName("expires_in") val expiresIn: Long, - // The type of the token. - // example: Bearer @SerializedName("token_type") val tokenType: String, ) { diff --git a/src/main/kotlin/com/lambda/network/api/v1/models/Player.kt b/src/main/kotlin/com/lambda/network/api/v1/models/Player.kt index 216a68b98..c01ab8aaa 100644 --- a/src/main/kotlin/com/lambda/network/api/v1/models/Player.kt +++ b/src/main/kotlin/com/lambda/network/api/v1/models/Player.kt @@ -21,18 +21,12 @@ import com.google.gson.annotations.SerializedName import java.util.* data class Player( - // The player's name. - // example: Notch @SerializedName("name") val name: String, - // The player's UUID. - // example: 069a79f4-44e9-4726-a5be-fca90e38aaf5 @SerializedName("id") val uuid: UUID, - // The player's Discord ID. - // example: "385441179069579265" @SerializedName("discord_id") val discordId: String, diff --git a/src/main/kotlin/com/lambda/network/mojang/GetProfile.kt b/src/main/kotlin/com/lambda/network/mojang/GetProfile.kt index 934cf26ca..10bfceed4 100644 --- a/src/main/kotlin/com/lambda/network/mojang/GetProfile.kt +++ b/src/main/kotlin/com/lambda/network/mojang/GetProfile.kt @@ -28,8 +28,6 @@ import java.util.* * * Example: * - name: jeb_ - * - * @return result of [GameProfile] */ suspend fun getProfile(name: String) = runCatching { LambdaHttp.get("https://api.mojang.com/users/profiles/minecraft/$name").body() @@ -40,8 +38,6 @@ suspend fun getProfile(name: String) = runCatching { * * Example: * - name: ab24f5d6-dcf1-45e4-897e-b50a7c5e7422 - * - * @return result of [GameProfile] */ suspend fun getProfile(uuid: UUID) = runCatching { LambdaHttp.get("https://api.minecraftservices.com/minecraft/profile/lookup/$uuid").body() diff --git a/src/main/kotlin/com/lambda/threading/Hook.kt b/src/main/kotlin/com/lambda/threading/Hook.kt index 4ab2559c2..92931cc69 100644 --- a/src/main/kotlin/com/lambda/threading/Hook.kt +++ b/src/main/kotlin/com/lambda/threading/Hook.kt @@ -21,59 +21,9 @@ import kotlin.concurrent.thread /** * Registers a shutdown hook to execute the specified [block] of code when the application is shutting down. - * This function ensures that the given [block] will be executed before the JVM terminates, allowing graceful cleanup - * or finalization tasks to be performed. * - * @param block the code to be executed on shutdown. - * - * ## Usage: - * ``` - * onShutdown { - * // Perform cleanup tasks or finalizations here - * println("Shutting down gracefully...") - * } - * ``` - * - * ## Advantages: - * - Provides a convenient and reliable mechanism for executing cleanup tasks when the application exits. - * - Helps ensure that critical resources are released properly, preventing resource leaks or data corruption. - * - Can be used to handle cleanup in various scenarios, such as closing open connections, saving application state, - * or logging shutdown events. - * - * ## Code Examples: - * 1. Registering a simple shutdown hook: - * ``` - * onShutdown { - * println("Performing cleanup...") - * } - * ``` - * 2. Performing resource cleanup on shutdown: - * ``` - * onShutdown { - * databaseConnection.close() - * fileWriter.close() - * println("Resources closed successfully.") - * } - * ``` - * - * ## What Not to Do: - * - Avoid performing time-consuming or blocking operations inside the shutdown hook, as it may delay the shutdown process - * and lead to undesirable behavior. - * - Do not rely solely on shutdown hooks for critical tasks that must be executed reliably. Consider using other - * mechanisms such as proper exception handling or manual cleanup where necessary. - * - Avoid registering multiple shutdown hooks for the same purpose, as it may result in unexpected behavior or conflicts - * between the hooks. - * - * ## Edge Cases: - * - If the JVM is terminated abruptly or forcefully (e.g., via `kill -9` on Unix-like systems), the shutdown hooks may - * not have a chance to run, leading to potential resource leaks or incomplete cleanup. This is a rare scenario but - * should be considered when designing critical cleanup tasks. - * - In certain environments or configurations where the JVM shutdown process is interrupted or bypassed, such as when - * running in a containerized environment where the container itself is forcibly stopped, the shutdown hooks may not - * execute as expected. It's important to be aware of the behavior of the runtime environment and handle such cases - * accordingly, possibly by implementing additional cleanup mechanisms or relying on external systems for graceful - * shutdown coordination. - */ + * @see Design of the Shutdown Hooks API +*/ inline fun onShutdown(crossinline block: () -> Unit) { Runtime.getRuntime().addShutdownHook(thread(start = false) { block() }) } diff --git a/src/main/kotlin/com/lambda/threading/Threading.kt b/src/main/kotlin/com/lambda/threading/Threading.kt index 15583efcd..a23a200c6 100644 --- a/src/main/kotlin/com/lambda/threading/Threading.kt +++ b/src/main/kotlin/com/lambda/threading/Threading.kt @@ -31,38 +31,41 @@ import kotlinx.coroutines.launch import java.util.concurrent.CompletableFuture /** - * Executes a block of code only if the context is safe. A context is considered safe when all the following properties are not null: + * Runs the [block] in a safe context. + * + * A context is considered safe when all the following properties are not null: * - [SafeContext.world] * - [SafeContext.player] * - [SafeContext.interaction] * - [SafeContext.connection] - * - * If the context is not safe, the function will return null and the block of code will not be executed. - * - * @param block The block of code to be executed within the safe context. - * @return The result of the block execution if the context is safe, null otherwise. */ inline fun runSafe(block: SafeContext.() -> T): T? = SafeContext.create()?.run(block) +/** + * Runs the [block] in an automated context. + * + * The context contains various settings for various systems. + */ @JvmName("runSafeAutomated0") context(safeContext: SafeContext) inline fun Automated.runSafeAutomated(automated: Automated = this, block: AutomatedSafeContext.() -> T): T = AutomatedSafeContext(safeContext, automated).run(block) +/** + * Runs the [block] in an automated context. + * + * The context contains various settings for various systems. + */ @JvmName("runSafeAutomated1") inline fun Automated.runSafeAutomated(block: AutomatedSafeContext.() -> T): T? { return AutomatedSafeContext(SafeContext.create() ?: return null, this).run(block) } /** - * This function is used to execute a block of code on a new thread running asynchronously to the game thread. - * It should only be used when you need to perform read actions on the game data (not write). - * - * Caution: Using this function to write to the game data can lead to race conditions. Therefore, it is recommended - * to use this function only for read operations to avoid potential concurrency issues. + * Runs the [block] in a coroutine. * - * @param block The block of code to be executed concurrently. + * Writing to game data is discouraged as it may cause race conditions. */ inline fun runConcurrent(scheduler: CoroutineDispatcher = Dispatchers.Default, crossinline block: suspend CoroutineScope.() -> Unit) = EventFlow.lambdaScope.launch(scheduler) { @@ -80,16 +83,15 @@ inline fun taskContext(crossinline block: suspend CoroutineScope.() -> Unit) = } /** - * This function is used to execute a block of code within a safe context on a new thread running asynchronously to the game thread. + * Runs the [block] within a safe context in a coroutine. + * * A context is considered safe when all the following properties are not null: * - [SafeContext.world] * - [SafeContext.player] * - [SafeContext.interaction] * - [SafeContext.connection] * - * If the context is not safe, the function will not execute the block of code. - * - * @param block The block of code to be executed within the safe context. + * Writing to game data is discouraged as it may cause race conditions. */ inline fun runSafeConcurrent(crossinline block: suspend SafeContext.() -> Unit) { EventFlow.lambdaScope.launch { @@ -98,40 +100,17 @@ inline fun runSafeConcurrent(crossinline block: suspend SafeContext.() -> Unit) } /** - * Executes a given task when the render procedure is available. - * - * This function is used when a task needs to be performed when the render thread is ready - * to be used as multiple threads are used simultaneously and the OpenGL context is only available - * on one thread + * Executes a given task before a new render tick begins. * - * Note: This function is non-blocking as the task is scheduled to be executed - * on the game's main thread, but does not provide any feedback. - * - * @param block The task to be executed on the game's main thread. + * This function should only be used for synchronization of threads that wish to dispatch + * to OpenGL. */ inline fun recordRenderCall(crossinline block: () -> Unit) { mc.renderTaskQueue.add { block() } } /** - * Executes a given task on the game's main thread. - * - * This function is used when a task needs to be performed on the game's main thread, - * as certain operations are not safe to perform on other threads. - * It uses the Minecraft client's `execute` method to schedule the task. - * - * ## Execution Flow: - * 1. If already on the render thread: Executes the task immediately (zero overhead). - * 2. Otherwise, schedules the task via Minecraft's [net.minecraft.util.thread.ThreadExecutor]: - * a. The task is wrapped in a Runnable and added to a thread-safe queue - * b. `LockSupport.unpark` wakes the game thread if it was parked - * - * [java.util.concurrent.locks.LockSupport.unpark] will unblock the permit available and allow for execution on that specific thread - * - * Note: This function is non-blocking as the task is scheduled to be executed - * on the game's main thread, but does not provide any feedback. - * - * @param block The task to be executed on the game's main thread. + * Schedules or executes the [block] on the main thread. */ inline fun runGameScheduled(crossinline block: () -> Unit) { if (isOnRenderThread()) { @@ -143,21 +122,13 @@ inline fun runGameScheduled(crossinline block: () -> Unit) { } /** - * Executes a given task on the game's main thread within a safe context. + * Schedules a task on the main thread within a safe context. + * * A context is considered safe when all the following properties are not null: * - [SafeContext.world] * - [SafeContext.player] * - [SafeContext.interaction] * - [SafeContext.connection] - * - * This function is used when a task needs to be performed on the game's main thread, - * as certain operations are not safe to perform on other threads. - * It uses the Minecraft client's `execute` method to schedule the task. - * - * Note: This function is non-blocking as the task is scheduled to be executed - * on the game's main thread, but does not provide any feedback. - * - * @param block The task to be executed on the game's main thread within a safe context. */ inline fun runSafeGameScheduled(crossinline block: SafeContext.() -> Unit) { runGameScheduled { runSafe { block() } } @@ -166,20 +137,14 @@ inline fun runSafeGameScheduled(crossinline block: SafeContext.() -> Unit) { /** * Executes a given task on the game's main thread within a safe context * and blocks the coroutine until the task is completed. + * * A context is considered safe when all the following properties are not null: * - [SafeContext.world] * - [SafeContext.player] * - [SafeContext.interaction] * - [SafeContext.connection] * - * This function is used when a task needs to be performed on the game's main thread, - * as certain operations are not safe to perform on other threads. - * - * Note: - * This function is blocking - * as it uses [CompletableFuture]'s [await] method to [suspend] the coroutine until the task is completed. - * - * @param block The task to be executed on the game's main thread within a safe context. + * This function blocks until the task is completed. */ suspend inline fun awaitMainThread(noinline block: SafeContext.() -> T) = CompletableFuture.supplyAsync({ runSafe { block() } }, mc).await() ?: throw IllegalStateException("Unsafe") From f424726e43b50bab1b24439693d49f4e9f58dce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emy=20=F0=9F=92=9C?= Date: Mon, 10 Nov 2025 18:10:25 -0500 Subject: [PATCH 5/8] module ktdoc cleanup --- .../kotlin/com/lambda/config/Configurable.kt | 232 +----------------- .../config/settings/complex/KeybindSetting.kt | 16 +- .../kotlin/com/lambda/module/HudModule.kt | 2 +- src/main/kotlin/com/lambda/module/Module.kt | 52 ++-- 4 files changed, 43 insertions(+), 259 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/Configurable.kt b/src/main/kotlin/com/lambda/config/Configurable.kt index cc7d4ffee..53b55e7ea 100644 --- a/src/main/kotlin/com/lambda/config/Configurable.kt +++ b/src/main/kotlin/com/lambda/config/Configurable.kt @@ -91,20 +91,6 @@ abstract class Configurable( } } - /** - * Creates a [BooleanSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Boolean] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * ```kotlin - * private val foo by setting("Foo", true) - * ``` - * - * @return The created [BooleanSetting]. - */ fun setting( name: String, defaultValue: Boolean, @@ -112,22 +98,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = BooleanSetting(name, defaultValue, description, visibility).register() - /** - * Creates an [EnumSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Enum] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * ```kotlin - * enum class Foo { A, B, C } - * private val foo by setting("Foo", Foo.A) - * ``` - * - * - * @return The created [EnumSetting]. - */ inline fun > setting( name: String, defaultValue: T, @@ -136,16 +106,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = EnumSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [CharSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Char] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [CharSetting]. - */ fun setting( name: String, defaultValue: Char, @@ -153,20 +113,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = CharSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [StringSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [String] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * ```kotlin - * private val foo by setting("Foo", "bar") - * ``` - * - * @return The created [StringSetting]. - */ fun setting( name: String, defaultValue: String, @@ -176,23 +122,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = StringSetting(name, defaultValue, multiline, flags, description, visibility).register() - /** - * Constructs a [ListSetting] instance with the specified parameters and appends it to the [settings] collection. - * - * The type parameter [T] must either be a primitive type or a type with a registered type adapter in [Lambda.gson]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [List] value of type [T] for the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * ```kotlin - * // the parameter type is inferred from the defaultValue - * private val foo by setting("Foo", arrayListOf("bar", "baz")) - * ``` - * - * @return The created [ListSetting]. - */ inline fun setting( name: String, immutableList: List, @@ -208,23 +137,6 @@ abstract class Configurable( visibility, ).register() - /** - * Constructs a [MapSetting] instance with the specified parameters and appends it to the [settings] collection. - * - * The type parameter [K] and [V] must either be a primitive type or a type with a registered type adapter in [Lambda.gson]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Map] value of type [K] and [V] for the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * ```kotlin - * // the parameter types are inferred from the defaultValue - * private val foo by setting("Foo", mapOf("bar" to 1, "baz" to 2)) - * ``` - * - * @return The created [MapSetting]. - */ inline fun setting( name: String, defaultValue: Map, @@ -238,23 +150,6 @@ abstract class Configurable( visibility ).register() - /** - * Constructs a [SetSetting] instance with the specified parameters and appends it to the [settings] collection. - * - * The type parameter [T] must either be a primitive type or a type with a registered type adapter in [Lambda.gson]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Set] value of type [T] for the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * ```kotlin - * // the parameter type is inferred from the defaultValue - * private val foo by setting("Foo", setOf("bar", "baz")) - * ``` - * - * @return The created [SetSetting]. - */ inline fun setting( name: String, immutableList: Set, @@ -270,21 +165,6 @@ abstract class Configurable( visibility, ).register() - /** - * Creates a [DoubleSetting] with the provided parameters and adds it to the [settings]. - * - * The value of the setting is coerced into the specified [range] and rounded to the nearest [step]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Double] value of the setting. - * @param range The range within which the setting's value must fall. - * @param step The step to which the setting's value is rounded. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * @param unit The unit of the setting. E.g. "°C", "m/s", "ms", "ticks", etc. - * - * @return The created [DoubleSetting]. - */ fun setting( name: String, defaultValue: Double, @@ -295,21 +175,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = DoubleSetting(name, defaultValue, range, step, description, unit, visibility).register() - /** - * Creates a [FloatSetting] with the provided parameters and adds it to the [settings]. - * - * The value of the setting is coerced into the specified [range] and rounded to the nearest [step]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Float] value of the setting. - * @param range The range within which the setting's value must fall. - * @param step The step to which the setting's value is rounded. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * @param unit The unit of the setting. E.g. "°C", "m/s", "ms", "ticks", etc. - * - * @return The created [FloatSetting]. - */ fun setting( name: String, defaultValue: Float, @@ -320,21 +185,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = FloatSetting(name, defaultValue, range, step, description, unit, visibility).register() - /** - * Creates an [IntegerSetting] with the provided parameters and adds it to the [settings]. - * - * The value of the setting is coerced into the specified [range] and rounded to the nearest [step]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Int] value of the setting. - * @param range The range within which the setting's value must fall. - * @param step The step to which the setting's value is rounded. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * @param unit The unit of the setting. E.g. "°C", "m/s", "ms", "ticks", etc. - * - * @return The created [IntegerSetting]. - */ fun setting( name: String, defaultValue: Int, @@ -345,21 +195,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = IntegerSetting(name, defaultValue, range, step, description, unit, visibility).register() - /** - * Creates a [LongSetting] with the provided parameters and adds it to the [settings]. - * - * The value of the setting is coerced into the specified [range] and rounded to the nearest [step]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Long] value of the setting. - * @param range The range within which the setting's value must fall. - * @param step The step to which the setting's value is rounded. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * @param unit The unit of the setting. E.g. "°C", "m/s", "ms", "ticks", etc. - * - * @return The created [LongSetting]. - */ fun setting( name: String, defaultValue: Long, @@ -370,16 +205,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = LongSetting(name, defaultValue, range, step, description, unit, visibility).register() - /** - * Creates a [KeybindSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [KeyCode] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [KeybindSetting]. - */ fun setting( name: String, defaultValue: Bind, @@ -387,16 +212,13 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = KeybindSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [ColorSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Color] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [ColorSetting]. - */ + fun setting( + name: String, + defaultValue: KeyCode, + description: String = "", + visibility: () -> Boolean = { true }, + ) = KeybindSetting(name, defaultValue, description, visibility).register() + fun setting( name: String, defaultValue: Color, @@ -404,16 +226,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = ColorSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [Vec3dSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Vec3d] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [Vec3dSetting]. - */ fun setting( name: String, defaultValue: Vec3d, @@ -421,16 +233,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = Vec3dSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [BlockPosSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [BlockPos.Mutable] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [BlockPosSetting]. - */ fun setting( name: String, defaultValue: BlockPos.Mutable, @@ -438,16 +240,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = BlockPosSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [BlockPosSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [BlockPos] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [BlockPosSetting]. - */ fun setting( name: String, defaultValue: BlockPos, @@ -455,16 +247,6 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = BlockPosSetting(name, defaultValue, description, visibility).register() - /** - * Creates a [BlockSetting] with the provided parameters and adds it to the [settings]. - * - * @param name The unique identifier for the setting. - * @param defaultValue The default [Block] value of the setting. - * @param description A brief explanation of the setting's purpose and behavior. - * @param visibility A lambda expression that determines the visibility status of the setting. - * - * @return The created [BlockSetting]. - */ fun setting( name: String, defaultValue: Block, diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt index c7fd0c285..2c9b6960f 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -27,7 +27,6 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.optional import com.lambda.brigadier.required import com.lambda.config.AbstractSetting -import com.lambda.config.settings.complex.Bind.Companion.mouseBind import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.util.InputUtils import com.lambda.util.KeyCode @@ -60,6 +59,9 @@ class KeybindSetting( description, visibility ) { + constructor(name: String, defaultValue: KeyCode, description: String, visibility: () -> Boolean) + : this(name, Bind(defaultValue.code, 0, -1), description, visibility) + private var listening = false override fun ImGuiBuilder.buildLayout() { @@ -91,7 +93,7 @@ class KeybindSetting( sameLine() smallButton("Unbind") { - value = Bind.EMPTY + value = Bind.Empty listening = false } onItemHover(ImGuiHoveredFlags.Stationary) { @@ -114,7 +116,7 @@ class KeybindSetting( if ((it.isPressed && !isModKey) || (it.isReleased && isModKey)) { when (it.translated) { KeyCode.Escape -> {} - KeyCode.Backspace, KeyCode.Delete -> value = Bind.EMPTY + KeyCode.Backspace, KeyCode.Delete -> value = Bind.Empty else -> value = Bind(it.keyCode, it.modifiers, -1) } @@ -136,14 +138,14 @@ class KeybindSetting( optional(boolean("mouse button")) { isMouseButton -> executeWithResult { val isMouse = if (isMouseButton != null) isMouseButton().value() else false - var bind = Bind.EMPTY + var bind = Bind.Empty if (isMouse) { val num = try { name().value().toInt() } catch(_: NumberFormatException) { return@executeWithResult failure("${name().value()} doesn't match with a mouse button") } - bind = mouseBind(num) + bind = Bind(0, 0, mouse = num) } else { bind = try { Bind(KeyCode.valueOf(name().value()).code, 0) @@ -191,8 +193,6 @@ data class Bind( "Key Code: $key, Modifiers: ${truemods.joinToString(separator = "+") { it.name }}, Mouse Button: ${Mouse.entries.getOrNull(mouse) ?: "None"}" companion object { - val EMPTY = Bind(0, 0) - - fun mouseBind(code: Int) = Bind(0, 0, code) + val Empty = Bind(0, 0, -1) } } diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index 5c26f37cd..28ce5042e 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -29,7 +29,7 @@ abstract class HudModule( val customWindow: Boolean = false, alwaysListening: Boolean = false, enabledByDefault: Boolean = false, - defaultKeybind: Bind = Bind.EMPTY, + defaultKeybind: Bind = Bind.Empty, ) : Module(name, description, tag, alwaysListening, enabledByDefault, defaultKeybind), Layout { val backgroundColor by setting("Background Color", Color(0, 0, 0, 0)) val outline by setting("Show Outline", false) diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index b64b2b5a2..8e1d8e673 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -60,25 +60,34 @@ import com.lambda.util.Nameable * the default [keybind] should not be set (using [KeyCode.Unbound]). * * [Module]s are [Configurable]s with [settings] (see [AbstractSetting] for all setting types). - * For example, a [BooleanSetting] and a [DoubleSetting] can be defined like this: - * ```kotlin + * Example: + * ``` * private val foo by setting("Foo", true) * private val bar by setting("Bar", 0.0, 0.1..5.0, 0.1) * ``` + * * These settings are persisted in the `lambda/config/modules.json` config file. * See [ModuleConfig.primary] and [Configuration] for more details. * - * In the `init` block, you can add triggers like [onEnable], [onDisable], [onToggle] and register [Listener]. - * For example: + * In the `init` block, you can add hooks like [onEnable], [onDisable], [onToggle] and add listeners. * - * ```kotlin + * Example: + * ``` * init { * onEnable { // runs on module activation * LOG.info("I was enabled!") * } * - * listener { event -> // runs every game tick - * LOG.info("I'm ${if (foo) "super boring ($bar)" else "boring"}!") + * onToggle { to -> + * LOG.info("Module enabled: ${to}") + * } + * + * onDisable { + * LOG.info("I was disable!") + * } + * + * listener { event -> + * LOG.info("I've ticked!") * } * } * ``` @@ -88,35 +97,28 @@ import com.lambda.util.Nameable * - [Module] was configured to [alwaysListening] * - [Listener] was configured to [Listener.alwaysListen] * - * For example: + * Example: + * ``` + * val bind1 = setting("Keybind", KeyCode.A) + * val bind2 = setting("Keybind", Bind(KeyCode.A.code, 0, -1)) * - * ```kotlin - * listener(alwaysListen = true) { event -> - * if (event.key == keybind.key) { - * toggle() - * } + * listen(alwaysListen = true) { event -> + * if (!event.satisfies(bind1) || !event.satisfies(bind2)) return@listen + * + * if (event.isPressed) toggle() + * else if (event.isReleased) disable() * } * ``` * * See [SafeListener] and [UnsafeListener] for more details. - * - * @property name The name of the module, displayed in-game. - * @property description The description of the module shown on hover over the module button in the GUI and in commands. - * @property tag The default [ModuleTag]s associated with the module. - * @property alwaysListening If true, the module's listeners will be triggered even if the module is not enabled. - * @property isEnabledSetting The setting that determines if the module is enabled. - * @property keybindSetting The setting that determines the keybind for the module. - * @property isEnabled The current enabled state of the module. - * @property isMuted If true, the module's listeners will not be triggered. - * @property keybind The current keybind for the module. - * */ + */ abstract class Module( override val name: String, val description: String = "", val tag: ModuleTag, private val alwaysListening: Boolean = false, enabledByDefault: Boolean = false, - defaultKeybind: Bind = Bind.EMPTY, + defaultKeybind: Bind = Bind.Empty, autoDisable: Boolean = false ) : Nameable, Muteable, Configurable(ModuleConfig), Automated by AutomationConfig { private val isEnabledSetting = setting("Enabled", enabledByDefault) { false } From 1aa30e2ba83cb092ab55754c69e8ece40ed907d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emy=20=F0=9F=92=9C?= Date: Mon, 10 Nov 2025 19:07:12 -0500 Subject: [PATCH 6/8] Fixed some stuff --- .../kotlin/com/lambda/command/commands/ModuleCommand.kt | 4 ++-- src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt | 6 +++--- .../kotlin/com/lambda/gui/components/ClickGuiLayout.kt | 3 +-- .../kotlin/com/lambda/module/modules/debug/StateInfo.kt | 5 +++-- src/main/kotlin/com/lambda/module/modules/player/Replay.kt | 2 +- .../kotlin/com/lambda/module/modules/player/Scaffold.kt | 7 ++++--- src/main/kotlin/com/lambda/util/math/Linear.kt | 5 ++++- src/main/kotlin/com/lambda/util/world/WorldUtils.kt | 5 +++++ 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/lambda/command/commands/ModuleCommand.kt b/src/main/kotlin/com/lambda/command/commands/ModuleCommand.kt index b957a07f6..f1794394d 100644 --- a/src/main/kotlin/com/lambda/command/commands/ModuleCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/ModuleCommand.kt @@ -33,6 +33,7 @@ import com.lambda.util.Communication.info import com.lambda.util.Communication.joinToText import com.lambda.util.Communication.warn import com.lambda.util.StringUtils +import com.lambda.util.StringUtils.findSimilarStrings import com.lambda.util.extension.CommandBuilder import com.lambda.util.text.ClickEvents.suggestCommand import com.lambda.util.text.buildText @@ -93,8 +94,7 @@ object ModuleCommand : LambdaCommand( } literal("not found!") } - val similarModules = StringUtils.findSimilarStrings( - name, + val similarModules = name.findSimilarStrings( ModuleRegistry.moduleNames, 3 ) diff --git a/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt b/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt index e14e971b5..732371495 100644 --- a/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt @@ -40,9 +40,9 @@ sealed class KeyboardEvent { val action: Int, val modifiers: Int, ) : Event { - /** - * Maps the scancode to the US layout - */ + val bind: Bind + get() = Bind(keyCode, modifiers, -1) + val translated: KeyCode get() = KeyCode.virtualMapUS(keyCode, scanCode) diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index c76b4eeee..764844b1e 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -220,8 +220,7 @@ object ClickGuiLayout : Loadable, Configurable(GuiConfig) { listen(alwaysListen = true) { event -> if (!event.isPressed) return@listen if (mc.options.commandKey.isPressed) return@listen - if (keybind == KeyCode.Unbound) return@listen - if (event.translated != keybind) return@listen + if (!event.satisfies(keybind)) return@listen if (!open && mc.currentScreen != null) return@listen if (open && DearImGui.io.wantTextInput) return@listen diff --git a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt index e856049e5..a6e2dd817 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt @@ -42,8 +42,9 @@ object StateInfo : Module( init { listen { event -> - if (!event.isPressed) return@listen - if (event.keyCode != printBind.code) return@listen + if (!event.isPressed || + !event.satisfies(printBind)) return@listen + val crosshair = mc.crosshairTarget ?: return@listen if (crosshair !is BlockHitResult) return@listen info(blockState(crosshair.blockPos).betterToString()) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index f290f0979..97b3897ad 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -134,7 +134,7 @@ object Replay : Module( if (!it.isPressed) return@listen if (mc.currentScreen != null && !mc.options.commandKey.isPressed) return@listen - when (it.translated) { + when (it.bind) { record -> handleRecord() play -> handlePlay() cycle -> handlePlayModeCycle() diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 74f1f892a..ad38da463 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -21,6 +21,7 @@ import com.lambda.config.groups.BuildSettings import com.lambda.config.groups.HotbarSettings import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings +import com.lambda.config.settings.complex.Bind import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent @@ -60,7 +61,7 @@ object Scaffold : Module( private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player", unit = " blocks").group(Group.General) private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 }.group(Group.General) private val descend by setting("Descend", KeyCode.Unbound, "Lower the place position by one to allow the player to lower y level").group(Group.General) - private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != KeyCode.Unbound }.group(Group.General) + private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != Bind.Empty }.group(Group.General) override val buildConfig = BuildSettings(this, Group.Build).apply { editTyped(::pathing, ::stayInRange, ::collectDrops) { defaultValue(false) @@ -88,7 +89,7 @@ object Scaffold : Module( val playerSupport = player.blockPos.down() val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) if (alreadySupported) return@listen - val offset = if (isKeyPressed(descend.code)) descendAmount else 0 + val offset = if (isKeyPressed(descend.key)) descendAmount else 0 val beneath = playerSupport.down(offset) runSafeAutomated { scaffoldPositions(beneath) @@ -108,7 +109,7 @@ object Scaffold : Module( } listen { - if (descend.code != mc.options.sneakKey.boundKey.code) return@listen + if (descend.key != mc.options.sneakKey.boundKey.code) return@listen it.sneak = false } } diff --git a/src/main/kotlin/com/lambda/util/math/Linear.kt b/src/main/kotlin/com/lambda/util/math/Linear.kt index 1befe0e69..f9a30fdf6 100644 --- a/src/main/kotlin/com/lambda/util/math/Linear.kt +++ b/src/main/kotlin/com/lambda/util/math/Linear.kt @@ -24,7 +24,6 @@ import java.awt.Color import kotlin.math.max import kotlin.math.min import kotlin.random.Random -import kotlin.random.Random.Default.nextDouble infix fun ClosedRange.step(step: Double) = object : DoubleIterator() { private var next = start @@ -38,13 +37,17 @@ infix fun ClosedRange.step(step: Float) = object : FloatIterator() { override fun nextFloat() = next.also { next += step } } +@JvmName("randomDouble") fun ClosedRange.random(random: Random = Random) = start + (endInclusive - start) * random.nextDouble() +@JvmName("randomFloat") fun ClosedRange.random(random: Random = Random) = start + (endInclusive - start) * random.nextDouble() fun ClosedRange.normalize(value: Double): Double = transform(value, 0.0, 1.0) fun ClosedRange.normalize(value: Float): Float = transform(value, 0f, 1f) +@JvmName("invDouble") fun ClosedRange.inv() = endInclusive to start +@JvmName("invFloat") fun ClosedRange.inv() = endInclusive to start /** diff --git a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index 0e499c2a7..aa6d3eb01 100644 --- a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -34,6 +34,11 @@ import kotlin.math.ceil import kotlin.sequences.filter object WorldUtils { + fun SafeContext.isLoaded(pos: BlockPos) = + world.chunkManager.isChunkLoaded( + ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z) + ) + /** * Returns a sequence of entities. * From 055f7805b431ae9f24b0a4536dbaada13446aaa2 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Tue, 11 Nov 2025 00:08:54 +0000 Subject: [PATCH 7/8] Bind.EMPTY over Bind.Empty :3 --- .../com/lambda/config/settings/complex/KeybindSetting.kt | 8 ++++---- src/main/kotlin/com/lambda/module/HudModule.kt | 2 +- src/main/kotlin/com/lambda/module/Module.kt | 2 +- .../kotlin/com/lambda/module/modules/player/Scaffold.kt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt index 2c9b6960f..39b22e11d 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -93,7 +93,7 @@ class KeybindSetting( sameLine() smallButton("Unbind") { - value = Bind.Empty + value = Bind.EMPTY listening = false } onItemHover(ImGuiHoveredFlags.Stationary) { @@ -116,7 +116,7 @@ class KeybindSetting( if ((it.isPressed && !isModKey) || (it.isReleased && isModKey)) { when (it.translated) { KeyCode.Escape -> {} - KeyCode.Backspace, KeyCode.Delete -> value = Bind.Empty + KeyCode.Backspace, KeyCode.Delete -> value = Bind.EMPTY else -> value = Bind(it.keyCode, it.modifiers, -1) } @@ -138,7 +138,7 @@ class KeybindSetting( optional(boolean("mouse button")) { isMouseButton -> executeWithResult { val isMouse = if (isMouseButton != null) isMouseButton().value() else false - var bind = Bind.Empty + var bind = Bind.EMPTY if (isMouse) { val num = try { name().value().toInt() @@ -193,6 +193,6 @@ data class Bind( "Key Code: $key, Modifiers: ${truemods.joinToString(separator = "+") { it.name }}, Mouse Button: ${Mouse.entries.getOrNull(mouse) ?: "None"}" companion object { - val Empty = Bind(0, 0, -1) + val EMPTY = Bind(0, 0, -1) } } diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index 28ce5042e..5c26f37cd 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -29,7 +29,7 @@ abstract class HudModule( val customWindow: Boolean = false, alwaysListening: Boolean = false, enabledByDefault: Boolean = false, - defaultKeybind: Bind = Bind.Empty, + defaultKeybind: Bind = Bind.EMPTY, ) : Module(name, description, tag, alwaysListening, enabledByDefault, defaultKeybind), Layout { val backgroundColor by setting("Background Color", Color(0, 0, 0, 0)) val outline by setting("Show Outline", false) diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index 8e1d8e673..ffc5b0bb2 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -118,7 +118,7 @@ abstract class Module( val tag: ModuleTag, private val alwaysListening: Boolean = false, enabledByDefault: Boolean = false, - defaultKeybind: Bind = Bind.Empty, + defaultKeybind: Bind = Bind.EMPTY, autoDisable: Boolean = false ) : Nameable, Muteable, Configurable(ModuleConfig), Automated by AutomationConfig { private val isEnabledSetting = setting("Enabled", enabledByDefault) { false } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index ad38da463..4080c2aaf 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -61,7 +61,7 @@ object Scaffold : Module( private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player", unit = " blocks").group(Group.General) private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 }.group(Group.General) private val descend by setting("Descend", KeyCode.Unbound, "Lower the place position by one to allow the player to lower y level").group(Group.General) - private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != Bind.Empty }.group(Group.General) + private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != Bind.EMPTY }.group(Group.General) override val buildConfig = BuildSettings(this, Group.Build).apply { editTyped(::pathing, ::stayInRange, ::collectDrops) { defaultValue(false) From 93ab581528fc8321d91ea47860c00c9e7e448575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emy=20=F0=9F=92=9C?= Date: Wed, 12 Nov 2025 13:17:42 -0500 Subject: [PATCH 8/8] beanbag fucked up --- .../render/CapeFeatureRendererMixin.java | 2 +- .../kotlin/com/lambda/network/CapeManager.kt | 48 +++++++------------ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java b/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java index 5410e63e9..c1a319f3d 100644 --- a/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java @@ -33,7 +33,7 @@ public class CapeFeatureRendererMixin { @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/SkinTextures;capeTexture()Lnet/minecraft/util/Identifier;")) Identifier renderCape(Identifier original, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, PlayerEntityRenderState player, float f, float g) { - var entry = Lambda.getMc().getNetworkHandler().getPlayerListEntry(player.name); + var entry = Lambda.getMc().getNetworkHandler().getPlayerListEntry(player.name); // this will cause issues if we try to render while not in game if (entry == null) return original; var profile = entry.getProfile(); diff --git a/src/main/kotlin/com/lambda/network/CapeManager.kt b/src/main/kotlin/com/lambda/network/CapeManager.kt index 5aa84fb6a..117eca658 100644 --- a/src/main/kotlin/com/lambda/network/CapeManager.kt +++ b/src/main/kotlin/com/lambda/network/CapeManager.kt @@ -19,15 +19,16 @@ package com.lambda.network import com.lambda.Lambda.LOG import com.lambda.Lambda.mc -import com.lambda.context.SafeContext +import com.lambda.config.Configurable +import com.lambda.config.configurations.SecretsConfig import com.lambda.core.Loadable import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.network.api.v1.endpoints.getCape import com.lambda.network.api.v1.endpoints.getCapes import com.lambda.network.api.v1.endpoints.setCape +import com.lambda.threading.runGameScheduled import com.lambda.threading.runIO -import com.lambda.threading.runSafe import com.lambda.util.FileUtils.createIfNotExists import com.lambda.util.FileUtils.downloadCompare import com.lambda.util.FileUtils.downloadIfNotPresent @@ -44,23 +45,19 @@ import org.lwjgl.BufferUtils import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.concurrent.fixedRateTimer -import kotlin.io.path.extension -import kotlin.io.path.inputStream -import kotlin.io.path.nameWithoutExtension -import kotlin.io.path.walk import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds -@Suppress("JavaIoSerializableObjectMustHaveReadResolve") -object CapeManager : ConcurrentHashMap(), Loadable { - private val images = capes.walk() - .filter { it.extension == "png" } - .associate { it.nameWithoutExtension to NativeImageBackedTexture({ it.nameWithoutExtension }, read(it.inputStream())) } - .onEach { (key, value) -> mc.textureManager.registerTexture(key.asIdentifier, value) } +object CapeManager : Configurable(SecretsConfig), Loadable { + override val name: String = "capes" + var currentCape by setting("cape", "") + .onValueChangeUnsafe { _, to -> updateCape(to) } + + val cache = ConcurrentHashMap() private val fetchQueue = mutableListOf() - val capeList = runBlocking { + val availableCapes = runBlocking { capes.resolveFile("capes.txt") .isOlderThan(24.hours) { it.downloadIfNotPresent("${LambdaAPI.capes}.txt") @@ -72,26 +69,15 @@ object CapeManager : ConcurrentHashMap(), Loadable { } .createIfNotExists() .readText() - .split("\n") + .split(Regex("\\s+")) } - /** - * Sets the current player's cape - * - * @param block Lambda called once the coroutine completes, it contains the throwable if any - */ fun updateCape(cape: String, block: (Throwable?) -> Unit = {}) = runIO { setCape(cape).getOrThrow() - - runSafe { fetchCape(player.uuid) } + fetchCape(mc.gameProfile.id) }.invokeOnCompletion { block(it) } - /** - * Fetches the cape of the given player id - * - * @param block Lambda called once the coroutine completes, it contains the throwable if any - */ - fun SafeContext.fetchCape(uuid: UUID, block: (Throwable?) -> Unit = {}) = runIO { + fun fetchCape(uuid: UUID, block: (Throwable?) -> Unit = {}) = runIO { val cape = getCape(uuid).getOrNull() ?: return@runIO val bytes = capes.resolveFile("${cape.id}.png") @@ -105,12 +91,12 @@ object CapeManager : ConcurrentHashMap(), Loadable { val image = read(NativeImage.Format.RGBA, buffer) - mc.textureManager.registerTexture(cape.id.asIdentifier, NativeImageBackedTexture({ cape.id }, image)) + runGameScheduled { mc.textureManager.registerTexture(cape.id.asIdentifier, NativeImageBackedTexture({ cape.id }, image)) } - put(uuid, cape.id) + cache[uuid] = cape.id }.invokeOnCompletion { block(it) } - override fun load() = "Loaded ${images.size} cached capes and ${capeList.size} remote capes" + override fun load() = "Loaded ${availableCapes.size} capes" init { fixedRateTimer( @@ -122,7 +108,7 @@ object CapeManager : ConcurrentHashMap(), Loadable { runBlocking { getCapes(fetchQueue) - .onSuccess { it.forEach { cape -> put(cape.uuid, cape.id) } } + .onSuccess { it.forEach { cape -> cache[cape.uuid] = cape.id } } fetchQueue.clear() }