diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt index 2cd5893b1..b232fd3ec 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt @@ -25,6 +25,9 @@ import net.minecraft.block.BlockState import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos +/** + * Holds the necessary information for managers to perform actions. + */ abstract class BuildContext : Comparable, Drawable, Automated { abstract val hitResult: BlockHitResult abstract val rotationRequest: RotationRequest diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt index d1880aedc..5fa00bfa2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt @@ -20,6 +20,11 @@ package com.lambda.interaction.construction.processing import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos +/** + * The class all pre-processors must extend to provide the structure. Preprocessors are used to + * optimize how blocks are simulated. Some blocks might only be placeable on certain sides, so it is + * unnecessary to scan all of them, for example. + */ abstract class PlacementProcessor { abstract fun acceptsState(state: BlockState): Boolean abstract fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt deleted file mode 100644 index 569726982..000000000 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.construction.processing - -import com.lambda.task.Task - -abstract class ProcessingStep : Task() diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt index 1d219c4d6..83dd717b3 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -29,6 +29,10 @@ object ProcessorRegistry : Loadable { private val processors = getInstances() private val processorCache = Collections.synchronizedMap(mutableMapOf()) + /** + * List of properties that can be processed after the block is placed. This is often used to ignore these properties + * when placing blocks, as sometimes they can only be set to the right state after placement. + */ val postProcessedProperties = setOf( Properties.EXTENDED, Properties.EYE, @@ -106,6 +110,13 @@ object ProcessorRegistry : Loadable { override fun load() = "Loaded ${processors.size} pre processors" + /** + * [PreProcessingInfo]'s are cached to avoid duplicate computations as block states are immutable. + * + * @return A [PreProcessingInfo] object containing information about the block state. This method runs through + * each pre-processor checking if the state can be accepted. If so, the state is passed through the pre-processor + * which can call the functions within the [PreProcessingInfoAccumulator] DSL to modify the information. + */ fun TargetState.getProcessingInfo(pos: BlockPos): PreProcessingInfo? = if (this !is TargetState.State) PreProcessingInfo.DEFAULT else { diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt deleted file mode 100644 index 85b393520..000000000 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.construction.processing - -object TaskPlanner diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt index a95f01cb5..849a251f6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt @@ -20,6 +20,9 @@ package com.lambda.interaction.construction.result import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.request.hotbar.HotbarManager +/** + * Represents a result holding a [BuildContext]. + */ interface Contextual : ComparableResult { val context: BuildContext diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt index d688892bc..3d61ac5b2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt @@ -17,6 +17,9 @@ package com.lambda.interaction.construction.result +/** + * Represents a [BuildResult] that depends on another [BuildResult]. + */ interface Dependent { val dependency: BuildResult val lastDependency: BuildResult diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt index 84b71901e..ba9e05615 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt @@ -19,6 +19,9 @@ package com.lambda.interaction.construction.result import com.lambda.graphics.renderer.esp.ShapeBuilder +/** + * Represents a [BuildResult] that can be rendered in-game. + */ interface Drawable { fun ShapeBuilder.buildRenderer() } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Navigable.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Navigable.kt index 89404fa38..fc5483c2c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Navigable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Navigable.kt @@ -19,6 +19,9 @@ package com.lambda.interaction.construction.result import baritone.api.pathing.goals.Goal +/** + * Represents a [BuildResult] with a pathing goal. + */ interface Navigable { val goal: Goal } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt index 951256cb0..bbf96023c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt @@ -20,6 +20,9 @@ package com.lambda.interaction.construction.result import com.lambda.context.Automated import com.lambda.task.Task +/** + * Represents a [BuildResult] with a resolvable [Task] + */ interface Resolvable { context(automated: Automated) fun resolve(): Task<*> diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt index 67a6012ad..b074036d2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt @@ -30,6 +30,10 @@ import net.minecraft.util.math.BlockPos sealed class InteractResult : BuildResult() { override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + /** + * Represents a successful interaction. All checks have been passed. + * @param context The context of the interaction. + */ data class Interact( override val pos: BlockPos, override val context: InteractionContext diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt index 2f016a0f7..c607af354 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt @@ -24,6 +24,9 @@ import net.minecraft.util.math.BlockPos sealed class PostSimResult : BuildResult() { override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + /** + * No result can be found for the given position. + */ data class NoMatch( override val pos: BlockPos, ) : PostSimResult() { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 78612b403..1f0565f6a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -35,6 +35,16 @@ import kotlinx.coroutines.supervisorScope import net.minecraft.util.math.Vec3d object BuildSimulator : Sim() { + /** + * Iterates over the blueprint and performs the best suited simulation. Each simulation adds [BuildResult]s to + * the provided concurrent set. This method uses coroutines to perform the simulations in parallel. The results + * will likely not be returned in the same order they were simulated due to the parallel nature of the simulations. + * + * @see ISimInfo.sim + * @see simPostProcessing + * @see simPlacement + * @see simBreak + */ context(automatedSafeContext: AutomatedSafeContext) fun Blueprint.simulate( pov: Vec3d = automatedSafeContext.player.eyePos diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index 3a460ecd4..ca5e1bc58 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -29,6 +29,10 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import java.util.* +/** + * An interface representing all the information required to simulate a state. All simulators must present their public api + * as an extension of the [SimInfo] class to allow easy access through the DSL style sim builder. + */ interface ISimInfo : Automated { val pos: BlockPos val state: BlockState @@ -39,6 +43,9 @@ interface ISimInfo : Automated { val dependencyStack: Stack> companion object { + /** + * Creates a [SimInfo], checks its basic requirements, and runs the [simBuilder] block. + */ @SimDsl context(_: BuildSimulator) suspend fun AutomatedSafeContext.sim( @@ -56,6 +63,11 @@ interface ISimInfo : Automated { ).takeIf { it.hasBasicRequirements() }?.run { simBuilder() } } + /** + * Creates a new [SimInfo] using the current [ISimInfo]'s [dependencyStack] and [concurrentResults], + * checks its basic requirements, and runs the [simBuilder] block. As simulations tend to make use of + * concurrency, a new stack is created and the dependencies from the previous stack are added. + */ @SimDsl context(_: AutomatedSafeContext) suspend fun ISimInfo.sim( diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt index 1f47b9b8c..8436871b0 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt @@ -41,11 +41,40 @@ import kotlin.math.pow @DslMarker annotation class SimDsl +/** + * A class designed to simulate transforming a BlockState at a specified BlockPos to + * a TargetState. + * + * Some [Sim]s might need to call other sims as an intermediary between the current BlockState + * and the TargetState. In this case, we use a dependency system to ensure type safety. + * All sims must only return either [GenericResult]s or typed [BuildResult]s. For example, the BreakSim + * must only return BreakResults. For this reason, each type has its own Dependency result. + * To make sure that the results added are of the correct type, each [Sim] must be called from another [Sim]. + * Assuming the dependency stack has not reached max capacity, the original sim is then added to the dependency stack + * kept within the [SimInfo] object. Each [BuildResult] added is then iterated over the dependency stack, calling + * [dependentUpon] on each one. By the end, the result will be a nested group, with your initial [BuildResult] at + * the very bottom, which is then added to the [ISimInfo.concurrentResults] set. After a sim is completed, the dependency + * is then popped from the stack. + * + * @param T The type of [BuildResult] this sim produces. + * + * @see com.lambda.interaction.construction.result.Dependent + * @see dependentUpon + * @see withDependent + */ @SimDsl abstract class Sim : Results { + /** + * Can be overridden to return a typed Dependent result with the initial [buildResult] nested inside. + * + * @see com.lambda.interaction.construction.result.Dependent + */ @SimDsl open fun dependentUpon(buildResult: BuildResult): BuildResult = buildResult + /** + * Pushes and pops the [dependent] onto and off of the dependency stack unless the [maxSimDependencies] is reached. + */ protected suspend fun ISimInfo.withDependent(dependent: Sim<*>, block: suspend () -> Unit) { // +1 because the build sim counts as a dependent if (dependencyStack.size >= maxSimDependencies + 1) return @@ -54,6 +83,9 @@ abstract class Sim : Results { dependencyStack.pop() } + /** + * Scans a [voxelShape] on the given [sides] at the [pos] from the [pov]. + */ suspend fun ISimInfo.scanShape( pov: Vec3d, voxelShape: VoxelShape, @@ -91,7 +123,7 @@ abstract class Sim : Results { } if (hit.blockPos != pos || hit.side != side) return@scanSurfaces - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + val checked = CheckedHit(hit, newRotation) validHits.add(checked) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt index ec61a518c..55a7662a6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt @@ -29,6 +29,9 @@ import com.lambda.util.world.WorldUtils.isLoaded import net.minecraft.block.OperatorBlock object BasicChecker : Results { + /** + * A sequence of basic checks to make sure that the block is worth simulating. + */ @SimDsl context(automatedSafeContext: AutomatedSafeContext) fun SimInfo.hasBasicRequirements(): Boolean = with(automatedSafeContext) { diff --git a/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt b/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt index 4c64ae909..4eb5b43ac 100644 --- a/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt @@ -19,6 +19,10 @@ package com.lambda.interaction.request import com.lambda.interaction.construction.context.BuildContext +/** + * A simple interface to provide a basic object to hold key information that managers might need if information + * must persist longer than the request. + */ interface ActionInfo { val context: BuildContext val pendingInteractionsList: MutableCollection diff --git a/src/main/kotlin/com/lambda/interaction/request/DebugLogger.kt b/src/main/kotlin/com/lambda/interaction/request/DebugLogger.kt index 1bd34af3a..f841484b9 100644 --- a/src/main/kotlin/com/lambda/interaction/request/DebugLogger.kt +++ b/src/main/kotlin/com/lambda/interaction/request/DebugLogger.kt @@ -35,6 +35,9 @@ import imgui.flag.ImGuiWindowFlags import java.awt.Color import java.util.* +/** + * A simple logger that can be used to display information about what is happening within the managers. + */ class DebugLogger( val name: String ) { diff --git a/src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt b/src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt index ef3cd6a77..5086495a1 100644 --- a/src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt +++ b/src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt @@ -19,6 +19,11 @@ package com.lambda.interaction.request import net.minecraft.util.math.BlockPos +/** + * Provides a [blockedPositions] list to inform other managers what positions should not be processed. + * + * Reasons a position could be blocked include pending interactions and or active interactions. + */ interface PositionBlocking { val blockedPositions: List } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt b/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt index e8afb77fe..5c4a8ee75 100644 --- a/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt @@ -25,6 +25,9 @@ import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.request.breaking.BrokenBlockHandler import com.lambda.util.collections.LimitedDecayQueue +/** + * A simple interface for handlers of actions that need some sort of server response after being executed. + */ abstract class PostActionHandler { abstract val pendingActions: LimitedDecayQueue diff --git a/src/main/kotlin/com/lambda/interaction/request/Request.kt b/src/main/kotlin/com/lambda/interaction/request/Request.kt index 5e319ae6c..3c09b2d0e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/Request.kt +++ b/src/main/kotlin/com/lambda/interaction/request/Request.kt @@ -19,8 +19,15 @@ package com.lambda.interaction.request import com.lambda.context.Automated +/** + * A simple format to ensure basic requirements and information when requesting a manager. + * + * @property requestId Used for tracking how many requests there have been and what number this request is. + * @property fresh If this request is new. + * @property done If this request has been completed. + */ abstract class Request : Automated { - abstract val requestID: Int + abstract val requestId: Int var fresh = true abstract val done: Boolean diff --git a/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt b/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt deleted file mode 100644 index e756864d8..000000000 --- a/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.request - -interface RequestConfig \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt b/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt index e7f77a209..48141cf1e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt @@ -55,7 +55,7 @@ abstract class RequestHandler( var queuedRequest: R? = null; protected set /** - * Represents if the handler performed any external actions within this tick + * Represents if the handler performed any actions within this tick */ var activeThisTick = false; protected set diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt index 3db058511..b01f1d31a 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt @@ -19,13 +19,12 @@ package com.lambda.interaction.request.breaking import com.lambda.config.groups.BuildConfig import com.lambda.event.Event -import com.lambda.interaction.request.RequestConfig import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.block.Block import java.awt.Color -interface BreakConfig : RequestConfig { +interface BreakConfig { val breakMode: BreakMode val sorter: SortMode val rebreak: Boolean 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/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt index 3ef12f5ca..23e59cb33 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 +/** + * Manager responsible for breaking blocks in the most efficient manner possible. It can be accessed + * from anywhere through a [BreakRequest]. + * + * 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,17 @@ 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 + * PlayerActionC2SPacket with 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. + * + * @see net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket */ private fun AutomatedSafeContext.initNewBreak( requestCtx: BreakContext, @@ -521,8 +549,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 +561,6 @@ object BreakManager : RequestHandler( .toStructure(TargetState.Empty) .toBlueprint() .simulate() - .asSequence() .filterIsInstance() .filter { canAccept(it.context) } .sorted() @@ -540,10 +570,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 +608,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 +653,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 +686,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 @@ -668,7 +699,7 @@ object BreakManager : RequestHandler( * * @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 @@ -852,6 +883,9 @@ object BreakManager : RequestHandler( return true } + /** + * Wrapper method for calculating block-breaking delta. + */ context(automatedSafeContext: AutomatedSafeContext) fun BlockState.calcBreakDelta( pos: BlockPos, @@ -884,6 +918,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) { 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..c86c5d254 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt @@ -24,18 +24,32 @@ 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. + * + * A private constructor is used 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 BuildSimulator. + * @param pendingInteractions A mutable, concurrent list to store the pending actions. + * + * @see com.lambda.interaction.construction.simulation.BuildSimulator + */ data class BreakRequest private constructor( val contexts: Collection, val pendingInteractions: MutableCollection, private val automated: Automated ) : Request(), LogContext, Automated by automated { - override val requestID = ++requestCount + override val requestId = ++requestCount var onStart: (SafeContext.(BlockPos) -> Unit)? = null var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null @@ -53,7 +67,7 @@ data class BreakRequest private constructor( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Break Request") { - value("Request ID", requestID) + value("Request ID", requestId) value("Contexts", contexts.size) group("Callbacks") { value("onStart", onStart != null) 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..65b14b745 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,21 +121,19 @@ 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 } } } @@ -143,22 +141,20 @@ object BrokenBlockHandler : PostActionHandler() { /** * A modified version of the minecraft 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. + * Performs the actions required to display breaking particles, sounds, texture overlay, etc. + * based on the user's settings. * * @see net.minecraft.client.world.ClientWorld.breakBlock */ - 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 +162,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..52b3df455 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,16 @@ 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 diff --git a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt index 43a2c806d..4c6646d8f 100644 --- a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt @@ -18,14 +18,13 @@ package com.lambda.interaction.request.hotbar import com.lambda.event.Event -import com.lambda.interaction.request.RequestConfig /** * Abstract base class for configuring hotbar slot switch behavior. * * @param priority The priority of this configuration. */ -interface HotbarConfig : RequestConfig { +interface HotbarConfig { /** * The number of ticks to keep the current hotbar selection active. diff --git a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt index 6380f64db..d601b85ce 100644 --- a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt @@ -28,11 +28,22 @@ import com.lambda.interaction.request.Logger import com.lambda.interaction.request.ManagerUtils.newStage import com.lambda.interaction.request.ManagerUtils.newTick import com.lambda.interaction.request.RequestHandler +import com.lambda.interaction.request.hotbar.HotbarManager.activeRequest import com.lambda.interaction.request.hotbar.HotbarManager.checkResetSwap +import com.lambda.interaction.request.hotbar.HotbarManager.maxSwapsThisTick +import com.lambda.interaction.request.hotbar.HotbarManager.setActiveRequest import com.lambda.module.hud.ManagerDebugLoggers.hotbarManagerLogger import com.lambda.threading.runSafe import net.minecraft.item.ItemStack +/** + * Manager responsible for handling the current selected hotbar index. It can be accessed from anywhere through a + * [HotbarRequest] + * + * "Silent swapping" is a feature of this manager. If requested with a [HotbarRequest] that has [HotbarRequest.keepTicks] + * set to 0, assuming the request is accepted, the manager will only swap for the duration of the current [tickStage]. + * After which, the manager will end the request and swap back to the player's selected slot. + */ object HotbarManager : RequestHandler( 1, TickEvent.Pre, @@ -83,6 +94,14 @@ object HotbarManager : RequestHandler( return "Loaded Hotbar Manager" } + /** + * Attempts to accept the request and process it. If the [activeRequest] is not null, the new [request] matches hotbar index, + * and the new request has an equal or longer [HotbarRequest.keepTicks] than the current request, the new request is accepted. + * Otherwise, if the [activeRequest] is null, or is from an old request, assuming the swap doesn't exceed [maxSwapsThisTick], + * the request is accepted. + * + * @see setActiveRequest + */ override fun AutomatedSafeContext.handleRequest(request: HotbarRequest) { logger.debug("Handling request:", request) @@ -104,6 +123,12 @@ object HotbarManager : RequestHandler( setActiveRequest(request) } + /** + * Sets the [activeRequest]. This also calls syncSelectedSlot to + * update the server now to keep predictability. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot + */ private fun AutomatedSafeContext.setActiveRequest(request: HotbarRequest) { maxSwapsThisTick = hotbarConfig.swapsPerTick if (request.slot != serverSlot) { @@ -116,6 +141,13 @@ object HotbarManager : RequestHandler( logger.success("Set active request", request) } + /** + * Called after every [tickStage] closes. This method checks if the current [activeRequest] should be stopped. + * This action is counted as another swap, so the conditions for a regular swap must be met. If the requests + * [HotbarConfig.sequenceStageMask] does not contain the current tick stage, no actions can be performed. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot + */ private fun SafeContext.checkResetSwap() { activeRequest?.let { active -> val canStopSwap = swapsThisTick < maxSwapsThisTick diff --git a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt index 49acf4d7c..d6f74e8ec 100644 --- a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt @@ -28,7 +28,7 @@ class HotbarRequest( var keepTicks: Int = automated.hotbarConfig.keepTicks, var swapPause: Int = automated.hotbarConfig.swapPause ) : Request(), LogContext, Automated by automated { - override val requestID = ++requestCount + override val requestId = ++requestCount var activeRequestAge = 0 var swapPauseAge = 0 @@ -41,7 +41,7 @@ class HotbarRequest( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Hotbar Request") { - value("Request ID", requestID) + value("Request ID", requestId) value("Slot", slot) value("Keep Ticks", keepTicks) value("Swap Pause", swapPause) diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt index b563f5fdc..fa473159f 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt @@ -19,11 +19,10 @@ package com.lambda.interaction.request.interacting import com.lambda.config.groups.BuildConfig import com.lambda.event.Event -import com.lambda.interaction.request.RequestConfig import com.lambda.util.Describable import com.lambda.util.NamedEnum -interface InteractConfig : RequestConfig { +interface InteractConfig { val rotate: Boolean val swingHand: Boolean val tickStageMask: Set diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt index dbb74e138..3f06ff57e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt @@ -33,7 +33,7 @@ data class InteractRequest( private val automated: Automated, val onInteract: ((BlockPos) -> Unit)? ) : Request(), LogContext, Automated by automated { - override val requestID = ++requestCount + override val requestId = ++requestCount override val done: Boolean get() = contexts.all { mc.world?.getBlockState(it.blockPos)?.matches(it.expectedState) == true } @@ -43,7 +43,7 @@ data class InteractRequest( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Interact Request") { - value("Request ID", requestID) + value("Request ID", requestId) value("Contexts", contexts.size) } } diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt index 582bf1541..b959d6215 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt @@ -28,6 +28,11 @@ import com.lambda.util.Communication.info import com.lambda.util.Communication.warn import com.lambda.util.collections.LimitedDecayQueue +/** + * Designed to handle interactions pending a response from the server. + * + * @see InteractionManager + */ object InteractedBlockHandler : PostActionHandler() { override val pendingActions = LimitedDecayQueue( AutomationConfig.buildConfig.maxPendingInteractions, diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt index 476bd6758..9f8bce255 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt @@ -35,6 +35,10 @@ import com.lambda.interaction.request.breaking.BreakManager import com.lambda.interaction.request.interacting.InteractedBlockHandler.pendingActions import com.lambda.interaction.request.interacting.InteractedBlockHandler.setPendingConfigs import com.lambda.interaction.request.interacting.InteractedBlockHandler.startPending +import com.lambda.interaction.request.interacting.InteractionManager.activeRequest +import com.lambda.interaction.request.interacting.InteractionManager.maxInteractionsThisTick +import com.lambda.interaction.request.interacting.InteractionManager.populateFrom +import com.lambda.interaction.request.interacting.InteractionManager.potentialInteractions import com.lambda.interaction.request.interacting.InteractionManager.processRequest import com.lambda.interaction.request.placing.PlaceManager import com.lambda.module.hud.ManagerDebugLoggers.interactionManagerLogger @@ -44,6 +48,10 @@ import com.lambda.util.player.swingHand import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.util.Hand +/** + * Manager responsible for handling block interactions other than placing. It can be accessed from anywhere + * through an [InteractRequest] + */ object InteractionManager : RequestHandler( 0, TickEvent.Pre, @@ -51,9 +59,9 @@ object InteractionManager : RequestHandler( TickEvent.Input.Post, TickEvent.Player.Post, onOpen = { - if (InteractionManager.potentialInteractions.isNotEmpty()) + if (potentialInteractions.isNotEmpty()) InteractionManager.logger.newStage(InteractionManager.tickStage) - InteractionManager.activeRequest?.let { it.runSafeAutomated { processRequest(it) } } + activeRequest?.let { it.runSafeAutomated { processRequest(it) } } } ), PositionBlocking, Logger { private var activeRequest: InteractRequest? = null @@ -90,6 +98,12 @@ object InteractionManager : RequestHandler( return "Loaded Interaction Manager" } + /** + * Attempts to accept and process the request, if there is not already an [activeRequest] and the request's [InteractRequest.contexts] + * collection is not empty. + * + * @see processRequest + */ override fun AutomatedSafeContext.handleRequest(request: InteractRequest) { if (activeRequest != null || request.contexts.isEmpty()) return @@ -98,6 +112,11 @@ object InteractionManager : RequestHandler( if (interactionsThisTick > 0) activeThisTick = true } + /** + * Handles populating the manager and performing interactions. + * + * @see populateFrom + */ fun AutomatedSafeContext.processRequest(request: InteractRequest) { if (BreakManager.activeThisTick || PlaceManager.activeThisTick) return @@ -138,6 +157,12 @@ object InteractionManager : RequestHandler( } } + /** + * Populates the [potentialInteractions] collection, and sets the configurations + * + * @see setPendingConfigs + * @see maxInteractionsThisTick + */ private fun Automated.populateFrom(request: InteractRequest) { logger.debug("Populating from request", request) setPendingConfigs() diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt index 5297cc62f..db527337e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt @@ -19,6 +19,11 @@ package com.lambda.interaction.request.inventory import com.lambda.context.SafeContext +/** + * Represents a type of action. Inventory actions are strictly inventory-related. + * Other actions can be external actions that happen some time within the sequence of inventory actions + * to avoid having to use multiple requests. + */ sealed interface InventoryAction { val action: SafeContext.() -> Unit diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt index fcfe09b06..2d03d437c 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt @@ -21,12 +21,11 @@ import com.lambda.event.events.TickEvent import com.lambda.interaction.material.ContainerSelection import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.RequestConfig import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.block.Block -interface InventoryConfig : RequestConfig { +interface InventoryConfig { val actionsPerSecond: Int val tickStageMask: Set val disposables: Set diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index df97b9888..c5c1ceff5 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -27,6 +27,7 @@ import com.lambda.event.events.UpdateManagerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.request.Logger import com.lambda.interaction.request.RequestHandler +import com.lambda.interaction.request.inventory.InventoryManager.alteredSlots import com.lambda.interaction.request.placing.PlaceManager import com.lambda.module.hud.ManagerDebugLoggers.inventoryManagerLogger import com.lambda.threading.runSafe @@ -41,6 +42,10 @@ import net.minecraft.screen.PlayerScreenHandler import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot +/** + * Manager designed to handle inventory actions. One of the key features being the inventory change detection to + * avoid accepting old information from the server in cases where ping is high. This helps to prevent desync. + */ object InventoryManager : RequestHandler( 1, TickEvent.Pre, @@ -57,7 +62,7 @@ object InventoryManager : RequestHandler( private var screenHandler: ScreenHandler? = null set(value) { - if (value != null && field !== value) + if (value != null && field?.syncId != value.syncId) slots = getStacks(value.slots) field = value } @@ -85,6 +90,13 @@ object InventoryManager : RequestHandler( return "Loaded Inventory Manager" } + /** + * Attempts to accept the request and perform the actions. If, for example, the tick stage isn't valid, or + * not all the actions can be performed and [InventoryRequest.settleForLess] is set to false, the request is rejected. + * All checks aside from tick stage are ignored if the request has [InventoryRequest.mustPerform] set to true. + * This is typically used in dangerous situations where typical rules are worth breaking. For example, if the player + * needs to equip a totem of undying. + */ override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory } if (inventoryActionCount >= request.inventoryConfig.actionsPerSecond - actionsThisSecond && @@ -122,8 +134,14 @@ object InventoryManager : RequestHandler( alteredSlots.setDecayTime(AutomationConfig.desyncTimeout * 50L) } + /** + * Detects changes in item stacks between now and the last time slots were cached and + * adds them all to the [alteredSlots] collection where they can be compared to + * incoming [InventoryS2CPacket] and [ScreenHandlerSlotUpdateS2CPacket] packets to decide whether to + * block certain updates. + */ private fun SafeContext.indexInventoryChanges() { - if (player.currentScreenHandler !== screenHandler) return + if (player.currentScreenHandler.syncId != screenHandler?.syncId) return val changes = screenHandler?.slots ?.filter { !it.stack.equal(slots[it.id]) } ?.map { Pair(it.id, Pair(slots[it.id], it.stack.copy())) } @@ -135,7 +153,9 @@ object InventoryManager : RequestHandler( private fun getStacks(slots: Collection) = slots.map { it.stack.copy() } /** - * A modified version of the [net.minecraft.client.network.ClientPlayNetworkHandler.onInventory] method + * A modified version of the minecraft onInventory method + * + * @see net.minecraft.client.network.ClientPlayNetworkHandler.onInventory */ @JvmStatic fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation){ diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt index 8893ee390..4e9518bfe 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt @@ -30,9 +30,13 @@ import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -@DslMarker -private annotation class InvRequestDsl - +/** + * A private constructor is used to enforce use of the [InvRequestDsl] builder. + * + * @property actions A list of inventory actions to be performed in the request. + * @property settleForLess A flag indicating whether to settle for partial completion of the request. + * @property mustPerform A flag indicating whether the request must be performed regardless of conditions as long as the tick stage is valid. + */ class InventoryRequest private constructor( val actions: List, val settleForLess: Boolean, @@ -40,7 +44,7 @@ class InventoryRequest private constructor( automated: Automated, val onComplete: (SafeContext.() -> Unit)? ) : Request(), LogContext, Automated by automated { - override val requestID = ++requestCount + override val requestId = ++requestCount override var done = false override fun submit(queueIfClosed: Boolean) = @@ -48,11 +52,14 @@ class InventoryRequest private constructor( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Inventory Request") { - value("Request ID", requestID) + value("Request ID", requestId) value("Action Count", actions.size) } } + @DslMarker + private annotation class InvRequestDsl + @InvRequestDsl class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { val actions = mutableListOf() diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt index 15cf19b4e..a76a35899 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt @@ -19,11 +19,10 @@ package com.lambda.interaction.request.placing import com.lambda.config.groups.BuildConfig import com.lambda.event.Event -import com.lambda.interaction.request.RequestConfig import com.lambda.util.Describable import com.lambda.util.NamedEnum -interface PlaceConfig : RequestConfig { +interface PlaceConfig { val rotateForPlace: Boolean val airPlace: AirPlaceMode val axisRotateSetting: Boolean diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index ed3de1265..c5fde073c 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -135,11 +135,10 @@ object PlaceManager : RequestHandler( } /** - * If the request is fresh, local variables are populated through the [processRequest] method. - * It then attempts to perform as many placements within this tick as possible from the [potentialPlacements] collection. - * - * If all the [maxPlacementsThisTick] limit is reached and the user has rotations enabled, it will start rotating to - * the next predicted placement in the list for optimal speed. + * Returns immediately if [BreakManager] or [InteractionManager] have been active this tick. + * Otherwise, for fresh requests, [populateFrom] is called to fill the [potentialPlacements] collection. + * It then attempts to perform as many placements as possible from the [potentialPlacements] collection within + * the [maxPlacementsThisTick] limit. * * @see populateFrom * @see placeBlock @@ -182,7 +181,7 @@ object PlaceManager : RequestHandler( /** * Filters the [request]'s [PlaceContext]s, placing them into the [potentialPlacements] collection, and - * setting the maxPlacementsThisTick value. + * setting other configurations. * * @see isPosBlocked */ @@ -205,7 +204,8 @@ object PlaceManager : RequestHandler( } /** - * A modified version of the minecraft interactBlock method, renamed to better suit its usage. + * A modified version of the minecraft interactBlock method, + * renamed to better suit its usage. * * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlock */ @@ -396,7 +396,7 @@ object PlaceManager : RequestHandler( } /** - * Plays the block placement sound at a given position. + * Plays the block placement sound at a given [pos]. */ fun SafeContext.placeSound(state: BlockState, pos: BlockPos) { val blockSoundGroup = state.soundGroup diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt index c5576a1c3..ad41ef7c1 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt @@ -35,7 +35,7 @@ data class PlaceRequest( private val automated: Automated, val onPlace: (SafeContext.(BlockPos) -> Unit)? = null ) : Request(), LogContext, Automated by automated { - override val requestID = ++requestCount + override val requestId = ++requestCount override val done: Boolean get() = runSafe { @@ -47,7 +47,7 @@ data class PlaceRequest( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("PlaceRequest") { - value("Request ID", requestID) + value("Request ID", requestId) value("Contexts", contexts.size) } } diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt index cb223b421..355fa7f79 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt @@ -17,9 +17,7 @@ package com.lambda.interaction.request.rotating -import com.lambda.interaction.request.RequestConfig - -interface RotationConfig : RequestConfig { +interface RotationConfig { /** * - [RotationMode.Silent] Spoofing server-side rotation. * - [RotationMode.Sync] Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim). diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt index 15fcbfc4a..f05a6dc34 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt @@ -33,6 +33,10 @@ import com.lambda.interaction.BaritoneManager import com.lambda.interaction.request.Logger import com.lambda.interaction.request.RequestHandler import com.lambda.interaction.request.rotating.Rotation.Companion.slerp +import com.lambda.interaction.request.rotating.RotationManager.activeRequest +import com.lambda.interaction.request.rotating.RotationManager.activeRotation +import com.lambda.interaction.request.rotating.RotationManager.serverRotation +import com.lambda.interaction.request.rotating.RotationManager.updateActiveRotation import com.lambda.interaction.request.rotating.visibilty.lookAt import com.lambda.module.hud.ManagerDebugLoggers.rotationManagerLogger import com.lambda.threading.runGameScheduled @@ -53,6 +57,9 @@ import kotlin.math.atan2 import kotlin.math.cos import kotlin.math.sin +/** + * Manager designed to rotate the player and adjust movement input to match the camera's direction. + */ object RotationManager : RequestHandler( 1, TickEvent.Pre, @@ -62,8 +69,7 @@ object RotationManager : RequestHandler( ), Logger { var activeRotation = Rotation.ZERO var serverRotation = Rotation.ZERO - @JvmStatic - var prevServerRotation = Rotation.ZERO + @JvmStatic var prevServerRotation = Rotation.ZERO var baritoneRequest: RotationRequest? = null var activeRequest: RotationRequest? = null @@ -138,6 +144,12 @@ object RotationManager : RequestHandler( return "Loaded Rotation Manager" } + /** + * If the [activeRequest] is from an older tick or null, and the [request]'s target rotation is not null, + * the request is accepted and set as the [activeRequest]. The [activeRotation] is then updated. + * + * @see updateActiveRotation + */ override fun AutomatedSafeContext.handleRequest(request: RotationRequest) { activeRequest?.let { if (it.age <= 0) return } if (request.target.targetRotation.value != null) { @@ -148,6 +160,11 @@ object RotationManager : RequestHandler( } } + /** + * If the rotation has not been changed this tick, the [activeRequest]'s target rotation is updated, and + * likewise the [activeRotation]. The [activeRequest] is then updated, ticking the [RotationRequest.keepTicks] + * and [RotationRequest.decayTicks]. + */ @JvmStatic fun processRotations() = runSafe { if (activeRequest != null) activeThisTick = true @@ -171,6 +188,11 @@ object RotationManager : RequestHandler( } } + /** + * Calculates and sets the optimal movement input for moving in the direction the player is facing. This + * is not the direction the rotation manager is rotated towards, but the underlying player rotation, typically + * also the camera's rotation. + */ @JvmStatic fun redirectStrafeInputs(input: Input) = runSafe { if (activeRequest == baritoneRequest) return@runSafe @@ -263,11 +285,15 @@ object RotationManager : RequestHandler( } } + /** + * Updates the [activeRotation]. If [activeRequest] is null, the player's rotation is used. + * Otherwise, the [serverRotation] is interpolated towards the [RotationRequest.target] rotation. + */ private fun SafeContext.updateActiveRotation() { activeRotation = activeRequest?.let { request -> val rotationTo = if (request.keepTicks >= 0) request.target.targetRotation.value - ?: activeRotation // same context gets used again && the rotation is null this tick + ?: activeRotation // the same context gets used again && the rotation is null this tick else player.rotation val speedMultiplier = if (request.keepTicks < 0) 1.0 else request.speedMultiplier diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt index dc977e8b8..6a5bb9b93 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt @@ -33,7 +33,7 @@ data class RotationRequest( var decayTicks: Int = automated.rotationConfig.decayTicks, val speedMultiplier: Double = 1.0 ) : Request(), LogContext, Automated by automated { - override val requestID = ++requestCount + override val requestId = ++requestCount var age = 0 @@ -45,7 +45,7 @@ data class RotationRequest( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Rotation Request") { - value("Request ID", requestID) + value("Request ID", requestId) value("Rotation Mode", rotationMode) value("Turn Speed", turnSpeed) value("Keep Ticks", keepTicks) diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt index 684f4650a..000af3d3f 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt @@ -67,7 +67,7 @@ object VisibilityChecker { if (boxes.any { it.contains(eye) }) { currentRotation.rayCast(reach, eye)?.let { hit -> - return CheckedHit(hit, currentRotation, reach) + return CheckedHit(hit, currentRotation) } } @@ -111,7 +111,7 @@ object VisibilityChecker { val mask = if (buildConfig.strictRayCast) InteractionMask.Both else targetType val hit = newRotation.rayCast(reach, eye, mask = mask) ?: return@scanSurfaces - val checked = CheckedHit(hit, newRotation, reach) + val checked = CheckedHit(hit, newRotation) if (!checked.verify()) return@scanSurfaces add(checked) @@ -142,7 +142,7 @@ object VisibilityChecker { val mask = if (buildConfig.strictRayCast || entity == null) InteractionMask.Both else targetType val hit = newRotation.rayCast(reach, eye, mask = mask) ?: return@scanSurfaces - val checked = CheckedHit(hit, newRotation, reach) + val checked = CheckedHit(hit, newRotation) if (!checked.verify()) return@scanSurfaces add(checked) @@ -151,11 +151,11 @@ object VisibilityChecker { } /** - * Scans the surfaces of a given box, optionally excluding specific sides, + * Scans the surfaces of a given box on the [sides] specified * and executes a callback for each point calculated based on the scanning parameters. * * @param box The 3D box whose surfaces will be scanned. - * @param excludedSides A set of directions representing the sides of the box to exclude from the scan (default is an empty set). + * @param sides A set of sides to scan * @param resolution The number of intervals into which each dimension is divided for scanning (default is 5). * @param scan Configuration specifying the axis and mode of the scan (default is `SurfaceScan.DEFAULT`). * @param check A callback function that performs an action for each surface point, receiving the direction of the surface and the current 3D vector. @@ -291,7 +291,6 @@ object VisibilityChecker { class CheckedHit( val hit: HitResult, - val targetRotation: Rotation, - val reach: Double + val targetRotation: Rotation ) }