diff --git a/build.gradle.kts b/build.gradle.kts index aade0e4d1..660bacb7c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -285,4 +285,8 @@ publishing { } } } +} +tasks.named("compileJava") { + dependsOn(tasks.named("compileKotlin")) + classpath += files(tasks.named("compileKotlin").get().destinationDirectory) } \ No newline at end of file diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 768ece4c2..fe7c740ae 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -17,11 +17,11 @@ package com.lambda.mixin; +import com.lambda.interaction.handler.handlers.TimerHandler; import com.lambda.event.EventFlow; import com.lambda.event.events.*; import com.lambda.gui.DearImGui; import com.lambda.gui.components.ClickGuiLayout; -import com.lambda.interaction.handlers.TimerHandler; import com.lambda.module.modules.movement.BetterFirework; import com.lambda.module.modules.player.Interact; import com.lambda.module.modules.player.InventoryMove; @@ -141,7 +141,7 @@ private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) { } } - @Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.AFTER)) + @Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.BEFORE)) private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) { if (currentScreen == null) return; if (currentScreen instanceof ScreenHandlerProvider handledScreen) { diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index dbdb9a9d2..e13b3f459 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -20,9 +20,9 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.PlayerEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.inventory.InventoryManager; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.inventory.InventoryManager; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index c8bd52b47..136c9d2d4 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -19,8 +19,8 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.*; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.NoJumpCooldown; import com.lambda.module.modules.movement.elytrafly.ElytraFly; import com.lambda.module.modules.player.PortalGui; @@ -56,7 +56,7 @@ import java.util.Objects; import static com.lambda.Lambda.getMc; -import static com.lambda.interaction.managers.rotating.Rotation.dist; +import static com.lambda.interaction.manager.managers.rotating.Rotation.dist; @Mixin(value = ClientPlayerEntity.class, priority = Integer.MAX_VALUE) public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity { diff --git a/src/main/java/com/lambda/mixin/entity/EntityMixin.java b/src/main/java/com/lambda/mixin/entity/EntityMixin.java index 2c31d2ff8..99fb142f6 100644 --- a/src/main/java/com/lambda/mixin/entity/EntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/EntityMixin.java @@ -20,8 +20,8 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.EntityEvent; import com.lambda.event.events.PlayerEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.elytrafly.ElytraFly; import com.lambda.module.modules.render.NoRender; import com.lambda.util.math.Vec2d; diff --git a/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java b/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java index 079ea9411..106e0bbb0 100644 --- a/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java +++ b/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.entity; -import com.lambda.interaction.managers.inventory.InventoryManager; +import com.lambda.interaction.manager.managers.inventory.InventoryManager; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.HandledScreens; import net.minecraft.screen.ScreenHandler; diff --git a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java index 1f3caf618..9af7e6302 100644 --- a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java @@ -20,9 +20,9 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.handlers.GlideHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.handler.handlers.GlideHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.movement.elytrafly.ElytraFly; import com.lambda.module.modules.render.ViewModel; @@ -49,7 +49,6 @@ @Mixin(LivingEntity.class) public abstract class LivingEntityMixin extends EntityMixin { - @Unique private final LivingEntity lambda$instance = (LivingEntity) (Object) this; @Definition(id = "getJumpVelocity", method = "Lnet/minecraft/entity/LivingEntity;getJumpVelocity()F") diff --git a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java index 8a0fc6d0d..c7722b2bc 100644 --- a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java @@ -20,9 +20,9 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.handlers.GlideHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.handler.handlers.GlideHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.player.Reach; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; diff --git a/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java index 26c4c0a91..e538912bd 100644 --- a/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java +++ b/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java @@ -18,7 +18,7 @@ package com.lambda.mixin.entity; import com.lambda.Lambda; -import com.lambda.interaction.managers.hotbar.HotbarManager; +import com.lambda.interaction.manager.managers.hotbar.HotbarManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.entity.player.PlayerInventory; import org.objectweb.asm.Opcodes; diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index 9f83518dd..54a5b4ef3 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -23,8 +23,8 @@ import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.PlayerEvent; import com.lambda.event.events.WorldEvent; -import com.lambda.interaction.managers.inventory.InventoryManager; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.manager.managers.inventory.InventoryManager; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.render.NoRender; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; diff --git a/src/main/java/com/lambda/mixin/render/CameraMixin.java b/src/main/java/com/lambda/mixin/render/CameraMixin.java index 38f629489..e6ca7231f 100644 --- a/src/main/java/com/lambda/mixin/render/CameraMixin.java +++ b/src/main/java/com/lambda/mixin/render/CameraMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.render.CameraTweaks; import com.lambda.module.modules.render.FreeLook; import com.lambda.module.modules.render.Freecam; diff --git a/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java b/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java index 97d8c5e63..260898329 100644 --- a/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java @@ -20,6 +20,12 @@ import com.google.common.base.MoreObjects; import com.lambda.Lambda; import com.lambda.module.modules.render.ViewModel; +import com.lambda.module.modules.player.SilentEat; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemDisplayContext; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; @@ -53,7 +59,14 @@ private void injectRenderFirstPersonItem(AbstractClientPlayerEntity player, floa private void injectRenderArmHoldingItem(AbstractClientPlayerEntity player, float tickProgress, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, OrderedRenderCommandQueue orderedRenderCommandQueue, int light, CallbackInfo ci) { if (ViewModel.INSTANCE.isEnabled()) ViewModel.INSTANCE.transform(item, hand, matrices); } - + @WrapOperation(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;I)V")) + private void wrapRenderItem(HeldItemRenderer instance, LivingEntity entity, ItemStack item, ItemDisplayContext displayContext, MatrixStack matrices, OrderedRenderCommandQueue queue, int light, Operation original, @Local(argsOnly = true) Hand hand) { + if (SilentEat.isUsingOffhand() && hand == Hand.OFF_HAND && SilentEat.INSTANCE.getSilentVisual()) { + original.call(instance, entity, SilentEat.getVisualOffhandStack(), displayContext, matrices, queue, light); + return; + } + original.call(instance, entity, item, displayContext, matrices, queue, light); + } @ModifyArg(method = "updateHeldItems", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/MathHelper;clamp(FFF)F", ordinal = 2), index = 0) private float modifyEquipProgressMainHand(float value) { if (client.player == null || ViewModel.INSTANCE.isDisabled()) return value; diff --git a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java index dc18dc35a..86ffe2d5e 100644 --- a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java @@ -18,8 +18,8 @@ package com.lambda.mixin.render; import com.lambda.Lambda; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.render.Nametags; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; diff --git a/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java b/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java index eefaba6f1..f6e090956 100644 --- a/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.interaction.handlers.FriendHandler; +import com.lambda.interaction.handler.handlers.FriendHandler; import com.lambda.module.modules.render.ExtraTab; import com.lambda.util.text.TextBuilder; import com.lambda.util.text.TextDslKt; diff --git a/src/main/java/com/lambda/mixin/world/DirectionMixin.java b/src/main/java/com/lambda/mixin/world/DirectionMixin.java index 765248bb3..da8698f2e 100644 --- a/src/main/java/com/lambda/mixin/world/DirectionMixin.java +++ b/src/main/java/com/lambda/mixin/world/DirectionMixin.java @@ -17,8 +17,8 @@ package com.lambda.mixin.world; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.entity.Entity; import net.minecraft.util.math.Direction; diff --git a/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt index bafbb787a..9c0034f3f 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt @@ -37,9 +37,8 @@ import java.util.function.Predicate * @see BlockPredicateArgumentType.getBlockPredicate */ @BrigadierDsl -fun DefaultArgumentReader.value(): Predicate { - return BlockPredicateArgumentType.getBlockPredicate(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Predicate = + BlockPredicateArgumentType.getBlockPredicate(context.assumeSourceNotUsed(), name) /** * Reads the [BlockStateArgument] value of the argument in @@ -48,9 +47,8 @@ fun DefaultArgumentReader.value(): Predicate.value(): BlockStateArgument { - return BlockStateArgumentType.getBlockState(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): BlockStateArgument = + BlockStateArgumentType.getBlockState(context.assumeSourceNotUsed(), name) /** * Creates a block predicate argument with [name] as the parameter name. @@ -62,9 +60,8 @@ fun DefaultArgumentReader.value(): BlockStateArgument { fun blockPredicate( name: String, registryAccess: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, BlockPredicateArgumentType.blockPredicate(registryAccess)) -} +): DefaultArgumentConstructor = + argument(name, BlockPredicateArgumentType.blockPredicate(registryAccess)) /** * Creates a block state argument with [name] as the parameter name. @@ -76,6 +73,5 @@ fun blockPredicate( fun blockState( name: String, registryAccess: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, BlockStateArgumentType.blockState(registryAccess)) -} +): DefaultArgumentConstructor = + argument(name, BlockStateArgumentType.blockState(registryAccess)) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt index 6c2117456..065bb8e0c 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt @@ -167,9 +167,8 @@ fun rotation(name: String): DefaultArgumentConstructor swizzle( name: String, -): DefaultArgumentConstructor { - return argument(name, SwizzleArgumentType.swizzle()) -} +): DefaultArgumentConstructor = + argument(name, SwizzleArgumentType.swizzle()) /** * Creates a block pos argument with [name] as the parameter name. @@ -177,9 +176,8 @@ fun swizzle( @BrigadierDsl fun blockPos( name: String, -): DefaultArgumentConstructor { - return argument(name, BlockPosArgumentType.blockPos()) -} +): DefaultArgumentConstructor = + argument(name, BlockPosArgumentType.blockPos()) /** * Creates a column pos argument with [name] as the parameter name. @@ -187,9 +185,8 @@ fun blockPos( @BrigadierDsl fun columnPos( name: String, -): DefaultArgumentConstructor { - return argument(name, ColumnPosArgumentType.columnPos()) -} +): DefaultArgumentConstructor = + argument(name, ColumnPosArgumentType.columnPos()) /** * Creates a vec2 argument with [name] as the parameter name. @@ -200,9 +197,8 @@ fun columnPos( fun vec2( name: String, centerIntegers: Boolean = false, -): DefaultArgumentConstructor { - return argument(name, Vec2ArgumentType.vec2(centerIntegers)) -} +): DefaultArgumentConstructor = + argument(name, Vec2ArgumentType.vec2(centerIntegers)) /** * Creates a vec3 argument with [name] as the parameter name. @@ -213,6 +209,5 @@ fun vec2( fun vec3( name: String, centerIntegers: Boolean = false, -): DefaultArgumentConstructor { - return argument(name, Vec3ArgumentType.vec3(centerIntegers)) -} +): DefaultArgumentConstructor = + argument(name, Vec3ArgumentType.vec3(centerIntegers)) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt index c88a39cb3..d2a3b0218 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt @@ -27,8 +27,6 @@ import com.lambda.brigadier.assumeSourceNotUsed import net.minecraft.advancement.AdvancementEntry import net.minecraft.command.argument.IdentifierArgumentType import net.minecraft.command.argument.RegistryKeyArgumentType -import net.minecraft.loot.condition.LootCondition -import net.minecraft.loot.function.LootFunction import net.minecraft.recipe.RecipeEntry import net.minecraft.server.command.ServerCommandSource import net.minecraft.util.Identifier @@ -41,9 +39,8 @@ import net.minecraft.util.Identifier */ @JvmName("valueIdentifierArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Identifier { - return IdentifierArgumentType.getIdentifier(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Identifier = + IdentifierArgumentType.getIdentifier(context.assumeSourceNotUsed(), name) /** * Reads the [Identifier] value from the @@ -53,22 +50,16 @@ fun DefaultArgumentReader.value(): Identifier { * @see RegistryKeyArgumentType.getAdvancementEntry */ @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - IdentifierArgumentType - > - >.asAdvancement(): AdvancementEntry { - return RegistryKeyArgumentType.getAdvancementEntry(context, name) -} +fun ArgumentReader>.asAdvancement(): AdvancementEntry = + RegistryKeyArgumentType.getAdvancementEntry(context, name) -/** - * Reads the [Identifier] value from the - * argument in the receiver [ArgumentReader] - * as a [LootCondition]. - * - * @see IdentifierArgumentType.getPredicateArgument - */ +///** +// * Reads the [Identifier] value from the +// * argument in the receiver [ArgumentReader] +// * as a [LootCondition]. +// * +// * @see IdentifierArgumentType.getPredicateArgument +// */ //@BrigadierDsl //fun ArgumentReader< // ServerCommandSource, @@ -79,13 +70,13 @@ fun ArgumentReader< // return IdentifierArgumentType.getPredicateArgument(context, name) //} -/** - * Reads the [Identifier] value from the - * argument in the receiver [ArgumentReader] - * as a [LootFunction]. - * - * @see IdentifierArgumentType.getItemModifierArgument - */ +///** +// * Reads the [Identifier] value from the +// * argument in the receiver [ArgumentReader] +// * as a [LootFunction]. +// * +// * @see IdentifierArgumentType.getItemModifierArgument +// */ //@BrigadierDsl //fun ArgumentReader< // ServerCommandSource, @@ -104,14 +95,8 @@ fun ArgumentReader< * @see IdentifierArgumentType.getRecipeArgument */ @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - IdentifierArgumentType - > - >.asRecipe(): RecipeEntry<*> { - return RegistryKeyArgumentType.getRecipeEntry(context, name) -} +fun ArgumentReader>.asRecipe(): RecipeEntry<*> = + RegistryKeyArgumentType.getRecipeEntry(context, name) /** * Creates an identifier argument with [name] as the parameter name. @@ -119,6 +104,5 @@ fun ArgumentReader< @BrigadierDsl fun identifier( name: String, -): DefaultArgumentConstructor { - return argument(name, IdentifierArgumentType.identifier()) -} +): DefaultArgumentConstructor = + argument(name, IdentifierArgumentType.identifier()) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt index 846cdb2e3..ecaa559ef 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt @@ -46,9 +46,8 @@ import java.util.function.Predicate */ @JvmName("valueItemPredicateArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Predicate { - return ItemPredicateArgumentType.getItemStackPredicate(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Predicate = + ItemPredicateArgumentType.getItemStackPredicate(context.assumeSourceNotUsed(), name) /** * Reads the integer value from the @@ -58,9 +57,8 @@ fun DefaultArgumentReader.value(): Predicate.value(): Int { - return ItemSlotArgumentType.getItemSlot(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Int = + ItemSlotArgumentType.getItemSlot(context.assumeSourceNotUsed(), name) /** * Reads the [ItemStackArgument] value from the @@ -70,9 +68,8 @@ fun DefaultArgumentReader.value(): Int { */ @JvmName("valueItemStackArg") @BrigadierDsl -fun DefaultArgumentReader.value(): ItemStackArgument { - return ItemStackArgumentType.getItemStackArgument(context, name) -} +fun DefaultArgumentReader.value(): ItemStackArgument = + ItemStackArgumentType.getItemStackArgument(context, name) /** * Creates an item predicate argument with [name] as the parameter name. @@ -83,9 +80,8 @@ fun DefaultArgumentReader.value(): ItemStackArgument { fun itemPredicate( name: String, context: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, ItemPredicateArgumentType.itemPredicate(context)) -} +): DefaultArgumentConstructor = + argument(name, ItemPredicateArgumentType.itemPredicate(context)) /** * Creates an item slot argument with [name] as the parameter name. @@ -93,9 +89,8 @@ fun itemPredicate( @BrigadierDsl fun itemSlot( name: String, -): DefaultArgumentConstructor { - return argument(name, ItemSlotArgumentType.itemSlot()) -} +): DefaultArgumentConstructor = + argument(name, ItemSlotArgumentType.itemSlot()) /** * Creates an item stack argument with [name] as the parameter name. @@ -106,6 +101,5 @@ fun itemSlot( fun itemStack( name: String, context: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, ItemStackArgumentType.itemStack(context)) -} +): DefaultArgumentConstructor = + argument(name, ItemStackArgumentType.itemStack(context)) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt index 3deb92a56..7a2c1f498 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt @@ -48,9 +48,8 @@ object LiteralDescriptor : ArgumentDescriptor> */ @JvmName("valueTimeArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Int { - return IntegerArgumentType.getInteger(context, name) -} // TimeArgumentType does not provide an accessor, defaulting to int +fun DefaultArgumentReader.value(): Int = + IntegerArgumentType.getInteger(context, name) // TimeArgumentType does not provide an accessor, defaulting to int /** * Reads the [UUID] value from the @@ -60,9 +59,8 @@ fun DefaultArgumentReader.value(): Int { */ @JvmName("valueUuidArg") @BrigadierDsl -fun DefaultArgumentReader.value(): UUID { - return UuidArgumentType.getUuid(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): UUID = + UuidArgumentType.getUuid(context.assumeSourceNotUsed(), name) /** * Creates a time argument with [name] as the parameter name. @@ -73,9 +71,8 @@ fun DefaultArgumentReader.value(): UUID { fun time( name: String, minimumTicks: Int = 0, -): DefaultArgumentConstructor { - return argument(name, TimeArgumentType.time(minimumTicks)) -} +): DefaultArgumentConstructor = + argument(name, TimeArgumentType.time(minimumTicks)) /** * Creates a UUID argument with [name] as the parameter name. @@ -83,9 +80,8 @@ fun time( @BrigadierDsl fun uuid( name: String, -): RequiredArgumentConstructor> { - return argument(name, UuidArgumentType.uuid()) -} +): RequiredArgumentConstructor> = + argument(name, UuidArgumentType.uuid()) /** * Creates a literal argument with [name] as the literal. @@ -98,6 +94,5 @@ fun uuid( @BrigadierDsl fun literal( name: String, -): ArgumentConstructor, LiteralDescriptor> { - return ArgumentConstructor(LiteralArgumentBuilder.literal(name), name, LiteralDescriptor) -} +): ArgumentConstructor, LiteralDescriptor> = + ArgumentConstructor(LiteralArgumentBuilder.literal(name), name, LiteralDescriptor) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt index 08122cc82..41a7c13ac 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt @@ -45,9 +45,8 @@ import net.minecraft.nbt.NbtElement */ @JvmName("valueNbtCompoundArg") @BrigadierDsl -fun DefaultArgumentReader.value(): NbtCompound { - return NbtCompoundArgumentType.getNbtCompound(context, name) -} +fun DefaultArgumentReader.value(): NbtCompound = + NbtCompoundArgumentType.getNbtCompound(context, name) /** * Reads the [NbtElement] value from the @@ -57,9 +56,8 @@ fun DefaultArgumentReader.value(): NbtCompound { */ @JvmName("valueNbtElementArg") @BrigadierDsl -fun DefaultArgumentReader.value(): NbtElement { - return NbtElementArgumentType.getNbtElement(context, name) -} +fun DefaultArgumentReader.value(): NbtElement = + NbtElementArgumentType.getNbtElement(context, name) /** * Reads the [NbtPath] value from the @@ -69,9 +67,8 @@ fun DefaultArgumentReader.value(): NbtElement { */ @JvmName("valueNbtPathArg") @BrigadierDsl -fun DefaultArgumentReader.value(): NbtPath { - return NbtPathArgumentType.getNbtPath(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): NbtPath = + NbtPathArgumentType.getNbtPath(context.assumeSourceNotUsed(), name) /** * Creates a nbt compound argument with [name] as the parameter name. @@ -79,9 +76,8 @@ fun DefaultArgumentReader.value(): NbtPath { @BrigadierDsl fun nbtCompound( name: String, -): DefaultArgumentConstructor { - return argument(name, NbtCompoundArgumentType.nbtCompound()) -} +): DefaultArgumentConstructor = + argument(name, NbtCompoundArgumentType.nbtCompound()) /** * Creates an NBT element argument with [name] as the parameter name. @@ -89,9 +85,8 @@ fun nbtCompound( @BrigadierDsl fun nbtElement( name: String, -): DefaultArgumentConstructor { - return argument(name, NbtElementArgumentType.nbtElement()) -} +): DefaultArgumentConstructor = + argument(name, NbtElementArgumentType.nbtElement()) /** * Creates an NBT path argument with [name] as the parameter name. @@ -99,6 +94,5 @@ fun nbtElement( @BrigadierDsl fun nbtPath( name: String, -): DefaultArgumentConstructor { - return argument(name, NbtPathArgumentType.nbtPath()) -} +): DefaultArgumentConstructor = + argument(name, NbtPathArgumentType.nbtPath()) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt index 102021f26..36169393c 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt @@ -65,14 +65,8 @@ object ListPlayerArgumentDescriptor : ArgumentDescriptor */ @JvmName("valueGameProfileArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - GameProfileArgumentType - > - >.value(): Collection { - return GameProfileArgumentType.getProfileArgument(context, name) -} +fun ArgumentReader>.value(): Collection = + GameProfileArgumentType.getProfileArgument(context, name) /** * Reads the [Team] value from the @@ -82,14 +76,8 @@ fun ArgumentReader< */ @JvmName("valueTeamArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - TeamArgumentType - > - >.value(): Team { - return TeamArgumentType.getTeam(context, name) -} +fun ArgumentReader>.value(): Team = + TeamArgumentType.getTeam(context, name) /** * Reads the [ServerPlayerEntity] value from the @@ -99,12 +87,8 @@ fun ArgumentReader< */ @JvmName("valuePlayerArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - SinglePlayerArgumentDescriptor - >.value(): ServerPlayerEntity { - return EntityArgumentType.getPlayer(context, name) -} +fun ArgumentReader.value(): ServerPlayerEntity = + EntityArgumentType.getPlayer(context, name) /** * Reads the collection of players from the argument in @@ -116,12 +100,8 @@ fun ArgumentReader< */ @JvmName("requiredPlayerArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - ListPlayerArgumentDescriptor - >.required(): Collection { - return EntityArgumentType.getPlayers(context, name) -} +fun ArgumentReader.required(): Collection = + EntityArgumentType.getPlayers(context, name) /** * Reads the collection of players from the argument in @@ -133,12 +113,8 @@ fun ArgumentReader< */ @JvmName("optionalPlayerArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - ListPlayerArgumentDescriptor - >.optional(): Collection { - return EntityArgumentType.getOptionalPlayers(context, name) -} +fun ArgumentReader.optional(): Collection = + EntityArgumentType.getOptionalPlayers(context, name) /** * Creates a game profile argument with [name] as the parameter name. @@ -146,9 +122,8 @@ fun ArgumentReader< @BrigadierDsl fun gameProfile( name: String, -): DefaultArgumentConstructor { - return argument(name, GameProfileArgumentType.gameProfile()) -} +): DefaultArgumentConstructor = + argument(name, GameProfileArgumentType.gameProfile()) /** * Creates a team argument with [name] as the parameter name. @@ -156,9 +131,8 @@ fun gameProfile( @BrigadierDsl fun team( name: String, -): DefaultArgumentConstructor { - return argument(name, TeamArgumentType.team()) -} +): DefaultArgumentConstructor = + argument(name, TeamArgumentType.team()) /** * Creates a player selector argument with [name] as the parameter name. @@ -166,12 +140,8 @@ fun team( @BrigadierDsl fun player( name: String, -): RequiredArgumentConstructor< - S, - SinglePlayerArgumentDescriptor - > { - return argument(name, EntityArgumentType.player(), SinglePlayerArgumentDescriptor) -} +): RequiredArgumentConstructor = + argument(name, EntityArgumentType.player(), SinglePlayerArgumentDescriptor) /** * Creates a multiple player selector argument with [name] as the parameter name. @@ -179,9 +149,5 @@ fun player( @BrigadierDsl fun players( name: String, -): RequiredArgumentConstructor< - S, - ListPlayerArgumentDescriptor - > { - return argument(name, EntityArgumentType.players(), ListPlayerArgumentDescriptor) -} +): RequiredArgumentConstructor = + argument(name, EntityArgumentType.players(), ListPlayerArgumentDescriptor) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt index 6544a31f3..258676e5c 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt @@ -42,9 +42,8 @@ import com.mojang.brigadier.arguments.LongArgumentType */ @JvmName("valueBoolArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Boolean { - return BoolArgumentType.getBool(context, name) -} +fun DefaultArgumentReader.value(): Boolean = + BoolArgumentType.getBool(context, name) /** * Reads the boolean value from the argument in @@ -54,9 +53,8 @@ fun DefaultArgumentReader.value(): Boolean { */ @JvmName("valueDoubleArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Double { - return DoubleArgumentType.getDouble(context, name) -} +fun DefaultArgumentReader.value(): Double = + DoubleArgumentType.getDouble(context, name) /** * Reads the float value from the argument in @@ -66,9 +64,8 @@ fun DefaultArgumentReader.value(): Double { */ @JvmName("valueFloatArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Float { - return FloatArgumentType.getFloat(context, name) -} +fun DefaultArgumentReader.value(): Float = + FloatArgumentType.getFloat(context, name) /** * Reads the integer value from the argument in @@ -78,9 +75,8 @@ fun DefaultArgumentReader.value(): Float { */ @JvmName("valueIntArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Int { - return IntegerArgumentType.getInteger(context, name) -} +fun DefaultArgumentReader.value(): Int = + IntegerArgumentType.getInteger(context, name) /** * Reads the long value from the argument in @@ -90,9 +86,8 @@ fun DefaultArgumentReader.value(): Int { */ @JvmName("valueLongArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Long { - return LongArgumentType.getLong(context, name) -} +fun DefaultArgumentReader.value(): Long = + LongArgumentType.getLong(context, name) /** * Creates a boolean argument with [name] as the parameter name. @@ -100,9 +95,8 @@ fun DefaultArgumentReader.value(): Long { @BrigadierDsl fun boolean( name: String, -): DefaultArgumentConstructor { - return argument(name, BoolArgumentType.bool()) -} +): DefaultArgumentConstructor = + argument(name, BoolArgumentType.bool()) /** * Creates a double argument with [name] as the parameter name. @@ -115,9 +109,8 @@ fun double( name: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, DoubleArgumentType.doubleArg(min, max)) -} +): DefaultArgumentConstructor = + argument(name, DoubleArgumentType.doubleArg(min, max)) /** * Creates a float argument with [name] as the parameter name. @@ -130,9 +123,8 @@ fun float( name: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, FloatArgumentType.floatArg(min, max)) -} +): DefaultArgumentConstructor = + argument(name, FloatArgumentType.floatArg(min, max)) /** * Creates an integer argument with [name] as the parameter name. @@ -145,9 +137,8 @@ fun integer( name: String, min: Int = -Int.MAX_VALUE, max: Int = Int.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, IntegerArgumentType.integer(min, max)) -} +): DefaultArgumentConstructor = + argument(name, IntegerArgumentType.integer(min, max)) /** * Creates a long argument with [name] as the parameter name. @@ -160,6 +151,5 @@ fun long( name: String, min: Long = -Long.MAX_VALUE, max: Long = Long.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, LongArgumentType.longArg(min, max)) -} +): DefaultArgumentConstructor = + argument(name, LongArgumentType.longArg(min, max)) diff --git a/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 51e93f749..ccd2212ab 100644 --- a/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -28,10 +28,10 @@ import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig import com.lambda.interaction.construction.StructureRegistry import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint -import com.lambda.task.RootTask.run +import com.lambda.interaction.construction.blueprint.toBlueprint +import com.lambda.task.start import com.lambda.task.tasks.BuildTask -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.tasks.build import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info import com.lambda.util.extension.CommandBuilder @@ -62,12 +62,12 @@ object BuildCommand : LambdaCommand( .loadStructureByRelativePath(Path.of(pathString)) .let { template -> info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}") - lastBuildTask = with(AutomationConfig.Companion.DEFAULT) { + lastBuildTask = with(AutomationConfig.DEFAULT) { template.toStructure() .move(player.blockPos) .toBlueprint() .build() - .run() + .start() } return@executeWithResult success() diff --git a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt index 8d3a6e6af..3de171a5f 100644 --- a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt @@ -29,7 +29,7 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.categories.FriendCategory -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.network.mojang.getProfile import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 33adbaee7..c5740b639 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,12 +28,12 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithSpace -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.task.RootTask +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.findContainers import com.lambda.task.Task +import com.lambda.task.start +import com.lambda.task.tasks.transfer import com.lambda.threading.runSafeAutomated import com.lambda.util.CommunicationUtils.info import com.lambda.util.extension.CommandBuilder @@ -51,60 +51,78 @@ object TransferCommand : LambdaCommand( required(integer("amount", 1)) { amount -> required(string("from")) { from -> suggests { ctx, builder -> - val selection = selectStack(amount(ctx).value()) { + val selection = stackSelection(amount(ctx).value()) { isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainersWithMaterial() + val containers = + findContainers( + containerSelection { + hasStack(selection) + } + ).toList() val indexedContainers = containers.withIndex() suggestMatching( indexedContainers, builder, - { (index, container) -> - "\"${index + 1}. ${container.name}\"" - }, - { (_, container) -> - container.description(selection) - } + { (index, container) -> "\"${index + 1}. ${container.name}\"" }, + { (_, container) -> container.descriptionAndStock(selection) } ) } ?: builder.buildFuture() } required(string("to")) { to -> suggests { ctx, builder -> - val selection = selectStack(amount(ctx).value()) { - isItem(stack(ctx).value().item) - } + val selection = + stackSelection(amount(ctx).value()) { + isItem(stack(ctx).value().item) + } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainersWithSpace() + val containers = + findContainers( + containerSelection { + hasSpace(selection) + } + ).toList() val indexedContainers = containers.withIndex() suggestMatching( indexedContainers, builder, - { (index, container) -> - "\"${index + 1}. ${container.name}\"" - }, - { (_, container) -> - container.description(selection) - } + { (index, container) -> "\"${index + 1}. ${container.name}\"" }, + { (_, container) -> container.descriptionAndStock(selection) } ) } ?: builder.buildFuture() } executeWithResult { - val selection = selectStack(amount().value()) { - isItem(stack().value().item) - } AutomationConfig.DEFAULT.runSafeAutomated { - val fromContainer = ContainerHandler.containers().find { - it.name == from().value().split(".").last().trim() - } ?: return@executeWithResult failure("From container not found") + fun parsePredicate(raw: String): (com.lambda.interaction.container.Container) -> Boolean { + val clean = raw.trim().removeSurrounding("\"") + val indexPrefix = clean.substringBefore(". ").toIntOrNull() + val nameAfterDot = if (indexPrefix != null) clean.substringAfter(". ").trim() else clean + return { container -> + container.name.equals(clean, ignoreCase = true) || + container.name.equals(nameAfterDot, ignoreCase = true) + } + } - val toContainer = ContainerHandler.containers().find { - it.name == to().value().split(".").last().trim() - } ?: return@executeWithResult failure("To container not found") + val fromSelection = + containerSelection { + predicate(parsePredicate(from().value())) + } - fromContainer.transferByTask(selection, toContainer).execute(RootTask) + val toSelection = + containerSelection { + predicate(parsePredicate(to().value())) + } + lastContainerTransfer = + transfer( + stackSelection(amount().value()) { + isItem(stack().value().item) + }, + fromSelection, + toSelection + ).start() } return@executeWithResult success() } @@ -115,9 +133,11 @@ object TransferCommand : LambdaCommand( required(literal("cancel")) { executeWithResult { - lastContainerTransfer?.cancel() ?: run { - return@executeWithResult failure("No transfer to cancel") - } + lastContainerTransfer + ?.cancel() + ?: run { + return@executeWithResult failure("No transfer to cancel") + } this@TransferCommand.info("$lastContainerTransfer cancelled") lastContainerTransfer = null success() diff --git a/src/main/kotlin/com/lambda/config/ConfigCategory.kt b/src/main/kotlin/com/lambda/config/ConfigCategory.kt index 989cd5c31..fc181fc12 100644 --- a/src/main/kotlin/com/lambda/config/ConfigCategory.kt +++ b/src/main/kotlin/com/lambda/config/ConfigCategory.kt @@ -25,7 +25,7 @@ import com.lambda.config.migration.ConfigMigrationHandler import com.lambda.core.Loadable import com.lambda.event.events.ClientEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.interaction.handlers.BaritoneHandler.primary +import com.lambda.interaction.handler.handlers.BaritoneHandler.primary import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils.info import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/config/ConfigEditor.kt b/src/main/kotlin/com/lambda/config/ConfigEditor.kt index 8700d9889..df332bd5d 100644 --- a/src/main/kotlin/com/lambda/config/ConfigEditor.kt +++ b/src/main/kotlin/com/lambda/config/ConfigEditor.kt @@ -21,7 +21,7 @@ package com.lambda.config import com.lambda.config.entries.Property import com.lambda.config.entries.Setting -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import kotlin.reflect.KProperty0 import kotlin.reflect.jvm.isAccessible diff --git a/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt b/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt index 4272fdf96..d90420e89 100644 --- a/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt @@ -31,6 +31,9 @@ import com.lambda.config.categories.AutomationCategory import com.lambda.context.Automated import com.lambda.module.Module +@DslMarker +private annotation class AutomationConfigMarker + open class AutomationConfig( name: String, configCategory: ConfigCategory = AutomationCategory @@ -55,20 +58,17 @@ open class AutomationConfig( private const val HOTBAR_TAB = "Hotbar" private const val EAT_TAB = "Eat" - @DslMarker - private annotation class AutomationConfigMarker - - @AutomationConfigMarker - context(module: Module) - fun IMutableAutomationConfig.setDefaultAutomationConfig( - name: String = module.name - ) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } - - @AutomationConfigMarker - fun IMutableAutomationConfig.setDefaultAutomationConfig( - name: String - ) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } - val DEFAULT = AutomationConfig("Default") } -} \ No newline at end of file +} + +@AutomationConfigMarker +context(module: Module) +fun IMutableAutomationConfig.setDefaultAutomationConfig( + name: String = module.name +) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } + +@AutomationConfigMarker +fun IMutableAutomationConfig.setDefaultAutomationConfig( + name: String +) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt b/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt index 7e98fc835..6e7922008 100644 --- a/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt @@ -17,8 +17,8 @@ package com.lambda.config.blocks -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.util.Describable import com.lambda.util.NamedEnum import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt index 1e0e5714b..d727e4831 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt @@ -19,7 +19,8 @@ package com.lambda.config.blocks import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum @@ -55,17 +56,17 @@ interface EatConfig { @Suppress("unused") enum class SelectionPriority( - val comparator: Comparator, + val comparator: Comparator>, override val displayName: String, override val description: String ): NamedEnum, Describable { LeastNutritious( - compareBy { it.item.nutrition }, + compareBy { it.stack.item.nutrition }, "Least Nutritious", "Eats food items with the least nutritional value." ), MostNutritious( - compareByDescending { it.item.nutrition }, + compareByDescending { it.stack.item.nutrition }, "Most Nutritious", "Eats food items with the most nutritional value." ) @@ -94,14 +95,17 @@ interface EatConfig { } ?: false context(c: Automated) - fun selector() = selectStack(sorter = c.eatConfig.selectionPriority.comparator) { - when(this@Reason) { - None -> any() - Hunger -> isOneOfItems(c.eatConfig.nutritiousFood) - Damage -> isOneOfItems(c.eatConfig.regenerationFood) - Fire -> isOneOfItems(c.eatConfig.resistanceFood) - } and if (c.eatConfig.ignoreBadFood) isNoneOfItems(c.eatConfig.badFood) else any() - } + fun selector() = + stackSelection { + when(this@Reason) { + Hunger -> ofAnyItems(c.eatConfig.nutritiousFood) + Damage -> ofAnyItems(c.eatConfig.regenerationFood) + Fire -> ofAnyItems(c.eatConfig.resistanceFood) + else -> {} + } + if (c.eatConfig.ignoreBadFood) noneOfItems(c.eatConfig.badFood) + sortedWith(c.eatConfig.selectionPriority.comparator) + } } companion object { diff --git a/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt b/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt index 4545e7d1c..ea27b3e0b 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt @@ -20,7 +20,7 @@ package com.lambda.config.blocks import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.util.EntityUtils.EntityGroup import com.lambda.util.EntityUtils.entityGroup import com.lambda.util.extension.blockColor diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 6a0c26116..a5eaa4285 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -17,11 +17,11 @@ package com.lambda.config.blocks -import com.lambda.context.SafeContext 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.container.Container +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelection import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.item.Item @@ -29,56 +29,47 @@ import net.minecraft.item.Item interface InventoryConfig { val tickStageMask: Collection val disposables: Collection - val swapWithDisposables: Boolean - val providerPriority: Priority - val storePriority: Priority - - val accessShulkerBoxes: Boolean - val accessChests: Boolean - val accessEnderChest: Boolean - val accessStashes: Boolean + val accessPriority: ContainerPriority + val storePriority: ContainerPriority + val allowedContainers: Collection val containerSelection: ContainerSelection - get() = ContainerSelection.selectContainer { - val allowedContainers = buildSet { - addAll(MaterialContainer.Rank.entries) - if (!accessShulkerBoxes) remove(MaterialContainer.Rank.ShulkerBox) - if (!accessEnderChest) remove(MaterialContainer.Rank.EnderChest) - if (!accessChests) remove(MaterialContainer.Rank.Chest) - if (!accessStashes) remove(MaterialContainer.Rank.Stash) - } - ofAnyType(*allowedContainers.toTypedArray()) - } + get() = containerSelection { ofAnyType(*allowedContainers.toTypedArray()) } - enum class Priority( + val enderChestSearchRadius: Int + @Suppress("unused") + enum class ContainerPriority( override val displayName: String, override val description: String ) : NamedEnum, Describable { WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."), WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers."); - context(_: SafeContext) - fun materialComparator(selection: StackSelection) = + fun materialComparator(selection: StackSelection): Comparator = when (this) { - WithMaxItems -> compareBy { it.rank } - .thenByDescending { it.materialAvailable(selection) } + WithMaxItems -> compareBy { it.type } + .thenByDescending { it.count(selection) } .thenBy { it.name } - WithMinItems -> compareBy { it.rank } - .thenBy { it.materialAvailable(selection) } + WithMinItems -> compareBy { it.type } + .thenBy { it.count(selection) } .thenBy { it.name } } - context(_: SafeContext) - fun spaceComparator(selection: StackSelection) = + fun spaceComparator(selection: StackSelection): Comparator = when (this) { - WithMaxItems -> compareBy { it.rank } - .thenByDescending { it.spaceAvailable(selection) } + WithMaxItems -> compareBy { it.type } + .thenByDescending { it.spaceLeft(selection) } .thenBy { it.name } - WithMinItems -> compareBy { it.rank } - .thenBy { it.spaceAvailable(selection) } + WithMinItems -> compareBy { it.type } + .thenBy { it.spaceLeft(selection) } .thenBy { it.name } } + + fun comparator(countSelector: (Container, T) -> Int) = + compareBy> { it.first.type } + .thenByDescending { countSelector(it.first, it.second) } + .thenBy { it.first.name } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt index ee64c7e13..4a2c50c57 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt @@ -21,22 +21,19 @@ import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.config.Group import com.lambda.event.events.TickEvent.Companion.ALL_STAGES +import com.lambda.interaction.container.ContainerType import com.lambda.util.item.ItemUtils class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { companion object { - private const val CONTAINER_GROUP = "Container" - private const val ACCESS_GROUP = "Access" + const val ENDER_CHEST_GROUP = "Ender Chest" } override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true) - @Group(CONTAINER_GROUP) override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot") - @Group(CONTAINER_GROUP) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones") - @Group(CONTAINER_GROUP) override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from") - @Group(CONTAINER_GROUP) override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to") + override val disposables by c.setting("Disposables", ItemUtils.DEFAULT_DISPOSABLES, description = "Items that will be ignored when checking for a free slot") + override val allowedContainers by c.setting("Allowed Containers", ContainerType.entries, description = "What containers to search for items in") + override val accessPriority by c.setting("Access Priority", InventoryConfig.ContainerPriority.WithMinItems, description = "What container to prefer when retrieving the item from") + override val storePriority by c.setting("Store Priority", InventoryConfig.ContainerPriority.WithMinItems, description = "What container to prefer when storing the item to") - @Group(ACCESS_GROUP) override val accessShulkerBoxes by c.setting("Access Shulker Boxes", false, "Allow access to the player's shulker boxes") - @Group(ACCESS_GROUP) override val accessChests by c.setting("Access Chests", false, "Allow access to the player's normal chests") - @Group(ACCESS_GROUP) override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest") - @Group(ACCESS_GROUP) override val accessStashes by c.setting("Access Stashes", false, "Allow access to the player's stashes") + @Group(ENDER_CHEST_GROUP) override val enderChestSearchRadius by c.setting("Initial Search Radius", 10, 0..20, unit = " blocks", description = "The search radius for ender chests to access if one hasn't been accessed yet") } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt b/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt index a20530cf4..3f13dfa0b 100644 --- a/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt @@ -18,8 +18,8 @@ package com.lambda.config.blocks import com.lambda.event.events.TickEvent -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationMode interface RotationConfig { /** diff --git a/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt b/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt index cdaaf397c..c927e65a5 100644 --- a/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt @@ -21,7 +21,7 @@ import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationMode import kotlin.math.PI import kotlin.math.abs import kotlin.math.cos diff --git a/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt b/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt index d29bf56af..071f78564 100644 --- a/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt @@ -22,10 +22,10 @@ import com.lambda.config.ConfigBlock import com.lambda.config.hide import com.lambda.config.withEdits import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler.isFriend -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.threading.runSafe import com.lambda.util.extension.fullHealth import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/config/entries/Setting.kt b/src/main/kotlin/com/lambda/config/entries/Setting.kt index e68c0def7..17777d42a 100644 --- a/src/main/kotlin/com/lambda/config/entries/Setting.kt +++ b/src/main/kotlin/com/lambda/config/entries/Setting.kt @@ -31,6 +31,7 @@ import com.lambda.config.ConfigEntry import com.lambda.config.EntryCore import com.lambda.config.EntryLayer import com.lambda.config.MultipleLayerType +import com.lambda.config.entries.Setting.ValueListener import com.lambda.context.SafeContext import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.threading.runSafe @@ -229,42 +230,40 @@ abstract class Setting( override fun toString() = "Setting $name: $value" - companion object { - /** - * Will only register changes of the variable, not the content of the variable! - * E.g., if the variable is a list, it will only register if the list reference changes, not if the content of the list changes. - */ - @ConfigEntryDsl - fun , T> S.onValueChange(block: SafeContext.(from: T, to: T) -> Unit) = - apply { - listeners.add(ValueListener(true) { from, to -> - runSafe { block(from, to) } - }) - } - - @ConfigEntryDsl - fun , T> S.onValueChangeUnsafe(block: (from: T, to: T) -> Unit) = - apply { listeners.add(ValueListener(true, block)) } + class ValueListener(val requiresValueChange: Boolean, val execute: (from: T, to: T) -> Unit) +} - @ConfigEntryDsl - fun , T> S.onValueSet(block: SafeContext.(from: T, to: T) -> Unit) = - apply { - listeners.add(ValueListener(false) { from, to -> - runSafe { block(from, to) } - }) - } +/** + * Will only register changes of the variable, not the content of the variable! + * E.g., if the variable is a list, it will only register if the list reference changes, not if the content of the list changes. + */ +@ConfigEntryDsl +fun , T> S.onValueChange(block: SafeContext.(from: T, to: T) -> Unit) = + apply { + listeners.add(ValueListener(true) { from, to -> + runSafe { block(from, to) } + }) + } - @ConfigEntryDsl - fun , T> S.onValueSetUnsafe(block: (from: T, to: T) -> Unit) = - apply { listeners.add(ValueListener(false, block)) } +@ConfigEntryDsl +fun , T> S.onValueChangeUnsafe(block: (from: T, to: T) -> Unit) = + apply { listeners.add(ValueListener(true, block)) } - @ConfigEntryDsl - fun , T> S.disabled(predicate: () -> Boolean) = - apply { disabled = predicate } +@ConfigEntryDsl +fun , T> S.onValueSet(block: SafeContext.(from: T, to: T) -> Unit) = + apply { + listeners.add(ValueListener(false) { from, to -> + runSafe { block(from, to) } + }) } - class ValueListener(val requiresValueChange: Boolean, val execute: (from: T, to: T) -> Unit) -} +@ConfigEntryDsl +fun , T> S.onValueSetUnsafe(block: (from: T, to: T) -> Unit) = + apply { listeners.add(ValueListener(false, block)) } + +@ConfigEntryDsl +fun , T> S.disabled(predicate: () -> Boolean) = + apply { disabled = predicate } class SettingEntryLayer, U>( override val parent: EntryLayer.Multiple>, diff --git a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt new file mode 100644 index 000000000..0effb88ca --- /dev/null +++ b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2026 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 . + */ + +@file:Suppress("unused") + +package com.lambda.config.serializers + +import com.lambda.config.Deserializer +import com.lambda.config.JsonOps +import com.lambda.config.Serializer +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.containers.external.EnderChestContainer +import net.minecraft.item.ItemStack +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.node.ArrayNode +import tools.jackson.databind.node.JsonNodeFactory +import tools.jackson.databind.node.ObjectNode + +object EnderChestContainerSerializer : Serializer(EnderChestContainer::class.java) { + override fun serialize(container: EnderChestContainer, gen: JsonGenerator, ctxt: SerializationContext) { + val root = JsonNodeFactory.instance.objectNode() + root.put("Type", ContainerType.EnderChest.name) + + val stackArray = JsonNodeFactory.instance.arrayNode() + for (stack in container.stacks) { + val encoded = ItemStack.CODEC + .encodeStart(JsonOps.UNCOMPRESSED, stack) + .result() + encoded.ifPresent { stackArray.add(it) } + } + root.set("Stacks", stackArray) + + gen.writeTree(root) + } +} + +object EnderChestContainerDeserializer : Deserializer(EnderChestContainer::class.java) { + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): EnderChestContainer? { + if (p == null || ctxt == null) return null + return deserialize(p, ctxt, EnderChestContainer) + } + + override fun deserialize(p: JsonParser, ctxt: DeserializationContext, intoValue: EnderChestContainer): EnderChestContainer { + val root = p.readValueAsTree() + val stackArray = root.get("Stacks") as? ArrayNode + val stacks = + stackArray?.mapNotNull { element -> + ItemStack.CODEC + .parse(JsonOps.UNCOMPRESSED, element) + .result() + .orElse(null) + } ?: emptyList() + + EnderChestContainer.update(stacks) + EnderChestContainer.scanStacksForNestedContainers() + return EnderChestContainer + } +} diff --git a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt new file mode 100644 index 000000000..062915fab --- /dev/null +++ b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt @@ -0,0 +1,115 @@ +@file:Suppress("unused") + +package com.lambda.config.serializers + +import com.lambda.config.Deserializer +import com.lambda.config.JsonOps +import com.lambda.config.Serializer +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.container.containers.external.ChestContainer +import com.lambda.interaction.container.containers.external.DoubleChestContainer +import com.lambda.interaction.container.containers.external.PlacedShulkerBoxContainer +import net.minecraft.block.Blocks +import net.minecraft.item.ItemStack +import net.minecraft.registry.Registries +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.node.ArrayNode +import tools.jackson.databind.node.JsonNodeFactory +import tools.jackson.databind.node.ObjectNode + +object PlacedContainerSerializer : Serializer(PlacedContainer::class.java) { + override fun serialize(container: PlacedContainer, gen: JsonGenerator, ctxt: SerializationContext) { + val root = JsonNodeFactory.instance.objectNode() + + root.put("Type", container.type.name) + root.set("Pos", blockPosNode(container.pos)) + + when (container) { + is PlacedShulkerBoxContainer -> { + val blockId = Registries.BLOCK.getId(container.block).toString() + root.put("Block", blockId) + } + is DoubleChestContainer -> { + root.put("Double", true) + root.set("Left Pos", blockPosNode(container.leftPos)) + root.set("Right Pos", blockPosNode(container.rightPos)) + } + else -> {} + } + + val stackArray = JsonNodeFactory.instance.arrayNode() + for (stack in container.stacks) { + val encoded = ItemStack.CODEC + .encodeStart(JsonOps.UNCOMPRESSED, stack) + .result() + encoded.ifPresent { stackArray.add(it) } + } + root.set("Stacks", stackArray) + + gen.writeTree(root) + } + + private fun blockPosNode(pos: BlockPos): ObjectNode { + val node = JsonNodeFactory.instance.objectNode() + node.put("X", pos.x) + node.put("Y", pos.y) + node.put("Z", pos.z) + return node + } +} + +object PlacedContainerDeserializer : Deserializer(PlacedContainer::class.java) { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PlacedContainer? { + val root = p.readValueAsTree() ?: return null + + val typeName = root.get("Type")?.asString() ?: return null + val type = ContainerType.entries.find { it.name == typeName } ?: return null + + val posNode = root.get("Pos") as? ObjectNode ?: return null + val pos = parseBlockPos(posNode) ?: return null + + val stackArray = root.get("Stacks") as? ArrayNode + val stacks = + stackArray?.mapNotNull { element -> + ItemStack.CODEC + .parse(JsonOps.UNCOMPRESSED, element) + .result() + .orElse(null) + } ?: emptyList() + + val container = + when (type) { + ContainerType.PlacedShulkerBox -> { + val blockId = root.get("Block")?.asString() + val block = blockId?.let { Registries.BLOCK.get(Identifier.of(it)) } ?: Blocks.SHULKER_BOX + PlacedShulkerBoxContainer(pos, block, null, stacks) + } + ContainerType.Chest -> { + if (root.get("Double")?.asBoolean() == true) { + val leftPos = (root.get("Left Pos") as? ObjectNode)?.let { parseBlockPos(it) } ?: pos + val rightPos = (root.get("Right Pos") as? ObjectNode)?.let { parseBlockPos(it) } ?: pos + DoubleChestContainer(pos, leftPos, rightPos, stacks) + } else { + ChestContainer(pos, stacks) + } + } + else -> return null + } + + container.scanStacksForNestedContainers() + return container + } + + private fun parseBlockPos(node: ObjectNode): BlockPos? { + val x = node.get("X")?.asInt() ?: return null + val y = node.get("Y")?.asInt() ?: return null + val z = node.get("Z")?.asInt() ?: return null + return BlockPos(x, y, z) + } +} 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 ed2c94041..00629b99f 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -36,7 +36,6 @@ import com.lambda.event.Muteable import com.lambda.event.events.ButtonEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.gui.dsl.ImGuiBuilder -import com.lambda.imgui.ImGui.isMouseClicked import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiHoveredFlags import com.lambda.imgui.flag.ImGuiMouseButton diff --git a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt index 2c0031cb1..0bfdb1899 100644 --- a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt @@ -19,14 +19,14 @@ package com.lambda.event.events import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.Container import net.minecraft.screen.slot.Slot sealed class ContainerEvent { data class Transfer( val fromSlot: Slot, val toSlot: Slot, - val from: MaterialContainer, - val to: MaterialContainer + val from: Container, + val to: Container ) : ICancellable by Cancellable() } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/RotationEvent.kt b/src/main/kotlin/com/lambda/event/events/RotationEvent.kt index c6f6f2a8a..c807c825e 100644 --- a/src/main/kotlin/com/lambda/event/events/RotationEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/RotationEvent.kt @@ -18,7 +18,7 @@ package com.lambda.event.events import com.lambda.event.Event -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import net.minecraft.client.input.Input sealed class RotationEvent { diff --git a/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt b/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt index d7aebfca2..767c56ca6 100644 --- a/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt +++ b/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt @@ -63,7 +63,7 @@ class ChunkedRenderer( private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>() init { - owner.listenUnsafe { event -> + owner.listenUnsafe(alwaysListen = true) { event -> val pos = event.pos val world = mc.world ?: return@listenUnsafe world.getWorldChunk(pos)?.chunkData?.markDirty() @@ -77,9 +77,9 @@ class ChunkedRenderer( if (zInChunk == 15) world.getWorldChunk(pos.south())?.chunkData?.markDirty() } - owner.listenUnsafe { event -> event.chunk.chunkData.markDirty() } - owner.listenUnsafe { chunkMap.remove(it.chunk.chunkKey)?.clearData() } - owner.listenUnsafe { clear() } + owner.listenUnsafe(alwaysListen = true) { event -> event.chunk.chunkData.markDirty() } + owner.listenUnsafe(alwaysListen = true) { chunkMap.remove(it.chunk.chunkKey)?.clearData() } + owner.listenUnsafe(alwaysListen = true) { clear() } owner.listenConcurrentlyUnsafe { if (pauseUpdates()) return@listenConcurrentlyUnsafe diff --git a/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt b/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt index edb196076..197e207fc 100644 --- a/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt +++ b/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt @@ -20,7 +20,7 @@ package com.lambda.graphics.text import com.lambda.Lambda.LOG import com.lambda.config.Config import com.lambda.config.categories.FontCategory -import com.lambda.config.entries.Setting.Companion.onValueChangeUnsafe +import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.core.Loadable import com.lambda.event.events.ClientEvent import com.lambda.event.listener.SafeListener.Companion.listen diff --git a/src/main/kotlin/com/lambda/gui/DearImGui.kt b/src/main/kotlin/com/lambda/gui/DearImGui.kt index fba0d5cf1..794b7daa1 100644 --- a/src/main/kotlin/com/lambda/gui/DearImGui.kt +++ b/src/main/kotlin/com/lambda/gui/DearImGui.kt @@ -62,6 +62,11 @@ object DearImGui : Loadable { addRanges(io.fonts.glyphRangesGreek) addChar(EXTERNAL_LINK) addChar(BREAD_CRUMB_SEPARATOR) + addChar('▼') + addChar('▶') + addChar('▲') + addChar('◀') + addChar('⚙') }.buildRanges() val fontConfig = ImFontConfig() val size = BASE_FONT_SCALE * scale @@ -80,19 +85,20 @@ object DearImGui : Loadable { val dpi = ClickGuiLayout.deviceScaleMultiplier() val base = ClickGuiLayout.BASE_SCALE_MULTI * dpi val fontScaleSetting = ClickGuiLayout.fontScale - val scale = (base * userPercent * fontScaleSetting).toFloat() + val guiScale = (base * userPercent).toFloat() + val fontScale = (guiScale * fontScaleSetting).toFloat() if (lastScale == 0f) { - targetScale = scale + targetScale = fontScale updateScale(targetScale) lastScale = targetScale } - if (scale > 0 && abs(scale - lastScale) > 0.001f) { - if (abs(scale - targetScale) > 0.001f) { + if (fontScale > 0 && abs(fontScale - lastScale) > 0.001f) { + if (abs(fontScale - targetScale) > 0.001f) { lastScaleChangeTimestamp = System.currentTimeMillis() scaleChanged = true - targetScale = scale + targetScale = fontScale } } @@ -101,7 +107,6 @@ object DearImGui : Loadable { lastScale = targetScale scaleChanged = false } - val framebuffer = mc.framebuffer val prevFramebuffer = (framebuffer.getColorAttachment() as GlTexture).getOrCreateFramebuffer( (RenderSystem.getDevice() as GlBackend).bufferManager, @@ -120,7 +125,7 @@ object DearImGui : Loadable { implGlfw.newFrame() implGl3.newFrame() - ClickGuiLayout.applyStyle(lastScale) + ClickGuiLayout.applyStyle(guiScale) ImGui.newFrame() GuiEvent.NewImguiFrame.post() diff --git a/src/main/kotlin/com/lambda/gui/LambdaScreen.kt b/src/main/kotlin/com/lambda/gui/LambdaScreen.kt index 64b3c2804..03849936b 100644 --- a/src/main/kotlin/com/lambda/gui/LambdaScreen.kt +++ b/src/main/kotlin/com/lambda/gui/LambdaScreen.kt @@ -18,9 +18,13 @@ package com.lambda.gui import com.lambda.gui.components.ClickGuiLayout +import com.lambda.gui.components.QuickSearch import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.input.KeyInput +import net.minecraft.client.util.InputUtil import net.minecraft.text.Text +import org.lwjgl.glfw.GLFW object LambdaScreen : Screen(Text.of("Lambda")) { /** @@ -30,6 +34,12 @@ object LambdaScreen : Screen(Text.of("Lambda")) { var parentScreen: Screen? = null override fun shouldPause() = false + override fun shouldCloseOnEsc(): Boolean { + if (QuickSearch.isOpen || System.currentTimeMillis() - QuickSearch.lastClosedTimestamp < 200L) { + return false + } + return true + } override fun removed() = ClickGuiLayout.close() override fun render(context: DrawContext?, mouseX: Int, mouseY: Int, deltaTicks: Float) {} @@ -55,11 +65,41 @@ object LambdaScreen : Screen(Text.of("Lambda")) { } override fun close() { + if (QuickSearch.isOpen) { + QuickSearch.close() + return + } + if (System.currentTimeMillis() - QuickSearch.lastClosedTimestamp < 200L) { + return + } val previous = parentScreen parentScreen = null client?.setScreen(previous) } + override fun keyPressed(input: KeyInput): Boolean { + val win = client?.window + val isCtrl = (input.modifiers() and GLFW.GLFW_MOD_CONTROL != 0) + || (win != null && (InputUtil.isKeyPressed(win, GLFW.GLFW_KEY_LEFT_CONTROL) || InputUtil.isKeyPressed(win, GLFW.GLFW_KEY_RIGHT_CONTROL))) + + if (input.key() == GLFW.GLFW_KEY_F && isCtrl) { + if (!QuickSearch.isOpen) QuickSearch.open() + return true + } + + if (input.key() == GLFW.GLFW_KEY_ESCAPE) { + if (QuickSearch.isOpen) { + QuickSearch.close() + return true + } + if (System.currentTimeMillis() - QuickSearch.lastClosedTimestamp < 200L) { + return true + } + } + + return super.keyPressed(input) + } + override fun applyBlur(context: DrawContext?) { if (!ClickGuiLayout.backgroundBlur) return super.applyBlur(context) diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt index 2b205382b..0f8b8fb23 100644 --- a/src/main/kotlin/com/lambda/gui/MenuBar.kt +++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt @@ -36,15 +36,14 @@ import com.lambda.gui.components.QuickSearch import com.lambda.gui.components.SettingsWidget.buildConfigSettingsContext import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImGui -import com.lambda.imgui.ImGui.closeCurrentPopup import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiStyleVar import com.lambda.imgui.flag.ImGuiWindowFlags import com.lambda.imgui.type.ImBoolean -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.ModuleRegistry import com.lambda.module.ModuleRegistry.moduleNameMap -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.network.LambdaAPI import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info @@ -62,6 +61,7 @@ import java.util.* object MenuBar { private var aboutRequested = false + private var shortcutsModalOpen = false var newConfigName = "" val headerLogo = upload("textures/lambda_text_color.png") val lambdaLogo = upload("textures/lambda.png") @@ -80,6 +80,7 @@ object MenuBar { menu("Automation Configs") { buildAutomationConfigsMenu() } menu("Minecraft") { buildMinecraftMenu() } menu("Help") { buildHelpMenu() } + buildSearchButton() buildGitHubReference() } @@ -89,6 +90,13 @@ object MenuBar { } aboutPopup() + + if (shortcutsModalOpen) { + ImGui.openPopup("Keyboard Shortcuts") + shortcutsModalOpen = false + } + + shortcutsPopup() } private fun ImGuiBuilder.lambdaMenu() { @@ -246,10 +254,10 @@ object MenuBar { } private fun ImGuiBuilder.buildHudMenu() { - menuItem(if (HudGuiLayout.isLocked) "Unlock" else "Lock") { + menuItem(if (HudGuiLayout.isLocked) "Unlock HUD (Enable Dragging)" else "Lock HUD (Disable Dragging)") { HudGuiLayout.isLocked = !HudGuiLayout.isLocked } - menuItem(if (HudGuiLayout.isShownInGUI) "Hide" else "Show") { + menuItem(if (HudGuiLayout.isShownInGUI) "Hide HUD in GUI" else "Show HUD in GUI") { HudGuiLayout.isShownInGUI = !HudGuiLayout.isShownInGUI } separator() @@ -259,9 +267,46 @@ object MenuBar { } private fun ImGuiBuilder.buildGuiMenu() { - buildConfigSettingsContext(ClickGuiLayout) + menu("Layout & Windows") { + menuItem("Tile Windows (Auto-Arrange)") { ClickGuiLayout.tileWindows() } + menuItem("Reset Window Positions") { ClickGuiLayout.resetWindowPositions() } + separator() + menuItem("Collapse All Modules") { ClickGuiLayout.collapseAllModules() } + } + menu("Theme Presets") { + ClickGuiLayout.ThemePreset.entries.forEach { preset -> + menuItem(preset.displayName, selected = ClickGuiLayout.themePreset == preset) { + ClickGuiLayout.applyTheme(preset) + } + } + separator() + menuItem("Sync Theme to Accent Color") { + ClickGuiLayout.syncThemeToAccent(ClickGuiLayout.primaryColor) + } + } + separator() + menu("Display Options") { + menuItem("Show Keybind Badges", selected = ClickGuiLayout.showKeybindBadges) { + ClickGuiLayout.showKeybindBadges = !ClickGuiLayout.showKeybindBadges + } + menuItem("Highlight Enabled Modules", selected = ClickGuiLayout.highlightEnabledModules) { + ClickGuiLayout.highlightEnabledModules = !ClickGuiLayout.highlightEnabledModules + } + menuItem("Allow Window Collapse", selected = ClickGuiLayout.allowWindowCollapse) { + ClickGuiLayout.allowWindowCollapse = !ClickGuiLayout.allowWindowCollapse + } + menuItem("Category Window Search", selected = ClickGuiLayout.categoryWindowSearch) { + ClickGuiLayout.categoryWindowSearch = !ClickGuiLayout.categoryWindowSearch + } + menuItem("Show Category Counts", selected = ClickGuiLayout.showCategoryCounts) { + ClickGuiLayout.showCategoryCounts = !ClickGuiLayout.showCategoryCounts + } + } + separator() + menu("All GUI Settings") { + buildConfigSettingsContext(ClickGuiLayout) + } } - private fun ImGuiBuilder.buildModulesMenu() { menu("Module Tag") { ModuleTag.defaults.forEach { tag -> @@ -439,9 +484,13 @@ object MenuBar { } private fun ImGuiBuilder.buildHelpMenu() { - menuItem("Quick Search...", "Shift+Shift") { + menuItem("Quick Search...", "Ctrl+F / Shift+Shift") { QuickSearch.open() } + menuItem("Keyboard Shortcuts Guide") { + shortcutsModalOpen = true + } + separator() menuItem("Documentation $EXTERNAL_LINK") { Util.getOperatingSystem().open("$REPO_URL/wiki") } @@ -532,4 +581,52 @@ object MenuBar { } } } + + private fun ImGuiBuilder.buildSearchButton() { + val label = "Search (Ctrl+F)" + val btnW = ImGui.calcTextSize(label).x + style.framePadding.x * 2.5f + val iconSize = (frameHeight - 6f).coerceAtLeast(14f) + val spacingPx = 8f + sameLine() + cursorPosX = windowContentRegionMaxX - iconSize - spacingPx - btnW - 8f + withStyleVar(ImGuiStyleVar.FramePadding, style.framePadding.x * 0.8f, 2f) { + smallButton(label) { + QuickSearch.open() + } + } + lambdaTooltip("Search modules, settings and commands (Ctrl+F or Double Shift)") + } + + private fun ImGuiBuilder.shortcutsPopup() { + popupModal("Keyboard Shortcuts", ImGuiWindowFlags.AlwaysAutoResize) { + text("Lambda Client - Shortcuts & Controls") + separator() + + val shortcuts = listOf( + "Ctrl + F / Double Shift" to "Open Quick Search modal", + "Up / Down Arrow" to "Navigate through search results", + "Enter" to "Toggle module or run selected search result", + "Tab" to "Cycle search filters (All, Modules, Settings, Commands)", + "Escape" to "Close Quick Search / Close GUI", + "Left Click (Module)" to "Toggle module on / off", + "Right Click (Module)" to "Expand / collapse inline settings or open popup", + "Small Arrow (▼ / ▶)" to "Expand or collapse module settings inline", + "Double Click (Header)" to "Collapse or expand category window", + "Drag Title Bar" to "Move category window (snaps to grid & edges)", + "Drag HUD Element" to "Move HUD component (when HUD is unlocked)", + "Right Click (Window)" to "Open window layout menu (Tile, Reset, etc.)" + ) + + shortcuts.forEach { (key, desc) -> + ImGui.textColored(0.9f, 0.75f, 0.2f, 1f, key) + sameLine(220f) + text(desc) + } + + separator() + smallButton("Got it") { + closeCurrentPopup() + } + } + } } diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index f4e708bd8..efd91f654 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.config.Config import com.lambda.config.Tab import com.lambda.config.categories.GuiCategory -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.settings.complex.KeybindSetting.Companion.onPressUnsafe import com.lambda.core.Loadable import com.lambda.event.events.GuiEvent @@ -46,11 +46,13 @@ import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiCond import com.lambda.imgui.flag.ImGuiHoveredFlags import com.lambda.imgui.flag.ImGuiWindowFlags +import com.lambda.imgui.flag.ImGuiKey +import com.lambda.imgui.type.ImString import com.lambda.module.ModuleRegistry +import com.lambda.module.ModuleTag +import com.lambda.module.ModuleTag.Companion.shownTags import com.lambda.module.modules.client.Client import com.lambda.module.modules.combat.autodisconnect.AutoDisconnectScreen -import com.lambda.module.tag.ModuleTag -import com.lambda.module.tag.ModuleTag.Companion.shownTags import com.lambda.sound.LambdaSound import com.lambda.sound.SoundHandler.play import com.lambda.util.Describable @@ -66,9 +68,11 @@ import net.minecraft.client.gui.screen.ingame.CommandBlockScreen import net.minecraft.client.gui.screen.ingame.SignEditScreen import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen import net.minecraft.client.util.Icons +import net.minecraft.client.util.InputUtil +import org.lwjgl.glfw.GLFW import java.awt.Color import kotlin.math.abs - +import kotlin.math.max @Suppress("unused") object ClickGuiLayout : Loadable, Config( "GUI", @@ -135,7 +139,7 @@ object ClickGuiLayout : Loadable, Config( } const val BASE_SCALE = 100 - const val BASE_SCALE_MULTI = 1.8 + const val BASE_SCALE_MULTI = 1.45 fun deviceScaleMultiplier() = try { val monitorWidth = mc.window.monitor!!.currentVideoMode!!.width.toDouble() @@ -144,6 +148,12 @@ object ClickGuiLayout : Loadable, Config( 1.0 } + val currentScale: Float + get() { + val userPercent = scaleSetting / 100.0 + val dpi = deviceScaleMultiplier() + return (BASE_SCALE_MULTI * dpi * userPercent).toFloat() + } // General @Tab(GENERAL_TAB) internal val scaleSetting by setting("Scale", BASE_SCALE, 50..300, 1, unit = "%") @Tab(GENERAL_TAB) val alpha by setting("Alpha", 1.0f, 0.0f..1.0f, 0.01f) @@ -157,12 +167,31 @@ object ClickGuiLayout : Loadable, Config( mc.window.setIcon(mc.defaultResourcePack, icon) } } - @JvmStatic - @Tab(GENERAL_TAB) val setLambdaWindowTitle by setting("Set Lambda Window Title", true).onValueChange { _, _ -> mc.updateWindowTitle() } + @Tab(GENERAL_TAB) @JvmStatic val setLambdaWindowTitle by setting("Set Lambda Window Title", true).onValueChange { _, _ -> mc.updateWindowTitle() } @Tab(GENERAL_TAB) val lambdaTitleAppendixName by setting("Append Username", true) { setLambdaWindowTitle }.onValueChange { _, _ -> mc.updateWindowTitle() } @Tab(GENERAL_TAB) val backgroundBlur by setting("Background Blur", true) @Tab(GENERAL_TAB) val backgroundDarkening by setting("Background Darkening", true) + enum class SettingsMode(override val displayName: String) : NamedEnum { + INLINE("Inline"), + POPUP("Popup"), + BOTH("Both") + } + @Tab(GENERAL_TAB) var settingsMode by setting("Settings Mode", SettingsMode.INLINE, "How module settings open (Inline in category window, Popup menu, or Both)") + @Tab(GENERAL_TAB) var showKeybindBadges by setting("Show Keybind Badges", true, "Show assigned keybind pill on module entries") + @Tab(GENERAL_TAB) var highlightEnabledModules by setting("Highlight Enabled Modules", true, "Show visual accent bar and highlight on active modules") + @Tab(GENERAL_TAB) var allowWindowCollapse by setting("Allow Window Collapse", true, "Allow category windows to collapse by clicking arrow or double-clicking title bar") + @Tab(GENERAL_TAB) var categoryWindowSearch by setting("Category Window Search", false, "Show a search filter at the top of category windows") + @Tab(GENERAL_TAB) var showCategoryCounts by setting("Show Category Counts", true, "Display enabled/total module count in category window headers") + @Tab(GENERAL_TAB) var categoryWindowWidth by setting("Category Width", 115, 75..220, 5, "Default width for category windows in pixels", unit = "px") + .onValueChange { _, newWidth -> + val scale = currentScale + val w = newWidth.toFloat() * scale + shownTags.forEach { pendingSizes[it.name] = w to 0f } + } + @Tab(GENERAL_TAB) var maxCategoryHeight by setting("Max Category Height", 0.72f, 0.30f..0.95f, 0.02f, "Maximum height of category windows relative to screen height", unit = "%") + @Tab(GENERAL_TAB) val tileWindowsAction by setting("Tile Windows", { tileWindows() }, "Auto-arrange all category windows into neat columns that fit the screen") + @Tab(GENERAL_TAB) val resetLayoutAction by setting("Reset Windows", { resetWindowPositions() }, "Reset all category windows to default positions and sizes") // Snapping @Tab(SNAPPING_TAB) val snapEnabled by setting("Enable Snapping", true, "Master toggle for GUI/HUD snapping") @Tab(SNAPPING_TAB) val gridSize by setting("Grid Size", 25f, 2f..128f, 1f, "Grid step in pixels") { snapEnabled } @@ -178,14 +207,14 @@ object ClickGuiLayout : Loadable, Config( // Sizing @Tab(SIZING_TAB) val windowPaddingX by setting("Window Padding X", 8.0f, 0.0f..20.0f, 0.1f) @Tab(SIZING_TAB) val windowPaddingY by setting("Window Padding Y", 8.0f, 0.0f..20.0f, 0.1f) - @Tab(SIZING_TAB) val windowMinSizeX by setting("Window Min Size X", 32.0f, 0.0f..100.0f, 1.0f) + @Tab(SIZING_TAB) val windowMinSizeX by setting("Window Min Size X", 90.0f, 40.0f..250.0f, 1.0f) @Tab(SIZING_TAB) val windowMinSizeY by setting("Window Min Size Y", 32.0f, 0.0f..100.0f, 1.0f) @Tab(SIZING_TAB) val windowTitleAlignX by setting("Window Title Align X", 0.0f, 0.0f..1.0f, 0.01f) @Tab(SIZING_TAB) val windowTitleAlignY by setting("Window Title Align Y", 0.5f, 0.0f..1.0f, 0.01f) - @Tab(SIZING_TAB) val framePaddingX by setting("Frame Padding X", 4.0f, 0.0f..20.0f, 0.1f) - @Tab(SIZING_TAB) val framePaddingY by setting("Frame Padding Y", 3.0f, 0.0f..20.0f, 0.1f) + @Tab(SIZING_TAB) val framePaddingX by setting("Frame Padding X", 5.0f, 0.0f..20.0f, 0.1f) + @Tab(SIZING_TAB) val framePaddingY by setting("Frame Padding Y", 3.5f, 0.0f..20.0f, 0.1f) @Tab(SIZING_TAB) val itemSpacingX by setting("Item Spacing X", 8.0f, 0.0f..20.0f, 0.1f) - @Tab(SIZING_TAB) val itemSpacingY by setting("Item Spacing Y", 4.0f, 0.0f..20.0f, 0.1f) + @Tab(SIZING_TAB) val itemSpacingY by setting("Item Spacing Y", 3.0f, 0.0f..20.0f, 0.1f) @Tab(SIZING_TAB) val itemInnerSpacingX by setting("Item Inner Spacing X", 4.0f, 0.0f..20.0f, 0.1f) @Tab(SIZING_TAB) val itemInnerSpacingY by setting("Item Inner Spacing Y", 4.0f, 0.0f..20.0f, 0.1f) @Tab(SIZING_TAB) val indentSpacing by setting("Indent Spacing", 21.0f, 0.0f..50.0f, 0.1f) @@ -211,65 +240,367 @@ object ClickGuiLayout : Loadable, Config( @Tab(FONT_TAB) val fontScale by setting("Font Scale", 1.0, 0.5..2.0, 0.1) // Colors - @Tab(COLORS_TAB) val primaryColor by setting("Primary Color", Color(130, 200, 255)) - @Tab(COLORS_TAB) val secondaryColor by setting("Secondary Color", Color(225, 130, 225)) - - @Tab(COLORS_TAB) val text by setting("Text", Color(255, 255, 255, 255)) - @Tab(COLORS_TAB) val textDisabled by setting("Text Disabled", Color(128, 128, 128, 255)) - @Tab(COLORS_TAB) val windowBg by setting("Window Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val childBg by setting("Child Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val popupBg by setting("Popup Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val border by setting("Border", Color(130, 12, 60, 240)) - @Tab(COLORS_TAB) val borderShadow by setting("Border Shadow", Color(51, 0, 21, 240)) - @Tab(COLORS_TAB) val frameBg by setting("Frame Background", Color(171, 32, 93, 102)) - @Tab(COLORS_TAB) val frameBgHovered by setting("Frame Background Hovered", Color(214, 45, 119, 102)) - @Tab(COLORS_TAB) val frameBgActive by setting("Frame Background Active", Color(255, 50, 140, 102)) - @Tab(COLORS_TAB) val titleBg by setting("Title Background", Color(125, 0, 50, 240)) - @Tab(COLORS_TAB) val titleBgActive by setting("Title Background Active", Color(162, 0, 68, 240)) - @Tab(COLORS_TAB) val titleBgCollapsed by setting("Title Background Collapsed", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val menuBarBg by setting("MenuBar Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val scrollbarBg by setting("Scrollbar Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val scrollbarGrab by setting("Scrollbar Grab", Color(159, 30, 83, 240)) - @Tab(COLORS_TAB) val scrollbarGrabHovered by setting("Scrollbar Grab Hovered", Color(198, 40, 105, 240)) - @Tab(COLORS_TAB) val scrollbarGrabActive by setting("Scrollbar Grab Active", Color(235, 49, 126, 240)) - @Tab(COLORS_TAB) val checkMark by setting("Check Mark", Color(255, 64, 148, 220)) - @Tab(COLORS_TAB) val sliderGrab by setting("Slider Grab", Color(207, 46, 117, 200)) - @Tab(COLORS_TAB) val sliderGrabActive by setting("Slider Grab Active", Color(241, 67, 143, 200)) - @Tab(COLORS_TAB) val button by setting("Button", Color(171, 32, 93, 102)) - @Tab(COLORS_TAB) val buttonHovered by setting("Button Hovered", Color(214, 45, 119, 102)) - @Tab(COLORS_TAB) val buttonActive by setting("Button Active", Color(255, 50, 140, 102)) - @Tab(COLORS_TAB) val header by setting("Header", Color(192, 30, 94, 115)) - @Tab(COLORS_TAB) val headerHovered by setting("Header Hovered", Color(255, 59, 136, 115)) - @Tab(COLORS_TAB) val headerActive by setting("Header Active", Color(202, 36, 101, 115)) - @Tab(COLORS_TAB) val separator by setting("Separator", Color(107, 0, 47, 128)) - @Tab(COLORS_TAB) val separatorHovered by setting("Separator Hovered", Color(146, 0, 64, 128)) - @Tab(COLORS_TAB) val separatorActive by setting("Separator Active", Color(186, 0, 82, 128)) - @Tab(COLORS_TAB) val resizeGrip by setting("Resize Grip", Color(214, 45, 119, 102)) - @Tab(COLORS_TAB) val resizeGripHovered by setting("Resize Grip Hovered", Color(214, 45, 119, 102)) - @Tab(COLORS_TAB) val resizeGripActive by setting("Resize Grip Active", Color(214, 45, 119, 102)) - @Tab(COLORS_TAB) val tab by setting("Tab", Color(121, 21, 65, 140)) - @Tab(COLORS_TAB) val tabHovered by setting("Tab Hovered", Color(169, 34, 94, 140)) - @Tab(COLORS_TAB) val tabActive by setting("Tab Active", Color(209, 34, 112, 140)) - @Tab(COLORS_TAB) val tabUnfocused by setting("Tab Unfocused", Color(121, 21, 65, 120)) - @Tab(COLORS_TAB) val tabUnfocusedActive by setting("Tab Unfocused Active", Color(196, 36, 107, 120)) - @Tab(COLORS_TAB) val dockingPreview by setting("Docking Preview", Color(208, 47, 117, 102)) - @Tab(COLORS_TAB) val dockingEmptyBg by setting("Docking Empty Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val plotLines by setting("Plot Lines", Color(178, 36, 95, 240)) - @Tab(COLORS_TAB) val plotLinesHovered by setting("Plot Lines Hovered", Color(209, 40, 110, 240)) - @Tab(COLORS_TAB) val plotHistogram by setting("Plot Histogram", Color(192, 32, 91, 255)) - @Tab(COLORS_TAB) val plotHistogramHovered by setting("Plot Histogram Hovered", Color(226, 38, 108, 255)) - @Tab(COLORS_TAB) val tableHeaderBg by setting("Table Header Background", Color(75, 0, 31, 240)) - @Tab(COLORS_TAB) val tableBorderStrong by setting("Table Border Strong", Color(88, 0, 36, 240)) - @Tab(COLORS_TAB) val tableBorderLight by setting("Table Border Light", Color(67, 0, 28, 240)) - @Tab(COLORS_TAB) val tableRowBg by setting("Table Row Background", Color(35, 0, 14, 240)) - @Tab(COLORS_TAB) val tableRowBgAlt by setting("Table Row Background Alt", Color(242, 140, 182, 240)) - @Tab(COLORS_TAB) val textSelectedBg by setting("Text Selected Background", Color(218, 54, 121, 240)) - @Tab(COLORS_TAB) val dragDropTarget by setting("Drag Drop Target", Color(218, 54, 121, 240)) - @Tab(COLORS_TAB) val navHighlight by setting("Nav Highlight", Color(218, 54, 121, 240)) - @Tab(COLORS_TAB) val navWindowingHighlight by setting("Nav Windowing Highlight", Color(242, 140, 182, 240)) - @Tab(COLORS_TAB) val navWindowingDimBg by setting("Nav Windowing Dim Background", Color(242, 140, 182, 240)) - @Tab(COLORS_TAB) val modalWindowDimBg by setting("Modal Window Dim Background", Color(35, 0, 14, 90)) + enum class ThemePreset(override val displayName: String) : NamedEnum { + CUSTOM("Custom"), + LAMBDA_VIOLET("Lambda Violet"), + MIDNIGHT_BLUE("Midnight Blue"), + NORDIC_FROST("Nordic Frost"), + CYBERPUNK_NEON("Cyberpunk Neon"), + DRACULA("Dracula"), + EMERALD_OBSIDIAN("Emerald Obsidian"), + MONOKAI_GOLD("Monokai Gold") + } + + @Tab(COLORS_TAB) val themePreset by setting("Theme Preset", ThemePreset.LAMBDA_VIOLET, "Predefined theme palettes for the ClickGUI") + .onValueChange { _, preset -> applyTheme(preset) } + + @Tab(COLORS_TAB) var primaryColor by setting("Primary Color", Color(130, 200, 255)) + @Tab(COLORS_TAB) var secondaryColor by setting("Secondary Color", Color(225, 130, 225)) + + @Tab(COLORS_TAB) var text by setting("Text", Color(255, 255, 255, 255)) + @Tab(COLORS_TAB) var textDisabled by setting("Text Disabled", Color(128, 128, 128, 255)) + @Tab(COLORS_TAB) var windowBg by setting("Window Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var childBg by setting("Child Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var popupBg by setting("Popup Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var border by setting("Border", Color(130, 12, 60, 240)) + @Tab(COLORS_TAB) var borderShadow by setting("Border Shadow", Color(51, 0, 21, 240)) + @Tab(COLORS_TAB) var frameBg by setting("Frame Background", Color(171, 32, 93, 102)) + @Tab(COLORS_TAB) var frameBgHovered by setting("Frame Background Hovered", Color(214, 45, 119, 102)) + @Tab(COLORS_TAB) var frameBgActive by setting("Frame Background Active", Color(255, 50, 140, 102)) + @Tab(COLORS_TAB) var titleBg by setting("Title Background", Color(125, 0, 50, 240)) + @Tab(COLORS_TAB) var titleBgActive by setting("Title Background Active", Color(162, 0, 68, 240)) + @Tab(COLORS_TAB) var titleBgCollapsed by setting("Title Background Collapsed", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var menuBarBg by setting("MenuBar Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var scrollbarBg by setting("Scrollbar Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var scrollbarGrab by setting("Scrollbar Grab", Color(159, 30, 83, 240)) + @Tab(COLORS_TAB) var scrollbarGrabHovered by setting("Scrollbar Grab Hovered", Color(198, 40, 105, 240)) + @Tab(COLORS_TAB) var scrollbarGrabActive by setting("Scrollbar Grab Active", Color(235, 49, 126, 240)) + @Tab(COLORS_TAB) var checkMark by setting("Check Mark", Color(255, 64, 148, 220)) + @Tab(COLORS_TAB) var sliderGrab by setting("Slider Grab", Color(207, 46, 117, 200)) + @Tab(COLORS_TAB) var sliderGrabActive by setting("Slider Grab Active", Color(241, 67, 143, 200)) + @Tab(COLORS_TAB) var button by setting("Button", Color(171, 32, 93, 102)) + @Tab(COLORS_TAB) var buttonHovered by setting("Button Hovered", Color(214, 45, 119, 102)) + @Tab(COLORS_TAB) var buttonActive by setting("Button Active", Color(255, 50, 140, 102)) + @Tab(COLORS_TAB) var header by setting("Header", Color(192, 30, 94, 115)) + @Tab(COLORS_TAB) var headerHovered by setting("Header Hovered", Color(255, 59, 136, 115)) + @Tab(COLORS_TAB) var headerActive by setting("Header Active", Color(202, 36, 101, 115)) + @Tab(COLORS_TAB) var separator by setting("Separator", Color(107, 0, 47, 128)) + @Tab(COLORS_TAB) var separatorHovered by setting("Separator Hovered", Color(146, 0, 64, 128)) + @Tab(COLORS_TAB) var separatorActive by setting("Separator Active", Color(186, 0, 82, 128)) + @Tab(COLORS_TAB) var resizeGrip by setting("Resize Grip", Color(214, 45, 119, 102)) + @Tab(COLORS_TAB) var resizeGripHovered by setting("Resize Grip Hovered", Color(214, 45, 119, 102)) + @Tab(COLORS_TAB) var resizeGripActive by setting("Resize Grip Active", Color(214, 45, 119, 102)) + @Tab(COLORS_TAB) var tab by setting("Tab", Color(121, 21, 65, 140)) + @Tab(COLORS_TAB) var tabHovered by setting("Tab Hovered", Color(169, 34, 94, 140)) + @Tab(COLORS_TAB) var tabActive by setting("Tab Active", Color(209, 34, 112, 140)) + @Tab(COLORS_TAB) var tabUnfocused by setting("Tab Unfocused", Color(121, 21, 65, 120)) + @Tab(COLORS_TAB) var tabUnfocusedActive by setting("Tab Unfocused Active", Color(196, 36, 107, 120)) + @Tab(COLORS_TAB) var dockingPreview by setting("Docking Preview", Color(208, 47, 117, 102)) + @Tab(COLORS_TAB) var dockingEmptyBg by setting("Docking Empty Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var plotLines by setting("Plot Lines", Color(178, 36, 95, 240)) + @Tab(COLORS_TAB) var plotLinesHovered by setting("Plot Lines Hovered", Color(209, 40, 110, 240)) + @Tab(COLORS_TAB) var plotHistogram by setting("Plot Histogram", Color(192, 32, 91, 255)) + @Tab(COLORS_TAB) var plotHistogramHovered by setting("Plot Histogram Hovered", Color(226, 38, 108, 255)) + @Tab(COLORS_TAB) var tableHeaderBg by setting("Table Header Background", Color(75, 0, 31, 240)) + @Tab(COLORS_TAB) var tableBorderStrong by setting("Table Border Strong", Color(88, 0, 36, 240)) + @Tab(COLORS_TAB) var tableBorderLight by setting("Table Border Light", Color(67, 0, 28, 240)) + @Tab(COLORS_TAB) var tableRowBg by setting("Table Row Background", Color(35, 0, 14, 240)) + @Tab(COLORS_TAB) var tableRowBgAlt by setting("Table Row Background Alt", Color(242, 140, 182, 240)) + @Tab(COLORS_TAB) var textSelectedBg by setting("Text Selected Background", Color(218, 54, 121, 240)) + @Tab(COLORS_TAB) var dragDropTarget by setting("Drag Drop Target", Color(218, 54, 121, 240)) + @Tab(COLORS_TAB) var navHighlight by setting("Nav Highlight", Color(218, 54, 121, 240)) + @Tab(COLORS_TAB) var navWindowingHighlight by setting("Nav Windowing Highlight", Color(242, 140, 182, 240)) + @Tab(COLORS_TAB) var navWindowingDimBg by setting("Nav Windowing Dim Background", Color(242, 140, 182, 240)) + @Tab(COLORS_TAB) var modalWindowDimBg by setting("Modal Window Dim Background", Color(35, 0, 14, 90)) + + val expandedModules = mutableSetOf() + fun isModuleExpanded(name: String) = expandedModules.contains(name) + fun toggleModuleExpanded(name: String) { + if (expandedModules.contains(name)) expandedModules.remove(name) + else expandedModules.add(name) + } + fun collapseAllModules() = expandedModules.clear() + fun expandAllModules(tag: ModuleTag) = ModuleRegistry.modules.filter { it.tag == tag }.forEach { expandedModules.add(it.name) } + + private val categoryFilters = mutableMapOf() + fun getCategoryFilter(name: String) = categoryFilters.getOrPut(name) { ImString(64) } + + fun tileWindows() { + val startX = 20f + val startY = MenuBar.height + 12f + val scale = currentScale + val windowWidth = categoryWindowWidth.toFloat() * scale + val padding = 10f * scale + val displayWidth = DearImGui.io.displaySize.x + val cols = max(1, ((displayWidth - startX * 2 + padding) / (windowWidth + padding)).toInt()) + + var col = 0 + var row = 0 + val tags = if (developerMode) shownTags + ModuleTag.DEBUG else shownTags + tags.forEach { tag -> + val x = startX + col * (windowWidth + padding) + val y = startY + row * (260f * scale) + pendingPositions[tag.name] = x to y + pendingSizes[tag.name] = windowWidth to 0f + col++ + if (col >= cols) { + col = 0 + row++ + } + } + } + fun resetWindowPositions() { + pendingPositions.clear() + pendingSizes.clear() + tileWindows() + } + + fun applyTheme(preset: ThemePreset) { + when (preset) { + ThemePreset.CUSTOM -> {} + ThemePreset.LAMBDA_VIOLET -> { + primaryColor = Color(130, 200, 255) + secondaryColor = Color(225, 130, 225) + windowBg = Color(35, 0, 14, 240) + childBg = Color(35, 0, 14, 240) + popupBg = Color(35, 0, 14, 240) + border = Color(130, 12, 60, 240) + borderShadow = Color(51, 0, 21, 240) + frameBg = Color(171, 32, 93, 102) + frameBgHovered = Color(214, 45, 119, 102) + frameBgActive = Color(255, 50, 140, 102) + titleBg = Color(125, 0, 50, 240) + titleBgActive = Color(162, 0, 68, 240) + titleBgCollapsed = Color(35, 0, 14, 240) + menuBarBg = Color(35, 0, 14, 240) + button = Color(171, 32, 93, 102) + buttonHovered = Color(214, 45, 119, 102) + buttonActive = Color(255, 50, 140, 102) + header = Color(192, 30, 94, 115) + headerHovered = Color(255, 59, 136, 115) + headerActive = Color(202, 36, 101, 115) + checkMark = Color(255, 64, 148, 220) + sliderGrab = Color(207, 46, 117, 200) + sliderGrabActive = Color(241, 67, 143, 200) + scrollbarBg = Color(35, 0, 14, 240) + scrollbarGrab = Color(159, 30, 83, 240) + scrollbarGrabHovered = Color(198, 40, 105, 240) + scrollbarGrabActive = Color(235, 49, 126, 240) + separator = Color(107, 0, 47, 128) + separatorHovered = Color(146, 0, 64, 128) + separatorActive = Color(186, 0, 82, 128) + } + ThemePreset.MIDNIGHT_BLUE -> { + primaryColor = Color(56, 189, 248) + secondaryColor = Color(129, 140, 248) + windowBg = Color(15, 20, 28, 245) + childBg = Color(15, 20, 28, 245) + popupBg = Color(18, 24, 36, 245) + border = Color(40, 60, 90, 240) + borderShadow = Color(10, 14, 20, 240) + frameBg = Color(30, 45, 70, 140) + frameBgHovered = Color(40, 65, 105, 160) + frameBgActive = Color(56, 189, 248, 140) + titleBg = Color(20, 30, 48, 245) + titleBgActive = Color(30, 50, 85, 245) + titleBgCollapsed = Color(15, 20, 28, 245) + menuBarBg = Color(12, 16, 24, 245) + button = Color(35, 55, 88, 140) + buttonHovered = Color(48, 75, 120, 160) + buttonActive = Color(56, 189, 248, 160) + header = Color(32, 50, 80, 160) + headerHovered = Color(45, 70, 115, 180) + headerActive = Color(56, 189, 248, 180) + checkMark = Color(56, 189, 248, 240) + sliderGrab = Color(56, 189, 248, 200) + sliderGrabActive = Color(125, 211, 252, 240) + scrollbarBg = Color(15, 20, 28, 245) + scrollbarGrab = Color(35, 55, 88, 240) + scrollbarGrabHovered = Color(50, 75, 120, 240) + scrollbarGrabActive = Color(56, 189, 248, 240) + separator = Color(30, 45, 70, 180) + separatorHovered = Color(45, 70, 110, 180) + separatorActive = Color(56, 189, 248, 180) + } + ThemePreset.NORDIC_FROST -> { + primaryColor = Color(0, 210, 255) + secondaryColor = Color(136, 192, 208) + windowBg = Color(24, 28, 36, 245) + childBg = Color(24, 28, 36, 245) + popupBg = Color(30, 35, 46, 245) + border = Color(55, 75, 95, 240) + borderShadow = Color(12, 16, 20, 240) + frameBg = Color(40, 52, 68, 140) + frameBgHovered = Color(55, 72, 95, 160) + frameBgActive = Color(0, 210, 255, 140) + titleBg = Color(30, 40, 52, 245) + titleBgActive = Color(42, 60, 80, 245) + titleBgCollapsed = Color(24, 28, 36, 245) + menuBarBg = Color(18, 22, 28, 245) + button = Color(44, 58, 76, 140) + buttonHovered = Color(60, 80, 105, 160) + buttonActive = Color(0, 210, 255, 160) + header = Color(40, 56, 75, 160) + headerHovered = Color(55, 78, 105, 180) + headerActive = Color(0, 210, 255, 180) + checkMark = Color(0, 210, 255, 240) + sliderGrab = Color(0, 210, 255, 200) + sliderGrabActive = Color(136, 235, 255, 240) + scrollbarBg = Color(24, 28, 36, 245) + scrollbarGrab = Color(44, 58, 76, 240) + scrollbarGrabHovered = Color(60, 80, 105, 240) + scrollbarGrabActive = Color(0, 210, 255, 240) + separator = Color(40, 55, 72, 180) + separatorHovered = Color(55, 75, 100, 180) + separatorActive = Color(0, 210, 255, 180) + } + ThemePreset.CYBERPUNK_NEON -> { + primaryColor = Color(255, 42, 133) + secondaryColor = Color(0, 240, 255) + windowBg = Color(12, 12, 16, 245) + childBg = Color(12, 12, 16, 245) + popupBg = Color(18, 18, 24, 245) + border = Color(255, 42, 133, 180) + borderShadow = Color(0, 0, 0, 240) + frameBg = Color(35, 20, 30, 150) + frameBgHovered = Color(60, 25, 45, 180) + frameBgActive = Color(255, 42, 133, 160) + titleBg = Color(25, 14, 22, 245) + titleBgActive = Color(180, 20, 90, 245) + titleBgCollapsed = Color(12, 12, 16, 245) + menuBarBg = Color(8, 8, 12, 245) + button = Color(45, 20, 35, 150) + buttonHovered = Color(80, 25, 55, 180) + buttonActive = Color(255, 42, 133, 180) + header = Color(60, 20, 42, 160) + headerHovered = Color(110, 25, 70, 180) + headerActive = Color(255, 42, 133, 180) + checkMark = Color(0, 240, 255, 255) + sliderGrab = Color(0, 240, 255, 220) + sliderGrabActive = Color(255, 42, 133, 240) + scrollbarBg = Color(12, 12, 16, 245) + scrollbarGrab = Color(60, 20, 42, 240) + scrollbarGrabHovered = Color(100, 25, 65, 240) + scrollbarGrabActive = Color(255, 42, 133, 240) + separator = Color(60, 20, 40, 180) + separatorHovered = Color(100, 25, 65, 180) + separatorActive = Color(0, 240, 255, 180) + } + ThemePreset.DRACULA -> { + primaryColor = Color(189, 147, 249) + secondaryColor = Color(255, 121, 198) + windowBg = Color(40, 42, 54, 245) + childBg = Color(40, 42, 54, 245) + popupBg = Color(48, 50, 64, 245) + border = Color(98, 114, 164, 200) + borderShadow = Color(20, 20, 28, 240) + frameBg = Color(68, 71, 90, 150) + frameBgHovered = Color(98, 114, 164, 180) + frameBgActive = Color(189, 147, 249, 150) + titleBg = Color(33, 34, 44, 245) + titleBgActive = Color(68, 71, 90, 245) + titleBgCollapsed = Color(33, 34, 44, 245) + menuBarBg = Color(28, 29, 38, 245) + button = Color(68, 71, 90, 150) + buttonHovered = Color(98, 114, 164, 180) + buttonActive = Color(189, 147, 249, 180) + header = Color(68, 71, 90, 160) + headerHovered = Color(98, 114, 164, 180) + headerActive = Color(189, 147, 249, 180) + checkMark = Color(80, 250, 123, 240) + sliderGrab = Color(189, 147, 249, 220) + sliderGrabActive = Color(255, 121, 198, 240) + scrollbarBg = Color(40, 42, 54, 245) + scrollbarGrab = Color(68, 71, 90, 240) + scrollbarGrabHovered = Color(98, 114, 164, 240) + scrollbarGrabActive = Color(189, 147, 249, 240) + separator = Color(68, 71, 90, 180) + separatorHovered = Color(98, 114, 164, 180) + separatorActive = Color(189, 147, 249, 180) + } + ThemePreset.EMERALD_OBSIDIAN -> { + primaryColor = Color(16, 185, 129) + secondaryColor = Color(52, 211, 153) + windowBg = Color(15, 23, 19, 245) + childBg = Color(15, 23, 19, 245) + popupBg = Color(20, 30, 25, 245) + border = Color(30, 70, 50, 220) + borderShadow = Color(8, 14, 10, 240) + frameBg = Color(25, 50, 38, 140) + frameBgHovered = Color(35, 75, 55, 160) + frameBgActive = Color(16, 185, 129, 150) + titleBg = Color(18, 32, 25, 245) + titleBgActive = Color(25, 60, 42, 245) + titleBgCollapsed = Color(15, 23, 19, 245) + menuBarBg = Color(10, 16, 12, 245) + button = Color(28, 58, 42, 140) + buttonHovered = Color(38, 80, 58, 160) + buttonActive = Color(16, 185, 129, 170) + header = Color(25, 55, 40, 160) + headerHovered = Color(35, 80, 56, 180) + headerActive = Color(16, 185, 129, 180) + checkMark = Color(16, 185, 129, 240) + sliderGrab = Color(16, 185, 129, 220) + sliderGrabActive = Color(52, 211, 153, 240) + scrollbarBg = Color(15, 23, 19, 245) + scrollbarGrab = Color(28, 58, 42, 240) + scrollbarGrabHovered = Color(38, 80, 58, 240) + scrollbarGrabActive = Color(16, 185, 129, 240) + separator = Color(28, 55, 40, 180) + separatorHovered = Color(38, 75, 55, 180) + separatorActive = Color(16, 185, 129, 180) + } + ThemePreset.MONOKAI_GOLD -> { + primaryColor = Color(230, 219, 116) + secondaryColor = Color(253, 151, 31) + windowBg = Color(39, 40, 34, 245) + childBg = Color(39, 40, 34, 245) + popupBg = Color(48, 49, 42, 245) + border = Color(90, 85, 65, 220) + borderShadow = Color(20, 20, 16, 240) + frameBg = Color(60, 58, 46, 140) + frameBgHovered = Color(85, 80, 60, 160) + frameBgActive = Color(230, 219, 116, 140) + titleBg = Color(30, 31, 26, 245) + titleBgActive = Color(65, 62, 48, 245) + titleBgCollapsed = Color(30, 31, 26, 245) + menuBarBg = Color(24, 25, 20, 245) + button = Color(60, 58, 46, 140) + buttonHovered = Color(85, 80, 60, 160) + buttonActive = Color(230, 219, 116, 160) + header = Color(60, 58, 46, 160) + headerHovered = Color(85, 80, 60, 180) + headerActive = Color(230, 219, 116, 180) + checkMark = Color(166, 226, 46, 240) + sliderGrab = Color(230, 219, 116, 220) + sliderGrabActive = Color(253, 151, 31, 240) + scrollbarBg = Color(39, 40, 34, 245) + scrollbarGrab = Color(60, 58, 46, 240) + scrollbarGrabHovered = Color(85, 80, 60, 240) + scrollbarGrabActive = Color(230, 219, 116, 240) + separator = Color(60, 58, 46, 180) + separatorHovered = Color(85, 80, 60, 180) + separatorActive = Color(230, 219, 116, 180) + } + } + } + + fun syncThemeToAccent(accent: Color) { + primaryColor = accent + val r = accent.red + val g = accent.green + val b = accent.blue + header = Color(r, g, b, 120) + headerHovered = Color((r * 1.15f).coerceAtMost(255f).toInt(), (g * 1.15f).coerceAtMost(255f).toInt(), (b * 1.15f).coerceAtMost(255f).toInt(), 160) + headerActive = Color((r * 0.9f).toInt(), (g * 0.9f).toInt(), (b * 0.9f).toInt(), 180) + buttonActive = Color(r, g, b, 150) + checkMark = Color(r, g, b, 230) + sliderGrab = Color(r, g, b, 200) + sliderGrabActive = Color((r * 1.2f).coerceAtMost(255f).toInt(), (g * 1.2f).coerceAtMost(255f).toInt(), (b * 1.2f).coerceAtMost(255f).toInt(), 230) + scrollbarGrabActive = Color(r, g, b, 230) + border = Color((r * 0.6f).toInt(), (g * 0.6f).toInt(), (b * 0.6f).toInt(), 200) + } init { listenUnsafe { if (!open) return@listenUnsafe @@ -312,26 +643,71 @@ object ClickGuiLayout : Loadable, Config( val tags = if (developerMode) shownTags + ModuleTag.DEBUG else shownTags if (tags.isEmpty()) return@buildLayout + val displayWidth = DearImGui.io.displaySize.x + val scale = currentScale + val defaultWinWidth = categoryWindowWidth.toFloat() * scale + val paddingX = 10f * scale var nextX = 20f - val baseY = MenuBar.height + 10f + var nextY = MenuBar.height + 10f tags.forEach { tag -> val override = pendingPositions[tag.name] if (override != null) { ImGui.setNextWindowPos(override.first, override.second) + if (activeDragWindowName != tag.name) { + pendingPositions.remove(tag.name) + } } else if (frameCount >= 1) { - ImGui.setNextWindowPos(nextX, baseY, ImGuiCond.FirstUseEver) + if (nextX + defaultWinWidth > displayWidth - 10f && nextX > 20f) { + nextX = 20f + nextY += 260f * scale + } + ImGui.setNextWindowPos(nextX, nextY, ImGuiCond.FirstUseEver) + } + + val pendingSize = pendingSizes[tag.name] + if (pendingSize != null) { + val (w, h) = pendingSize + if (h <= 0f) { + ImGui.setNextWindowSize(w, 0f) + pendingSizes.remove(tag.name) + } else { + ImGui.setNextWindowSizeConstraints(w, h, w, h) + } + } else { + val minW = windowMinSizeX.coerceAtMost(categoryWindowWidth.toFloat()) * scale + val maxH = io.displaySize.y * maxCategoryHeight + ImGui.setNextWindowSizeConstraints(minW, 0f, 320f * scale, maxH) + ImGui.setNextWindowSize(defaultWinWidth, 0f, ImGuiCond.FirstUseEver) } + val windowFlags = if (allowWindowCollapse) ImGuiWindowFlags.None else ImGuiWindowFlags.NoCollapse - pendingSizes[tag.name]?.let { (w, h) -> - ImGui.setNextWindowSizeConstraints(w, h, w, h) + val categoryModules = ModuleRegistry.modules.filter { it.tag == tag && it.showInClickGui.value } + val enabledCount = categoryModules.count { it.isEnabled } + val totalCount = categoryModules.size + + val windowTitle = if (showCategoryCounts) { + "${tag.name} [$enabledCount/$totalCount]###Category-${tag.name}" + } else { + "${tag.name}###Category-${tag.name}" } - window(tag.name, flags = ImGuiWindowFlags.NoCollapse) { - ModuleRegistry.modules - .filter { it.tag == tag && it.showInClickGui.value } + window(windowTitle, flags = windowFlags) { + // Optional category search filter + if (categoryWindowSearch) { + val filter = getCategoryFilter(tag.name) + withItemWidth(ImGui.getContentRegionAvailX()) { + ImGui.inputTextWithHint("##cat-filter-${tag.name}", "Filter ${tag.name}...", filter) + } + separator() + } + + val filterText = if (categoryWindowSearch) getCategoryFilter(tag.name).get().trim() else "" + categoryModules + .filter { filterText.isEmpty() || it.name.contains(filterText, ignoreCase = true) } .forEach { with(ModuleEntry(it)) { buildLayout() } } + snapOverlays[tag.name]?.let { vis -> drawSnapLines(vis.snapX, vis.kindX, vis.snapY, vis.kindY) } @@ -339,19 +715,30 @@ object ClickGuiLayout : Loadable, Config( val rect = RectF(windowPos.x, windowPos.y, windowSize.x, windowSize.y) if (mouseDown) claimInteraction(tag.name, rect) + // Clamp if window was pushed off the right of the screen + if (windowPos.x + 30f > displayWidth) { + ImGui.setWindowPos(max(10f, displayWidth - windowSize.x - 10f), windowPos.y) + } + SnapHandler.registerElement(tag.name, rect) lastBounds[tag.name] = rect - nextX += ImGui.getWindowWidth() + 20f + nextX += (if (override != null) ImGui.getWindowWidth() else defaultWinWidth) + paddingX } } if (frameCount++ == 1) { initialLayoutComplete = true } + // Global shortcut to open QuickSearch + val win = mc.window + val isCtrlDown = DearImGui.io.keyCtrl || (win != null && (InputUtil.isKeyPressed(win, GLFW.GLFW_KEY_LEFT_CONTROL) || InputUtil.isKeyPressed(win, GLFW.GLFW_KEY_RIGHT_CONTROL))) + if (isCtrlDown && ImGui.isKeyPressed(ImGuiKey.F, false)) { + if (!QuickSearch.isOpen) QuickSearch.open() + } - renderQuickSearch() + renderQuickSearch() if (developerMode) { ImGui.showDemoWindow() ImPlot.showDemoWindow() diff --git a/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt index b070261d3..99cb830d9 100644 --- a/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt @@ -134,6 +134,10 @@ object HudGuiLayout : Loadable, Config( huds.forEach { hud -> registerHudElement(hud) } + + if (ClickGuiLayout.open) { + renderHudStatusBar() + } } } } @@ -176,11 +180,18 @@ object HudGuiLayout : Loadable, Config( with(hud) { buildLayout() } if (ClickGuiLayout.open) { + if (!isLocked && ImGui.isWindowHovered()) { + lambdaTooltip("Drag to move ${hud.name} • Right-click for options") + } + popupContextWindow("##ctx-${hud.name}") { menuItem("Remove HUD Element") { hud.disable() SnapHandler.unregisterElement(hud.name) } + menuItem(if (isLocked) "Unlock HUD" else "Lock HUD") { + isLocked = !isLocked + } separator() buildConfigSettingsContext(hud) } @@ -293,4 +304,42 @@ object HudGuiLayout : Loadable, Config( // BL: 0.5pi -> pi strokeArc(blCx, blCy, HALF_PI_F, PI_F) } + + private fun ImGuiBuilder.renderHudStatusBar() { + val statusText = if (isLocked) "HUD: Locked (Click to unlock)" else "HUD: Unlocked (Draggable • Click to lock)" + val textW = ImGui.calcTextSize(statusText).x + val padX = 14f + val padY = 5f + val pillW = textW + padX * 2f + val pillH = ImGui.getTextLineHeight() + padY * 2f + val pillX = (io.displaySize.x - pillW) * 0.5f + val pillY = io.displaySize.y - pillH - 12f + + ImGui.setNextWindowPos(pillX, pillY) + ImGui.setNextWindowSize(pillW, pillH) + withStyleVar(ImGuiStyleVar.WindowRounding, 8f) { + withStyleVar(ImGuiStyleVar.WindowPadding, padX, padY) { + val primary = ClickGuiLayout.primaryColor + val bgAlpha = if (isLocked) 170 else 220 + val pillBg = if (isLocked) Color(24, 24, 24, bgAlpha) else Color((primary.red * 0.2f).toInt(), (primary.green * 0.2f).toInt(), (primary.blue * 0.2f).toInt(), bgAlpha) + val borderR = if (isLocked) 0.35f else primary.red / 255f + val borderG = if (isLocked) 0.35f else primary.green / 255f + val borderB = if (isLocked) 0.35f else primary.blue / 255f + withStyleColor(ImGuiCol.WindowBg, pillBg.red / 255f, pillBg.green / 255f, pillBg.blue / 255f, pillBg.alpha / 255f) { + withStyleColor(ImGuiCol.Border, borderR, borderG, borderB, 0.7f) { + window("##hud-status-bar", flags = ImGuiWindowFlags.NoDecoration or ImGuiWindowFlags.NoMove or ImGuiWindowFlags.NoDocking) { + if (isLocked) { + textDisabled(statusText) + } else { + ImGui.textColored(primary.red / 255f, primary.green / 255f, primary.blue / 255f, 1f, statusText) + } + if (ImGui.isWindowHovered() && isMouseClicked()) { + isLocked = !isLocked + } + } + } + } + } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/gui/components/ModuleEntry.kt b/src/main/kotlin/com/lambda/gui/components/ModuleEntry.kt index 7c73ae9ab..a9adf6ace 100644 --- a/src/main/kotlin/com/lambda/gui/components/ModuleEntry.kt +++ b/src/main/kotlin/com/lambda/gui/components/ModuleEntry.kt @@ -20,36 +20,129 @@ package com.lambda.gui.components import com.lambda.gui.Layout import com.lambda.gui.components.SettingsWidget.buildConfigSettingsContext import com.lambda.gui.dsl.ImGuiBuilder +import com.lambda.imgui.ImColor import com.lambda.imgui.ImGui +import com.lambda.imgui.ImVec2 +import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiHoveredFlags +import com.lambda.imgui.flag.ImGuiMouseButton import com.lambda.imgui.flag.ImGuiPopupFlags +import com.lambda.imgui.flag.ImGuiStyleVar import com.lambda.module.Module -class ModuleEntry(val module: Module): Layout { +class ModuleEntry(val module: Module) : Layout { override fun ImGuiBuilder.buildLayout() { - selectable(module.name, selected = module.isEnabled) { + val isEnabled = module.isEnabled + val isExpanded = ClickGuiLayout.isModuleExpanded(module.name) + val primary = ClickGuiLayout.primaryColor + val scale = ClickGuiLayout.currentScale + + val rowStartY = cursorPosY + val textHeight = ImGui.getTextLineHeight() + val rowHeight = textHeight + style.framePadding.y * 2f + + // Visual highlight / accent bar for enabled modules + if (isEnabled && ClickGuiLayout.highlightEnabledModules) { + val minX = ImGui.getWindowPosX() + style.windowPadding.x * 0.4f + val maxX = ImGui.getWindowPosX() + ImGui.getWindowWidth() - style.windowPadding.x * 0.4f + val minY = ImGui.getWindowPosY() + rowStartY - ImGui.getScrollY() + val maxY = minY + rowHeight + + // Soft tint background + val accentCol = ImColor.rgba(primary.red, primary.green, primary.blue, 25) + windowDrawList.addRectFilled(minX, minY, maxX, maxY, accentCol, style.frameRounding) + + // Distinct left accent bar + val barWidth = 2.5f * scale + val barCol = ImColor.rgba(primary.red, primary.green, primary.blue, 240) + windowDrawList.addRectFilled(minX, minY, minX + barWidth, maxY, barCol, style.frameRounding) + } + + // Invisible selectable covering the entire row + selectable("##sel-${module.name}", selected = false, 0, ImVec2(0f, rowHeight)) { module.toggle() } + val isRowHovered = ImGui.isItemHovered() lambdaTooltip(module.description) - val popupId = "##ctx-${module.name}" - val entryId = getId(popupId) - + // Right-click handling: toggle inline settings expansion inside the category window onItemHover(ImGuiHoveredFlags.AllowWhenBlockedByPopup) { - if (isMouseClicked() && isPopupOpen(popupId)) suppressedEntryId = entryId - if (isMouseReleased() && suppressedEntryId != entryId) openPopup(popupId) + if (isMouseClicked(ImGuiMouseButton.Right)) { + ClickGuiLayout.toggleModuleExpanded(module.name) + } } - if (isMouseReleased() && suppressedEntryId == entryId) suppressedEntryId = null + // Measure right-side elements + val bind = module.keybind + val hasBind = (bind.isKeyBind || bind.isMouseBind) && ClickGuiLayout.showKeybindBadges + val bindText = if (hasBind) "[${bind.name}]" else "" + val bindWidth = if (hasBind) ImGui.calcTextSize(bindText).x + 6f else 0f + val indicatorWidth = 14f * scale - ImGui.setNextWindowSizeConstraints(0f, 0f, Float.MAX_VALUE, io.displaySize.y * 0.5f) - popupContextItem(popupId, ImGuiPopupFlags.MouseButtonRight) { - buildConfigSettingsContext(module) + val winLeft = ImGui.getWindowPosX() + val winWidth = ImGui.getWindowWidth() + val rightEdge = winLeft + winWidth - style.windowPadding.x - 4f + + // Draw keybind badge if present + if (hasBind) { + val bindX = rightEdge - indicatorWidth - bindWidth + val textY = ImGui.getWindowPosY() + rowStartY + style.framePadding.y - ImGui.getScrollY() + windowDrawList.addText(bindX, textY, ImColor.rgba(255, 205, 75, 230), bindText) } - } - private companion object { - /** Entry whose reopen is pending suppression; only one item can be pressed at a time. */ - var suppressedEntryId: Int? = null + // Draw expander indicator (vector-drawn, never broken or missing glyph!) + val triCenterY = ImGui.getWindowPosY() + rowStartY + rowHeight * 0.5f - ImGui.getScrollY() + val triCenterX = rightEdge - 5f * scale + val triSize = 3.5f * scale + + if (isExpanded) { + // Downward pointing arrow for expanded + windowDrawList.addTriangleFilled( + triCenterX - triSize, triCenterY - triSize * 0.5f, + triCenterX + triSize, triCenterY - triSize * 0.5f, + triCenterX, triCenterY + triSize * 0.7f, + ImColor.rgba(primary.red, primary.green, primary.blue, 220) + ) + } else if (isRowHovered) { + // Right-pointing subtle arrow on hover to indicate settings exist + windowDrawList.addTriangleFilled( + triCenterX - triSize * 0.5f, triCenterY - triSize, + triCenterX - triSize * 0.5f, triCenterY + triSize, + triCenterX + triSize * 0.7f, triCenterY, + ImColor.rgba(200, 200, 200, 160) + ) + } + + // Draw Module Name (clipped if needed to prevent overlap with badges) + val nameIndent = if (isEnabled && ClickGuiLayout.highlightEnabledModules) 6f * scale else 2f + val nameX = winLeft + style.windowPadding.x + nameIndent + val nameY = ImGui.getWindowPosY() + rowStartY + style.framePadding.y - ImGui.getScrollY() + val maxNameWidth = (rightEdge - indicatorWidth - bindWidth - nameX - 4f).coerceAtLeast(20f) + + val nameCol = if (isEnabled) { + ImColor.rgba(primary.red, primary.green, primary.blue, 255) + } else { + val c = ClickGuiLayout.text + ImColor.rgba(c.red, c.green, c.blue, c.alpha) + } + + // Use clip rect to guarantee text NEVER bleeds into the keybind badge + windowDrawList.pushClipRect(nameX, nameY - 2f, nameX + maxNameWidth, nameY + textHeight + 2f, true) + windowDrawList.addText(nameX, nameY, nameCol, module.name) + windowDrawList.popClipRect() + + + // Inline expanded settings view + if (isExpanded) { + val childBgCol = ClickGuiLayout.childBg + withStyleVar(ImGuiStyleVar.ChildRounding, 4f) { + withStyleColor(ImGuiCol.ChildBg, childBgCol.red / 255f, childBgCol.green / 255f, childBgCol.blue / 255f, childBgCol.alpha / 255f) { + indent(8f) + buildConfigSettingsContext(module) + unindent(8f) + separator() + } + } + } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt b/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt index e21cec084..aa299520b 100644 --- a/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt +++ b/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt @@ -23,11 +23,13 @@ import com.lambda.command.LambdaCommand import com.lambda.config.Config import com.lambda.config.ConfigLoader import com.lambda.config.entries.Setting +import com.lambda.config.settings.comparable.BooleanSetting import com.lambda.event.events.ButtonEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.gui.LambdaScreen import com.lambda.gui.Layout import com.lambda.gui.dsl.ImGuiBuilder +import com.lambda.imgui.ImColor import com.lambda.imgui.ImGui import com.lambda.imgui.flag.ImGuiHoveredFlags import com.lambda.imgui.flag.ImGuiInputTextFlags @@ -43,19 +45,36 @@ import com.lambda.util.KeyCode import com.lambda.util.StringUtils.capitalize import com.lambda.util.StringUtils.levenshteinDistance import net.minecraft.client.gui.screen.ChatScreen +import net.minecraft.client.util.InputUtil +import org.lwjgl.glfw.GLFW import kotlin.math.max -// ToDo: Add support for searching of menu bar entries @Suppress("unused") object QuickSearch { private val searchInput = ImString(256) var isOpen = false private set + var lastClosedTimestamp = 0L + private set private var shouldFocus = false private var pendingClose = false private var lastShiftPressTime = 0L private var lastShiftKeyCode = -1 + var selectedIndex = 0 + private var needScrollToSelected = false + private var lastSearchedQuery = "" + + enum class SearchFilter(val label: String) { + ALL("All"), + MODULES("Modules"), + SETTINGS("Settings"), + COMMANDS("Commands") + } + + var currentFilter = SearchFilter.ALL + private var currentResults: List = emptyList() + private const val DOUBLE_SHIFT_WINDOW_MS = 500L private const val MAX_RESULTS = 50 private const val POPUP_ID = "QuickSearch" @@ -75,46 +94,129 @@ object QuickSearch { } interface SearchResult : Layout { + val title: String val breadcrumb: String + val category: String + val description: String + fun onActivate() } private class ModuleResult(val module: Module) : SearchResult { - override val breadcrumb = if (module is HudModule) "HUD" else "Module" + override val title: String = module.name + override val breadcrumb: String = if (module is HudModule) "HUD" else module.tag.name + override val category: String = if (module is HudModule) "HUD" else module.tag.name + override val description: String = module.description + + override fun onActivate() { + module.toggle() + } override fun ImGuiBuilder.buildLayout() { - with(ModuleEntry(module)) { - buildLayout { - withItemWidth(ImGui.getContentRegionAvailX()) { - buildLayout() - } - } + val isEnabled = module.isEnabled + val primary = ClickGuiLayout.primaryColor + + // Category tag + ImGui.textColored(primary.red / 255f, primary.green / 255f, primary.blue / 255f, 1f, "[${category.uppercase()}]") + sameLine() + + // Module name + text(module.name) + sameLine() + + // Status badge + if (isEnabled) { + ImGui.textColored(0.2f, 0.9f, 0.3f, 1.0f, "[ON]") + } else { + ImGui.textColored(0.55f, 0.55f, 0.55f, 1.0f, "[OFF]") + } + + // Keybind badge + val bind = module.keybind + if (bind.isKeyBind || bind.isMouseBind) { + sameLine() + ImGui.textColored(1.0f, 0.8f, 0.2f, 1.0f, "[${bind.name}]") + } + + // Action button + val btnLabel = if (isEnabled) "Disable##m-${module.name}" else "Enable##m-${module.name}" + val btnW = ImGui.calcTextSize(if (isEnabled) "Disable" else "Enable").x + style.framePadding.x * 2f + sameLine(windowContentRegionMaxX - btnW - style.windowPadding.x) + smallButton(btnLabel) { + module.toggle() + } + + if (module.description.isNotBlank()) { + textDisabled(" ${module.description}") } } } private class CommandResult(val command: LambdaCommand) : SearchResult { - override val breadcrumb = "Command" + override val title: String = command.name.capitalize() + override val breadcrumb: String = "Command" + override val category: String = "Command" + override val description: String = command.description + + override fun onActivate() { + close() + mc.setScreen(ChatScreen("${CommandRegistry.prefix}${command.name} ", true)) + } + override fun ImGuiBuilder.buildLayout() { - text(command.name.capitalize()) + ImGui.textColored(1.0f, 0.6f, 0.2f, 1.0f, "[COMMAND]") sameLine() - smallButton("Insert") { mc.setScreen(ChatScreen("${CommandRegistry.prefix}${command.name} ", true)) } + text("${CommandRegistry.prefix}${command.name}") + + val btnLabel = "Insert##cmd-${command.name}" + val btnW = ImGui.calcTextSize("Insert").x + style.framePadding.x * 2f + sameLine(windowContentRegionMaxX - btnW - style.windowPadding.x) + smallButton(btnLabel) { + onActivate() + } + if (command.description.isNotBlank()) { - sameLine() - textDisabled(command.description) + textDisabled(" ${command.description}") } } } private class SettingResult(val setting: Setting<*>, val config: Config) : SearchResult { + override val title: String = setting.name override val breadcrumb: String by lazy { buildSettingBreadcrumb(config.name, setting) } + override val category: String = "Setting" + override val description: String = setting.description + + override fun onActivate() { + if (setting is BooleanSetting) { + setting.value = !setting.value + } + } override fun ImGuiBuilder.buildLayout() { - with(setting) { - buildLayout { - withItemWidth(ImGui.getContentRegionAvailX()) { - buildLayout() - } + ImGui.textColored(0.4f, 0.7f, 1.0f, 1.0f, "[SETTING]") + sameLine() + text(setting.name) + sameLine() + textDisabled("($breadcrumb)") + + // Quick toggle if boolean + if (setting is BooleanSetting) { + val stateText = if (setting.value) "Disable" else "Enable" + val btnW = ImGui.calcTextSize(stateText).x + style.framePadding.x * 2f + sameLine(windowContentRegionMaxX - btnW - style.windowPadding.x) + smallButton("$stateText##st-${setting.name}") { + setting.value = !setting.value } + } else { + val valStr = setting.value.toString() + val valPreview = if (valStr.length > 20) valStr.take(18) + "…" else valStr + val previewW = ImGui.calcTextSize(valPreview).x + style.framePadding.x * 2f + sameLine(windowContentRegionMaxX - previewW - style.windowPadding.x) + textDisabled(valPreview) + } + + if (setting.description.isNotBlank()) { + textDisabled(" ${setting.description}") } } } @@ -123,6 +225,7 @@ object QuickSearch { isOpen = true shouldFocus = true pendingClose = false + selectedIndex = 0 searchInput.clear() } @@ -130,26 +233,41 @@ object QuickSearch { isOpen = false shouldFocus = false pendingClose = true + lastClosedTimestamp = System.currentTimeMillis() } fun toggle() { if (isOpen) close() else open() } + fun moveSelection(delta: Int) { + if (currentResults.isEmpty()) return + selectedIndex = (selectedIndex + delta).let { + if (it < 0) currentResults.lastIndex + else if (it >= currentResults.size) 0 + else it + } + needScrollToSelected = true + } + + fun activateSelected() { + currentResults.getOrNull(selectedIndex)?.onActivate() + } + fun ImGuiBuilder.renderQuickSearch() { if (!isOpen && !pendingClose) return if (isOpen) openPopup(POPUP_ID) ImGui.setNextFrameWantCaptureKeyboard(true) - val maxW = io.displaySize.x * 0.5f - val maxH = io.displaySize.y * 0.5f + val maxW = (io.displaySize.x * 0.55f).coerceAtLeast(420f) + val maxH = (io.displaySize.y * 0.55f).coerceAtLeast(300f) val popupX = (io.displaySize.x - maxW) * 0.5f - val popupY = io.displaySize.y * 0.3f + val popupY = (io.displaySize.y - maxH) * 0.28f ImGui.setNextWindowPos(popupX, popupY) ImGui.setNextWindowSize(maxW, 0f) - ImGui.setNextWindowSizeConstraints(0f, 0f, maxW, maxH) + ImGui.setNextWindowSizeConstraints(maxW, 0f, maxW, maxH) popupModal(POPUP_ID, WINDOW_FLAGS) { if (pendingClose) { @@ -168,46 +286,137 @@ object QuickSearch { shouldFocus = false } + // Search Header & Input Box withItemWidth(ImGui.getContentRegionAvailX()) { - withStyleVar(ImGuiStyleVar.FramePadding, style.framePadding.x, style.framePadding.y) { + withStyleVar(ImGuiStyleVar.FramePadding, style.framePadding.x * 1.5f, style.framePadding.y * 1.5f) { ImGui.inputTextWithHint( "##qs-input", - "Type to search modules, settings, and commands...", + "Search modules, settings, commands... (Ctrl+F, Esc to close)", searchInput, ImGuiInputTextFlags.AutoSelectAll ) } } - val query = searchInput.get().trim() - if (query.isEmpty()) return@popupModal + val rawInput = searchInput.get().trim() + + // Detect prefix filters (m: modules, s: settings, c: commands) + val (effectiveFilter, query) = when { + rawInput.startsWith("m:", ignoreCase = true) || rawInput.startsWith("mod:", ignoreCase = true) -> { + SearchFilter.MODULES to rawInput.substringAfter(':').trim() + } + rawInput.startsWith("s:", ignoreCase = true) || rawInput.startsWith("set:", ignoreCase = true) -> { + SearchFilter.SETTINGS to rawInput.substringAfter(':').trim() + } + rawInput.startsWith("c:", ignoreCase = true) || rawInput.startsWith("cmd:", ignoreCase = true) -> { + SearchFilter.COMMANDS to rawInput.substringAfter(':').trim() + } + else -> currentFilter to rawInput + } + + // Category Filter Buttons + cursorPosY += 2f + SearchFilter.entries.forEach { filter -> + val isSelected = effectiveFilter == filter + val label = filter.label + + if (isSelected) { + val primary = ClickGuiLayout.primaryColor + withStyleColor(com.lambda.imgui.flag.ImGuiCol.Button, primary.red / 255f, primary.green / 255f, primary.blue / 255f, 0.8f) { + smallButton("$label##filter") { + currentFilter = filter + selectedIndex = 0 + } + } + } else { + smallButton("$label##filter") { + currentFilter = filter + selectedIndex = 0 + } + } + sameLine(0f, 6f) + } + newLine() + separator() + + val results = if (query.isEmpty()) { + SearchService.getQuickAccessList(effectiveFilter) + } else { + SearchService.performSearch(query, effectiveFilter) + } + + if (query != lastSearchedQuery) { + lastSearchedQuery = query + selectedIndex = 0 + } + + currentResults = results - val results = SearchService.performSearch(query) if (results.isEmpty()) { - textDisabled("Nothing found.") + if (query.isNotEmpty()) { + textDisabled("No results found for \"$query\".") + } else { + textDisabled("Type to search or select a filter tab above.") + } + renderFooterTips() return@popupModal } - val rowH = frameHeightWithSpacing + val rowH = frameHeightWithSpacing * 1.9f val topArea = cursorPosY + style.windowPadding.y - val listH = (results.size * rowH).coerceAtMost(maxH - topArea).coerceAtLeast(rowH) + val listH = (results.size * rowH).coerceAtMost(maxH - topArea - 30f).coerceAtLeast(rowH) child("qs_rows", 0f, listH, false) { results.forEachIndexed { idx, result -> withId(idx) { + val isSelected = idx == selectedIndex + val startY = cursorPosY + + if (isSelected && needScrollToSelected) { + ImGui.setScrollHereY(0.5f) + needScrollToSelected = false + } + + // Background highlight for selected row + val highlightAlpha = if (isSelected) 85 else 0 + if (highlightAlpha > 0) { + val minX = ImGui.getWindowPosX() + style.windowPadding.x + val maxX = ImGui.getWindowPosX() + ImGui.getWindowWidth() - style.windowPadding.x + val minY = ImGui.getWindowPosY() + startY - ImGui.getScrollY() + val maxY = minY + rowH + val col = ClickGuiLayout.headerHovered + val packedCol = ImColor.rgba(col.red, col.green, col.blue, highlightAlpha) + windowDrawList.addRectFilled(minX, minY, maxX, maxY, packedCol, style.frameRounding) + } + + // Interactive selectable for row selection and double-click activation + selectable("##row-sel-$idx", isSelected) { + selectedIndex = idx + result.onActivate() + } + if (ImGui.isItemHovered()) { + selectedIndex = idx + } + + // Overlay content + cursorPosY = startY + 2f with(result) { - if (breadcrumb.isNotBlank()) { - textDisabled(breadcrumb) - sameLine() - } buildLayout() } + cursorPosY = startY + rowH } } } + + renderFooterTips() } } + private fun ImGuiBuilder.renderFooterTips() { + separator() + textDisabled("Navigate: [↑/↓] • Execute: [Enter] • Close: [Esc] • Prefixes: m: s: c:") + } + private object SearchService { private data class RankedSearchResult(val result: SearchResult, val score: Int) @@ -215,92 +424,99 @@ object QuickSearch { private const val HUD_MODULE_PRIORITY_BONUS = 270 private const val COMMAND_PRIORITY_BONUS = 200 - /** - * Calculates a relevance score for a query against a target string. - * Returns 0 for no match. Higher scores are better. - * The `lenient` flag adjusts the threshold for fuzzy matching. - */ + fun getQuickAccessList(filter: SearchFilter): List { + val list = mutableListOf() + if (filter == SearchFilter.ALL || filter == SearchFilter.MODULES) { + // Show enabled modules first, then prominent default modules + val enabled = ModuleRegistry.modules.filter { it.isEnabled }.map { ModuleResult(it) } + list.addAll(enabled) + if (list.size < 8) { + val defaults = ModuleRegistry.modules.filter { !it.isEnabled }.take(10 - list.size).map { ModuleResult(it) } + list.addAll(defaults) + } + } + if (filter == SearchFilter.COMMANDS) { + list.addAll(CommandRegistry.commands.take(10).map { CommandResult(it) }) + } + return list.take(MAX_RESULTS) + } + private fun calculateScore(query: String, target: String, lenient: Boolean = false): Int { if (query.isEmpty() || target.isEmpty()) return 0 - // 1. Strong Matches (Exact, Prefix, Substring) if (target == query) return 200 if (target.startsWith(query)) { val completeness = (query.length * 50) / target.length - return 100 + completeness // Score: 101 - 150 + return 100 + completeness } if (target.contains(query)) { val completeness = (query.length * 40) / target.length - return 50 + completeness // Score: 51 - 90 + return 50 + completeness } - // 2. Weak Match (Fuzzy) val distance = query.levenshteinDistance(target) val strictThreshold = (query.length / 3).coerceAtLeast(1).coerceAtMost(4) val lenientThreshold = (query.length / 2).coerceAtLeast(2).coerceAtMost(6) val threshold = if (lenient) lenientThreshold else strictThreshold return if (distance <= threshold) { - (50 - (distance * 10)).coerceAtLeast(1) // Score: 1-40 + (50 - (distance * 10)).coerceAtLeast(1) } else { 0 } } - /** - * Performs a search and returns a list of ranked results. This is the internal - * implementation that can be run in strict or lenient mode. - */ - private fun searchInternal(query: String, lenient: Boolean): List { + private fun searchInternal(query: String, filter: SearchFilter, lenient: Boolean): List { val lowerCaseQuery = query.lowercase() - val moduleResults = ModuleRegistry.modules.mapNotNull { module -> - val nameScore = calculateScore(lowerCaseQuery, module.name.lowercase(), lenient) - val tagScore = calculateScore(lowerCaseQuery, module.tag.name.lowercase(), lenient) - val bestScore = max(nameScore, tagScore) - - if (bestScore > 0) { - when(module) { - is HudModule -> RankedSearchResult(ModuleResult(module), bestScore + HUD_MODULE_PRIORITY_BONUS) - else -> RankedSearchResult(ModuleResult(module), bestScore + MODULE_PRIORITY_BONUS) - } - } else null - } + val moduleResults = if (filter == SearchFilter.ALL || filter == SearchFilter.MODULES) { + ModuleRegistry.modules.mapNotNull { module -> + val nameScore = calculateScore(lowerCaseQuery, module.name.lowercase(), lenient) + val tagScore = calculateScore(lowerCaseQuery, module.tag.name.lowercase(), lenient) + val bestScore = max(nameScore, tagScore) - val commandResults = CommandRegistry.commands.mapNotNull { command -> - val nameScore = calculateScore(lowerCaseQuery, command.name.lowercase(), lenient) - val aliasScore = command.aliases.maxOfOrNull { calculateScore(lowerCaseQuery, it.lowercase(), lenient) } ?: 0 - val bestScore = max(nameScore, aliasScore) + if (bestScore > 0) { + when (module) { + is HudModule -> RankedSearchResult(ModuleResult(module), bestScore + HUD_MODULE_PRIORITY_BONUS) + else -> RankedSearchResult(ModuleResult(module), bestScore + MODULE_PRIORITY_BONUS) + } + } else null + } + } else emptyList() - if (bestScore > 0) { - RankedSearchResult(CommandResult(command), bestScore + COMMAND_PRIORITY_BONUS) - } else null - } + val commandResults = if (filter == SearchFilter.ALL || filter == SearchFilter.COMMANDS) { + CommandRegistry.commands.mapNotNull { command -> + val nameScore = calculateScore(lowerCaseQuery, command.name.lowercase(), lenient) + val aliasScore = command.aliases.maxOfOrNull { calculateScore(lowerCaseQuery, it.lowercase(), lenient) } ?: 0 + val bestScore = max(nameScore, aliasScore) - val settingResults = buildList { - ConfigLoader.configCategories.forEach { category -> - category.configs.forEach { config -> - config.settingLayers.forEachEntry { _, single -> - val setting = single.entry - if (setting.visibility()) { - val score = calculateScore(lowerCaseQuery, setting.name.lowercase(), lenient) - if (score > 0) add(RankedSearchResult(SettingResult(setting, config), score)) + if (bestScore > 0) { + RankedSearchResult(CommandResult(command), bestScore + COMMAND_PRIORITY_BONUS) + } else null + } + } else emptyList() + + val settingResults = if (filter == SearchFilter.ALL || filter == SearchFilter.SETTINGS) { + buildList { + ConfigLoader.configCategories.forEach { category -> + category.configs.forEach { config -> + config.settingLayers.forEachEntry { _, single -> + val setting = single.entry + if (setting.visibility()) { + val score = calculateScore(lowerCaseQuery, setting.name.lowercase(), lenient) + if (score > 0) add(RankedSearchResult(SettingResult(setting, config), score)) + } } } } } - } + } else emptyList() return moduleResults + commandResults + settingResults } - /** - * Main search entry point. It first attempts a strict search. If no results - * are found, it falls back to a more lenient fuzzy search. - */ - fun performSearch(query: String): List { - // First pass: strict search for high-quality matches. - val strictResults = searchInternal(query, lenient = false) + fun performSearch(query: String, filter: SearchFilter = SearchFilter.ALL): List { + val strictResults = searchInternal(query, filter, lenient = false) if (strictResults.isNotEmpty()) { return strictResults .sortedByDescending { it.score } @@ -308,8 +524,7 @@ object QuickSearch { .take(MAX_RESULTS) } - // Second pass: if nothing was found, perform a more generous fuzzy search. - return searchInternal(query, lenient = true) + return searchInternal(query, filter, lenient = true) .sortedByDescending { it.score } .map { it.result } .take(MAX_RESULTS) @@ -324,19 +539,63 @@ object QuickSearch { private fun handleKeyPress(event: ButtonEvent.Keyboard.Press) { if (AutoUpdater.showInstallModal || AutoUpdater.showUninstallModal) return - if ((!event.isPressed || event.isRepeated) || - !(event.keyCode == KeyCode.LeftShift.code || event.keyCode == KeyCode.RightShift.code)) return - - val currentTime = System.currentTimeMillis() - if (lastShiftKeyCode == event.keyCode && - currentTime - lastShiftPressTime <= DOUBLE_SHIFT_WINDOW_MS - ) { - toggle() - lastShiftPressTime = 0L - lastShiftKeyCode = -1 - } else { - lastShiftPressTime = currentTime - lastShiftKeyCode = event.keyCode + if (!event.isPressed || event.isRepeated) return + + // Navigation when QuickSearch is open + if (isOpen) { + when (event.keyCode) { + GLFW.GLFW_KEY_DOWN -> { + moveSelection(1) + event.cancel() + return + } + GLFW.GLFW_KEY_UP -> { + moveSelection(-1) + event.cancel() + return + } + GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER -> { + activateSelected() + event.cancel() + return + } + GLFW.GLFW_KEY_ESCAPE -> { + close() + event.cancel() + return + } + GLFW.GLFW_KEY_TAB -> { + // Cycle filter + val values = SearchFilter.entries + val nextIdx = (currentFilter.ordinal + 1) % values.size + currentFilter = values[nextIdx] + selectedIndex = 0 + event.cancel() + return + } + } + } + // Ctrl+F shortcut to open/toggle + val win = mc.window + val isCtrl = (event.modifiers and GLFW.GLFW_MOD_CONTROL != 0) + || (win != null && (InputUtil.isKeyPressed(win, GLFW.GLFW_KEY_LEFT_CONTROL) || InputUtil.isKeyPressed(win, GLFW.GLFW_KEY_RIGHT_CONTROL))) + if ((event.keyCode == GLFW.GLFW_KEY_F || event.translated == KeyCode.F) && isCtrl) { + if (!isOpen) open() else close() + event.cancel() + return + } + if (event.keyCode == KeyCode.LeftShift.code || event.keyCode == KeyCode.RightShift.code) { + val currentTime = System.currentTimeMillis() + if (lastShiftKeyCode == event.keyCode && + currentTime - lastShiftPressTime <= DOUBLE_SHIFT_WINDOW_MS + ) { + toggle() + lastShiftPressTime = 0L + lastShiftKeyCode = -1 + } else { + lastShiftPressTime = currentTime + lastShiftKeyCode = event.keyCode + } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/lambda/gui/components/SettingsWidget.kt b/src/main/kotlin/com/lambda/gui/components/SettingsWidget.kt index 4b4efeab8..5e255a1f6 100644 --- a/src/main/kotlin/com/lambda/gui/components/SettingsWidget.kt +++ b/src/main/kotlin/com/lambda/gui/components/SettingsWidget.kt @@ -29,118 +29,165 @@ import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImGui import com.lambda.imgui.flag.ImGuiPopupFlags import com.lambda.imgui.flag.ImGuiTabBarFlags +import com.lambda.imgui.type.ImString import com.lambda.module.HudModule import com.lambda.module.Module import com.lambda.module.modules.client.AutoUpdater object SettingsWidget { + private val settingFilters = mutableMapOf() + /** - * Builds the settings context popup content for the given config. + * Builds the settings content for the given config (used in popups and inline expansion). */ fun ImGuiBuilder.buildConfigSettingsContext(config: Config) { group { if (config is Module && config != AutoUpdater) { - button("Module Settings") { - ImGui.openPopup("##module-settings-popup-${config.name}") - } - ImGui.setNextWindowSizeConstraints(0f, 0f, Float.MAX_VALUE, io.displaySize.y * 0.5f) - popupContextItem("##module-settings-popup-${config.name}", ImGuiPopupFlags.None) { - with(config.keybindSetting) { buildLayout() } - with(config.prioritySetting) { buildLayout() } - with(config.disableOnReleaseSetting) { buildLayout() } - with(config.drawSetting) { buildLayout() } - if (config is HudModule) { - with(config.backgroundColor) { buildLayout() } - } - smallButton("Reset") { - config.resetSettings() - } - } + withId("##header-${config.name}") { + // Row 1: Keybind setting + Reset button + with(config.keybindSetting) { buildLayout() } + + sameLine() + val resetText = "Reset" + val resetBtnW = ImGui.calcTextSize(resetText).x + style.framePadding.x * 2.5f + cursorPosX = (windowContentRegionMaxX - resetBtnW - style.windowPadding.x).coerceAtLeast(cursorPosX + 10f) + smallButton("$resetText##rst-${config.name}") { + config.resetSettings() + } + lambdaTooltip("Reset all settings for ${config.name} to their default values") + + // Row 2: Draw on HUD and Toggle on Release checkboxes + checkbox("Draw on HUD##draw-${config.name}", config.drawSetting::value) + lambdaTooltip(config.drawSetting.description) + + sameLine(0f, 15f) + checkbox("Toggle on Release##rel-${config.name}", config.disableOnReleaseSetting::value) + lambdaTooltip("Disable this module when the bound key is released") + + if (config is HudModule) { + sameLine(0f, 15f) + with(config.backgroundColor) { buildLayout() } + } + + // Collapsible Advanced section for priority + treeNode("Advanced Options##adv-${config.name}") { + with(config.prioritySetting) { buildLayout() } + } + } } - lambdaTooltip("Resets all settings for this module to their default values") + if (config is IMutableAutomationConfig && config.automationConfig !== AutomationConfig.DEFAULT) { - button("Automation Config") { + button("Automation Config##btn-${config.name}") { ImGui.openPopup("##automation-config-popup-${config.name}") } - if (config.backingAutomationConfig !== config.defaultAutomationConfig) { - sameLine() - text("(${config.backingAutomationConfig.name})") - } + if (config.backingAutomationConfig !== config.defaultAutomationConfig) { + sameLine() + text("(${config.backingAutomationConfig.name})") + } ImGui.setNextWindowSizeConstraints(0f, 0f, Float.MAX_VALUE, io.displaySize.y * 0.5f) popupContextItem("##automation-config-popup-${config.name}", ImGuiPopupFlags.None) { - combo("##LinkedConfig", preview = "Linked Config: ${config.backingAutomationConfig.name}") { - val addItem: (Config) -> Unit = { item -> - val selected = item === config.backingAutomationConfig - - selectable(item.name, selected) { - if (!selected) { - (config.backingAutomationConfig as? UserAutomationConfig)?.linkedModules?.value?.remove(config.name) - (item as? UserAutomationConfig)?.linkedModules?.value?.add(config.name) - config.automationConfig = item as? AutomationConfig ?: return@selectable - } - } - } - addItem(config.defaultAutomationConfig) - UserAutomationCategory.configs.forEach { addItem(it) } - } + combo("##LinkedConfig", preview = "Linked Config: ${config.backingAutomationConfig.name}") { + val addItem: (Config) -> Unit = { item -> + val selected = item === config.backingAutomationConfig + + selectable(item.name, selected) { + if (!selected) { + (config.backingAutomationConfig as? UserAutomationConfig)?.linkedModules?.value?.remove(config.name) + (item as? UserAutomationConfig)?.linkedModules?.value?.add(config.name) + config.automationConfig = item as? AutomationConfig ?: return@selectable + } + } + } + addItem(config.defaultAutomationConfig) + UserAutomationCategory.configs.forEach { addItem(it) } + } buildConfigSettingsContext(config.automationConfig) } } } - if (!hasVisibleSettings(config.settingLayers)) return - separator() - drawLayers(config.settingLayers, config.name) + if (!hasVisibleSettings(config.settingLayers)) return + separator() + + // Setting search filter if module has 4 or more visible settings + val visibleCount = countVisibleSettings(config.settingLayers) + var filterQuery = "" + if (visibleCount >= 4) { + val filter = settingFilters.getOrPut(config.name) { ImString(64) } + withItemWidth(ImGui.getContentRegionAvailX()) { + ImGui.inputTextWithHint("##st-filter-${config.name}", "Filter ${config.name} settings...", filter) + } + filterQuery = filter.get().trim() + separator() + } + + drawLayers(config.settingLayers, config.name, filterQuery) } - private fun ImGuiBuilder.drawLayers(root: EntryLayer.Multiple>, idPrefix: String) { - var tabsDrawn = false - - root.layers.forEach { layer -> - when (layer) { - is EntryLayer.Single> -> drawSetting(layer.entry) - is EntryLayer.Group -> { - if (hasVisibleSettings(layer)) { - treeNode("${layer.name}##$idPrefix-group-${layer.name}") { - drawLayers(layer, "$idPrefix-${layer.name}") - } - } - } - is EntryLayer.Tab -> { - if (!tabsDrawn) { - tabsDrawn = true - val allTabs = root.layers - .filterIsInstance>>() - .filter { hasVisibleSettings(it) } - if (allTabs.isNotEmpty()) { - tabBar("##$idPrefix-tabs", ImGuiTabBarFlags.FittingPolicyResizeDown) { - allTabs.forEach { tab -> - tabItem(tab.name) { - drawLayers(tab, "$idPrefix-${tab.name}") - } - } - } - } - } - } - else -> {} - } - } + private fun ImGuiBuilder.drawLayers(root: EntryLayer.Multiple>, idPrefix: String, filter: String) { + var tabsDrawn = false + + root.layers.forEach { layer -> + when (layer) { + is EntryLayer.Single> -> { + if (filter.isEmpty() || layer.entry.name.contains(filter, ignoreCase = true)) { + drawSetting(layer.entry) + } + } + is EntryLayer.Group -> { + if (hasVisibleSettings(layer, filter)) { + treeNode("${layer.name}##$idPrefix-group-${layer.name}") { + drawLayers(layer, "$idPrefix-${layer.name}", filter) + } + } + } + is EntryLayer.Tab -> { + if (!tabsDrawn) { + tabsDrawn = true + val allTabs = root.layers + .filterIsInstance>>() + .filter { hasVisibleSettings(it, filter) } + if (allTabs.isNotEmpty()) { + tabBar("##$idPrefix-tabs", ImGuiTabBarFlags.FittingPolicyResizeDown) { + allTabs.forEach { tab -> + tabItem(tab.name) { + drawLayers(tab, "$idPrefix-${tab.name}", filter) + } + } + } + } + } + } + else -> {} + } + } } private fun ImGuiBuilder.drawSetting(setting: Setting<*>) { - if (!setting.visibility()) return - if (setting.disabled()) ImGui.beginDisabled() - with(setting) { buildLayout() } - if (setting.disabled()) ImGui.endDisabled() + if (!setting.visibility()) return + if (setting.disabled()) ImGui.beginDisabled() + with(setting) { buildLayout() } + if (setting.disabled()) ImGui.endDisabled() } - private fun hasVisibleSettings(layer: EntryLayer.Multiple>): Boolean = - layer.layers.any { layer -> - when (layer) { - is SettingEntryLayer<*, *> -> layer.entry.visibility() - is EntryLayer.Multiple> -> hasVisibleSettings(layer) - else -> false - } - } -} \ No newline at end of file + private fun countVisibleSettings(layer: EntryLayer.Multiple>): Int { + var count = 0 + layer.layers.forEach { l -> + when (l) { + is SettingEntryLayer<*, *> -> if (l.entry.visibility()) count++ + is EntryLayer.Multiple> -> count += countVisibleSettings(l) + else -> {} + } + } + return count + } + + private fun hasVisibleSettings(layer: EntryLayer.Multiple>, filter: String = ""): Boolean = + layer.layers.any { l -> + when (l) { + is SettingEntryLayer<*, *> -> l.entry.visibility() && (filter.isEmpty() || l.entry.name.contains(filter, ignoreCase = true)) + is EntryLayer.Multiple> -> hasVisibleSettings(l, filter) + else -> false + } + } +} diff --git a/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt b/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt index 449b8bc40..5ba9f8b16 100644 --- a/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt +++ b/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt @@ -20,6 +20,8 @@ package com.lambda.gui.dsl import com.lambda.gui.components.ClickGuiLayout +import com.lambda.gui.dsl.ImGuiBuilder.onItemClick +import com.lambda.gui.dsl.ImGuiBuilder.onItemHover import com.lambda.gui.dsl.ImGuiBuilder.openPopup import com.lambda.gui.dsl.ImGuiBuilder.text import com.lambda.imgui.ImDrawList @@ -108,10 +110,8 @@ import com.lambda.imgui.ImGui.isItemEdited import com.lambda.imgui.ImGui.isItemFocused import com.lambda.imgui.ImGui.isItemHovered import com.lambda.imgui.ImGui.isItemToggledOpen -import com.lambda.imgui.ImGui.isMouseClicked import com.lambda.imgui.ImGui.isWindowAppearing import com.lambda.imgui.ImGui.isWindowCollapsed -import com.lambda.imgui.ImGui.isWindowHovered import com.lambda.imgui.ImGui.plotHistogram import com.lambda.imgui.ImGui.plotLines import com.lambda.imgui.ImGui.popFont diff --git a/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt b/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt index f06de623e..f5e6e3176 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt @@ -23,8 +23,6 @@ data class StaticBlueprint( override val structure: Structure, ) : Blueprint() { override fun toString() = "Static Blueprint at ${center?.toShortString()}" - - companion object { - fun Structure.toBlueprint() = StaticBlueprint(this) - } } + +fun Structure.toBlueprint() = StaticBlueprint(this) 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 2ecda1e9b..1a68cd105 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt @@ -21,7 +21,7 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingData import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.results.GenericResult -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.util.math.distSq import com.lambda.util.math.vec3d import com.lambda.util.player.CheckedHit diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt index 2222f6791..d3b142bcf 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt @@ -19,8 +19,8 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder -import com.lambda.interaction.managers.rotating.RotationRequest -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.emptyState import net.minecraft.block.BlockState diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt index 676d99b47..de4669021 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.config.blocks.ActionConfig import com.lambda.context.Automated import com.lambda.interaction.construction.simulation.result.Drawable -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runSafe import net.minecraft.block.BlockState import net.minecraft.util.hit.BlockHitResult diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt index a431da618..6ab4617b6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt @@ -20,8 +20,8 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.interaction.construction.simulation.processing.PreProcessingInfo -import com.lambda.interaction.managers.interacting.InteractRequest -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.interacting.InteractRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import net.minecraft.block.BlockState import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt index 9f25ed94f..56a7395f3 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt @@ -21,8 +21,8 @@ import com.lambda.config.blocks.ActionConfig import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.managers.hotbar.HotbarManager -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.hotbar.HotbarManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.threading.runSafe import com.lambda.util.BlockUtils import net.minecraft.block.Blocks diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt index 533123ef0..496faa8cc 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt @@ -17,13 +17,13 @@ package com.lambda.interaction.construction.simulation.result -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated import com.lambda.task.Task /** * Represents a [BuildResult] with a resolvable [Task] */ interface Resolvable { - context(task: Task<*>, _: AutomatedSafeContext) - fun resolve() + context(_: Automated) + fun resolve(): Task<*> } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 58b684006..87bc4f532 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.util.DirectionMask.mask import com.lambda.interaction.construction.simulation.context.BreakContext @@ -31,12 +31,11 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.handlers.ContainerHandler.transferByTask -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.containers.HotbarContainer -import com.lambda.task.Task -import com.lambda.task.wrappers.softFail +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.task.tasks.transfer import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -118,12 +117,10 @@ sealed class BreakResult : BuildResult() { ) : Resolvable, BreakResult() { override val rank = Rank.BreakItemCantMine - context(task: Task<*>, _: AutomatedSafeContext) - override fun resolve() { - selectStack { - isItem(badItem).not() - }.transferByTask(HotbarContainer)?.softFail()?.execute(task) - } + context(_: Automated) + override fun resolve() = + stackSelection { notItem(badItem) } + .transfer(toSelection = HotbarContainer.select()) override fun compareResult(other: ComparableResult) = when (other) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index 54b8fd2fd..2c5e64a8d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -18,7 +18,7 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalNear -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult @@ -26,12 +26,11 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.handlers.ContainerHandler.transferByTask -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.containers.HotbarContainer -import com.lambda.task.Task -import com.lambda.task.wrappers.softFail +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.task.tasks.transfer import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos @@ -99,10 +98,10 @@ sealed class GenericResult : BuildResult() { override val rank = Rank.WrongItem private val color = Color(3, 252, 169, 25) - context(task: Task<*>, _: AutomatedSafeContext) - override fun resolve() { - neededSelection.transferByTask(HotbarContainer)?.softFail()?.execute(task) - } + context(_: Automated) + override fun resolve() = + neededSelection + .transfer(toSelection = HotbarContainer.select()) override fun RenderBuilder.render() { val center = pos.toCenterPos() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt index 6c45686a3..c0946491d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt @@ -27,7 +27,7 @@ import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import net.minecraft.block.BlockState import net.minecraft.entity.Entity import net.minecraft.item.Item diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt index efa0cbaf6..62d15fb0f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt @@ -24,7 +24,7 @@ import com.lambda.interaction.construction.simulation.result.ComparableResult import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos import java.awt.Color diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index b611c8260..f3876b581 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -28,15 +28,15 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.managers.hotbar.HotbarManager -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.EVERYTHING -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.findStacks +import com.lambda.interaction.manager.managers.hotbar.HotbarManager +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable @@ -138,36 +138,27 @@ class BreakSim internal constructor(simInfo: SimInfo) } private fun AutomatedSafeContext.getSwapStack(): Pair? { - // Stack size 0 to account for attacking with an empty hand. Empty slots have stack size 0 - val stackSelection = selectStack( - count = 0, - sorter = compareByDescending { - it.canBreak(CachedBlockPosition(world, pos, false)) - }.thenByDescending { - state.calcItemBlockBreakingDelta(pos, it) - }.thenByDescending { - it.inventoryIndex == HotbarManager.serverSlot - } - ) { - EVERYTHING - .andIf(breakConfig.efficientOnly) { - isEfficientForBreaking(state) - }.andIf(breakConfig.suitableToolsOnly) { - isSuitableForBreaking(state) - }.andIf(breakConfig.forceSilkTouch) { - hasEnchantment(Enchantments.SILK_TOUCH) - }.andIf(breakConfig.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE) + val stackSelection = stackSelection { + if (breakConfig.efficientOnly) isEfficientForBreaking(state) + if (breakConfig.suitableToolsOnly) isSuitableForBreaking(state) + if (breakConfig.forceSilkTouch) withEnchantment(Enchantments.SILK_TOUCH) + if (breakConfig.forceFortunePickaxe) withEnchantment(Enchantments.FORTUNE) + sortedWith { + compareByDescending> { + it.stack.canBreak(CachedBlockPosition(world, pos, false)) + }.thenByDescending { + state.calcItemBlockBreakingDelta(pos, it.stack) + }.thenByDescending { + it.stack.inventoryIndex == HotbarManager.serverSlot } + } } - val silentSwapSelection = selectContainer { - ofAnyType(MaterialContainer.Rank.Hotbar) + val containerSelection = containerSelection { + ofAnyType(ContainerType.Hotbar) } - val hotbarCandidates = stackSelection - .findContainersWithMaterial(silentSwapSelection) - .flatMap { it.matchingStacks(stackSelection) } + val hotbarCandidates = findStacks(stackSelection, containerSelection).toList() if (hotbarCandidates.isEmpty()) { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index df60f11ff..e20ac4ecd 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -27,15 +27,15 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.util.BlockUtils.blockState import com.lambda.util.EntityUtils.getPositionsWithinHitboxXZ import com.lambda.util.PlaceDirection @@ -205,17 +205,24 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) supervisorScope.cancel() return null } - val stackSelection = item?.select()?.apply { if (item == Items.AIR) count = 0 } - ?: StackSelection.selectStack(0, sorter = compareByDescending { it.inventoryIndex == player.inventory.selectedSlot }) - val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.Hotbar) } - val container = stackSelection.findContainersWithMaterial(containerSelection).firstOrNull() ?: run { - result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) - return null - } - return stackSelection.filterSlots(container.slots).run { - firstOrNull { it.index == player.inventory.selectedSlot } - ?: firstOrNull() - } + val stackSelection = item?.select(if (item == Items.AIR) 0 else 1) + ?: stackSelection { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } + val containerSelection = + containerSelection { + hasStack(stackSelection) + ofAnyType(ContainerType.Hotbar) + } + val container = findContainer(containerSelection) + ?: run { + result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) + return null + } + return stackSelection + .filter(container.slots) + .run { + firstOrNull { it.index == player.inventory.selectedSlot } + ?: firstOrNull() + } } private suspend fun AutomatedSafeContext.simRotation( diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt index 9f30d19e2..7c5d72c22 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt @@ -34,7 +34,9 @@ interface StateMatcher { context(automatedSafeContext: AutomatedSafeContext) fun getStack(pos: BlockPos): ItemStack + context(automatedSafeContext: AutomatedSafeContext) fun getState(pos: BlockPos): BlockState + fun isEmpty(): Boolean } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 91cb32184..d79b3748b 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler.findDisposable +import com.lambda.interaction.handler.handlers.findContainerWithDisposable import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty @@ -86,9 +86,12 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findDisposable()?.stacks?.firstOrNull { - it.item in inventoryConfig.disposables && it.item.block !in replace - } ?: ItemStack(Items.NETHERRACK) + findContainerWithDisposable() + ?.stacks + ?.firstOrNull { + it.item in inventoryConfig.disposables && it.item.block !in replace + } + ?: ItemStack(Items.NETHERRACK) } context(_: AutomatedSafeContext) @@ -113,9 +116,12 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findDisposable()?.stacks?.firstOrNull { - it.item in inventoryConfig.disposables - } ?: ItemStack(Items.NETHERRACK) + findContainerWithDisposable() + ?.stacks + ?.firstOrNull { + it.item in inventoryConfig.disposables + } + ?: ItemStack(Items.NETHERRACK) } context(_: AutomatedSafeContext) @@ -176,12 +182,33 @@ sealed class TargetState : StateMatcher { ignoredProperties: Collection> ) = state.block == block - context(automatedSafeContext: AutomatedSafeContext) + context(_: AutomatedSafeContext) override fun getStack(pos: BlockPos): ItemStack = itemStack context(_: AutomatedSafeContext) override fun getState(pos: BlockPos): BlockState = block.defaultState - override fun isEmpty() = false + override fun isEmpty() = block.defaultState.isEmpty + } + + data class SpecificStack(val itemStack: ItemStack) : TargetState() { + override fun toString() = "Specific stack of ${itemStack.item.name.string.capitalize()}" + + private val block = itemStack.item.block + + context(safeContext: SafeContext) + override fun matches( + state: BlockState, + pos: BlockPos, + ignoredProperties: Collection> + ) = state.block == block + + context(_: AutomatedSafeContext) + override fun getStack(pos: BlockPos) = itemStack + + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = block.defaultState + + override fun isEmpty() = block.defaultState.isEmpty } } diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt new file mode 100644 index 000000000..6ffecb4a6 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt @@ -0,0 +1,176 @@ +/* + * Copyright 2026 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.container + +import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext +import com.lambda.event.EventFlow.post +import com.lambda.event.events.ContainerEvent +import com.lambda.interaction.container.containers.external.ShulkerBoxContainer +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.util.Nameable +import com.lambda.util.item.ItemStackUtils.count +import com.lambda.util.item.ItemStackUtils.emptySpace +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks +import com.lambda.util.item.ItemStackUtils.spaceLeft +import com.lambda.util.item.ItemUtils.SHULKER_BOXES +import com.lambda.util.item.ItemUtils.toItemCount +import com.lambda.util.text.TextBuilder +import com.lambda.util.text.TextDsl +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import com.lambda.util.text.text +import net.minecraft.component.DataComponentTypes +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.text.Text + +// ToDo: Make jsonable to persistently store them +abstract class Container( + val type: ContainerType +) : Nameable, Comparable { + override val name: String + get() = description.string + + abstract val slots: List + abstract var stacks: List + val storedContainers = mutableMapOf() + + open val swapMethodPriority = 0 + abstract val description: Text + + context(automated: Automated) + open val replaceSorter get() = compareByDescending { + it.stack.isEmpty + }.thenByDescending { + it.stack.item in automated.inventoryConfig.disposables + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.TOOL) + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.FOOD) + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.CONSUMABLE) + }.thenByDescending { + it.stack.isStackable + } + + open val isAccessed get() = true + + context(_: SafeContext) + fun descriptionAndStock(selection: StackSelection) = + buildText { + text(description) + stock(selection) + } + + @TextDsl + context(_: SafeContext) + fun TextBuilder.stock(selection: StackSelection) { + literal("\n") + literal("Contains ") + val available = count(selection) + highlighted(if (available == Int.MAX_VALUE) "∞" else available.toItemCount()) + literal(" of ") + highlighted("${selection.optimalStack?.name?.string}") + literal("\n") + literal("Could store ") + val left = spaceLeft(selection) + highlighted(if (left == Int.MAX_VALUE) "∞" else left.toItemCount()) + literal(" of ") + highlighted("${selection.optimalStack?.name?.string}") + } + + fun update(slots: List) { + this.stacks = slots + } + + fun scanStacksForNestedContainers() { + stacks.forEachIndexed { index, stack -> + if (stack.item in SHULKER_BOXES) { + storedContainers[index] = + ShulkerBoxContainer( + stack.name.string, + stack.item, + stack.shulkerBoxStacks, + this, + index + ).also { it.scanStacksForNestedContainers() } + } else storedContainers.remove(index) + } + } + + @Ta5kBuilder + context(automated: Automated) + open fun access(): OpenContainerTask<*>? = null + + open fun count(selection: StackSelection) = + slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).count } + ?: selection.filter(stacks).count + + open fun spaceLeft(selection: StackSelection) = + slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).spaceLeft + it.emptySpace } + ?: (selection.filter(stacks).spaceLeft + stacks.emptySpace) + + open fun findSlots(selection: StackSelection) = selection.filter(slots) + + fun findSlot(selection: StackSelection) = findSlots(selection).firstOrNull() + + open fun findStacks(selection: StackSelection) = selection.filter(stacks) + + fun findStack(selection: StackSelection) = findStacks(selection).firstOrNull() + + context(_: Automated) + fun findMoveSlots( + selection: StackSelection, + toContainer: Container, + toStackSelection: StackSelection = StackSelection.ANYTHING + ) = Pair(selection.filter(slots).firstOrNull(), toContainer.findReplaceSlot(toStackSelection)) + + context(_: Automated) + open fun findReplaceSlot( + selection: StackSelection = StackSelection.ANYTHING + ) = selection.filter(slots) + .sortedWith(replaceSorter) + .firstOrNull() + + context(automatedSafeContext: AutomatedSafeContext) + internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { + val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, toContainer) + if (transferEvent.post().isCanceled()) return false + return automatedSafeContext.inventoryRequest { + if (swapMethodPriority > toContainer.swapMethodPriority) swap(fromSlot, toSlot) + else with(toContainer) { swap(toSlot, fromSlot) } + }.submit().done + } + + context(safeContext: SafeContext) + protected open fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { + if (fromHere.stack.isEmpty) moveSlot(toSlot.id, fromHere.id) + else { + moveSlot(fromHere.id, toSlot.id) + if (!toSlot.stack.isEmpty) pickup(fromHere.id) + } + } + + override fun compareTo(other: Container) = compareBy { it.type }.compare(this, other) +} diff --git a/src/main/kotlin/com/lambda/interaction/container/ContainerDslMarker.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerDslMarker.kt new file mode 100644 index 000000000..9269e464e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerDslMarker.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 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.container + +@DslMarker +annotation class ContainerDslMarker \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt new file mode 100644 index 000000000..d53ce206e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2026 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.container + +import com.lambda.Lambda.LOG +import com.lambda.Lambda.mapper +import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.util.FolderRegistry +import net.minecraft.util.math.BlockPos +import java.nio.file.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteIfExists +import kotlin.io.path.exists +import kotlin.io.path.notExists + +/** + * Handles saving and loading [PlacedContainer]s to/from disk as JSON files. + * + * Serialized containers are stored under [FolderRegistry.containers]. + */ +object ContainerSerializer { + private const val ENDER_CHEST_FILE = "ender_chest.json" + + /** + * A reusable sequence that lazily loads all serialized containers from disk. + * Every iteration reads the files anew, preventing RAM exhaustion. + */ + val serializedContainers: Sequence = + sequence { + val dir = FolderRegistry.containers + if (dir.notExists()) return@sequence + + dir.toFile().walkTopDown().forEach { file -> + if (file.isFile && file.name.endsWith(".json") && file.name != ENDER_CHEST_FILE) { + yield(readContainer(file.toPath()) ?: return@forEach) + } + } + } + + /** + * Saves a [PlacedContainer] to disk as JSON. + */ + @Synchronized + fun saveContainer(container: PlacedContainer) { + val dir = ensureDirectory() + val file = dir.resolve(containerFileName(container.pos)).toFile() + mapper.writeValue(file, container) + } + + /** + * Saves the [EnderChestContainer] to disk. This is always loaded into memory + * for the entire session. + */ + fun saveEnderChest() { + val dir = ensureDirectory() + val file = dir.resolve(ENDER_CHEST_FILE).toFile() + mapper.writeValue(file, EnderChestContainer) + } + + /** + * Loads the [EnderChestContainer] contents from disk, if available. + * Called on session start to restore the ender chest state. + */ + fun loadEnderChest() { + val file = FolderRegistry.containers.resolve(ENDER_CHEST_FILE) + if (!file.exists()) return + runCatching { + mapper.readValue(file.toFile(), EnderChestContainer::class.java) + }.onFailure { + LOG.warn("Failed to load ender chest from $file", it) + } + } + + /** + * Removes a serialized container from disk. + */ + @Synchronized + fun removeContainer(pos: BlockPos) { + val dir = FolderRegistry.containers + dir.resolve(containerFileName(pos)).deleteIfExists() + } + + + + private fun readContainer(file: Path): PlacedContainer? = + runCatching { + mapper.readValue(file.toFile(), PlacedContainer::class.java) + }.onFailure { + LOG.warn("Failed to read container from ${file.fileName}", it) + }.getOrNull() + + private fun ensureDirectory() = + FolderRegistry.containers.also { if (it.notExists()) it.createDirectories() } + + private fun containerFileName(pos: BlockPos): String { + val chunkX = pos.x shr 4 + val chunkZ = pos.z shr 4 + return "${chunkX}_${chunkZ}_${pos.x}_${pos.y}_${pos.z}.json" + } +} diff --git a/src/main/kotlin/com/lambda/interaction/container/ContainerType.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerType.kt new file mode 100644 index 000000000..cfc76c5f5 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerType.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.container + +enum class ContainerType { + OffHand, + Hotbar, + Cursor, + Inventory, + Armor, + Creative, + ShulkerBox, + EnderChest, + PlacedShulkerBox, + PlacedEnderChest, + Chest, + Stash +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/container/ExternalContainer.kt new file mode 100644 index 000000000..500d2f6e2 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/ExternalContainer.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2026 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.container + +interface ExternalContainer \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt b/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt new file mode 100644 index 000000000..e71c7033e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2026 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.container + +abstract class NestedContainer(type: ContainerType) : Container(type), ExternalContainer { + abstract val containedIn: Container + abstract val index: Int +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt similarity index 70% rename from src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt index 7bad81182..27cc94c61 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.container -import com.lambda.context.AutomatedSafeContext import com.lambda.task.Task -import com.lambda.task.wrappers.TaskSupplier +import net.minecraft.text.Text -interface ExternalContainer { - context(_: AutomatedSafeContext) - fun accessThen(exitAfter: Boolean = true, taskSupplier: TaskSupplier): Task<*>? +abstract class OpenContainerTask @Ta5kBuilder internal constructor( + description: Text +) : Task() { + override val name = "accessing container: ${description.string}" } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt b/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt new file mode 100644 index 000000000..4deafbcb0 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 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.container + +import com.lambda.context.Automated +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.actionTask + +interface OpenedContainerContext { + @Ta5kBuilder + context(automated: Automated) + fun close(): Task<*>? +} + +open class BasicOpenedContainerContext( + private val isAccessed: () -> Boolean +) : OpenedContainerContext { + context(_: Automated) + final override fun close() = + if (!isAccessed()) null + else actionTask { player.closeScreen() } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/PlacedContainer.kt b/src/main/kotlin/com/lambda/interaction/container/PlacedContainer.kt new file mode 100644 index 000000000..98c5b8f38 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/PlacedContainer.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2026 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.container + +import com.lambda.interaction.container.containers.external.StashContainer +import net.minecraft.util.math.BlockPos + +abstract class PlacedContainer(type: ContainerType) : Container(type), ExternalContainer { + abstract val pos: BlockPos + abstract val stash: StashContainer? +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt new file mode 100644 index 000000000..2a76c37ab --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 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.container.containers + +import com.lambda.Lambda.mc +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.util.player.SlotUtils.armorSlots +import com.lambda.util.player.SlotUtils.armorStacks +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +object ArmorContainer : Container(ContainerType.Armor) { + override val slots: List + get() = mc.player?.armorSlots ?: emptyList() + override var stacks: List + get() = mc.player?.armorStacks ?: emptyList() + set(_) {} + + override val description = buildText { literal("Armor") } + + override val isAccessed get() = mc.player?.currentScreenHandler?.syncId == 0 +} diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt new file mode 100644 index 000000000..f6538f81c --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2026 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.container.containers + +import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.inventory.SingleStackInventory +import net.minecraft.item.ItemStack +import net.minecraft.screen.PlayerScreenHandler +import net.minecraft.screen.slot.Slot + +data object CreativeContainer : Container(ContainerType.Creative) { + override val slots = emptyList() + override var stacks = emptyList() + + override val swapMethodPriority = 11 + + override val description = buildText { literal("Creative") } + + override fun count(selection: StackSelection): Int = + if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + + override fun spaceLeft(selection: StackSelection): Int = + if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + + context(safeContext: SafeContext) + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { + clickCreativeStack(fromHere.stack, toSlot.id) + safeContext.player.currentScreenHandler.slots.getOrNull(toSlot.id)?.stack = fromHere.stack + } + + override fun findSlots(selection: StackSelection) = + listOfNotNull( + selection.optimalStack?.let { stack -> + Slot( + object : SingleStackInventory { + override fun getStack() = stack + override fun setStack(stack: ItemStack?) {} + override fun markDirty() {} + override fun canPlayerUse(player: PlayerEntity?) = false + }, + 0, 0, 0 + ) + } + ) + + override fun findStacks(selection: StackSelection) = + listOfNotNull(selection.optimalStack) + + private val correctScreenHandler + get() = mc.player?.currentScreenHandler is PlayerScreenHandler || mc.player?.currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler +} diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt new file mode 100644 index 000000000..4455c6f4a --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2026 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.container.containers + +import com.lambda.Lambda.mc +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +object CursorContainer : Container(ContainerType.Cursor) { + override val slots: List = emptyList() + override var stacks: List + get() = mc.player?.currentScreenHandler?.cursorStack?.let { listOf(it) } ?: emptyList() + set(_) {} + + override val description = buildText { literal("Cursor") } +} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/HotbarContainer.kt similarity index 71% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/HotbarContainer.kt index f2dc5d3f4..3e5ba3744 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/HotbarContainer.kt @@ -15,22 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.screen.slot.Slot -object HotbarContainer : MaterialContainer(Rank.Hotbar) { - context(safeContext: SafeContext) +object HotbarContainer : Container(ContainerType.Hotbar) { override val slots: List - get() = safeContext.player.hotbarSlots + get() = mc.player?.hotbarSlots ?: emptyList() override var stacks get() = mc.player?.hotbarStacks ?: emptyList() set(_) {} @@ -40,7 +40,7 @@ object HotbarContainer : MaterialContainer(Rank.Hotbar) { override val description = buildText { literal("Hotbar") } context(safeContext: SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - swap(toSlot.id, safeContext.player.hotbarSlots.indexOf(fromHere)) + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { + swapWithHotbar(toSlot.id, safeContext.player.hotbarSlots.indexOf(fromHere)) } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/InventoryContainer.kt similarity index 78% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/InventoryContainer.kt index 10867bdb2..f1c56e1cf 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/InventoryContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc -import com.lambda.context.SafeContext -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType import com.lambda.util.player.SlotUtils.inventorySlots import com.lambda.util.player.SlotUtils.inventoryStacks import com.lambda.util.text.buildText @@ -27,10 +27,9 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object InventoryContainer : MaterialContainer(Rank.Inventory) { - context(safeContext: SafeContext) +object InventoryContainer : Container(ContainerType.Inventory) { override val slots: List - get() = safeContext.player.inventorySlots + get() = mc.player?.inventorySlots ?: emptyList() override var stacks: List get() = mc.player?.inventoryStacks ?: emptyList() set(_) {} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt similarity index 73% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt index 2ae235e42..400af4860 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt @@ -15,22 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object OffHandContainer : MaterialContainer(Rank.OffHand) { - context(safeContext: SafeContext) +object OffHandContainer : Container(ContainerType.OffHand) { override val slots: List - get() = safeContext.player.offHandSlots + get() = mc.player?.offHandSlots ?: emptyList() override var stacks: List get() = mc.player?.offHandStack?.let { listOf(it) } ?: emptyList() set(_) {} @@ -40,7 +40,7 @@ object OffHandContainer : MaterialContainer(Rank.OffHand) { override val description = buildText { literal("OffHand") } context(_: SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - swap(toSlot.id, 40) + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { + swapWithHotbar(toSlot.id, 40) } } diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ChestContainer.kt new file mode 100644 index 000000000..ab545dea2 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ChestContainer.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2026 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.container.containers.external + +import com.lambda.Lambda.mc +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.container.BasicOpenedContainerContext +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.openContainer +import com.lambda.util.extension.containerSlots +import com.lambda.util.player.SlotUtils.typeSafe +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import net.minecraft.block.ChestBlock +import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandlerType +import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos + +data class ChestContainer( + override val pos: BlockPos, + override var stacks: List, + override val stash: StashContainer? = null +) : PlacedContainer(ContainerType.Chest) { + override val slots + get(): List = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + + override val description = + buildText { + literal("Chest at ") + highlighted(pos.toShortString()) + stash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } + + override val isAccessed + get() = lastInteractedBlockEntity?.let { blockEntity -> + blockEntity.pos == pos && + blockEntity.cachedState.block is ChestBlock && + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.GENERIC_9X3 + } ?: false + + @Ta5kBuilder + context(automated: Automated) + override fun access() = + if (isAccessed) null + else AccessChestTask(automated) + + inner class AccessChestTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + openContainer(pos) + .onSuccess { success(BasicOpenedContainerContext(::isAccessed)) } + .start() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt new file mode 100644 index 000000000..f9a9ff386 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2026 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.container.containers.external + +import com.lambda.Lambda.mc +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.container.BasicOpenedContainerContext +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.withRecovery +import com.lambda.util.extension.containerSlots +import com.lambda.util.math.distSq +import com.lambda.util.player.SlotUtils.typeSafe +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import net.minecraft.block.ChestBlock +import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandlerType +import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos + +data class DoubleChestContainer( + override val pos: BlockPos, + val leftPos: BlockPos, + val rightPos: BlockPos, + override var stacks: List, + override val stash: StashContainer? = null +) : PlacedContainer(ContainerType.Chest) { + override val slots + get(): List = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + + override val description = + buildText { + literal("Double Chest at ") + highlighted(leftPos.toShortString()) + literal(" and ") + highlighted(rightPos.toShortString()) + stash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } + + override val isAccessed + get() = lastInteractedBlockEntity?.let { blockEntity -> + (blockEntity.pos == leftPos || blockEntity.pos == rightPos) && + blockEntity.cachedState.block is ChestBlock && + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.GENERIC_9X6 + } ?: false + + @Ta5kBuilder + context(automated: Automated) + override fun access() = + if (isAccessed) null + else AccessChestTask(automated) + + inner class AccessChestTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + val leftDist = leftPos distSq player.pos + val rightDist = rightPos distSq player.pos + val closestPos = if (leftDist <= rightDist) leftPos else rightPos + val farthestPos = if (leftDist > rightDist) leftPos else rightPos + openContainer(closestPos) + .withRecovery { openContainer(farthestPos) } + .onSuccess { success(BasicOpenedContainerContext(::isAccessed)) } + .start() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt new file mode 100644 index 000000000..da40583bb --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2026 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.container.containers.external + +import com.lambda.Lambda.mc +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.ExternalContainer +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.handler.handlers.findSlot +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.acquireStack +import com.lambda.task.tasks.breakAndCollect +import com.lambda.task.tasks.findBlock +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.placeContainer +import com.lambda.task.tasks.wrappers.actionTask +import com.lambda.task.tasks.wrappers.taskOrNull +import com.lambda.task.tasks.wrappers.then +import com.lambda.task.tasks.wrappers.thenOrNull +import com.lambda.task.tasks.wrappers.withBranch +import com.lambda.util.extension.containerSlots +import com.lambda.util.player.SlotUtils.typeSafe +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.block.Blocks +import net.minecraft.block.entity.EnderChestBlockEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.screen.ScreenHandlerType +import net.minecraft.util.math.BlockPos + +object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContainer { + override val slots + get() = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + override var stacks = emptyList() + + override val description = buildText { literal("Ender Chest") } + + override val isAccessed + get() = + lastInteractedBlockEntity?.let { blockEntity -> + blockEntity is EnderChestBlockEntity && + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.GENERIC_9X3 + } ?: false + + @Ta5kBuilder + context(automated: Automated) + override fun access() = + if (isAccessed) null + else OpenEnderChestContainerTask(automated) + + class OpenEnderChestContainerTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch( + onSuccess = { pos -> + openContainer(pos).onSuccess { + success(OpenedEnderChestContext(null)) + } + }, + onFailure = { + acquireStack(Items.ENDER_CHEST.select(1)) + .then { placeContainer(findSlot(it, HotbarContainer.select())) } + .then { pos -> + openContainer(pos).onSuccess { + success(OpenedEnderChestContext(pos)) + } + } + } + ).start() + } + } + + class OpenedEnderChestContext( + val blockPos: BlockPos? + ) : OpenedContainerContext { + context(_: Automated) + override fun close() = + taskOrNull { + if (isAccessed) actionTask { player.closeScreen() } + else null + }.thenOrNull { + if (blockPos != null) breakAndCollect(blockPos) + else null + } + } +} diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/PlacedShulkerBoxContainer.kt new file mode 100644 index 000000000..716e2489e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/PlacedShulkerBoxContainer.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2026 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.container.containers.external + +import com.lambda.Lambda.mc +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.container.BasicOpenedContainerContext +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.openContainer +import com.lambda.util.extension.containerSlots +import com.lambda.util.player.SlotUtils.typeSafe +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import net.minecraft.block.Block +import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandlerType +import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos + +class PlacedShulkerBoxContainer( + override val pos: BlockPos, + val block: Block, + override val stash: StashContainer?, + override var stacks: List +) : PlacedContainer(ContainerType.PlacedShulkerBox) { + override val slots: List + get() = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + + override val description = + buildText { + literal("Placed shulker box at ") + highlighted(pos.toShortString()) + stash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } + + override val isAccessed + get() = lastInteractedBlockEntity?.let { blockEntity -> + blockEntity.pos == pos && + blockEntity.cachedState.block == block && + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.SHULKER_BOX + } ?: false + + @Ta5kBuilder + context(automated: Automated) + override fun access() = + if (isAccessed) null + else OpenPlacedShulkerBoxTask(automated) + + inner class OpenPlacedShulkerBoxTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + openContainer(pos) + .onSuccess { success(BasicOpenedContainerContext(::isAccessed)) } + .start() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt new file mode 100644 index 000000000..9a61e1a27 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2026 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.container.containers.external + +import com.lambda.Lambda.mc +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.ExternalContainer +import com.lambda.interaction.container.NestedContainer +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.ContainerSearchScope +import com.lambda.interaction.handler.handlers.findSlot +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.breakAndCollect +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.placeContainer +import com.lambda.task.tasks.transfer +import com.lambda.task.tasks.wrappers.actionTask +import com.lambda.task.tasks.wrappers.taskOrNull +import com.lambda.task.tasks.wrappers.then +import com.lambda.util.extension.containerSlots +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos +import java.util.Objects.hash + +data class ShulkerBoxContainer( + val itemName: String, + val shulkerItem: Item, + override var stacks: List, + override val containedIn: Container, + override val index: Int, +) : NestedContainer(ContainerType.ShulkerBox), ExternalContainer { + override val slots + get(): List = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + + override val description = + buildText { + highlighted(itemName) + literal(" in ") + highlighted(containedIn.name) + literal(" in slot index $index") + } + + override val isAccessed = false + + @Ta5kBuilder + context(automated: Automated) + override fun access() = OpenShulkerBoxTask(automated) + + inner class OpenShulkerBoxTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + transfer( + stackSelection { predicate { _, slot -> slot?.index == index }; isItem(shulkerItem) }, + containedIn.select(), + HotbarContainer.select() + ).then { result -> placeContainer(findSlot(result.stackSelection, result.containerSelection)) } + .then { pos -> + openContainer(pos).onSuccess { + success(OpenedShulkerBoxContext(pos, containedIn)) + } + } + .start() + } + } + + inner class OpenedShulkerBoxContext( + val blockPos: BlockPos, + val fromContainer: Container + ) : OpenedContainerContext { + context(automated: Automated) + override fun close() = + taskOrNull { + if (!isAccessed) null + else actionTask { player.closeScreen() } + }.then { breakAndCollect(blockPos) } + .then { + stackSelection { + isItem(shulkerItem) + withName(itemName) + sortedByBestContentMatch(stacks) + }.transfer( + ContainerSelection.HOTBAR_AND_INVENTORY, + fromContainer.select(ContainerSearchScope.Loaded) + ) + } + } + + override fun hashCode() = hash(containedIn.hashCode(), index) + + override fun equals(other: Any?) = + other is ShulkerBoxContainer && + containedIn == other.containedIn && + index == other.index +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt similarity index 67% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt index e84b3ce4d..7d1276ac1 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.container.containers.external -import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.StackSelection import com.lambda.util.math.roundedBlockPos import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -31,22 +31,19 @@ import net.minecraft.util.math.Box data class StashContainer( val chests: Set, val pos: Box, -) : MaterialContainer(Rank.Stash) { - context(_: SafeContext) +) : Container(ContainerType.Stash) { override val slots: List get() = chests.flatMap { it.slots } override var stacks: List get() = chests.flatMap { it.stacks } set(_) {} - override val description = buildText { - literal("Stash at ") - highlighted(pos.center.roundedBlockPos.toShortString()) - } - - context(_: SafeContext) - override fun materialAvailable(selection: StackSelection): Int = - chests.sumOf { - it.materialAvailable(selection) + override val description = + buildText { + literal("Stash at ") + highlighted(pos.center.roundedBlockPos.toShortString()) } + + override fun count(selection: StackSelection): Int = + chests.sumOf { it.count(selection) } } diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt new file mode 100644 index 000000000..a72854a9a --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt @@ -0,0 +1,262 @@ +/* + * Copyright 2026 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.container.selection + +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerDslMarker +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.NestedContainer +import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.containers.CreativeContainer +import com.lambda.interaction.container.containers.CursorContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.handler.handlers.ContainerSearchScope + +@ContainerDslMarker +fun Container.select( + scope: ContainerSearchScope = ContainerSearchScope.Accessed +) = containerSelection(scope) { ofAny(this@select) } + +@ContainerDslMarker +fun Iterable.select( + scope: ContainerSearchScope = ContainerSearchScope.Accessed +) = containerSelection(scope) { ofAny(this@select) } + +@ContainerDslMarker +fun selectContainers( + vararg containers: Container, + scope: ContainerSearchScope = ContainerSearchScope.Accessed +) = containerSelection(scope) { ofAny(*containers) } + +@ContainerDslMarker +fun selectContainer( + scope: ContainerSearchScope = ContainerSearchScope.Accessed, + builder: ContainerSelectionBuilder.() -> Unit +) = containerSelection(scope, builder) + +/** + * ContainerSelection is a class that holds a predicate for matching containers. + */ +@Suppress("unused") +class ContainerSelection @ContainerDslMarker internal constructor( + val selector: (Container) -> Boolean, + val scope: ContainerSearchScope = ContainerSearchScope.Accessed, + val containersWhitelist: Collection = emptyList(), + val loadedContainers: Collection = emptyList(), + val containersBlacklist: Collection = emptyList(), + val accessScope: AccessScope = AccessScope.Both, + val comparator: Comparator = compareBy { it.type } +) { + private val whitelistSet = containersWhitelist.takeIf { it.isNotEmpty() }?.toSet() + private val blacklistSet = containersBlacklist.takeIf { it.isNotEmpty() }?.toSet() + + @ContainerDslMarker + fun bestMatch(containers: Iterable) = filter(containers).firstOrNull() + + @ContainerDslMarker + fun matches(container: Container): Boolean { + if (whitelistSet != null && container !in whitelistSet) return false + if (blacklistSet != null && container in blacklistSet) return false + if (accessScope.accessed != null && container.isAccessed != accessScope.accessed) return false + return selector(container) + } + + @ContainerDslMarker + fun filter(containers: Iterable): List = + containers.asSequence() + .filter { container -> + (whitelistSet == null || container in whitelistSet) && + (blacklistSet == null || container !in blacklistSet) && + (accessScope.accessed == null || container.isAccessed == accessScope.accessed) && + selector(container) + } + .sortedWith(comparator) + .toList() + + companion object { + val ACCESSED = ContainerSelection({ true }, ContainerSearchScope.Accessed) + val EVERYTHING = ContainerSelection({ true }, ContainerSearchScope.All) + val NOTHING = ContainerSelection({ false }, ContainerSearchScope.Accessed) + val HOTBAR_AND_INVENTORY = selectContainers(HotbarContainer, InventoryContainer) + val PLAYER = + selectContainers( + HotbarContainer, + InventoryContainer, + OffHandContainer, + CursorContainer, + ArmorContainer, + CreativeContainer + ) + } +} + +@Suppress("unused") +@ContainerDslMarker +class ContainerSelectionBuilder @ContainerDslMarker private constructor( + private val scope: ContainerSearchScope +) { + @ContainerDslMarker + private constructor( + selection: ContainerSelection, + scope: ContainerSearchScope + ) : this(scope) { + this.selector = selection.selector + this.containersWhitelist.addAll(selection.containersWhitelist) + this.containersBlacklist.addAll(selection.containersBlacklist) + this.accessScope = selection.accessScope + this.comparator = selection.comparator + } + + private var selector: (Container) -> Boolean = { true } + private val containersWhitelist = mutableListOf() + private val loadedContainers = mutableListOf() + private val containersBlacklist = mutableListOf() + private var accessScope: AccessScope = AccessScope.Both + private var comparator: Comparator = compareBy { it.type } + + fun withContainers(vararg containers: Container) { + loadedContainers.addAll(containers) + } + + fun withContainers(containers: Iterable) { + loadedContainers.addAll(containers) + } + + fun ofAny(vararg containers: Container) { + containersWhitelist.addAll(containers) + } + + fun ofAny(containers: Iterable) { + containersWhitelist.addAll(containers) + } + + fun noneOf(vararg containers: Container) { + containersBlacklist.addAll(containers) + } + + fun noneOf(containers: Iterable) { + containersBlacklist.addAll(containers) + } + + fun ofAnyType(vararg types: ContainerType) { + appendSelector { container -> types.contains(container.type) } + } + + fun ofAnyType(types: Iterable) { + appendSelector { container -> types.contains(container.type) } + } + + fun noneOfType(vararg types: ContainerType) { + appendSelector { container -> !types.contains(container.type) } + } + + fun noneOfType(types: Iterable) { + appendSelector { container -> !types.contains(container.type) } + } + + fun isNested() { + appendSelector { container -> container is NestedContainer } + } + + fun notNested() { + appendSelector { container -> container !is NestedContainer } + } + + fun matches(containerSelection: ContainerSelection) { + appendSelector { container -> containerSelection.matches(container) } + } + + fun noMatch(containerSelection: ContainerSelection) { + appendSelector { container -> !containerSelection.matches(container) } + } + + fun hasStack(stackSelection: StackSelection) { + appendSelector { container -> stackSelection isIn container } + } + + fun noStack(stackSelection: StackSelection) { + appendSelector { container -> !stackSelection.isIn(container) } + } + + fun hasSpace(stackSelection: StackSelection) { + appendSelector { container -> stackSelection spaceIn container } + } + + fun noSpace(stackSelection: StackSelection) { + appendSelector { container -> !stackSelection.spaceIn(container) } + } + + fun isAccessed() { + accessScope = AccessScope.Accessed + appendSelector { it.isAccessed } + } + + fun notAccessed() { + accessScope = AccessScope.NotAccessed + appendSelector { !it.isAccessed } + } + + fun predicate(predicate: (Container) -> Boolean) { + appendSelector { predicate(it) } + } + + fun sortedWith(comparator: Comparator) { + this.comparator = comparator + } + + fun sortedWith(comparatorSupplier: () -> Comparator) { + this.comparator = comparatorSupplier() + } + + private fun appendSelector(selector: (Container) -> Boolean) { + val currentSelector = this.selector + this.selector = { currentSelector(it) && selector(it) } + } + + private fun build() = + ContainerSelection( + selector, + scope, + containersWhitelist, + loadedContainers, + containersBlacklist, + accessScope, + comparator + ) + + companion object { + fun containerSelection( + scope: ContainerSearchScope = ContainerSearchScope.Accessed, + builder: ContainerSelectionBuilder.() -> Unit + ) = ContainerSelectionBuilder(scope).apply(builder).build() + + fun ContainerSelection.mutate( + scope: ContainerSearchScope = this.scope, + builder: ContainerSelectionBuilder.() -> Unit = {} + ) = ContainerSelectionBuilder(this, scope).apply(builder).build() + } +} + +enum class AccessScope(val accessed: Boolean?) { + Both(null), + Accessed(true), + NotAccessed(false) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt new file mode 100644 index 000000000..21fa3e9f6 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt @@ -0,0 +1,432 @@ +/* + * Copyright 2026 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 . + */ + +@file:Suppress("unused") + +package com.lambda.interaction.container.selection + +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerDslMarker +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.util.EnchantmentUtils.getEnchantment +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks +import com.lambda.util.item.ItemUtils +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.component.ComponentType +import net.minecraft.component.DataComponentTypes +import net.minecraft.enchantment.Enchantment +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.item.consume.UseAction +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.tag.TagKey +import net.minecraft.screen.slot.Slot +import java.util.* + +@ContainerDslMarker +fun Item.select(count: Int = 1) = stackSelection(count) { isItem(this@select) } + +@ContainerDslMarker +fun ItemStack.select(count: Int = 1) = stackSelection(count) { isItemStack(this@select) } + +@ContainerDslMarker +fun selectStack( + count: Int = 1, + builder: StackSelectionBuilder.() -> Unit +) = stackSelection(count, builder) + +class StackSelection @ContainerDslMarker internal constructor( + val count: Int = 1, + val whitelistedSlots: Collection = emptyList(), + val blacklistedSlots: Collection = emptyList(), + val item: Item? = null, + val itemStack: ItemStack? = null, + val shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.Both, + val comparator: Comparator> = compareBy { it.stack.count }, + val selector: (ItemStack, Slot?) -> Boolean, +) { + val optimalStack = itemStack ?: item?.let { ItemStack(it, count.coerceIn(1, it.maxCount)) } + private val whitelistSet = whitelistedSlots.takeIf { it.isNotEmpty() }?.toSet() + private val blacklistSet = blacklistedSlots.takeIf { it.isNotEmpty() }?.toSet() + + @ContainerDslMarker + fun bestMatch(stacks: Iterable) = filter(stacks).firstOrNull() + + @ContainerDslMarker + fun bestMatch(slots: Iterable) = filter(slots).firstOrNull() + + @ContainerDslMarker + fun matches(stack: ItemStack): Boolean { + val matchesSelf = selector(stack, null) + return when (shulkerBoxScope.inShulkerBox) { + true -> if (stack.item in ItemUtils.SHULKER_BOXES) stack.shulkerBoxStacks.any { selector(it, null) } else false + false -> matchesSelf + null -> matchesSelf || (stack.item in ItemUtils.SHULKER_BOXES && stack.shulkerBoxStacks.any { selector(it, null) }) + } + } + + @ContainerDslMarker + fun matches(slot: Slot): Boolean { + val matchesSelf = selector(slot.stack, slot) + return when (shulkerBoxScope.inShulkerBox) { + true -> if (slot.stack.item in ItemUtils.SHULKER_BOXES) slot.stack.shulkerBoxStacks.any { selector(it, null) } else false + false -> matchesSelf + null -> matchesSelf || (slot.stack.item in ItemUtils.SHULKER_BOXES && slot.stack.shulkerBoxStacks.any { selector(it, null) }) + } + } + + @ContainerDslMarker + @JvmName("filter1") + fun filter(stacks: Iterable) = + stacks + .asSequence() + .filter(::matches) + .map { StackAndSlot(it, null) } + .sortedWith(comparator) + .map { it.stack } + .toList() + + @ContainerDslMarker + @JvmName("filter2") + fun filter(slots: Iterable) = + slots + .asSequence() + .filter { slot -> + (whitelistSet == null || slot in whitelistSet) && + (blacklistSet == null || slot !in blacklistSet) && + matches(slot) + } + .map { StackAndSlot(it.stack, it) } + .sortedWith(comparator) + .map { it.slot } + .toList() + + @ContainerDslMarker + infix fun isIn(container: Container) = + if (count <= 0) container.count(this) > 0 else container.count(this) >= count + + @ContainerDslMarker + fun isIn(vararg containers: Container) = + if (count <= 0) containers.any { it.count(this) > 0 } else containers.sumOf { it.count(this) } >= count + + @ContainerDslMarker + infix fun isIn(containers: Iterable) = + if (count <= 0) containers.any { it.count(this) > 0 } else containers.sumOf { it.count(this) } >= count + + @ContainerDslMarker + infix fun spaceIn(container: Container) = + if (count <= 0) container.spaceLeft(this) > 0 else container.spaceLeft(this) >= count + + @ContainerDslMarker + fun spaceIn(vararg containers: Container) = + if (count <= 0) containers.any { it.spaceLeft(this) > 0 } else containers.sumOf { it.spaceLeft(this) } >= count + + @ContainerDslMarker + infix fun spaceIn(containers: Iterable) = + if (count <= 0) containers.any { it.spaceLeft(this) > 0 } else containers.sumOf { it.spaceLeft(this) } >= count + + companion object { + val ANYTHING = StackSelection(0) { _, _ -> true } + val EVERYTHING = ANYTHING + val NOTHING = StackSelection(0) { _, _ -> false } + } +} + + +@ContainerDslMarker +class StackSelectionBuilder @ContainerDslMarker private constructor( + private var count: Int = 1 +) { + private val whitelistedSlots = mutableListOf() + private val blacklistedSlots = mutableListOf() + private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } + private var item: Item? = null + private var itemStack: ItemStack? = null + private var comparator: Comparator>? = null + private var shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.NotInShulkerBox + + @ContainerDslMarker + private constructor( + selection: StackSelection, + count: Int = selection.count + ) : this(count) { + this.whitelistedSlots.addAll(selection.whitelistedSlots) + this.blacklistedSlots.addAll(selection.blacklistedSlots) + this.selector = selection.selector + this.item = selection.item + this.itemStack = selection.itemStack + this.comparator = selection.comparator + this.shulkerBoxScope = selection.shulkerBoxScope + } + + fun isItem(item: Item) { + this.item = item + appendSelector { stack, _ -> stack.item == item } + } + + fun notItem(item: Item) { + appendSelector { stack, _ -> stack.item != item } + } + + inline fun isItem() { + appendSelector { stack, _ -> stack.item is T } + } + + inline fun notItem() { + appendSelector { stack, _ -> stack.item !is T } + } + + fun inverted(builder: StackSelectionBuilder.() -> Unit) { + val nested = StackSelectionBuilder().apply(builder).build() + appendSelector { stack, slot -> + if (slot != null) !nested.matches(slot) else !nested.matches(stack) + } + } + + fun isItemStack(stack: ItemStack) { + this.itemStack = stack + appendSelector { s, _ -> ItemStack.areEqual(s, stack) } + } + + fun notItemStack(stack: ItemStack) { + appendSelector { s, _ -> !ItemStack.areEqual(s, stack) } + } + + fun isItemStackByRef(stack: ItemStack) { + this.itemStack = stack + appendSelector { s, _ -> s === stack } + } + + fun notItemStackByRef(stack: ItemStack) { + appendSelector { s, _ -> s !== stack } + } + + fun alsoInShulkerBoxes() { + shulkerBoxScope = ShulkerBoxScope.Both + } + + fun onlyInShulkerBoxes() { + shulkerBoxScope = ShulkerBoxScope.InShulkerBox + } + + fun ofAnyItems(items: Iterable) { + appendSelector { stack, _ -> items.contains(stack.item) } + } + + fun noneOfItems(items: Iterable) { + appendSelector { stack, _ -> !items.contains(stack.item) } + } + + fun isShulkerBox() { + appendSelector { stack, _ -> stack.item in ItemUtils.SHULKER_BOXES } + } + + fun notShulkerBox() { + appendSelector { stack, _ -> stack.item !in ItemUtils.SHULKER_BOXES } + } + + fun withName(name: String) { + appendSelector { stack, _ -> stack.name.string == name } + } + + fun withoutName(name: String) { + appendSelector { stack, _ -> stack.name.string != name } + } + + fun ofAnyStacks(stacks: Iterable) { + appendSelector { stack, _ -> stacks.contains(stack) } + } + + fun noneOfStacks(stacks: Iterable) { + appendSelector { stack, _ -> !stacks.contains(stack) } + } + + fun isEfficientForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + if (hasEfficientTool(blockState)) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f + else true + } + } + + fun notEfficientForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + if (hasEfficientTool(blockState)) itemStack.item.getMiningSpeed(itemStack, blockState) <= 1f + else true + } + } + + private fun hasEfficientTool(blockState: BlockState) = + efficientToolCache.getOrPut(blockState) { + ItemUtils.TOOLS.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } + } + + fun isSuitableForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + !blockState.isToolRequired || itemStack.isSuitableFor(blockState) + } + } + + fun notSuitableForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + blockState.isToolRequired && !itemStack.isSuitableFor(blockState) + } + } + + fun withTag(tag: TagKey) { + appendSelector { stack, _ -> stack.isIn(tag) } + } + + fun withoutTag(tag: TagKey) { + appendSelector { stack, _ -> !stack.isIn(tag) } + } + + fun withUseAction(action: UseAction) { + appendSelector { stack, _ -> stack.useAction == action } + } + + fun withoutUseAction(action: UseAction) { + appendSelector { stack, _ -> stack.useAction != action } + } + + fun isTool() = withComponent(DataComponentTypes.TOOL) + + fun notTool() = withoutComponent(DataComponentTypes.TOOL) + + fun isFood() = withComponent(DataComponentTypes.FOOD) + + fun notFood() = withoutComponent(DataComponentTypes.FOOD) + + fun withComponent(type: ComponentType<*>) { + appendSelector { stack, _ -> stack.components.contains(type) } + } + + fun withoutComponent(type: ComponentType<*>) { + appendSelector { stack, _ -> !stack.components.contains(type) } + } + + fun isBlock(block: Block) { + item = block.asItem() + appendSelector { stack, _ -> stack.item == block.asItem() } + } + + fun notBlock(block: Block) { + appendSelector { stack, _ -> stack.item != block.asItem() } + } + + fun withDamage(damage: Int) { + appendSelector { stack, _ -> stack.damage == damage } + } + + fun withoutDamage(damage: Int) { + appendSelector { stack, _ -> stack.damage != damage } + } + + fun withEnchantment(enchantment: RegistryKey, level: Int = -1) { + appendSelector { stack, _ -> + if (level < 0) stack.getEnchantment(enchantment) > 0 + else stack.getEnchantment(enchantment) == level + } + } + + fun withoutEnchantment(enchantment: RegistryKey, level: Int = -1) { + appendSelector { stack, _ -> + if (level < 0) stack.getEnchantment(enchantment) <= 0 + else stack.getEnchantment(enchantment) != level + } + } + + fun isEmpty() { + appendSelector { stack, _ -> stack.isEmpty } + } + + fun notEmpty() { + appendSelector { stack, _ -> !stack.isEmpty } + } + + fun predicate(predicate: (ItemStack, Slot?) -> Boolean) { + appendSelector { stack, slot -> predicate(stack, slot) } + } + + fun sortedWith(newComparator: Comparator>) { + comparator = comparator?.thenComparing(newComparator) ?: newComparator + } + + fun sortedWith(comparatorSupplier: () -> Comparator>) { + sortedWith(comparatorSupplier()) + } + + fun sortedByBestContentMatch(expectedContents: List) { + val expectedFrequencies = expectedContents + .filter { !it.isEmpty } + .groupingBy { it.item } + .eachCount() + sortedWith( + compareByDescending { stackAndSlot -> + val currentFrequencies = stackAndSlot.stack.shulkerBoxStacks + .filter { !it.isEmpty } + .groupingBy { it.item } + .eachCount() + expectedFrequencies.asSequence().fold(0) { acc, (item, count) -> + acc + minOf(count, currentFrequencies[item] ?: 0) + } + } + ) + } + + @PublishedApi + internal fun appendSelector(selector: (ItemStack, Slot?) -> Boolean) { + val currentSelector = this.selector + this.selector = { stack, slot -> + currentSelector(stack, slot) && selector(stack, slot) + } + } + + private fun build() = + StackSelection( + count, + whitelistedSlots, + blacklistedSlots, + item, + itemStack, + shulkerBoxScope, + comparator ?: compareBy { it.stack.count }, + selector + ) + + companion object { + private val efficientToolCache = Collections.synchronizedMap(mutableMapOf()) + + fun stackSelection( + count: Int = 1, + builder: StackSelectionBuilder.() -> Unit + ) = StackSelectionBuilder(count).apply(builder).build() + + fun StackSelection.mutate( + count: Int = this.count, + builder: StackSelectionBuilder.() -> Unit = {} + ) = StackSelectionBuilder(this, count).apply(builder).build() + } +} + +data class StackAndSlot(val stack: ItemStack, val slot: T) + +enum class ShulkerBoxScope(val inShulkerBox: Boolean?) { + Both(null), + InShulkerBox(true), + NotInShulkerBox(false) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt similarity index 84% rename from src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt index b1d2b7a44..0c64982fb 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.handler import com.lambda.context.Automated import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler +import com.lambda.interaction.manager.ActionInfo import com.lambda.util.collections.LimitedDecayQueue /** @@ -51,8 +51,10 @@ abstract class PostActionHandler { pendingInteractionsList.remove(context) } - fun Automated.setPendingConfigs() { - BrokenBlockHandler.pendingActions.setSizeLimit(buildConfig.maxPendingActions) - BrokenBlockHandler.pendingActions.setDecayTime(buildConfig.actionTimeout * 50L) - } + context(automated: Automated) + fun setPendingConfigs() = + with(automated) { + pendingActions.setSizeLimit(buildConfig.maxPendingActions) + pendingActions.setDecayTime(buildConfig.actionTimeout * 50L) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handlers/BaritoneHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/handlers/BaritoneHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt index ff939f26c..85fe0978f 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/BaritoneHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import baritone.api.BaritoneAPI import baritone.api.IBaritone @@ -28,7 +28,7 @@ import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig import com.lambda.config.blocks.RotationSettings import com.lambda.config.categories.LambdaCategory -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.Automated import com.lambda.util.BlockUtils.blockPos import net.minecraft.util.BlockMirror diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt new file mode 100644 index 000000000..3a0519983 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -0,0 +1,448 @@ +/* + * Copyright 2026 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.handler.handlers + +import com.lambda.Lambda +import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext +import com.lambda.core.Loadable +import com.lambda.event.events.InventoryEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.WorldEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerDslMarker +import com.lambda.interaction.container.ContainerSerializer +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.container.containers.external.ChestContainer +import com.lambda.interaction.container.containers.external.DoubleChestContainer +import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.interaction.container.containers.external.PlacedShulkerBoxContainer +import com.lambda.interaction.container.containers.external.ShulkerBoxContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.util.BlockUtils.blockEntity +import com.lambda.util.FolderRegistry +import com.lambda.util.ReflectionUtils.getInstances +import com.lambda.util.extension.containerStacks +import com.lambda.util.item.ItemStackUtils.count +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks +import com.lambda.util.item.ItemUtils.SHULKER_BOXES +import com.lambda.util.player.SlotUtils.typeSafe +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.block.ChestBlock +import net.minecraft.block.ShulkerBoxBlock +import net.minecraft.block.entity.BlockEntity +import net.minecraft.block.entity.ChestBlockEntity +import net.minecraft.block.entity.EnderChestBlockEntity +import net.minecraft.block.entity.ShulkerBoxBlockEntity +import net.minecraft.block.enums.ChestType +import net.minecraft.inventory.Inventory +import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.ScreenHandlerType +import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos +import java.nio.file.Files + +@Suppress("unused") +object ContainerHandler : Loadable { + val compileContainers = getInstances() + var accessedPlacedContainer: PlacedContainer? = null + private set + + var lastInteractedBlockEntity: BlockEntity? = null + var pendingInteractedBlockEntity: BlockEntity? = null + + override fun load(): String { + ContainerSerializer.loadEnderChest() + return "Loaded ${compileContainers.size} containers" + } + + init { + listen { event -> + val packet = event.packet as? PlayerInteractBlockC2SPacket ?: return@listen + val entity = blockEntity(packet.blockHitResult.blockPos) + pendingInteractedBlockEntity = entity + lastInteractedBlockEntity = entity + } + + listen { event -> + val sh = event.screenHandler + onContainerUpdate(sh) + if (sh.syncId != 0 && pendingInteractedBlockEntity == null) { + lastInteractedBlockEntity = null + } + accessedPlacedContainer = null + } + + listen { + if (it.screenHandler.syncId != 0) { + pendingInteractedBlockEntity = null + } + } + + listen { onContainerUpdate() } + listen { onContainerUpdate() } + + listen { event -> + val oldBlock = event.oldState.block + if (oldBlock is ChestBlock || oldBlock is ShulkerBoxBlock) { + if (event.newState.block != oldBlock) { + ContainerSerializer.removeContainer(event.pos) + } + } + } + + listen { event -> + val chunk = event.chunk + val prefix = "${chunk.pos.x}_${chunk.pos.z}_" + + try { + val dir = FolderRegistry.containers + if (Files.exists(dir)) { + Files.newDirectoryStream(dir, "$prefix*.json").use { stream -> + stream.forEach { path -> + val parts = path.fileName.toString().removeSuffix(".json").split('_') + if (parts.size == 5) { + val x = parts[2].toIntOrNull() ?: return@forEach + val y = parts[3].toIntOrNull() ?: return@forEach + val z = parts[4].toIntOrNull() ?: return@forEach + val pos = BlockPos(x, y, z) + val entity = chunk.blockEntities[pos] + if (entity !is ChestBlockEntity && entity !is ShulkerBoxBlockEntity) { + Files.deleteIfExists(path) + } + } + } + } + } + } catch (e: Exception) { + Lambda.LOG.warn("Failed to clean up ghost containers for chunk ${chunk.pos}", e) + } + } + } + + private fun SafeContext.onContainerUpdate(sh: ScreenHandler = player.currentScreenHandler) { + val blockEntity = lastInteractedBlockEntity + if (blockEntity != null) updatePlacedContainer(sh, blockEntity) + } + + private fun updatePlacedContainer( + sh: ScreenHandler, + blockEntity: BlockEntity + ) { + val pos = blockEntity.pos + when (blockEntity) { + is EnderChestBlockEntity -> { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return + EnderChestContainer.apply { + update(sh.containerStacks) + scanContainerContents() + } + ContainerSerializer.saveEnderChest() + } + + is ChestBlockEntity -> { + val state = blockEntity.cachedState + if (state.block !is ChestBlock) return + + val stacks = blockEntity.stacks() + val chestType = state.get(Properties.CHEST_TYPE) ?: return + + if (chestType == ChestType.SINGLE) { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return + (accessedPlacedContainer as? ChestContainer) + ?.update(stacks) + ?: run { accessedPlacedContainer = ChestContainer(pos, stacks) } + } else { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X6) return + val facing = state.get(Properties.HORIZONTAL_FACING) ?: return + val otherPos = + when (chestType) { + ChestType.LEFT -> pos.offset(facing.rotateYClockwise()) + ChestType.RIGHT -> pos.offset(facing.rotateYCounterclockwise()) + } + val leftPos = if (chestType == ChestType.LEFT) pos else otherPos + val rightPos = if (chestType == ChestType.RIGHT) pos else otherPos + + val existing = accessedPlacedContainer as? DoubleChestContainer + if (existing != null && existing.pos == pos) { + existing.update(stacks) + } else { + accessedPlacedContainer = DoubleChestContainer(pos, leftPos, rightPos, stacks) + } + } + + accessedPlacedContainer?.let { + it.scanContainerContents() + ContainerSerializer.saveContainer(it) + } + } + + is ShulkerBoxBlockEntity -> { + if (sh.typeSafe != ScreenHandlerType.SHULKER_BOX) return + val stacks = blockEntity.stacks() + (accessedPlacedContainer as? PlacedShulkerBoxContainer) + ?.update(stacks) + ?: run { + accessedPlacedContainer = PlacedShulkerBoxContainer(pos, blockEntity.cachedState.block, null, stacks) + } + + accessedPlacedContainer?.let { + it.scanContainerContents() + ContainerSerializer.saveContainer(it) + } + } + } + } + + private fun Container.scanContainerContents() { + slots.forEach { slot -> + val stack = slot.stack + val index = slot.index + if (stack.item in SHULKER_BOXES) { + storedContainers[index] = + ShulkerBoxContainer( + stack.name.string, + stack.item, + stack.shulkerBoxStacks, + this, + index + ).also { it.scanContainerContents() } + } else storedContainers.remove(index) + } + } + + private fun Inventory.stacks() = iterator().asSequence().toList() +} + +@ContainerDslMarker +context(_: AutomatedSafeContext) +fun StackSelection.move( + fromSelection: ContainerSelection = ContainerSelection.ACCESSED, + toSelection: ContainerSelection = ContainerSelection.ACCESSED, + replaceSelection: StackSelection = StackSelection.ANYTHING +): Boolean { + val fromContainer = + findContainer( + containerSelection { + matches(fromSelection) + hasStack(this@move) + isAccessed() + } + ) + ?: return false + + val toContainer = + findContainer( + containerSelection { + matches(toSelection) + hasSpace(this@move) + isAccessed() + } + ) + ?: return false + + val (fromSlot, toSlot) = fromContainer.findMoveSlots(this, toContainer, replaceSelection) + if (fromSlot == null || toSlot == null) return false + + return fromContainer.swap(fromSlot, toSlot, toContainer) +} + +@ContainerDslMarker +context(automated: Automated) +fun findSlot( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findSlots(stackSelection, containerSelection, sorted).firstOrNull() + +@ContainerDslMarker +context(automated: Automated) +fun findSlots( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Sequence = + findContainers(containerSelection, sorted) + .mapNotNull { container -> + val slots = stackSelection.filter(container.slots) + if (slots.count >= stackSelection.count) slots + else null + } + .flatten() + +@ContainerDslMarker +context(automated: Automated) +fun findStack( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findStacks(stackSelection, containerSelection, sorted).firstOrNull() + +@ContainerDslMarker +context(automated: Automated) +fun findStacks( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Sequence = + findContainers(containerSelection, sorted) + .mapNotNull { container -> + val stacks = stackSelection.filter(container.stacks) + if (stacks.count >= stackSelection.count) stacks + else null + } + .flatten() + +@ContainerDslMarker +context(_: Automated) +fun findContainer( + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findContainers(containerSelection, sorted).firstOrNull() + +@ContainerDslMarker +context(automated: Automated) +fun findContainer( + stackSelection: StackSelection, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Container? = findContainers(stackSelection, containerSelection, sorted).firstOrNull() + +@ContainerDslMarker +context(automated: Automated) +fun findContainers( + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Sequence { + val containers = searchContainers(containerSelection) + return if (sorted) { + containerSelection.filter(containers.asIterable()).asSequence() + } else { + containers.filter { containerSelection.matches(it) } + } +} + +@ContainerDslMarker +context(automated: Automated) +fun findContainers( + stackSelection: StackSelection, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Sequence = + findContainers( + containerSelection(containerSelection.scope) { + matches(containerSelection) + hasStack(stackSelection) + }, + sorted + ) + +@ContainerDslMarker +context(automated: Automated) +fun findContainerWithSpace( + stackSelection: StackSelection, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Container? = findContainersWithSpace(stackSelection, containerSelection, sorted).firstOrNull() + +@ContainerDslMarker +context(automated: Automated) +fun findContainersWithSpace( + stackSelection: StackSelection, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Sequence = + findContainers( + containerSelection(containerSelection.scope) { + matches(containerSelection) + hasSpace(stackSelection) + }, + sorted + ) + +@ContainerDslMarker +context(_: Automated) +fun findContainerWithDisposable( + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findContainersWithDisposable(containerSelection, sorted).firstOrNull() + +@ContainerDslMarker +context(automated: Automated) +fun findContainersWithDisposable( + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +): Sequence = + with(automated) { + findContainers( + containerSelection(containerSelection.scope) { + matches(containerSelection) + hasStack( + stackSelection(1) { ofAnyItems(inventoryConfig.disposables) } + ) + }, + sorted + ) + } +context(automated: Automated) +private fun searchContainers( + selection: ContainerSelection +): Sequence { + fun Container.allNested(): Sequence = + sequence { + yield(this@allNested) + storedContainers.values.forEach { nested -> + yieldAll(nested.allNested()) + } + } + + val compiled = ContainerHandler.compileContainers.asSequence() + val placedSeq = sequenceOf(ContainerHandler.accessedPlacedContainer).filterNotNull() + + val baseContainers = + (when (selection.scope) { + ContainerSearchScope.Player -> compiled - EnderChestContainer + ContainerSearchScope.Compiled -> compiled + ContainerSearchScope.Loaded -> compiled + placedSeq + selection.loadedContainers + ContainerSearchScope.Accessed -> placedSeq + compiled.filter { it.isAccessed } + ContainerSearchScope.All -> compiled + placedSeq + + ContainerSerializer.serializedContainers.filter { diskContainer -> + diskContainer.pos != ContainerHandler.accessedPlacedContainer?.pos + } + } + selection.containersWhitelist.asSequence()).distinct() + + return baseContainers + .flatMap { it.allNested() } + .filter { automated.inventoryConfig.containerSelection.matches(it) } +} + +enum class ContainerSearchScope { + Accessed, + Compiled, + Loaded, + Player, + All +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendHandler.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/FriendHandler.kt index 9afa8a5d3..b2c02b37e 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.Lambda import com.lambda.command.CommandRegistry diff --git a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt similarity index 70% rename from src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt index c519e2774..db54e5943 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt @@ -15,12 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.modules.movement.elytrafly.ElytraFly import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode @@ -30,7 +32,6 @@ import com.lambda.module.modules.player.AutoElytraSwap.glideDelay import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece import com.lambda.util.player.PlayerUtils.canStartGliding -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.component.DataComponentTypes import net.minecraft.enchantment.Enchantments import net.minecraft.entity.attribute.EntityAttributes @@ -40,37 +41,41 @@ import net.minecraft.registry.tag.ItemTags object GlideHandler { val ELYTRA_SELECTION = - selectStack { - sortByDescending { - it.getEnchantment(Enchantments.UNBREAKING) - }.thenByDescending { - it.getEnchantment(Enchantments.MENDING) - } + stackSelection { isItem(Items.ELYTRA) - .and { it.damage < it.maxDamage } + predicate { stack, _ -> stack.damage < stack.maxDamage } + sortedWith { + compareByDescending> { + it.stack.getEnchantment(Enchantments.UNBREAKING) + }.thenByDescending { + it.stack.getEnchantment(Enchantments.MENDING) + } + } } val CHESTPLATE_SELECTION = - selectStack { - sortByDescending { - it.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers - ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } - ?.modifier?.value - ?: 0.0 - }.thenByDescending { - it.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers - ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } - ?.modifier?.value - ?: 0.0 - }.thenByDescending { - it.getEnchantment(Enchantments.UNBREAKING) - }.thenByDescending { - it.getEnchantment(Enchantments.MENDING) + stackSelection { + withTag(ItemTags.CHEST_ARMOR) + notItem(Items.ELYTRA) + predicate { stack, _ -> stack.damage < stack.maxDamage } + sortedWith { + compareByDescending> { + val modifiers = it.stack.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + ?: it.stack.item.components.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + modifiers?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } + ?.modifier?.value ?: 0.0 + }.thenByDescending { + val modifiers = it.stack.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + ?: it.stack.item.components.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + modifiers?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } + ?.modifier?.value ?: 0.0 + }.thenByDescending { + it.stack.getEnchantment(Enchantments.UNBREAKING) + }.thenByDescending { + it.stack.getEnchantment(Enchantments.MENDING) + } } - hasTag(ItemTags.CHEST_ARMOR) - .and { it.item != Items.ELYTRA } - .and { it.damage < it.maxDamage } } @JvmStatic val overridingGlide @@ -84,7 +89,10 @@ object GlideHandler { init { listen({ -1000 }) { - if (!player.isGliding && pendingGlides.isEmpty()) AutoElytraSwap.restore() + if (pendingGlides.isEmpty()) { + if (swapped && !player.isGliding) AutoElytraSwap.restore() + return@listen + } tickPendingGlides() } @@ -163,9 +171,12 @@ object GlideHandler { val fakeFly = ElytraFly.isEnabled && ElytraFly.fakeFly val canGlideAlready = player.canGlideWithChestPiece() != fakeFly if (!autoSwapChecking || canGlideAlready) return canGlideAlready - return player.hotbarAndInventorySlots.let { slots -> - if (fakeFly) CHESTPLATE_SELECTION.filterSlots(slots).isNotEmpty() - else ELYTRA_SELECTION.filterSlots(slots).isNotEmpty() + val selection = if (fakeFly) CHESTPLATE_SELECTION else ELYTRA_SELECTION + return with(ElytraFly) { + findContainer( + containerSelection { hasStack(selection) }, + false + ) != null } } diff --git a/src/main/kotlin/com/lambda/interaction/handlers/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/InteractedBlockHandler.kt similarity index 84% rename from src/main/kotlin/com/lambda/interaction/handlers/interacting/InteractedBlockHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/InteractedBlockHandler.kt index b1c1db359..829ce02ed 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/InteractedBlockHandler.kt @@ -15,25 +15,25 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.interacting +package com.lambda.interaction.handler.handlers import com.lambda.config.automation.AutomationConfig import com.lambda.config.blocks.InteractConfig import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.PostActionHandler -import com.lambda.interaction.managers.interacting.InteractInfo -import com.lambda.interaction.managers.interacting.InteractManager.placeSound +import com.lambda.interaction.handler.PostActionHandler +import com.lambda.interaction.manager.managers.interacting.InteractInfo +import com.lambda.interaction.manager.managers.interacting.InteractManager.placeSound import com.lambda.module.modules.client.Client import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.matches import com.lambda.util.CommunicationUtils.warn import com.lambda.util.collections.LimitedDecayQueue -object InteractedBlockHandler : PostActionHandler() { +internal object InteractedBlockHandler : PostActionHandler() { override val pendingActions = LimitedDecayQueue( - AutomationConfig.DEFAULT.buildConfig.maxPendingActions, - AutomationConfig.DEFAULT.buildConfig.actionTimeout * 50L + AutomationConfig.Companion.DEFAULT.buildConfig.maxPendingActions, + AutomationConfig.Companion.DEFAULT.buildConfig.actionTimeout * 50L ) { if (Client.verboseDebug) warn("${it::class.simpleName} at ${it.context.blockPos.toShortString()} timed out") if (it.interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.AwaitThenPlace) { diff --git a/src/main/kotlin/com/lambda/interaction/handlers/packet/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/handlers/packet/PacketLimitHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt index aa11a5d9e..8820cd659 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/packet/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.packet +package com.lambda.interaction.handler.handlers import com.lambda.config.blocks.BuildConfig import com.lambda.context.Automated @@ -26,7 +26,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.TimeSource -object PacketLimitHandler { +internal object PacketLimitHandler { private val packetLimitMap = PacketType.entries.associateWith { LimitHandler() } init { diff --git a/src/main/kotlin/com/lambda/interaction/handlers/PingHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/PingHandler.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/handlers/PingHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/PingHandler.kt index 6913519a0..e8720a75e 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/PingHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/PingHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.core.Loadable import com.lambda.event.events.PacketEvent diff --git a/src/main/kotlin/com/lambda/interaction/handlers/TimerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/TimerHandler.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/handlers/TimerHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/TimerHandler.kt index 0bd755ea9..d9d620358 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/TimerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/TimerHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.core.Loadable import com.lambda.event.EventFlow.post diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt similarity index 92% rename from src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt index 6279c1039..bd1241726 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.breaking +package com.lambda.interaction.handler.handlers.breaking import com.lambda.config.automation.AutomationConfig.Companion.DEFAULT import com.lambda.config.blocks.BreakConfig.BreakConfirmationMode @@ -24,11 +24,11 @@ import com.lambda.event.events.EntityEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry -import com.lambda.interaction.handlers.breaking.RebreakHandler.reBreak -import com.lambda.interaction.managers.PostActionHandler -import com.lambda.interaction.managers.breaking.BreakInfo -import com.lambda.interaction.managers.breaking.BreakManager.lastPosStarted -import com.lambda.interaction.managers.breaking.BreakManager.matchesBlockItem +import com.lambda.interaction.handler.PostActionHandler +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak +import com.lambda.interaction.manager.managers.breaking.BreakInfo +import com.lambda.interaction.manager.managers.breaking.BreakManager.lastPosStarted +import com.lambda.interaction.manager.managers.breaking.BreakManager.matchesBlockItem import com.lambda.module.modules.client.Client import com.lambda.module.modules.client.Client.verboseDebug import com.lambda.threading.runGameScheduled @@ -51,7 +51,7 @@ import net.minecraft.util.math.ChunkSectionPos * * @see com.lambda.interaction.managers.breaking.BreakManager */ -object BrokenBlockHandler : PostActionHandler() { +internal object BrokenBlockHandler : PostActionHandler() { override val pendingActions = LimitedDecayQueue( DEFAULT.buildConfig.maxPendingActions, DEFAULT.buildConfig.actionTimeout * 50L ) { info -> diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt similarity index 83% rename from src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt index fcf34a1a9..40f04a2bf 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.breaking +package com.lambda.interaction.handler.handlers.breaking import com.lambda.config.blocks.BreakConfig import com.lambda.context.SafeContext @@ -24,23 +24,23 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handlers.breaking.RebreakHandler.reBreak -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.breaking.BreakInfo -import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta -import com.lambda.interaction.managers.breaking.BreakRequest +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak +import com.lambda.interaction.manager.managers.breaking.BreakInfo +import com.lambda.interaction.manager.managers.breaking.BreakManager +import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta +import com.lambda.interaction.manager.managers.breaking.BreakRequest import com.lambda.threading.runSafeAutomated import com.lambda.util.player.PlayerUtils.swingHand import net.minecraft.util.Hand /** - * Designed to track the latest primary-broken [com.lambda.interaction.managers.breaking.BreakInfo] in order to exploit a flaw in Minecraft's code that allows + * 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 { +internal object RebreakHandler { var reBreak: BreakInfo? = null init { @@ -134,12 +134,12 @@ object RebreakHandler { RebreakResult.StillBreaking(reBreak) } } +} - enum class RebreakPotential { - Instant, - PartialProgress, - None; +enum class RebreakPotential { + Instant, + PartialProgress, + None; - fun isPossible() = this != None - } + fun isPossible() = this != None } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakResult.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt similarity index 83% rename from src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakResult.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt index 27dc43bbb..67982cf31 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.breaking +package com.lambda.interaction.handler.handlers.breaking -import com.lambda.interaction.managers.breaking.BreakInfo +import com.lambda.interaction.manager.managers.breaking.BreakInfo -sealed class RebreakResult { +internal sealed class RebreakResult { data object Ignored : RebreakResult() data object Rebroke : RebreakResult() diff --git a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt deleted file mode 100644 index e7a083287..000000000 --- a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2026 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.handlers - -import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext -import com.lambda.core.Loadable -import com.lambda.event.events.InventoryEvent -import com.lambda.event.events.PlayerEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.ContainerSelection -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.container.containers.ChestContainer -import com.lambda.interaction.material.container.containers.EnderChestContainer -import com.lambda.util.BlockUtils.blockEntity -import com.lambda.util.ReflectionUtils -import com.lambda.util.extension.containerStacks -import net.minecraft.block.entity.BlockEntity -import net.minecraft.block.entity.ChestBlockEntity -import net.minecraft.block.entity.EnderChestBlockEntity -import net.minecraft.screen.GenericContainerScreenHandler -import net.minecraft.screen.ScreenHandlerType -import net.minecraft.screen.slot.Slot - -// ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. -@Suppress("unused") -object ContainerHandler : Loadable { - private val containers: List - get() = compileContainers + runtimeContainers - - private val compileContainers = ReflectionUtils.getInstances() - private val runtimeContainers = mutableSetOf() - - var lastInteractedBlockEntity: BlockEntity? = null - - override fun load() = "Loaded ${compileContainers.size} containers" - - init { - listen { - lastInteractedBlockEntity = blockEntity(it.blockHitResult.blockPos) - } - - listen { event -> - if (event.screenHandler !is GenericContainerScreenHandler) return@listen - - val handler = event.screenHandler - - when (val block = lastInteractedBlockEntity) { - is EnderChestBlockEntity -> { - if (handler.type != ScreenHandlerType.GENERIC_9X3) return@listen - - EnderChestContainer.update(handler.containerStacks) - } - - is ChestBlockEntity -> { - // ToDo: Handle double chests and single chests - if (handler.type != ScreenHandlerType.GENERIC_9X6) return@listen - val stacks = handler.containerStacks - - containers - .filterIsInstance() - .find { - it.blockPos == block.pos - }?.update(stacks) ?: runtimeContainers.add(ChestContainer(stacks, block.pos)) - } - } - lastInteractedBlockEntity = null - } - } - - context(_: SafeContext) - fun containers() = containers.flatMap { setOf(it) + it.shulkerContainer }.sorted() - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.transfer(destination: MaterialContainer) = - with(automatedSafeContext) { - findContainerWithMaterial( - inventoryConfig.containerSelection - )?.transfer(this@transfer, destination) - ?: false - } - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.transferByTask(destination: MaterialContainer) = - with(automatedSafeContext) { - findContainerWithMaterial()?.transferByTask(this@transferByTask, destination) - } - - context(_: SafeContext) - fun findContainer( - block: (MaterialContainer) -> Boolean, - ): MaterialContainer? = containers().find(block) - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findContainerWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): MaterialContainer? = findContainersWithMaterial(containerSelection).firstOrNull() - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findContainersWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection, - ): List = - containers() - .filter { containerSelection.matches(it) } - .filter { it.materialAvailable(this) >= count } - .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.materialComparator(this)) - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findContainerWithSpace( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): MaterialContainer? = findContainersWithSpace(containerSelection).firstOrNull() - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findContainersWithSpace( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): List = - containers() - .filter { containerSelection.matches(it) } - .filter { it.spaceAvailable(this) >= count } - .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.spaceComparator(this)) - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findSlotWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ) = findSlotsWithMaterial(containerSelection).firstOrNull() - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findSlotsWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): List = - findContainersWithMaterial(containerSelection) - .flatMap { filterSlots(it.slots) } - - - context(automatedSafeContext: AutomatedSafeContext) - fun findDisposable() = containers().find { container -> - automatedSafeContext.inventoryConfig.disposables.any { container.materialAvailable(it.asItem().select()) > 0 } - } - - class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/ActionInfo.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/ActionInfo.kt index 6d457e8d3..dfaf5548e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/ActionInfo.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.interaction.construction.simulation.context.BuildContext diff --git a/src/main/kotlin/com/lambda/interaction/managers/Manager.kt b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/managers/Manager.kt rename to src/main/kotlin/com/lambda/interaction/manager/Manager.kt index 7da159c1f..cab1aa21f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Manager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext @@ -25,7 +25,7 @@ import com.lambda.event.Event import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.ManagerUtils.accumulatedManagerPriority +import com.lambda.interaction.manager.ManagerUtils.accumulatedManagerPriority import com.lambda.threading.runSafeAutomated import kotlin.reflect.KClass diff --git a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt b/src/main/kotlin/com/lambda/interaction/manager/ManagerUtils.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt rename to src/main/kotlin/com/lambda/interaction/manager/ManagerUtils.kt index 9a5f193bd..ae2b3589c 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/ManagerUtils.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.util.ReflectionUtils.getInstances import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt b/src/main/kotlin/com/lambda/interaction/manager/PositionBlocking.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt rename to src/main/kotlin/com/lambda/interaction/manager/PositionBlocking.kt index 14c3bf240..e10a9d2ba 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/PositionBlocking.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/managers/Request.kt b/src/main/kotlin/com/lambda/interaction/manager/Request.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/managers/Request.kt rename to src/main/kotlin/com/lambda/interaction/manager/Request.kt index 61fa20be2..9bdcd58c6 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Request.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/Request.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.context.Automated import com.lambda.event.events.TickEvent diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt similarity index 88% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt index 9a8367812..f789d0253 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt @@ -15,19 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.config.blocks.BreakConfig import com.lambda.config.blocks.BreakConfig.BreakMode import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handlers.breaking.RebreakHandler -import com.lambda.interaction.managers.ActionInfo -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.RedundantSecondary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta +import com.lambda.interaction.handler.handlers.breaking.RebreakPotential +import com.lambda.interaction.manager.ActionInfo +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.RedundantSecondary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta import com.lambda.threading.runSafeAutomated import com.lambda.util.Describable import com.lambda.util.NamedEnum @@ -51,7 +51,7 @@ data class BreakInfo( // Pre Processing var shouldProgress = false - var rebreakPotential = RebreakHandler.RebreakPotential.None + var rebreakPotential = RebreakPotential.None var swapInfo = SwapInfo.EMPTY var swapStack: ItemStack = ItemStack.EMPTY @@ -163,7 +163,5 @@ data class BreakInfo( } } - fun getBreakDelay() = if (!bypassedDelay) 6 else breakConfig.breakDelay - override fun toString() = "$type, ${context.cachedState}, ${context.blockPos}" } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt similarity index 88% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt index edc52499e..eabc193ee 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.config.blocks.BreakConfig import com.lambda.config.blocks.BreakConfig.BreakConfirmationMode @@ -34,47 +34,47 @@ import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.pendingActions -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.setPendingConfigs -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.startPending -import com.lambda.interaction.handlers.breaking.RebreakHandler -import com.lambda.interaction.handlers.breaking.RebreakHandler.getRebreakPotential -import com.lambda.interaction.handlers.breaking.RebreakResult -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.managers.PositionBlocking -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.RedundantSecondary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary -import com.lambda.interaction.managers.breaking.BreakManager.abandonedBreak -import com.lambda.interaction.managers.breaking.BreakManager.activeInfos -import com.lambda.interaction.managers.breaking.BreakManager.activeRequest -import com.lambda.interaction.managers.breaking.BreakManager.breakInfos -import com.lambda.interaction.managers.breaking.BreakManager.breaks -import com.lambda.interaction.managers.breaking.BreakManager.canAccept -import com.lambda.interaction.managers.breaking.BreakManager.checkForCancels -import com.lambda.interaction.managers.breaking.BreakManager.handlePreProcessing -import com.lambda.interaction.managers.breaking.BreakManager.hotbarRequest -import com.lambda.interaction.managers.breaking.BreakManager.initNewBreak -import com.lambda.interaction.managers.breaking.BreakManager.maxBreaksThisTick -import com.lambda.interaction.managers.breaking.BreakManager.nullify -import com.lambda.interaction.managers.breaking.BreakManager.populateFrom -import com.lambda.interaction.managers.breaking.BreakManager.processNewBreak -import com.lambda.interaction.managers.breaking.BreakManager.processRequest -import com.lambda.interaction.managers.breaking.BreakManager.rotationRequest -import com.lambda.interaction.managers.breaking.BreakManager.simulateAbandoned -import com.lambda.interaction.managers.breaking.BreakManager.updateBreakProgress -import com.lambda.interaction.managers.breaking.BreakManager.updatePreProcessing -import com.lambda.interaction.managers.breaking.SwapInfo.Companion.getSwapInfo -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.interacting.InteractManager -import com.lambda.interaction.managers.rotating.RotationRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.pendingActions +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.startPending +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.getRebreakPotential +import com.lambda.interaction.handler.handlers.breaking.RebreakResult +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.ManagerUtils.isPosBlocked +import com.lambda.interaction.manager.PositionBlocking +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.RedundantSecondary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.manager.managers.breaking.BreakManager.abandonedBreak +import com.lambda.interaction.manager.managers.breaking.BreakManager.activeInfos +import com.lambda.interaction.manager.managers.breaking.BreakManager.activeRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.breakInfos +import com.lambda.interaction.manager.managers.breaking.BreakManager.breaks +import com.lambda.interaction.manager.managers.breaking.BreakManager.canAccept +import com.lambda.interaction.manager.managers.breaking.BreakManager.checkForCancels +import com.lambda.interaction.manager.managers.breaking.BreakManager.handlePreProcessing +import com.lambda.interaction.manager.managers.breaking.BreakManager.hotbarRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.initNewBreak +import com.lambda.interaction.manager.managers.breaking.BreakManager.maxBreaksThisTick +import com.lambda.interaction.manager.managers.breaking.BreakManager.nullify +import com.lambda.interaction.manager.managers.breaking.BreakManager.populateFrom +import com.lambda.interaction.manager.managers.breaking.BreakManager.processNewBreak +import com.lambda.interaction.manager.managers.breaking.BreakManager.processRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.rotationRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.simulateAbandoned +import com.lambda.interaction.manager.managers.breaking.BreakManager.updateBreakProgress +import com.lambda.interaction.manager.managers.breaking.BreakManager.updatePreProcessing +import com.lambda.interaction.manager.managers.breaking.SwapInfo.Companion.getSwapInfo +import com.lambda.interaction.manager.managers.hotbar.HotbarRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.interacting.InteractManager +import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated @@ -139,7 +139,7 @@ object BreakManager : Manager( .lastOrNull { it.breakConfig.doubleBreak || it.type == Secondary }?.context?.itemSelection - ?: StackSelection.EVERYTHING.select() + ?: StackSelection.ANYTHING override val blockedPositions get() = activeInfos.map { it.context.blockPos } + pendingActions.map { it.context.blockPos } @@ -440,12 +440,10 @@ object BreakManager : Manager( 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) + request.hotbarRequest(context.hotbarIndex) { + keepTicks(request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks)) + swapPause(request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1)) + }.submit(false) } return @@ -469,7 +467,8 @@ object BreakManager : Manager( if (breaksThisTick >= maxBreaksThisTick) return false breaks.forEach { ctx -> - if (!currentStackSelection.filterStack(player.inventory.getStack(ctx.hotbarIndex))) return@forEach + if (breaksThisTick >= maxBreaksThisTick) return false + if (!currentStackSelection.matches(player.inventory.getStack(ctx.hotbarIndex))) return@forEach initNewBreak(ctx, request) ?: return false breaks.remove(ctx) @@ -529,7 +528,7 @@ object BreakManager : Manager( } primaryBreak = breakInfo - setPendingConfigs() + BrokenBlockHandler.setPendingConfigs() return primaryBreak } diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt new file mode 100644 index 000000000..f52910054 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt @@ -0,0 +1,186 @@ +/* + * Copyright 2026 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.manager.managers.breaking + +import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.simulation.context.BreakContext +import com.lambda.interaction.construction.simulation.context.BuildContext +import com.lambda.interaction.construction.simulation.result.BuildResult +import com.lambda.interaction.construction.simulation.result.Dependent +import com.lambda.interaction.construction.simulation.result.results.BreakResult +import com.lambda.interaction.construction.simulation.sim +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.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 + +@JvmName("breakRequest1") +fun AutomatedSafeContext.breakRequest( + positions: Collection, + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null +) = positions + .associateWith { TargetState.Empty } + .sim() + .breakRequest(pendingInteractions, nowOrNothing, builder) + +@JvmName("breakRequest2") +context(automated: Automated) +fun Collection.breakRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null +) = asSequence() + .breakRequest(pendingInteractions, nowOrNothing, builder) + +@JvmName("breakRequest3") +context(automated: Automated) +fun Sequence.breakRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null +) = map { if (it is Dependent) it.lastDependency else it } + .filterIsInstance() + .sorted() + .map { it.context } + .toSet() + .takeIf { it.isNotEmpty() } + ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } + +/** + * 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 [BreakRequestMarker] 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( + val contexts: Collection, + val pendingInteractions: MutableCollection, + override val nowOrNothing: Boolean, + private val automated: Automated, + val onStart: (SafeContext.(BlockPos) -> Unit)?, + val onUpdate: (SafeContext.(BlockPos) -> Unit)?, + val onStop: (SafeContext.(BlockPos) -> Unit)?, + val onCancel: (SafeContext.(BlockPos) -> Unit)?, + val onItemDrop: (SafeContext.(ItemEntity) -> Unit)?, + val onReBreakStart: (SafeContext.(BlockPos) -> Unit)?, + val onReBreak: (SafeContext.(BlockPos) -> Unit)? +) : Request(), Automated by automated { + override val requestId = ++requestCount + override val tickStageMask get() = breakConfig.tickStageMask + + override val done: Boolean + get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true + + @BreakRequestMarker + override fun submit(queueIfMismatchedStage: Boolean) = + BreakManager.request(this, queueIfMismatchedStage) + + companion object { + var requestCount = 0 + private set + } +} + +@DslMarker +annotation class BreakRequestMarker + +@BreakRequestMarker +class BreakRequestBuilder private constructor( + private val contexts: Collection, + private val pendingInteractions: MutableCollection, + private val nowOrNothing: Boolean, + private val automated: Automated +) { + private var onStart: (SafeContext.(BlockPos) -> Unit)? = null + private var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null + private var onStop: (SafeContext.(BlockPos) -> Unit)? = null + private var onCancel: (SafeContext.(BlockPos) -> Unit)? = null + private var onItemDrop: (SafeContext.(ItemEntity) -> Unit)? = null + private var onReBreakStart: (SafeContext.(BlockPos) -> Unit)? = null + private var onReBreak: (SafeContext.(BlockPos) -> Unit)? = null + + fun onStart(callback: SafeContext.(BlockPos) -> Unit) { + onStart = callback + } + + fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { + onUpdate = callback + } + + fun onStop(callback: SafeContext.(BlockPos) -> Unit) { + onStop = callback + } + + fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { + onCancel = callback + } + + fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { + onItemDrop = callback + } + + fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { + onReBreakStart = callback + } + + fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { + onReBreak = callback + } + + private fun build() = + BreakRequest( + contexts, + pendingInteractions, + nowOrNothing, + automated, + onStart, + onUpdate, + onStop, + onCancel, + onItemDrop, + onReBreakStart, + onReBreak + ) + + companion object { + @JvmName("breakRequest4") + fun Automated.breakRequest( + contexts: Collection, + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null + ) = BreakRequestBuilder( + contexts, pendingInteractions, nowOrNothing, this + ).apply { builder?.invoke(this) }.build() + } +} diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/SwapInfo.kt similarity index 87% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/SwapInfo.kt index c3f0b4202..c0b3a44a3 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/SwapInfo.kt @@ -15,16 +15,16 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.config.automation.AutomationConfig.Companion.DEFAULT import com.lambda.config.blocks.BreakConfig import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.breaking.RebreakHandler -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta import com.lambda.threading.runSafeAutomated /** diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt similarity index 90% rename from src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt index ab53fe97c..76c69b950 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt @@ -15,19 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.hotbar +package com.lambda.interaction.manager.managers.hotbar import com.lambda.config.blocks.HotbarConfig.SwapMode import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.hotbar.HotbarManager.activeRequest -import com.lambda.interaction.managers.hotbar.HotbarManager.activeSlot -import com.lambda.interaction.managers.hotbar.HotbarManager.checkResetSwap -import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveRequest -import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveSlot +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.activeRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.activeSlot +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.checkResetSwap +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.setActiveRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.setActiveSlot import com.lambda.threading.runSafe import net.minecraft.item.ItemStack @@ -41,11 +41,7 @@ import net.minecraft.item.ItemStack */ object HotbarManager : Manager( 1, - onOpen = { - if (activeRequest != null) { - setActiveSlot() - } - }, + onOpen = { if (activeRequest != null) setActiveSlot() }, onClose = { checkResetSwap() } ) { private var activeRequest: HotbarRequest? = null diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt similarity index 55% rename from src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt index 331086e9e..904c32489 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt @@ -15,17 +15,17 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.hotbar +package com.lambda.interaction.manager.managers.hotbar import com.lambda.context.Automated -import com.lambda.interaction.managers.Request +import com.lambda.interaction.manager.Request class HotbarRequest( val slot: Int, - automated: Automated, var keepTicks: Int = automated.hotbarConfig.keepTicks, val swapPause: Int = automated.hotbarConfig.swapPause, - override val nowOrNothing: Boolean = true + override val nowOrNothing: Boolean = true, + automated: Automated ) : Request(), Automated by automated { override val requestId = ++requestCount override val tickStageMask get() = hotbarConfig.tickStageMask @@ -36,10 +36,41 @@ class HotbarRequest( override val done: Boolean get() = slot == HotbarManager.activeSlot && swapPauseAge >= swapPause + @HotbarRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = HotbarManager.request(this, queueIfMismatchedStage) companion object { var requestCount = 0 + private set } } + +@DslMarker +private annotation class HotbarRequestMarker + +@Suppress("unused") +@HotbarRequestMarker +class HotbarRequestBuilder private constructor( + private val slot: Int, + private val nowOrNothing: Boolean, + private val automated: Automated, +) { + private var keepTicks: Int = automated.hotbarConfig.keepTicks + private var swapPause: Int = automated.hotbarConfig.swapPause + + fun keepTicks(keepTicks: Int) { + this.keepTicks = keepTicks + } + + fun swapPause(swapPause: Int) { + this.swapPause = swapPause + } + + private fun build() = HotbarRequest(slot, keepTicks, swapPause, nowOrNothing, automated) + + companion object { + fun Automated.hotbarRequest(slot: Int, nowOrNothing: Boolean = false, builder: (HotbarRequestBuilder.() -> Unit)? = null) = + HotbarRequestBuilder(slot, nowOrNothing, this).apply { builder?.invoke(this) }.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt index b4e813267..a0401782e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.interacting +package com.lambda.interaction.manager.managers.interacting import com.lambda.config.blocks.InteractConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.managers.ActionInfo +import com.lambda.interaction.manager.ActionInfo import net.minecraft.util.math.BlockPos data class InteractInfo( diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt similarity index 87% rename from src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt index b016527b6..315281898 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt @@ -15,10 +15,9 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.interacting +package com.lambda.interaction.manager.managers.interacting import com.lambda.config.blocks.InteractConfig -import com.lambda.config.blocks.InteractConfig.AirPlaceMode import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext @@ -29,23 +28,22 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.pendingActions -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.setPendingConfigs -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.startPending -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.managers.PositionBlocking -import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.interacting.InteractManager.activeRequest -import com.lambda.interaction.managers.interacting.InteractManager.maxInteractionsThisTick -import com.lambda.interaction.managers.interacting.InteractManager.populateFrom -import com.lambda.interaction.managers.interacting.InteractManager.potentialInteractions -import com.lambda.interaction.managers.interacting.InteractManager.processRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.module.modules.world.AutoSign.signWriteDelay +import com.lambda.interaction.handler.handlers.InteractedBlockHandler +import com.lambda.interaction.handler.handlers.InteractedBlockHandler.startPending +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.ManagerUtils.isPosBlocked +import com.lambda.interaction.manager.PositionBlocking +import com.lambda.interaction.manager.managers.breaking.BreakManager +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.interacting.InteractManager.activeRequest +import com.lambda.interaction.manager.managers.interacting.InteractManager.maxInteractionsThisTick +import com.lambda.interaction.manager.managers.interacting.InteractManager.populateFrom +import com.lambda.interaction.manager.managers.interacting.InteractManager.potentialInteractions +import com.lambda.interaction.manager.managers.interacting.InteractManager.processRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.module.modules.world.AutoSign import com.lambda.threading.runConcurrent import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled @@ -69,7 +67,6 @@ import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket import net.minecraft.sound.SoundCategory import net.minecraft.util.ActionResult -import net.minecraft.util.ActionResult.PassToDefaultBlockAction import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos @@ -90,7 +87,7 @@ object InteractManager : Manager( private val ClientPlayerEntity.validSneak get() = isSneaking == shouldSneak override val blockedPositions - get() = pendingActions.map { it.context.blockPos } + get() = InteractedBlockHandler.pendingActions.map { it.context.blockPos } private var cancellingSignScreens = 0 @@ -128,7 +125,7 @@ object InteractManager : Manager( } /** - * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [BreakManager] has not + * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [com.lambda.interaction.manager.managers.breaking.BreakManager] has not * been active this tick. If nowOrNothing is true, the request is cleared after the first process. * * @see processRequest @@ -162,14 +159,11 @@ object InteractManager : Manager( if (!canInteractThisTick()) break val firstInteraction = potentialInteractions.first() if (player.inventory.selectedSlot != firstInteraction.hotbarIndex) { - val hotbarRequest = HotbarRequest( - firstInteraction.hotbarIndex, - this - ).submit(queueIfMismatchedStage = false) + val hotbarRequest = hotbarRequest(firstInteraction.hotbarIndex).submit(false) if (!hotbarRequest.done) break } var interactResult: InteractResult? = null - if (interactConfig.airPlace == AirPlaceMode.Grim) { + if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) { val inventoryRequest = inventoryRequest { swapHands() action { interactResult = performInteractions(request) } @@ -197,7 +191,7 @@ object InteractManager : Manager( if (!player.validSneak) return InteractResult.CompleteFailure if (tickStage !in interactConfig.tickStageMask) return InteractResult.CompleteFailure - val hand = if (interactConfig.airPlace == AirPlaceMode.Grim) Hand.OFF_HAND else Hand.MAIN_HAND + val hand = if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) Hand.OFF_HAND else Hand.MAIN_HAND val actionResult = if (ctx.preProcessingInfo.placing) placeBlock(ctx, request, hand) else interaction.interactBlock(player, if (ctx.preProcessingInfo.item != null) hand else Hand.MAIN_HAND, ctx.hitResult) @@ -217,7 +211,7 @@ object InteractManager : Manager( if (ctx.preProcessingInfo.placing && expectedState.block is AbstractSignBlock) { cancellingSignScreens++ runConcurrent { - delay(signWriteDelay) + delay(AutoSign.signWriteDelay) runSafeGameScheduled { connection.sendPacket { UpdateSignC2SPacket(ctx.blockPos, true, "", "", "", "") @@ -246,7 +240,7 @@ object InteractManager : Manager( * @see isPosBlocked */ private fun Automated.populateFrom(request: InteractRequest) { - setPendingConfigs() + InteractedBlockHandler.setPendingConfigs() potentialInteractions = request.contexts .distinctBy { it.blockPos } .filter { !isPosBlocked(it.blockPos) } @@ -290,7 +284,7 @@ object InteractManager : Manager( val actionResult = blockState.onUseWithItem(player.getStackInHand(hand), world, player, hand, hitResult) if (actionResult.isAccepted) return actionResult - if (actionResult is PassToDefaultBlockAction && hand == Hand.MAIN_HAND) { + if (actionResult is ActionResult.PassToDefaultBlockAction && hand == Hand.MAIN_HAND) { val actionResult2 = blockState.onUse(world, player, hitResult) if (actionResult2.isAccepted) return actionResult2 } @@ -414,4 +408,4 @@ object InteractManager : Manager( WrongSlot, CompleteFailure } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt similarity index 53% rename from src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt index 80d6a859f..a0d9d7849 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.interacting +package com.lambda.interaction.manager.managers.interacting import com.lambda.context.Automated import com.lambda.context.SafeContext @@ -24,87 +24,98 @@ import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult -import com.lambda.interaction.managers.Request +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.matches import net.minecraft.util.math.BlockPos -data class InteractRequest private constructor( +@JvmName("interactRequest1") +context(automated: Automated) +fun Collection.interactRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (PlaceRequestBuilder.() -> Unit)? = null +) = asSequence() + .interactRequest(pendingInteractions, nowOrNothing, builder) + +@JvmName("interactRequest2") +context(automated: Automated) +fun Sequence.interactRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (PlaceRequestBuilder.() -> Unit)? = null +) = map { if (it is Dependent) it.lastDependency else it } + .filterIsInstance() + .sorted() + .map { it.context } + .toSet() + .takeIf { it.isNotEmpty() } + ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } + +data class InteractRequest( val contexts: Collection, val pendingInteractions: MutableCollection, - private val automated: Automated, override val nowOrNothing: Boolean = false, + private val automated: Automated, + val onPlace: (SafeContext.(BlockPos) -> Unit)? ) : Request(), Automated by automated { override val requestId = ++requestCount override val tickStageMask get() = interactConfig.tickStageMask - var onPlace: (SafeContext.(BlockPos) -> Unit)? = null - override val done: Boolean get() = runSafe { contexts.all { it.expectedState.matches(blockState(it.blockPos)) } } == true - @DslMarker - annotation class PlaceRequestDsl - - @PlaceRequestDsl + @InteractRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = InteractManager.request(this, queueIfMismatchedStage) - @PlaceRequestDsl - class PlaceRequestBuilder( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean, - automated: Automated - ) { - val request = InteractRequest(contexts, pendingInteractions, automated, nowOrNothing) - - fun onPlace(callback: SafeContext.(BlockPos) -> Unit) { - request.onPlace = callback - } - } - companion object { var requestCount = 0 + private set + } +} - @PlaceRequestDsl - @JvmName("interactRequest1") - context(automated: Automated) - fun Collection.interactRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = asSequence() - .interactRequest(pendingInteractions, nowOrNothing, builder) +@DslMarker +annotation class InteractRequestMarker - @PlaceRequestDsl - @JvmName("interactRequest2") - context(automated: Automated) - fun Sequence.interactRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = map { if (it is Dependent) it.lastDependency else it } - .filterIsInstance() - .sorted() - .map { it.context } - .toSet() - .takeIf { it.isNotEmpty() } - ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } +@InteractRequestMarker +class PlaceRequestBuilder private constructor( + private val contexts: Collection, + private val pendingInteractions: MutableCollection, + private val nowOrNothing: Boolean, + private val automated: Automated +) { + private var onPlace: (SafeContext.(BlockPos) -> Unit)? = null - @PlaceRequestDsl + fun onPlace(callback: SafeContext.(BlockPos) -> Unit) { + onPlace = callback + } + + private fun build() = + InteractRequest( + contexts, + pendingInteractions, + nowOrNothing, + automated, + onPlace + ) + + companion object { @JvmName("interactRequest3") fun Automated.interactRequest( contexts: Collection, pendingInteractions: MutableCollection, nowOrNothing: Boolean = false, builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = PlaceRequestBuilder(contexts, pendingInteractions, nowOrNothing, this).apply { builder?.invoke(this) }.build() - - @PlaceRequestDsl - fun PlaceRequestBuilder.build() = request + ) = PlaceRequestBuilder( + contexts, + pendingInteractions, + nowOrNothing, + this + ).apply { builder?.invoke(this) }.build() } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryAction.kt similarity index 95% rename from src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryAction.kt index 33f4559a6..ebf22474f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryAction.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.inventory +package com.lambda.interaction.manager.managers.inventory import com.lambda.context.SafeContext diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt similarity index 93% rename from src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt index f89841caf..62fda5e39 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt @@ -15,21 +15,21 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.inventory +package com.lambda.interaction.manager.managers.inventory import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.packet.PacketLimitHandler.canSendPackets -import com.lambda.interaction.handlers.packet.PacketLimitHandler.sentPackets -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.inventory.InventoryManager.actions -import com.lambda.interaction.managers.inventory.InventoryManager.activeRequest -import com.lambda.interaction.managers.inventory.InventoryManager.alteredSlots -import com.lambda.interaction.managers.inventory.InventoryManager.processActiveRequest +import com.lambda.interaction.handler.handlers.PacketLimitHandler.canSendPackets +import com.lambda.interaction.handler.handlers.PacketLimitHandler.sentPackets +import com.lambda.interaction.handler.handlers.PacketType +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.managers.inventory.InventoryManager.actions +import com.lambda.interaction.manager.managers.inventory.InventoryManager.activeRequest +import com.lambda.interaction.manager.managers.inventory.InventoryManager.alteredSlots +import com.lambda.interaction.manager.managers.inventory.InventoryManager.processActiveRequest import com.lambda.module.modules.client.Client import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt new file mode 100644 index 000000000..3a7521c21 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2026 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.manager.managers.inventory + +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.manager.Request +import com.lambda.util.PacketUtils.sendPacket +import com.lambda.util.player.SlotUtils.clickSlot +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import net.minecraft.item.ItemStack +import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket +import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket +import net.minecraft.screen.slot.SlotActionType +import net.minecraft.screen.sync.ItemStackHash +import net.minecraft.util.Hand +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction + +/** + * 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( + val actions: List, + val settleForLess: Boolean, + val mustPerform: Boolean, + automated: Automated, + override val nowOrNothing: Boolean = false, + val onComplete: (SafeContext.() -> Unit)? +) : Request(), Automated by automated { + override val requestId = ++requestCount + override val tickStageMask get() = inventoryConfig.tickStageMask + override var done = false + + @InvRequestDsl + override fun submit(queueIfMismatchedStage: Boolean) = + InventoryManager.request(this, queueIfMismatchedStage) + + companion object { + var requestCount = 0 + private set + } +} + +@DslMarker +annotation class InvRequestDsl + +@Suppress("unused") +@InvRequestDsl +class InvRequestBuilder private constructor( + private val settleForLess: Boolean, + private val mustPerform: Boolean +) { + private val actions = mutableListOf() + private var onComplete: (SafeContext.() -> Unit)? = null + + fun click(slotId: Int, button: Int, actionType: SlotActionType) { + InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() + } + + fun resyncInventory() { + InventoryAction.Inventory { + connection.sendPacket { + val sh = player.currentScreenHandler + ClickSlotC2SPacket( + sh.syncId, + -1, -1, 0, + SlotActionType.CLONE, + Int2ObjectOpenHashMap(), + ItemStackHash.fromItemStack(sh.cursorStack, connection.componentHasher) + ) + } + }.addToActions() + } + + fun pickFromInventory(slotId: Int) { + InventoryAction.Inventory { + clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) + }.addToActions() + } + + fun dropItemInHand(entireStack: Boolean = true) { + InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() + } + + fun swapHands() { + InventoryAction.Player { + val offhandStack = player.getStackInHand(Hand.OFF_HAND) + player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) + player.setStackInHand(Hand.MAIN_HAND, offhandStack) + connection.sendPacket { + PlayerActionC2SPacket( + PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, + BlockPos.ORIGIN, + Direction.DOWN + ) + } + }.addToActions() + } + + fun clickCreativeStack(stack: ItemStack, slotId: Int) { + InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() + } + + fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) + + fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) + + fun swapWithHotbar(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) + + fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) + + fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) + + fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) + + fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) + + fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) + + fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) + + fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) + + fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { + pickup(fromSlotId, button) + pickup(toSlotId, button) + } + + fun splitStack(slotId: Int, targetSlotId: Int) { + pickup(slotId, 1) + pickup(targetSlotId, 0) + } + + fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { + pickup(sourceSlotId, 0) + pickup(targetSlotId, 0) + } + + fun action(action: SafeContext.() -> Unit) { + InventoryAction.Other(action).addToActions() + } + + fun onComplete(callback: SafeContext.() -> Unit) { + onComplete = callback + } + + private fun InventoryAction.addToActions() { + actions.add(this) + } + + context(automated: Automated) + private fun build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete) + + companion object { + fun Automated.inventoryRequest(settleForLess: Boolean = false, mustPerform: Boolean = false, builder: InvRequestBuilder.() -> Unit) = + InvRequestBuilder(settleForLess, mustPerform).apply(builder).build() + } +} diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/Rotation.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/Rotation.kt index 06600b9d1..9efeb74ce 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/Rotation.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.threading.runSafe import com.lambda.util.math.MathUtils.toDegree diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationManager.kt similarity index 95% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationManager.kt index d719df9e2..4d40683d3 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext @@ -25,12 +25,12 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerpPitch -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerpYaw -import com.lambda.interaction.managers.rotating.RotationManager.activeRotation -import com.lambda.interaction.managers.rotating.RotationManager.serverRotation -import com.lambda.interaction.managers.rotating.RotationManager.updateActiveRotation +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.slerpPitch +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.slerpYaw +import com.lambda.interaction.manager.managers.rotating.RotationManager.activeRotation +import com.lambda.interaction.manager.managers.rotating.RotationManager.serverRotation +import com.lambda.interaction.manager.managers.rotating.RotationManager.updateActiveRotation import com.lambda.threading.runSafe import com.lambda.util.extension.rotation import com.lambda.util.math.Vec2d diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationMode.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationMode.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/RotationMode.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationMode.kt index 5d0cae02e..0ce450f52 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationMode.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationMode.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt similarity index 62% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt index 94187dd9f..f7d234f6f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt @@ -15,25 +15,24 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.requestCount -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.rotating.IRotationRequest.Full +import com.lambda.interaction.manager.managers.rotating.IRotationRequest.Pitch +import com.lambda.interaction.manager.managers.rotating.IRotationRequest.Yaw +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.wrap import com.lambda.threading.runSafe import com.lambda.util.collections.UpdatableLazy import com.lambda.util.collections.updatableLazy import kotlin.math.abs import kotlin.math.hypot -@DslMarker -annotation class RotationRequestDsl - abstract class RotationRequest(automated: Automated) : Request(), Automated by automated { override val requestId = requestCount++ override val tickStageMask = automated.rotationConfig.tickStageMask @@ -41,11 +40,19 @@ abstract class RotationRequest(automated: Automated) : Request(), Automated by a abstract infix fun dist(rotation: Rotation): Double - @RotationRequestDsl + @RotationRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = RotationManager.request(this, queueIfMismatchedStage) + + companion object { + var requestCount = 0 + private set + } } +@DslMarker +annotation class RotationRequestMarker + interface IRotationRequest : Automated { var keepTicks: Int var decayTicks: Int @@ -141,73 +148,58 @@ interface IRotationRequest : Automated { interface YawRot : IRotationRequest { val yaw: UpdatableLazy } interface PitchRot : IRotationRequest { val pitch: UpdatableLazy } interface FullRot : YawRot, PitchRot { val rotation: UpdatableLazy } +} + +@RotationRequestMarker +class RotationRequestBuilder private constructor() { + private var pitchBuilder: (SafeContext.() -> Double)? = null + private var yawBuilder: (SafeContext.() -> Double)? = null + private var rotationBuilder: (SafeContext.() -> Rotation)? = null - class RotationRequestBuilder { - var pitchBuilder: (SafeContext.() -> Double)? = null - var yawBuilder: (SafeContext.() -> Double)? = null - var rotationBuilder: (SafeContext.() -> Rotation)? = null + @JvmName("yawBuilder1") + fun yaw(builder: SafeContext.() -> Double) { yawBuilder = builder } - @JvmName("yawBuilder1") - @RotationRequestDsl - fun yaw(builder: SafeContext.() -> Double) { yawBuilder = builder } + @JvmName("yawBuilder2") + fun yaw(builder: SafeContext.() -> Float) { yawBuilder = { builder().toDouble() } } - @JvmName("yawBuilder2") - @RotationRequestDsl - fun yaw(builder: SafeContext.() -> Float) { yawBuilder = { builder().toDouble() } } + fun yaw(yaw: Double) { yawBuilder = { yaw } } - @RotationRequestDsl - fun yaw(yaw: Double) { yawBuilder = { yaw } } + fun yaw(yaw: Float) { yawBuilder = { yaw.toDouble() } } - @RotationRequestDsl - fun yaw(yaw: Float) { yawBuilder = { yaw.toDouble() } } + @JvmName("pitchBuilder1") + fun pitch(builder: SafeContext.() -> Double) { pitchBuilder = builder } - @JvmName("pitchBuilder1") - @RotationRequestDsl - fun pitch(builder: SafeContext.() -> Double) { pitchBuilder = builder } + @JvmName("pitchBuilder2") + fun pitch(builder: SafeContext.() -> Float) { pitchBuilder = { builder().toDouble() } } - @JvmName("pitchBuilder2") - @RotationRequestDsl - fun pitch(builder: SafeContext.() -> Float) { pitchBuilder = { builder().toDouble() } } + fun pitch(pitch: Double) { pitchBuilder = { pitch } } - @RotationRequestDsl - fun pitch(pitch: Double) { pitchBuilder = { pitch } } + fun pitch(pitch: Float) { pitchBuilder = { pitch.toDouble() } } - @RotationRequestDsl - fun pitch(pitch: Float) { pitchBuilder = { pitch.toDouble() } } + fun rotation(builder: SafeContext.() -> Rotation) { rotationBuilder = builder } - @RotationRequestDsl - fun rotation(builder: SafeContext.() -> Rotation) { rotationBuilder = builder } + fun rotation(yaw: Double, pitch: Double) { rotationBuilder = { Rotation(yaw, pitch) } } - @RotationRequestDsl - fun rotation(yaw: Double, pitch: Double) { rotationBuilder = { Rotation(yaw, pitch) } } + fun rotation(yaw: Float, pitch: Float) { rotationBuilder = { Rotation(yaw, pitch) } } - @RotationRequestDsl - fun rotation(yaw: Float, pitch: Float) { rotationBuilder = { Rotation(yaw, pitch) } } + fun rotation(rotation: Rotation) { rotationBuilder = { rotation } } - @RotationRequestDsl - fun rotation(rotation: Rotation) { rotationBuilder = { rotation } } + context(automated: Automated) + private fun build(): RotationRequest { + val yawBuilder = yawBuilder + val pitchBuilder = pitchBuilder + val rotationBuilder = rotationBuilder + return when { + rotationBuilder != null -> Full(automated, rotationBuilder) + yawBuilder != null && pitchBuilder != null -> Full(automated) { Rotation(yawBuilder(), pitchBuilder()) } + yawBuilder != null -> Yaw(automated, yawBuilder) + pitchBuilder != null -> Pitch(automated, pitchBuilder) + else -> throw IllegalArgumentException("Must specify at least one rotation value to build a rotation request") + } } companion object { - var requestCount = 0 - - @RotationRequestDsl fun Automated.rotationRequest(builder: RotationRequestBuilder.() -> Unit) = RotationRequestBuilder().apply(builder).build() - - @RotationRequestDsl - context(automated: Automated) - private fun RotationRequestBuilder.build(): RotationRequest { - val yawBuilder = yawBuilder - val pitchBuilder = pitchBuilder - val rotationBuilder = rotationBuilder - return when { - rotationBuilder != null -> Full(automated, rotationBuilder) - yawBuilder != null && pitchBuilder != null -> Full(automated) { Rotation(yawBuilder(), pitchBuilder()) } - yawBuilder != null -> Yaw(automated, yawBuilder) - pitchBuilder != null -> Pitch(automated, pitchBuilder) - else -> throw IllegalArgumentException("Must specify at least one rotation value to build a rotation request") - } - } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt deleted file mode 100644 index 3330cf195..000000000 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2026 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.managers.breaking - -import com.lambda.context.Automated -import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext -import com.lambda.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.construction.simulation.context.BuildContext -import com.lambda.interaction.construction.simulation.result.BuildResult -import com.lambda.interaction.construction.simulation.result.Dependent -import com.lambda.interaction.construction.simulation.result.results.BreakResult -import com.lambda.interaction.construction.simulation.sim -import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.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, - override val nowOrNothing: Boolean = false -) : Request(), Automated by automated { - override val requestId = ++requestCount - override val tickStageMask get() = breakConfig.tickStageMask - - var onStart: (SafeContext.(BlockPos) -> Unit)? = null - var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null - var onStop: (SafeContext.(BlockPos) -> Unit)? = null - var onCancel: (SafeContext.(BlockPos) -> Unit)? = null - var onItemDrop: (SafeContext.(ItemEntity) -> Unit)? = null - var onReBreakStart: (SafeContext.(BlockPos) -> Unit)? = null - var onReBreak: (SafeContext.(BlockPos) -> Unit)? = null - - override val done: Boolean - get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true - - @DslMarker - annotation class BreakRequestDsl - - @BreakRequestDsl - override fun submit(queueIfMismatchedStage: Boolean) = - BreakManager.request(this, queueIfMismatchedStage) - - @BreakRequestDsl - class BreakRequestBuilder( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean, - automated: Automated - ) { - val request = BreakRequest(contexts, pendingInteractions, automated, nowOrNothing) - - fun onStart(callback: SafeContext.(BlockPos) -> Unit) { - request.onStart = callback - } - - fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { - request.onUpdate = callback - } - - fun onStop(callback: SafeContext.(BlockPos) -> Unit) { - request.onStop = callback - } - - fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { - request.onCancel = callback - } - - fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { - request.onItemDrop = callback - } - - fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { - request.onReBreakStart = callback - } - - fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { - request.onReBreak = callback - } - } - - companion object { - var requestCount = 0 - - @BreakRequestDsl - @JvmName("breakRequest1") - fun AutomatedSafeContext.breakRequest( - positions: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = positions - .associateWith { TargetState.Empty } - .sim() - .breakRequest(pendingInteractions, nowOrNothing, builder) - - @BreakRequestDsl - @JvmName("breakRequest2") - context(automated: Automated) - fun Collection.breakRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = asSequence() - .breakRequest(pendingInteractions, nowOrNothing, builder) - - @BreakRequestDsl - @JvmName("breakRequest3") - context(automated: Automated) - fun Sequence.breakRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = map { if (it is Dependent) it.lastDependency else it } - .filterIsInstance() - .sorted() - .map { it.context } - .toSet() - .takeIf { it.isNotEmpty() } - ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } - - @BreakRequestDsl - @JvmName("breakRequest4") - fun Automated.breakRequest( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = BreakRequestBuilder( - contexts, pendingInteractions, nowOrNothing, this - ).apply { builder?.invoke(this) }.build() - - @BreakRequestDsl - private fun BreakRequestBuilder.build(): BreakRequest = request - } -} diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt deleted file mode 100644 index 277eb2faf..000000000 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2026 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.managers.inventory - -import com.lambda.context.Automated -import com.lambda.context.SafeContext -import com.lambda.interaction.managers.Request -import com.lambda.util.PacketUtils.sendPacket -import com.lambda.util.player.SlotUtils.clickSlot -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap -import net.minecraft.item.ItemStack -import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket -import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket -import net.minecraft.screen.slot.SlotActionType -import net.minecraft.screen.sync.ItemStackHash -import net.minecraft.util.Hand -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction - -/** - * 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, - val mustPerform: Boolean, - automated: Automated, - override val nowOrNothing: Boolean = false, - val onComplete: (SafeContext.() -> Unit)? -) : Request(), Automated by automated { - override val requestId = ++requestCount - override val tickStageMask get() = inventoryConfig.tickStageMask - override var done = false - - @InvRequestDsl - override fun submit(queueIfMismatchedStage: Boolean) = - InventoryManager.request(this, queueIfMismatchedStage) - - @DslMarker - private annotation class InvRequestDsl - - @Suppress("unused") - @InvRequestDsl - class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { - val actions = mutableListOf() - var onComplete: (SafeContext.() -> Unit)? = null - - fun click(slotId: Int, button: Int, actionType: SlotActionType) { - InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() - } - - fun resyncInventory() { - InventoryAction.Inventory { - connection.sendPacket { - val sh = player.currentScreenHandler - ClickSlotC2SPacket( - sh.syncId, - -1, -1, 0, - SlotActionType.CLONE, - Int2ObjectOpenHashMap(), - ItemStackHash.fromItemStack(sh.cursorStack, connection.componentHasher) - ) - } - }.addToActions() - } - - fun pickFromInventory(slotId: Int) { - InventoryAction.Inventory { - clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) - }.addToActions() - } - - fun dropItemInHand(entireStack: Boolean = true) { - InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() - } - - fun swapHands() { - InventoryAction.Player { - val offhandStack = player.getStackInHand(Hand.OFF_HAND) - player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) - player.setStackInHand(Hand.MAIN_HAND, offhandStack) - connection.sendPacket { - PlayerActionC2SPacket( - PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, - BlockPos.ORIGIN, - Direction.DOWN - ) - } - }.addToActions() - } - - fun clickCreativeStack(stack: ItemStack, slotId: Int) { - InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() - } - - fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) - - // Quick move action (Shift-click) - fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) - - fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) - - // Clone action (Creative mode) - fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) - - // Throw stack or single item - fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) - - fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) - - // Quick craft action - fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) - - fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) - - fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) - - // Pickup all items (double-click) - fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) - - // Helper function: Move items from one slot to another - fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { - pickup(fromSlotId, button) - pickup(toSlotId, button) - } - - // Helper function: Split a stack into two - fun splitStack(slotId: Int, targetSlotId: Int) { - pickup(slotId, 1) // Pickup half the stack - pickup(targetSlotId, 0) // Place it in the target slot - } - - // Helper function: Merge stacks - fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { - pickup(sourceSlotId, 0) - pickup(targetSlotId, 0) - } - - fun action(action: SafeContext.() -> Unit) { - InventoryAction.Other(action).addToActions() - } - - fun onComplete(callback: SafeContext.() -> Unit) { - onComplete = callback - } - - private fun InventoryAction.addToActions() { - actions.add(this) - } - } - - companion object { - var requestCount = 0 - - @InvRequestDsl - fun Automated.inventoryRequest(settleForLess: Boolean = false, mustPerform: Boolean = false, builder: InvRequestBuilder.() -> Unit) = - InvRequestBuilder(settleForLess, mustPerform).apply(builder).build() - - @InvRequestDsl - context(automated: Automated) - private fun InvRequestBuilder.build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete) - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt deleted file mode 100644 index fca5a4d20..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2026 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.material - -import com.lambda.context.SafeContext -import com.lambda.interaction.material.container.MaterialContainer - -/** - * ContainerSelection is a class that holds a predicate for matching MaterialContainers. - * It can be combined using "and", "or", etc. - */ -class ContainerSelection { - private var selector: (MaterialContainer) -> Boolean = { true } - - /** - * Tests whether the provided container matches this selection. - */ - @ContainerSelectionDsl - fun matches(container: MaterialContainer): Boolean = selector(container) - - /** - * Returns a function that matches containers having at least one stack - * which matches the given StackSelection. - */ - @ContainerSelectionDsl - context(_: SafeContext) - fun matches(stackSelection: StackSelection): (MaterialContainer) -> Boolean = - { container -> container.matchingSlots(stackSelection).isNotEmpty() } - - /** - * Returns a function that checks whether a given MaterialContainer matches the criteria - * defined in the provided ContainerSelection. - */ - @ContainerSelectionDsl - fun matches(containerSelection: ContainerSelection): (MaterialContainer) -> Boolean = - { container -> containerSelection.matches(container) } - - /** - * Returns a function that matches containers whose rank is any of the types provided. - */ - @ContainerSelectionDsl - fun ofAnyType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean = - { container -> types.contains(container.rank) } - - /** - * Returns a function that matches containers whose rank is not any of the types provided. - */ - @ContainerSelectionDsl - fun noneOfType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean = - { container -> !types.contains(container.rank) } - - /** - * Returns a function that combines two container predicates using logical AND. - */ - @ContainerSelectionDsl - infix fun ((MaterialContainer) -> Boolean).and(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean = - { container -> this(container) && other(container) } - - /** - * Returns a function that combines two container predicates using logical OR. - */ - @ContainerSelectionDsl - infix fun ((MaterialContainer) -> Boolean).or(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean = - { container -> this(container) || other(container) } - - /** - * Returns a function that negates the current selection predicate. - */ - @ContainerSelectionDsl - fun ((MaterialContainer) -> Boolean).negate(): (MaterialContainer) -> Boolean = - { container -> !this(container) } - - companion object { - @DslMarker - annotation class ContainerSelectionDsl - - @ContainerSelectionDsl - fun selectContainer( - block: ContainerSelection.() -> (MaterialContainer) -> Boolean - ): ContainerSelection = - ContainerSelection().apply { selector = block() } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt deleted file mode 100644 index 96a8b96ad..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2026 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.material - -import com.lambda.interaction.material.StackSelection.Companion.StackSelectionDsl -import com.lambda.util.EnchantmentUtils.getEnchantment -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents -import com.lambda.util.item.ItemUtils -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.component.ComponentType -import net.minecraft.component.DataComponentTypes -import net.minecraft.enchantment.Enchantment -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.item.consume.UseAction -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.tag.TagKey -import net.minecraft.screen.slot.Slot -import java.util.* -import kotlin.reflect.KClass - -/** - * [StackSelection] is a class that holds a predicate for matching [ItemStack]s. - */ -@Suppress("unused") -@StackSelectionDsl -class StackSelection { - var selector: (ItemStack) -> Boolean = EVERYTHING - var comparator: Comparator = NO_COMPARE - var count: Int = DEFAULT_AMOUNT - var inShulkerBox: Boolean = false - - var item: Item? = null - var itemClass: KClass? = null - private var damage: Int? = null - var itemStack: ItemStack? = null - val stackSize: Int - get() = optimalStack?.maxCount ?: 64 - - val optimalStack: ItemStack? - get() = itemStack ?: item?.let { ItemStack(it, count) } - - /** - * Filters the given [stacks], sorts them with the [comparator] and returns the first value - */ - fun bestItemMatch(stacks: List): ItemStack? = filterStacks(stacks).firstOrNull() - - fun matches(stack: ItemStack): Boolean = filterStack(stack) - - fun filterStack(stack: ItemStack) = - if (inShulkerBox) stack.shulkerBoxContents.any { selector(it) } - else selector(stack) - - fun filterSlot(slot: Slot): Boolean = filterStack(slot.stack) - - fun filterStacks(stacks: List): List = - stacks.filter(::filterStack).sortedWith(comparator) - - fun filterSlots(slots: List): List = - slots.filter(::filterSlot).sortedWith { slot, slot2 -> comparator.compare(slot.stack, slot2.stack) } - - fun > sortBy(selector: (ItemStack) -> R?): StackSelection = apply { - comparator = compareBy(selector) - } - - fun > sortByDescending(selector: (ItemStack) -> R?): StackSelection = apply { - comparator = compareByDescending(selector) - } - - fun > thenBy(selector: (ItemStack) -> R?): StackSelection = apply { - check(comparator != NO_COMPARE) { "No comparator specified" } - comparator = comparator.thenBy(selector) - } - - fun > thenByDescending(selector: (ItemStack) -> R?): StackSelection = apply { - check(comparator != NO_COMPARE) { "No comparator specified" } - comparator = comparator.thenByDescending(selector) - } - - fun sortWith(custom: Comparator): StackSelection = apply { - comparator = custom - } - - fun reversed(): StackSelection = apply { - comparator = comparator.reversed() - } - - /** - * returns a function that finds a shulker box to push matching items into. - */ - val findShulkerToPush = { stack: ItemStack -> - stack.shulkerBoxContents.let { inventory -> - if (inventory.all { selector(it) || it.isEmpty }) { - val storableItems = inventory.sumOf { -// if (it.isEmpty) item.itemStackLimit else it.maxStackSize - it.count - if (it.isEmpty) item?.maxCount ?: 0 else 0 - } - - if (storableItems > 0) storableItems else null - } else { - null - } - } - } - - /** - * returns a function that finds a shulker box to pull matching items from. - */ - val findShulkerToPull = { slot: Slot -> - slot.stack.shulkerBoxContents.let { inventory -> - val usableItems = inventory.sumOf { if (selector(it)) it.count else 0 } - - if (usableItems > 0) slot to usableItems else null - } - } - - fun any() = EVERYTHING - fun none() = NOTHING - - /** - * [isItem] returns a predicate that matches a specific [Item]. - * @param item The [Item] to be matched. - * @return A predicate that matches the [Item]. - */ - fun isItem(item: Item): (ItemStack) -> Boolean { - this.item = item - return { it.item == item } - } - - /** - * Returns a predicate that matches if the `ItemStack`'s item is one of the specified items in the collection. - * - * @param items The collection of `Item` instances to match against. - * @return A predicate that checks if the `ItemStack`'s item is contained in the provided collection. - */ - fun isOneOfItems(items: Collection): (ItemStack) -> Boolean = { it.item in items } - - fun isNoneOfItems(items: Collection): (ItemStack) -> Boolean = isOneOfItems(items).not() - - /** - * Returns a predicate that checks if a given `ItemStack` exists within the provided collection of `ItemStack`s. - * - * @param stacks A collection of `ItemStack` instances to be checked against. - * @return A predicate that evaluates to `true` if the given `ItemStack` is within the specified collection, otherwise `false`. - */ - fun isOneOfStacks(stacks: Collection): (ItemStack) -> Boolean = stacks::contains - - fun isEfficientForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { itemStack -> - val hasEfficientTool = efficientToolCache.getOrPut(blockState) { - ItemUtils.tools.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } - } - if (hasEfficientTool) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f - else true - } - - fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { !blockState.isToolRequired || it.isSuitableFor(blockState) } - - fun hasTag(tag: TagKey): (ItemStack) -> Boolean = { it.isIn(tag) } - - fun hasUseAction(action: UseAction): (ItemStack) -> Boolean = { it.useAction == action } - - fun isTool(): (ItemStack) -> Boolean = hasComponent(DataComponentTypes.TOOL) - - fun isFood(): (ItemStack) -> Boolean = hasComponent(DataComponentTypes.FOOD) - - fun hasComponent(type: ComponentType<*>): (ItemStack) -> Boolean = { it.components.contains(type) } - - /** - * [isItem] returns a predicate that matches a specific [Item] instance. - * @param T The instance of [Item] to be matched. - * @return A predicate that matches the [Item]. - */ - inline fun isItem(): (ItemStack) -> Boolean { - itemClass = T::class - return { it.item is T } - } - - /** - * [isBlock] returns a predicate that matches a specific [Block]. - * @param block The [Block] to be matched. - * @return A predicate that matches the [Block]. - */ - fun isBlock(block: Block): (ItemStack) -> Boolean { - item = block.asItem() - return { it.item == block.asItem() } - } - - /** - * [isItemStack] returns a predicate that matches a specific [ItemStack]. - * @param stack The [ItemStack] to be matched. - * @return A predicate that matches the [ItemStack]. - */ - fun isItemStack(stack: ItemStack): (ItemStack) -> Boolean { - this.itemStack = stack - return { ItemStack.areEqual(it, stack) } - } - - /** - * [hasDamage] returns a predicate that matches a specific damage value. - * @param damage The damage value to be matched. - * @return A predicate that matches the damage value. - */ - fun hasDamage(damage: Int): (ItemStack) -> Boolean { - this.damage = damage - return { it.damage == damage } - } - - /** - * [hasEnchantment] returns a predicate that matches a specific [Enchantment] and level. - * @param enchantment The [Enchantment] to be matched. - * @param level The level to be matched (if -1 will look for any level above 0). - * @return A predicate that matches the [Enchantment] and `level`. - */ - fun hasEnchantment(enchantment: RegistryKey, level: Int = -1): (ItemStack) -> Boolean = { - if (level < 0) { - it.getEnchantment(enchantment) > 0 - } else { - it.getEnchantment(enchantment) == level - } - } - - /** - * Returns the negation of the original predicate. - * @return A new predicate that matches if the original predicate does not match. - */ - fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean = { !this(it) } - - /** - * Combines two predicates using the logical AND operator. - * @param otherPredicate The second predicate. - * @return A new predicate that matches if both inputs predicate match. - */ - infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean = { this(it) && otherPredicate(it) } - - /** - * Combines two predicates using the logical OR operator. - * @param otherPredicate The second predicate. - * @return A new predicate that matches if either input predicate matches. - */ - infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean = { this(it) || otherPredicate(it) } - - fun ((ItemStack) -> Boolean).andIf(predicate: Boolean, otherPredicate: () -> (ItemStack) -> Boolean): (ItemStack) -> Boolean = - if (predicate) { { this(it) && otherPredicate()(it) } } else this - - override fun toString() = buildString { - append("selection of ${count}x ") - item?.let { append(it.name.string) } - itemClass?.let { append(it.simpleName) } - itemStack?.let { append(it.name.string) } - damage?.let { append(" with damage $it") } - when (selector) { - EVERYTHING -> append(" everything") - NOTHING -> append(" nothing") - else -> append(" custom predicate") - } - if (inShulkerBox) append(" in shulker box") - if (comparator != NO_COMPARE) append(" sorted by custom comparator") - } - - companion object { - @DslMarker - annotation class StackSelectionDsl - - const val DEFAULT_AMOUNT = 1 - - val FULL_SHULKERS: (ItemStack) -> Boolean = { stack -> stack.shulkerBoxContents.none { it.isEmpty } } - val EMPTY_SHULKERS: (ItemStack) -> Boolean = { stack -> stack.shulkerBoxContents.all { it.isEmpty } } - val EVERYTHING: (ItemStack) -> Boolean = { true } - val NOTHING: (ItemStack) -> Boolean = { false } - val NO_COMPARE: Comparator = Comparator { _, _ -> 0 } - - val efficientToolCache: MutableMap = Collections.synchronizedMap(mutableMapOf()) - - @StackSelectionDsl - fun Item.select() = selectStack { isItem(this@select) } - - @StackSelectionDsl - fun ItemStack.select() = selectStack { isItemStack(this@select) } - - @StackSelectionDsl - @JvmName("selectStacks") - fun Collection.select() = selectStack { isOneOfStacks(this@select) } - - @StackSelectionDsl - @JvmName("selectItems") - fun Collection.select() = selectStack { isOneOfItems(this@select) } - - @StackSelectionDsl - fun ((ItemStack) -> Boolean).select() = selectStack { this@select } - - @StackSelectionDsl - fun selectStack( - count: Int = DEFAULT_AMOUNT, - inShulkerBox: Boolean = false, - sorter: Comparator = NO_COMPARE, - block: StackSelection.() -> (ItemStack) -> Boolean = { EVERYTHING }, - ) = StackSelection().apply { - selector = block() - comparator = sorter - this.count = count - this.inShulkerBox = inShulkerBox - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt deleted file mode 100644 index 895bdcbdb..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2026 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.material.container - -import com.lambda.context.Automated -import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext -import com.lambda.event.EventFlow.post -import com.lambda.event.events.ContainerEvent -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.containers.ShulkerBoxContainer -import com.lambda.task.Task -import com.lambda.task.tasks.ContainerTransferTask -import com.lambda.util.Nameable -import com.lambda.util.item.ItemStackUtils.count -import com.lambda.util.item.ItemStackUtils.empty -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents -import com.lambda.util.item.ItemStackUtils.spaceLeft -import com.lambda.util.item.ItemUtils -import com.lambda.util.item.ItemUtils.toItemCount -import com.lambda.util.text.TextBuilder -import com.lambda.util.text.TextDsl -import com.lambda.util.text.buildText -import com.lambda.util.text.highlighted -import com.lambda.util.text.literal -import com.lambda.util.text.text -import net.minecraft.component.DataComponentTypes -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot -import net.minecraft.text.Text - -// ToDo: Make jsonable to persistently store them -abstract class MaterialContainer( - val rank: Rank -) : Nameable, Comparable { - context(_: SafeContext) - abstract val slots: List - abstract var stacks: List - open val swapMethodPriority = 0 - abstract val description: Text - - context(automated: Automated) - open val replaceSorter get() = compareByDescending { - it.stack.isEmpty - }.thenByDescending { - it.stack.item in automated.inventoryConfig.disposables - }.thenByDescending { - !it.stack.item.components.contains(DataComponentTypes.TOOL) - }.thenByDescending { - !it.stack.item.components.contains(DataComponentTypes.FOOD) - }.thenByDescending { - !it.stack.item.components.contains(DataComponentTypes.CONSUMABLE) - }.thenByDescending { - it.stack.isStackable - } - - @TextDsl - context(_: SafeContext) - fun TextBuilder.stock(selection: StackSelection) { - literal("\n") - literal("Contains ") - val available = materialAvailable(selection) - highlighted(if (available == Int.MAX_VALUE) "∞" else available.toItemCount()) - literal(" of ") - highlighted("${selection.optimalStack?.name?.string}") - literal("\n") - literal("Could store ") - val left = spaceAvailable(selection) - highlighted(if (left == Int.MAX_VALUE) "∞" else left.toItemCount()) - literal(" of ") - highlighted("${selection.optimalStack?.name?.string}") - } - - context(_: SafeContext) - fun description(selection: StackSelection) = - buildText { - text(description) - stock(selection) - } - - override val name: String - get() = buildText { text(description) }.string - - context(_: SafeContext) - val shulkerContainer - get() = - slots.filter { - it.stack.item in ItemUtils.shulkerBoxes - }.map { slot -> - ShulkerBoxContainer( - slot.stack.shulkerBoxContents, - containedIn = this@MaterialContainer, - shulkerSlot = slot - ) - }.toSet() - - fun update(slots: List) { - this.stacks = slots - } - - class FailureTask(override val name: String) : Task() { - override fun SafeContext.onStart() { - failure(name) - } - } - - context(_: SafeContext) - open fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - if (fromHere.stack.isEmpty) pickupAndPlace(toSlot.id, fromHere.id) - else { - pickupAndPlace(fromHere.id, toSlot.id) - if (!toSlot.stack.isEmpty) pickup(fromHere.id) - } - } - - context(automatedSafeContext: AutomatedSafeContext) - fun transfer(stackSelection: StackSelection, destination: MaterialContainer): Boolean = - with(automatedSafeContext) { - val fromSlot = getSlot(stackSelection) ?: return false - val toSlot = destination.getReplaceableSlot() ?: return false - val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@MaterialContainer, destination) - if (transferEvent.post().isCanceled()) return false - return inventoryRequest { - if (swapMethodPriority > destination.swapMethodPriority) transfer(fromSlot, toSlot) - else with(destination) { transfer(toSlot, fromSlot) } - }.submit().done - } - - context(automatedSafeContext: AutomatedSafeContext) - fun transferByTask(stackSelection: StackSelection, destination: MaterialContainer, failIfNoMaterial: Boolean = false) = - ContainerTransferTask(this, destination, stackSelection, automatedSafeContext, failIfNoMaterial) - - protected fun InventoryRequest.InvRequestBuilder.pickupAndPlace(fromId: Int, toId: Int) { - pickup(fromId) - pickup(toId) - } - - context(_: SafeContext) - open fun matchingStacks(selection: StackSelection) = - selection.filterStacks(stacks) - - context(_: SafeContext) - open fun matchingSlots(selection: StackSelection) = - selection.filterSlots(slots) - - context(_: SafeContext) - open fun materialAvailable(selection: StackSelection) = - matchingStacks(selection).count - - context(_: SafeContext) - open fun spaceAvailable(selection: StackSelection) = - matchingStacks(selection).spaceLeft + stacks.empty * selection.stackSize - - context(_: AutomatedSafeContext) - open fun getReplaceableSlot() = slots.sortedWith(replaceSorter).firstOrNull() - - context(_: SafeContext) - open fun getSlot(stackSelection: StackSelection): Slot? = - stackSelection.filterSlots(slots).firstOrNull() - - enum class Rank { - MainHand, - OffHand, - Hotbar, - Inventory, - Creative, - ShulkerBox, - EnderChest, - Chest, - Stash - } - - override fun compareTo(other: MaterialContainer) = - compareBy { - it.rank - }.compare(this, other) -} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt deleted file mode 100644 index a9872acce..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2026 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.material.container.containers - -import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.task.Task -import com.lambda.task.wrappers.TaskSupplier -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.wrappers.thenAction -import com.lambda.util.extension.containerSlots -import com.lambda.util.text.buildText -import com.lambda.util.text.highlighted -import com.lambda.util.text.literal -import net.minecraft.block.entity.ChestBlockEntity -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot -import net.minecraft.util.math.BlockPos - -data class ChestContainer( - override var stacks: List, - val blockPos: BlockPos, - val containedInStash: StashContainer? = null -) : MaterialContainer(Rank.Chest), ExternalContainer { - context(safeContext: SafeContext) - override val slots - get(): List = - if (ContainerHandler.lastInteractedBlockEntity is ChestBlockEntity) - safeContext.player.currentScreenHandler.containerSlots - else emptyList() - - override val description = - buildText { - literal("Chest at ") - highlighted(blockPos.toShortString()) - containedInStash?.let { stash -> - literal(" (contained in ") - highlighted(stash.name) - literal(")") - } - } - - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(exitAfter: Boolean, taskSupplier: TaskSupplier): Task<*> = - OpenContainerTask(blockPos, automatedSafeContext).thenAction { - taskSupplier.invoke(automatedSafeContext, Unit).thenAction { - if (exitAfter) automatedSafeContext.player.closeHandledScreen() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt deleted file mode 100644 index 5845263a1..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2026 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.material.container.containers - -import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.util.text.buildText -import com.lambda.util.text.literal -import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.inventory.SingleStackInventory -import net.minecraft.item.ItemStack -import net.minecraft.screen.PlayerScreenHandler -import net.minecraft.screen.slot.Slot - -data object CreativeContainer : MaterialContainer(Rank.Creative) { - context(_: SafeContext) - override val slots: List - get() = emptyList() - override var stacks = emptyList() - - override val swapMethodPriority = 11 - - override val description = buildText { literal("Creative") } - - context(safeContext : SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - clickCreativeStack(fromHere.stack, toSlot.id) - safeContext.player.currentScreenHandler.slots[toSlot.id].stack = fromHere.stack - } - - context(safeContext: SafeContext) - override fun getSlot(stackSelection: StackSelection) = - stackSelection.optimalStack?.let { stack -> - Slot( - object : SingleStackInventory { - override fun getStack() = stack - override fun setStack(stack: ItemStack?) {} - override fun markDirty() {} - override fun canPlayerUse(player: PlayerEntity?) = false - }, - 0, 0, 0 - ) - } - - context(safeContext: SafeContext) - override fun materialAvailable(selection: StackSelection): Int = - if (safeContext.player.isCreative && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 - - context(safeContext: SafeContext) - override fun spaceAvailable(selection: StackSelection): Int = - if (safeContext.player.isCreative && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 - - context(safeContext: SafeContext) - private val correctScreenHandler - get() = safeContext.player.currentScreenHandler is PlayerScreenHandler || safeContext.player.currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler -} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt deleted file mode 100644 index 1b0c9182a..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2026 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.material.container.containers - -import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.task.wrappers.TaskSupplier -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.tasks.PlaceContainerTask -import com.lambda.task.wrappers.thenAction -import com.lambda.task.wrappers.thenOrNull -import com.lambda.util.extension.containerSlots -import com.lambda.util.text.buildText -import com.lambda.util.text.literal -import net.minecraft.block.entity.EnderChestBlockEntity -import net.minecraft.item.ItemStack -import net.minecraft.item.Items - -object EnderChestContainer : MaterialContainer(Rank.EnderChest), ExternalContainer { - context(safeContext: SafeContext) - override val slots - get() = - if (ContainerHandler.lastInteractedBlockEntity is EnderChestBlockEntity) - safeContext.player.currentScreenHandler.containerSlots - else emptyList() - override var stacks = emptyList() - - override val description = buildText { literal("Ender Chest") } - - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(exitAfter: Boolean, taskSupplier: TaskSupplier) = - Items.ENDER_CHEST - .select() - .findSlotsWithMaterial() - .firstOrNull()?.let { slot -> - PlaceContainerTask(slot, automatedSafeContext).thenAction { pos -> - OpenContainerTask(pos, automatedSafeContext).thenAction { - taskSupplier.invoke(automatedSafeContext, Unit).thenOrNull { - if (exitAfter) automatedSafeContext.breakAndCollectBlock(pos, lifeMaintenance = false) - else null - } - } - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt deleted file mode 100644 index 88d936f34..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2026 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.material.container.containers - -import com.lambda.Lambda.mc -import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.util.player.SlotUtils.mainHandSlots -import com.lambda.util.text.buildText -import com.lambda.util.text.literal -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot - -object MainHandContainer : MaterialContainer(Rank.MainHand) { - context(safeContext: SafeContext) - override val slots: List - get() = safeContext.player.mainHandSlots - override var stacks: List - get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList() - set(_) {} - - override val swapMethodPriority = 10 - - override val description = buildText { literal("MainHand") } - - context(safeContext: SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - swap(toSlot.id, safeContext.player.inventory.selectedSlot) - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt deleted file mode 100644 index d5505e801..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2026 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.material.container.containers - -import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.task.wrappers.TaskSupplier -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.tasks.PlaceContainerTask -import com.lambda.task.wrappers.thenAction -import com.lambda.task.wrappers.thenOrNull -import com.lambda.threading.runSafe -import com.lambda.util.extension.containerSlots -import com.lambda.util.text.buildText -import com.lambda.util.text.highlighted -import com.lambda.util.text.literal -import net.minecraft.block.entity.ShulkerBoxBlockEntity -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot -import net.minecraft.util.math.BlockPos - -data class ShulkerBoxContainer( - override var stacks: List, - val containedIn: MaterialContainer, - val shulkerSlot: Slot, -) : MaterialContainer(Rank.ShulkerBox), ExternalContainer { - context(safeContext: SafeContext) - override val slots - get(): List = - if (ContainerHandler.lastInteractedBlockEntity is ShulkerBoxBlockEntity) - safeContext.player.currentScreenHandler.containerSlots - else emptyList() - - override val description = - buildText { - highlighted(shulkerSlot.stack.name.string) - literal(" in ") - highlighted(containedIn.name) - literal(" in slot ") - highlighted("${runSafe { slotInContainer }}") - } - - context(_: SafeContext) - private val slotInContainer: Int get() = containedIn.slots.indexOf(shulkerSlot) - - private var placePos = BlockPos.ORIGIN - - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(exitAfter: Boolean, taskSupplier: TaskSupplier) = - PlaceContainerTask(shulkerSlot, automatedSafeContext).thenAction { pos -> - placePos = pos - OpenContainerTask(pos, automatedSafeContext).thenAction { - taskSupplier.invoke(automatedSafeContext, Unit).thenOrNull { - if (exitAfter) automatedSafeContext.breakAndCollectBlock(placePos) - else null - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index 9b3d571e4..5a4372b02 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -19,7 +19,6 @@ package com.lambda.module import com.lambda.config.settings.complex.Bind import com.lambda.gui.Layout -import com.lambda.module.tag.ModuleTag import java.awt.Color abstract class HudModule( diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index 0f106d0db..593fdce38 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -23,8 +23,8 @@ import com.lambda.config.ConfigCategory import com.lambda.config.automation.IMutableAutomationConfig import com.lambda.config.automation.MutableAutomationConfig import com.lambda.config.categories.ModuleCategory -import com.lambda.config.entries.Setting.Companion.onValueChange -import com.lambda.config.entries.Setting.Companion.onValueChangeUnsafe +import com.lambda.config.entries.onValueChange +import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.config.settings.complex.KeybindSetting.Companion.onRelease @@ -41,7 +41,6 @@ import com.lambda.event.listener.SafeListener import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener import com.lambda.module.modules.client.Client -import com.lambda.module.tag.ModuleTag import com.lambda.sound.LambdaSound import com.lambda.sound.SoundHandler.play import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt b/src/main/kotlin/com/lambda/module/ModuleTag.kt similarity index 96% rename from src/main/kotlin/com/lambda/module/tag/ModuleTag.kt rename to src/main/kotlin/com/lambda/module/ModuleTag.kt index caab346cc..e01c0d70b 100644 --- a/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt +++ b/src/main/kotlin/com/lambda/module/ModuleTag.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.module.tag +package com.lambda.module import com.lambda.util.Nameable /** - * The [ModuleTag] class represents a tag, that can be associated in any cardinality with a [Module]. + * The [ModuleTag] class represents a tag, that can be associated in any cardinality with a [java.lang.Module]. * * Tags are used to categorize and organize modules, making them easier to find. * They can be custom created as per the user's needs. @@ -61,4 +61,4 @@ data class ModuleTag(override val name: String) : Nameable { fun isTagShown(tag: ModuleTag) = shownTags.contains(tag) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/hud/AccountName.kt b/src/main/kotlin/com/lambda/module/hud/AccountName.kt index 7b81bdaa3..24289c330 100644 --- a/src/main/kotlin/com/lambda/module/hud/AccountName.kt +++ b/src/main/kotlin/com/lambda/module/hud/AccountName.kt @@ -19,7 +19,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/hud/Baritone.kt b/src/main/kotlin/com/lambda/module/hud/Baritone.kt index 601602b71..fa0e9c10b 100644 --- a/src/main/kotlin/com/lambda/module/hud/Baritone.kt +++ b/src/main/kotlin/com/lambda/module/hud/Baritone.kt @@ -19,9 +19,9 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.interaction.construction.simulation.BuildGoal -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object Baritone : HudModule( diff --git a/src/main/kotlin/com/lambda/module/hud/Coordinates.kt b/src/main/kotlin/com/lambda/module/hud/Coordinates.kt index a09dae66f..5498a2d2a 100644 --- a/src/main/kotlin/com/lambda/module/hud/Coordinates.kt +++ b/src/main/kotlin/com/lambda/module/hud/Coordinates.kt @@ -23,7 +23,7 @@ import com.lambda.config.editSetting import com.lambda.config.withEdits import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.FormattingUtils.format import com.lambda.util.extension.dimensionName diff --git a/src/main/kotlin/com/lambda/module/hud/Fps.kt b/src/main/kotlin/com/lambda/module/hud/Fps.kt index 917238a02..2dcf3a3a9 100644 --- a/src/main/kotlin/com/lambda/module/hud/Fps.kt +++ b/src/main/kotlin/com/lambda/module/hud/Fps.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.collections.LimitedDecayQueue import kotlin.time.Duration.Companion.seconds diff --git a/src/main/kotlin/com/lambda/module/hud/ModuleList.kt b/src/main/kotlin/com/lambda/module/hud/ModuleList.kt index cd438fbc2..6180de437 100644 --- a/src/main/kotlin/com/lambda/module/hud/ModuleList.kt +++ b/src/main/kotlin/com/lambda/module/hud/ModuleList.kt @@ -21,7 +21,7 @@ import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.flag.ImGuiCol import com.lambda.module.HudModule import com.lambda.module.ModuleRegistry -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/hud/Rotation.kt b/src/main/kotlin/com/lambda/module/hud/Rotation.kt index b9246cd0b..6530c1992 100644 --- a/src/main/kotlin/com/lambda/module/hud/Rotation.kt +++ b/src/main/kotlin/com/lambda/module/hud/Rotation.kt @@ -22,7 +22,7 @@ import com.lambda.config.editSetting import com.lambda.config.withEdits import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.FormattingUtils.format diff --git a/src/main/kotlin/com/lambda/module/hud/Speedometer.kt b/src/main/kotlin/com/lambda/module/hud/Speedometer.kt index 5ef49be74..354e8b719 100644 --- a/src/main/kotlin/com/lambda/module/hud/Speedometer.kt +++ b/src/main/kotlin/com/lambda/module/hud/Speedometer.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.SpeedUnit import net.minecraft.util.math.Vec3d diff --git a/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt b/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt index eaa6aadf4..326b2b363 100644 --- a/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt +++ b/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt @@ -19,7 +19,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.RootTask @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/hud/Tps.kt b/src/main/kotlin/com/lambda/module/hud/Tps.kt index 3c94e178e..2d8c4fb97 100644 --- a/src/main/kotlin/com/lambda/module/hud/Tps.kt +++ b/src/main/kotlin/com/lambda/module/hud/Tps.kt @@ -20,7 +20,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImVec2 import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FormattingUtils.format import com.lambda.util.ServerTPSUtils import com.lambda.util.ServerTPSUtils.recentData diff --git a/src/main/kotlin/com/lambda/module/hud/Watermark.kt b/src/main/kotlin/com/lambda/module/hud/Watermark.kt index fa46da254..4436e3f2d 100644 --- a/src/main/kotlin/com/lambda/module/hud/Watermark.kt +++ b/src/main/kotlin/com/lambda/module/hud/Watermark.kt @@ -21,7 +21,7 @@ import com.lambda.graphics.texture.TextureOwner.upload import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImGui import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object Watermark : HudModule( diff --git a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt index 9f2199c93..cb55a5b9b 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt @@ -22,9 +22,9 @@ import com.lambda.config.ConfigBlock import com.lambda.config.Group import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.ChatUtils.addresses import com.lambda.util.ChatUtils.colors import com.lambda.util.ChatUtils.discord diff --git a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt index 9fafeecbb..f3a7fded9 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt @@ -20,13 +20,13 @@ package com.lambda.module.modules.chat import com.lambda.config.blocks.FormatterConfig import com.lambda.config.blocks.FormatterSettings import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.withEdits import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FormattingUtils.format import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt index 2c68b2abd..4cff328bd 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt @@ -21,9 +21,9 @@ import com.google.common.collect.Comparators.min import com.lambda.command.CommandRegistry.prefix import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt index b25e12a60..074b96cd8 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt @@ -20,9 +20,9 @@ package com.lambda.module.modules.chat import com.lambda.command.CommandRegistry.prefix import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.ChatUtils.toBlue import com.lambda.util.ChatUtils.toGreen import com.lambda.util.ChatUtils.toLeet diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt index 72d48f3b2..239dcec05 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt @@ -17,12 +17,12 @@ package com.lambda.module.modules.chat -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils.logError import com.lambda.util.text.MessageType diff --git a/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt b/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt index 9d62142d3..4d3dd975e 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt @@ -26,7 +26,7 @@ import com.lambda.gui.dsl.ImGuiBuilder.popupModal import com.lambda.imgui.ImGui import com.lambda.imgui.flag.ImGuiWindowFlags import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils.debug import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/module/modules/client/Capes.kt b/src/main/kotlin/com/lambda/module/modules/client/Capes.kt index e1e42039f..bb42498e2 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/Capes.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/Capes.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.client import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Capes : Module( name = "Capes", diff --git a/src/main/kotlin/com/lambda/module/modules/client/Client.kt b/src/main/kotlin/com/lambda/module/modules/client/Client.kt index 180314afd..a56bd2565 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/Client.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/Client.kt @@ -22,7 +22,7 @@ import com.lambda.config.Tab import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color object Client : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/client/Discord.kt b/src/main/kotlin/com/lambda/module/modules/client/Discord.kt index 7858e254f..2679a1052 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/Discord.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/Discord.kt @@ -24,7 +24,7 @@ import com.lambda.event.EventFlow import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listenOnce import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.network.NetworkHandler.updateToken import com.lambda.network.api.v1.endpoints.linkDiscord import com.lambda.threading.runConcurrent diff --git a/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt b/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt index 95425ca6b..40a791379 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.client import com.lambda.event.events.ModuleEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.log import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index fde140314..0aa6a9298 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress @@ -25,13 +25,16 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemUtils.armorSlot -import com.lambda.util.player.SlotUtils.armorSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.component.DataComponentTypes import net.minecraft.enchantment.Enchantment import net.minecraft.enchantment.Enchantments @@ -72,18 +75,19 @@ object AutoArmor : Module( else 0.0 } else 0.0 }.thenByDescending { - it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers + val modifiers = it.stack.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + ?: it.stack.item.components.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + modifiers?.modifiers ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } ?.modifier?.value ?: 0.0 }.thenByDescending { - it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers + val modifiers = it.stack.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + ?: it.stack.item.components.get(DataComponentTypes.ATTRIBUTE_MODIFIERS) + modifiers?.modifiers ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } ?.modifier?.value ?: 0.0 - }.thenByDescending { val stack = it.stack when { stack.isIn(ItemTags.FOOT_ARMOR) -> stack.getEnchantment(feetProtection.enchant) @@ -119,18 +123,23 @@ object AutoArmor : Module( fun SafeContext.tick() { tickedThisTick = true - val armorSlots = player.armorSlots - - val swappable = player.hotbarAndInventorySlots - .filter { it.stack.isEquipable && (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) } + val armorSlots = findContainer(containerSelection = ArmorContainer.select())?.slots ?: return + + val swappable = findContainers(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY) + .flatMap { it.slots } + .filter { + it.stack.isEquipable(armorSlots) && + (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) + } .sortedWith(SORTER) .distinctBy { it.stack.armorSlot } val swaps = mutableListOf>() armorSlots.forEach { equipped -> - val new = swappable.find { new -> - equipped.canInsert(new.stack) && SORTER.compare(equipped, new) > 0 - } ?: return@forEach + val new = + swappable.find { new -> + equipped.canInsert(new.stack) && SORTER.compare(equipped, new) > 0 + } ?: return@forEach swaps.add(Pair(new, equipped)) } @@ -144,9 +153,8 @@ object AutoArmor : Module( }.submit() } - context(safeContext: SafeContext) - private val ItemStack.isEquipable - get() = safeContext.player.armorSlots.any { it.canInsert(this) } + context(_: SafeContext) + private fun ItemStack.isEquipable(armorSlots: List) = armorSlots.any { it.canInsert(this) } private enum class Protection(val enchant: RegistryKey) { Protection(Enchantments.PROTECTION), diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index da32a1807..2a39fbea3 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -17,22 +17,26 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.FriendHandler -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.FriendHandler +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth import com.lambda.util.extension.tickDeltaF import com.lambda.util.world.fastEntitySearch +import com.lambda.util.Describable +import com.lambda.util.NamedEnum +import net.minecraft.registry.tag.ItemTags +import net.minecraft.item.Item import net.minecraft.entity.mob.CreeperEntity import net.minecraft.entity.player.PlayerEntity import net.minecraft.item.Items @@ -40,20 +44,42 @@ import net.minecraft.item.Items @Suppress("unused") object AutoTotem : Module( name = "AutoTotem", - description = "Swaps the your off-hand item to a totem", + description = "Intelligently manages offhand items with CPvP modes (Totem, Crystal, Gapple) and safety swaps", tag = ModuleTag.COMBAT, modulePriority = 100 ) { - private val always by setting("Always", true, "Always attempt to keep a totem in offhand") - private val ignoreWhenHolding by setting("Ignore When Holding", false, "Ignore swapping to offhand when already holding a totem") - private val minimumHealth by setting("Min Health", 10, 6..36, 1, "Set the minimum health threshold to swap", unit = " half-hearts") { !always } - private val falls by setting("Falls", true, "Swap if the player will die of fall damage") { !always } - private val fallDistance by setting("Falls Time", 10, 0..30, 1, "Number of blocks fallen before swapping", unit = " blocks") { !always && falls } - private val crystals by setting("Crystals", true, "Swap if an End Crystal explosion would be lethal") { !always } - private val creeper by setting("Creepers", true, "Swap when an ignited Creeper is nearby") { !always } - private val players by setting("Players", false, "Swap if a nearby player is detected within the set distance") { !always } - private val minPlayerDistance by setting("Player Distance", 64, 32..128, 4, "Set the distance to detect players to swap") { !always && players } - private val friends by setting("Friends", false, "Exclude friends from triggering player-based swaps") { !always && players } + enum class OffhandMode( + override val displayName: String, + override val description: String, + val item: Item + ) : NamedEnum, Describable { + Totem("Totem", "Always equips Totem of Undying in the offhand.", Items.TOTEM_OF_UNDYING), + Crystal("Crystal", "Equips End Crystals in the offhand for CPvP.", Items.END_CRYSTAL), + Gapple("Gapple", "Equips Enchanted Golden Apples in the offhand.", Items.ENCHANTED_GOLDEN_APPLE), + GoldenApple("Golden Apple", "Equips regular Golden Apples in the offhand.", Items.GOLDEN_APPLE), + Shield("Shield", "Equips a Shield in the offhand for defense.", Items.SHIELD) + } + + private val mode by setting("Mode", OffhandMode.Totem, "Primary item to hold in the offhand when safe") + private val always by setting("Always Totem", false, "Always keeps a totem in the offhand regardless of safe state or selected mode") + private val swordGap by setting("Sword Gap", true, "Swaps to Golden Apple when holding a sword or axe and pressing use/right-click") + private val crystalOnCA by setting("Crystal on CA", true, "Automatically equips crystals in offhand when CrystalAura is active") + private val fallbackToTotem by setting("Fallback to Totem", true, "Falls back to a Totem if the desired offhand item is missing from inventory") + private val ignoreWhenHolding by setting("Ignore When Holding", false, "Ignore swapping to offhand when already holding a totem in main hand") + private val minimumHealth by setting("Min Health", 12, 4..36, 1, "Minimum health threshold to force a totem swap", unit = " half-hearts") + private val falls by setting("Falls", true, "Swap to totem if the player will die of fall damage") + private val fallDistance by setting("Falls Distance", 8, 0..30, 1, "Number of blocks fallen before swapping", unit = " blocks") { falls } + private val crystals by setting("Crystals", true, "Swap to totem if an End Crystal explosion would be lethal") + private val creeper by setting("Creepers", true, "Swap to totem when an ignited Creeper is nearby") + private val elytraSafety by setting("Elytra Safety", true, "Swap to totem when flying with Elytra at dangerous speeds or low altitude") + private val players by setting("Players", false, "Swap to totem if a nearby hostile player is detected") + private val minPlayerDistance by setting("Player Distance", 32, 8..64, 2, "Distance to detect players to force totem") { players } + private val friends by setting("Friends", false, "Exclude friends from triggering player-based swaps") { players } + + @JvmStatic var offhandOverride: (() -> Boolean)? = null + + fun isDangerous(context: SafeContext): Boolean = + Reason.entries.any { it.check(context) } init { setDefaultAutomationConfig() @@ -62,26 +88,61 @@ object AutoTotem : Module( } listen { - if (!always && Reason.entries.none { it.check(this) }) return@listen - - if ((!ignoreWhenHolding || !player.isHolding(Items.TOTEM_OF_UNDYING)) && player.offHandStack.item != Items.TOTEM_OF_UNDYING) { - Items.TOTEM_OF_UNDYING.select() - .filterSlots(player.currentScreenHandler.slots) - .takeIf { it.isNotEmpty() } - ?.let { totems -> - val cursor = player.currentScreenHandler.cursorStack - val targetSlot = player.currentScreenHandler.slots - .findLast { !cursor.isEmpty && it.canInsert(cursor) } - - inventoryRequest { - targetSlot?.let { pickup(it.id, 0) } - swap(totems.first().id, 40) - }.submit() - } + // Prevent swapping when an external container (chest, furnace, etc.) is open + if (player.currentScreenHandler !== player.playerScreenHandler && player.currentScreenHandler.syncId != 0) return@listen + + val dangerous = isDangerous(this) + if (!dangerous && offhandOverride?.invoke() == true) return@listen + + val targetItem = when { + dangerous || always -> Items.TOTEM_OF_UNDYING + swordGap && isHoldingWeaponAndUsing() -> Items.ENCHANTED_GOLDEN_APPLE + crystalOnCA && CrystalAura.isEnabled -> Items.END_CRYSTAL + else -> mode.item } + + equipOffhand(targetItem) } } + private fun SafeContext.isHoldingWeaponAndUsing(): Boolean { + val holdingWeapon = player.mainHandStack.isIn(ItemTags.SWORDS) || player.mainHandStack.isIn(ItemTags.AXES) + return holdingWeapon && mc.options.useKey.isPressed + } + + private fun SafeContext.equipOffhand(targetItem: Item) { + if (player.offHandStack.item == targetItem) return + if (targetItem == Items.TOTEM_OF_UNDYING && ignoreWhenHolding && player.isHolding(Items.TOTEM_OF_UNDYING)) return + + // Find the slot with target item in player's current screen handler + var slot = targetItem.select() + .filter(player.currentScreenHandler.slots) + .firstOrNull() + + // Fallback between enchanted golden apple and standard golden apple for eating + if (slot == null && targetItem == Items.ENCHANTED_GOLDEN_APPLE) { + slot = Items.GOLDEN_APPLE.select().filter(player.currentScreenHandler.slots).firstOrNull() + } else if (slot == null && targetItem == Items.GOLDEN_APPLE) { + slot = Items.ENCHANTED_GOLDEN_APPLE.select().filter(player.currentScreenHandler.slots).firstOrNull() + } + + // If requested item is missing, fallback to totem if permitted + if (slot == null && targetItem != Items.TOTEM_OF_UNDYING && fallbackToTotem) { + slot = Items.TOTEM_OF_UNDYING.select().filter(player.currentScreenHandler.slots).firstOrNull() + } + + if (slot == null) return + if (player.offHandStack.item == slot.stack.item) return + + val cursor = player.currentScreenHandler.cursorStack + val emptySlot = player.currentScreenHandler.slots.findLast { !cursor.isEmpty && it.canInsert(cursor) } + + inventoryRequest { + emptySlot?.let { pickup(it.id, 0) } + swapWithHotbar(slot.id, 40) + }.submit() + } + enum class Reason(val check: SafeContext.() -> Boolean) { Health({ player.fullHealth < minimumHealth }), Creeper({ creeper && fastEntitySearch(15.0).any { @@ -94,6 +155,7 @@ object AutoTotem : Module( && (!friends || !FriendHandler.isFriend(otherPlayer.uuid)) } }), EndCrystal({ crystals && hasDeadlyCrystal() }), - FallDamage({ falls && isFallDeadly() && player.fallDistance > fallDistance }) + FallDamage({ falls && (isFallDeadly() || player.fallDistance > fallDistance) }), + Elytra({ elytraSafety && player.isGliding && (player.velocity.lengthSquared() > 0.8 || player.y < player.world.bottomY + 25) }) } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt index a4c848b0d..2672268f3 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt @@ -20,10 +20,10 @@ package com.lambda.module.modules.combat import com.lambda.context.SafeContext import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.extension.rotation import com.lambda.util.math.component1 @@ -35,6 +35,8 @@ import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction +import net.minecraft.entity.effect.StatusEffects +import net.minecraft.util.math.Vec3d @Suppress("unused") object Criticals : Module( @@ -43,7 +45,9 @@ object Criticals : Module( tag = ModuleTag.COMBAT, ) { enum class Mode { - Grim + Grim, + Packet, + MiniJump } private val mode by setting("Mode", Mode.Grim) @@ -55,7 +59,7 @@ object Criticals : Module( if (player.isOnGround) posPacket(0.00000001, rotation = player.rotation) posPacket(-0.000000001, rotation = player.eyePos.rotationTo(it.entity.boundingBox.center)) - connection.sendPacket(PlayerInteractItemC2SPacket(Hand.OFF_HAND, 0, player.yaw, player.pitch)) // TODO: This is wrong, fix it + connection.sendPacket(PlayerInteractItemC2SPacket(Hand.OFF_HAND, 0, player.yaw, player.pitch)) // TODO: offhand eating Grim desync fix connection.sendPacket { PlayerActionC2SPacket( PlayerActionC2SPacket.Action.RELEASE_USE_ITEM, @@ -64,6 +68,19 @@ object Criticals : Module( ) } } + Mode.Packet -> { + if (!player.isOnGround || player.isTouchingWater || player.isInLava || player.hasStatusEffect(StatusEffects.BLINDNESS)) return@listen + posPacket(0.0625, false, null) + posPacket(0.0, false, null) + posPacket(0.0125, false, null) + posPacket(0.0, false, null) + } + Mode.MiniJump -> { + if (player.isOnGround) { + player.jump() + player.velocity = Vec3d(player.velocity.x, 0.25, player.velocity.z) + } + } } } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 9bfa55a20..2c3fe1e74 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.combat import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings import com.lambda.config.hide import com.lambda.config.hideAllExcept @@ -27,16 +27,18 @@ import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.ContainerHandler.transfer -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.containers.HotbarContainer -import com.lambda.interaction.material.container.containers.OffHandContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findSlot +import com.lambda.interaction.handler.handlers.move +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled @@ -49,18 +51,19 @@ import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.extension.fullHealth import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep +import com.lambda.util.math.blockPos import com.lambda.util.math.distSq -import com.lambda.util.math.flooredBlockPos import com.lambda.util.math.getHitVec import com.lambda.util.math.minus import com.lambda.util.math.plus import com.lambda.util.player.RotationUtils.getVisibleSurfaces -import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.world.fastEntitySearch import net.minecraft.block.Blocks import net.minecraft.entity.Entity import net.minecraft.entity.LivingEntity import net.minecraft.entity.decoration.EndCrystalEntity +import net.minecraft.entity.effect.StatusEffects +import net.minecraft.registry.tag.ItemTags import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket @@ -100,6 +103,9 @@ object CrystalAura : Module( @Tab(PLACEMENT_TAB) private val priorityMode by setting("Crystal Priority", Priority.Damage) @Tab(PLACEMENT_TAB) private val minDamageAdvantage by setting("Min Damage Advantage", 4.0, 1.0..10.0, 0.5) { priorityMode == Priority.Advantage } @Tab(PLACEMENT_TAB) private val minTargetDamage by setting("Min Target Damage", 8.0, 0.0..20.0, 0.5, "Minimum target damage to use crystals") + @Tab(PLACEMENT_TAB) private val facePlace by setting("Face Place", true, "Places crystals at lower damage thresholds to finish weak targets or break armor") + @Tab(PLACEMENT_TAB) private val facePlaceHealth by setting("Face Place Health", 10.0, 1.0..36.0, 0.5, "Maximum target health to trigger Face Place") { facePlace } + @Tab(PLACEMENT_TAB) private val facePlaceArmor by setting("Face Place Armor", 15, 1..100, 1, "Armor durability percentage to trigger Face Place", unit = "%") { facePlace } @Tab(PLACEMENT_TAB) private val maxSelfDamage by setting("Max Self Damage", 8.0, 0.0..36.0, 0.5, "Maximum self damage to use crystals") @Tab(PLACEMENT_TAB) private val minPlaceHealth by setting("Min Place Health", 5.0, 0.0..36.0, 0.5, "Minimum player health to place crystals") @Tab(PLACEMENT_TAB) private val preventDeath by setting("Prevent Death", true, "Prevent death by crystal") @@ -107,6 +113,7 @@ object CrystalAura : Module( @Tab(EXPLODING_TAB) private val explodeRange by setting("Explode Range", 3.0, 1.0..7.0, 0.1, "Range to explode crystals", " blocks") @Tab(EXPLODING_TAB) private val explodeDelay by setting("Explode Delay", 10L, 0L..1000L, 1L, "Delay between explosion attempts", " ms") + @Tab(EXPLODING_TAB) private val antiWeakness by setting("Anti Weakness", true, "Swaps to a weapon before exploding when under the Weakness effect") @Tab(PREDICTION_TAB) private val prediction by setting("Prediction", PredictionMode.None) @Tab(PREDICTION_TAB) private val packetPredictions by setting("Packet Predictions", 1, 0..20, 1) { prediction.onPacket } @@ -296,6 +303,13 @@ object CrystalAura : Module( } private fun SafeContext.explodeInternal(id: Int) { + if (antiWeakness && player.hasStatusEffect(StatusEffects.WEAKNESS)) { + val weaponIndex = findWeaponSlotIndex() + if (weaponIndex != null && weaponIndex != player.inventory.selectedSlot) { + hotbarRequest(weaponIndex).submit() + } + } + connection.sendPacket { PlayerInteractEntityC2SPacket( id, player.isSneaking, PlayerInteractEntityC2SPacket.ATTACK @@ -305,10 +319,42 @@ object CrystalAura : Module( player.swingHand(Hand.MAIN_HAND) } + private fun SafeContext.findWeaponSlotIndex(): Int? { + for (i in 0..8) { + val stack = player.inventory.getStack(i) + if (stack.isIn(ItemTags.SWORDS) || stack.isIn(ItemTags.AXES)) { + return i + } + } + return null + } + + private fun isArmorLow(entity: LivingEntity, thresholdPercent: Int): Boolean { + val armorSlots = arrayOf( + net.minecraft.entity.EquipmentSlot.HEAD, + net.minecraft.entity.EquipmentSlot.CHEST, + net.minecraft.entity.EquipmentSlot.LEGS, + net.minecraft.entity.EquipmentSlot.FEET + ) + for (slot in armorSlots) { + val armorStack = entity.getEquippedStack(slot) + if (armorStack.isEmpty || !armorStack.isDamageable) continue + val duraPercent = (1.0 - armorStack.damage.toDouble() / armorStack.maxDamage.toDouble()) * 100.0 + if (duraPercent <= thresholdPercent) return true + } + return false + } + private fun SafeContext.updateBlueprint(target: LivingEntity) = updateTimer.runIfPassed(updateDelay.milliseconds) { resetBlueprint() + val isFacePlacing = facePlace && ( + target.health <= facePlaceHealth || + isArmorLow(target, facePlaceArmor) + ) + val effectiveMinTargetDamage = if (isFacePlacing) 2.0 else minTargetDamage + fun info( pos: BlockPos, target: LivingEntity, @@ -318,8 +364,7 @@ object CrystalAura : Module( val crystalPos = pos.crystalPosition val targetDamage = crystalDamage(crystalPos, target) - if (targetDamage < minTargetDamage) return null - + if (targetDamage < effectiveMinTargetDamage) return null val selfDamage = crystalDamage(crystalPos, player) if (selfDamage > maxSelfDamage || player.fullHealth - selfDamage <= minPlaceHealth || @@ -361,7 +406,7 @@ object CrystalAura : Module( val entitiesNearby = fastEntitySearch(3.5, pos) val crystals = entitiesNearby.filterIsInstance() - val otherEntities = entitiesNearby - crystals + player + val otherEntities = entitiesNearby - crystals.toSet() + player if (otherEntities.any { it.boundingBox.intersects(crystalBox) @@ -482,21 +527,32 @@ object CrystalAura : Module( if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return@runSafe - val selection = selectStack { isItem(Items.END_CRYSTAL) } + val selection = Items.END_CRYSTAL.select() + if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != selection.item) || (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) ) runSafeAutomated { if (!swap) return@runSafe - var crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } - if (crystalSlot < 0) { - val swapTo = when (swapHand) { - Hand.MAIN_HAND -> HotbarContainer - Hand.OFF_HAND -> OffHandContainer - } - if (!selection.transfer(swapTo)) return@runSafe - crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } + + val toContainerSelection = + when (swapHand) { + Hand.MAIN_HAND -> HotbarContainer + Hand.OFF_HAND -> OffHandContainer + }.select() + + val crystalSlot = findSlot(selection, toContainerSelection) + + if (crystalSlot == null) { + if (!selection.move(InventoryContainer.select(), toContainerSelection)) return@runSafe + } + + if (swapHand == Hand.MAIN_HAND) { + val crystalSlot = selection + .bestMatch(HotbarContainer.slots) + ?.index + ?: return@runSafe + if (crystalSlot < 0 || !hotbarRequest(crystalSlot).submit().done) return@runSafe } - if (!HotbarRequest(crystalSlot, this).submit().done) return@runSafe } placeTimer.runSafeIfPassed(placeDelay.milliseconds) { @@ -533,7 +589,7 @@ object CrystalAura : Module( } private val EndCrystalEntity.baseBlockPos get() = - (pos - Vec3d(0.0, 0.5, 0.0)).flooredBlockPos + (pos - Vec3d(0.0, 0.5, 0.0)).blockPos private val BlockPos.crystalPosition get() = this.getHitVec(Direction.UP) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt b/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt index 266fad905..318fad2fb 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.SafeListener.Companion.listenConcurrently import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.network.mojang.getProfile import com.lambda.threading.onShutdown import com.lambda.util.Timer diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 878d4470e..52ecd3f32 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.combat import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings import com.lambda.config.editSetting import com.lambda.config.hide @@ -28,20 +28,22 @@ import com.lambda.context.SafeContext import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.NamedEnum import com.lambda.util.item.ItemStackUtils.attackDamage import com.lambda.util.item.ItemStackUtils.attackSpeed import com.lambda.util.math.random import com.lambda.util.player.RotationUtils.lookAtEntity -import com.lambda.util.player.SlotUtils.hotbarStacks import net.minecraft.entity.Entity import net.minecraft.item.ItemStack +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.AxeItem import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.util.Hand import net.minecraft.world.GameMode @@ -57,6 +59,7 @@ object KillAura : Module( @Tab(GENERAL_TAB) private val rotate by setting("Rotate", true) @Tab(GENERAL_TAB) private val swap by setting("Swap", true, "Swap to the item with the highest damage") + @Tab(GENERAL_TAB) private val antiShield by setting("Anti Shield", true, "Swaps to an axe when target is blocking with a shield to disable it") @Tab(GENERAL_TAB) private val disableWhileGliding by setting("Disable While Gliding", false, "Disables when gliding with an elytra") @Tab(GENERAL_TAB) private val damageMode by setting("Damage Mode", DamageMode.Dps) @Tab(GENERAL_TAB) private val attackMode by setting("Attack Mode", AttackMode.Cooldown) @@ -75,6 +78,7 @@ object KillAura : Module( private var lastAttackTime = 0L private var hitDelay = 100.0 private var cooldownFromSwap = false + @JvmStatic var isPaused = false enum class AttackMode { Cooldown, @@ -106,8 +110,7 @@ object KillAura : Module( listen { cooldownFromSwap = true } listen { - if (disableWhileGliding && player.isGliding) return@listen - + if (isPaused || (disableWhileGliding && player.isGliding)) return@listen target?.let { entity -> // Wait until the rotation has a hit result on the entity var rotated = true @@ -119,13 +122,34 @@ object KillAura : Module( } if (swap) { - val selection = selectStack().sortByDescending { - damageMode.block(this, it) - } - - selection.bestItemMatch(player.hotbarStacks)?.let { bestStack -> - val slotId = player.hotbarStacks.indexOf(bestStack) - if (!HotbarRequest(slotId, this@KillAura, nowOrNothing = false).submit().done) return@listen + val targetIsBlocking = antiShield && entity is PlayerEntity && entity.isBlocking + val selection = + stackSelection { + if (targetIsBlocking) { + isItem() + } + sortedWith { + compareByDescending { + damageMode.block(this@listen, it.stack) + } + } + } + + val matchedStack = selection.bestMatch(HotbarContainer.stacks) + ?: if (targetIsBlocking) { + // Fallback to highest damage weapon if no axe found + stackSelection { + sortedWith { + compareByDescending { + damageMode.block(this@listen, it.stack) + } + } + }.bestMatch(HotbarContainer.stacks) + } else null + + matchedStack?.let { bestStack -> + val slotId = HotbarContainer.stacks.indexOf(bestStack) + if (slotId >= 0 && !hotbarRequest(slotId).submit().done) return@listen } } @@ -140,9 +164,9 @@ object KillAura : Module( cooldownFromSwap = false // Attack - connection.sendPacket(PlayerInteractEntityC2SPacket.attack(target, player.isSneaking)) + connection.sendPacket(PlayerInteractEntityC2SPacket.attack(entity, player.isSneaking)) if (interaction.gameMode != GameMode.SPECTATOR) { - player.attack(target) + player.attack(entity) player.resetTicksSince() } if (interactConfig.swing) player.swingHand(Hand.MAIN_HAND) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 104fb4a25..9755fdb84 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -17,24 +17,26 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings import com.lambda.config.hideBlock import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.findStack import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.start +import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.shrinkByEpsilon import com.lambda.util.item.ItemUtils.block -import com.lambda.util.math.flooredBlockPos -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks +import com.lambda.util.math.blockPos import com.lambda.util.world.entitySearch import net.minecraft.block.Blocks import net.minecraft.client.network.OtherClientPlayerEntity @@ -69,23 +71,35 @@ object PlayerTrap : Module( onEnable { task = tickingBlueprint { - val block = player.hotbarAndInventoryStacks.firstOrNull { - it.item is BlockItem && blocks.contains(it.item.block) - }?.item?.block ?: return@tickingBlueprint emptyMap() - val targetPlayer = if (self) player - else entitySearch( - buildConfig.blockReach, - player.eyePos.flooredBlockPos - ).firstOrNull { friends || !isFriend(it.gameProfile) } ?: return@tickingBlueprint emptyMap() + val selection = + stackSelection { + predicate { stack, _ -> + stack.item is BlockItem && blocks.contains(stack.item.block) + } + } + + val block = (findStack(selection, ContainerSelection.HOTBAR_AND_INVENTORY)?.item as? BlockItem) + ?.block + ?: return@tickingBlueprint emptyMap() + + val targetPlayer = + if (self) player + else entitySearch( + buildConfig.blockReach, + player.eyePos.blockPos + ).firstOrNull { friends || !isFriend(it.gameProfile) } + ?: return@tickingBlueprint emptyMap() + getTrapPositions(targetPlayer).associateWith { TargetState.Block(block) } - }.build(finishOnDone = false).run() + }.build(finishOnDone = false) + .start() } onDisable { task?.cancel(); task = null } } fun SafeContext.getTrapPositions(player: PlayerEntity): Set { - val min = player.boundingBox.shrinkByEpsilon().minPos.flooredBlockPos.add(-1, -1, -1) - val max = player.boundingBox.shrinkByEpsilon().maxPos.flooredBlockPos.add(1, 1, 1) + val min = player.boundingBox.shrinkByEpsilon().minPos.blockPos.add(-1, -1, -1) + val max = player.boundingBox.shrinkByEpsilon().maxPos.blockPos.add(1, 1, 1) return buildSet { (min.x + 1.. diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index 52b790afc..3e2fae990 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -17,23 +17,28 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings import com.lambda.config.hideBlock import com.lambda.config.withEdits +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.findStack import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.start +import com.lambda.task.tasks.build import com.lambda.util.item.ItemUtils.block -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import net.minecraft.block.Blocks import net.minecraft.item.BlockItem - +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket +import net.minecraft.util.math.BlockPos @Suppress("unused") object Surround : Module( name = "Surround", @@ -41,8 +46,13 @@ object Surround : Module( tag = ModuleTag.COMBAT ) { private val blocks by setting("Blocks", setOf(Blocks.OBSIDIAN, Blocks.ENDER_CHEST, Blocks.CRYING_OBSIDIAN)) + private val autoCenter by setting("Auto Center", true, "Snaps the player into the center of the block upon enabling") + private val disableOnJump by setting("Disable On Jump", true, "Automatically disables Surround when jumping or leaving the hole") + private val antiCity by setting("Anti City", true, "Places extra diagonal blocks to prevent city mining") + private val floor by setting("Floor", true, "Places a block under feet if standing on air") private var task: Task<*>? = null + private var startY = 0.0 init { setDefaultAutomationConfig() @@ -57,16 +67,56 @@ object Surround : Module( hideBlock(::eatConfig) } + listen { + if (disableOnJump && (mc.options.jumpKey.isPressed || player.y > startY + 0.25 || (!player.isOnGround && player.y < startY - 0.5))) { + disable() + } + } + onEnable { + startY = player.y + if (autoCenter) { + val center = player.blockPos.toCenterPos() + player.setPosition(center.x, player.y, center.z) + connection.sendPacket( + PlayerMoveC2SPacket.PositionAndOnGround( + center.x, player.y, center.z, player.isOnGround, player.horizontalCollision + ) + ) + } + task = tickingBlueprint { - val block = player.hotbarAndInventoryStacks.firstOrNull { - it.item is BlockItem && blocks.contains(it.item.block) - }?.item?.block ?: return@tickingBlueprint emptyMap() - getTrapPositions(player) - .filter { it.y <= player.blockPos.y } - .associateWith { TargetState.Block(block) } - }.build(finishOnDone = false).run() + val selection = + stackSelection { + predicate { stack, _ -> + stack.item is BlockItem && blocks.contains(stack.item.block) + } + } + + val block = (findStack(selection, ContainerSelection.HOTBAR_AND_INVENTORY)?.item as? BlockItem) + ?.block + ?: return@tickingBlueprint emptyMap() + + val trapPositions = getTrapPositions(player).filter { + if (floor) it.y <= player.blockPos.y else it.y == player.blockPos.y + }.toMutableSet() + + if (antiCity) { + val feet = player.blockPos + trapPositions.add(feet.add(1, 0, 1)) + trapPositions.add(feet.add(1, 0, -1)) + trapPositions.add(feet.add(-1, 0, 1)) + trapPositions.add(feet.add(-1, 0, -1)) + } + + trapPositions.associateWith { TargetState.Block(block) } + }.build(finishOnDone = false) + .start() + } + onDisable { + task?.cancel() + task = null + startY = 0.0 } - onDisable { task?.cancel(); task = null } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt index 20f684903..0d968759d 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt @@ -26,9 +26,9 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils import com.lambda.util.CommunicationUtils.info @@ -41,8 +41,8 @@ import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth import com.lambda.util.extension.tickDeltaF -import com.lambda.util.item.ItemStackUtils.bundleContents -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents +import com.lambda.util.item.ItemStackUtils.bundleStacks +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.player.MovementUtils.moveDelta import com.lambda.util.player.PlayerUtils.isIn2b2tQueue import com.lambda.util.player.SlotUtils.allStacks @@ -706,7 +706,7 @@ object AutoDisconnect : Module( private fun itemRow(stack: ItemStack, label: String? = null, selected: Boolean = false): DetailSection { val header = itemHeader(stack, label, selected) val enchantments = stack.forEachEnchantment { entry, level -> Enchantment.getName(entry, level) } - val contents = (stack.shulkerBoxContents + stack.bundleContents).filter { !it.isEmpty } + val contents = (stack.shulkerBoxStacks + stack.bundleStacks).filter { !it.isEmpty } if (enchantments.isEmpty() && contents.isEmpty()) { return DetailSection.TextSection(header) diff --git a/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt index 0edf97ccc..82e684486 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt @@ -20,9 +20,9 @@ package com.lambda.module.modules.debug import baritone.api.pathing.goals.GoalXZ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object BaritoneTest : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt index 8dd42d214..71db39448 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.world.blockSearch import net.minecraft.block.Blocks diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index fc9ceb73a..364e2bc90 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -19,11 +19,11 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.container.selection.select import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run -import com.lambda.task.tasks.AcquireMaterialTask.Companion.acquire +import com.lambda.module.ModuleTag +import com.lambda.task.start +import com.lambda.task.tasks.acquireStack import net.minecraft.item.Items @Suppress("unused") @@ -38,9 +38,8 @@ object ContainerTest : Module( } onEnable { - acquire { - Items.OBSIDIAN.select() - }.run() + acquireStack { Items.OBSIDIAN.select() } + .start() } } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt index 3e417f071..e4abf5b44 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.debug import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object DebugRendererModule: Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt index d397b76a5..9ec65d9e8 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.combat.DamageUtils.fallDamage import com.lambda.util.combat.DamageUtils.isFallDeadly diff --git a/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index 47ec92134..85af88c45 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.DynamicReflectionSerializer.dynamicString import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket diff --git a/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt b/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt index 6b5a18da1..77a3dabd2 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.debug import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FolderRegistry import com.lambda.util.extension.resolveFile import net.minecraft.block.Block diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index 1a0dc8dc7..5d5b177f5 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -21,7 +21,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.graphics.util.DynamicAABB.Companion.dynamicBox import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.tickDelta import com.lambda.util.math.setAlpha diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt index 0e8692900..23b48c4a1 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt @@ -32,7 +32,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.graphics.outline.OutlineStyle import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.prevPos import com.lambda.util.extension.tickDelta diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt index 6e8a3e4a0..b5d63290b 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt @@ -19,9 +19,9 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.player.RotationUtils.lookAt import net.minecraft.util.hit.HitResult diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt index 2facef200..f3430fc74 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt @@ -26,7 +26,7 @@ import com.lambda.config.blocks.ScreenTextSettings import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.blocks.WorldTextSettings import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object SettingsTestModule : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt index 092419a65..9c3e8df60 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt @@ -17,14 +17,14 @@ package com.lambda.module.modules.debug -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info object SilentSwap : Module( @@ -39,7 +39,7 @@ object SilentSwap : Module( } listen { - if (!HotbarRequest(0, this@SilentSwap).submit().done) { + if (!hotbarRequest(0).submit().done) { it.cancel() 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 1cf5a0a47..1789ca0ca 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.info import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt index 25073151a..10b6abe2c 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.ClientEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.SafeListener.Companion.listenConcurrently import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt index ba2af3e0f..15e4f463c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt @@ -21,10 +21,10 @@ import baritone.api.pathing.goals.GoalXZ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.BlockPosIterators import com.lambda.util.extension.isNether import com.lambda.util.player.RotationUtils.lookAt diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt index 2c5492a24..9218fe002 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt @@ -22,10 +22,10 @@ import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.interacting.InteractManager +import com.lambda.interaction.manager.managers.breaking.BreakManager +import com.lambda.interaction.manager.managers.interacting.InteractManager import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.MathUtils.toDouble import com.lambda.util.player.MovementUtils.roundedForward import com.lambda.util.player.MovementUtils.roundedStrafing diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt index 281766a82..abcfe1088 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt @@ -27,8 +27,8 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.util.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag import com.lambda.util.ClientPacket import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index fe29c57b6..c5e81af76 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.movement import com.lambda.Lambda import com.lambda.Lambda.mc -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind @@ -28,21 +28,23 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.GlideHandler -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.GlideHandler +import com.lambda.interaction.handler.handlers.findStack +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.warn import com.lambda.util.KeyCode import com.lambda.util.Mouse import com.lambda.util.player.PlayerUtils import com.lambda.util.player.PlayerUtils.canStartGliding -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks -import com.lambda.util.player.SlotUtils.hotbarStacks import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket @@ -84,9 +86,11 @@ object BetterFirework : Module( private var takeoffState = TakeoffState.Idle val ClientPlayerEntity.hasFireworks: Boolean - get() = Items.FIREWORK_ROCKET.select() - .filterStacks(hotbarAndInventoryStacks) - .isNotEmpty() || offHandStack.item == Items.FIREWORK_ROCKET + get() = + findStack( + Items.FIREWORK_ROCKET.select(), + ContainerSelection.HOTBAR_AND_INVENTORY + ) != null || offHandStack.item == Items.FIREWORK_ROCKET context(_: SafeContext) private val ClientPlayerEntity.canTakeoff: Boolean @@ -156,39 +160,41 @@ object BetterFirework : Module( * Return true if a firework has been used */ fun SafeContext.startFirework(inventory: Boolean) { - val stack = selectStack(count = 1) { isItem(Items.FIREWORK_ROCKET) } - - stack.bestItemMatch(player.hotbarStacks) - ?.let { - val request = HotbarRequest(player.hotbarStacks.indexOf(it), this@BetterFirework, keepTicks = 0) - .submit(queueIfMismatchedStage = false) - if (request.done) { - interaction.interactItem(player, Hand.MAIN_HAND) - sendSwing() - } - return - } + if (player.offHandStack.item == Items.FIREWORK_ROCKET) { + interaction.interactItem(player, Hand.OFF_HAND) + if (clientSwing) player.swingHand(Hand.OFF_HAND) + else connection.sendPacket(HandSwingC2SPacket(Hand.OFF_HAND)) + return + } + + val selection = Items.FIREWORK_ROCKET.select(1) + val hotbarMatch = selection.bestMatch(HotbarContainer.slots) + if (hotbarMatch != null) { + swapAndFirework(hotbarMatch.index) + return + } if (!inventory) return - stack.bestItemMatch(player.hotbarAndInventoryStacks) - ?.let { - val swapSlotId = player.hotbarAndInventoryStacks.indexOf(it) - val hotbarSlotToSwapWith = player.hotbarStacks.find { slot -> slot.isEmpty }?.let { slot -> player.hotbarStacks.indexOf(slot) } ?: 8 - - inventoryRequest { - swap(swapSlotId, hotbarSlotToSwapWith) - action { - val request = HotbarRequest(hotbarSlotToSwapWith, this@BetterFirework, keepTicks = 0, nowOrNothing = true) - .submit(queueIfMismatchedStage = false) - if (request.done) { - interaction.interactItem(player, Hand.MAIN_HAND) - sendSwing() - } - } - swap(swapSlotId, hotbarSlotToSwapWith) - }.submit() - } + val inventoryMatch = selection.bestMatch(InventoryContainer.slots) ?: return + val hotbarSlot = stackSelection { isEmpty() }.bestMatch(HotbarContainer.slots)?.index ?: 8 + + inventoryRequest { + swapWithHotbar(inventoryMatch.id, hotbarSlot) + action { swapAndFirework(hotbarSlot) } + swapWithHotbar(inventoryMatch.id, hotbarSlot) + }.submit() + } + + private fun SafeContext.swapAndFirework(hotbarSlot: Int) { + val request = + hotbarRequest(hotbarSlot, true) { + keepTicks(0) + }.submit(false) + if (request.done) { + interaction.interactItem(player, Hand.MAIN_HAND) + sendSwing() + } } enum class TakeoffState { diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt index 444006b51..eca5c391e 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt @@ -26,8 +26,8 @@ import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.graphics.util.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index 7116ba655..9f076b769 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -18,15 +18,15 @@ package com.lambda.module.modules.movement import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.movement.BetterFirework.startFirework -import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info import com.lambda.util.SpeedUnit diff --git a/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt index 03bff2f1d..240b35840 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.world.fastEntitySearch import net.minecraft.entity.passive.AbstractHorseEntity import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt b/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt index 7f024e4bb..059ce26e3 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum import com.lambda.util.extension.isElytraFlying import com.lambda.util.math.MathUtils.toInt diff --git a/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt b/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt index 5275eb08a..82b2f4bea 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.MovementEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.math.component1 import com.lambda.util.math.component2 diff --git a/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt b/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt index 1746e1e95..4e0ddef0d 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object NoJumpCooldown : Module( name = "NoJumpCooldown", diff --git a/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt b/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt index e34f6db0f..87dad538c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.MovementEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.player.MovementUtils.motionX import com.lambda.util.player.MovementUtils.motionZ import com.lambda.util.player.MovementUtils.sneaking diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 58283a22b..cd173de2b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -17,8 +17,8 @@ package com.lambda.module.modules.movement -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.automation.setDefaultAutomationConfig +import com.lambda.config.entries.onValueChange import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext @@ -26,9 +26,9 @@ import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum import com.lambda.util.extension.contains import com.lambda.util.extension.isElytraFlying diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt b/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt index d31ac27cc..7dfe55128 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Sprint : Module( name = "Sprint", diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index ffb65f330..78865afd7 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -26,10 +26,10 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag import net.minecraft.entity.effect.StatusEffects import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt b/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt index 98cf8c9fa..c89e52c07 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt @@ -23,8 +23,8 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag import com.lambda.threading.runConcurrent import com.lambda.util.CommunicationUtils.info import com.lambda.util.PacketUtils.handlePacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt b/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt index 359f78bde..2765c5fbe 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.ClientEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object Timer : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt b/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt index cd2470c54..76e27baa0 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket object Velocity : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt index 4ea420a64..4f1389c3e 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt @@ -18,9 +18,9 @@ package com.lambda.module.modules.movement.elytrafly import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits @@ -29,12 +29,12 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.movement.elytrafly.modes.BounceElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GeneralElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GrimControlElytraFly -import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.NamedEnum import com.lambda.util.Timer diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index 804876f1d..cd1eb6e30 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -24,17 +24,18 @@ import com.lambda.event.Muteable import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.GlideHandler.ELYTRA_SELECTION -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryManager -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION +import com.lambda.interaction.handler.handlers.findSlot +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InventoryManager import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError -import com.lambda.util.player.SlotUtils.armorSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.entity.Entity import net.minecraft.entity.EquipmentSlot import net.minecraft.item.Items @@ -86,25 +87,24 @@ abstract class ElytraFlyMode( } } - val elytraSlot = findElytra() ?: run { - logError("Fake Fly requires an elytra in your inventory, preferably in your hotbar.") - ElytraFly.disable() - return false - } + val elytraSlot = findElytra() + ?: run { + logError("Fake Fly requires an elytra in your inventory, preferably in your hotbar") + ElytraFly.disable() + return false + } val elytraInHotbar = elytraSlot.index in 0..8 - val chestSlot = player.armorSlots.getOrNull(1) ?: return false + val chestSlot = ArmorContainer.slots.getOrNull(1) ?: return false if (elytraInHotbar) { - val hotbarRequest = HotbarRequest( - elytraSlot.index, - ElytraFly, - keepTicks = 0, - nowOrNothing = true - ).submit() + val hotbarRequest = + hotbarRequest(elytraSlot.index) { + keepTicks(0) + }.submit() if (!hotbarRequest.done) return false } - fun InventoryRequest.InvRequestBuilder.swapChest() { + fun InvRequestBuilder.swapChest() { if (elytraInHotbar) { interaction.interactItem(player, Hand.MAIN_HAND) InventoryManager.indexInventoryChanges() @@ -114,17 +114,17 @@ abstract class ElytraFlyMode( } } - val inventoryRequest = inventoryRequest { - swapChest() - action { startFly() } - swapChest() - }.submit(false) + val inventoryRequest = + inventoryRequest { + swapChest() + action { startFly() } + swapChest() + }.submit(false) return inventoryRequest.done } - fun SafeContext.findElytra(): Slot? = - ELYTRA_SELECTION.filterSlots(player.hotbarAndInventorySlots).minByOrNull { it.index } + fun findElytra(): Slot? = findSlot(ELYTRA_SELECTION, ContainerSelection.HOTBAR_AND_INVENTORY) protected fun SafeContext.startFly() { player.setFlag(Entity.GLIDING_FLAG_INDEX, true) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt index dd3344672..c70ead991 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement.elytrafly import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import net.minecraft.util.math.MathHelper import net.minecraft.util.math.Vec3d import kotlin.math.asin diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt index 36e1a7b1e..f92853026 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt @@ -21,16 +21,16 @@ import baritone.api.pathing.goals.GoalGetToBlock import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.hud.Speedometer import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.threading.runGameScheduled import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.logError import com.lambda.util.SpeedUnit +import com.lambda.util.math.blockPos import com.lambda.util.math.dist import com.lambda.util.math.distSq -import com.lambda.util.math.flooredBlockPos import com.lambda.util.math.isLoaded import com.lambda.util.world.raycast.InteractionMask import com.lambda.util.world.raycast.RayCastUtils.blockResult @@ -90,7 +90,7 @@ abstract class ObstaclePassingMode( passingToPos?.let { passingTo -> if (passingTo distSq startPos < playerPos distSq startPos) { - val atClosestPointBlockPos = closestLinePoint.flooredBlockPos == player.blockPos + val atClosestPointBlockPos = closestLinePoint.blockPos == player.blockPos if (!atClosestPointBlockPos) pathToValidPoint(closestLinePoint, snappedDir, false) else { BaritoneHandler.cancel() @@ -172,7 +172,7 @@ abstract class ObstaclePassingMode( private fun passTo(pos: Vec3d) { passingToPos = pos - BaritoneHandler.setGoalAndPath(GoalGetToBlock(pos.flooredBlockPos)) + BaritoneHandler.setGoalAndPath(GoalGetToBlock(pos.blockPos)) } protected fun Vec3d.findClosestPointOnLine(snappedDirection: Vec3d): Vec3d { @@ -185,7 +185,7 @@ abstract class ObstaclePassingMode( private fun Vec3d.isObstructed(direction: Vec3d) = if (!isLoaded) false else { - flooredBlockPos.down().let { downPos -> + blockPos.down().let { downPos -> !safeContext.blockState(downPos).isSolidBlock(safeContext.world, downPos) } || if (add(0.0, passerConfig.minObstacleHeight, 0.0).rayCastObstructed(direction)) true diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt index ebc48b599..942f1442c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt @@ -19,16 +19,16 @@ package com.lambda.module.modules.movement.elytrafly.modes import com.lambda.config.Config import com.lambda.config.Group -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.handlers.GlideHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.GlideHandler +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.hud.Speedometer import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index 095762b84..bf3bc8b28 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -18,14 +18,15 @@ package com.lambda.module.modules.movement.elytrafly.modes import com.lambda.config.Config -import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.modules.movement.elytrafly.ElytraFly import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode @@ -36,8 +37,6 @@ import com.lambda.module.modules.render.Freecam import com.lambda.util.NamedEnum import com.lambda.util.TickTimer import com.lambda.util.math.MathUtils.toFloat -import com.lambda.util.player.SlotUtils.hotbarStacks -import com.lambda.util.player.SlotUtils.inventoryStacks import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket @@ -141,11 +140,22 @@ class GrimControlElytraFly( val rot = RotationManager.activeRotation val flipFlop = rotFlipFlop.toFloat() * 0.0001f rotFlipFlop = !rotFlipFlop - event.packet = PlayerMoveC2SPacket.Full(player.pos, rot.yawF + flipFlop, rot.pitchF, player.isOnGround, player.horizontalCollision) + event.packet = + PlayerMoveC2SPacket.Full( + player.pos, + rot.yawF + flipFlop, + rot.pitchF, + player.isOnGround, + player.horizontalCollision + ) } listen { event -> - if (event.packet !is PlayerMoveC2SPacket || !player.isGliding || flipFlopMode.isFlipFlopping(hasFirework) || moving) return@listen + if (event.packet !is PlayerMoveC2SPacket || + !player.isGliding || + flipFlopMode.isFlipFlopping(hasFirework) || + moving + ) return@listen if (stillTickTimer.hasSurpassed(packetGap)) { stillTickTimer.reset() return@listen @@ -154,9 +164,9 @@ class GrimControlElytraFly( } } - private fun SafeContext.findFirework(): ItemStack? { + private fun findFirework(): ItemStack? { val stack = Items.FIREWORK_ROCKET.select() - return stack.bestItemMatch(player.hotbarStacks) ?: if (inventory) stack.bestItemMatch(player.inventoryStacks) else null + return stack.bestMatch(HotbarContainer.stacks) ?: if (inventory) stack.bestMatch(InventoryContainer.stacks) else null } override fun pausingMovement() = still diff --git a/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt b/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt index c6774f7a2..c150e5484 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runConcurrent import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt b/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt index f1b0aa7c2..a62fd1f71 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt @@ -17,11 +17,11 @@ package com.lambda.module.modules.network -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.ReflectionUtils.className import com.lambda.util.collections.LimitedDecayQueue diff --git a/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt b/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt index efcf05299..3660e73d5 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.UnsafeListener.Companion.listenConcurrentlyUnsafe import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt b/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt index 45fb7f1b7..148fb311c 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.warn import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.dist diff --git a/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt b/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt index 39dbcfdd1..d034bca9c 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.network import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.text.ClickEvents import com.lambda.util.text.buildText diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt index 25f485e17..4091c92b2 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.player import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.util.Hand @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt index 77ba3ca05..5fd9b717e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt @@ -17,14 +17,14 @@ package com.lambda.module.modules.player -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.distSq import net.minecraft.entity.Entity import net.minecraft.entity.player.PlayerEntity diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt index e8ea02ee0..a6da8b71d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt @@ -17,18 +17,18 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.EatConfig.Companion.reasonEating import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.EatTask.Companion.eat -import com.lambda.task.wrappers.thenAction +import com.lambda.task.start +import com.lambda.task.tasks.eat +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated @Suppress("unused") @@ -49,7 +49,9 @@ object AutoEat : Module( val reason = runSafeAutomated { reasonEating() } if (eatTask != null || !reason.shouldEat()) return@listen - eatTask = eat().thenAction { eatTask = null }.run() + eatTask = eat() + .thenAction { eatTask = null } + .start() } onDisable { diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index 091341b9e..1fe7d4100 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -18,18 +18,19 @@ package com.lambda.module.modules.player import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.GlideHandler.CHESTPLATE_SELECTION -import com.lambda.interaction.handlers.GlideHandler.ELYTRA_SELECTION -import com.lambda.interaction.handlers.GlideHandler.manuallySwapped -import com.lambda.interaction.handlers.GlideHandler.swapped -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION +import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION +import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped +import com.lambda.interaction.handler.handlers.GlideHandler.swapped +import com.lambda.interaction.handler.handlers.findSlots +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.AutoArmor -import com.lambda.module.tag.ModuleTag import com.lambda.util.CommunicationUtils.warn import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece -import com.lambda.util.player.SlotUtils.armorSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.screen.slot.Slot object AutoElytraSwap : Module( @@ -44,24 +45,25 @@ object AutoElytraSwap : Module( onDisable { restore() } } - context(safeContext: SafeContext) fun manualSwap(elytra: Boolean): Boolean { val swapSlot = - safeContext.player.hotbarAndInventorySlots.let { slots -> - if (elytra) ELYTRA_SELECTION.filterSlots(slots) - else CHESTPLATE_SELECTION.filterSlots(slots) - }.minByOrNull { it.index } ?: run { - AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") - return false - } + findSlots( + if (elytra) ELYTRA_SELECTION + else CHESTPLATE_SELECTION + ).firstOrNull() + ?: run { + AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") + return false + } - return safeContext.swapWithChestplate(swapSlot) + return swapWithChestplate(swapSlot) } - private fun SafeContext.swapWithChestplate(swapSlot: Slot): Boolean { - val chestplateSlot = player.armorSlots[1] + private fun swapWithChestplate(swapSlot: Slot): Boolean { + val chestplateSlot = ArmorContainer.slots.getOrNull(1) ?: return false + val hotbarIndex = HotbarContainer.slots.indexOf(swapSlot) return AutoElytraSwap.inventoryRequest { - if (swapSlot.index in 0..8) swap(chestplateSlot.id, swapSlot.index) + if (hotbarIndex != -1) swapWithHotbar(chestplateSlot.id, hotbarIndex) else { moveSlot(swapSlot.id, chestplateSlot.id) if (!chestplateSlot.stack.isEmpty) pickup(swapSlot.id) diff --git a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt index 5bbfb4bf3..a1adaea38 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt @@ -20,12 +20,12 @@ package com.lambda.module.modules.player import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler -import com.lambda.interaction.handlers.FriendHandler.befriend -import com.lambda.interaction.handlers.FriendHandler.isFriend -import com.lambda.interaction.handlers.FriendHandler.unfriend +import com.lambda.interaction.handler.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler.befriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.unfriend import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.world.raycast.RayCastUtils.entityResult diff --git a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt index b7cdcee1d..7aa51038d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt @@ -17,16 +17,16 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.distSq import net.minecraft.entity.EquipmentSlot import net.minecraft.entity.mob.EndermanEntity diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 82e5425e7..76a5204fc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.editTypedSettings import com.lambda.config.hide @@ -27,9 +27,9 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest +import com.lambda.interaction.manager.managers.breaking.breakRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import java.util.concurrent.ConcurrentLinkedQueue diff --git a/src/main/kotlin/com/lambda/module/modules/player/Interact.kt b/src/main/kotlin/com/lambda/module/modules/player/Interact.kt index 48fec9a24..6ae205a89 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Interact.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Interact.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Interact : Module( name = "Interact", diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt index 0fdbccd43..59f5d317c 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt @@ -22,10 +22,10 @@ import com.lambda.config.blocks.RotationConfig import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.LambdaScreen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.InputUtils import com.lambda.util.InputUtils.isKeyPressed import com.lambda.util.math.MathUtils.toFloatSign diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt index c432a001d..3ce04fa89 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt @@ -19,9 +19,9 @@ package com.lambda.module.modules.player import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.TickTimer @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index b38be09d0..b43257a5f 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -17,27 +17,30 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.NestedContainer +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.tasks.PlaceContainerTask -import com.lambda.task.wrappers.then -import com.lambda.task.wrappers.thenAction -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.task.start +import com.lambda.task.tasks.wrappers.then +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import net.minecraft.item.Items import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.SlotActionType -import net.minecraft.util.math.BlockPos +import java.util.* +@Suppress("unused") object InventoryTweaks : Module( name = "InventoryTweaks", tag = ModuleTag.PLAYER, @@ -45,10 +48,10 @@ object InventoryTweaks : Module( private val instantShulker by setting("Instant Shulker", true, description = "Right-click shulker boxes in your inventory to instantly place them and open them.") private val instantEChest by setting("Instant Ender-Chest", true, description = "Right-click ender chests in your inventory to instantly place them and open them.") - private var placedPos: BlockPos? = null - private var placeAndOpen: Task<*>? = null - private var lastBreak: Task<*>? = null - private var lastOpenScreen: ScreenHandler? = null + private var openTask: Task<*>? = null + private val openContexts = LinkedList() + var lastOpenScreen: ScreenHandler? = null + private var isClosing = false init { setDefaultAutomationConfig() @@ -56,32 +59,64 @@ object InventoryTweaks : Module( hideAllExcept(::breakConfig, ::interactConfig, ::inventoryConfig, ::hotbarConfig) } - listen { - if (it.action != SlotActionType.PICKUP || it.button != 1) return@listen - val slot = it.screenHandler.getSlot(it.slot) - if (!(instantShulker && slot.stack.item in shulkerBoxes) && !(instantEChest && slot.stack.item == Items.ENDER_CHEST)) return@listen - it.cancel() - lastOpenScreen = null - placeAndOpen = PlaceContainerTask(slot, this@InventoryTweaks).then { placePos -> - placedPos = placePos - OpenContainerTask(placePos, this@InventoryTweaks).thenAction { screenHandler -> - lastOpenScreen = screenHandler + listen { event -> + if (event.action != SlotActionType.PICKUP || event.button != 1) return@listen + val slot = event.screenHandler.getSlot(event.slot) ?: return@listen + val stack = slot.stack + + when (stack.item) { + in SHULKER_BOXES if (!instantShulker) -> return@listen + Items.ENDER_CHEST if (!instantEChest) -> return@listen + } + + val targetContainer = + if (stack.item == Items.ENDER_CHEST) EnderChestContainer + else findContainer( + containerSelection { + ofAnyType(ContainerType.ShulkerBox) + predicate { container -> + container is NestedContainer && container.index == slot.index + } + } + ) ?: return@listen + + event.cancel() + + openTask = targetContainer + .access() + ?.onSuccess { ctx -> + openContexts.push(ctx) + lastOpenScreen = player.currentScreenHandler + openTask = null } - }.run() + openTask?.start() } listen { event -> - if (event.screenHandler != lastOpenScreen) return@listen - lastOpenScreen = null - placedPos?.let { - lastBreak = breakAndCollectBlock(it).run() - placedPos = null + if (isClosing || openTask != null) return@listen + if (event.screenHandler != lastOpenScreen || openContexts.isEmpty()) return@listen + + isClosing = true + val contextsToClose = mutableListOf() + while (openContexts.isNotEmpty()) { + contextsToClose.add(openContexts.pop()) } + + val closeTask = + contextsToClose.fold?>(null) { acc, ctx -> + val task = ctx.close() ?: return@fold acc + acc?.then(task) ?: task + } + + closeTask + ?.onCompletion { isClosing = false } + ?.start() + ?: run { isClosing = false } } onDisable { - placeAndOpen?.cancel() - lastBreak?.cancel() + openContexts.clear() + isClosing = false } } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt index df99b71f8..40d78570d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits @@ -26,10 +26,10 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt b/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt index 7ca7798fb..9ef7a1953 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.player import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object NoForceRotate : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 13ab6cdbc..45d138e92 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -18,12 +18,12 @@ package com.lambda.module.modules.player import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.BreakConfig.SwingMode import com.lambda.config.editSetting import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.disabled -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.disabled +import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.hideAllExcept import com.lambda.config.withEdits @@ -37,9 +37,12 @@ import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest +import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag +import com.lambda.interaction.handler.handlers.FriendHandler +import com.lambda.util.world.fastEntitySearch +import net.minecraft.entity.player.PlayerEntity import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockState @@ -67,6 +70,15 @@ object PacketMine : Module( .onValueChange { _, to -> if (!to) queuePositions.clear() } private val queueOrder by setting("Queue Order", QueueOrder.Standard, "Which end of the queue to break blocks from") { queue } + private val autoCity by setting("Auto City", false, "Automatically targets and mines surround blocks of nearby enemies") + private val cityRange by setting("City Range", 5.0, 2.0..8.0, 0.5, "Maximum range to search for enemy players to city") { autoCity } + private val cityBurrow by setting("City Burrow", true, "Prioritizes burrow blocks if target is burrowed") { autoCity } + private val cityFriends by setting("City Friends", false, "Whether to target friends with Auto City") { autoCity } + + private const val CITY_RENDERS_GROUP = "City Renders" + @Group(CITY_RENDERS_GROUP) private val renderCity by setting("Render City", true, "Renders the Auto City block target") { autoCity } + @Group(CITY_RENDERS_GROUP) private val cityColor by setting("City Color", Color(0, 255, 255, 120)) { autoCity && renderCity } + private const val REBREAK_RENDERS_GROUP = "ReBreak Renders" private const val QUEUE_RENDERS_GROUP = "Queue Renders" @@ -172,11 +184,29 @@ object PacketMine : Module( } listen { + val cityTarget = findCityTarget() + val requestList = mutableListOf() + breakPositions.filterNotNull().forEach { requestList.add(it) } + requestList.addAll(queueSorted.flatten()) + + if (cityTarget != null && !requestList.contains(cityTarget)) { + requestList.add(cityTarget) + } + if (!attackedThisTick) { - requestBreakManager((breakPositions + queueSorted.flatten()).toList()) - if (!breakConfig.rebreak || (rebreakMode != RebreakMode.Auto /*&& rebreakMode != RebreakMode.AutoConstant*/)) return@listen + requestBreakManager(requestList) + } + + if (breakConfig.rebreak && (rebreakMode == RebreakMode.Auto || rebreakMode == RebreakMode.AutoConstant)) { val reBreak = rebreakPos ?: return@listen - requestBreakManager(listOf(reBreak), true) + val state = blockState(reBreak) + if (rebreakMode == RebreakMode.AutoConstant) { + if (!state.isAir && state.getHardness(world, reBreak) >= 0) { + requestBreakManager(listOf(reBreak), true) + } + } else if (!attackedThisTick) { + requestBreakManager(listOf(reBreak), true) + } } } @@ -189,6 +219,16 @@ object PacketMine : Module( } } } + if (autoCity && renderCity) { + runSafe { + findCityTarget()?.let { pos -> + box(pos) { + hideFill() + outlineColor(cityColor) + } + } + } + } if (!renderQueue) return@tickedRenderer runSafe { queueSorted.forEachIndexed { index, positions -> @@ -299,6 +339,36 @@ object PacketMine : Module( forEach { if (it.any(predicate)) return true } return false } + private fun SafeContext.findCityTarget(): BlockPos? { + if (!autoCity) return null + val targets = fastEntitySearch(cityRange).filter { + it != player && it.isAlive && (cityFriends || !FriendHandler.isFriend(it.uuid)) + }.sortedBy { it.squaredDistanceTo(player) } + + val maxReachSq = 4.5 * 4.5 + + for (target in targets) { + val feetPos = target.blockPos + if (cityBurrow) { + val state = blockState(feetPos) + if (!state.isAir && state.getHardness(world, feetPos) >= 0 && feetPos.distSq(player.eyePos) <= maxReachSq) { + return feetPos + } + } + + val surroundPositions = arrayOf( + feetPos.north(), feetPos.south(), feetPos.east(), feetPos.west() + ) + val validSurround = surroundPositions.filter { pos -> + val state = blockState(pos) + !state.isAir && state.getHardness(world, pos) >= 0 && pos.distSq(player.eyePos) <= maxReachSq + }.minByOrNull { it.distSq(player.eyePos) } + + if (validSurround != null) return validSurround + } + return null + } + private enum class RebreakMode( override val displayName: String, @@ -306,8 +376,7 @@ object PacketMine : Module( ) : NamedEnum, Describable { Manual("Manual", "Re-break only when you trigger it explicitly."), Auto("Auto", "Automatically re-break when it’s beneficial or required."), - //ToDo: Implement auto constant rebreak -// AutoConstant("Auto (Constant)", "Continuously re-break as soon as conditions allow; most aggressive.") + AutoConstant("Auto Constant", "Continuously re-break as soon as a block is placed; most aggressive civbreak.") } private enum class QueueOrder( diff --git a/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt b/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt index dea4ec2e1..3b7022704 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object PortalGui : Module( name = "PortalGui", diff --git a/src/main/kotlin/com/lambda/module/modules/player/Reach.kt b/src/main/kotlin/com/lambda/module/modules/player/Reach.kt index 13855a2e6..5a6e38b84 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Reach.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Reach.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Reach : Module( name = "Reach", 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 56b5d83eb..c5135903e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -34,13 +34,13 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.handlers.TimerHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.handler.handlers.TimerHandler +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction -import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils.info import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt index 32036b1c1..3fa739d4b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt @@ -17,16 +17,16 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import kotlin.math.roundToInt @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/SilentEat.kt b/src/main/kotlin/com/lambda/module/modules/player/SilentEat.kt new file mode 100644 index 000000000..d8fc689fd --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/SilentEat.kt @@ -0,0 +1,333 @@ +/* + * Copyright 2026 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.module.modules.player + +import com.lambda.config.automation.setDefaultAutomationConfig +import com.lambda.config.blocks.EatConfig +import com.lambda.config.blocks.EatConfig.Companion.reasonEating +import com.lambda.config.hideAllExcept +import com.lambda.config.withEdits +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.module.Module +import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.AutoTotem +import com.lambda.module.modules.combat.KillAura +import com.lambda.threading.runSafe +import com.lambda.threading.runSafeAutomated +import com.lambda.util.Describable +import com.lambda.util.NamedEnum +import com.lambda.util.extension.fullHealth +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand + +@Suppress("unused") +object SilentEat : Module( + name = "SilentEat", + description = "Silently eats food from inventory without equipping it", + tag = ModuleTag.PLAYER, + modulePriority = 95 +) { + val mode by setting("Mode", Mode.Smart, "Eating mode: Smart (Offhand with AutoTotem safety), Offhand, or MainHand") + val safetyHealth by setting("Safety Health", 12, 6..36, 1, "Minimum health threshold to allow offhand eating in Smart mode") + val pauseKillAura by setting("Pause KillAura", true, "Pauses KillAura during MainHand eating to prevent hit cancellation") + val silentVisual by setting("Silent Visual", true, "Hides the food item visually in first person") + + enum class Mode(override val displayName: String, override val description: String) : NamedEnum, Describable { + Smart("Smart", "Uses Offhand when safe for simultaneous KillAura/PacketMine, yields to AutoTotem when in danger"), + Offhand("Offhand", "Swaps food into offhand and restores original item afterwards"), + MainHand("MainHand", "Uses hotbar silent swap, keeping offhand completely untouched for AutoTotem") + } + + private enum class State { + IDLE, + SWAPPING, + EATING, + RESTORING + } + + private var state = State.IDLE + private var activeHand: Hand? = null + private var swappedOffhand = false + private var foodSlotId: Int = -1 + private var originalOffhandStack: ItemStack = ItemStack.EMPTY + private var swappedHotbar = false + private var originalHotbarSlot: Int = -1 + private var originalHotbarItemSlotId: Int = -1 + private var eatStack: ItemStack? = null + private var reason = EatConfig.Reason.None + private var holdingUse = false + private var ticksInState = 0 + + @JvmStatic val isSilentEating: Boolean get() = isEnabled && state == State.EATING + @JvmStatic val isUsingOffhand: Boolean get() = isEnabled && state == State.EATING && activeHand == Hand.OFF_HAND + @JvmStatic val visualOffhandStack: ItemStack get() = originalOffhandStack + + init { + setDefaultAutomationConfig() + .withEdits { + hideAllExcept(::eatConfig, ::inventoryConfig, ::hotbarConfig) + } + + AutoTotem.offhandOverride = { isUsingOffhand } + + listen { + when (state) { + State.IDLE -> { + val currentReason = runSafeAutomated { reasonEating() } + if (!currentReason.shouldEat()) return@listen + startEatingProcess(currentReason) + } + State.SWAPPING -> tickSwapping() + State.EATING -> tickEating() + State.RESTORING -> tickRestoring() + } + } + + onDisableUnsafe { + runSafe { cleanupAndReset() } + } + } + + private fun SafeContext.startEatingProcess(currentReason: EatConfig.Reason) { + if (player.currentScreenHandler !== player.playerScreenHandler) return + + val targetHand = when (mode) { + Mode.Smart -> { + val inDanger = player.fullHealth < safetyHealth || (AutoTotem.isEnabled && AutoTotem.isDangerous(this)) + if (inDanger) Hand.MAIN_HAND else Hand.OFF_HAND + } + Mode.Offhand -> Hand.OFF_HAND + Mode.MainHand -> Hand.MAIN_HAND + } + + val selection = currentReason.selector() + val slots = player.currentScreenHandler.slots + + if (targetHand == Hand.OFF_HAND) { + val alreadyInOffhand = selection.matches(player.offHandStack) && !player.offHandStack.isEmpty + if (alreadyInOffhand) { + originalOffhandStack = player.offHandStack.copy() + foodSlotId = -1 + swappedOffhand = false + activeHand = Hand.OFF_HAND + reason = currentReason + eatStack = player.offHandStack.copy() + state = State.EATING + ticksInState = 0 + interactFood(Hand.OFF_HAND) + } else { + val foodSlot = slots.firstOrNull { slot -> + slot.id in 9..44 && selection.matches(slot.stack) && !slot.stack.isEmpty + } ?: return + + originalOffhandStack = player.offHandStack.copy() + foodSlotId = foodSlot.id + swappedOffhand = true + activeHand = Hand.OFF_HAND + reason = currentReason + eatStack = foodSlot.stack.copy() + state = State.SWAPPING + ticksInState = 0 + + inventoryRequest { + swapWithHotbar(foodSlot.id, 40) + }.submit() + } + } else { + // Main Hand mode + val hotbarMatch = HotbarContainer.slots.firstOrNull { selection.matches(it.stack) && !it.stack.isEmpty } + if (hotbarMatch != null) { + originalHotbarSlot = player.inventory.selectedSlot + swappedHotbar = false + activeHand = Hand.MAIN_HAND + reason = currentReason + eatStack = hotbarMatch.stack.copy() + + if (pauseKillAura) KillAura.isPaused = true + + if (hotbarMatch.index == player.inventory.selectedSlot) { + state = State.EATING + ticksInState = 0 + interactFood(Hand.MAIN_HAND) + } else { + state = State.SWAPPING + ticksInState = 0 + hotbarRequest(hotbarMatch.index).submit() + } + } else { + val invMatch = slots.firstOrNull { slot -> + slot.id in 9..35 && selection.matches(slot.stack) && !slot.stack.isEmpty + } ?: return + + originalHotbarSlot = player.inventory.selectedSlot + originalHotbarItemSlotId = invMatch.id + swappedHotbar = true + activeHand = Hand.MAIN_HAND + reason = currentReason + eatStack = invMatch.stack.copy() + + if (pauseKillAura) KillAura.isPaused = true + + state = State.SWAPPING + ticksInState = 0 + + inventoryRequest { + swapWithHotbar(invMatch.id, player.inventory.selectedSlot) + }.submit() + } + } + } + + private fun SafeContext.tickSwapping() { + ticksInState++ + + val hand = activeHand ?: run { + cleanupAndReset() + return + } + + val currentHeld = if (hand == Hand.OFF_HAND) player.offHandStack else player.mainHandStack + val selection = reason.selector() + + if (selection.matches(currentHeld) && !currentHeld.isEmpty) { + // Food is now in hand; start eating! + eatStack = currentHeld.copy() + state = State.EATING + ticksInState = 0 + interactFood(hand) + return + } + + // Swap timeout: if not in hand after 10 ticks, abort + if (ticksInState > 10) { + cleanupAndReset() + } + } + + private fun SafeContext.interactFood(hand: Hand) { + val result = interaction.interactItem(player, hand) + if (result is ActionResult.Success) { + if (result.swingSource == ActionResult.SwingSource.CLIENT) { + player.swingHand(hand) + } + mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand) + } + mc.options.useKey.isPressed = true + holdingUse = true + } + + private fun SafeContext.tickEating() { + ticksInState++ + + val hand = activeHand ?: run { + cleanupAndReset() + return + } + + // Safety check for AutoTotem in offhand mode + if (hand == Hand.OFF_HAND) { + val inDanger = player.fullHealth < safetyHealth || (AutoTotem.isEnabled && AutoTotem.isDangerous(this)) + if (inDanger) { + cleanupAndReset() + return + } + } + + // Maintain use key + if (!holdingUse || !mc.options.useKey.isPressed) { + mc.options.useKey.isPressed = true + holdingUse = true + } + + // If after 3 ticks the player isn't using item, retry interaction once + if (ticksInState == 3 && !player.isUsingItem) { + interactFood(hand) + } + + // Check if food was consumed or should stop + val currentHeld = if (hand == Hand.OFF_HAND) player.offHandStack else player.mainHandStack + val isFoodStillHeld = reason.selector().matches(currentHeld) && !currentHeld.isEmpty + + val finishedEating = (ticksInState >= 32 && !player.isUsingItem) + || (ticksInState >= 32 && !isFoodStillHeld) + || (!reason.shouldKeepEating(eatStack) && ticksInState >= 5) + + if (finishedEating || ticksInState > 45) { + state = State.RESTORING + ticksInState = 0 + } + } + + private fun SafeContext.tickRestoring() { + cleanupAndReset() + } + + private fun SafeContext.cleanupAndReset() { + if (holdingUse) { + mc.options.useKey.isPressed = false + holdingUse = false + } + if (player.isUsingItem) { + interaction.stopUsingItem(player) + } + + if (pauseKillAura) { + KillAura.isPaused = false + } + + if (swappedOffhand) { + // Guard: Do not overwrite Totem of Undying if AutoTotem equipped one during an emergency abort + if (foodSlotId != -1 && player.offHandStack.item != Items.TOTEM_OF_UNDYING) { + inventoryRequest { + swapWithHotbar(foodSlotId, 40) + }.submit() + } + swappedOffhand = false + foodSlotId = -1 + originalOffhandStack = ItemStack.EMPTY + } + + if (swappedHotbar) { + if (originalHotbarItemSlotId != -1 && originalHotbarSlot != -1) { + inventoryRequest { + swapWithHotbar(originalHotbarItemSlotId, originalHotbarSlot) + }.submit() + } + swappedHotbar = false + originalHotbarItemSlotId = -1 + } + + if (originalHotbarSlot != -1) { + hotbarRequest(originalHotbarSlot).submit() + originalHotbarSlot = -1 + } + + state = State.IDLE + activeHand = null + eatStack = null + ticksInState = 0 + } +} diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index 4e962aea4..8b0d2a493 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -17,19 +17,19 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hide import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.item.ItemStackUtils.slotId -import com.lambda.util.player.SlotUtils.hotbarStacks -import com.lambda.util.player.SlotUtils.inventoryStacks import net.minecraft.item.ItemStack import net.minecraft.item.Items @@ -47,13 +47,13 @@ object StackReplenish : Module( .withEdits { hideAllExcept(::inventoryConfig) inventoryConfig.apply { - hide(::disposables, ::swapWithDisposables, ::providerPriority, ::storePriority) + hide(::disposables) } } listen { if (player.currentScreenHandler.cursorStack.item !== Items.AIR) return@listen - player.hotbarStacks.forEach { stack -> checkReplenish(stack) } + HotbarContainer.stacks.forEach { stack -> checkReplenish(stack) } if (offhand) checkReplenish(player.offHandStack) } } @@ -62,13 +62,15 @@ object StackReplenish : Module( if (stack.count.toFloat() / stack.maxCount >= (minStackPercent.toFloat() / 100)) return if (!stack.isStackable) return - player.inventoryStacks.forEach { invStack -> + InventoryContainer.stacks.forEach { invStack -> if (!ItemStack.areItemsAndComponentsEqual(invStack, stack)) return@forEach val invId = invStack.slotId + val targetId = stack.slotId + if (invId == -1 || targetId == -1) return@forEach val completing = stack.count + invStack.count >= stack.maxCount val tooMany = invStack.count + stack.count > stack.maxCount inventoryRequest { - moveSlot(invId, stack.slotId) + moveSlot(invId, targetId) if (tooMany) pickup(invId) }.submit() if (completing) return diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index e0fea783f..ffaecdeb2 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -17,20 +17,19 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.EnchantmentUtils.forEachEnchantment import com.lambda.util.EnchantmentUtils.getEnchantment -import com.lambda.util.player.SlotUtils.hotbarSlots -import com.lambda.util.player.SlotUtils.inventorySlots import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot @@ -50,9 +49,9 @@ object ToolSaver : Module( } listen { - val endangeredStacks = player.hotbarSlots.filter { it.stack.isEndangered } + val endangeredStacks = HotbarContainer.slots.filter { it.stack.isEndangered } - val inventorySlots = player.inventorySlots + val inventorySlots = InventoryContainer.slots val swaps = endangeredStacks .mapNotNull { endangered -> val sorter = compareByDescending { swapSlot -> diff --git a/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt b/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt index 81eae6976..17d7a2dc3 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.tickDelta diff --git a/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt b/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt index d3d9303d5..f255e0c81 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Bobbing : Module( name = "Bobbing", diff --git a/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt b/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt index 21c383419..0647ea3b1 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object CameraTweaks : Module( name = "CameraTweaks", diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index d8c79f5d4..0ee377384 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -19,18 +19,18 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.config.Tab -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.settings.complex.Bind -import com.lambda.interaction.material.container.containers.EnderChestContainer +import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode -import com.lambda.util.item.ItemStackUtils.bundleContents -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents -import com.lambda.util.item.ItemUtils.bundles -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.item.ItemStackUtils.bundleStacks +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks +import com.lambda.util.item.ItemUtils.BUNDLES +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.block.ShulkerBoxBlock @@ -261,7 +261,7 @@ object ContainerPreview : Module( private fun getContainerContents(stack: ItemStack): List { return when { - isShulkerBox(stack) -> stack.shulkerBoxContents + isShulkerBox(stack) -> stack.shulkerBoxStacks isEnderChest(stack) -> EnderChestContainer.stacks else -> emptyList() } @@ -360,7 +360,7 @@ object ContainerPreview : Module( val hash = container.hashCode() return containerCache.computeIfAbsent(hash) { - val contents = container.shulkerBoxContents + container.bundleContents + val contents = container.shulkerBoxStacks + container.bundleStacks if (contents.isEmpty()) return@computeIfAbsent ContainerPreviewInfo(null, false) val group = contents.filter { stack -> stack.item != Items.AIR } @@ -379,7 +379,7 @@ object ContainerPreview : Module( } @JvmStatic - fun isShulkerBox(stack: ItemStack) = stack.item in shulkerBoxes + fun isShulkerBox(stack: ItemStack) = stack.item in SHULKER_BOXES @JvmStatic fun isEnderChest(stack: ItemStack) = stack.item == Items.ENDER_CHEST && EnderChestContainer.stacks.isNotEmpty() @@ -388,7 +388,7 @@ object ContainerPreview : Module( fun isPreviewableContainer(stack: ItemStack) = isShulkerBox(stack) || isEnderChest(stack) @JvmStatic - fun isBundle(stack: ItemStack) = stack.item in bundles + fun isBundle(stack: ItemStack) = stack.item in BUNDLES @JvmStatic fun drawOnItem(drawContext: DrawContext, state: GuiRenderState, entity: LivingEntity?, world: World?, stack: ItemStack, x: Int, y: Int, seed: Int) { diff --git a/src/main/kotlin/com/lambda/module/modules/render/Esp.kt b/src/main/kotlin/com/lambda/module/modules/render/Esp.kt index 1639b59ee..be0d21c83 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Esp.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Esp.kt @@ -23,7 +23,7 @@ import com.lambda.config.blocks.EntityColorSettings import com.lambda.config.blocks.EntitySelectionSettings import com.lambda.config.blocks.OutlineSettings import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits @@ -31,7 +31,7 @@ import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.NamedEnum import com.lambda.util.math.setAlpha diff --git a/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt b/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt index d70c37a5a..fd561bcd2 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color object ExtraTab : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt index a67af3e35..486efbd56 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt @@ -20,10 +20,10 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.extension.rotation import net.minecraft.client.option.Perspective diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt index c53b87e09..94f24cc09 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -18,9 +18,9 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext @@ -31,11 +31,11 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt b/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt index 09e15863c..18dfd1a33 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt @@ -17,12 +17,12 @@ package com.lambda.module.modules.render -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listenOnce import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.entity.effect.StatusEffectInstance import net.minecraft.entity.effect.StatusEffects diff --git a/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt b/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt index eb590e40b..7d3de14c9 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.config.Group import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits @@ -29,10 +29,10 @@ import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ChunkedRenderer.Companion.chunkedRenderer import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.setAlpha import com.lambda.util.math.vec3d import com.lambda.util.world.toBlockPos @@ -92,7 +92,7 @@ object LightLevels : Module( runSafe { if (areaMode.player) buildPositions(positions, player.blockPos) - if (areaMode.camera) buildPositions(positions, mc.gameRenderer.camera.pos.flooredBlockPos) + if (areaMode.camera) buildPositions(positions, mc.gameRenderer.camera.pos.blockPos) positions.forEach { pos -> buildRender(pos, dashStyle) } diff --git a/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt index fedeea4cd..270f73c83 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.client.font.TextRenderer import net.minecraft.client.gl.RenderPipelines import net.minecraft.client.gui.DrawContext diff --git a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt index 94add83e2..176106c69 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt @@ -30,9 +30,9 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized import com.lambda.graphics.text.FontHandler import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.fullHealth import com.lambda.util.extension.maxFullHealth diff --git a/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt b/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt index 1287a2fcb..2ba504765 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt @@ -22,7 +22,7 @@ import com.lambda.config.blocks.EntitySelectionSettings import com.lambda.config.editTypedSettings import com.lambda.config.withEdits import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.EntityUtils.createNameMap import com.lambda.util.ReflectionUtils.scanResult import net.minecraft.block.entity.BlockEntity diff --git a/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt b/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt index 478cb318b..455ea2ebb 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.config.Group import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits @@ -28,7 +28,7 @@ import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ChunkedRenderer.Companion.chunkedRenderer import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.math.setAlpha diff --git a/src/main/kotlin/com/lambda/module/modules/render/Search.kt b/src/main/kotlin/com/lambda/module/modules/render/Search.kt index 15752cf49..a0b0f134d 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Search.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Search.kt @@ -21,7 +21,7 @@ import com.lambda.config.Group import com.lambda.config.blocks.ScreenLineSettings import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.settings.collections.onDeselect @@ -36,7 +36,7 @@ import com.lambda.graphics.util.DirectionMask import com.lambda.graphics.util.DirectionMask.buildSideMesh import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.EntityUtils.decorationEntityMap diff --git a/src/main/kotlin/com/lambda/module/modules/render/Time.kt b/src/main/kotlin/com/lambda/module/modules/render/Time.kt index d5e384c68..986b7876d 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Time.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Time.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket object Time : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt index c5774a800..f2033ad5c 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt @@ -26,9 +26,9 @@ import com.lambda.config.hide import com.lambda.config.withEdits import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.prevPos import com.lambda.util.extension.tickDelta diff --git a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt index ff2acce3d..b109d3b5a 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt @@ -19,12 +19,12 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.config.Tab -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.event.events.ButtonEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.client.network.AbstractClientPlayerEntity import net.minecraft.client.util.math.MatrixStack import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/module/modules/render/Weather.kt b/src/main/kotlin/com/lambda/module/modules/render/Weather.kt index 13a6b5239..5c218aaf1 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Weather.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Weather.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.config.Tab import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import net.minecraft.world.World diff --git a/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt b/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt index 227d0e209..b8307141e 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.vec3d import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt index 6e9d1e2a6..426526ee8 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt @@ -18,12 +18,12 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.settings.collections.onDeselect import com.lambda.config.settings.collections.onSelect import com.lambda.context.SafeContext import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.BlockUtils import net.minecraft.block.Blocks import net.minecraft.fluid.Fluids diff --git a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt index d991feb93..d654a69c5 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.render import com.lambda.event.events.ButtonEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum import java.lang.Math.clamp diff --git a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt index eae5e120d..7ac8b39a9 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.world import com.lambda.Lambda.mc import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind import com.lambda.config.withEdits @@ -32,11 +32,11 @@ import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.interacting.InteractRequest -import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.interacting.InteractRequest +import com.lambda.interaction.manager.managers.interacting.interactRequest +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 3202ed162..59eb6e0ca 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.world import baritone.api.pathing.goals.GoalBlock import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.editSetting import com.lambda.config.forEachSetting @@ -35,20 +35,24 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DirectionMask import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findSlot +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.portalPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.prevAnchorPos -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.task.wrappers.thenOrNull +import com.lambda.task.start +import com.lambda.task.tasks.build +import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty @@ -59,8 +63,6 @@ import com.lambda.util.extension.tickDelta import com.lambda.util.math.lerp import com.lambda.util.math.setAlpha import com.lambda.util.math.vec3d -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots -import com.lambda.util.player.SlotUtils.hotbarSlots import net.minecraft.block.Blocks import net.minecraft.item.FlintAndSteelItem import net.minecraft.item.Items @@ -105,7 +107,7 @@ object AutoPortal : Module( else null } .onCompletion { buildTask = null } - .run() + .start() } private val corners by setting("Corners", false) private val light by setting("Light", true, "Attempts to automatically light the portal after building") @@ -323,45 +325,40 @@ object AutoPortal : Module( return } - val sel = selectStack(1) { isItem() } + val selection = stackSelection(1) { isItem() } - val hotbarStack = sel.filterSlots(player.hotbarSlots).firstOrNull() - if (hotbarStack != null) { - val request = HotbarRequest( - hotbarStack.index, - this@AutoPortal, - keepTicks = 0 - ).submit(queueIfMismatchedStage = false) + val hotbarSlot = findSlot(selection, HotbarContainer.select()) + if (hotbarSlot != null) { + val request = + hotbarRequest(hotbarSlot.index) { + keepTicks(0) + }.submit(false) if (request.done) block() return } val invSlot = - if (inventory) sel.filterSlots(player.hotbarAndInventorySlots).firstOrNull() + if (inventory) findSlot(selection, ContainerSelection.HOTBAR_AND_INVENTORY) else null if (invSlot == null) { failure("No Flint and Steel!") return } val hotbarSlotToSwapWith = - player.hotbarSlots.find { slot -> + HotbarContainer.slots.find { slot -> slot.stack.isEmpty }?.index ?: 8 inventoryRequest { - swap(invSlot.id, hotbarSlotToSwapWith) + swapWithHotbar(invSlot.id, hotbarSlotToSwapWith) action { - val request = HotbarRequest( - hotbarSlotToSwapWith, - this@AutoPortal, - keepTicks = 0, - nowOrNothing = true - ).submit(queueIfMismatchedStage = false) - if (request.done) { - block() - } + val request = + hotbarRequest(hotbarSlotToSwapWith) { + keepTicks(0) + }.submit(false) + if (request.done) block() } - swap(invSlot.id, hotbarSlotToSwapWith) + swapWithHotbar(invSlot.id, hotbarSlotToSwapWith) }.submit() } } diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt index d2f1e502b..068c898ad 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt @@ -21,7 +21,7 @@ import com.ibm.icu.util.Calendar import com.lambda.event.events.GuiEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runConcurrent import com.lambda.threading.runSafeGameScheduled import com.lambda.util.PacketUtils.sendPacket diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 64511f511..6ee1bc20c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.world import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress @@ -28,16 +28,16 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound -import com.lambda.task.RootTask.run import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.task.wrappers.thenAction +import com.lambda.task.start +import com.lambda.task.tasks.build +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty @@ -220,7 +220,7 @@ object AutoVillagerCycle : Module( .thenAction { switchState(CycleState.OpenVillager) } - .run() + .start() } switchState(CycleState.WaitLectern) } @@ -281,7 +281,7 @@ object AutoVillagerCycle : Module( lecternPos.toStructure(TargetState.Empty) .build(finishOnDone = true) .thenAction { switchState(CycleState.PlaceLectern) } - .run() + .start() } switchState(CycleState.WaitBreak) return diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index 22cd0d2f9..82f0aee02 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -17,18 +17,18 @@ package com.lambda.module.modules.world -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint.Companion.propagatingBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.start +import com.lambda.task.tasks.build import com.lambda.util.CommunicationUtils.info import com.lambda.util.Describable import com.lambda.util.NamedEnum @@ -131,7 +131,7 @@ object HighwayTools : Module( emptyStructure() } }.build(collectDrops = buildConfig.collectDrops, lifeMaintenance = true) - .run() + .start() } private fun generateSlice(): Structure { diff --git a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt index b4de4c5e7..608deef16 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt @@ -19,12 +19,13 @@ package com.lambda.module.modules.world import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.handler.handlers.findStacks import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FileUtils.locationBoundDirectory import com.lambda.util.FolderRegistry import com.lambda.util.StringUtils.hashString -import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.world.entitySearch import net.minecraft.block.MapColor import net.minecraft.entity.decoration.ItemFrameEntity @@ -43,7 +44,8 @@ object MapDownloader : Module( listen { val mapStates = entitySearch(128.0) .mapNotNull { FilledMapItem.getMapState(it.heldItemStack, world) } + - player.allStacks.mapNotNull { FilledMapItem.getMapState(it, world) } + findStacks(containerSelection = ContainerSelection.PLAYER) + .mapNotNull { FilledMapItem.getMapState(it, world) } mapStates.forEach { map -> val name = map.hash diff --git a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt index d58d517a3..d19eec4f0 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt @@ -17,18 +17,18 @@ package com.lambda.module.modules.world -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.start +import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.PlayerBuildLayerUtils.FlattenMode import com.lambda.util.PlayerBuildLayerUtils.isInBaritoneSelection @@ -98,7 +98,7 @@ object Nuker : Module( selection }.build(finishOnDone = false, async = async) - .run() + .start() } private fun SafeContext.isWithinDigDirection(pos: BlockPos): Boolean { diff --git a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt index 97e290420..a8284547c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt @@ -19,9 +19,9 @@ package com.lambda.module.modules.world import com.lambda.Lambda.mc import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits @@ -33,10 +33,10 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.start +import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Describable @@ -149,7 +149,8 @@ object Printer : Module( .filter { DataManager.getRenderLayerRange().isPositionWithinRange(it) && inSchematic(it) } .associateWith { TargetState.State(schematicWorld.getBlockState(it)) } .filter { air || !it.value.blockState.isAir } - }.build(finishOnDone = false, async = async) { filterBuildResults(it) }.run() + }.build(finishOnDone = false, async = async) { filterBuildResults(it) } + .start() } /** diff --git a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt index f4fd7a190..7354bf66e 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.world -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.editTypedSettings import com.lambda.config.hide @@ -30,11 +30,11 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState @@ -74,11 +74,13 @@ object Scaffold : Module( } listen { - val selection = selectStack { - { it.blockItem.let { blockItem -> blockItem != null && blockItem.block !in blacklistedBlocks } } + val selection = stackSelection { + predicate { stack, _ -> + stack.blockItem.let { blockItem -> blockItem != null && blockItem.block !in blacklistedBlocks } + } } - val stack = player.mainHandStack.takeIf { selection.filterStack(it) } - ?: selection.filterStacks(HotbarContainer.stacks).firstOrNull() ?: return@listen + val stack = player.mainHandStack.takeIf { selection.matches(it) } + ?: selection.filter(HotbarContainer.stacks).firstOrNull() ?: return@listen val playerSupport = player.blockPos.down() val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) if (alreadySupported) return@listen diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index d25a7e5b4..4c29df3f8 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -20,9 +20,9 @@ package com.lambda.module.modules.world import baritone.api.pathing.goals.GoalBlock import com.lambda.Lambda.mc import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.hideBlock import com.lambda.config.settings.complex.Bind @@ -38,21 +38,30 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.container.containers.EnderChestContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.handler.handlers.findSlots +import com.lambda.interaction.handler.handlers.findStack +import com.lambda.interaction.handler.handlers.findStacks +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.module.ModuleTag import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.wrappers.thenAction +import com.lambda.task.start +import com.lambda.task.tasks.build +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState @@ -67,12 +76,6 @@ import com.lambda.util.extension.rotation import com.lambda.util.math.distSq import com.lambda.util.math.setAlpha import com.lambda.util.player.SlotUtils.allSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks -import com.lambda.util.player.SlotUtils.hotbarSlots -import com.lambda.util.player.SlotUtils.hotbarStacks -import com.lambda.util.player.SlotUtils.inventoryStacks -import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.bold import com.lambda.util.text.buildText import com.lambda.util.text.color @@ -363,7 +366,7 @@ object StashMover : Module( ) task = null } - .run() + .start() } fun pauseUnpause() { @@ -457,7 +460,9 @@ object StashMover : Module( MoverState.DispensingPearl -> handleDispensingPearl() MoverState.AwaitingPearl -> checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { - if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { + val hasPearl = findContainers(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY) + .any { it.stacks.any { it.item == Items.ENDER_PEARL } } + if (hasPearl) { pearlThrown = false moverState = MoverState.ThrowingPearl true @@ -504,7 +509,7 @@ object StashMover : Module( if (moverState != MoverState.AwaitingTeleport) return@listen val packet = event.packet if (packet !is PlayerPositionLookS2CPacket) return@listen - if (finished && player.hotbarAndInventoryStacks.all { it.isEmpty }) { + if (finished && findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).all { it.isEmpty }) { success(finishedMessage) return@listen } @@ -523,12 +528,17 @@ object StashMover : Module( moverState = MoverState.OpeningPullContainer return } - if (!moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) return + val move = + moveFromContainerToContainer( + screenHandler.containerSlots, + findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList() + ) + if (!move) return pullContainer?.let { container -> if (screenHandler.containerStacks.all { it.isEmpty }) { pullContainers.remove(container) pulledContainers.add(container) - if (player.hotbarAndInventoryStacks.any { it.isEmpty }) { + if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) != null) { moverState = MoverState.OpeningPullContainer return } @@ -546,7 +556,12 @@ object StashMover : Module( moverState = MoverState.OpeningPutEnderChest return } - if (moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) { + val move = + moveFromContainerToContainer( + findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList(), + screenHandler.containerStacks + ) + if (move) { moverState = MoverState.OpeningPullContainer } } @@ -583,7 +598,7 @@ object StashMover : Module( rotation(rotation) }.submit() if (!rotationRequest.done || rotation dist RotationManager.serverRotation > 0.001) return - val throwSlots = player.hotbarAndInventorySlots.filter { !it.stack.isEmpty } + val throwSlots = findSlots(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY).toList() if (throwSlots.isNotEmpty()) { val inventoryRequest = inventoryRequest(settleForLess = true) { throwSlots.forEach { slot -> @@ -600,12 +615,17 @@ object StashMover : Module( moverState = MoverState.OpeningPutContainer return } - if (!moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) return + val move = + moveFromContainerToContainer( + findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList(), + screenHandler.containerStacks + ) + if (!move) return putContainer?.let { container -> if (screenHandler.containerStacks.all { !it.isEmpty }) { putContainers.remove(container) filledContainers.add(container) - if (player.hotbarAndInventoryStacks.any { !it.isEmpty }) { + if (findStack(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) != null) { moverState = MoverState.OpeningPutContainer return } @@ -619,7 +639,12 @@ object StashMover : Module( moverState = MoverState.OpeningPullEnderChest return } - if (moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) { + val move = + moveFromContainerToContainer( + screenHandler.containerSlots, + findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList() + ) + if (move) { putOrThrowItems() } } @@ -631,13 +656,13 @@ object StashMover : Module( return } if (BaritoneHandler.isActive) return - if (player.hotbarStacks.none { it.isEmpty }) { - val firstSlot = player.hotbarSlots.getOrNull(0) ?: run { failWithLog("No first slot? This shouldn't occur.", ::failure); return } - if (player.inventoryStacks.any { it.isEmpty }) { + if (HotbarContainer.stacks.none { it.isEmpty }) { + val firstSlot = HotbarContainer.slots.getOrNull(0) ?: run { failWithLog("No first slot? This shouldn't occur.", ::failure); return } + if (InventoryContainer.stacks.any { it.isEmpty }) { inventoryRequest { quickMove(firstSlot.id) }.submit() return } else if (player.offHandStack.isEmpty) { - inventoryRequest { swap(firstSlot.id, 40) }.submit() + inventoryRequest { swapWithHotbar(firstSlot.id, 40) }.submit() return } failWithLog("No free slots for an ender pearl!", ::failure) @@ -662,11 +687,11 @@ object StashMover : Module( if (pearlThrown) { if (!player.offHandStack.isEmpty) { - if (player.hotbarAndInventoryStacks.none { it.isEmpty }) { + if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) == null) { failWithLog("No free slots to return the offhand stack to!", ::failure) return } - val offhandSlot = player.offHandSlots.firstOrNull() ?: run { failWithLog("No offhand slot? This shouldn't occur.", ::failure); return } + val offhandSlot = OffHandContainer.slots.firstOrNull() ?: run { failWithLog("No offhand slot? This shouldn't occur.", ::failure); return } inventoryRequest { quickMove(offhandSlot.id) }.submit() } putOrThrowItems() @@ -679,9 +704,9 @@ object StashMover : Module( }.submit() if (!rotationRequest.done) return if (player.mainHandStack.item != Items.ENDER_PEARL) { - val hotbarSlot = player.hotbarSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + val hotbarSlot = HotbarContainer.slots.firstOrNull { it.stack.item === Items.ENDER_PEARL } if (hotbarSlot != null) { - val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover, nowOrNothing = false).submit() + val hotbarRequest = hotbarRequest(hotbarSlot.index).submit() if (!hotbarRequest.done) return } else { val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } @@ -689,7 +714,7 @@ object StashMover : Module( failWithLog("No pearl in inventory!", ::failure) return } - inventoryRequest { swap(inventorySlot.id, 0) }.submit() + inventoryRequest { swapWithHotbar(inventorySlot.id, 0) }.submit() return } } @@ -748,10 +773,7 @@ object StashMover : Module( return } - OpenContainerTask( - pos, - StashMover - ).thenAction { + openContainer(pos).thenAction { onOpened(pos) }.execute(this@MoverBot) } diff --git a/src/main/kotlin/com/lambda/network/CapeHandler.kt b/src/main/kotlin/com/lambda/network/CapeHandler.kt index e92ba1540..241c1f653 100644 --- a/src/main/kotlin/com/lambda/network/CapeHandler.kt +++ b/src/main/kotlin/com/lambda/network/CapeHandler.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.LOG import com.lambda.Lambda.mc import com.lambda.config.Config import com.lambda.config.categories.SecretsCategory -import com.lambda.config.entries.Setting.Companion.onValueChangeUnsafe +import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.core.Loadable import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen diff --git a/src/main/kotlin/com/lambda/task/RootTask.kt b/src/main/kotlin/com/lambda/task/RootTask.kt index cd73c3aa1..db4abda1e 100644 --- a/src/main/kotlin/com/lambda/task/RootTask.kt +++ b/src/main/kotlin/com/lambda/task/RootTask.kt @@ -17,22 +17,12 @@ package com.lambda.task -import com.lambda.task.wrappers.TaskSupplier -import com.lambda.threading.runSafe +import com.lambda.task.Task.Ta5kBuilder object RootTask : Task() { override val name get() = "Root Task" - - @Ta5kBuilder - inline fun > T.run(): T { - execute(this@RootTask) - return this - } - - @Ta5kBuilder - fun Task<*>.run(task: TaskSupplier) { - runSafe { - task(Unit).execute(this@run) - } - } } + +@Ta5kBuilder +inline fun > T.start(): T = + apply { execute(RootTask) } diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index cbad1a911..65679fd1b 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -24,27 +24,27 @@ import com.lambda.event.Muteable import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.modules.client.Client.verboseDebug -import com.lambda.task.wrappers.onFail +import com.lambda.task.tasks.wrappers.withRecovery import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Nameable -import com.lambda.util.StringUtils.capitalize import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit import kotlin.time.toDuration @Suppress("unused") -abstract class Task : Nameable, Muteable { - var parent: Task<*>? = null - val subTasks = mutableListOf>() +abstract class Task : Nameable, Muteable { var state = State.Init - override val isMuted: Boolean get() = state == State.Paused || state == State.Init var age = 0 + + var parent: Task<*>? = null + val subTasks = mutableListOf>() private val depth: Int get() = parent?.depth?.plus(1) ?: 0 - val isCompleted get() = state == State.Completed val size: Int get() = subTasks.sumOf { it.size } + 1 - private val successCallbacks = mutableListOf Unit>() + override val isMuted: Boolean get() = state == State.Paused || state == State.Init + + private val successCallbacks = mutableListOf Unit>() private val completionCallbacks = mutableListOf Unit>() private val failureCallbacks = mutableListOf Unit>() @@ -60,11 +60,10 @@ abstract class Task : Nameable, Muteable { Cancelled, Failed, Completed; - - val display get() = name.lowercase().capitalize() } init { + if (this is RootTask) state = State.Running listen({ Int.MAX_VALUE }) { age++ } } @@ -75,6 +74,38 @@ abstract class Task : Nameable, Muteable { @DslMarker annotation class Ta5kBuilder + @Ta5kBuilder + context(parentTask: Task<*>) + fun start(pauseParent: Boolean = true) = + this.execute(parentTask, pauseParent) + + /** + * Executes the current task as a subtask of the specified owner task. + * + * This method adds the current task to the owner's subtasks, sets the parent relationship, + * logs the execution details, and invokes the necessary lifecycle hooks. Additionally, + * it manages the state of the parent task and starts any required listeners for execution. + * + * @param parent The parent task that will execute this task as a sub task. Must not be the same as this task. + * @param pauseParent Defines whether the parent task should be paused during the execution of this task. Defaults to `true`. + * @return The current task instance as a `Task` to support chaining or further configuration. + * @throws IllegalArgumentException if the owner task is the same as the task being executed. + */ + @Ta5kBuilder + fun execute(parent: Task<*>, pauseParent: Boolean = true): Task { + require(parent != this) { "Cannot execute a task as a sub task of itself" } + parent.subTasks.add(this) + this@Task.parent = parent + if (verboseDebug) LOG.info("${parent.name} started $name") + if (pauseParent) { + if (verboseDebug) LOG.info("$name pausing parent ${parent.name}") + if (parent !is RootTask) parent.pause() + } + state = State.Running + runSafe { runCatching { onStart() }.onFailure { failure(it) } } + return this + } + /** * Invoked when the task starts execution. * @@ -99,37 +130,12 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder protected open fun SafeContext.onCancel() {} - /** - * Executes the current task as a subtask of the specified owner task. - * - * This method adds the current task to the owner's subtasks, sets the parent relationship, - * logs the execution details, and invokes the necessary lifecycle hooks. Additionally, - * it manages the state of the parent task and starts any required listeners for execution. - * - * @param owner The parent task that will execute this task as a sub task. Must not be the same as this task. - * @param pauseParent Defines whether the parent task should be paused during the execution of this task. Defaults to `true`. - * @return The current task instance as a `Task` to support chaining or further configuration. - * @throws IllegalArgumentException if the owner task is the same as the task being executed. - */ @Ta5kBuilder - fun execute(owner: Task<*>, pauseParent: Boolean = true): Task { - require(owner != this) { "Cannot execute a task as a sub task of itself" } - owner.subTasks.add(this) - parent = owner - if (verboseDebug) LOG.info("${owner.name} started $name") - if (pauseParent) { - if (verboseDebug) LOG.info("$name pausing parent ${owner.name}") - if (owner !is RootTask) owner.pause() - } - state = State.Running - runSafe { runCatching { onStart() }.onFailure { failure(it) } } - return this - } - - @Ta5kBuilder - protected fun success(result: Result) { + protected fun success(result: R) { + if (state != State.Running && state != State.Paused) return unsubscribe() state = State.Completed + cancelSubTasks() parent?.onSubTaskSuccess(this) parent?.onSubTaskCompletion(this) @@ -152,15 +158,16 @@ abstract class Task : Nameable, Muteable { e: Throwable, stacktrace: MutableList> = mutableListOf(), ) { - state = State.Failed + if (state != State.Running && state != State.Paused) return unsubscribe() + state = State.Failed cancelSubTasks() stacktrace.add(this) parent?.onSubTaskFailure(this, e) ?: run { if (!verboseDebug) return@run - val message = + logError( buildString { val first = stacktrace.firstOrNull() ?: return@buildString append("${first.name} failed: ${e.message}\n") @@ -168,7 +175,7 @@ abstract class Task : Nameable, Muteable { append(" -> ${it.name}\n") } } - logError(message) + ) } parent?.onSubTaskCompletion(this) @@ -183,45 +190,45 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder protected fun failure(message: String) = failure(IllegalStateException(message)) - @Ta5kBuilder - fun activate() { - if (state != State.Paused) return - state = State.Running - } - - @Ta5kBuilder - fun pause() { - if (state != State.Running) return - state = State.Paused - } - @Ta5kBuilder fun cancel() = internalCancel(true) + @Ta5kBuilder private fun internalCancel(removeFromParent: Boolean = true) { - unsubscribe() - runSafe { onCancel() } + if (state != State.Running && state != State.Paused) return cancelSubTasks() - if (removeFromParent) parent?.subTasks?.remove(this) - parent?.activate() if (this is RootTask) return - if (state == State.Completed || state == State.Cancelled) return + unsubscribe() state = State.Cancelled + runSafe { + onCancel() + completionCallbacks.forEach { it.invoke(this) } + } + + if (removeFromParent) parent?.subTasks?.remove(this) + parent?.activate() } @Ta5kBuilder - fun cancelSubTasks() { + private fun cancelSubTasks() { subTasks.forEach { it.internalCancel(removeFromParent = false) } subTasks.clear() } @Ta5kBuilder - protected open fun onSubTaskSuccess(subTask: Task<*>) { - activate() + fun pause() { + if (state != State.Running) return + state = State.Paused } @Ta5kBuilder - protected open fun onSubTaskCompletion(subTask: Task<*>) { + fun activate() { + if (state != State.Paused) return + state = State.Running + } + + @Ta5kBuilder + protected open fun onSubTaskSuccess(subTask: Task<*>) { activate() } @@ -230,11 +237,16 @@ abstract class Task : Nameable, Muteable { failure(cause) } + @Ta5kBuilder + protected open fun onSubTaskCompletion(subTask: Task<*>) { + activate() + } + /** * Registers a callback for if the task succeeds. */ @Ta5kBuilder - fun onSuccess(callback: SafeContext.(Result) -> Unit): Task { + fun onSuccess(callback: SafeContext.(R) -> Unit): Task { successCallbacks.add(callback) return this } @@ -242,10 +254,10 @@ abstract class Task : Nameable, Muteable { /** * Registers a callback for if the task fails. * - * This could be mistaken for [onFail] which is used to wrap the given task with another task that runs a recovery task if this one fails. + * This could be mistaken for [withRecovery] which is used to wrap the given task with another task that runs a recovery task if this one fails. */ @Ta5kBuilder - fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task { + fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task { failureCallbacks.add(callback) return this } @@ -256,7 +268,7 @@ abstract class Task : Nameable, Muteable { * This is called regardless of whether the task succeeds or fails. */ @Ta5kBuilder - fun onCompletion(callback: SafeContext.() -> Unit): Task { + fun onCompletion(callback: SafeContext.() -> Unit): Task { completionCallbacks.add(callback) return this } @@ -266,7 +278,7 @@ abstract class Task : Nameable, Muteable { private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0, maxEntries: Int = 10) { if (task.state == State.Cancelled) return - appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.display}] ${(task.age * 50).milliseconds}" else "") + appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.name}] ${(task.age * 50).milliseconds}" else "") val left = task.subTasks.size - maxEntries if (left > 0) { appendLine("${" ".repeat((level + 1) * 4)}...and $left more tasks") @@ -276,3 +288,9 @@ abstract class Task : Nameable, Muteable { } } } + +typealias TaskGenerator = SafeContext.(R) -> Task +typealias TaskOrNullGenerator = SafeContext.(R) -> Task? + +typealias TaskSupplier = SafeContext.() -> Task +typealias TaskOrNullSupplier = SafeContext.() -> Task? \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt new file mode 100644 index 000000000..3547c047d --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2026 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.task.tasks + +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.containers.ArmorContainer.findSlot +import com.lambda.interaction.container.selection.select +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.softFail +import com.lambda.threading.runSafeAutomated +import com.lambda.util.BlockUtils.item +import net.minecraft.block.Block +import net.minecraft.util.math.BlockPos + +@Ta5kBuilder +context(automated: Automated) +fun acquirePlacedBlock( + block: Block, + maxSearchRadius: Int = 10 +) = AcquirePlacedBlockTask(block, maxSearchRadius, automated) + +class AcquirePlacedBlockTask @Ta5kBuilder internal constructor( + val block: Block, + val searchRadius: Int = 10, + automated: Automated +) : Task(), Automated by automated { + override val name get() = "Acquiring placed ${block.name.string}" + + init { + listen { + runSafeAutomated { + findSlot(block.item.select()).let { slot -> + PlaceContainerTask(slot, this) + .onSuccess { success(it) } + .execute(this@AcquirePlacedBlockTask) + return@listen + } + } + + failure("No $block found in range or hotbar/inventory!") + return@listen + } + } + + override fun SafeContext.onStart() { + findBlock(block, searchRadius) + .softFail() + .start() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt similarity index 56% rename from src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt rename to src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index e55d2fa9d..b9e289f4c 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -19,15 +19,24 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.handlers.ContainerHandler.findContainerWithMaterial -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.select import com.lambda.task.Task -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated -class AcquireMaterialTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun acquireStack(selection: StackSelection) = + AcquireStackTask(selection, automated) + +@Ta5kBuilder +context(automated: Automated) +fun acquireStack(selection: () -> StackSelection) = + AcquireStackTask(selection(), automated) + +class AcquireStackTask @Ta5kBuilder internal constructor( val selection: StackSelection, automated: Automated ) : Task(), Automated by automated { @@ -36,18 +45,10 @@ class AcquireMaterialTask @Ta5kBuilder constructor( override fun SafeContext.onStart() { runSafeAutomated { - selection.findContainerWithMaterial() - ?.transferByTask(selection, HotbarContainer) - ?.thenAction { - success(selection) - }?.execute(this@AcquireMaterialTask) - ?: failure(ContainerHandler.NoContainerFound(selection)) // ToDo: Create crafting path + selection.transfer(toSelection = HotbarContainer.select()) + .onSuccess { result -> success(result.stackSelection) } + .onFailure { failure(it) } + .execute(this@AcquireStackTask) } } - - companion object { - @Ta5kBuilder - fun Automated.acquire(selection: () -> StackSelection) = - AcquireMaterialTask(selection(), this) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index c2abfdfa2..e4ca93e65 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -17,7 +17,6 @@ package com.lambda.task.tasks -import baritone.api.pathing.goals.GoalBlock import com.lambda.Lambda.LOG import com.lambda.config.blocks.EatConfig.Companion.reasonEating import com.lambda.context.Automated @@ -29,8 +28,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.blueprint.TickingBlueprint +import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.Simulation.Companion.simulation import com.lambda.interaction.construction.simulation.context.BuildContext @@ -46,14 +45,14 @@ import com.lambda.interaction.construction.simulation.result.results.InteractRes import com.lambda.interaction.construction.simulation.result.results.PreSimResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest -import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.managers.breaking.breakRequest +import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task -import com.lambda.task.tasks.EatTask.Companion.eat -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.softFail +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runConcurrent import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated @@ -62,9 +61,7 @@ import com.lambda.util.EntityUtils.getClosestPointTo import com.lambda.util.EntityUtils.getPositionsWithinBox import com.lambda.util.FormattingUtils.format import com.lambda.util.extension.Structure -import com.lambda.util.extension.playerSlots import com.lambda.util.math.dist -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking import net.minecraft.block.BlockState @@ -79,7 +76,50 @@ import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.cancellation.CancellationException import kotlin.math.sqrt -class BuildTask private constructor( +@Ta5kBuilder +fun Automated.build( + finishOnDone: Boolean = true, + collectDrops: Boolean = buildConfig.collectDrops, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, + blueprint: () -> Blueprint +) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, async, this, buildResultFilter) + +@Ta5kBuilder +context(automated: Automated) +fun Structure.build( + finishOnDone: Boolean = true, + collectDrops: Boolean = automated.buildConfig.collectDrops, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, +) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) + +@Ta5kBuilder +context(automated: Automated) +fun Blueprint.build( + finishOnDone: Boolean = true, + collectDrops: Boolean = automated.buildConfig.collectDrops, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, +) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) + +@Ta5kBuilder +context(automated: Automated) +fun breakAndCollect( + blockPos: BlockPos, + finishOnDone: Boolean = true, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, +) = BuildTask( + blockPos.toStructure(TargetState.Empty).toBlueprint(), + finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter +) + +class BuildTask @Ta5kBuilder internal constructor( private val blueprint: Blueprint, private val finishOnDone: Boolean, private val collectDrops: Boolean, @@ -98,6 +138,7 @@ class BuildTask private constructor( private var breaks = 0 private val dropsToCollect = mutableSetOf() var eatTask: Task<*>? = null + var collectDropsTask: Task<*>? = null private val onItemDrop: ((item: ItemEntity) -> Unit)? get() = if (collectDrops) { item -> @@ -205,7 +246,7 @@ class BuildTask private constructor( lifeMaintenance && eatTask == null && runSafeAutomated { reasonEating() }.shouldEat() -> { eatTask = eat() .thenAction { eatTask = null } - .execute(this@BuildTask) + .start() return true } @@ -307,86 +348,25 @@ class BuildTask private constructor( is Resolvable -> { LOG.info("Resolving: ${result.name}") result.resolve() + .softFail() + .start() } } } - private fun SafeContext.collectDrops() = - dropsToCollect - .firstOrNull() - ?.let { itemDrop -> - if (pendingInteractions.isNotEmpty()) return@let true - - if (!world.entities.contains(itemDrop)) { - dropsToCollect.remove(itemDrop) - BaritoneHandler.cancel() - return@let true - } - - if (player.hotbarAndInventoryStacks.none { it.isEmpty }) { - val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { - it.stack.item in inventoryConfig.disposables - } ?: run { - failure("No item in inventory to throw but inventory is full and cant pick up item drop") - return@let true - } - inventoryRequest { - throwStack(stackToThrow.id) - }.submit() - return@let true - } - - BaritoneHandler.setGoalAndPath(GoalBlock(itemDrop.blockPos)) - true - } ?: false + private fun AutomatedSafeContext.collectDrops(): Boolean { + if (dropsToCollect.isEmpty()) return false + if (pendingInteractions.isNotEmpty()) return true + if (collectDropsTask != null) return true + collectDropsTask = collectDrops(dropsToCollect) + .thenAction { collectDropsTask = null } + .start() + return true + } - fun iteratePropagating() = + private fun iteratePropagating() = if (blueprint is PropagatingBlueprint) { blueprint.next() ?: failure("Failed to propagate the next blueprint") true } else false - - companion object { - @Ta5kBuilder - fun Automated.build( - finishOnDone: Boolean = true, - collectDrops: Boolean = buildConfig.collectDrops, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - blueprint: () -> Blueprint - ) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, async, this, buildResultFilter) - - @Ta5kBuilder - context(automated: Automated) - fun Structure.build( - finishOnDone: Boolean = true, - collectDrops: Boolean = automated.buildConfig.collectDrops, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) - - @Ta5kBuilder - context(automated: Automated) - fun Blueprint.build( - finishOnDone: Boolean = true, - collectDrops: Boolean = automated.buildConfig.collectDrops, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - ) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) - - @Ta5kBuilder - fun Automated.breakAndCollectBlock( - blockPos: BlockPos, - finishOnDone: Boolean = true, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - ) = BuildTask( - blockPos.toStructure(TargetState.Empty).toBlueprint(), - finishOnDone, true, lifeMaintenance, async, this, buildResultFilter - ) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt new file mode 100644 index 000000000..19bc25c21 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2026 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.task.tasks + +import baritone.api.pathing.goals.GoalBlock +import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findStack +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.threading.runSafeAutomated +import com.lambda.util.extension.playerSlots +import net.minecraft.entity.ItemEntity + +@Ta5kBuilder +context(automated: Automated) +fun collectDrops(drops: MutableSet) = CollectDropsTask(drops, automated) + +/** + * A task that collects a set of item entity drops by walking to them. + * + * Items are considered collected when they despawn from the world (picked up by the player). + * If inventory is full, disposable items are thrown to make space. + * + * Succeeds when all drops have been collected. + */ +class CollectDropsTask @Ta5kBuilder internal constructor( + private val drops: MutableSet, + automated: Automated +) : Task(), Automated by automated { + override val name get() = "Collecting ${drops.size} item drop(s)" + + init { + listen { + runSafeAutomated { + do { + val done = pollNextItem() + } while (!done) + } + } + } + + private fun AutomatedSafeContext.pollNextItem(): Boolean { + val target = + drops.firstOrNull() + ?: run { + BaritoneHandler.cancel() + success() + return true + } + + if (!world.entities.contains(target)) { + drops.remove(target) + BaritoneHandler.cancel() + return false + } + + if (findStack(stackSelection(0) { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY, false) == null) { + val stackToThrow = + player.currentScreenHandler.playerSlots.firstOrNull { + it.stack.item in inventoryConfig.disposables + } ?: run { + failure("Inventory is full and no disposable items to throw") + return true + } + inventoryRequest { + throwStack(stackToThrow.id) + }.submit() + return true + } + + BaritoneHandler.setGoalAndPath(GoalBlock(target.blockPos)) + + return true + } + + override fun SafeContext.onCancel() { + BaritoneHandler.cancel() + } +} diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 02dede417..c2f46dcfe 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -18,92 +18,290 @@ package com.lambda.task.tasks import com.lambda.context.Automated -import com.lambda.event.EventFlow.post -import com.lambda.event.events.ContainerEvent +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.container.containers.InventoryContainer +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ExternalContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.container.selection.selectContainers +import com.lambda.interaction.handler.handlers.findContainers import com.lambda.task.Task -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.taskOrNull +import com.lambda.task.tasks.wrappers.then +import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.threading.runSafeAutomated +import net.minecraft.screen.slot.Slot -class ContainerTransferTask( - private var fromContainer: MaterialContainer, - private val toContainer: MaterialContainer, - private val stackSelection: StackSelection, - automated: Automated, - private val failIfNoMaterial: Boolean = false -) : Task(), Automated by automated { - override val name = "Transferring $stackSelection from $fromContainer to $toContainer" +@Ta5kBuilder +context(automated: Automated) +fun transfer( + fromStack: StackSelection, + fromSelection: ContainerSelection = ContainerSelection.ACCESSED, + toSelection: ContainerSelection, + toStack: StackSelection = StackSelection.ANYTHING +) = ContainerTransferTask(fromStack, fromSelection, toSelection, toStack, automated) - private var delegateTask: Task<*>? = null +@Ta5kBuilder +@JvmName("transferExt") +context(automated: Automated) +fun StackSelection.transfer( + fromSelection: ContainerSelection = ContainerSelection.ACCESSED, + toSelection: ContainerSelection, + toStack: StackSelection = StackSelection.ANYTHING +) = transfer(this, fromSelection, toSelection, toStack) - init { - listen { - if (delegateTask?.isCompleted == true) { - success() - return@listen - } - runSafeAutomated { - val fromExternal = fromContainer as? ExternalContainer - val slots = fromContainer.slots - val toSlots = toContainer.slots - fromExternal.takeIf { slots.isEmpty() }?.let { fromExternal -> - if (toContainer is ExternalContainer && toSlots.isEmpty()) { - fromContainer.transferByTask(stackSelection, InventoryContainer) - .thenAction { fromContainer = InventoryContainer } - .execute(this@ContainerTransferTask) - return@listen - } - delegateTask = fromExternal.accessThen { - fromContainer.transferByTask(stackSelection, toContainer) - }?.execute(this@ContainerTransferTask) ?: run { - checkFail() - return@listen - } - return@listen +class TransferResult internal constructor( + val stackSelection: StackSelection, + val containerSelection: ContainerSelection +) + +class ContainerTransferTask @Ta5kBuilder internal constructor( + private val fromStack: StackSelection, + private val fromSelection: ContainerSelection, + private val toSelection: ContainerSelection, + private val toStack: StackSelection, + automated: Automated +) : Task(), Automated by automated { + override val name = "Transferring $fromStack" + + private var transferred = 0 + private val resultSlots = mutableListOf() + private val resultContainers = mutableSetOf() + + override fun SafeContext.onStart() { + val fromContainers = + findContainers( + containerSelection(fromSelection.scope) { + matches(fromSelection) + hasStack(fromStack.mutate(1)) } - if (toContainer is ExternalContainer && toSlots.isEmpty()) { - delegateTask = toContainer.accessThen { - fromContainer.transferByTask(stackSelection, toContainer) - }?.execute(this@ContainerTransferTask) ?: run { - checkFail() - return@listen - } - return@listen + ).toList() + .takeIf { fromStack isIn it } + ?: run { + failure("Could not find source containers for $fromStack") + return } - fromContainer.getSlot(stackSelection)?.let { fromSlot -> - toContainer.getReplaceableSlot()?.let { toSlot -> - val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer) - if (transferEvent.post().isCanceled()) { - failure("Transfer prevented by an internal interruption") - return@listen - } - inventoryRequest { - if (fromContainer.swapMethodPriority > toContainer.swapMethodPriority) - with(fromContainer) { transfer(fromSlot, toSlot) } - else with(toContainer) { transfer(toSlot, fromSlot) } - onComplete { success() } - }.submit() - return@listen - } + val toContainers = + findContainers( + containerSelection(toSelection.scope) { + matches(toSelection) + hasSpace(fromStack.mutate(1)) + } + ).toList() + .takeIf { fromStack spaceIn it } + ?: run { + failure("Could not find destination containers with space for $fromStack") + return } - checkFail() + transferNextPair(fromContainers.toMutableList(), toContainers.toMutableList()) + } + + private fun remainingSelection() = + fromStack.mutate(count = if (fromStack.count <= 0) 0 else fromStack.count - transferred) + + private fun isComplete() = + fromStack.count > 0 && transferred >= fromStack.count + private fun buildResult() = + TransferResult( + stackSelection { + predicate { _, slot -> + slot != null && + resultSlots.any { + slot.inventory::class == it.inventory::class && slot.index == it.index + } + } + }, + selectContainers(*resultContainers.toTypedArray()) + ) + + /** + * Takes the first container from each queue as a pair, builds a transfer + * chain for that pair, then advances to the next pair on completion. + * + * Calls [success] or [failure] directly on this task when done. + */ + private fun transferNextPair( + fromQueue: MutableList, + toQueue: MutableList + ) { + if (isComplete()) { + success(buildResult()) + return + } + + if (fromQueue.isEmpty() || toQueue.isEmpty()) { + failure("Not enough containers to transfer $fromStack (transferred $transferred/${fromStack.count})") + return + } + + val fromContainer = fromQueue.first() + val toContainer = toQueue.first() + + val bothExternal = fromContainer is ExternalContainer && !fromContainer.isAccessed && + toContainer is ExternalContainer && !toContainer.isAccessed + + val pairChain = + if (bothExternal) pullPushChain(fromContainer, toContainer, toQueue) + else directPairChain(fromContainer, toContainer) + + pairChain.onSuccess { + if (fromContainer.count(fromStack) <= 0) fromQueue.remove(fromContainer) + if (!bothExternal && toContainer.spaceLeft(fromStack) <= 0) toQueue.remove(toContainer) + transferNextPair(fromQueue, toQueue) + }.onFailure { + failure(it) + }.execute(this@ContainerTransferTask) + } + + /** + * Builds a chain for a pair where at most one container is external. + * Uses [taskOrNull] to uniformly handle already-accessed containers. + */ + private fun directPairChain( + fromContainer: Container, + toContainer: Container + ) = + taskOrNull { fromContainer.access() }.then { fromContext -> + taskOrNull { toContainer.access() }.then { toContext -> + SwapTask(fromContainer.select(), toContainer.select()) + .thenOrNull { toContext?.close() } + .thenOrNull { fromContext?.close() } } } + + /** + * Builds a chain for when both containers are external. + * Pulls items from [fromContainer] into the player inventory, then delegates + * to [pushChain] to push them into [toContainer]. + */ + private fun pullPushChain( + fromContainer: Container, + toContainer: Container, + toQueue: MutableList + ) = + taskOrNull { fromContainer.access() }.then { fromContext -> + SwapTask( + fromContainer.select(), + ContainerSelection.HOTBAR_AND_INVENTORY, + replaceSelection = StackSelection.ANYTHING, + trackResult = false + ).thenOrNull { fromContext?.close() } + }.then { pushChain(toContainer, toQueue) } + + /** + * Pushes items from the player inventory into [destination]. + * If [destination] fills up but the player still has items, advances [toQueue] + * and pushes to the next destination. + */ + private fun pushChain( + destination: Container, + toQueue: MutableList + ): Task<*> { + val chain = taskOrNull { destination.access() }.then { destinationContext -> + SwapTask(ContainerSelection.HOTBAR_AND_INVENTORY, destination.select()) + .thenOrNull { destinationContext?.close() } + } + chain.onSuccess { + if (isComplete()) return@onSuccess + + if (destination.spaceLeft(fromStack) == 0) toQueue.remove(destination) + + val playerHasItems = findContainers(ContainerSelection.HOTBAR_AND_INVENTORY) + .any { container -> fromStack.mutate(1) isIn container } + if (!playerHasItems) return@onSuccess + + val nextDestination = toQueue.firstOrNull() ?: return@onSuccess + pushChain(nextDestination, toQueue).execute(this@ContainerTransferTask) + }.onFailure { + failure(it) + } + return chain } + /** + * Swaps items between containers matching [fromSelection] and [toSelection] one slot per tick. + * Completes when no more valid move slots can be found or the total count is reached. + * Updates [transferred], [resultSlots], and [resultContainers] on the outer task. + * + * @param trackResult whether to record destination slots in [resultSlots]/[resultContainers]. + * Set to `false` for intermediary transfers (e.g. pulling to player inventory). + */ + private inner class SwapTask @Ta5kBuilder constructor( + private val fromSelection: ContainerSelection, + private val toSelection: ContainerSelection, + private val replaceSelection: StackSelection = toStack, + private val trackResult: Boolean = true + ) : Task() { + override val name = "Swapping slots" - private fun checkFail(): Boolean = - failIfNoMaterial.also { - failure(NoMaterialAccessException(stackSelection)) + init { + listen { + runSafeAutomated { + swapStack() + } + } } - private class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") + private fun AutomatedSafeContext.swapStack(): Boolean { + val currentSelection = remainingSelection() + + val moveSlots = + findContainers(fromSelection).firstNotNullOfOrNull { fromContainer -> + findContainers(toSelection).firstNotNullOfOrNull { toContainer -> + val (from, to) = + fromContainer.findMoveSlots( + currentSelection, + toContainer, + replaceSelection + ) + if (from != null && to != null) { + Triple( + fromContainer, + toContainer, + Pair(from, to) + ) + } else null + } + } + + if (moveSlots == null) { + success() + return true + } + + val (fromContainer, toContainer, slots) = moveSlots + val (fromSlot, toSlot) = slots + val moveCount = + if (fromStack.count <= 0) fromSlot.stack.count + else minOf(fromSlot.stack.count, fromStack.count - transferred) + + if (!fromContainer.swap(fromSlot, toSlot, toContainer)) { + failure("Swap failed for $fromStack") + return true + } + + transferred += moveCount + if (trackResult) { + resultSlots.add(toSlot) + resultContainers.add(toContainer) + } + + if (isComplete()) { + success() + return true + } + + return false + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index e67320e33..546523535 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -23,16 +23,22 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.material.container.containers.HotbarContainer -import com.lambda.interaction.material.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack import net.minecraft.util.ActionResult import net.minecraft.util.Hand -class EatTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun eat() = EatTask(automated) + +class EatTask @Ta5kBuilder internal constructor( automated: Automated ) : Task(), Automated by automated { override val name: String @@ -41,7 +47,7 @@ class EatTask @Ta5kBuilder constructor( private var eatStack: ItemStack? = null private var reason = EatConfig.Reason.None private var holdingUse = false - + private var activeTransfer: ContainerTransferTask? = null override fun SafeContext.onStart() { reason = runSafeAutomated { reasonEating() } } @@ -56,20 +62,30 @@ class EatTask @Ta5kBuilder constructor( return@listen } - val foodFinder = reason.selector() - val hotbarSlot = foodFinder.filterSlots(HotbarContainer.slots).firstOrNull() + val selection = reason.selector() + val hotbarSlot = selection.bestMatch(HotbarContainer.slots) if (hotbarSlot != null) { - val request = HotbarRequest( - hotbarSlot.index, - this@EatTask, - keepTicks = hotbarConfig.keepTicks.coerceAtLeast(1), - nowOrNothing = false - ).submit() + val request = + hotbarRequest(hotbarSlot.index) { + keepTicks(hotbarConfig.keepTicks.coerceAtLeast(1)) + }.submit() if (!request.done) return@listen } else { - val inventorySlot = foodFinder.filterSlots(InventoryContainer.slots).firstOrNull() - if (inventorySlot != null) runSafeAutomated { - InventoryContainer.transfer(foodFinder, HotbarContainer) + if (InventoryContainer.slots.any { selection.matches(it) }) { + runSafeAutomated { + if (activeTransfer == null || activeTransfer?.state in listOf(Task.State.Completed, Task.State.Failed, Task.State.Cancelled)) { + activeTransfer = transfer( + selection, + InventoryContainer.select(), + HotbarContainer.select() + ).apply { + onSuccess { activeTransfer = null } + onFailure { activeTransfer = null } + start() + } + } + return@listen + } } if (holdingUse) { mc.options.useKey.isPressed = false @@ -96,10 +112,4 @@ class EatTask @Ta5kBuilder constructor( } } } - - companion object { - @Ta5kBuilder - context(automated: Automated) - fun eat() = EatTask(automated) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt new file mode 100644 index 000000000..8b755ef92 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 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.task.tasks + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.util.world.blockSearch +import net.minecraft.block.Block +import net.minecraft.util.math.BlockPos + +@Ta5kBuilder +fun findBlock(block: Block, searchRadius: Int) = FindPlacedBlockTask(block, searchRadius) + +class FindPlacedBlockTask @Ta5kBuilder internal constructor( + private val block: Block, + private val searchRadius: Int +) : Task() { + override val name = "Finding placed block: $block" + + override fun SafeContext.onStart() { + val pos = + blockSearch(searchRadius, player.blockPos) { _, state -> + state.isOf(block) + }.keys.firstOrNull() + + if (pos != null) success(pos) + else failure(IllegalStateException("Could not find block $block")) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt index cc79f7314..be2dbea17 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -22,9 +22,10 @@ import com.lambda.context.Automated import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated import com.lambda.util.TickTimer import com.lambda.util.player.RotationUtils.lookAtBlock @@ -34,11 +35,19 @@ import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class OpenContainerTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun openContainer( + blockPos: BlockPos, + waitForSlotLoad: Boolean = true, + sides: Set = Direction.entries.toSet() +) = OpenContainerTask(blockPos, waitForSlotLoad, sides, automated) + +class OpenContainerTask @Ta5kBuilder internal constructor( private val blockPos: BlockPos, - private val automated: Automated, private val waitForSlotLoad: Boolean = true, - private val sides: Set = Direction.entries.toSet() + private val sides: Set = Direction.entries.toSet(), + private val automated: Automated ) : Task(), Automated by automated { override val name get() = "${containerState.description()} at ${blockPos.toShortString()}" diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index f4fde6b10..58a1f0c88 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -20,18 +20,17 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.ManagerUtils +import com.lambda.interaction.manager.ManagerUtils import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.math.distSq import net.minecraft.block.ChestBlock import net.minecraft.entity.mob.ShulkerEntity @@ -41,13 +40,26 @@ import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class PlaceContainerTask @Ta5kBuilder constructor( - val slot: Slot, +@Ta5kBuilder +context(automated: Automated) +fun placeContainer(slot: Slot?) = PlaceContainerTask(slot, automated) + +@Ta5kBuilder +context(automated: Automated) +fun placeContainer(slot: () -> Slot?) = PlaceContainerTask(slot(), automated) + +class PlaceContainerTask @Ta5kBuilder internal constructor( + val slot: Slot?, automated: Automated ) : Task(), Automated by automated { - override val name: String get() = "Placing container ${slot.stack.name.string}" + override val name: String get() = "Placing container ${slot?.stack?.name?.string}" override fun SafeContext.onStart() { + if (slot == null) { + failure("No slot provided") + return + } + val results = runSafeAutomated { BlockPos.iterateOutwards(player.blockPos, 4, 3, 4) .map { it.blockPos } @@ -75,8 +87,8 @@ class PlaceContainerTask @Ta5kBuilder constructor( .toStructure(TargetState.Stack(slot.stack)) .toBlueprint() .build(finishOnDone = true, collectDrops = false) - .thenAction { success(containerPosition) } - .execute(this@PlaceContainerTask) + .onSuccess { success(containerPosition) } + .start() } private fun SafeContext.canBeOpened( @@ -87,7 +99,7 @@ class PlaceContainerTask @Ta5kBuilder constructor( Items.ENDER_CHEST -> { !ChestBlock.isChestBlocked(world, blockPos) } - in shulkerBoxes -> { + in SHULKER_BOXES -> { val box = ShulkerEntity .calculateBoundingBox(0.5f, direction, 0.0f, blockPos.toBottomCenterPos()) .offset(blockPos) diff --git a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt new file mode 100644 index 000000000..867777ba5 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 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.task.tasks + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.util.player.SlotUtils.matches +import net.minecraft.screen.slot.Slot + +@Ta5kBuilder +fun slotByCache(slotCache: Slot) = SlotByCacheTask(slotCache) + +class SlotByCacheTask @Ta5kBuilder internal constructor( + val slotCache: Slot +) : Task() { + override val name = "Finding slot by cache: $slotCache" + + override fun SafeContext.onStart() { + val slots = player.currentScreenHandler.slots + val slot = slots.find { it matches slotCache } + if (slot != null) success(slot) + else failure("Failed to find matching slot by cache: $slotCache") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/wrappers/ActionTask.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt similarity index 68% rename from src/main/kotlin/com/lambda/task/wrappers/ActionTask.kt rename to src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt index 01f3af7c2..ea5d58a2e 100644 --- a/src/main/kotlin/com/lambda/task/wrappers/ActionTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt @@ -15,16 +15,30 @@ * along with this program. If not, see . */ -package com.lambda.task.wrappers +package com.lambda.task.tasks.wrappers import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @Ta5kBuilder -infix fun Task.thenAction(action: SafeContext.(R) -> Unit): Task = +fun actionTask(action: SafeContext.() -> R) = + ActionTask(action) + +@Ta5kBuilder +infix fun Task.thenAction(action: SafeContext.(R) -> R2): Task = SequencedActionTask(this, action) +class ActionTask @Ta5kBuilder internal constructor( + private val action: SafeContext.() -> R +) : Task() { + override val name get() = "Performing action" + + override fun SafeContext.onStart() { + success(action()) + } +} + /** * A task that performs a given task ([innerTask]) and then a given [action]. * @@ -32,18 +46,17 @@ infix fun Task.thenAction(action: SafeContext.(R) -> Unit): Task = * * @see thenAction */ -class SequencedActionTask( +class SequencedActionTask @Ta5kBuilder internal constructor( private val innerTask: Task, - private val action: SafeContext.(R) -> Unit, -) : Task() { + private val action: SafeContext.(R) -> R2, +) : Task() { override val name get() = "Performing action after ${innerTask.name}" override fun SafeContext.onStart() { innerTask .onSuccess { result -> - action(this, result) - success(result) + success(action(this, result)) } - .execute(this@SequencedActionTask) + .start() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt new file mode 100644 index 000000000..1228310e9 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2026 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.task.tasks.wrappers + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator + +@Ta5kBuilder +fun Task.withBranch( + onSuccess: TaskGenerator, + onFailure: TaskGenerator +) = BranchTask(this, onSuccess, onFailure) + +@Ta5kBuilder +fun Task.withBranchOrNull( + onSuccess: TaskOrNullGenerator, + onFailure: TaskOrNullGenerator +) = OptionalBranchTask(this, onSuccess, onFailure) + +/** + * A task that strictly branches into one of two tasks based on the success or failure of the [innerTask]. + * + * @see withBranch + */ +class BranchTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onSuccessGenerator: TaskGenerator, + private val onFailureGenerator: TaskGenerator +) : Task() { + override val name get() = "Branching task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { result -> + onSuccessGenerator(this, result) + .onSuccess { success(it) } + .execute(this@BranchTask) + } + .onFailure { cause -> + onFailureGenerator(this, cause) + .onSuccess { success(it) } + .execute(this@BranchTask) + } + .execute(this@BranchTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that strictly branches into one of two optional tasks based on the success or failure of the [innerTask]. + * + * @see withBranchOrNull + */ +class OptionalBranchTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onSuccessGenerator: TaskOrNullGenerator, + private val onFailureGenerator: TaskOrNullGenerator +) : Task() { + override val name get() = "Optional branching task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { result -> + onSuccessGenerator(this, result) + ?.onSuccess { success(it) } + ?.execute(this@OptionalBranchTask) + ?: success(null) + } + .onFailure { cause -> + onFailureGenerator(this, cause) + ?.onSuccess { success(it) } + ?.execute(this@OptionalBranchTask) + ?: failure("Optional onFailure task not present for task: ${innerTask.name}") + } + .execute(this@OptionalBranchTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt new file mode 100644 index 000000000..a2b242777 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt @@ -0,0 +1,167 @@ +/* + * Copyright 2026 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.task.tasks.wrappers + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator +import com.lambda.threading.runSafe + +@Ta5kBuilder +infix fun Task.onFail(handler: TaskGenerator) = + OnFailTask(this, handler) + +@Ta5kBuilder +infix fun Task.onFailOrNull(handler: TaskOrNullGenerator) = + OnFailOrNullTask(this, handler) + +@Ta5kBuilder +infix fun Task.withRecovery(handler: TaskGenerator) = + RecoveryTask(this, handler) + +@Ta5kBuilder +infix fun Task.withRecoveryOrNull(handler: SafeContext.(Throwable) -> Task?) = + OptionalRecoveryTask(this, handler) + +/** + * A task that performs a given [innerTask] with a callback task in the event the [innerTask] fails. + * + * Useful for when you want a certain task to trigger when another task fails. + * If in need of a task that returns the same type to avoid further branching, see [withRecovery] or [withRecoveryOrNull]. + * + * @see onFail + */ +class OnFailTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onFailTaskGenerator: TaskGenerator +) : Task() { + override val name get() = "On fail task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .onFailure { cause -> + onFailTaskGenerator(cause) + .onSuccess { success(null) } + .execute(this@OnFailTask) + } + .execute(this@OnFailTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that performs a given [innerTask] with an optional callback task in the event the [innerTask] fails. + * + * Useful for when you want a certain task to trigger based on a predicate when another task fails. + * If in need of a task that returns the same type to avoid further branching, see [withRecovery] or [withRecoveryOrNull]. + * + * @see onFailOrNull + */ +class OnFailOrNullTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onFailTaskGenerator: TaskOrNullGenerator +) : Task() { + override val name get() = "On fail task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .execute(this@OnFailOrNullTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask == innerTask) { + runSafe { + onFailTaskGenerator(cause) + ?.onSuccess { success(null) } + ?.execute(this@OnFailOrNullTask) + ?: super.onSubTaskFailure(subTask, cause) + } + } else super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that performs a given [innerTask] with a fallback to the task supplied by [recoveryTaskGenerator] in case of failure. + * Both the [innerTask] and the task generated by [recoveryTaskGenerator] have the same result type. + * + * Useful for if you want to have a backup task to return the same result type in case the first fails. + * This leaves the task branch clean of further branching while providing protection against individual task failure. + * + * @see withRecovery + */ +class RecoveryTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val recoveryTaskGenerator: TaskGenerator, +) : Task() { + override val name get() = "Recovery task in case of failure for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .onFailure { cause -> + recoveryTaskGenerator(this, cause) + .onSuccess { success(it) } + .execute(this@RecoveryTask) + } + .execute(this@RecoveryTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that performs a given [innerTask] with a fallback to the optional task supplied by [recoveryTaskGenerator] in case of failure. + * Both the [innerTask] and the optional task generated by [recoveryTaskGenerator] have the same result type. + * + * Useful for if you want to have an optional backup task to return the same result type in case the first fails. + * This leaves the task branch clean of further branching while providing protection against individual task failure, unless null is returned from [recoveryTaskGenerator]. + * + * @see withRecovery + */ +class OptionalRecoveryTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val recoveryTaskGenerator: TaskOrNullGenerator, +) : Task() { + override val name get() = "Optional recovery task in case of failure for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .execute(this@OptionalRecoveryTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask == innerTask) { + runSafe { + recoveryTaskGenerator(this, cause) + ?.onSuccess { success(it) } + ?.execute(this@OptionalRecoveryTask) + ?: super.onSubTaskFailure(subTask, cause) + } + } else super.onSubTaskFailure(subTask, cause) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/wrappers/SequenceTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt similarity index 58% rename from src/main/kotlin/com/lambda/task/wrappers/SequenceTasks.kt rename to src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt index c8726880c..ea00d449d 100644 --- a/src/main/kotlin/com/lambda/task/wrappers/SequenceTasks.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt @@ -15,21 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.task.wrappers +package com.lambda.task.tasks.wrappers import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator +import com.lambda.task.TaskOrNullSupplier @Ta5kBuilder -infix fun Task.then(supplier: TaskSupplier) = - SequencedTask(this, supplier) +infix fun Task.then(generator: TaskGenerator) = + SequencedTask(this, generator) @Ta5kBuilder -infix fun Task.then(task: Task): Task { - require(task != this) { "Cannot link a task to itself" } - return SequencedTask(this) { task } -} +infix fun Task.then(task: Task): Task = + SequencedTask(this) { task } @Ta5kBuilder fun Task.then(vararg tasks: Task<*>) = @@ -38,19 +39,22 @@ fun Task.then(vararg tasks: Task<*>) = } @Ta5kBuilder -infix fun Task.thenOrNull(supplier: SafeContext.(R) -> Task?) = - OptionalSequencedTask(this, supplier) +infix fun Task.thenOrNull(generator: TaskOrNullGenerator) = + OptionalSequencedTask(this, generator) + +@Ta5kBuilder +fun taskOrNull(supplier: TaskOrNullSupplier) = TaskOrNullTask(supplier) /** - * A task that sequences the [firstTask] with the next task, supplied by [nextTaskSupplier]. + * A task that sequences the [firstTask] with the next task, supplied by [nextTaskGenerator]. * * Useful for when two tasks must be run sequentially. The result of the first task is fed into the supplier for the second. * * @see then */ -class SequencedTask( +class SequencedTask @Ta5kBuilder internal constructor( private val firstTask: Task, - private val nextTaskSupplier: TaskSupplier, + private val nextTaskGenerator: TaskGenerator, ) : Task() { override val name get() = "Chaining ${firstTask.name}" var second: Task? = null @@ -58,7 +62,7 @@ class SequencedTask( override fun SafeContext.onStart() { firstTask .onSuccess { result -> - second = nextTaskSupplier(this, result) + second = nextTaskGenerator(this, result) .onSuccess { success(it) } .execute(this@SequencedTask) } @@ -67,26 +71,46 @@ class SequencedTask( } /** - * A task that sequences the [firstTask] with the next optional task, supplied by [nextTaskSupplier]. + * A task that sequences the [firstTask] with the next optional task, supplied by [nextTaskGenerator]. * * Useful for when you want to sequence a second task after the first based on a predicate. The result of the first task could help shape that outcome. * * @see thenOrNull */ -class OptionalSequencedTask( +class OptionalSequencedTask @Ta5kBuilder internal constructor( private val firstTask: Task, - private val nextTaskSupplier: TaskOrNullSupplier + private val nextTaskGenerator: TaskOrNullGenerator ) : Task() { override val name get() = "Chaining ${firstTask.name}" override fun SafeContext.onStart() { firstTask .onSuccess { result -> - nextTaskSupplier(this, result) + nextTaskGenerator(this, result) ?.onSuccess { success(it) } ?.execute(this@OptionalSequencedTask) ?: success(null) } .execute(this@OptionalSequencedTask) } +} + +/** + * A task that runs an optional task, supplied by [taskOrNullSupplier]. + * + * Useful for starting a task branch where the first task could or could not be skipped. + * + * @see taskOrNull + */ +class TaskOrNullTask @Ta5kBuilder internal constructor( + private val taskOrNullSupplier: TaskOrNullSupplier +) : Task() { + override val name get() = "Optional task" + + override fun SafeContext.onStart() { + taskOrNullSupplier() + ?.onSuccess { success(it) } + ?.execute(this@TaskOrNullTask) + ?: success(null) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/wrappers/SoftFailTask.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt similarity index 89% rename from src/main/kotlin/com/lambda/task/wrappers/SoftFailTask.kt rename to src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt index 6163a43b0..5a0cb6004 100644 --- a/src/main/kotlin/com/lambda/task/wrappers/SoftFailTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt @@ -15,15 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.task.wrappers +package com.lambda.task.tasks.wrappers import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder -typealias TaskSupplier = SafeContext.(R) -> Task -typealias TaskOrNullSupplier = SafeContext.(R) -> Task? - @Ta5kBuilder fun Task.softFail(): Task = SoftFailTask(this) @@ -35,7 +32,7 @@ fun Task.softFail(): Task = * * @see softFail */ -class SoftFailTask( +class SoftFailTask @Ta5kBuilder internal constructor( private val innerTask: Task, ) : Task() { override val name get() = "Soft fail protection for ${innerTask.name}" diff --git a/src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt b/src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt deleted file mode 100644 index 990e526f1..000000000 --- a/src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2026 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.task.wrappers - -import com.lambda.context.SafeContext -import com.lambda.task.Task -import com.lambda.task.Task.Ta5kBuilder -import com.lambda.threading.runSafe - -@Ta5kBuilder -infix fun Task.onFail(handler: SafeContext.(Throwable) -> Task) = - RecoveryTask(this, handler) - -@Ta5kBuilder -infix fun Task.onFailOrNull(handler: SafeContext.(Throwable) -> Task?) = - OptionalRecoveryTask(this, handler) - -/** - * A task that performs a given task ([innerTask]) with a fallback to the task supplied by [recoveryTaskSupplier] in-case of failure. - * - * Useful for if you want to keep the task branch going even in the event an individual task fails. - * - * @see onFail - */ -class RecoveryTask( - private val innerTask: Task, - private val recoveryTaskSupplier: SafeContext.(Throwable) -> Task, -) : Task() { - override val name get() = "Recovery task in case of failure for ${innerTask.name}" - - override fun SafeContext.onStart() { - innerTask - .onSuccess { success(null) } - .execute(this@RecoveryTask) - } - - override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { - if (subTask == innerTask) { - runSafe { - recoveryTaskSupplier(this, cause) - .onSuccess { success(it) } - .execute(this@RecoveryTask) - } - } else super.onSubTaskFailure(subTask, cause) - } -} - -/** - * A task that performs a given task ([innerTask]) with a fallback to the nullable task supplied by [recoveryTaskSupplier] in-case of failure. - * - * Useful for if you want to keep the task branch going even in the event an individual task fails, but want to supply the recovery task based on a predicate. - * - * @see onFailOrNull - */ -class OptionalRecoveryTask( - private val innerTask: Task, - private val recoveryTaskSupplier: SafeContext.(Throwable) -> Task?, -) : Task() { - override val name get() = "Optional recovery task in case of failure for ${innerTask.name}" - - override fun SafeContext.onStart() { - innerTask - .onSuccess { success(null) } - .execute(this@OptionalRecoveryTask) - } - - override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { - if (subTask == innerTask) { - runSafe { - recoveryTaskSupplier(this, cause) - ?.onSuccess { success(it) } - ?.execute(this@OptionalRecoveryTask) - ?: super.onSubTaskFailure(subTask, cause) - } - } else super.onSubTaskFailure(subTask, cause) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt index 5da12fb6b..6ae3b7a78 100644 --- a/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -273,9 +273,8 @@ object BlockUtils { fun SafeContext.blockEntity(pos: BlockPos) = world.getBlockEntity(pos) fun BlockState.matches(state: BlockState, ignoredProperties: Collection> = emptySet()) = - this.block == state.block && this.properties.all { - this[it] == state[it] || it in ignoredProperties - } + this.block == state.block && + this.properties.all { this[it] == state[it] || it in ignoredProperties } fun SafeContext.instantBreakable(blockState: BlockState, blockPos: BlockPos, breakThreshold: Float): Boolean { val ticksNeeded = 1 / (blockState.calcBlockBreakingDelta(player, world, blockPos) / breakThreshold) diff --git a/src/main/kotlin/com/lambda/util/FolderRegistry.kt b/src/main/kotlin/com/lambda/util/FolderRegistry.kt index d02d89b6f..7c284a79f 100644 --- a/src/main/kotlin/com/lambda/util/FolderRegistry.kt +++ b/src/main/kotlin/com/lambda/util/FolderRegistry.kt @@ -35,6 +35,7 @@ object FolderRegistry : Loadable { val replay: Path = lambda.resolve("replay") val cache: Path = lambda.resolve("cache") val capes: Path = cache.resolve("capes") + val containers: Path = cache.resolve("containers") val structure: Path = lambda.resolve("structure") val maps: Path = lambda.resolve("maps") val fonts: Path = lambda.resolve("fonts") @@ -42,7 +43,7 @@ object FolderRegistry : Loadable { val File.relativeMCPath: Path get() = minecraft.relativize(toPath()) override fun load(): String { - val folders = listOf(lambda, config, packetLogs, replay, cache, capes, structure, maps, fonts) + val folders = listOf(lambda, config, packetLogs, replay, cache, capes, containers, structure, maps, fonts) val createdFolders = folders.mapNotNull { if (it.notExists()) { it.createDirectories() diff --git a/src/main/kotlin/com/lambda/util/PacketUtils.kt b/src/main/kotlin/com/lambda/util/PacketUtils.kt index 22d95044a..2d8652302 100644 --- a/src/main/kotlin/com/lambda/util/PacketUtils.kt +++ b/src/main/kotlin/com/lambda/util/PacketUtils.kt @@ -27,7 +27,7 @@ object PacketUtils { /** * Sends a packet through the regular packet pipeline */ - fun ClientPlayNetworkHandler.sendPacket(block: () -> Packet<*>) = connection.send(block()) + fun ClientPlayNetworkHandler.sendPacket(packetSupplier: () -> Packet<*>) = connection.send(packetSupplier()) /** * Sends a packet to the server without notifying the client. diff --git a/src/main/kotlin/com/lambda/util/PlaceDirection.kt b/src/main/kotlin/com/lambda/util/PlaceDirection.kt index b6e3b0484..bf0483a63 100644 --- a/src/main/kotlin/com/lambda/util/PlaceDirection.kt +++ b/src/main/kotlin/com/lambda/util/PlaceDirection.kt @@ -17,7 +17,7 @@ package com.lambda.util -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.util.math.MathHelper import net.minecraft.util.math.Vec3i diff --git a/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt b/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt index 86d0375b3..96b029a8e 100644 --- a/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt +++ b/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt @@ -18,7 +18,7 @@ package com.lambda.util import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isNotEmpty import com.lambda.util.math.MathUtils.ceilToInt diff --git a/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt b/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt index d6d890a09..f1b3abba8 100644 --- a/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt +++ b/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt @@ -20,7 +20,7 @@ package com.lambda.util.combat import com.lambda.context.SafeContext import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.fullHealth -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.minus import com.lambda.util.player.prediction.buildPlayerPrediction import net.minecraft.block.BedBlock @@ -76,7 +76,7 @@ object DamageUtils { val predictedPos = prediction.position val fallDistance = player.y - predictedPos.y + player.fallDistance - val state = blockState(predictedPos.flooredBlockPos) + val state = blockState(predictedPos.blockPos) val block = state.block val distance = fallDistance + diff --git a/src/main/kotlin/com/lambda/util/extension/Entity.kt b/src/main/kotlin/com/lambda/util/extension/Entity.kt index 50f761620..94ee5c188 100644 --- a/src/main/kotlin/com/lambda/util/extension/Entity.kt +++ b/src/main/kotlin/com/lambda/util/extension/Entity.kt @@ -17,7 +17,7 @@ package com.lambda.util.extension -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.entity.Entity import net.minecraft.entity.LivingEntity import net.minecraft.util.math.Vec3d diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index 9e39a0cbd..3a06ff7a4 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -76,8 +76,8 @@ object ItemStackUtils { val ItemStack.hasSpace get() = spaceLeft > 0 val List.spaceLeft get() = sumOf { it.spaceLeft } val List.spaceLeft @JvmName("slotSpaceLeft") get() = sumOf { it.stack.spaceLeft } - val List.empty: Int get() = count { it.isEmpty } - val List.empty: Int @JvmName("slotEmpty") get() = count { it.stack.isEmpty } + val List.emptySpace: Int get() = count { it.isEmpty } + val List.emptySpace: Int @JvmName("emptySlotSpace") get() = count { it.stack.isEmpty } val List.count: Int get() = if (isEmpty()) -1 else sumOf { it.count } val List.count: Int @JvmName("slotCount") get() = if (isEmpty()) -1 else sumOf { it.stack.count } val List.copy: List get() = map { it.copy() } @@ -111,11 +111,11 @@ object ItemStackUtils { return listOf(copyWithCount(maxCount), copyWithCount(remainder)) } - val ItemStack.shulkerBoxContents: List by cacheable { stack -> + val ItemStack.shulkerBoxStacks: List by cacheable { stack -> stack.components.get(DataComponentTypes.CONTAINER)?.stream()?.toList() ?: emptyList() } - val ItemStack.bundleContents: List by cacheable { stack -> + val ItemStack.bundleStacks: List by cacheable { stack -> stack.components.get(DataComponentTypes.BUNDLE_CONTENTS)?.stream()?.toList() ?: emptyList() } diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index 836aa1dba..361a26c8e 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -17,8 +17,7 @@ package com.lambda.util.item -import com.lambda.context.SafeContext -import com.lambda.util.player.SlotUtils.armorSlots +import com.lambda.interaction.container.containers.ArmorContainer import net.minecraft.block.Block import net.minecraft.component.DataComponentTypes import net.minecraft.item.BlockItem @@ -27,130 +26,144 @@ import net.minecraft.item.ItemStack import net.minecraft.item.Items object ItemUtils { - val pickaxes = setOf( - Items.WOODEN_PICKAXE, - Items.STONE_PICKAXE, - Items.IRON_PICKAXE, - Items.GOLDEN_PICKAXE, - Items.DIAMOND_PICKAXE, - Items.NETHERITE_PICKAXE, - ) - - val shovels = setOf( - Items.WOODEN_SHOVEL, - Items.STONE_SHOVEL, - Items.IRON_SHOVEL, - Items.GOLDEN_SHOVEL, - Items.DIAMOND_SHOVEL, - Items.NETHERITE_SHOVEL, - ) - - val axes = setOf( - Items.WOODEN_AXE, - Items.STONE_AXE, - Items.IRON_AXE, - Items.GOLDEN_AXE, - Items.DIAMOND_AXE, - Items.NETHERITE_AXE, - ) - - val hoes = setOf( - Items.WOODEN_HOE, - Items.STONE_HOE, - Items.IRON_HOE, - Items.GOLDEN_HOE, - Items.DIAMOND_HOE, - Items.NETHERITE_HOE, - ) - - val swords = setOf( - Items.WOODEN_SWORD, - Items.STONE_SWORD, - Items.IRON_SWORD, - Items.GOLDEN_SWORD, - Items.DIAMOND_SWORD, - Items.NETHERITE_SWORD, - ) - - val misc = setOf( - Items.SHEARS, - Items.FLINT_AND_STEEL, - ) - - val tools = pickaxes + shovels + axes + hoes + swords + misc - - val shulkerBoxes = setOf( - Items.SHULKER_BOX, - Items.WHITE_SHULKER_BOX, - Items.ORANGE_SHULKER_BOX, - Items.MAGENTA_SHULKER_BOX, - Items.LIGHT_BLUE_SHULKER_BOX, - Items.YELLOW_SHULKER_BOX, - Items.LIME_SHULKER_BOX, - Items.PINK_SHULKER_BOX, - Items.GRAY_SHULKER_BOX, - Items.LIGHT_GRAY_SHULKER_BOX, - Items.CYAN_SHULKER_BOX, - Items.PURPLE_SHULKER_BOX, - Items.BLUE_SHULKER_BOX, - Items.BROWN_SHULKER_BOX, - Items.GREEN_SHULKER_BOX, - Items.RED_SHULKER_BOX, - Items.BLACK_SHULKER_BOX, - ) - - val bundles = setOf( - Items.BUNDLE, - Items.WHITE_BUNDLE, - Items.ORANGE_BUNDLE, - Items.MAGENTA_BUNDLE, - Items.LIGHT_BLUE_BUNDLE, - Items.YELLOW_BUNDLE, - Items.LIME_BUNDLE, - Items.PINK_BUNDLE, - Items.GRAY_BUNDLE, - Items.LIGHT_GRAY_BUNDLE, - Items.CYAN_BUNDLE, - Items.PURPLE_BUNDLE, - Items.BLUE_BUNDLE, - Items.BROWN_BUNDLE, - Items.GREEN_BUNDLE, - Items.RED_BUNDLE, - Items.BLACK_BUNDLE - ) - - val chests = setOf( - Items.CHEST, - Items.TRAPPED_CHEST, - Items.ENDER_CHEST, - Items.BARREL, - ) - - val defaultDisposables = setOf( - Items.DIRT, - Items.GRASS_BLOCK, - Items.COBBLESTONE, - Items.GRANITE, - Items.DIORITE, - Items.ANDESITE, - Items.SANDSTONE, - Items.RED_SANDSTONE, - Items.NETHERRACK, - Items.END_STONE, - Items.STONE, - Items.BASALT, - Items.BLACKSTONE, - Items.COBBLED_DEEPSLATE - ) + val PICKAXES = + setOf( + Items.WOODEN_PICKAXE, + Items.STONE_PICKAXE, + Items.COPPER_PICKAXE, + Items.IRON_PICKAXE, + Items.GOLDEN_PICKAXE, + Items.DIAMOND_PICKAXE, + Items.NETHERITE_PICKAXE, + ) + + val SHOVELS = + setOf( + Items.WOODEN_SHOVEL, + Items.STONE_SHOVEL, + Items.COPPER_SHOVEL, + Items.IRON_SHOVEL, + Items.GOLDEN_SHOVEL, + Items.DIAMOND_SHOVEL, + Items.NETHERITE_SHOVEL, + ) + + val AXES = + setOf( + Items.WOODEN_AXE, + Items.STONE_AXE, + Items.COPPER_AXE, + Items.IRON_AXE, + Items.GOLDEN_AXE, + Items.DIAMOND_AXE, + Items.NETHERITE_AXE, + ) + + val HOES = + setOf( + Items.WOODEN_HOE, + Items.STONE_HOE, + Items.COPPER_HOE, + Items.IRON_HOE, + Items.GOLDEN_HOE, + Items.DIAMOND_HOE, + Items.NETHERITE_HOE, + ) + + val SWORDS = + setOf( + Items.WOODEN_SWORD, + Items.STONE_SWORD, + Items.COPPER_SWORD, + Items.IRON_SWORD, + Items.GOLDEN_SWORD, + Items.DIAMOND_SWORD, + Items.NETHERITE_SWORD, + ) + + val MISC = + setOf( + Items.SHEARS, + Items.FLINT_AND_STEEL, + ) + + val TOOLS = PICKAXES + SHOVELS + AXES + HOES + SWORDS + MISC + + val SHULKER_BOXES = + setOf( + Items.SHULKER_BOX, + Items.WHITE_SHULKER_BOX, + Items.ORANGE_SHULKER_BOX, + Items.MAGENTA_SHULKER_BOX, + Items.LIGHT_BLUE_SHULKER_BOX, + Items.YELLOW_SHULKER_BOX, + Items.LIME_SHULKER_BOX, + Items.PINK_SHULKER_BOX, + Items.GRAY_SHULKER_BOX, + Items.LIGHT_GRAY_SHULKER_BOX, + Items.CYAN_SHULKER_BOX, + Items.PURPLE_SHULKER_BOX, + Items.BLUE_SHULKER_BOX, + Items.BROWN_SHULKER_BOX, + Items.GREEN_SHULKER_BOX, + Items.RED_SHULKER_BOX, + Items.BLACK_SHULKER_BOX, + ) + + val BUNDLES = + setOf( + Items.BUNDLE, + Items.WHITE_BUNDLE, + Items.ORANGE_BUNDLE, + Items.MAGENTA_BUNDLE, + Items.LIGHT_BLUE_BUNDLE, + Items.YELLOW_BUNDLE, + Items.LIME_BUNDLE, + Items.PINK_BUNDLE, + Items.GRAY_BUNDLE, + Items.LIGHT_GRAY_BUNDLE, + Items.CYAN_BUNDLE, + Items.PURPLE_BUNDLE, + Items.BLUE_BUNDLE, + Items.BROWN_BUNDLE, + Items.GREEN_BUNDLE, + Items.RED_BUNDLE, + Items.BLACK_BUNDLE + ) + + val CHESTS = + setOf( + Items.CHEST, + Items.COPPER_CHEST, + Items.TRAPPED_CHEST, + Items.ENDER_CHEST + ) + + val DEFAULT_DISPOSABLES = + setOf( + Items.DIRT, + Items.GRASS_BLOCK, + Items.COBBLESTONE, + Items.GRANITE, + Items.DIORITE, + Items.ANDESITE, + Items.SANDSTONE, + Items.RED_SANDSTONE, + Items.NETHERRACK, + Items.END_STONE, + Items.STONE, + Items.BASALT, + Items.BLACKSTONE, + Items.COBBLED_DEEPSLATE + ) val Item.block: Block get() = Block.getBlockFromItem(this) val ItemStack.blockItem get() = item as? BlockItem val Item.nutrition: Int get() = components.get(DataComponentTypes.FOOD)?.nutrition ?: 0 - context(safeContext: SafeContext) val ItemStack.armorSlot get() = - safeContext.player.armorSlots.firstOrNull { it.canInsert(this) } + ArmorContainer.slots.firstOrNull { it.canInsert(this) } fun Int.toItemCount(): String { if (this < 0) { diff --git a/src/main/kotlin/com/lambda/util/math/Linear.kt b/src/main/kotlin/com/lambda/util/math/Linear.kt index fe0360966..de0ab8c2e 100644 --- a/src/main/kotlin/com/lambda/util/math/Linear.kt +++ b/src/main/kotlin/com/lambda/util/math/Linear.kt @@ -17,7 +17,7 @@ package com.lambda.util.math -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/util/math/Vectors.kt b/src/main/kotlin/com/lambda/util/math/Vectors.kt index b43767cae..baaa02434 100644 --- a/src/main/kotlin/com/lambda/util/math/Vectors.kt +++ b/src/main/kotlin/com/lambda/util/math/Vectors.kt @@ -107,7 +107,7 @@ fun Vec3d.approximate(other: Vec3d, precision: Double = 2.0E-4): Boolean = val Vec3d.roundedBlockPos: BlockPos get() = BlockPos(x.roundToInt(), y.roundToInt(), z.roundToInt()) -val Vec3d.flooredBlockPos: BlockPos +val Vec3d.blockPos: BlockPos get() = BlockPos(x.floorToInt(), y.floorToInt(), z.floorToInt()) val Entity.netherCoord: Vec3d get() = pos.multiply(0.125, 1.0, 0.125) @@ -205,7 +205,7 @@ infix fun Entity.distSq(other: Vec3i): Int = blockPos distSq other infix fun Entity.distSq(other: Entity): Double = squaredDistanceTo(other) context(safeContext: SafeContext) -val Vec3d.isLoaded get() = flooredBlockPos.isLoaded +val Vec3d.isLoaded get() = blockPos.isLoaded val UP = Vec3d(0.0, 1.0, 0.0) val DOWN = Vec3d(0.0, -1.0, 0.0) diff --git a/src/main/kotlin/com/lambda/util/player/MovementUtils.kt b/src/main/kotlin/com/lambda/util/player/MovementUtils.kt index 115a6cabc..b00b6cada 100644 --- a/src/main/kotlin/com/lambda/util/player/MovementUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/MovementUtils.kt @@ -18,7 +18,7 @@ package com.lambda.util.player import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.util.math.MathUtils.toDegree import com.lambda.util.math.MathUtils.toDouble import com.lambda.util.math.MathUtils.toRadian diff --git a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt index f381daac1..574394b7a 100644 --- a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt @@ -19,7 +19,7 @@ package com.lambda.util.player import com.lambda.config.blocks.BuildConfig import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.GlideHandler +import com.lambda.interaction.handler.handlers.GlideHandler import com.lambda.util.extension.getBlockState import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.world.fastEntitySearch diff --git a/src/main/kotlin/com/lambda/util/player/RotationUtils.kt b/src/main/kotlin/com/lambda/util/player/RotationUtils.kt index 840cf5670..cb0af20ec 100644 --- a/src/main/kotlin/com/lambda/util/player/RotationUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/RotationUtils.kt @@ -23,9 +23,9 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingData import com.lambda.interaction.construction.verify.ScanMode import com.lambda.interaction.construction.verify.SurfaceScan -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.modules.client.Client import com.lambda.util.BlockUtils.blockState import com.lambda.util.PlaceDirection diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index d09c0ae32..740520939 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -22,6 +22,8 @@ import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.player.PlayerInventory import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.ArmorSlot import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType @@ -29,29 +31,47 @@ import net.minecraft.screen.slot.SlotActionType object SlotUtils { val ClientPlayerEntity.allSlots get() = currentScreenHandler.slots.filter { it.inventory is PlayerInventory } val ClientPlayerEntity.armorSlots get() = allSlots.filterIsInstance() - val ClientPlayerEntity.inventorySlots: List get() = - if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) emptyList() - else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 31) - else allSlots.subList(0, 27) - val ClientPlayerEntity.hotbarSlots: List get() = - if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots - else if (currentScreenHandler === playerScreenHandler) allSlots.subList(31, 40) - else allSlots.subList(27, 36) - val ClientPlayerEntity.hotbarAndInventorySlots: List get() = - if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots - else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 40) - else allSlots.subList(0, 36) + + val ClientPlayerEntity.inventorySlots: List + get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) emptyList() + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 31) + else allSlots.subList(0, 27) + + val ClientPlayerEntity.hotbarSlots: List + get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(31, 40) + else allSlots.subList(27, 36) + + val ClientPlayerEntity.hotbarAndInventorySlots: List + get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 40) + else allSlots.subList(0, 36) + val ClientPlayerEntity.mainHandSlots: List get() = listOf(hotbarSlots[inventory.selectedSlot]) val ClientPlayerEntity.offHandSlots: List get() = if (currentScreenHandler === playerScreenHandler) listOf(allSlots.last()) else emptyList() val ClientPlayerEntity.allStacks: List get() = hotbarAndInventoryStacks + equipmentStacks + val ClientPlayerEntity.armorStacks: List get() = inventory.equipment.map.filter { it.key.index in 1..4 }.map { it.value } val ClientPlayerEntity.equipmentStacks: List get() = inventory.equipment.map.map { it.value } val ClientPlayerEntity.hotbarStacks: List get() = inventory.mainStacks.slice(0..8) val ClientPlayerEntity.inventoryStacks: List get() = inventory.mainStacks.slice(9..35) val ClientPlayerEntity.hotbarAndInventoryStacks: List get() = inventory.mainStacks + val ScreenHandler.typeSafe: ScreenHandlerType<*>? + get() = try { this.type } catch (_: Throwable) { null } + fun SafeContext.clickSlot(slotId: Int, button: Int, actionType: SlotActionType) { val syncId = player.currentScreenHandler?.syncId ?: return interaction.clickSlot(syncId, slotId, button, actionType, player) } + + infix fun Slot.matches(slot: Slot) = + index == slot.index && + inventory::class == slot.inventory::class && + id == slot.id && + x == slot.x && + y == slot.y } diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt index 17a9a4d7c..25a93a848 100644 --- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt +++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt @@ -19,14 +19,14 @@ package com.lambda.util.player.prediction import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.module.modules.movement.SafeWalk.isNearLedge import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.math.DOWN import com.lambda.util.math.MathUtils.toIntSign import com.lambda.util.math.MathUtils.toRadian -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.plus import com.lambda.util.math.times import com.lambda.util.player.MovementUtils.forward @@ -85,7 +85,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { // Other shit private var jumpingCooldown = player.jumpingCooldown - private var velocityAffectingPos = player.supportingBlockPos.orElse((position + DOWN * 0.001).flooredBlockPos) + private var velocityAffectingPos = player.supportingBlockPos.orElse((position + DOWN * 0.001).blockPos) private var horizontalCollision = player.horizontalCollision private var verticalCollision = player.verticalCollision @@ -208,7 +208,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { } val velocityMultiplier = run { - val f = blockState(position.flooredBlockPos).block.velocityMultiplier.toDouble() + val f = blockState(position.blockPos).block.velocityMultiplier.toDouble() val g = blockState(velocityAffectingPos).block.velocityMultiplier.toDouble() if (f == 1.0) g else f } @@ -220,7 +220,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { boundingBox = normalized.offset(position) } - velocityAffectingPos = (position + DOWN * 0.001).flooredBlockPos + velocityAffectingPos = (position + DOWN * 0.001).blockPos } /** @see net.minecraft.entity.LivingEntity.jump */ @@ -232,7 +232,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { /** @see net.minecraft.entity.Entity.getJumpVelocityMultiplier */ val jumpHeight = run { - val f = blockState(position.flooredBlockPos).block.jumpVelocityMultiplier.toDouble() + val f = blockState(position.blockPos).block.jumpVelocityMultiplier.toDouble() val g = blockState(velocityAffectingPos).block.jumpVelocityMultiplier.toDouble() if (f == 1.0) g else f } * 0.42 + player.jumpBoostVelocityModifier 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 074a6c69d..3d05d802c 100644 --- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt +++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt @@ -17,7 +17,7 @@ package com.lambda.util.player.prediction -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d diff --git a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index e9b3e4fe7..a13fa4684 100644 --- a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -171,13 +171,13 @@ object WorldUtils { range: FastVector, step: FastVector, ) = sequence { - for (x in -range.x..range.x step step.x) { - for (y in -range.y..range.y step step.y) { - for (z in -range.z..range.z step step.z) { - yield(pos plus fastVectorOf(x, y, z)) - } - } - } + (-range.x..range.x step step.x).forEach { x -> + (-range.y..range.y step step.y).forEach { y -> + (-range.z..range.z step step.z).forEach { z -> + yield(pos plus fastVectorOf(x, y, z)) + } + } + } } } diff --git a/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt b/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt index 343b0e8ca..91ac2263e 100644 --- a/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt @@ -19,7 +19,7 @@ package com.lambda.util.world.raycast import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.util.math.distSq import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.Entity diff --git a/src/test/kotlin/PlaceDirectionTest.kt b/src/test/kotlin/PlaceDirectionTest.kt index 02b5fb6e3..15c94436e 100644 --- a/src/test/kotlin/PlaceDirectionTest.kt +++ b/src/test/kotlin/PlaceDirectionTest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.util.PlaceDirection import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/test/kotlin/RotationTest.kt b/src/test/kotlin/RotationTest.kt index f57a3c33e..7c182d8c1 100644 --- a/src/test/kotlin/RotationTest.kt +++ b/src/test/kotlin/RotationTest.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.angleDifference -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.Rotation.Companion.lerp -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerp -import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.angleDifference +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.lerp +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.slerp +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.wrap import kotlin.math.abs import kotlin.math.hypot import kotlin.test.Test