diff --git a/build.gradle.kts b/build.gradle.kts
index 576788c9c..f427633a0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,3 +1,20 @@
+/*
+ * Copyright 2024 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 .
+ */
+
import org.gradle.internal.jvm.*
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import org.apache.tools.ant.taskdefs.condition.Os
diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java
index 7f9dd2aa8..ebc46893d 100644
--- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java
+++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java
@@ -20,7 +20,7 @@
import com.lambda.Lambda;
import com.lambda.event.EventFlow;
import com.lambda.event.events.ClientEvent;
-import com.lambda.event.events.ScreenHandlerEvent;
+import com.lambda.event.events.InventoryEvent;
import com.lambda.event.events.TickEvent;
import com.lambda.module.modules.player.Interact;
import net.minecraft.client.MinecraftClient;
@@ -78,7 +78,7 @@ private void onStartup(CallbackInfo ci) {
private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) {
if (screen == null) return;
if (screen instanceof ScreenHandlerProvider> handledScreen) {
- EventFlow.post(new ScreenHandlerEvent.Open(handledScreen.getScreenHandler()));
+ EventFlow.post(new InventoryEvent.Open(handledScreen.getScreenHandler()));
}
}
@@ -86,7 +86,7 @@ private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) {
private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) {
if (currentScreen == null) return;
if (currentScreen instanceof ScreenHandlerProvider> handledScreen) {
- EventFlow.post(new ScreenHandlerEvent.Close(handledScreen.getScreenHandler()));
+ EventFlow.post(new InventoryEvent.Close(handledScreen.getScreenHandler()));
}
}
diff --git a/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
index 3f863fd52..43896514f 100644
--- a/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
+++ b/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
@@ -66,9 +66,11 @@ public void interactItemHead(PlayerEntity player, Hand hand, CallbackInfoReturna
}
}
- @Inject(method = "attackBlock", at = @At("HEAD"))
+ @Inject(method = "attackBlock", at = @At("HEAD"), cancellable = true)
public void onAttackBlock(BlockPos pos, Direction side, CallbackInfoReturnable cir) {
- if (EventFlow.post(new PlayerEvent.Attack.Block(pos, side)).isCanceled()) cir.cancel();
+ if (EventFlow.post(new PlayerEvent.Attack.Block(pos, side)).isCanceled()) {
+ cir.setReturnValue(false);
+ }
}
@Inject(method = "attackEntity", at = @At("HEAD"), cancellable = true)
diff --git a/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java
new file mode 100644
index 000000000..e756a9f96
--- /dev/null
+++ b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 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.mixin.network;
+
+import com.lambda.event.EventFlow;
+import com.lambda.event.events.InventoryEvent;
+import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
+import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(ClientPlayNetworkHandler.class)
+public class ClientPlayNetworkHandlerMixin {
+ @Inject(method = "onUpdateSelectedSlot", at = @At(value = "TAIL"))
+ private void onUpdateSelectedSlot(UpdateSelectedSlotS2CPacket packet, CallbackInfo ci) {
+ EventFlow.post(new InventoryEvent.SelectedHotbarSlotUpdate(packet.getSlot()));
+ }
+
+ @Inject(method = "onScreenHandlerSlotUpdate", at = @At(value = "TAIL"))
+ private void onScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket packet, CallbackInfo ci) {
+ EventFlow.post(new InventoryEvent.SlotUpdate(packet.getSyncId(), packet.getRevision(), packet.getSlot(), packet.getStack()));
+ }
+}
diff --git a/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java b/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java
index 15d19aac3..c6027a33d 100644
--- a/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java
+++ b/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java
@@ -17,7 +17,7 @@
package com.lambda.mixin.render;
-import com.lambda.task.RootTask;
+import com.lambda.task.TaskFlow;
import com.lambda.util.DebugInfoHud;
import net.minecraft.client.gui.hud.DebugHud;
import org.spongepowered.asm.mixin.Mixin;
@@ -36,6 +36,6 @@ private void onGetRightText(CallbackInfoReturnable> cir) {
@Inject(method = "getLeftText", at = @At(value = "TAIL"))
private void onGetLeftText(CallbackInfoReturnable> cir) {
- RootTask.INSTANCE.addInfo(cir.getReturnValue());
+ cir.getReturnValue().addAll(List.of(TaskFlow.INSTANCE.toString().split("\n")));
}
}
diff --git a/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java b/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java
index 803825bf5..42ce6dd53 100644
--- a/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java
+++ b/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java
@@ -18,7 +18,7 @@
package com.lambda.mixin.render;
import com.lambda.event.EventFlow;
-import com.lambda.event.events.ScreenHandlerEvent;
+import com.lambda.event.events.InventoryEvent;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.ScreenHandler;
import org.spongepowered.asm.mixin.Mixin;
@@ -32,6 +32,6 @@
public class ScreenHandlerMixin {
@Inject(method = "updateSlotStacks", at = @At("TAIL"))
private void onUpdateSlotStacksHead(int revision, List stacks, ItemStack cursorStack, CallbackInfo ci) {
- EventFlow.post(new ScreenHandlerEvent.Update(revision, stacks, cursorStack));
+ EventFlow.post(new InventoryEvent.FullUpdate(revision, stacks, cursorStack));
}
}
diff --git a/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java b/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java
index 40ecde9d4..6ca55a063 100644
--- a/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java
+++ b/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java
@@ -34,13 +34,6 @@
@Mixin(ClientWorld.class)
public class ClientWorldMixin {
- @Inject(method = "handleBlockUpdate", at = @At("HEAD"), cancellable = true)
- private void handleBlockUpdateInject(BlockPos pos, BlockState state, int flags, CallbackInfo ci) {
- if (EventFlow.post(new WorldEvent.BlockUpdate(pos, state, flags)).isCanceled()) {
- ci.cancel();
- }
- }
-
@Inject(method = "addEntity", at = @At("HEAD"), cancellable = true)
private void addEntity(Entity entity, CallbackInfo ci) {
if (EventFlow.post(new WorldEvent.EntitySpawn(entity)).isCanceled()) ci.cancel();
diff --git a/common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java b/common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java
new file mode 100644
index 000000000..63960b453
--- /dev/null
+++ b/common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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.mixin.world;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import net.minecraft.block.Block;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.registry.RegistryEntryLookup;
+import net.minecraft.structure.StructureTemplate;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.util.Objects;
+
+@Mixin(StructureTemplate.class)
+public class StructureTemplateMixin {
+ @Shadow
+ private String author;
+
+ @ModifyReturnValue(method = "getAuthor()Ljava/lang/String;", at = @At("RETURN"))
+ public String getAuthor(String original) {
+ return Objects.equals(original, "?") || Objects.equals(original, "") ? "Unknown" : original;
+ }
+
+ @Inject(method = "writeNbt(Lnet/minecraft/nbt/NbtCompound;)Lnet/minecraft/nbt/NbtCompound;", at = @At("TAIL"))
+ public void writeNbt(NbtCompound nbt, CallbackInfoReturnable cir) {
+ nbt.putString("author", author);
+ }
+
+ @Inject(method = "readNbt(Lnet/minecraft/registry/RegistryEntryLookup;Lnet/minecraft/nbt/NbtCompound;)V", at = @At("TAIL"))
+ public void readNbt(RegistryEntryLookup blockLookup, NbtCompound nbt, CallbackInfo ci) {
+ author = nbt.getString("author");
+ }
+}
diff --git a/common/src/main/java/com/lambda/mixin/world/WorldMixin.java b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java
new file mode 100644
index 000000000..bc809a5e8
--- /dev/null
+++ b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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.mixin.world;
+
+import com.lambda.event.EventFlow;
+import com.lambda.event.events.WorldEvent;
+import net.minecraft.block.BlockState;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(World.class)
+public abstract class WorldMixin {
+ @Inject(method = "onBlockChanged", at = @At("TAIL"))
+ void onBlockChanged(BlockPos pos, BlockState oldBlock, BlockState newBlock, CallbackInfo ci) {
+ EventFlow.post(new WorldEvent.BlockChange(pos, oldBlock, newBlock));
+ }
+}
diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt
index 621b2f32d..cb2d81763 100644
--- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt
+++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt
@@ -17,18 +17,24 @@
package com.lambda.command.commands
-import com.lambda.brigadier.argument.literal
-import com.lambda.brigadier.execute
+import com.lambda.brigadier.CommandResult
+import com.lambda.brigadier.argument.*
+import com.lambda.brigadier.executeWithResult
+import com.lambda.brigadier.optional
import com.lambda.brigadier.required
import com.lambda.command.LambdaCommand
-import com.lambda.interaction.construction.Blueprint.Companion.toStructure
-import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint
-import com.lambda.interaction.construction.verify.TargetState
+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.TaskFlow.run
import com.lambda.task.tasks.BuildTask.Companion.build
import com.lambda.threading.runSafe
+import com.lambda.util.Communication.info
import com.lambda.util.extension.CommandBuilder
-import net.minecraft.block.Blocks
-import net.minecraft.util.math.BlockBox
+import com.lambda.util.extension.move
+import java.nio.file.InvalidPathException
+import java.nio.file.NoSuchFileException
+import java.nio.file.Path
object BuildCommand : LambdaCommand(
name = "Build",
@@ -37,26 +43,37 @@ object BuildCommand : LambdaCommand(
) {
override fun CommandBuilder.create() {
required(literal("place")) {
- execute {
- runSafe {
- val materials = setOf(
- TargetState.Block(Blocks.NETHERRACK),
- TargetState.Block(Blocks.AIR),
- TargetState.Block(Blocks.COBBLESTONE),
- TargetState.Block(Blocks.AIR),
- )
- val facing = player.horizontalFacing
- val pos = player.blockPos.add(facing.vector.multiply(2))
+ required(greedyString("structure")) { structure ->
+ suggests { _, builder ->
+ StructureRegistry.forEach { key, _ -> builder.suggest(key) }
+ builder.buildFuture()
+ }
+ executeWithResult {
+ val pathString = structure().value()
+ runSafe {
+ try {
+ StructureRegistry
+ .loadStructureByRelativePath(Path.of(pathString))
+ ?.let { template ->
+ info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}")
+ template.toStructure()
+ .move(player.blockPos)
+ .toBlueprint()
+ .build()
+ .run()
- BlockBox.create(pos, pos.add(facing.rotateYClockwise().vector.multiply(3)))
- .toStructure(TargetState.Block(Blocks.NETHERRACK))
- .toBlueprint {
- it.mapValues { (_, _) ->
- materials.elementAt((System.currentTimeMillis() / 5000).toInt() % materials.size)
- }
+ return@executeWithResult CommandResult.success()
+ }
+ } catch (e: InvalidPathException) {
+ return@executeWithResult CommandResult.failure("Invalid path $pathString")
+ } catch (e: NoSuchFileException) {
+ return@executeWithResult CommandResult.failure("Structure $pathString not found")
+ } catch (e: Exception) {
+ return@executeWithResult CommandResult.failure(e.message ?: "Failed to load structure $pathString")
}
- .build(finishOnDone = false)
- .start(null)
+ }
+
+ CommandResult.failure("Structure $pathString not found")
}
}
}
diff --git a/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt
index f5610f997..4d079f4cd 100644
--- a/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt
+++ b/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt
@@ -30,6 +30,7 @@ import com.lambda.module.modules.player.Replay
import com.lambda.util.FolderRegister
import com.lambda.util.FolderRegister.listRecursive
import com.lambda.util.extension.CommandBuilder
+import kotlin.io.path.exists
object ReplayCommand : LambdaCommand(
name = "replay",
@@ -48,7 +49,7 @@ object ReplayCommand : LambdaCommand(
required(literal("load")) {
required(greedyString("replay filepath")) { replayName ->
suggests { _, builder ->
- val dir = FolderRegister.replay
+ val dir = FolderRegister.replay.toFile()
dir.listRecursive { it.isFile }.forEach {
builder.suggest(it.relativeTo(dir).path)
}
@@ -63,7 +64,7 @@ object ReplayCommand : LambdaCommand(
}
try {
- Replay.loadRecording(replayFile)
+ Replay.loadRecording(replayFile.toFile())
} catch (e: JsonSyntaxException) {
return@executeWithResult CommandResult.failure("Failed to load replay file: ${e.message}")
}
diff --git a/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt
index 41dd5fb7e..97c33383a 100644
--- a/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt
+++ b/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt
@@ -21,20 +21,28 @@ import com.lambda.brigadier.argument.literal
import com.lambda.brigadier.execute
import com.lambda.brigadier.required
import com.lambda.command.LambdaCommand
-import com.lambda.task.RootTask
+import com.lambda.task.TaskFlow
import com.lambda.util.Communication.info
import com.lambda.util.extension.CommandBuilder
object TaskCommand : LambdaCommand(
name = "task",
- usage = "task ",
+ usage = "task ",
description = "Control tasks"
) {
override fun CommandBuilder.create() {
required(literal("cancel")) {
execute {
this@TaskCommand.info("Cancelling all tasks")
- RootTask.cancel()
+ TaskFlow.cancel()
+ }
+ }
+
+ required(literal("clear")) {
+ execute {
+ this@TaskCommand.info("Clearing all tasks")
+ TaskFlow.cancel()
+ TaskFlow.clear()
}
}
}
diff --git a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
index 4ab9ab51f..35ca83ec6 100644
--- a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
+++ b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
@@ -23,10 +23,12 @@ import com.lambda.brigadier.argument.*
import com.lambda.brigadier.executeWithResult
import com.lambda.brigadier.required
import com.lambda.command.LambdaCommand
-import com.lambda.interaction.material.ContainerManager
-import com.lambda.interaction.material.ContainerManager.containerMatchSelection
+import com.lambda.interaction.material.container.ContainerManager
+import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial
+import com.lambda.interaction.material.container.ContainerManager.containerWithSpace
import com.lambda.interaction.material.StackSelection.Companion.selectStack
import com.lambda.interaction.material.transfer.TransferResult
+import com.lambda.task.TaskFlow.run
import com.lambda.util.Communication.info
import com.lambda.util.extension.CommandBuilder
@@ -35,21 +37,19 @@ object TransferCommand : LambdaCommand(
usage = "transfer - ",
description = "Transfer items from anywhere to anywhere",
) {
- private var lastTransfer: TransferResult.Transfer? = null
+ private var lastContainerTransfer: TransferResult.ContainerTransfer? = null
override fun CommandBuilder.create() {
required(itemStack("stack", registry)) { stack ->
- required(integer("amount")) { amount ->
+ required(integer("amount", 1)) { amount ->
required(string("from")) { from ->
suggests { ctx, builder ->
val count = amount(ctx).value()
val selection = selectStack(count) {
isItem(stack(ctx).value().item)
}
- containerMatchSelection(selection).forEach {
- val available = it.available(selection)
- val availableMsg = if (available == Int.MAX_VALUE) "∞" else available.toString()
- builder.suggest("\"${it.name} with $availableMsg\"")
+ containerWithMaterial(selection).forEachIndexed { i, container ->
+ builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection))
}
builder.buildFuture()
}
@@ -58,10 +58,8 @@ object TransferCommand : LambdaCommand(
val selection = selectStack(amount(ctx).value()) {
isItem(stack(ctx).value().item)
}
- ContainerManager.container().forEach {
- val space = it.spaceLeft(selection)
- val spaceMsg = if (space == Int.MAX_VALUE) "∞" else space.toString()
- if (space > 0) builder.suggest("\"${it.name} with $spaceMsg space left\"")
+ containerWithSpace(selection).forEachIndexed { i, container ->
+ builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection))
}
builder.buildFuture()
}
@@ -70,25 +68,25 @@ object TransferCommand : LambdaCommand(
isItem(stack().value().item)
}
val fromContainer = ContainerManager.container().find {
- it.name == from().value().split(" with ").firstOrNull()
+ it.name == from().value().split(".").last().trim()
} ?: return@executeWithResult failure("From container not found")
val toContainer = ContainerManager.container().find {
- it.name == to().value().split(" with ").firstOrNull()
+ it.name == to().value().split(".").last().trim()
} ?: return@executeWithResult failure("To container not found")
- when (val result = fromContainer.transfer(selection, toContainer)) {
- is TransferResult.Transfer -> {
- info("$result started.")
- lastTransfer = result
- result.onSuccess { _, _ ->
- info("$lastTransfer completed.")
- }.start(null)
+ when (val transaction = fromContainer.transfer(selection, toContainer)) {
+ is TransferResult.ContainerTransfer -> {
+ info("${transaction.name} started.")
+ lastContainerTransfer = transaction
+ transaction.finally {
+ info("${transaction.name} completed.")
+ }.run()
return@executeWithResult success()
}
is TransferResult.MissingItems -> {
- return@executeWithResult failure("Missing items: ${result.missing}")
+ return@executeWithResult failure("Missing items: ${transaction.missing}")
}
is TransferResult.NoSpace -> {
@@ -105,11 +103,11 @@ object TransferCommand : LambdaCommand(
required(literal("cancel")) {
executeWithResult {
- lastTransfer?.cancel() ?: run {
+ lastContainerTransfer?.cancel() ?: run {
return@executeWithResult failure("No transfer to cancel")
}
- info("$lastTransfer cancelled")
- lastTransfer = null
+ info("$lastContainerTransfer cancelled")
+ lastContainerTransfer = null
success()
}
}
diff --git a/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt
index e224f828c..92d0b8bea 100644
--- a/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt
@@ -19,8 +19,9 @@ package com.lambda.config.configurations
import com.lambda.config.Configuration
import com.lambda.util.FolderRegister
+import java.io.File
object FriendConfig : Configuration() {
override val configName get() = "friends"
- override val primary = FolderRegister.config.resolve("$configName.json")
+ override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
}
diff --git a/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt
index 592c37bdf..1793bf5c9 100644
--- a/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt
@@ -19,8 +19,9 @@ package com.lambda.config.configurations
import com.lambda.config.Configuration
import com.lambda.util.FolderRegister
+import java.io.File
object GuiConfig : Configuration() {
override val configName get() = "gui"
- override val primary = FolderRegister.config.resolve("$configName.json")
+ override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
}
diff --git a/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt
index be717ec42..4f03e3449 100644
--- a/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt
@@ -19,8 +19,9 @@ package com.lambda.config.configurations
import com.lambda.config.Configuration
import com.lambda.util.FolderRegister
+import java.io.File
object LambdaConfig : Configuration() {
override val configName get() = "lambda"
- override val primary = FolderRegister.config.resolve("$configName.json")
+ override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
}
diff --git a/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt
index e8410194d..01e235f37 100644
--- a/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt
@@ -19,6 +19,7 @@ package com.lambda.config.configurations
import com.lambda.config.Configuration
import com.lambda.util.FolderRegister
+import java.io.File
/**
@@ -31,5 +32,5 @@ import com.lambda.util.FolderRegister
*/
object ModuleConfig : Configuration() {
override val configName get() = "modules"
- override val primary = FolderRegister.config.resolve("$configName.json")
+ override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
}
diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
index db143825a..5b3aa74b0 100644
--- a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
@@ -17,13 +17,27 @@
package com.lambda.config.groups
+import net.minecraft.block.Block
+
interface BuildConfig {
- val breakConfirmation: Boolean
- val placeConfirmation: Boolean
- val collectDrops: Boolean
- val breakWeakBlocks: Boolean
+ // General
val pathing: Boolean
- val breaksPerTick: Int
+ val stayInRange: Boolean
+ val collectDrops: Boolean
+
+ // Breaking
val rotateForBreak: Boolean
+ val breakConfirmation: Boolean
+ val maxPendingBreaks: Int
+ val breaksPerTick: Int
+ val breakWeakBlocks: Boolean
+ val forceSilkTouch: Boolean
+ val ignoredBlocks: Set
+
+ // Placing
val rotateForPlace: Boolean
+ val placeConfirmation: Boolean
+ val placeTimeout: Int
+ val maxPendingPlacements: Int
+ val placementsPerTick: Int
}
diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
index f9e70ae7b..89a6deabf 100644
--- a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
+++ b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
@@ -18,6 +18,8 @@
package com.lambda.config.groups
import com.lambda.config.Configurable
+import com.lambda.module.modules.client.TaskFlowModule.setting
+import com.lambda.util.BlockUtils.allSigns
class BuildSettings(
c: Configurable,
@@ -29,16 +31,24 @@ class BuildSettings(
private val page by c.setting("Build Page", Page.GENERAL, "Current page", vis)
+ // General
override val pathing by c.setting("Pathing", true, "Path to blocks") { vis() && page == Page.GENERAL }
+ override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks") { vis() && page == Page.GENERAL && pathing }
+ override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks") { vis() && page == Page.GENERAL }
+ // Breaking
+ override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking") { vis() && page == Page.BREAK }
override val breakConfirmation by c.setting("Break Confirmation", false, "Wait for block break confirmation") { vis() && page == Page.BREAK }
+ override val maxPendingBreaks by c.setting("Max Pending Breaks", 1, 1..10, 1, "Maximum pending block breaks") { vis() && page == Page.BREAK }
+ override val breaksPerTick by c.setting("Instant Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.BREAK }
override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)") { vis() && page == Page.BREAK }
- override val breaksPerTick by c.setting("Instant Breaks Per Tick", 10, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.BREAK }
- override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking") { vis() && page == Page.BREAK }
-
- override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks") { vis() && page == Page.BREAK }
-
- override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation") { vis() && page == Page.PLACE }
+ override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { vis() && page == Page.BREAK }
+ override val ignoredBlocks by setting("Ignored Blocks", allSigns, "Blocks that wont be broken") { vis() && page == Page.BREAK }
+ // Placing
override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing") { vis() && page == Page.PLACE }
+ override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation") { vis() && page == Page.PLACE }
+ override val placeTimeout by c.setting("Place Timeout", 10, 1..30, 1, "Timeout for block placement in ticks", unit = " ticks") { vis() && page == Page.PLACE && placeConfirmation }
+ override val maxPendingPlacements by c.setting("Max Pending Places", 1, 1..10, 1, "Maximum pending block places") { vis() && page == Page.PLACE }
+ override val placementsPerTick by c.setting("Instant Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick") { vis() && page == Page.PLACE }
}
diff --git a/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt
index 63d45b4be..4b1a946fb 100644
--- a/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt
@@ -25,6 +25,8 @@ interface InteractionConfig {
*/
val reach: Double
+ val visibilityCheck: Boolean
+
/**
* Will check `resolution squared` many points on a grid on each visible surface of the hit box.
*/
diff --git a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt
index 5d0691e83..e528607af 100644
--- a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt
+++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt
@@ -26,6 +26,7 @@ class InteractionSettings(
) : InteractionConfig {
override val reach by c.setting("Reach", defaultReach, 0.1..10.0, 0.1, "Players reach / range", " blocks", vis)
override val useRayCast by c.setting("Raycast", true, "Verify hit vector with ray casting (for very strict ACs)", vis)
+ override val visibilityCheck by c.setting("Visibility Check", true, "Check if target is visible", vis)
override val resolution by c.setting("Resolution", 4, 1..40, 1, "How many raycast checks per surface (will be squared)") { vis() && useRayCast }
override val swingHand by c.setting("Swing Hand", true, "Swing hand on interactions", vis)
override val pingTimeout by c.setting("Ping Timeout", false, "Timeout on high ping", vis)
diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt
new file mode 100644
index 000000000..4f068b8df
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 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.config.groups
+
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.interaction.material.StackSelection
+import net.minecraft.block.Block
+
+interface InventoryConfig {
+ val disposables: Set
+ val accessEnderChest: Boolean
+
+ val actionTimout: Int
+ val swapWithDisposables: Boolean
+
+ val providerPriority: Priority
+ val storePriority: Priority
+
+ enum class Priority {
+ WITH_MIN_ITEMS,
+ WITH_MAX_ITEMS;
+
+ fun materialComparator(selection: StackSelection) =
+ when (this) {
+ WITH_MAX_ITEMS -> compareBy { it.rank }
+ .thenByDescending { it.materialAvailable(selection) }
+ .thenBy { it.name }
+ WITH_MIN_ITEMS -> compareBy { it.rank }
+ .thenBy { it.materialAvailable(selection) }
+ .thenBy { it.name }
+ }
+
+ fun spaceComparator(selection: StackSelection) =
+ when (this) {
+ WITH_MAX_ITEMS -> compareBy { it.rank }
+ .thenByDescending { it.spaceAvailable(selection) }
+ .thenBy { it.name }
+ WITH_MIN_ITEMS -> compareBy { it.rank }
+ .thenBy { it.spaceAvailable(selection) }
+ .thenBy { it.name }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
new file mode 100644
index 000000000..74f962802
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 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.config.groups
+
+import com.lambda.config.Configurable
+import com.lambda.util.item.ItemUtils
+
+class InventorySettings(
+ c: Configurable,
+ vis: () -> Boolean = { true },
+) : InventoryConfig {
+ override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis)
+ override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest", vis)
+ override val actionTimout by c.setting("Action Timeout", 10, 0..100, 1, "How long to wait for after each inventory action", " ticks", vis)
+ override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis)
+ override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when retrieving the item from", vis)
+ override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when storing the item to", vis)
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/config/groups/IRotationConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt
similarity index 95%
rename from common/src/main/kotlin/com/lambda/config/groups/IRotationConfig.kt
rename to common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt
index fc83f9eed..a0e4d4b81 100644
--- a/common/src/main/kotlin/com/lambda/config/groups/IRotationConfig.kt
+++ b/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt
@@ -19,7 +19,7 @@ package com.lambda.config.groups
import com.lambda.interaction.rotation.RotationMode
-interface IRotationConfig {
+interface RotationConfig {
/**
* - [RotationMode.SILENT] Spoofing server-side rotation.
* - [RotationMode.SYNC] Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim).
@@ -42,7 +42,7 @@ interface IRotationConfig {
*/
val resetTicks: Int
- interface Instant : IRotationConfig {
+ interface Instant : RotationConfig {
override val turnSpeed get() = 360.0
override val keepTicks get() = 1
override val resetTicks get() = 1
diff --git a/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt
index ae112bfe3..8981c468c 100644
--- a/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt
+++ b/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt
@@ -25,7 +25,7 @@ import kotlin.random.Random
class RotationSettings(
c: Configurable,
vis: () -> Boolean = { true },
-) : IRotationConfig {
+) : RotationConfig {
/**
* The rotation mode
*/
diff --git a/common/src/main/kotlin/com/lambda/event/EventFlow.kt b/common/src/main/kotlin/com/lambda/event/EventFlow.kt
index 0b66b1478..9abf905a8 100644
--- a/common/src/main/kotlin/com/lambda/event/EventFlow.kt
+++ b/common/src/main/kotlin/com/lambda/event/EventFlow.kt
@@ -87,6 +87,11 @@ object EventFlow {
*/
val concurrentListeners = Subscriber()
+ fun Any.unsubscribe() {
+ syncListeners.unsubscribe(this)
+ concurrentListeners.unsubscribe(this)
+ }
+
init {
// parallel event execution on dedicated threads
runConcurrent {
diff --git a/common/src/main/kotlin/com/lambda/event/Subscriber.kt b/common/src/main/kotlin/com/lambda/event/Subscriber.kt
index 3a78dcf4a..6b9354a7c 100644
--- a/common/src/main/kotlin/com/lambda/event/Subscriber.kt
+++ b/common/src/main/kotlin/com/lambda/event/Subscriber.kt
@@ -54,6 +54,18 @@ class Subscriber : ConcurrentHashMap, ConcurrentSkipListSet unsubscribe(listener: Listener) =
getOrElse(T::class) { defaultListenerSet }.remove(listener)
+ /**
+ * Unsubscribes all listeners associated with the current instance (the caller object).
+ * This method iterates over all values in the `Subscriber`'s map and removes listeners
+ * whose `owner` property matches the caller object.
+ *
+ * Use this method when you want to clean up listeners that were registered with the
+ * current instance, preventing further event notifications.
+ */
+ fun unsubscribe(owner: Any) {
+ values.forEach { it.removeAll { listener -> listener.owner == owner } }
+ }
+
/** Allows a [Subscriber] to stop receiving all [Event]s of another [Subscriber] */
infix fun unsubscribe(subscriber: Subscriber) {
subscriber.forEach { (eventType, listeners) ->
diff --git a/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt
new file mode 100644
index 000000000..e9d3dc607
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 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.event.events
+
+import com.lambda.event.Event
+import net.minecraft.item.ItemStack
+import net.minecraft.screen.ScreenHandler
+
+/**
+ * Represents various events related to inventory interactions, updates, and state changes.
+ *
+ * This sealed class encompasses different types of inventory events that occur during gameplay,
+ * such as opening or closing an inventory, updating inventory contents, or changing hotbar slots.
+ * Each event provides specific contextual data relevant to the inventory interaction.
+ */
+sealed class InventoryEvent {
+ /**
+ * Represents an event triggered when an inventory or screen is opened.
+ *
+ * This event provides access to the associated [ScreenHandler], which manages the interaction
+ * logic between the inventory and the player. It is typically used to initialize or handle
+ * logic when the screen linked to a player's inventory is opened.
+ *
+ * @property screenHandler The screen handler associated with the opened inventory.
+ */
+ class Open(val screenHandler: ScreenHandler) : Event
+
+ /**
+ * Represents an event triggered when an inventory or screen is closed.
+ *
+ * This event provides access to the associated [ScreenHandler], which manages the interaction
+ * logic between the inventory and the player. It is typically used to finalize or clean up logic
+ * when a player's inventory screen is closed.
+ *
+ * @property screenHandler The screen handler associated with the closed inventory.
+ */
+ class Close(val screenHandler: ScreenHandler) : Event
+
+ /**
+ * Represents an update event for an inventory, typically triggered when inventory contents or states
+ * change during gameplay.
+ *
+ * @property revision The revision number indicating the current state of the inventory.
+ * @property stacks A list of item stacks representing the contents of the inventory slots.
+ * @property cursorStack The item stack currently held by the cursor.
+ */
+ data class FullUpdate(
+ val revision: Int,
+ val stacks: List,
+ val cursorStack: ItemStack,
+ ) : Event
+
+ /**
+ * Represents an event triggered when a specific inventory slot is updated.
+ *
+ * @property syncId The synchronization ID related to the container or inventory.
+ * @property revision The revision number representing the state of the inventory.
+ * @property slot The index of the updated inventory slot.
+ * @property stack The new item stack in the updated slot.
+ */
+ class SlotUpdate(
+ val syncId: Int,
+ val revision: Int,
+ val slot: Int,
+ val stack: ItemStack
+ ) : Event
+
+ /**
+ * Represents an event triggered when a player switches their selected hotbar slot.
+ *
+ * @property slot The index of the newly selected hotbar slot.
+ */
+ class SelectedHotbarSlotUpdate(val slot: Int) : Event
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt
index f7be6c13c..3679a973c 100644
--- a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt
+++ b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt
@@ -21,7 +21,6 @@ import com.lambda.event.Event
import com.lambda.event.callback.Cancellable
import com.lambda.event.callback.ICancellable
import net.minecraft.block.BlockState
-import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.entity.data.TrackedData
import net.minecraft.util.math.BlockPos
@@ -46,14 +45,11 @@ sealed class WorldEvent {
) : Event
}
- /**
- * Represents a block update in the world
- */
- class BlockUpdate(
+ class BlockChange(
val pos: BlockPos,
- val state: BlockState,
- val flags: Int
- ) : ICancellable by Cancellable()
+ val oldState: BlockState,
+ val newState: BlockState,
+ ) : Event
/**
* Represents an entity being added to the world
diff --git a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt
index 49611d4cb..1c7cd869b 100644
--- a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt
+++ b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt
@@ -46,9 +46,9 @@ import kotlin.reflect.KProperty
* The [SafeListener] will keep a reference to the last signal processed by the listener.
* Allowing use cases where the last signal is needed.
* ```kotlin
- * val lastPacketReceived by listener()
+ * val lastPacketReceived by listen()
*
- * listener { event ->
+ * listen { event ->
* println("Last packet received: ${lastPacketReceived?.packet}")
* // prints the last packet received
* // prints null if no packet was received
@@ -130,51 +130,6 @@ class SafeListener(
return listener
}
- /**
- * Registers a new [SafeListener] for a generic [Event] type [T] within the context of a [Task].
- * The [function] is executed on the same thread where the [Event] was dispatched.
- * The [function] will only be executed when the context satisfies certain safety conditions.
- * These conditions are met when none of the following [SafeContext] properties are null:
- * - [SafeContext.world]
- * - [SafeContext.player]
- * - [SafeContext.interaction]
- * - [SafeContext.connection]
- *
- * Usage:
- * ```kotlin
- * myTask.listen { event ->
- * player.sendMessage("Event received: $event")
- * }
- *
- * myTask.listen(priority = 1) { event ->
- * player.sendMessage("Event received before the previous listener: $event")
- * }
- * ```
- *
- * @param T The type of the event to listen for.
- * This should be a subclass of Event.
- * @param priority The priority of the listener.
- * Listeners with higher priority will be executed first.
- * The Default value is 0.
- * @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false.
- * @param function The function to be executed when the event is posted.
- * This function should take a SafeContext and an event of type T as parameters.
- * @return The newly created and registered [SafeListener].
- */
- inline fun Task<*>.listen(
- priority: Int = 0,
- alwaysListen: Boolean = false,
- noinline function: SafeContext.(T) -> Unit = {},
- ): SafeListener {
- val listener = SafeListener(priority, this, alwaysListen) { event ->
- function(event) // ToDo: run function always on game thread
- }
-
- syncListeners.subscribe(listener)
-
- return listener
- }
-
/**
* This function registers a new [SafeListener] for a generic [Event] type [T].
* The [transform] is executed on the same thread where the [Event] was dispatched.
diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt
index 6210c599d..8e5919078 100644
--- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt
+++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt
@@ -53,7 +53,7 @@ class ChunkedESP private constructor(
}
init {
- listenConcurrently { event ->
+ listenConcurrently { event ->
world.getWorldChunk(event.pos).renderer.notifyChunks()
}
diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt
index 9a29f1781..c2a8994b8 100644
--- a/common/src/main/kotlin/com/lambda/http/Request.kt
+++ b/common/src/main/kotlin/com/lambda/http/Request.kt
@@ -55,7 +55,7 @@ data class Request(
* @param maxAge The maximum age of the cached resource. Default is 4 days.
*/
fun maybeDownload(name: String, maxAge: Duration = 7.days): File {
- val file = cache.resolve(name).createIfNotExists()
+ val file = cache.resolve(name).toFile().createIfNotExists()
if (
System.currentTimeMillis() - file.lastModified() < maxAge.inWholeMilliseconds
diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt
index 188967335..3e27cedf0 100644
--- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt
@@ -22,7 +22,9 @@ import com.lambda.config.groups.RotationSettings
import com.lambda.context.SafeContext
import com.lambda.core.Loadable
import com.lambda.event.EventFlow.post
-import com.lambda.event.events.*
+import com.lambda.event.events.ConnectionEvent
+import com.lambda.event.events.PacketEvent
+import com.lambda.event.events.RotationEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
import com.lambda.interaction.rotation.Rotation
@@ -34,14 +36,17 @@ import com.lambda.interaction.rotation.RotationMode
import com.lambda.module.modules.client.Baritone
import com.lambda.threading.runGameScheduled
import com.lambda.threading.runSafe
-import com.lambda.util.math.lerp
-import com.lambda.util.math.MathUtils.toRadian
-import com.lambda.util.math.Vec2d
import com.lambda.util.extension.partialTicks
import com.lambda.util.extension.rotation
+import com.lambda.util.math.MathUtils.toRadian
+import com.lambda.util.math.Vec2d
+import com.lambda.util.math.lerp
import net.minecraft.client.input.Input
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket
-import kotlin.math.*
+import kotlin.math.cos
+import kotlin.math.round
+import kotlin.math.sign
+import kotlin.math.sin
object RotationManager : Loadable {
var currentRotation = Rotation.ZERO
@@ -54,16 +59,17 @@ object RotationManager : Loadable {
override fun load() = "Loaded Rotation Manager"
- fun Any.requestRotation(
+ @RotationDsl
+ fun Any.rotate(
priority: Int = 0,
alwaysListen: Boolean = false,
- onUpdate: SafeContext.(lastContext: RotationContext?) -> RotationContext?,
- onReceive: SafeContext.() -> Unit = {}
+ block: RequestRotationBuilder.() -> Unit,
) {
+ val builder = RequestRotationBuilder().apply(block)
var lastCtx: RotationContext? = null
- this.listen(priority, alwaysListen) { event ->
- val rotationContext = onUpdate(event.context)
+ listen(priority, alwaysListen) { event ->
+ val rotationContext = builder.onUpdate?.invoke(this, event.context)
rotationContext?.let {
event.context = it
@@ -72,13 +78,31 @@ object RotationManager : Loadable {
lastCtx = rotationContext
}
- this.listen { event ->
- if (event.context == lastCtx && event.context.isValid) {
- onReceive()
+ listen { event ->
+ if (event.context == lastCtx) {
+ builder.onReceive?.invoke(this, event.context)
}
}
}
+ @DslMarker
+ annotation class RotationDsl
+
+ class RequestRotationBuilder {
+ var onUpdate: (SafeContext.(lastContext: RotationContext?) -> RotationContext?)? = null
+ var onReceive: (SafeContext.(context: RotationContext) -> Unit)? = null
+
+ @RotationDsl
+ fun onUpdate(block: SafeContext.(lastContext: RotationContext?) -> RotationContext?) {
+ onUpdate = block
+ }
+
+ @RotationDsl
+ fun onReceive(block: SafeContext.(context: RotationContext) -> Unit) {
+ onReceive = block
+ }
+ }
+
@JvmStatic
fun update() = runSafe {
RotationEvent.Update(BaritoneProcessor.poolContext()).post {
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt
new file mode 100644
index 000000000..4d4003f5b
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction
+
+import com.lambda.Lambda.LOG
+import com.lambda.core.Loadable
+import com.lambda.util.Communication.logError
+import com.lambda.util.FolderRegister
+import com.lambda.util.extension.readLitematicaOrException
+import com.lambda.util.extension.readNbtOrException
+import com.lambda.util.extension.readSchematicOrException
+import com.lambda.util.extension.readSpongeOrException
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.nbt.NbtIo
+import net.minecraft.nbt.NbtSizeTracker
+import net.minecraft.registry.Registries
+import net.minecraft.structure.StructureTemplate
+import java.nio.file.*
+import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
+import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.io.path.*
+
+/**
+ * The `StructureRegistry` object is responsible for managing the loading, saving, and validating of Minecraft structure templates.
+ * It extends [ConcurrentHashMap] to allow concurrent access to structure templates by their names.
+ * This registry supports multiple structure formats and automatically monitors changes in the structure directory.
+ */
+@OptIn(ExperimentalPathApi::class)
+@Suppress("JavaIoSerializableObjectMustHaveReadResolve")
+object StructureRegistry : ConcurrentHashMap(), Loadable {
+ private val pathWatcher by lazy {
+ FileSystems.getDefault().newWatchService()
+ .apply { FolderRegister.structure.register(this, ENTRY_CREATE, ENTRY_DELETE) }
+ }
+
+ /**
+ * Map of file suffix to their respective read function
+ */
+ private val serializers = mapOf(
+ "nbt" to StructureTemplate::readNbtOrException,
+ "schem" to StructureTemplate::readSpongeOrException,
+ "litematica" to StructureTemplate::readLitematicaOrException,
+
+ // Not supported, who could guess that converting a format from 14 years ago would be hard? :clueless:
+ "schematic" to StructureTemplate::readSchematicOrException,
+ )
+
+ /**
+ * Loads a structure by relative path, will attempt a discovery sequence if the structure could not be found
+ * and performs format conversions if necessary
+ *
+ * @param relativePath The name of the structure to load (without extension).
+ * @param convert Whether to replace the file after converting it.
+ * @return The loaded [StructureTemplate], or null if the structure is not found.
+ */
+ fun loadStructureByRelativePath(
+ relativePath: Path,
+ convert: Boolean = true,
+ ): StructureTemplate? {
+ updateFileWatcher()
+
+ return computeIfAbsent(relativePath.pathString.lowercase()) {
+ loadFileAndCreate(relativePath, convert)
+ }
+ }
+
+ /**
+ * Poll directory file events to load many structures at once.
+ * They might not show up in the command suggestion, but they are
+ * present in the map.
+ */
+ private fun updateFileWatcher() {
+ pathWatcher.poll()?.let { key ->
+ key.pollEvents()
+ ?.filterIsInstance>()
+ ?.forEach { event ->
+ val newPath = event.context()
+
+ when (event.kind()) {
+ ENTRY_DELETE -> remove(newPath.pathString)
+ ENTRY_CREATE -> {
+ computeIfAbsent(newPath.pathString) {
+ loadStructureByRelativePath(newPath, convert = true)
+ }
+ }
+ }
+
+ // Reset the key -- this step is critical if you want to
+ // receive further watch events. If the key is no longer valid,
+ // the directory is inaccessible so exit the loop.
+ if (!key.reset()) return@forEach
+ }
+ }
+ }
+
+ /**
+ * Loads the structure file and creates a [StructureTemplate].
+ *
+ * @param convert Whether to replace the file after converting it.
+ * @return The created [StructureTemplate], or null if the structure is not found or invalid.
+ */
+ private fun loadFileAndCreate(path: Path, convert: Boolean) =
+ FolderRegister.structure.resolve(path).inputStream().use { templateStream ->
+ val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes())
+ val extension = path.extension
+ val template = createStructure(compound, extension)
+
+ if (convert && extension != "nbt") {
+ template?.let { saveStructure(path.pathString, it) }
+ }
+
+ // Verify the structure integrity after it had been
+ // converted to a regular structure template
+ if (compound.isValidStructureTemplate()) {
+ template
+ } else {
+ logError("Corrupted structure file: ${path.pathString}")
+ null
+ }
+ }
+
+ /**
+ * Creates a [StructureTemplate] from the provided NBT data.
+ *
+ * @param nbt The [NbtCompound] containing the structure's data.
+ * @return The created [StructureTemplate], or null if there was an error.
+ */
+ private fun createStructure(nbt: NbtCompound, suffix: String): StructureTemplate? =
+ StructureTemplate().apply {
+ serializers[suffix]
+ ?.invoke(this, Registries.BLOCK.readOnlyWrapper, nbt)
+ ?.let { error ->
+ logError("Could not create structure from file: ${error.message}")
+ return null
+ }
+ }
+
+ /**
+ * Saves the provided [structure] to disk under the specified [name].
+ *
+ * @param relativePath The relative path of the structure to save.
+ * @param structure The [StructureTemplate] to save.
+ */
+ private fun saveStructure(relativePath: String, structure: StructureTemplate) {
+ val path = FolderRegister.structure.resolve(relativePath)
+ val compound = structure.writeNbt(NbtCompound())
+
+ Files.createDirectories(path.parent)
+ path.outputStream().use { output ->
+ NbtIo.writeCompressed(compound, output)
+ }
+ }
+
+ /**
+ * Verifies that the provided NBT data represents a valid Minecraft structure template.
+ *
+ * @receiver The [NbtCompound] to validate.
+ * @return True if the NBT contains valid structure template data, false otherwise.
+ */
+ private fun NbtCompound.isValidStructureTemplate() =
+ contains("DataVersion") && contains("blocks") && contains("palette") && contains("size")
+
+ override fun load(): String {
+ FolderRegister.structure.walk()
+ .filter { it.extension in serializers.keys }
+ .forEach { loadStructureByRelativePath(FolderRegister.structure.relativize(it)) }
+
+ return "Loaded $size structure templates"
+ }
+}
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt
similarity index 65%
rename from common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt
index 3706a2210..136ba7aea 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt
@@ -15,30 +15,38 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.construction
+package com.lambda.interaction.construction.blueprint
-import com.lambda.context.SafeContext
import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.module.modules.client.TaskFlow
import com.lambda.util.BlockUtils.blockPos
-import com.lambda.util.BlockUtils.blockState
-import com.lambda.util.Communication.info
import com.lambda.util.extension.Structure
+import com.lambda.util.math.VecUtils.blockPos
import net.minecraft.structure.StructureTemplate
-import net.minecraft.util.math.BlockBox
-import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Box
+import net.minecraft.util.math.*
abstract class Blueprint {
abstract val structure: Structure
- open fun isDone(ctx: SafeContext) =
- structure.all { (pos, targetState) ->
- with(ctx) {
- val state = pos.blockState(world)
- targetState.matches(state, pos, world) || state.block in TaskFlow.ignoredBlocks
- }
- }
+ private val bounds: BlockBox by lazy {
+ val maxX = structure.keys.maxOf { it.x }
+ val maxY = structure.keys.maxOf { it.y }
+ val maxZ = structure.keys.maxOf { it.z }
+ val minX = structure.keys.minOf { it.x }
+ val minY = structure.keys.minOf { it.y }
+ val minZ = structure.keys.minOf { it.z }
+ BlockBox(minX, minY, minZ, maxX, maxY, maxZ)
+ }
+
+ fun getClosestPointTo(target: Vec3d): Vec3d {
+ val d = MathHelper.clamp(target.x, bounds.minX.toDouble(), bounds.maxX.toDouble())
+ val e = MathHelper.clamp(target.y, bounds.minY.toDouble(), bounds.maxY.toDouble())
+ val f = MathHelper.clamp(target.z, bounds.minZ.toDouble(), bounds.maxZ.toDouble())
+ return Vec3d(d, e, f)
+ }
+
+ fun isOutOfBounds(vec3d: Vec3d): Boolean = !bounds.contains(vec3d.blockPos)
+
+ val center get() = bounds.center.blockPos
companion object {
fun emptyStructure(): Structure = emptyMap()
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/DynamicBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt
similarity index 81%
rename from common/src/main/kotlin/com/lambda/interaction/construction/DynamicBlueprint.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt
index 27a45e036..27908f5ff 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/DynamicBlueprint.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt
@@ -15,9 +15,10 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.construction
+package com.lambda.interaction.construction.blueprint
import com.lambda.context.SafeContext
+import com.lambda.threading.runSafe
import com.lambda.util.extension.Structure
import net.minecraft.util.math.Vec3i
@@ -25,17 +26,23 @@ data class DynamicBlueprint(
val init: SafeContext.(Structure) -> Structure = { emptyMap() },
val update: SafeContext.(Structure) -> Structure = { it },
) : Blueprint() {
- fun update(ctx: SafeContext) {
- structure = ctx.update(structure)
+ fun update() {
+ runSafe {
+ structure = update(structure)
+ }
}
- fun create(ctx: SafeContext) {
- structure = ctx.init(structure)
+ fun create() {
+ runSafe {
+ structure = init(structure)
+ }
}
override var structure: Structure = emptyMap()
private set
+ override fun toString() = "Dynamic Blueprint at ${center.toShortString()}"
+
companion object {
fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure = {
it.map { (pos, state) ->
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StaticBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt
similarity index 87%
rename from common/src/main/kotlin/com/lambda/interaction/construction/StaticBlueprint.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt
index f276efc47..b41f806d0 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/StaticBlueprint.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt
@@ -15,13 +15,15 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.construction
+package com.lambda.interaction.construction.blueprint
import com.lambda.util.extension.Structure
data class StaticBlueprint(
override val structure: Structure
) : Blueprint() {
+ override fun toString() = "Static Blueprint at ${center.toShortString()}"
+
companion object {
fun Structure.toBlueprint() = StaticBlueprint(this)
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt
index 8cc5b1887..8f99ffe26 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt
@@ -35,7 +35,7 @@ data class BreakContext(
override var hand: Hand,
val instantBreak: Boolean,
) : BuildContext {
- override val resultingPos: BlockPos
+ override val expectedPos: BlockPos
get() = result.blockPos
override val distance: Double by lazy {
@@ -58,4 +58,8 @@ data class BreakContext(
else -> 1
}
}
+
+ override fun SafeContext.buildRenderer() {
+
+ }
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt
index 8483777a2..316ea1b84 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt
@@ -17,6 +17,7 @@
package com.lambda.interaction.construction.context
+import com.lambda.interaction.construction.result.Drawable
import com.lambda.interaction.rotation.RotationContext
import net.minecraft.block.BlockState
import net.minecraft.util.Hand
@@ -24,14 +25,14 @@ import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
-interface BuildContext : Comparable {
+interface BuildContext : Comparable, Drawable {
val pov: Vec3d
val result: BlockHitResult
val distance: Double
val expectedState: BlockState
+ val expectedPos: BlockPos
val checkedState: BlockState
val hand: Hand
- val resultingPos: BlockPos
val rotation: RotationContext
override fun compareTo(other: BuildContext): Int {
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt
index e6804360a..83481b88d 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt
@@ -17,14 +17,21 @@
package com.lambda.interaction.construction.context
+import com.lambda.context.SafeContext
+import com.lambda.graphics.renderer.esp.DirectionMask
+import com.lambda.graphics.renderer.esp.DirectionMask.exclude
import com.lambda.interaction.construction.verify.TargetState
import com.lambda.interaction.rotation.RotationContext
+import com.lambda.threading.runSafe
import com.lambda.util.BlockUtils
+import com.lambda.util.Communication.warn
import net.minecraft.block.BlockState
import net.minecraft.util.Hand
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
+import java.awt.Color
data class PlaceContext(
override val pov: Vec3d,
@@ -34,15 +41,39 @@ data class PlaceContext(
override val expectedState: BlockState,
override val checkedState: BlockState,
override val hand: Hand,
+ override val expectedPos: BlockPos,
val targetState: TargetState,
val sneak: Boolean,
val insideBlock: Boolean,
+ val primeDirection: Direction?,
) : BuildContext {
- override val resultingPos: BlockPos
- get() = result.blockPos.offset(result.side)
+ var placeTick = 0L
+ private val baseColor = Color(35, 188, 254, 25)
+ private val sideColor = Color(35, 188, 254, 100)
- override fun compareTo(other: BuildContext): Int {
- return when (other) {
+ fun place(swingHand: Boolean) {
+ runSafe {
+ val actionResult = interaction.interactBlock(
+ player, hand, result
+ )
+
+ if (actionResult.isAccepted) {
+ if (actionResult.shouldSwingHand() && swingHand) {
+ player.swingHand(hand)
+ }
+
+ if (!player.getStackInHand(hand).isEmpty && interaction.hasCreativeInventory()) {
+ mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand)
+ }
+ placeTick = mc.uptimeInTicks
+ } else {
+ warn("Internal interaction failed with $actionResult")
+ }
+ }
+ }
+
+ override fun compareTo(other: BuildContext) =
+ when (other) {
is PlaceContext -> compareBy {
BlockUtils.fluids.indexOf(it.checkedState.fluidState.fluid)
}.thenByDescending {
@@ -59,5 +90,9 @@ data class PlaceContext(
else -> 1
}
+
+ override fun SafeContext.buildRenderer() {
+ withState(expectedState, expectedPos, baseColor, DirectionMask.ALL.exclude(result.side.opposite))
+ withState(expectedState, expectedPos, sideColor, result.side.opposite)
}
}
diff --git a/common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt
similarity index 61%
rename from common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt
index df017b316..31fde9586 100644
--- a/common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt
@@ -15,19 +15,12 @@
* along with this program. If not, see .
*/
-package com.lambda.event.events
+package com.lambda.interaction.construction.processing
-import com.lambda.event.Event
-import net.minecraft.item.ItemStack
-import net.minecraft.screen.ScreenHandler
+import net.minecraft.block.BlockState
-sealed class ScreenHandlerEvent {
- class Open(val screenHandler: ScreenHandler) : Event
- class Close(val screenHandler: ScreenHandler) : Event
-
- data class Update(
- val revision: Int,
- val stacks: List,
- val cursorStack: ItemStack,
- ) : Event
+abstract class PlacementProcessor {
+ abstract fun acceptState(state: BlockState): Boolean
+ abstract fun preProcess(state: BlockState): PreprocessingStep
}
+
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt
new file mode 100644
index 000000000..bd1e566c0
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.processing
+
+import com.lambda.interaction.construction.verify.SurfaceScan
+import net.minecraft.state.property.Property
+import net.minecraft.util.math.Direction
+
+data class PreprocessingStep(
+ val surfaceScan: SurfaceScan = SurfaceScan.DEFAULT,
+ val ignore: Set> = emptySet(),
+ val sides: Set = Direction.entries.toSet(),
+)
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/processing/ProcessingStep.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt
similarity index 93%
rename from common/src/main/kotlin/com/lambda/interaction/processing/ProcessingStep.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt
index 7fc258826..23fa2722c 100644
--- a/common/src/main/kotlin/com/lambda/interaction/processing/ProcessingStep.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.processing
+package com.lambda.interaction.construction.processing
import com.lambda.task.Task
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt
new file mode 100644
index 000000000..dc70e7844
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.processing
+
+import com.lambda.core.Loadable
+import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.util.reflections.getInstances
+import net.minecraft.block.BlockState
+import net.minecraft.block.Blocks
+
+object ProcessorRegistry : Loadable {
+ private const val PROCESSOR_PACKAGE = "com.lambda.interaction.construction.processing.processors"
+ private val processors = getInstances { forPackages(PROCESSOR_PACKAGE) }
+ private val processorCache = mutableMapOf()
+
+ override fun load() = "Loaded ${processors.size} pre processors"
+
+ fun TargetState.findProcessorForState(): PreprocessingStep =
+ (this as? TargetState.State)?.let { state ->
+ processorCache.getOrPut(state.blockState) {
+ (processors.find { it.acceptState(state.blockState) } ?: DefaultProcessor).preProcess(state.blockState)
+ }
+ } ?: DefaultProcessor.preProcess(Blocks.AIR.defaultState)
+
+ object DefaultProcessor : PlacementProcessor() {
+ override fun acceptState(state: BlockState) = true
+ override fun preProcess(state: BlockState) = PreprocessingStep()
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt
similarity index 92%
rename from common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt
index 3f9529112..e8da4e1fc 100644
--- a/common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.processing
+package com.lambda.interaction.construction.processing
object TaskPlanner {
-}
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt
new file mode 100644
index 000000000..0ff14d216
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.processors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreprocessingStep
+import net.minecraft.block.BlockState
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.Direction
+
+object AxisPreprocessor : PlacementProcessor() {
+ override fun acceptState(state: BlockState) =
+ state.getOrEmpty(Properties.AXIS).isPresent
+
+ override fun preProcess(state: BlockState): PreprocessingStep {
+ val axis = state.getOrEmpty(Properties.AXIS).get()
+ return PreprocessingStep(
+ sides = Direction.entries.filter { it.axis == axis }.toSet()
+ )
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt
new file mode 100644
index 000000000..359709d5e
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.processors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreprocessingStep
+import net.minecraft.block.BlockState
+import net.minecraft.block.enums.BlockFace
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.Direction
+import kotlin.jvm.optionals.getOrNull
+
+object BlockFaceProcessor : PlacementProcessor() {
+ override fun acceptState(state: BlockState) =
+ state.getOrEmpty(Properties.BLOCK_FACE).isPresent
+
+ override fun preProcess(state: BlockState): PreprocessingStep {
+ val property = state.getOrEmpty(Properties.BLOCK_FACE).getOrNull() ?: return PreprocessingStep()
+ val sides = when (property) {
+ BlockFace.FLOOR -> setOf(Direction.DOWN)
+ BlockFace.CEILING -> setOf(Direction.UP)
+ BlockFace.WALL -> Direction.Type.HORIZONTAL.stream().toList().toSet()
+ }
+ return PreprocessingStep(sides = sides)
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt
new file mode 100644
index 000000000..62e791d4f
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.processors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreprocessingStep
+import net.minecraft.block.BlockState
+import net.minecraft.state.property.Properties
+
+object FacingProcessor : PlacementProcessor() {
+ override fun acceptState(state: BlockState) =
+ state.getOrEmpty(Properties.FACING).isPresent
+
+ override fun preProcess(state: BlockState): PreprocessingStep {
+ // Needs two sets of blocks: native and opposite!
+ return PreprocessingStep(
+ sides = setOf(state.getOrEmpty(Properties.FACING).get())
+ )
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt
new file mode 100644
index 000000000..ac5794690
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.processors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreprocessingStep
+import com.lambda.interaction.construction.verify.ScanMode
+import com.lambda.interaction.construction.verify.SurfaceScan
+import net.minecraft.block.BlockState
+import net.minecraft.block.SlabBlock
+import net.minecraft.block.enums.SlabType
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.Direction
+
+object SlabProcessor : PlacementProcessor() {
+ override fun acceptState(state: BlockState) = state.block is SlabBlock
+
+ override fun preProcess(state: BlockState): PreprocessingStep {
+ val slab = state.getOrEmpty(Properties.SLAB_TYPE).get()
+
+ val surfaceScan = when (slab) {
+ SlabType.BOTTOM -> SurfaceScan(
+ ScanMode.LESSER_HALF, Direction.Axis.Y
+ )
+ SlabType.TOP -> SurfaceScan(
+ ScanMode.GREATER_HALF, Direction.Axis.Y
+ )
+ SlabType.DOUBLE -> SurfaceScan(
+ ScanMode.FULL, Direction.Axis.Y
+ )
+ }
+
+ return PreprocessingStep(surfaceScan)
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt
index 526a96747..182ec9623 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt
@@ -21,12 +21,13 @@ import baritone.api.pathing.goals.GoalBlock
import baritone.api.pathing.goals.GoalInverted
import com.lambda.context.SafeContext
import com.lambda.interaction.construction.context.BreakContext
-import com.lambda.interaction.material.ContainerManager.findBestAvailableTool
-import com.lambda.interaction.material.ContainerManager.transfer
+import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool
+import com.lambda.interaction.material.container.ContainerManager.transfer
+import com.lambda.interaction.material.container.MaterialContainer
import com.lambda.interaction.material.StackSelection.Companion.select
import com.lambda.interaction.material.StackSelection.Companion.selectStack
-import com.lambda.interaction.material.container.MainHandContainer
-import com.lambda.task.tasks.BreakBlock.Companion.breakBlock
+import com.lambda.interaction.material.container.containers.MainHandContainer
+import com.lambda.task.tasks.BreakBlock
import net.minecraft.block.BlockState
import net.minecraft.item.Item
import net.minecraft.util.math.BlockPos
@@ -42,20 +43,17 @@ sealed class BreakResult : BuildResult() {
data class Break(
override val blockPos: BlockPos,
val context: BreakContext
- ) : Drawable, BreakResult() {
+ ) : Drawable, Resolvable, BreakResult() {
override val rank = Rank.BREAK_SUCCESS
private val color = Color(222, 0, 0, 100)
var collectDrop = false
+ override val pausesParent get() = collectDrop
- override fun SafeContext.onStart() {
- breakBlock(context, collectDrop = collectDrop).onSuccess { _, _ ->
- success(Unit)
- }.start(this@Break)
- }
+ override fun resolve() = BreakBlock(context, collectDrop)
override fun SafeContext.buildRenderer() {
- withPos(context.resultingPos, color, context.result.side)
+ withPos(context.expectedPos, color, context.result.side)
}
override fun compareTo(other: ComparableResult): Int {
@@ -78,10 +76,6 @@ sealed class BreakResult : BuildResult() {
override val rank = Rank.BREAK_NOT_EXPOSED
private val color = Color(46, 0, 0, 30)
- override fun SafeContext.onStart() {
- failure("Block is not exposed to air.")
- }
-
override fun SafeContext.buildRenderer() {
withPos(blockPos, color, side)
}
@@ -103,24 +97,20 @@ sealed class BreakResult : BuildResult() {
override val blockPos: BlockPos,
val blockState: BlockState,
val badItem: Item
- ) : Drawable, BreakResult() {
+ ) : Drawable, Resolvable, BreakResult() {
override val rank = Rank.BREAK_ITEM_CANT_MINE
private val color = Color(255, 0, 0, 100)
override val pausesParent get() = true
- override fun SafeContext.onStart() {
+ override fun resolve() =
findBestAvailableTool(blockState)
?.select()
?.transfer(MainHandContainer)
- ?.onSuccess { _, _ ->
- success(Unit)
- }?.start(this@ItemCantMine) ?: run {
- selectStack {
+ ?: selectStack {
isItem(badItem).not()
- }.transfer(MainHandContainer)?.start(this@ItemCantMine) ?: failure("No item found or space")
- }
- }
+ }.transfer(MainHandContainer)
+ ?: MaterialContainer.Nothing("Couldn't find a tool for ${blockState.block.name.string} with $badItem in main hand.")
override fun SafeContext.buildRenderer() {
withPos(blockPos, color)
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt
index 5a3403b8a..8954f1699 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt
@@ -19,15 +19,14 @@ package com.lambda.interaction.construction.result
import baritone.api.pathing.goals.GoalBlock
import baritone.api.pathing.goals.GoalNear
-import baritone.process.BuilderProcess.GoalPlace
import com.lambda.context.SafeContext
import com.lambda.interaction.construction.context.BuildContext
-import com.lambda.interaction.material.ContainerManager.transfer
+import com.lambda.interaction.material.container.ContainerManager.transfer
+import com.lambda.interaction.material.container.MaterialContainer
import com.lambda.interaction.material.StackSelection.Companion.select
-import com.lambda.interaction.material.container.MainHandContainer
-import com.lambda.task.Task
-import com.lambda.task.Task.Companion.failTask
+import com.lambda.interaction.material.container.containers.MainHandContainer
import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.Nameable
import net.minecraft.block.BlockState
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
@@ -37,16 +36,19 @@ import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
import java.awt.Color
-abstract class BuildResult : ComparableResult, Task() {
+abstract class BuildResult : ComparableResult, Nameable {
abstract val blockPos: BlockPos
open val pausesParent = false
+ override val name: String get() = "${this::class.simpleName} at ${blockPos.toShortString()}"
/**
* The build action is done.
*/
data class Done(
- override val blockPos: BlockPos,
+ override val blockPos: BlockPos
) : BuildResult() {
+ override val name: String
+ get() = "Build at $blockPos is done."
override val rank = Rank.DONE
}
@@ -54,8 +56,10 @@ abstract class BuildResult : ComparableResult, Task() {
* The build action is ignored.
*/
data class Ignored(
- override val blockPos: BlockPos,
+ override val blockPos: BlockPos
) : BuildResult() {
+ override val name: String
+ get() = "Build at $blockPos is ignored."
override val rank = Rank.IGNORED
}
@@ -66,6 +70,7 @@ abstract class BuildResult : ComparableResult, Task() {
data class ChunkNotLoaded(
override val blockPos: BlockPos
) : Navigable, Drawable, BuildResult() {
+ override val name: String get() = "Chunk at $blockPos is not loaded."
override val rank = Rank.CHUNK_NOT_LOADED
private val color = Color(252, 165, 3, 100)
@@ -90,6 +95,7 @@ abstract class BuildResult : ComparableResult, Task() {
data class Restricted(
override val blockPos: BlockPos
) : Drawable, BuildResult() {
+ override val name: String get() = "Restricted at $blockPos."
override val rank = Rank.BREAK_RESTRICTED
private val color = Color(255, 0, 0, 100)
@@ -107,6 +113,7 @@ abstract class BuildResult : ComparableResult, Task() {
override val blockPos: BlockPos,
val blockState: BlockState
) : Drawable, BuildResult() {
+ override val name: String get() = "No permission at $blockPos."
override val rank get() = Rank.BREAK_NO_PERMISSION
private val color = Color(255, 0, 0, 100)
@@ -122,6 +129,7 @@ abstract class BuildResult : ComparableResult, Task() {
data class OutOfWorld(
override val blockPos: BlockPos
) : Drawable, BuildResult() {
+ override val name: String get() = "$blockPos is out of the world."
override val rank = Rank.OUT_OF_WORLD
private val color = Color(3, 148, 252, 100)
@@ -139,6 +147,7 @@ abstract class BuildResult : ComparableResult, Task() {
override val blockPos: BlockPos,
val blockState: BlockState
) : Drawable, BuildResult() {
+ override val name: String get() = "Unbreakable at $blockPos."
override val rank = Rank.UNBREAKABLE
private val color = Color(11, 11, 11, 100)
@@ -158,6 +167,7 @@ abstract class BuildResult : ComparableResult, Task() {
val side: Direction,
val distance: Double
) : Drawable, BuildResult() {
+ override val name: String get() = "Not visible at $blockPos."
override val rank = Rank.NOT_VISIBLE
private val color = Color(46, 0, 0, 80)
@@ -180,20 +190,17 @@ abstract class BuildResult : ComparableResult, Task() {
data class WrongItem(
override val blockPos: BlockPos,
val context: BuildContext,
- val neededItem: Item
- ) : Drawable, BuildResult() {
+ val neededItem: Item,
+ val currentItem: ItemStack,
+ ) : Drawable, Resolvable, BuildResult() {
+ override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need ${neededItem.name.string}"
override val rank = Rank.WRONG_ITEM
private val color = Color(3, 252, 169, 100)
override val pausesParent get() = true
- override fun SafeContext.onStart() {
- neededItem.select()
- .transfer(MainHandContainer)
- ?.onSuccess { _, _ ->
- success(Unit)
- }?.start(this@WrongItem) ?: failure("Item ${neededItem.name.string} not found")
- }
+ override fun resolve() = neededItem.select()
+ .transfer(MainHandContainer) ?: MaterialContainer.Nothing()
override fun SafeContext.buildRenderer() {
if (blockPos.blockState(world).isAir) {
@@ -220,19 +227,15 @@ abstract class BuildResult : ComparableResult, Task() {
override val blockPos: BlockPos,
val context: BuildContext,
val neededStack: ItemStack
- ) : Drawable, BuildResult() {
+ ) : Drawable, Resolvable, BuildResult() {
+ override val name: String get() = "Wrong stack for $blockPos need $neededStack."
override val rank = Rank.WRONG_ITEM
private val color = Color(3, 252, 169, 100)
override val pausesParent get() = true
- override fun SafeContext.onStart() {
- neededStack.select()
- .transfer(MainHandContainer)
- ?.onSuccess { _, _ ->
- success(Unit)
- }?.start(this@WrongStack) ?: failTask("Stack ${neededStack.name.string} not found")
- }
+ override fun resolve() =
+ neededStack.select().transfer(MainHandContainer) ?: MaterialContainer.Nothing()
override fun SafeContext.buildRenderer() {
if (blockPos.blockState(world).isAir) {
@@ -253,26 +256,23 @@ abstract class BuildResult : ComparableResult, Task() {
/**
* Represents a break out of reach.
* @param blockPos The position of the block that is out of reach.
- * @param startVec The start vector of the reach.
- * @param hitVec The hit vector of the reach.
- * @param reach The maximum reach distance.
- * @param side The side that is out of reach.
+ * @param pov The point of view of the player.
+ * @param misses The points that are out of reach.
*/
data class OutOfReach(
override val blockPos: BlockPos,
- val startVec: Vec3d,
- val hitVec: Vec3d,
- val reach: Double,
- val side: Direction,
+ val pov: Vec3d,
+ val misses: Set
) : Navigable, Drawable, BuildResult() {
+ override val name: String get() = "Out of reach at $blockPos."
override val rank = Rank.OUT_OF_REACH
private val color = Color(252, 3, 207, 100)
val distance: Double by lazy {
- startVec.distanceTo(hitVec)
+ misses.minOfOrNull { pov.distanceTo(it) } ?: 0.0
}
- override val goal = GoalNear(blockPos, 2)
+ override val goal = GoalNear(blockPos, 3)
override fun SafeContext.buildRenderer() {
withPos(blockPos, color)
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt
index b7f114601..5cd46bfe0 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt
@@ -21,8 +21,9 @@ import baritone.api.pathing.goals.GoalBlock
import baritone.api.pathing.goals.GoalInverted
import com.lambda.context.SafeContext
import com.lambda.interaction.construction.context.PlaceContext
+import com.lambda.task.Task
import com.lambda.task.tasks.BuildTask.Companion.breakBlock
-import com.lambda.task.tasks.PlaceBlock.Companion.placeBlock
+import com.lambda.task.tasks.PlaceBlock
import net.minecraft.block.BlockState
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
@@ -44,23 +45,8 @@ sealed class PlaceResult : BuildResult() {
data class Place(
override val blockPos: BlockPos,
val context: PlaceContext
- ) : Drawable, PlaceResult() {
+ ) : PlaceResult() {
override val rank = Rank.PLACE_SUCCESS
- private val color = Color(35, 188, 254, 100)
-
- override fun SafeContext.onStart() {
- placeBlock(context).onSuccess { _, _ ->
- success(Unit)
- }.start(this@Place)
- }
-
- override fun SafeContext.buildRenderer() {
- val hitPos = context.result.blockPos
- withPos(hitPos, color, context.result.side)
-
- val light = Color(35, 188, 254, 20)
- withState(context.expectedState, context.resultingPos, light)
- }
override fun compareTo(other: ComparableResult): Int {
return when (other) {
@@ -71,15 +57,21 @@ sealed class PlaceResult : BuildResult() {
}
/**
- * The calculated placement configuration does not match the simulated outcome.
- * @param blockPos The position of the block that is not placed correctly.
- * @param expected The expected placement configuration.
- * @param simulated The simulated placement configuration.
+ * Represents a placement result where the block placement does not meet integrity expectations.
+ *
+ * This class is used to provide details about a block placement issue in which the actual block
+ * placed does not match the expected state, or additional integrity conditions are not met.
+ *
+ * @property blockPos The position of the block being inspected or placed.
+ * @property expected The expected state of the block.
+ * @property simulated The context of the item placement simulation.
+ * @property actual The expected
*/
data class NoIntegrity(
override val blockPos: BlockPos,
val expected: BlockState,
- val simulated: ItemPlacementContext
+ val simulated: ItemPlacementContext,
+ val actual: BlockState? = null
) : Drawable, PlaceResult() {
override val rank = Rank.PLACE_NO_INTEGRITY
private val color = Color(252, 3, 3, 100)
@@ -89,6 +81,11 @@ sealed class PlaceResult : BuildResult() {
}
}
+ /**
+ * Represents a scenario where block placement is obstructed by a player.
+ *
+ * @property blockPos The position of the block that was attempted to be placed.
+ */
data class BlockedByPlayer(
override val blockPos: BlockPos
) : Navigable, PlaceResult() {
@@ -98,25 +95,25 @@ sealed class PlaceResult : BuildResult() {
}
/**
- * The placement configuration cannot replace the block at the target position.
- * @param simulated The simulated placement configuration.
+ * Represents a result indicating that a block cannot be replaced during a placement operation.
+ *
+ * @property blockPos The position of the block that cannot be replaced.
+ * @property simulated The context of the item placement simulation.
*/
data class CantReplace(
override val blockPos: BlockPos,
val simulated: ItemPlacementContext
- ) : PlaceResult() {
+ ) : Resolvable, PlaceResult() {
override val rank = Rank.PLACE_CANT_REPLACE
- override fun SafeContext.onStart() {
- breakBlock(blockPos).onSuccess { _, _ ->
- success(Unit)
- }.start(this@CantReplace)
- }
+ override fun resolve() = breakBlock(blockPos)
}
/**
- * The placement configuration exceeds the maximum scaffold distance.
- * @param simulated The simulated placement configuration.
+ * Represents a placement result indicating that the scaffolding placement has exceeded the allowed limits.
+ *
+ * @property blockPos The position of the block where the placement attempt occurred.
+ * @property simulated The context of the simulated item placement attempt.
*/
data class ScaffoldExceeded(
override val blockPos: BlockPos,
@@ -126,7 +123,11 @@ sealed class PlaceResult : BuildResult() {
}
/**
- * The interaction with the block is restricted due to the feature being disabled.
+ * Represents a result where a block placement operation was prevented because
+ * the relevant block feature is disabled.
+ *
+ * @property blockPos The position of the block that could not be placed.
+ * @property itemStack The item stack associated with the attempted placement.
*/
data class BlockFeatureDisabled(
override val blockPos: BlockPos,
@@ -136,7 +137,24 @@ sealed class PlaceResult : BuildResult() {
}
/**
- * The player has no permission to interact with the block. Or the stack cannot be used on the block.
+ * Represents a result state where the placement or manipulation of a block resulted in an unexpected position.
+ *
+ * @property blockPos The intended position of the block.
+ * @property actualPos The actual position of the block, which differs from the intended position.
+ */
+ data class UnexpectedPosition(
+ override val blockPos: BlockPos,
+ val actualPos: BlockPos
+ ) : PlaceResult() {
+ override val rank = Rank.UNEXPECTED_POSITION
+ }
+
+ /**
+ * Represents a result indicating an illegal usage during a placement operation.
+ * E.g., the player can't modify the world or the block cannot be placed against the surface.
+ *
+ * @property blockPos The position of the block associated with the illegal usage result.
+ * @property rank The ranking of this result, which is always `PLACE_ILLEGAL_USAGE`.
*/
data class IllegalUsage(
override val blockPos: BlockPos
@@ -145,8 +163,10 @@ sealed class PlaceResult : BuildResult() {
}
/**
- * The item stack is not a block item.
- * This will be resolved by analyzing interaction with non-block items.
+ * Represents the result of a place operation where the provided item does not match the expected item block type.
+ *
+ * @property blockPos The position of the block where the operation was attempted.
+ * @property itemStack The item stack that was checked during the place operation.
*/
data class NotItemBlock(
override val blockPos: BlockPos,
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt
index 0d30ca5a0..da6b5a599 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt
@@ -42,6 +42,7 @@ enum class Rank {
BREAK_NO_PERMISSION,
PLACE_SCAFFOLD_EXCEEDED,
PLACE_BLOCK_FEATURE_DISABLED,
+ UNEXPECTED_POSITION,
PLACE_ILLEGAL_USAGE,
// not an issue
@@ -49,5 +50,5 @@ enum class Rank {
IGNORED;
val solvable: Boolean
- get() = ordinal < OUT_OF_WORLD.ordinal
+ get() = ordinal < PLACE_NOT_ITEM_BLOCK.ordinal
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferStep.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt
similarity index 84%
rename from common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferStep.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt
index 88d867c58..ac28db61c 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferStep.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt
@@ -15,14 +15,10 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material.transfer
+package com.lambda.interaction.construction.result
import com.lambda.task.Task
-abstract class TransferStep : Task() {
-
-
- companion object {
-
- }
-}
+interface Resolvable {
+ fun resolve(): Task<*>
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt
index 23e4f1c1e..b1259b228 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt
@@ -18,21 +18,16 @@
package com.lambda.interaction.construction.simulation
import baritone.api.pathing.goals.Goal
-import com.lambda.util.Communication.info
import com.lambda.util.world.fastVectorOf
class BuildGoal(private val sim: Simulation) : Goal {
- override fun isInGoal(x: Int, y: Int, z: Int): Boolean {
- val vec = fastVectorOf(x, y, z)
-// info("Checking goal at $x, $y, $z")
- val simu = sim.simulate(vec)
- return simu.any { it.rank.ordinal < 4 }
- }
+ override fun isInGoal(x: Int, y: Int, z: Int) =
+ sim.simulate(fastVectorOf(x, y, z))
+ .any { it.rank.ordinal < 4 }
override fun heuristic(x: Int, y: Int, z: Int): Double {
- val bestRank = sim.simulate(fastVectorOf(x, y, z)).minOrNull()?.rank?.ordinal ?: 10000
- val score = 1 / (bestRank.toDouble() + 1)
-// info("Calculating heuristic at $x, $y, $z with score $score")
- return score
+ val bestRank = sim.simulate(fastVectorOf(x, y, z))
+ .minOrNull()?.rank?.ordinal ?: 100000
+ return 1 / (bestRank.toDouble() + 1)
}
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
index 91c50ef47..03bed4002 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
@@ -19,26 +19,32 @@ package com.lambda.interaction.construction.simulation
import com.lambda.context.SafeContext
import com.lambda.interaction.RotationManager
-import com.lambda.interaction.construction.Blueprint
+import com.lambda.interaction.construction.blueprint.Blueprint
import com.lambda.interaction.construction.context.BreakContext
import com.lambda.interaction.construction.context.PlaceContext
+import com.lambda.interaction.construction.processing.ProcessorRegistry.findProcessorForState
import com.lambda.interaction.construction.result.BreakResult
import com.lambda.interaction.construction.result.BuildResult
import com.lambda.interaction.construction.result.PlaceResult
import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.material.ContainerManager.findBestAvailableTool
+import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool
+import com.lambda.interaction.rotation.Rotation.Companion.rotation
import com.lambda.interaction.rotation.Rotation.Companion.rotationTo
import com.lambda.interaction.rotation.RotationContext
+import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces
import com.lambda.interaction.visibilty.VisibilityChecker.optimum
-import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces
-import com.lambda.module.modules.client.TaskFlow
+import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces
+import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.threading.runSafe
import com.lambda.util.BlockUtils
import com.lambda.util.BlockUtils.blockState
import com.lambda.util.BlockUtils.instantBreakable
import com.lambda.util.BlockUtils.vecOf
+import com.lambda.util.Communication.warn
import com.lambda.util.item.ItemStackUtils.equal
import com.lambda.util.math.VecUtils.distSq
+import com.lambda.util.player.copyPlayer
import com.lambda.util.world.raycast.RayCastUtils.blockResult
import net.minecraft.block.OperatorBlock
import net.minecraft.block.pattern.CachedBlockPosition
@@ -46,6 +52,7 @@ import net.minecraft.item.BlockItem
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemUsageContext
import net.minecraft.registry.RegistryKeys
+import net.minecraft.state.property.Properties
import net.minecraft.util.Hand
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.HitResult
@@ -53,13 +60,14 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
+import kotlin.jvm.optionals.getOrNull
import kotlin.math.pow
object BuildSimulator {
- fun Blueprint.simulate(eye: Vec3d, reach: Double = TaskFlow.interact.reach) =
+ fun Blueprint.simulate(eye: Vec3d, reach: Double = TaskFlowModule.interact.reach) =
runSafe {
structure.entries.flatMap { (pos, target) ->
- checkRequirements(pos, target, reach)?.let {
+ checkRequirements(pos, target)?.let {
return@flatMap setOf(it)
}
checkPlaceResults(pos, target, eye, reach).let {
@@ -70,11 +78,12 @@ object BuildSimulator {
if (it.isEmpty()) return@let
return@flatMap it
}
+ warn("Nothing matched $pos $target")
emptySet()
}.toSet()
} ?: emptySet()
- private fun SafeContext.checkRequirements(pos: BlockPos, target: TargetState, reach: Double): BuildResult? {
+ private fun SafeContext.checkRequirements(pos: BlockPos, target: TargetState): BuildResult? {
/* the chunk is not loaded */
if (!world.isChunkLoaded(pos)) {
return BuildResult.ChunkNotLoaded(pos)
@@ -88,7 +97,7 @@ object BuildSimulator {
}
/* block should be ignored */
- if (state.block in TaskFlow.ignoredBlocks) {
+ if (state.block in TaskFlowModule.build.ignoredBlocks && target.type == TargetState.Type.AIR) {
return BuildResult.Ignored(pos)
}
@@ -108,7 +117,7 @@ object BuildSimulator {
}
/* block is unbreakable, so it cant be broken or replaced */
- if (state.getHardness(world, pos) < 0) {
+ if (state.getHardness(world, pos) < 0 && !player.isCreative) {
return BuildResult.Unbreakable(pos, state)
}
@@ -122,58 +131,62 @@ object BuildSimulator {
reach: Double
): Set {
val acc = mutableSetOf()
+ val targetPosState = pos.blockState(world)
- if (target is TargetState.Air || !pos.blockState(world).isReplaceable) return acc
+ if (target.isAir() || !targetPosState.isReplaceable) return acc
- val interact = TaskFlow.interact
- val rotation = TaskFlow.rotation
+ val interact = TaskFlowModule.interact
+ val rotation = TaskFlowModule.rotation
- Direction.entries.forEach { neighbor ->
- val hitPos = pos.offset(neighbor)
+ val preprocessing = target.findProcessorForState()
+
+ preprocessing.sides.forEach { neighbor ->
+ val hitPos = if (targetPosState.isAir) pos.offset(neighbor) else pos
val hitSide = neighbor.opposite
val voxelShape = hitPos.blockState(world).getOutlineShape(world, hitPos)
if (voxelShape.isEmpty) return@forEach
val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) }
-
- if (boxes.all { it.center.distanceTo(eye) > reach + 1 }) {
- acc.add(BuildResult.OutOfReach(pos, eye, hitPos.vecOf(hitSide), reach, hitSide))
- return@forEach
- }
-
val verify: HitResult.() -> Boolean = {
blockResult?.blockPos == hitPos && blockResult?.side == hitSide
}
val validHits = mutableMapOf()
+ val misses = mutableSetOf()
val reachSq = reach.pow(2)
boxes.forEach { box ->
- val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2
- scanVisibleSurfaces(eye, box, setOf(hitSide), res) { side, vec ->
+ val res = if (TaskFlowModule.interact.useRayCast) interact.resolution else 4
+ val sides = if (TaskFlowModule.interact.visibilityCheck) {
+ box.getVisibleSurfaces(eye).intersect(setOf(hitSide))
+ } else {
+ Direction.entries.toSet()
+ }
+
+ scanSurfaces(box, sides, res, preprocessing.surfaceScan) { side, vec ->
if (eye distSq vec > reachSq) {
- return@scanVisibleSurfaces
+ misses.add(vec)
+ return@scanSurfaces
}
- validHits[vec] = if (TaskFlow.interact.useRayCast) {
+ validHits[vec] = if (TaskFlowModule.interact.useRayCast && TaskFlowModule.interact.visibilityCheck) {
val cast = eye.rotationTo(vec)
- .rayCast(reach, eye) ?: return@scanVisibleSurfaces
- if (!cast.verify()) return@scanVisibleSurfaces
+ .rayCast(reach, eye) ?: return@scanSurfaces
+ if (!cast.verify()) return@scanSurfaces
cast
} else {
- BlockHitResult(
- vec,
- side,
- hitPos,
- false
- )
+ BlockHitResult(vec, side, hitPos, false)
}
-
}
}
if (validHits.isEmpty()) {
+ if (misses.isNotEmpty()) {
+ acc.add(BuildResult.OutOfReach(pos, eye, misses))
+ return@forEach
+ }
+
acc.add(BuildResult.NotVisible(pos, hitPos, hitSide, eye.distanceTo(hitPos.vecOf(hitSide))))
return@forEach
}
@@ -186,9 +199,15 @@ object BuildSimulator {
}?.let { rotation ->
val optimalStack = target.getStack(world, pos)
+ // ToDo: For each hand and sneak or not?
+ val fakePlayer = copyPlayer(player).apply {
+ setPos(eye.x, eye.y - standingEyeHeight, eye.z)
+ this.rotation = rotation.rotation
+ }
+
val usageContext = ItemUsageContext(
- player,
- Hand.MAIN_HAND, // ToDo: notice that the hand may have a different item stack and simulation will be wrong
+ fakePlayer,
+ Hand.MAIN_HAND,
rotation.hitResult?.blockResult,
)
val cachePos = CachedBlockPosition(
@@ -207,6 +226,11 @@ object BuildSimulator {
var context = ItemPlacementContext(usageContext)
+ if (context.blockPos != pos) {
+ acc.add(PlaceResult.UnexpectedPosition(pos, context.blockPos))
+ return@forEach
+ }
+
if (!optimalStack.item.isEnabled(world.enabledFeatures)) {
acc.add(PlaceResult.BlockFeatureDisabled(pos, optimalStack))
return@forEach
@@ -231,12 +255,14 @@ object BuildSimulator {
}
val resultState = blockItem.getPlacementState(context) ?: run {
- acc.add(PlaceResult.BlockedByPlayer(pos))
+// acc.add(PlaceResult.BlockedByPlayer(pos))
return@forEach
}
if (!target.matches(resultState, pos, world)) {
- acc.add(PlaceResult.NoIntegrity(pos, resultState, context))
+ acc.add(PlaceResult.NoIntegrity(
+ pos, resultState, context, (target as? TargetState.State)?.blockState)
+ )
return@forEach
}
@@ -244,6 +270,11 @@ object BuildSimulator {
val hitBlock = blockHit.blockPos.blockState(world).block
val shouldSneak = hitBlock in BlockUtils.interactionBlacklist
+ val primeDirection = (target as? TargetState.State)
+ ?.blockState
+ ?.getOrEmpty(Properties.HORIZONTAL_FACING)
+ ?.getOrNull()
+
val placeContext = PlaceContext(
eye,
blockHit,
@@ -252,9 +283,11 @@ object BuildSimulator {
resultState,
blockHit.blockPos.blockState(world),
Hand.MAIN_HAND,
+ context.blockPos,
target,
shouldSneak,
- false
+ false,
+ primeDirection
)
val currentHandStack = player.getStackInHand(Hand.MAIN_HAND)
@@ -264,7 +297,7 @@ object BuildSimulator {
}
if (optimalStack.item != currentHandStack.item) {
- acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item))
+ acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item, currentHandStack))
return@forEach
}
@@ -275,12 +308,16 @@ object BuildSimulator {
return acc
}
- private fun SafeContext.checkBreakResults(pos: BlockPos, eye: Vec3d, reach: Double): Set {
+ private fun SafeContext.checkBreakResults(
+ pos: BlockPos,
+ eye: Vec3d,
+ reach: Double
+ ): Set {
val acc = mutableSetOf()
val state = pos.blockState(world)
/* is a block that will be destroyed by breaking adjacent blocks */
- if (TaskFlow.build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) {
+ if (TaskFlowModule.build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) {
acc.add(BuildResult.Ignored(pos))
return acc
}
@@ -290,6 +327,8 @@ object BuildSimulator {
val aabb = Box(pBox.minX, pBox.minY - 1.0E-6, pBox.minZ, pBox.maxX, pBox.minY, pBox.maxZ)
world.findSupportingBlockPos(player, aabb).orElse(null)?.let { support ->
if (support != pos) return@let
+ val belowSupport = support.down().blockState(world)
+ if (belowSupport.isSolidSurface(world, support, player, Direction.UP)) return@let
acc.add(BreakResult.PlayerOnTop(pos, state))
return acc
}
@@ -325,22 +364,17 @@ object BuildSimulator {
return acc
}
- val interact = TaskFlow.interact
- val rotation = TaskFlow.rotation
+ val interact = TaskFlowModule.interact
+ val rotation = TaskFlowModule.rotation
val currentRotation = RotationManager.currentRotation
val currentCast = currentRotation.rayCast(reach, eye)
val voxelShape = state.getOutlineShape(world, pos)
voxelShape.getClosestPointTo(eye).ifPresent {
- // ToDo: Use closest point of shape
+ // ToDo: Use closest point of shape of only visible faces
}
val boxes = voxelShape.boundingBoxes.map { it.offset(pos) }
- if (boxes.all { it.center.distanceTo(eye) > reach + 1 }) {
- acc.add(BuildResult.OutOfReach(pos, eye, pos.toCenterPos(), reach, Direction.UP))
- return acc
- }
-
val verify: HitResult.() -> Boolean = { blockResult?.blockPos == pos }
/* the player is buried inside the block */
if (boxes.any { it.contains(eye) }) {
@@ -360,32 +394,36 @@ object BuildSimulator {
}
val validHits = mutableMapOf()
+ val misses = mutableSetOf()
val reachSq = reach.pow(2)
boxes.forEach { box ->
- val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2
- scanVisibleSurfaces(eye, box, emptySet(), res) { side, vec ->
+ val res = if (TaskFlowModule.interact.useRayCast) interact.resolution else 2
+ val sides = visibleSides(box, eye, TaskFlowModule.interact)
+ scanSurfaces(box, sides, res) { side, vec ->
if (eye distSq vec > reachSq) {
- return@scanVisibleSurfaces
+ misses.add(vec)
+ return@scanSurfaces
}
- validHits[vec] = if (TaskFlow.interact.useRayCast) {
+ validHits[vec] = if (TaskFlowModule.interact.useRayCast && TaskFlowModule.interact.visibilityCheck) {
val cast = eye.rotationTo(vec)
- .rayCast(reach, eye) ?: return@scanVisibleSurfaces
- if (!cast.verify()) return@scanVisibleSurfaces
+ .rayCast(reach, eye) ?: return@scanSurfaces
+ if (!cast.verify()) return@scanSurfaces
cast
} else {
- BlockHitResult(
- vec,
- side,
- pos,
- false
- )
+ BlockHitResult(vec, side, pos, false)
}
}
}
+ if (validHits.isEmpty()) {
+ // ToDo: If we can only mine exposed surfaces we need to add not visible result here
+ acc.add(BuildResult.OutOfReach(pos, eye, misses))
+ return acc
+ }
+
validHits.keys.optimum?.let { optimum ->
validHits.minByOrNull { optimum distSq it.key }?.let { closest ->
val optimumRotation = eye.rotationTo(closest.key)
@@ -413,14 +451,13 @@ object BuildSimulator {
acc.add(BreakResult.Break(pos, breakContext))
return acc
} ?: run {
- acc.add(BuildResult.WrongItem(pos, breakContext, bestTool))
+ acc.add(BuildResult.WrongItem(pos, breakContext, bestTool, player.activeItem))
return acc
}
}
acc.add(BreakResult.Break(pos, breakContext))
}
-
return acc
}
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
index 383747a94..f8c05bb27 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
@@ -18,57 +18,35 @@
package com.lambda.interaction.construction.simulation
import com.lambda.context.SafeContext
-import com.lambda.interaction.construction.Blueprint
+import com.lambda.interaction.construction.blueprint.Blueprint
import com.lambda.interaction.construction.result.BuildResult
import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.threading.runSafe
-import com.lambda.util.Communication.info
+import com.lambda.util.BlockUtils.blockState
import com.lambda.util.world.FastVector
import com.lambda.util.world.toBlockPos
-import com.lambda.util.world.toFastVec
import com.lambda.util.world.toVec3d
import net.minecraft.client.network.ClientPlayerEntity
-import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
+import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
data class Simulation(val blueprint: Blueprint) {
private val cache: MutableMap> = mutableMapOf()
- private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, 0.62, 0.5)
-// private lateinit var player: ClientPlayerEntity
-
-// init {
-// runSafe {
-// this@Simulation.player = player
-// BlockPos.iterateOutwards(player.blockPos, 5, 5, 5).forEach { pos ->
-// val vec = pos.toFastVec()
-// info("Preloading simulation at $vec")
-// simulate(vec)
-// }
-// }
-// }
-
-// val best: FastVector? get() = cache.filter { (_, results) ->
-// results.isNotEmpty() && results.any { it.rank.ordinal < 4 }
-// }.keys.minByOrNull { it.toVec3d().distanceTo(player.pos) }
-
-// fun best() = cache.filter { (_, results) ->
-// results.isNotEmpty() && results.any { it.rank.ordinal < 3 }
-// }.keys
-
- fun simulate(pos: FastVector): Set {
-// runSafe {
-// if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return emptySet()
-// }
-// return blueprint.simulate(pos.toView()).also { cache[pos] = it }
-// return cache.computeIfAbsent(pos) {
-//// runSafe {
-//// if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return@computeIfAbsent emptySet()
-//// }
-// blueprint.simulate(pos.toView())
-// }
- return blueprint.simulate(pos.toView(), reach = 3.5)
- }
+ private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.DEFAULT_EYE_HEIGHT.toDouble(), 0.5)
+
+ fun simulate(pos: FastVector) =
+ cache.getOrPut(pos) {
+ val view = pos.toView()
+ runSafe {
+ if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view).distanceTo(view) > 10.0) return@getOrPut emptySet()
+ val blockPos = pos.toBlockPos()
+ if (!playerFitsIn(Vec3d.ofBottomCenter(blockPos))) return@getOrPut emptySet()
+ if (!blockPos.down().blockState(world).isSideSolidFullSquare(world, blockPos, Direction.UP)) return@getOrPut emptySet()
+ }
+ blueprint.simulate(view, reach = TaskFlowModule.interact.reach - 1)
+ }
private fun SafeContext.playerFitsIn(pos: Vec3d): Boolean {
val pBox = player.boundingBox
diff --git a/common/src/main/kotlin/com/lambda/task/RootTask.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt
similarity index 74%
rename from common/src/main/kotlin/com/lambda/task/RootTask.kt
rename to common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt
index 9f1b7b9c8..44b190fdc 100644
--- a/common/src/main/kotlin/com/lambda/task/RootTask.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt
@@ -15,15 +15,8 @@
* along with this program. If not, see .
*/
-package com.lambda.task
+package com.lambda.interaction.construction.verify
-object RootTask : Task() {
- init {
- name = "RootTask"
- }
-
- fun addInfo(debugText: MutableList) {
- debugText.add("")
- debugText.addAll(info.string.split("\n"))
- }
-}
+enum class ScanMode {
+ GREATER_HALF, LESSER_HALF, FULL
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt
index 6f5e416d8..af2aaec11 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt
@@ -25,4 +25,5 @@ import net.minecraft.util.math.BlockPos
interface StateMatcher {
fun matches(state: BlockState, pos: BlockPos, world: ClientWorld): Boolean
fun getStack(world: ClientWorld, pos: BlockPos): ItemStack
+ fun isAir(): Boolean
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt
new file mode 100644
index 000000000..78586b7f7
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.construction.verify
+
+import net.minecraft.util.math.Direction
+
+data class SurfaceScan(
+ val mode: ScanMode,
+ val axis: Direction.Axis,
+) {
+ companion object {
+ val DEFAULT = SurfaceScan(ScanMode.FULL, Direction.Axis.Y)
+ }
+
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
index 78a480775..22982d5e2 100644
--- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
@@ -17,9 +17,10 @@
package com.lambda.interaction.construction.verify
-import com.lambda.interaction.material.ContainerManager.findDisposable
-import com.lambda.module.modules.client.TaskFlow
+import com.lambda.interaction.material.container.ContainerManager.findDisposable
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.StringUtils.capitalize
import com.lambda.util.item.ItemUtils.block
import net.minecraft.block.BlockState
import net.minecraft.client.world.ClientWorld
@@ -28,53 +29,82 @@ import net.minecraft.item.Items
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
-sealed class TargetState : StateMatcher {
- data object Air : TargetState() {
+sealed class TargetState(val type: Type) : StateMatcher {
+
+ enum class Type {
+ AIR, SOLID, SUPPORT, STATE, BLOCK, STACK
+ }
+
+ data object Air : TargetState(Type.AIR) {
+ override fun toString() = "Air"
+
override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
state.isAir
override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
ItemStack.EMPTY
+
+ override fun isAir() = true
}
- data object Solid : TargetState() {
+ data object Solid : TargetState(Type.SOLID) {
+ override fun toString() = "Solid"
+
override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
state.isSolidBlock(world, pos)
override fun getStack(world: ClientWorld, pos: BlockPos) =
findDisposable()?.stacks?.firstOrNull {
- it.item.block in TaskFlow.disposables
+ it.item.block in TaskFlowModule.inventory.disposables
} ?: ItemStack(Items.NETHERRACK)
+
+ override fun isAir() = false
}
- data class Support(val direction: Direction) : TargetState() {
+ data class Support(val direction: Direction) : TargetState(Type.SUPPORT) {
+ override fun toString() = "Support for ${direction.name}"
+
override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
pos.offset(direction).blockState(world).isSolidBlock(world, pos.offset(direction))
|| state.isSolidBlock(world, pos)
override fun getStack(world: ClientWorld, pos: BlockPos) =
findDisposable()?.stacks?.firstOrNull {
- it.item.block in TaskFlow.disposables
+ it.item.block in TaskFlowModule.inventory.disposables
} ?: ItemStack(Items.NETHERRACK)
+
+ override fun isAir() = false
}
- data class State(val blockState: BlockState) : TargetState() {
- override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
- state == blockState
+ data class State(val blockState: BlockState) : TargetState(Type.STATE) {
+ override fun toString() = "State of $blockState"
+ override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
+ state.block == blockState.block && state.properties.all {
+ /*it in TaskFlowModule.defaultIgnoreTags ||*/ state[it] == blockState[it]
+ }
override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
blockState.block.getPickStack(world, pos, blockState)
+
+ override fun isAir() = blockState.isAir
}
- data class Block(val block: net.minecraft.block.Block) : TargetState() {
+ data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) {
+ override fun toString() = "Block of ${block.name.string.capitalize()}"
+
override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
state.block == block
override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
block.getPickStack(world, pos, block.defaultState)
+
+ override fun isAir() = block.defaultState.isAir
}
- data class Stack(val itemStack: ItemStack) : TargetState() {
+ data class Stack(val itemStack: ItemStack) : TargetState(Type.STACK) {
+ private val startStack: ItemStack = itemStack.copy()
+ override fun toString() = "Stack of ${startStack.item.name.string.capitalize()}"
+
private val block = itemStack.item.block
override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
@@ -82,5 +112,7 @@ sealed class TargetState : StateMatcher {
override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
itemStack
+
+ override fun isAir() = false
}
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt b/common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt
new file mode 100644
index 000000000..c61589a24
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.task.Task
+
+abstract class ContainerTask : Task() {
+ private var finish = false
+ private val delay = 5
+ private var currentDelay = 0
+
+ fun delayedFinish() {
+ finish = true
+ }
+
+ init {
+ listen {
+ if (finish) {
+ if (currentDelay++ > delay) success()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt
similarity index 71%
rename from common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt
index ca383f546..714124825 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt
@@ -15,15 +15,17 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material
+package com.lambda.interaction.material.container
import com.lambda.core.Loadable
+import com.lambda.event.events.InventoryEvent
import com.lambda.event.events.PlayerEvent
-import com.lambda.event.events.ScreenHandlerEvent
import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.StackSelection
import com.lambda.interaction.material.StackSelection.Companion.select
-import com.lambda.interaction.material.container.*
-import com.lambda.module.modules.client.TaskFlow
+import com.lambda.interaction.material.container.containers.ChestContainer
+import com.lambda.interaction.material.container.containers.EnderChestContainer
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.util.BlockUtils.blockEntity
import com.lambda.util.BlockUtils.item
import com.lambda.util.Communication.info
@@ -35,14 +37,14 @@ import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.ChestBlockEntity
import net.minecraft.block.entity.EnderChestBlockEntity
import net.minecraft.item.Item
-import net.minecraft.item.ItemStack
import net.minecraft.screen.GenericContainerScreenHandler
import net.minecraft.screen.ScreenHandlerType
// ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system.
object ContainerManager : Loadable {
private val container: List
- get() = compileContainers + runtimeContainers
+ // ToDo: Filter containers based on a filter setting TaskFlowModule.inventory.accessEnderChest etc
+ get() = compileContainers.filter { it !is EnderChestContainer } + runtimeContainers
private val compileContainers =
getInstances { forPackages("com.lambda.interaction.material.container") }
@@ -57,7 +59,7 @@ object ContainerManager : Loadable {
lastInteractedBlockEntity = it.blockHitResult.blockPos.blockEntity(world)
}
- listen { event ->
+ listen { event ->
if (event.screenHandler !is GenericContainerScreenHandler) return@listen
val handler = event.screenHandler
@@ -87,39 +89,38 @@ object ContainerManager : Loadable {
}
}
- fun container() = container.flatMap {
- setOf(it) + it.shulkerContainer
- }.sorted()
+ fun container() = container.flatMap { setOf(it) + it.shulkerContainer }.sorted()
fun StackSelection.transfer(destination: MaterialContainer) =
- findContainerWithSelection(this)?.transfer(this, destination)
+ findContainerWithMaterial(this)?.transfer(this, destination)
fun findContainer(
block: (MaterialContainer) -> Boolean
): MaterialContainer? = container().find(block)
- fun findContainerWithSelection(
+ fun findContainerWithMaterial(
selection: StackSelection
): MaterialContainer? =
- container().find { it.available(selection) >= selection.count }
+ containerWithMaterial(selection).firstOrNull()
- fun containerMatchSelection(
+ fun findContainerWithSpace(
selection: StackSelection
- ): Set =
- container().filter { it.available(selection) >= selection.count }.toSet()
-
- fun findContainerWithSelection(
- selectionBuilder: StackSelection.() -> Unit
- ): MaterialContainer? {
- val selection = StackSelection().apply(selectionBuilder)
- return container().find { it.available(selection) >= selection.count }
- }
-
- fun findContainerWithStacks(
- count: Int = StackSelection.DEFAULT_AMOUNT,
- selection: (ItemStack) -> Boolean,
): MaterialContainer? =
- findContainerWithSelection(selection.select())
+ containerWithSpace(selection).firstOrNull()
+
+ fun containerWithMaterial(
+ selection: StackSelection
+ ): List =
+ container()
+ .sortedWith(TaskFlowModule.inventory.providerPriority.materialComparator(selection))
+ .filter { it.materialAvailable(selection) >= selection.count }
+
+ fun containerWithSpace(
+ selection: StackSelection
+ ): List =
+ container()
+ .sortedWith(TaskFlowModule.inventory.providerPriority.spaceComparator(selection))
+ .filter { it.spaceAvailable(selection) >= selection.count }
fun findBestAvailableTool(
blockState: BlockState,
@@ -129,13 +130,13 @@ object ContainerManager : Loadable {
}.filter { (item, speed) ->
speed > 1.0
&& item.isSuitableFor(blockState)
- && findContainerWithSelection(item.select()) != null
+ && containerWithMaterial(item.select()).isNotEmpty()
}.maxByOrNull {
it.second
}?.first
fun findDisposable() = container().find { container ->
- TaskFlow.disposables.any { container.available(it.item.select()) >= 0 }
+ TaskFlowModule.inventory.disposables.any { container.materialAvailable(it.item.select()) >= 0 }
}
class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection")
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt
deleted file mode 100644
index 126822684..000000000
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2024 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.Lambda.mc
-import com.lambda.interaction.construction.result.ComparableResult
-import com.lambda.interaction.material.MaterialContainer
-import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task.Companion.buildTask
-import com.lambda.util.item.ItemStackUtils.equal
-import net.minecraft.item.ItemStack
-
-data object CreativeContainer : MaterialContainer(Rank.CREATIVE) {
- override var stacks = emptyList()
- override val name = "Creative"
-
- override fun available(selection: StackSelection): Int =
- if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0
-
- override fun spaceLeft(selection: StackSelection) = Int.MAX_VALUE
-
- override fun deposit(selection: StackSelection) = buildTask("CreativeDeposit") {
- if (!player.isCreative) {
- // ToDo: Maybe switch gamemode?
- throw NotInCreativeModeException()
- }
-
- interaction.clickCreativeStack(
- ItemStack.EMPTY,
- 36 + player.inventory.selectedSlot
- )
- }
-
- // Withdraws items from the creative menu to the player's main hand
- override fun withdraw(selection: StackSelection) = buildTask("CreativeWithdraw") {
- selection.optimalStack?.let { optimalStack ->
- if (player.mainHandStack.equal(optimalStack)) return@buildTask
-
- if (!player.isCreative) {
- // ToDo: Maybe switch gamemode?
- throw NotInCreativeModeException()
- }
-
- interaction.clickCreativeStack(
- optimalStack,
- 36 + player.inventory.selectedSlot
- )
- return@buildTask
- }
-
- throw NoOptimalStackException()
- }
-
- class NotInCreativeModeException : IllegalStateException("Insufficient permission: not in creative mode")
- class NoOptimalStackException : IllegalStateException("Cannot move item: no optimal stack")
-}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt
deleted file mode 100644
index b2955d691..000000000
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2024 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.Lambda.mc
-import com.lambda.interaction.material.MaterialContainer
-import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task.Companion.buildTask
-import com.lambda.task.Task.Companion.emptyTask
-import com.lambda.util.player.SlotUtils.combined
-import com.lambda.util.player.SlotUtils.hotbar
-import net.minecraft.item.ItemStack
-import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
-import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
-
-object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) {
- override var stacks: List
- get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList()
- set(_) {}
- override val name = "MainHand"
-
- override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromMainHand")
-
- override fun deposit(selection: StackSelection) = buildTask("DepositToMainHand") {
- InventoryContainer.matchingStacks(selection).firstOrNull()?.let { stack ->
- if (ItemStack.areEqual(stack, player.mainHandStack)) {
- return@buildTask
- }
-
- if (ItemStack.areEqual(stack, player.offHandStack)) {
- connection.sendPacket(
- PlayerActionC2SPacket(
- PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND,
- BlockPos.ORIGIN,
- Direction.DOWN,
- ),
- )
- return@buildTask
- }
-
- if (stack in player.hotbar) {
- player.inventory.selectedSlot = player.hotbar.indexOf(stack)
- return@buildTask
- }
-
- interaction.pickFromInventory(player.combined.indexOf(stack))
- }
- }
-}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt
similarity index 62%
rename from common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt
index c825e5a07..312b19ef3 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt
@@ -15,10 +15,11 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material
+package com.lambda.interaction.material.container
-import com.lambda.interaction.material.StackSelection.Companion.select
-import com.lambda.interaction.material.container.ShulkerBoxContainer
+import com.lambda.context.SafeContext
+import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.container.containers.ShulkerBoxContainer
import com.lambda.interaction.material.transfer.TransferResult
import com.lambda.task.Task
import com.lambda.util.Nameable
@@ -27,13 +28,41 @@ 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.text.*
import net.minecraft.item.ItemStack
+import net.minecraft.text.Text
// ToDo: Make jsonable to persistently store them
abstract class MaterialContainer(
- private val rank: Rank
+ val rank: Rank
) : Nameable, Comparable {
abstract var stacks: List
+ abstract val description: Text
+
+ @TextDsl
+ fun TextBuilder.stock(selection: StackSelection) {
+ literal("\n")
+ literal("Contains ")
+ val available = materialAvailable(selection)
+ highlighted(if (available == Int.MAX_VALUE) "∞" else available.toString())
+ literal(" of ")
+ highlighted("${selection.optimalStack?.name?.string}")
+ literal("\n")
+ literal("Could store ")
+ val left = spaceAvailable(selection)
+ highlighted(if (left == Int.MAX_VALUE) "∞" else left.toString())
+ literal(" of ")
+ highlighted("${selection.optimalStack?.name?.string}")
+ }
+
+ fun description(selection: StackSelection) =
+ buildText {
+ text(description)
+ stock(selection)
+ }
+
+ override val name: String
+ get() = buildText { text(description) }.string
val shulkerContainer
get() =
@@ -51,46 +80,49 @@ abstract class MaterialContainer(
this.stacks = stacks
}
+ class Nothing(override val name: String = "Nothing") : Task() {
+ override fun SafeContext.onStart() {
+ success()
+ }
+ }
+
/**
* Withdraws items from the container to the player's inventory.
*/
@Task.Ta5kBuilder
- abstract fun withdraw(selection: StackSelection): Task<*>
+ open fun withdraw(selection: StackSelection): Task<*> = Nothing(name)
/**
* Deposits items from the player's inventory into the container.
*/
@Task.Ta5kBuilder
- abstract fun deposit(selection: StackSelection): Task<*>
+ open fun deposit(selection: StackSelection): Task<*> = Nothing(name)
open fun matchingStacks(selection: StackSelection) =
selection.filterStacks(stacks)
- open fun matchingStacks(selection: (ItemStack) -> Boolean) =
- matchingStacks(selection.select())
-
- open fun available(selection: StackSelection) =
+ open fun materialAvailable(selection: StackSelection) =
matchingStacks(selection).count
- open fun spaceLeft(selection: StackSelection) =
+ open fun spaceAvailable(selection: StackSelection) =
matchingStacks(selection).spaceLeft + stacks.empty * selection.stackSize
fun transfer(selection: StackSelection, destination: MaterialContainer): TransferResult {
- val amount = available(selection)
+ val amount = materialAvailable(selection)
if (amount < selection.count) {
return TransferResult.MissingItems(selection.count - amount)
}
-// val space = destination.spaceLeft(selection)
+// val space = destination.spaceAvailable(selection)
// if (space == 0) {
// return TransferResult.NoSpace
// }
-//
+
// val transferAmount = minOf(amount, space)
// selection.selector = { true }
// selection.count = transferAmount
- return TransferResult.Transfer(selection, from = this, to = destination)
+ return TransferResult.ContainerTransfer(selection, from = this, to = destination)
}
enum class Rank {
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt
deleted file mode 100644
index e2a86fae7..000000000
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2024 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.Lambda.mc
-import com.lambda.interaction.material.MaterialContainer
-import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task.Companion.buildTask
-import com.lambda.task.Task.Companion.emptyTask
-import com.lambda.util.player.SlotUtils.combined
-import com.lambda.util.player.SlotUtils.hotbar
-import net.minecraft.item.ItemStack
-import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
-import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
-
-object OffHandContainer : MaterialContainer(Rank.OFF_HAND) {
- override var stacks: List
- get() = mc.player?.offHandStack?.let { listOf(it) } ?: emptyList()
- set(_) {}
- override val name = "OffHand"
-
- override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromOffHand")
-
- override fun deposit(selection: StackSelection) = buildTask("DepositToOffHand") {
- InventoryContainer.matchingStacks(selection).firstOrNull()?.let { stack ->
- if (ItemStack.areEqual(stack, player.offHandStack)) {
- return@buildTask
- }
-
- if (stack in player.hotbar) {
- player.inventory.selectedSlot = player.hotbar.indexOf(stack)
- } else {
- interaction.pickFromInventory(player.combined.indexOf(stack))
- }
-
- if (ItemStack.areEqual(stack, player.mainHandStack)) {
- connection.sendPacket(
- PlayerActionC2SPacket(
- PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND,
- BlockPos.ORIGIN,
- Direction.DOWN,
- ),
- )
- }
- }
- }
-}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt
deleted file mode 100644
index 63565b0b6..000000000
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2024 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.SafeContext
-import com.lambda.interaction.material.MaterialContainer
-import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task
-import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock
-import com.lambda.task.tasks.InventoryTask.Companion.deposit
-import com.lambda.task.tasks.InventoryTask.Companion.withdraw
-import com.lambda.task.tasks.OpenContainer.Companion.openContainer
-import com.lambda.task.tasks.PlaceContainer.Companion.placeContainer
-import net.minecraft.item.ItemStack
-
-data class ShulkerBoxContainer(
- override var stacks: List,
- val containedIn: MaterialContainer,
- val shulkerStack: ItemStack,
-) : MaterialContainer(Rank.SHULKER_BOX) {
- override val name = "${shulkerStack.name.string} in slot $slotInContainer in ${containedIn.name}"
-
- private val slotInContainer: Int get() = containedIn.stacks.indexOf(shulkerStack)
-
- class Withdraw(
- private val selection: StackSelection,
- private val shulkerStack: ItemStack
- ) : Task() {
- override fun SafeContext.onStart() {
- placeContainer(shulkerStack).thenRun(this@Withdraw) { _, placePos ->
- openContainer(placePos).thenRun(this@Withdraw) { _, screen ->
- withdraw(screen, selection).thenRun(this@Withdraw) { _, _ ->
- breakAndCollectBlock(placePos).onSuccess { _, _ ->
- success(Unit)
- }
- }
- }
- }.start(this@Withdraw)
- }
- }
-
- override fun withdraw(selection: StackSelection) = Withdraw(selection, shulkerStack)
-
- class Deposit(
- private val selection: StackSelection,
- private val shulkerStack: ItemStack
- ) : Task() {
- override fun SafeContext.onStart() {
- placeContainer(shulkerStack).thenRun(this@Deposit) { _, placePos ->
- openContainer(placePos).thenRun(this@Deposit) { _, screen ->
- deposit(screen, selection).thenRun(this@Deposit) { _, _ ->
- breakAndCollectBlock(placePos).onSuccess { _, _ ->
- success(Unit)
- }
- }
- }
- }.start(this@Deposit)
- }
- }
-
- override fun deposit(selection: StackSelection) = Deposit(selection, shulkerStack)
-}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt
similarity index 63%
rename from common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt
index 92a8a469b..8bb41286e 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt
@@ -15,16 +15,18 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material.container
+package com.lambda.interaction.material.container.containers
-import com.lambda.interaction.material.MaterialContainer
import com.lambda.interaction.material.StackSelection
-import com.lambda.task.tasks.InventoryTask.Companion.deposit
-import com.lambda.task.tasks.InventoryTask.Companion.withdraw
-import com.lambda.task.tasks.OpenContainer.Companion.openContainer
+import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit
+import com.lambda.interaction.material.transfer.SlotTransfer.Companion.withdraw
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.task.tasks.OpenContainer
import com.lambda.util.Communication.info
+import com.lambda.util.text.buildText
+import com.lambda.util.text.highlighted
+import com.lambda.util.text.literal
import net.minecraft.item.ItemStack
-import net.minecraft.screen.GenericContainerScreenHandler
import net.minecraft.screen.ScreenHandler
import net.minecraft.util.math.BlockPos
@@ -33,7 +35,16 @@ data class ChestContainer(
val blockPos: BlockPos,
val containedInStash: StashContainer? = null,
) : MaterialContainer(Rank.CHEST) {
- override val name = "Chest at ${blockPos.toShortString()}"
+ override val description =
+ buildText {
+ literal("Chest at ")
+ highlighted(blockPos.toShortString())
+ containedInStash?.let { stash ->
+ literal(" (contained in ")
+ highlighted(stash.name)
+ literal(")")
+ }
+ }
// override fun prepare() =
// moveIntoEntityRange(blockPos).onSuccess { _, _ ->
@@ -47,21 +58,17 @@ data class ChestContainer(
// }
override fun withdraw(selection: StackSelection) =
- openContainer(blockPos)
-// .withMaxAttempts(3)
-// .withTimeout(20)
- .onSuccess { open, screen ->
- info("Withdrawing $selection from ${screen.type}")
- withdraw(screen, selection).start(open)
+ OpenContainer(blockPos)
+ .then {
+ info("Withdrawing $selection from ${it.type}")
+ withdraw(it, selection)
}
override fun deposit(selection: StackSelection) =
- openContainer(blockPos)
-// .withMaxAttempts(3)
-// .withTimeout(20)
- .onSuccess { open, screen ->
- info("Depositing $selection to ${screen.type}")
- deposit(screen, selection).start(open)
+ OpenContainer(blockPos)
+ .then {
+ info("Depositing $selection to ${it.type}")
+ deposit(it, selection)
}
class ChestBlockedException : Exception("The chest is blocked by another block or a cat")
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt
new file mode 100644
index 000000000..52683117a
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2024 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.material.StackSelection
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.task.Task
+import com.lambda.util.item.ItemStackUtils.equal
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
+import net.minecraft.item.ItemStack
+
+data object CreativeContainer : MaterialContainer(Rank.CREATIVE) {
+ override var stacks = emptyList()
+
+ override val description = buildText { literal("Creative") }
+
+ override fun materialAvailable(selection: StackSelection): Int =
+ if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0
+
+ override fun spaceAvailable(selection: StackSelection): Int =
+ if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0
+
+ class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection) : Task() {
+ override val name: String get() = "Removing $selection from creative inventory"
+
+ override fun SafeContext.onStart() {
+ if (!player.isCreative) {
+ // ToDo: Maybe switch gamemode?
+ throw NotInCreativeModeException()
+ }
+
+ player.currentScreenHandler?.slots?.let { slots ->
+ selection.filterSlots(slots).forEach {
+ interaction.clickCreativeStack(ItemStack.EMPTY, it.id)
+ }
+ }
+
+ success()
+ }
+ }
+
+ override fun deposit(selection: StackSelection) = CreativeDeposit(selection)
+
+ class CreativeWithdrawal @Ta5kBuilder constructor(val selection: StackSelection) : Task() {
+ override val name: String get() = "Withdrawing $selection from creative inventory"
+
+ override fun SafeContext.onStart() {
+ selection.optimalStack?.let { optimalStack ->
+ if (player.mainHandStack.equal(optimalStack)) return
+
+ if (!player.isCreative) {
+ // ToDo: Maybe switch gamemode?
+ throw NotInCreativeModeException()
+ }
+
+ interaction.clickCreativeStack(
+ optimalStack,
+ 36 + player.inventory.selectedSlot
+ )
+ success()
+ return
+ }
+
+ throw NoOptimalStackException()
+ }
+ }
+
+ // Withdraws items from the creative menu to the player's main hand
+ override fun withdraw(selection: StackSelection) = CreativeWithdrawal(selection)
+
+ class NotInCreativeModeException : IllegalStateException("Insufficient permission: not in creative mode")
+ class NoOptimalStackException : IllegalStateException("Cannot move item: no optimal stack")
+}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt
similarity index 59%
rename from common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt
index 1500596a8..398549eac 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt
@@ -15,24 +15,24 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material.container
+package com.lambda.interaction.material.container.containers
-import com.lambda.interaction.material.MaterialContainer
+import com.lambda.context.SafeContext
import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.container.MaterialContainer
import com.lambda.task.Task
-import com.lambda.task.Task.Companion.emptyTask
-import com.lambda.task.Task.Companion.failTask
-import com.lambda.task.tasks.OpenContainer.Companion.openContainer
import com.lambda.util.Communication.info
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
import net.minecraft.item.ItemStack
-import net.minecraft.screen.GenericContainerScreenHandler
import net.minecraft.util.math.BlockPos
object EnderChestContainer : MaterialContainer(Rank.ENDER_CHEST) {
override var stacks = emptyList()
- override val name = "EnderChest"
private var placePos: BlockPos? = null
+ override val description = buildText { literal("Ender Chest") }
+
// override fun prepare(): Task<*> {
// TODO("Not yet implemented")
// }
@@ -47,13 +47,25 @@ object EnderChestContainer : MaterialContainer(Rank.ENDER_CHEST) {
// }
// }
- override fun withdraw(selection: StackSelection): Task<*> {
- info("Not yet implemented")
- return emptyTask()
+ class EnderchestWithdrawal @Ta5kBuilder constructor(selection: StackSelection) : Task() {
+ override val name = "Withdrawing $selection from ender chest"
+
+ override fun SafeContext.onStart() {
+ info("Not yet implemented")
+ success()
+ }
}
- override fun deposit(selection: StackSelection): Task<*> {
- info("Not yet implemented")
- return emptyTask()
+ override fun withdraw(selection: StackSelection) = EnderchestWithdrawal(selection)
+
+ class EnderchestDeposit @Ta5kBuilder constructor(selection: StackSelection) : Task() {
+ override val name = "Depositing $selection into ender chest"
+
+ override fun SafeContext.onStart() {
+ info("Not yet implemented")
+ success()
+ }
}
+
+ override fun deposit(selection: StackSelection) = EnderchestDeposit(selection)
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt
new file mode 100644
index 000000000..a9000639e
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 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.material.ContainerTask
+import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.util.player.SlotUtils.hotbar
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
+import net.minecraft.item.ItemStack
+
+object HotbarContainer : MaterialContainer(Rank.HOTBAR) {
+ override var stacks: List
+ get() = mc.player?.hotbar ?: emptyList()
+ set(_) {}
+
+ override val description = buildText { literal("Hotbar") }
+
+ class HotbarDeposit @Ta5kBuilder constructor(val selection: StackSelection) : ContainerTask() {
+ override val name: String get() = "Depositing $selection into hotbar"
+
+ override fun SafeContext.onStart() {
+ val handler = player.currentScreenHandler
+ deposit(handler, selection).finally {
+ delayedFinish()
+ }.execute(this@HotbarDeposit)
+ }
+ }
+
+ override fun deposit(selection: StackSelection) = HotbarDeposit(selection)
+}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt
similarity index 68%
rename from common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt
index 2f8ca8c4e..928bbd113 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt
@@ -15,23 +15,19 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material.container
+package com.lambda.interaction.material.container.containers
import com.lambda.Lambda.mc
-import com.lambda.interaction.material.MaterialContainer
-import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task
-import com.lambda.task.Task.Companion.emptyTask
+import com.lambda.interaction.material.container.MaterialContainer
import com.lambda.util.player.SlotUtils.combined
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
import net.minecraft.item.ItemStack
object InventoryContainer : MaterialContainer(Rank.INVENTORY) {
override var stacks: List
get() = mc.player?.combined ?: emptyList()
set(_) {}
- override val name = "Inventory"
- override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromInventory")
-
- override fun deposit(selection: StackSelection) = emptyTask("DepositToInventory")
+ override val description = buildText { literal("Inventory") }
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt
new file mode 100644
index 000000000..5e26d6644
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2024 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.material.ContainerTask
+import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer
+import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.util.item.ItemStackUtils.equal
+import com.lambda.util.player.SlotUtils.combined
+import com.lambda.util.player.SlotUtils.hotbar
+import com.lambda.util.player.SlotUtils.storage
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
+import net.minecraft.item.ItemStack
+import net.minecraft.util.Hand
+
+object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) {
+ override var stacks: List
+ get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList()
+ set(_) {}
+
+ override val description = buildText { literal("MainHand") }
+
+ class HandDeposit @Ta5kBuilder constructor(val selection: StackSelection, val hand: Hand) : ContainerTask() {
+ override val name: String get() = "Depositing [$selection] to ${hand.name.lowercase().replace("_", " ")}"
+
+ override fun SafeContext.onStart() {
+ val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run {
+ failure("No matching stacks found in inventory")
+ return
+ }
+
+ val handStack = player.getStackInHand(hand)
+ if (moveStack.equal(handStack)) {
+ success()
+ return
+ }
+
+ transfer {
+ val stackInOffHand = moveStack.equal(player.offHandStack)
+ if (hand == Hand.MAIN_HAND && stackInOffHand) {
+ swapHands()
+ return@transfer
+ }
+
+ when (moveStack) {
+ in player.hotbar -> swapToHotbarSlot(player.hotbar.indexOf(moveStack))
+ in player.storage -> pickFromInventory(player.combined.indexOf(moveStack))
+ }
+
+ if (hand == Hand.OFF_HAND) swapHands()
+ }.finally {
+ success()
+ }.execute(this@HandDeposit)
+ }
+ }
+
+ override fun deposit(selection: StackSelection) = HandDeposit(selection, Hand.MAIN_HAND)
+}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt
similarity index 54%
rename from common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt
index 55b75ae47..d19a7a0a6 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt
@@ -15,27 +15,22 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material.container
+package com.lambda.interaction.material.container.containers
import com.lambda.Lambda.mc
-import com.lambda.interaction.material.MaterialContainer
import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task
-import com.lambda.task.Task.Companion.emptyTask
-import com.lambda.task.tasks.InventoryTask.Companion.deposit
-import com.lambda.util.player.SlotUtils.hotbar
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
import net.minecraft.item.ItemStack
+import net.minecraft.util.Hand
-object HotbarContainer : MaterialContainer(Rank.HOTBAR) {
+object OffHandContainer : MaterialContainer(Rank.OFF_HAND) {
override var stacks: List
- get() = mc.player?.hotbar ?: emptyList()
+ get() = mc.player?.offHandStack?.let { listOf(it) } ?: emptyList()
set(_) {}
- override val name = "Hotbar"
- override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromHotbar")
+ override val description = buildText { literal("OffHand") }
- override fun deposit(selection: StackSelection): Task<*> {
- val handler = mc.player?.currentScreenHandler ?: return emptyTask("NoScreenHandler")
- return deposit(handler, selection)
- }
+ override fun deposit(selection: StackSelection) = MainHandContainer.HandDeposit(selection, Hand.OFF_HAND)
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt
new file mode 100644
index 000000000..52cc0da78
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2024 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.material.StackSelection
+import com.lambda.task.Task
+import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock
+import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit
+import com.lambda.interaction.material.transfer.SlotTransfer.Companion.withdraw
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.task.tasks.OpenContainer
+import com.lambda.task.tasks.PlaceContainer
+import com.lambda.util.text.buildText
+import com.lambda.util.text.highlighted
+import com.lambda.util.text.literal
+import net.minecraft.item.ItemStack
+
+data class ShulkerBoxContainer(
+ override var stacks: List,
+ val containedIn: MaterialContainer,
+ val shulkerStack: ItemStack,
+) : MaterialContainer(Rank.SHULKER_BOX) {
+ override val description =
+ buildText {
+ highlighted(shulkerStack.name.string)
+ literal(" in ")
+ highlighted(containedIn.name)
+ literal(" in slot ")
+ highlighted("$slotInContainer")
+ }
+
+ private val slotInContainer: Int get() = containedIn.stacks.indexOf(shulkerStack)
+
+ class ShulkerWithdraw(
+ private val selection: StackSelection,
+ private val shulkerStack: ItemStack
+ ) : Task() {
+ override val name = "Withdraw $selection from ${shulkerStack.name.string}"
+
+ override fun SafeContext.onStart() {
+ PlaceContainer(shulkerStack).then { placePos ->
+ OpenContainer(placePos).then { screen ->
+ withdraw(screen, selection).then {
+ breakAndCollectBlock(placePos).finally {
+ success()
+ }
+ }
+ }
+ }.execute(this@ShulkerWithdraw)
+ }
+ }
+
+ override fun withdraw(selection: StackSelection) = ShulkerWithdraw(selection, shulkerStack)
+
+ class ShulkerDeposit(
+ private val selection: StackSelection,
+ private val shulkerStack: ItemStack
+ ) : Task() {
+ override val name = "Deposit $selection into ${shulkerStack.name.string}"
+
+ override fun SafeContext.onStart() {
+ PlaceContainer(shulkerStack).then { placePos ->
+ OpenContainer(placePos).then { screen ->
+ deposit(screen, selection).then {
+ breakAndCollectBlock(placePos).finally {
+ success()
+ }
+ }
+ }
+ }.execute(this@ShulkerDeposit)
+ }
+ }
+
+ override fun deposit(selection: StackSelection) = ShulkerDeposit(selection, shulkerStack)
+}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt
similarity index 68%
rename from common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt
rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt
index fd078fae2..647f2e7a5 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt
@@ -15,12 +15,14 @@
* along with this program. If not, see .
*/
-package com.lambda.interaction.material.container
+package com.lambda.interaction.material.container.containers
-import com.lambda.interaction.material.MaterialContainer
import com.lambda.interaction.material.StackSelection
-import com.lambda.task.Task
+import com.lambda.interaction.material.container.MaterialContainer
import com.lambda.util.math.VecUtils.blockPos
+import com.lambda.util.text.buildText
+import com.lambda.util.text.highlighted
+import com.lambda.util.text.literal
import net.minecraft.item.ItemStack
import net.minecraft.util.math.Box
@@ -31,18 +33,14 @@ data class StashContainer(
override var stacks: List
get() = chests.flatMap { it.stacks }
set(_) {}
- override val name = "Stash at ${pos.center.blockPos.toShortString()}"
- override fun withdraw(selection: StackSelection): Task<*> {
- TODO("Not yet implemented")
+ override val description = buildText {
+ literal("Stash at ")
+ highlighted(pos.center.blockPos.toShortString())
}
- override fun deposit(selection: StackSelection): Task<*> {
- TODO("Not yet implemented")
- }
-
- override fun available(selection: StackSelection): Int =
+ override fun materialAvailable(selection: StackSelection): Int =
chests.sumOf {
- it.available(selection)
+ it.materialAvailable(selection)
}
}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt
new file mode 100644
index 000000000..cffaed390
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt
@@ -0,0 +1,82 @@
+package com.lambda.interaction.material.transfer
+
+import com.lambda.interaction.material.StackSelection
+import com.lambda.util.item.ItemStackUtils.equal
+import net.minecraft.item.ItemStack
+import net.minecraft.screen.slot.Slot
+
+/**
+ * A class representing changes in an inventory's state over time. It acts as a tracker for
+ * detecting, storing, and merging differences between the original and updated states of
+ * inventory slots. This class extends a map-like structure, where the key is the slot index
+ * and the value is a list of pairs representing the original and updated states of an inventory slot.
+ *
+ * Example:
+ * ```
+ * #0: 64 obsidian -> 0 air, 0 air -> 64 obsidian
+ * #36: 12 observer -> 11 observer
+ * ```
+ * - Where `#0` is the slot id, first it was emptied and then got `64 obsidian` again
+ * - Where `#36` is the slot id, that was reduced by `1 observer`
+ *
+ * @property slots A list of `Slot` objects representing the current inventory slots being tracked.
+ * Defaults to an empty list.
+ */
+class InventoryChanges(
+ private var slots: List,
+) : MutableMap>> by HashMap() {
+ private val originalStacks = slots.map { it.stack.copy() }
+
+ /**
+ * Detect and store changes directly in the map.
+ */
+ fun detectChanges() {
+ require(slots.isNotEmpty()) { "Cannot detect changes on an empty slots list" }
+ slots.forEachIndexed { index, slot ->
+ val originalStack = originalStacks[index]
+ val updatedStack = slot.stack
+ if (!originalStack.equal(updatedStack)) {
+ getOrPut(index) { mutableListOf() }.add(originalStack to updatedStack.copy())
+ }
+ }
+ }
+
+ /**
+ * Create a new `InventoryChanges` object that merges this changes object with another one.
+ *
+ * @param other Another `InventoryChanges` instance to merge with.
+ * @return A new `InventoryChanges` instance containing merged entries.
+ */
+ infix fun merge(other: InventoryChanges) {
+ require(slots.isNotEmpty()) { "Cannot merge changes to an empty slots list" }
+ other.forEach { (key, value) ->
+ getOrPut(key) { mutableListOf() }.addAll(value)
+ }
+ }
+
+ /**
+ * Evaluates if the current inventory changes fulfill the given selection requirement.
+ *
+ * @param to A list of `Slot` objects to evaluate for the selection criteria.
+ * @param selection A `StackSelection` object that specifies the selection criteria, including the
+ * target items and their required count.
+ * @return `true` if the total count of matching items across the filtered slots meets or exceeds
+ * the required count specified in the `StackSelection`; `false` otherwise.
+ */
+ fun fulfillsSelection(to: List, selection: StackSelection): Boolean {
+ require(slots.isNotEmpty()) { "Cannot evaluate selection on an empty slots list" }
+ val targetSlots = selection.filterSlots(to).map { it.id }
+ return filter { it.key in targetSlots }.entries.sumOf { (_, changes) ->
+ changes.lastOrNull()?.second?.count ?: 0
+ } >= selection.count
+ }
+
+ override fun toString() =
+ if (entries.isEmpty()) {
+ "No changes detected"
+ } else {
+ entries.joinToString("\n") { key ->
+ "#${key.key}: ${key.value.joinToString { "${it.first} -> ${it.second}" }}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt
new file mode 100644
index 000000000..b656214bd
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 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.transfer
+
+import com.lambda.context.SafeContext
+import com.lambda.task.Task
+import com.lambda.threading.runSafe
+
+abstract class InventoryTransaction : Task() {
+ private lateinit var changes: InventoryChanges
+
+ override fun SafeContext.onStart() {
+ changes = InventoryChanges(player.currentScreenHandler.slots)
+ }
+
+ fun finish() {
+ runSafe {
+ changes.detectChanges()
+ success(changes)
+ } ?: failure("Failed to finish transaction")
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt
new file mode 100644
index 000000000..f19cd1c8f
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 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.transfer
+
+import com.lambda.Lambda.LOG
+import com.lambda.config.groups.InventoryConfig
+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
+import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer
+import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.task.Task
+import com.lambda.util.extension.containerSlots
+import com.lambda.util.extension.inventorySlots
+import net.minecraft.screen.ScreenHandler
+import net.minecraft.screen.slot.Slot
+
+class SlotTransfer @Ta5kBuilder constructor(
+ val screen: ScreenHandler,
+ private val selection: StackSelection,
+ val from: List,
+ val to: List,
+ private val closeScreen: Boolean = true,
+ private val settings: InventoryConfig = TaskFlowModule.inventory
+) : Task() {
+ private var selectedFrom = selection.filterSlots(from)
+ private var selectedTo = to.filter { it.stack.isEmpty } // + to.filter { it.stack.item.block in TaskFlowModule.disposables }
+ override val name: String
+ get() = "Moving $selection from slots [${selectedFrom.joinToString { "${it.id}" }}] to slots [${selectedTo.joinToString { "${it.id}" }}] in ${screen::class.simpleName}"
+
+ private var delay = 0
+ private lateinit var changes: InventoryChanges
+
+ override fun SafeContext.onStart() {
+ changes = InventoryChanges(player.currentScreenHandler.slots)
+ }
+
+ init {
+ listen {
+ val current = player.currentScreenHandler
+ if (current != screen) {
+ failure("Screen has changed. Expected ${screen::class.simpleName} (revision ${screen.revision}, got ${current::class.simpleName} (revision ${current.revision})")
+ return@listen
+ }
+
+ if (changes.fulfillsSelection(to, selection)) {
+ if (closeScreen) player.closeHandledScreen()
+ success()
+ return@listen
+ }
+
+ if (--delay >= 0) return@listen
+ delay = settings.actionTimout
+
+ selectedFrom = selection.filterSlots(from)
+ selectedTo = to.filter { it.stack.isEmpty } // + to.filter { it.stack.item.block in TaskFlowModule.disposables }
+
+ val nextFrom = selectedFrom.firstOrNull() ?: return@listen
+ val nextTo = selectedTo.firstOrNull() ?: return@listen
+
+ LOG.info("Changes so far:\n$changes")
+
+ transfer {
+ moveSlot(nextFrom.id, nextTo.id)
+ }.finally { change ->
+ changes merge change
+ }.execute(this@SlotTransfer)
+
+// if (transfer.fulfillsSelection(selection)) {
+// info("Transfer complete")
+//// success()
+//// if (closeScreen) player.closeHandledScreen()
+// return@listen
+// }
+ }
+ }
+
+ companion object {
+ @Ta5kBuilder
+ fun moveItems(
+ screen: ScreenHandler,
+ selection: StackSelection,
+ from: List,
+ to: List,
+ closeScreen: Boolean = true
+ ) = SlotTransfer(screen, selection, from, to, closeScreen)
+
+ @Ta5kBuilder
+ fun withdraw(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) =
+ moveItems(screen, selection, screen.containerSlots, screen.inventorySlots, closeScreen)
+
+ @Ta5kBuilder
+ fun deposit(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) =
+ moveItems(screen, selection, screen.inventorySlots, screen.containerSlots, closeScreen)
+ }
+}
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt
new file mode 100644
index 000000000..048cb2291
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2024 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.transfer
+
+import com.lambda.context.SafeContext
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.transfer.transaction.*
+import com.lambda.task.Task
+import net.minecraft.screen.slot.SlotActionType
+
+class TransactionExecutor @Ta5kBuilder constructor(
+ private val transactions: MutableList = mutableListOf()
+) : Task() {
+ override val name: String get() = "Execution of ${transactions.size} transactions left"
+
+ private lateinit var changes: InventoryChanges
+
+ override fun SafeContext.onStart() {
+ changes = InventoryChanges(player.currentScreenHandler.slots)
+ }
+
+ init {
+ listen {
+ if (transactions.isEmpty()) {
+ success(changes)
+ return@listen
+ }
+
+ transactions.removeFirstOrNull()?.finally { change ->
+ changes merge change
+ }?.execute(this@TransactionExecutor)
+ }
+ }
+
+ @DslMarker
+ annotation class InvTransfer
+
+ @InvTransfer
+ fun click(slotId: Int, button: Int, actionType: SlotActionType) {
+ transactions.add(ClickSlotTransaction(slotId, button, actionType))
+ }
+
+ @InvTransfer
+ fun pickFromInventory(slotId: Int) {
+ transactions.add(PickFromInventoryTransaction(slotId))
+ }
+
+ @InvTransfer
+ fun dropItemInHand(entireStack: Boolean = true) {
+ transactions.add(DropItemInHandTransaction(entireStack))
+ }
+
+ @InvTransfer
+ fun swapHands() {
+ transactions.add(SwapHandsTransaction())
+ }
+
+ @InvTransfer
+ fun swapToHotbarSlot(slotId: Int) {
+ transactions.add(SwapHotbarSlotTransaction(slotId))
+ }
+
+ @InvTransfer
+ fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP)
+
+ // Quick move action (Shift-click)
+ @InvTransfer
+ fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE)
+
+ @InvTransfer
+ fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP)
+
+ // Clone action (Creative mode)
+ @InvTransfer
+ fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE)
+
+ // Throw stack or single item
+ @InvTransfer
+ fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW)
+ @InvTransfer
+ fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW)
+
+ // Quick craft action
+ @InvTransfer
+ fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT)
+ @InvTransfer
+ fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT)
+ @InvTransfer
+ fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT)
+
+ // Pickup all items (double-click)
+ @InvTransfer
+ fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL)
+
+ // Helper function: Move items from one slot to another
+ @InvTransfer
+ fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) {
+ pickup(fromSlotId, button)
+ pickup(toSlotId, button)
+ }
+
+ // Helper function: Split a stack into two
+ @InvTransfer
+ 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
+ @InvTransfer
+ fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) {
+ pickup(sourceSlotId, 0)
+ pickup(targetSlotId, 0)
+ }
+
+ companion object {
+ @InvTransfer
+ fun transfer(block: TransactionExecutor.() -> Unit) =
+ TransactionExecutor().apply {
+ block(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt
index 7e0f1cea4..a64f961e9 100644
--- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt
@@ -18,28 +18,30 @@
package com.lambda.interaction.material.transfer
import com.lambda.context.SafeContext
-import com.lambda.interaction.material.MaterialContainer
+import com.lambda.interaction.material.container.MaterialContainer
import com.lambda.interaction.material.StackSelection
import com.lambda.task.Task
abstract class TransferResult : Task() {
- data class Transfer(
+ data class ContainerTransfer(
val selection: StackSelection,
val from: MaterialContainer,
val to: MaterialContainer
) : TransferResult() {
+ override val name = "Container Transfer of [$selection] from [${from.name}] to [${to.name}]"
+
override fun SafeContext.onStart() {
- from.withdraw(selection).thenRun(this@Transfer) { _, _ ->
- to.deposit(selection).onSuccess { _, _ ->
- success(Unit)
+ from.withdraw(selection).then {
+ to.deposit(selection).finally {
+ success()
}
- }.start(this@Transfer)
+ }.execute(this@ContainerTransfer)
}
-
- override fun toString() = "Transfer of [$selection] from [${from.name}] to [${to.name}]"
}
data object NoSpace : TransferResult() {
+ override val name = "No space left in the target container"
+
// ToDo: Needs inventory space resolver. compressing or disposing
override fun SafeContext.onStart() {
failure("No space left in the target container")
@@ -47,6 +49,8 @@ abstract class TransferResult : Task() {
}
data class MissingItems(val missing: Int) : TransferResult() {
+ override val name = "Missing $missing items"
+
// ToDo: Find other satisfying permutations
override fun SafeContext.onStart() {
failure("Missing $missing items")
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt
new file mode 100644
index 000000000..ac8ed4c62
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.material.transfer
+
+class TransferSelection {
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt
new file mode 100644
index 000000000..5c4ff8af8
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 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.transfer.transaction
+
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.transfer.InventoryTransaction
+import com.lambda.util.player.SlotUtils.clickSlot
+import net.minecraft.screen.slot.SlotActionType
+
+class ClickSlotTransaction @Ta5kBuilder constructor(
+ private val slotId: Int,
+ private val button: Int,
+ private val actionType: SlotActionType
+) : InventoryTransaction() {
+ override val name: String get() = "Click slot #$slotId with action $actionType and button $button"
+
+ init {
+ listen {
+ clickSlot(slotId, button, actionType)
+ }
+
+ listen {
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt
new file mode 100644
index 000000000..e480c6748
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 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.transfer.transaction
+
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.transfer.InventoryTransaction
+import net.minecraft.util.Hand
+
+class DropItemInHandTransaction @Ta5kBuilder constructor(
+ private val entireStack: Boolean = false
+) : InventoryTransaction() {
+ override val name: String get() = "Dropping ${if (entireStack) "stack" else "item"} in hand"
+
+ init {
+ listen {
+ if (!player.isSpectator && player.dropSelectedItem(entireStack)) {
+ player.swingHand(Hand.MAIN_HAND)
+ }
+ }
+
+ listen {
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt
new file mode 100644
index 000000000..9197741fe
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 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.transfer.transaction
+
+import com.lambda.event.events.InventoryEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.transfer.InventoryTransaction
+
+class PickFromInventoryTransaction @Ta5kBuilder constructor(
+ val slot: Int,
+) : InventoryTransaction() {
+ override val name: String get() = "Picking from slot #$slot"
+ private var confirming = false
+
+ init {
+ listen {
+ if (confirming) return@listen
+
+ interaction.pickFromInventory(slot)
+ confirming = true
+ }
+
+ listen {
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt
new file mode 100644
index 000000000..007f2d1d3
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.interaction.material.transfer.transaction
+
+import com.lambda.interaction.material.transfer.InventoryTransaction
+
+class QuickCraftTransaction @Ta5kBuilder constructor(
+ private val slots: List,
+ private val mode: Mode = Mode.SINGLE
+) : InventoryTransaction() {
+ override val name: String get() = "Drag and drop ${slots.size} slots"
+
+ enum class Mode {
+ SINGLE, SPLIT
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt
new file mode 100644
index 000000000..5c20afb80
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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.transfer.transaction
+
+import com.lambda.event.events.InventoryEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.transfer.InventoryTransaction
+import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+
+class SwapHandsTransaction @Ta5kBuilder constructor() : InventoryTransaction() {
+ override val name: String get() = "Swap Hand Stacks"
+
+ init {
+ listen {
+ if (player.isSpectator) return@listen
+ connection.sendPacket(
+ PlayerActionC2SPacket(
+ PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND,
+ BlockPos.ORIGIN,
+ Direction.DOWN
+ )
+ )
+ }
+
+ listen {
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt
new file mode 100644
index 000000000..5b302af17
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 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.transfer.transaction
+
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.transfer.InventoryTransaction
+
+class SwapHotbarSlotTransaction @Ta5kBuilder constructor(
+ val slot: Int
+) : InventoryTransaction() {
+ override val name: String get() = "Selecting slot #$slot"
+
+ init {
+ listen {
+ player.inventory.selectedSlot = slot
+ }
+
+ listen {
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt
index 520001059..39ed9948b 100644
--- a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt
@@ -72,19 +72,25 @@ data class Rotation(val yaw: Double, val pitch: Double) {
box.raycast(eyeVec, eyeVec + vector * reach).orElse(null)
}
- val Direction.yaw: Float
- get() = when (this) {
- Direction.NORTH -> -180.0f
- Direction.SOUTH -> 0.0f
- Direction.EAST -> -90.0f
- Direction.WEST -> 90.0f
- else -> 0.0f
- }
-
companion object {
+ val Direction.yaw: Float
+ get() = when (this) {
+ Direction.NORTH -> -180.0f
+ Direction.SOUTH -> 0.0f
+ Direction.EAST -> -90.0f
+ Direction.WEST -> 90.0f
+ else -> 0.0f
+ }
+
val ZERO = Rotation(0.0, 0.0)
val DOWN = Rotation(0.0, 90.0)
- val Entity.rotation get() = Rotation(yaw, pitch)
+ val UP = Rotation(0.0, -90.0)
+ val Direction.rotation get() = Rotation(yaw.toDouble(), 0.0)
+ var Entity.rotation get() = Rotation(yaw, pitch)
+ set(value) {
+ yaw = value.yawF
+ pitch = value.pitchF
+ }
fun wrap(deg: Double) = MathHelper.wrapDegrees(deg)
diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt
index 409491cfd..84dd5fbfd 100644
--- a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt
@@ -17,13 +17,13 @@
package com.lambda.interaction.rotation
-import com.lambda.config.groups.IRotationConfig
+import com.lambda.config.groups.RotationConfig
import com.lambda.util.world.raycast.RayCastUtils.orMiss
import net.minecraft.util.hit.HitResult
data class RotationContext(
val rotation: Rotation,
- val config: IRotationConfig,
+ val config: RotationConfig,
val hitResult: HitResult? = null,
val verify: HitResult.() -> Boolean = { true },
) {
diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt
index 4d1d2d980..9d1b14339 100644
--- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt
+++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt
@@ -17,17 +17,18 @@
package com.lambda.interaction.visibilty
-import com.lambda.config.groups.IRotationConfig
+import com.lambda.config.groups.RotationConfig
import com.lambda.config.groups.InteractionConfig
import com.lambda.context.SafeContext
import com.lambda.interaction.RotationManager
-import com.lambda.interaction.rotation.Rotation.Companion.dist
+import com.lambda.interaction.construction.verify.ScanMode
+import com.lambda.interaction.construction.verify.SurfaceScan
import com.lambda.interaction.rotation.Rotation.Companion.rotationTo
import com.lambda.interaction.rotation.RotationContext
-import com.lambda.module.modules.client.TaskFlow
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.util.BlockUtils.blockState
-import com.lambda.util.math.VecUtils.distSq
import com.lambda.util.extension.component6
+import com.lambda.util.math.VecUtils.distSq
import com.lambda.util.world.raycast.RayCastUtils.blockResult
import com.lambda.util.world.raycast.RayCastUtils.entityResult
import net.minecraft.entity.Entity
@@ -54,7 +55,7 @@ object VisibilityChecker {
* @return A [RotationContext] if a valid rotation was found; otherwise, null.
*/
fun SafeContext.lookAtEntity(
- rotationConfig: IRotationConfig,
+ rotationConfig: RotationConfig,
interactionConfig: InteractionConfig,
entity: Entity
) = findRotation(listOf(entity.boundingBox), rotationConfig, interactionConfig) {
@@ -73,9 +74,9 @@ object VisibilityChecker {
*/
fun SafeContext.lookAtBlock(
blockPos: BlockPos,
- rotationConfig: IRotationConfig = TaskFlow.rotation,
- interactionConfig: InteractionConfig = TaskFlow.interact,
- sides: Set = emptySet()
+ rotationConfig: RotationConfig = TaskFlowModule.rotation,
+ interactionConfig: InteractionConfig = TaskFlowModule.interact,
+ sides: Set = Direction.entries.toSet()
): RotationContext? {
val state = blockPos.blockState(world)
val voxelShape = state.getOutlineShape(world, blockPos)
@@ -99,9 +100,9 @@ object VisibilityChecker {
*/
fun SafeContext.findRotation(
boxes: List,
- rotationConfig: IRotationConfig,
+ rotationConfig: RotationConfig,
interact: InteractionConfig,
- sides: Set = emptySet(),
+ sides: Set = Direction.entries.toSet(),
reach: Double = interact.reach,
eye: Vec3d = player.getCameraPosVec(1f),
verify: HitResult.() -> Boolean,
@@ -117,13 +118,15 @@ object VisibilityChecker {
val reachSq = reach.pow(2)
boxes.forEach { box ->
- scanVisibleSurfaces(eye, box, sides, interact.resolution) { _, vec ->
- if (eye distSq vec > reachSq) return@scanVisibleSurfaces
+ val visible = visibleSides(box, eye, interact)
+
+ scanSurfaces(box, visible.intersect(sides), interact.resolution) { _, vec ->
+ if (eye distSq vec > reachSq) return@scanSurfaces
val newRotation = eye.rotationTo(vec)
- val cast = newRotation.rayCast(reach, eye) ?: return@scanVisibleSurfaces
- if (!cast.verify()) return@scanVisibleSurfaces
+ val cast = newRotation.rayCast(reach, eye) ?: return@scanSurfaces
+ if (!cast.verify()) return@scanSurfaces
validHits[vec] = cast
}
@@ -145,48 +148,92 @@ object VisibilityChecker {
}
/**
- * Scans the visible surfaces of a given box, identifying points on each surface within a defined resolution.
- * The surface is subdivided to hits the corners of the pixels
+ * Scans the surfaces of a given box, optionally excluding specific sides,
+ * and executes a callback for each point calculated based on the scanning parameters.
*
- * @param eyes The player's eye position.
- * @param box The bounding box of the target.
- * @param sides Set of block sides to consider for visibility.
- * @param resolution The number of points to sample along each axis of the box.
- * @param check A lambda to check each visible point for a hit.
+ * @param box The 3D box whose surfaces will be scanned.
+ * @param excludedSides A set of directions representing the sides of the box to exclude from the scan (default is an empty set).
+ * @param resolution The number of intervals into which each dimension is divided for scanning (default is 5).
+ * @param scan Configuration specifying the axis and mode of the scan (default is `SurfaceScan.DEFAULT`).
+ * @param check A callback function that performs an action for each surface point, receiving the direction of the surface and the current 3D vector.
*/
- inline fun scanVisibleSurfaces(
- eyes: Vec3d,
+ inline fun scanSurfaces(
box: Box,
- sides: Set = emptySet(),
- resolution: Int = 30,
+ excludedSides: Set = emptySet(),
+ resolution: Int = 5,
+ scan: SurfaceScan = SurfaceScan.DEFAULT,
check: (Direction, Vec3d) -> Unit,
) {
- box.getVisibleSurfaces(eyes)
- .forEach { side ->
- if (sides.isNotEmpty() && side !in sides) return@forEach
- val (minX, minY, minZ, maxX, maxY, maxZ) = box.shrink(0.01, 0.01, 0.01).bounds(side)
- val stepX = (maxX - minX) / resolution
- val stepY = (maxY - minY) / resolution
- val stepZ = (maxZ - minZ) / resolution
- (0..resolution).forEach { i ->
- val x = if (stepX != 0.0) minX + stepX * i else minX
- (0..resolution).forEach { j ->
- val y = if (stepY != 0.0) minY + stepY * j else minY
- val z = if (stepZ != 0.0) minZ + stepZ * ((if (stepX != 0.0) j else i)) else minZ
- check(side, Vec3d(x, y, z))
- }
+ excludedSides.forEach { side ->
+ if (excludedSides.isNotEmpty() && side !in excludedSides) return@forEach
+ val (minX, minY, minZ, maxX, maxY, maxZ) = box.shrink(0.01, 0.01, 0.01).bounds(side)
+ val stepX = (maxX - minX) / resolution
+ val stepY = (maxY - minY) / resolution
+ val stepZ = (maxZ - minZ) / resolution
+
+ // Determine the bounds to scan based on the axis and mode
+ val (startX, endX) = if (scan.axis == Direction.Axis.X && stepX != 0.0) {
+ val centerX = (minX + maxX) / 2
+ when (scan.mode) {
+ ScanMode.GREATER_HALF -> centerX + 0.01 to maxX
+ ScanMode.LESSER_HALF -> minX to centerX - 0.01
+ ScanMode.FULL -> minX to maxX
+ }
+ } else minX to maxX
+
+ val (startY, endY) = if (scan.axis == Direction.Axis.Y && stepY != 0.0) {
+ val centerY = (minY + maxY) / 2
+ when (scan.mode) {
+ ScanMode.GREATER_HALF -> centerY + 0.01 to maxY
+ ScanMode.LESSER_HALF -> minY to centerY - 0.01
+ ScanMode.FULL -> minY to maxY
+ }
+ } else minY to maxY
+
+ val (startZ, endZ) = if (scan.axis == Direction.Axis.Z && stepZ != 0.0) {
+ val centerZ = (minZ + maxZ) / 2
+ when (scan.mode) {
+ ScanMode.GREATER_HALF -> centerZ + 0.01 to maxZ
+ ScanMode.LESSER_HALF -> minZ to centerZ - 0.01
+ ScanMode.FULL -> minZ to maxZ
+ }
+ } else minZ to maxZ
+
+ (0..resolution).forEach outer@ { i ->
+ val x = if (stepX != 0.0) startX + stepX * i else startX
+ if (x > endX) return@outer
+ (0..resolution).forEach inner@ { j ->
+ val y = if (stepY != 0.0) startY + stepY * j else startY
+ if (y > endY) return@inner
+ val z = if (stepZ != 0.0) startZ + stepZ * ((if (stepX != 0.0) j else i)) else startZ
+ if (z > endZ) return@inner
+ check(side, Vec3d(x, y, z))
}
}
+ }
}
/**
- * Computes the optimum point by averaging the vectors in a set of points.
- */
+ * Determines the approximate central point (optimum) of a set of 3D vectors.
+ */
val Set.optimum: Vec3d?
get() = reduceOrNull { acc, vec3d ->
acc.add(vec3d)
}?.multiply(1.0 / size.toDouble())
+ /**
+ * Determines the sides of a box that are visible from a given position, based on interaction settings.
+ *
+ * @param box The box whose visible sides are to be determined.
+ * @param eye The position (e.g., the player's eyes) to determine visibility from.
+ * @param interactionSettings The settings that define how visibility checks are handled.
+ * @return A set of directions corresponding to the visible sides of the box.
+ */
+ fun visibleSides(box: Box, eye: Vec3d, interactionSettings: InteractionConfig) =
+ if (interactionSettings.visibilityCheck) {
+ box.getVisibleSurfaces(eye)
+ } else Direction.entries.toSet()
+
/**
* Gets the bounding coordinates of a box's side, specifying min and max values for each axis.
*
diff --git a/common/src/main/kotlin/com/lambda/module/HudModule.kt b/common/src/main/kotlin/com/lambda/module/HudModule.kt
index c9c68db0d..d51202658 100644
--- a/common/src/main/kotlin/com/lambda/module/HudModule.kt
+++ b/common/src/main/kotlin/com/lambda/module/HudModule.kt
@@ -87,7 +87,7 @@ abstract class HudModule(
rectHandler.screenSize = event.screenSize
renderCallables.forEach { function ->
- function.invoke(renderer)
+ function(renderer)
}
renderer.render()
diff --git a/common/src/main/kotlin/com/lambda/module/hud/TaskFlow.kt b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt
similarity index 73%
rename from common/src/main/kotlin/com/lambda/module/hud/TaskFlow.kt
rename to common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt
index 62416bf6e..1e741fea7 100644
--- a/common/src/main/kotlin/com/lambda/module/hud/TaskFlow.kt
+++ b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt
@@ -19,18 +19,21 @@ package com.lambda.module.hud
import com.lambda.module.HudModule
import com.lambda.module.tag.ModuleTag
+import com.lambda.task.TaskFlow
import com.lambda.util.math.Vec2d
-object TaskFlow : HudModule(
+object TaskFlowHUD : HudModule(
name = "TaskFlowHud",
defaultTags = setOf(ModuleTag.CLIENT),
) {
- override val width = 50.0
- override val height = 50.0
+ override val width = 200.0
+ override val height = 200.0
init {
onRender {
- font.build("TaskFlow", Vec2d.ZERO)
+ TaskFlow.toString().lines().forEachIndexed { index, line ->
+ font.build(line, Vec2d(position.x, position.y + index * (font.getHeight(font.scaleMultiplier) + 2.0)))
+ }
}
}
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt
index 18403baf5..64e1424c2 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt
@@ -18,7 +18,9 @@
package com.lambda.module.modules.client
import com.lambda.module.Module
+import com.lambda.module.modules.client.GuiSettings.Page
import com.lambda.module.tag.ModuleTag
+import java.awt.Color
object RenderSettings : Module(
name = "RenderSettings",
@@ -34,6 +36,7 @@ object RenderSettings : Module(
val gap by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font }
val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font }
private val lodBiasSetting by setting("Smoothing", 0.0, -10.0..10.0, 0.5) { page == Page.Font }
+ val highlightColor by setting("Text Highlight Color", Color(100, 100, 100, 150), visibility = { page == Page.Font })
// ESP
val uploadsPerTick by setting("Uploads", 16, 1..256, 1, unit = " chunk/tick") { page == Page.ESP }
diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt
similarity index 55%
rename from common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt
rename to common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt
index ac150e905..9106eb134 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt
@@ -18,35 +18,44 @@
package com.lambda.module.modules.client
import com.lambda.config.groups.BuildSettings
+import com.lambda.config.groups.BuildSettings.Page
import com.lambda.config.groups.InteractionSettings
+import com.lambda.config.groups.InventorySettings
import com.lambda.config.groups.RotationSettings
+import com.lambda.event.events.RenderEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.construction.result.Drawable
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.util.BlockUtils.allSigns
import com.lambda.util.item.ItemUtils
+import net.minecraft.state.property.Properties
-object TaskFlow : Module(
+object TaskFlowModule : Module(
name = "TaskFlow",
description = "Settings for task automation",
defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.AUTOMATION)
) {
enum class Page {
- BUILD, ROTATION, INTERACTION, TASKS
+ BUILD, ROTATION, INTERACTION, INVENTORY, DEBUG
}
private val page by setting("Page", Page.BUILD)
- val build = BuildSettings(this) {
- page == Page.BUILD
- }
- val rotation = RotationSettings(this) {
- page == Page.ROTATION
- }
- val interact = InteractionSettings(this) {
- page == Page.INTERACTION
- }
- val taskCooldown by setting("Task Cooldown", 0, 0..10000, 10, unit = " ms") {
- page == Page.TASKS
+ val build = BuildSettings(this) { page == Page.BUILD }
+ val rotation = RotationSettings(this) { page == Page.ROTATION }
+ val interact = InteractionSettings(this) { page == Page.INTERACTION }
+ val inventory = InventorySettings(this) { page == Page.INVENTORY }
+
+ val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree") { page == Page.DEBUG }
+
+ @Volatile
+ var drawables = listOf()
+
+ init {
+ listen {
+ drawables.toList().forEach { res ->
+ with(res) { buildRenderer() }
+ }
+ }
}
- val disposables by setting("Disposables", ItemUtils.defaultDisposables)
- val ignoredBlocks by setting("Ignored Blocks", allSigns)
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
index b09d0948f..95311b4ab 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
@@ -26,12 +26,13 @@ import com.lambda.event.events.PlayerPacketEvent
import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.interaction.RotationManager
-import com.lambda.interaction.RotationManager.requestRotation
+import com.lambda.interaction.RotationManager.rotate
import com.lambda.interaction.rotation.Rotation
import com.lambda.interaction.rotation.Rotation.Companion.dist
import com.lambda.interaction.rotation.Rotation.Companion.rotationTo
import com.lambda.interaction.rotation.RotationContext
-import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces
+import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces
+import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.threading.runConcurrent
@@ -113,15 +114,15 @@ object KillAura : Module(
}
init {
- requestRotation(
- onUpdate = {
- if (!rotate) return@requestRotation null
+ rotate {
+ onUpdate {
+ if (!rotate) return@onUpdate null
target?.let { target ->
buildRotation(target)
}
}
- )
+ }
listen(Int.MIN_VALUE) { event ->
prevY = lastY
@@ -243,13 +244,15 @@ object KillAura : Module(
// Get visible point set
val validHits = mutableMapOf()
- scanVisibleSurfaces(eye, box, resolution = interactionSettings.resolution) { _, vec ->
- if (eye distSq vec > reachSq) return@scanVisibleSurfaces
+ val sides = visibleSides(box, eye, interactionSettings)
+
+ scanSurfaces(box, sides, resolution = interactionSettings.resolution) { _, vec ->
+ if (eye distSq vec > reachSq) return@scanSurfaces
val newRotation = eye.rotationTo(vec)
- val cast = newRotation.rayCast(reach, eye) ?: return@scanVisibleSurfaces
- if (cast.entityResult?.entity != target) return@scanVisibleSurfaces
+ val cast = newRotation.rayCast(reach, eye) ?: return@scanSurfaces
+ if (cast.entityResult?.entity != target) return@scanSurfaces
validHits[vec] = newRotation
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt
index c541a6fdb..719724949 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt
@@ -22,6 +22,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.interaction.material.StackSelection.Companion.select
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
+import com.lambda.task.TaskFlow.run
import com.lambda.task.tasks.AcquireMaterial.Companion.acquire
import net.minecraft.item.Items
@@ -38,7 +39,7 @@ object ContainerTest : Module(
onEnable {
acquire {
Items.OBSIDIAN.select()
- }.start(null)
+ }.run()
}
}
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt
index 034e6380a..18e74be3a 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt
@@ -18,8 +18,8 @@
package com.lambda.module.modules.debug
import com.lambda.Lambda.LOG
+import com.lambda.event.events.InventoryEvent
import com.lambda.event.events.PacketEvent
-import com.lambda.event.events.ScreenHandlerEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
@@ -35,24 +35,34 @@ object InventoryDebug : Module(
defaultTags = setOf(ModuleTag.DEBUG)
) {
init {
- listen {
- info("Opened screen handler: ${it.screenHandler::class.simpleName}")
+ listen { event ->
+ info("Opened screen handler: ${event.screenHandler::class.simpleName}")
+
+ LOG.info("\n" + event.screenHandler.slots.joinToString("\n") {
+ "${it.inventory::class.simpleName} ${it.index} ${it.x} ${it.y}"
+ })
}
- listen {
+ listen {
info("Closed screen handler: ${it.screenHandler::class.simpleName}")
}
- listen {
+ listen {
info("Updated screen handler: ${it.revision}, ${it.stacks}, ${it.cursorStack}")
}
listen {
- when (val packet = it.packet) {
- is UpdateSelectedSlotS2CPacket, is InventoryS2CPacket -> {
- this@InventoryDebug.info(packet.dynamicString())
+ when (it.packet) {
+ is UpdateSelectedSlotS2CPacket,
+ is InventoryS2CPacket
+ -> {
+ LOG.info(it.packet.dynamicString())
}
}
+ when (val packet = it.packet) {
+ is UpdateSelectedSlotS2CPacket -> this@InventoryDebug.info("Updated selected slot: ${packet.slot}")
+ is InventoryS2CPacket -> this@InventoryDebug.info("Received inventory update: syncId: ${packet.syncId} | revision: ${packet.revision} | cursorStack ${packet.cursorStack}")
+ }
}
listen {
@@ -64,7 +74,7 @@ object InventoryDebug : Module(
is CreativeInventoryActionC2SPacket,
is PickFromInventoryC2SPacket,
is UpdateSelectedSlotC2SPacket,
- -> LOG.info(it.packet.dynamicString())
+ -> LOG.info(System.currentTimeMillis().toString() + " " + it.packet.dynamicString())
}
}
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
index 20b7ab900..9982d0575 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
@@ -17,12 +17,12 @@
package com.lambda.module.modules.movement
-import com.lambda.config.groups.IRotationConfig
+import com.lambda.config.groups.RotationConfig
import com.lambda.context.SafeContext
import com.lambda.event.events.ClientEvent
import com.lambda.event.events.MovementEvent
import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.RotationManager.requestRotation
+import com.lambda.interaction.RotationManager.rotate
import com.lambda.interaction.rotation.Rotation
import com.lambda.interaction.rotation.RotationContext
import com.lambda.interaction.rotation.RotationMode
@@ -71,7 +71,7 @@ object Speed : Module(
private val ncpTimerBoost by setting("Timer Boost", 1.08, 1.0..1.1, 0.01) { mode == Mode.NCP_STRAFE }
// Grim
- private val rotationConfig = object : IRotationConfig.Instant {
+ private val rotationConfig = object : RotationConfig.Instant {
override val rotationMode = RotationMode.SYNC
}
@@ -134,15 +134,15 @@ object Speed : Module(
}
}
- requestRotation(100, alwaysListen = false,
- onUpdate = { lastContext ->
- if (mode != Mode.GRIM_STRAFE) return@requestRotation null
- if (!shouldWork()) return@requestRotation null
+ rotate(100, alwaysListen = false) {
+ onUpdate { lastContext ->
+ if (mode != Mode.GRIM_STRAFE) return@onUpdate null
+ if (!shouldWork()) return@onUpdate null
var yaw = player.yaw
val input = newMovementInput()
- if (!input.isInputting) return@requestRotation null
+ if (!input.isInputting) return@onUpdate null
run {
if (!diagonal) return@run
@@ -161,8 +161,8 @@ object Speed : Module(
val rotation = Rotation(moveYaw, lastContext?.rotation?.pitch ?: player.pitch.toDouble())
RotationContext(rotation, rotationConfig)
- }, {}
- )
+ }
+ }
onEnable {
reset()
diff --git a/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt b/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt
index 36901a705..12b6f7456 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt
@@ -97,7 +97,7 @@ object PacketLogger : Module(
val fileName = "packet-log-${getTime(fileFormatter)}.txt"
// ToDo: Organize files with FolderRegister.worldBoundDirectory
- file = FolderRegister.packetLogs.resolve(fileName).apply {
+ file = FolderRegister.packetLogs.resolve(fileName).toFile().apply {
if (!parentFile.exists()) {
parentFile.mkdirs()
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt
index fbb08ceaa..adb76c4a3 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt
@@ -18,7 +18,7 @@
package com.lambda.module.modules.player
import com.lambda.Lambda.mc
-import com.lambda.config.groups.IRotationConfig
+import com.lambda.config.groups.RotationConfig
import com.lambda.event.events.*
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.interaction.rotation.Rotation
@@ -56,7 +56,7 @@ object Freecam : Module(
private val reach by setting("Reach", 10.0, 1.0..100.0, 1.0, "Freecam reach distance")
private val rotateToTarget by setting("Rotate to target", true)
- private val rotationConfig = object : IRotationConfig.Instant {
+ private val rotationConfig = object : RotationConfig.Instant {
override val rotationMode = RotationMode.LOCK
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt
index b3e9cbee2..809331367 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt
@@ -17,36 +17,46 @@
package com.lambda.module.modules.player
-import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint
+import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint
import com.lambda.interaction.construction.verify.TargetState
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.task.Task
+import com.lambda.task.TaskFlow.run
import com.lambda.task.tasks.BuildTask.Companion.build
import com.lambda.util.BaritoneUtils
import com.lambda.util.Communication.info
import com.lambda.util.extension.Structure
import com.lambda.util.player.MovementUtils.octant
+import com.lambda.util.extension.moveY
+import com.lambda.util.math.MathUtils.floorToInt
+import com.lambda.util.math.VecUtils.rotateClockwise
import com.lambda.util.world.StructureUtils.generateDirectionalTube
import net.minecraft.block.Blocks
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.EightWayDirection
import net.minecraft.util.math.Vec3i
-import kotlin.math.roundToInt
object HighwayTools : Module(
name = "HighwayTools",
description = "Auto highway builder",
defaultTags = setOf(ModuleTag.PLAYER, ModuleTag.AUTOMATION)
) {
- private val height by setting("Height", 4, 1..10, 1)
+ private val height by setting("Height", 4, 2..10, 1)
private val width by setting("Width", 6, 1..30, 1)
- private val rimHeight by setting("Rim Height", 1, 0..6, 1)
- private val cornerBlock by setting("Corner Block", false, description = "Include corner blocks in the highway")
- private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway (negative for infinite)")
+ private val pavement by setting("Pavement", Material.Block, description = "Material for the pavement")
+ private val rimHeight by setting("Pavement Rim Height", 1, 0..6, 1) { pavement != Material.None }
+ private val cornerBlock by setting("Corner", Corner.None, description = "Include corner blocks in the highway") { pavement != Material.None }
+ private val pavementMaterial by setting("Pavement Material", Blocks.OBSIDIAN, description = "Material to build the highway with") { pavement == Material.Block }
+ private val floor by setting("Floor", Material.None, description = "Material for the floor")
+ private val floorMaterial by setting("Floor Material", Blocks.NETHERRACK, description = "Material to build the floor with") { floor == Material.Block }
+ private val walls by setting("Walls", Material.None, description = "Material for the walls")
+ private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, description = "Material to build the walls with") { walls == Material.Block }
+ private val ceiling by setting("Ceiling", Material.None, description = "Material for the ceiling")
+ private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { ceiling == Material.Block }
+ private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway/tunnel (negative for infinite)")
private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once")
- private val material by setting("Material", Blocks.OBSIDIAN, description = "Material to build the highway with")
private var octant = EightWayDirection.NORTH
private var distanceMoved = 0
@@ -54,6 +64,17 @@ object HighwayTools : Module(
private var currentPos = BlockPos.ORIGIN
private var runningTask: Task<*>? = null
+ enum class Material {
+ None,
+ Solid,
+ Block,
+ }
+
+ enum class Corner {
+ None,
+ Solid,
+ }
+
init {
onEnable {
octant = player.octant
@@ -73,102 +94,138 @@ object HighwayTools : Module(
distanceMoved += sliceSize
var structure: Structure = mutableMapOf()
- val slice = highwaySlice()
+ val slice = generateSlice()
repeat(sliceSize) {
val vec = Vec3i(octant.offsetX, 0, octant.offsetZ)
currentPos = currentPos.add(vec)
structure = structure.plus(slice.map { it.key.add(currentPos) to it.value })
}
- runningTask = structure.toBlueprint().build().onSuccess { _, _ ->
+ runningTask = structure.toBlueprint().build().finally {
if (distanceMoved < distance || distance < 0) {
buildSlice()
} else {
this@HighwayTools.info("Highway built")
disable()
}
- }.onFailure { _, _ ->
- disable()
- }.start(null)
+ }.run()
}
- private fun highwaySlice(): Structure {
+ private fun generateSlice(): Structure {
val structure = mutableMapOf()
- val orthogonal = EightWayDirection.entries[(octant.ordinal + 2).mod(8)]
- val center = (width / 2.0).roundToInt()
+ val orthogonal = octant.rotateClockwise(2)
+ val center = (width / 2.0).floorToInt()
- // Area to clear
+ // Hole
structure += generateDirectionalTube(
orthogonal,
width,
height,
-center,
- -1,
+ 0,
).associateWith { TargetState.Air }
- // Highway
- structure += generateDirectionalTube(
- orthogonal,
- width,
- 1,
- -center,
- -1,
- ).associateWith { TargetState.Block(material) }
+ if (pavement != Material.None) {
+ structure += generateDirectionalTube(
+ orthogonal,
+ width,
+ 1,
+ -center,
+ 0,
+ ).associateWith { target(pavement, pavementMaterial) }
- // Left rim
- structure += generateDirectionalTube(
- orthogonal,
- 1,
- rimHeight,
- -center + width - 1,
- 0,
- ).associateWith { TargetState.Block(material) }
+ // Left rim
+ structure += generateDirectionalTube(
+ orthogonal,
+ 1,
+ rimHeight,
+ -center + width - 1,
+ 1,
+ ).associateWith { target(pavement, pavementMaterial) }
- // Right rim
- structure += generateDirectionalTube(
- orthogonal,
- 1,
- rimHeight,
- -center,
- 0,
- ).associateWith { TargetState.Block(material) }
+ // Right rim
+ structure += generateDirectionalTube(
+ orthogonal,
+ 1,
+ rimHeight,
+ -center,
+ 1,
+ ).associateWith { target(pavement, pavementMaterial) }
+
+ if (cornerBlock == Corner.None) {
+ // Support for the left rim
+ structure += generateDirectionalTube(
+ orthogonal,
+ 1,
+ 1,
+ -center + width - 1,
+ 0,
+ ).associateWith { TargetState.Support(Direction.UP) }
+
+ // Support for the right rim
+ structure += generateDirectionalTube(
+ orthogonal,
+ 1,
+ 1,
+ -center,
+ 0,
+ ).associateWith { TargetState.Support(Direction.UP) }
+ }
+ }
- if (!cornerBlock) {
- structure -= generateDirectionalTube(
+ if (ceiling != Material.None) {
+ structure += generateDirectionalTube(
orthogonal,
+ width,
1,
+ -center,
+ height,
+ ).associateWith { target(ceiling, ceilingMaterial) }
+ }
+
+ if (walls != Material.None) {
+ // Left wall
+ structure += generateDirectionalTube(
+ orthogonal,
1,
- -center + width - 1,
- -1,
- )
+ height,
+ -center + width,
+ 0,
+ ).associateWith { target(walls, wallMaterial) }
- structure -= generateDirectionalTube(
+ // Right wall
+ structure += generateDirectionalTube(
orthogonal,
1,
+ height,
+ -center - 1,
+ 0,
+ ).associateWith { target(walls, wallMaterial) }
+ }
+
+ if (floor != Material.None) {
+ structure += generateDirectionalTube(
+ orthogonal,
+ width,
1,
-center,
-1,
- )
+ ).associateWith { target(floor, floorMaterial) }
}
- // Support for the left corner
- structure += generateDirectionalTube(
- orthogonal,
- 1,
- 1,
- -center + width - 1,
- -1,
- ).associateWith { TargetState.Support(Direction.UP) }
+ val transformed = when {
+ pavement != Material.None -> structure.moveY(-1)
+ else -> structure
+ }
- // Support for the right corner
- structure += generateDirectionalTube(
- orthogonal,
- 1,
- 1,
- -center,
- -1,
- ).associateWith { TargetState.Support(Direction.UP) }
+ return transformed
+ }
- return structure
+ private fun target(target: Material, material: net.minecraft.block.Block): TargetState {
+ return when (target) {
+ Material.Solid -> TargetState.Solid
+ Material.Block -> TargetState.Block(material)
+ else -> throw IllegalStateException("Invalid material")
+ }
}
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
index 609305212..50ec1bd43 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
@@ -18,15 +18,16 @@
package com.lambda.module.modules.player
import com.lambda.context.SafeContext
+import com.lambda.event.events.InventoryEvent
import com.lambda.event.events.PlayerEvent
-import com.lambda.event.events.ScreenHandlerEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.task.Task
+import com.lambda.task.TaskFlow.run
import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock
-import com.lambda.task.tasks.OpenContainer.Companion.openContainer
-import com.lambda.task.tasks.PlaceContainer.Companion.placeContainer
+import com.lambda.task.tasks.OpenContainer
+import com.lambda.task.tasks.PlaceContainer
import com.lambda.util.item.ItemUtils.shulkerBoxes
import com.lambda.util.player.SlotUtils.clickSlot
import com.lambda.util.player.SlotUtils.hotbar
@@ -59,19 +60,19 @@ object InventoryTweaks : Module(
player.closeScreen()
- lastPlace = placeContainer(stack).thenRun(null) { _, placePos ->
+ lastPlace = PlaceContainer(stack).then { placePos ->
placedPos = placePos
- openContainer(placePos).onSuccess { _, screenHandler ->
+ OpenContainer(placePos).finally { screenHandler ->
lastOpenScreen = screenHandler
}
- }.start(null)
+ }.run()
}
- listen { event ->
+ listen { event ->
if (event.screenHandler != lastOpenScreen) return@listen
lastOpenScreen = null
placedPos?.let {
- lastBreak = breakAndCollectBlock(it).start(null)
+ lastBreak = breakAndCollectBlock(it).run()
placedPos = null
}
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt
index 7964d127d..606d3618f 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt
@@ -17,12 +17,13 @@
package com.lambda.module.modules.player
-import com.lambda.interaction.construction.Blueprint.Companion.emptyStructure
-import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint
+import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure
+import com.lambda.interaction.construction.blueprint.DynamicBlueprint.Companion.toBlueprint
import com.lambda.interaction.construction.verify.TargetState
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
-import com.lambda.task.Task.Companion.emptyTask
+import com.lambda.task.Task
+import com.lambda.task.TaskFlow.run
import com.lambda.task.tasks.BuildTask.Companion.build
import com.lambda.util.BlockUtils.blockPos
import com.lambda.util.BlockUtils.blockState
@@ -39,7 +40,7 @@ object Nuker : Module(
private val onlyBreakInstant by setting("Only Break Instant", true)
private val fillFloor by setting("Fill Floor", false)
- private var task = emptyTask()
+ private var task: Task<*>? = null
init {
onEnable {
@@ -63,16 +64,13 @@ object Nuker : Module(
selection
}
- .build(
- pathing = false,
- finishOnDone = false,
- cancelOnUnsolvable = false
- )
- task.start(null)
+ // ToDo: Add build setting delegates
+ .build()
+ task?.run()
}
onDisable {
- task.cancel()
+ task?.cancel()
}
// listener {
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
index 6e2049e11..f3c6b92ce 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
@@ -29,7 +29,7 @@ import com.lambda.interaction.RotationManager
import com.lambda.interaction.rotation.RotationContext
import com.lambda.interaction.visibilty.VisibilityChecker.findRotation
import com.lambda.module.Module
-import com.lambda.module.modules.client.TaskFlow
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.module.tag.ModuleTag
import com.lambda.util.BlockUtils.blockState
import com.lambda.util.math.lerp
@@ -514,10 +514,10 @@ object PacketMine : Module(
}
}
- listen {
+ listen {
currentMiningBlock.forEach { ctx ->
ctx?.apply {
- if (it.pos != pos || !isStateBroken(pos.blockState(world), it.state)) return@forEach
+ if (it.pos != pos || !isStateBroken(pos.blockState(world), it.newState)) return@forEach
if (breakType.isPrimary()) {
runHandlers(ProgressStage.PacketReceiveBreak, pos, lastValidBestTool)
@@ -539,7 +539,7 @@ object PacketMine : Module(
lastNonEmptyState?.let { state ->
val boxList = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) }
val rotationContext =
- findRotation(boxList, TaskFlow.rotation, TaskFlow.interact, emptySet()) { true }
+ findRotation(boxList, TaskFlowModule.rotation, TaskFlowModule.interact, emptySet()) { true }
rotationContext?.let { context ->
it.context = context
expectedRotation = context
@@ -1382,10 +1382,12 @@ object PacketMine : Module(
}
private fun SafeContext.packetStartBreak(pos: BlockPos) {
- startBreak(pos)
- if (packets != PacketMode.Vanilla || doubleBreak) {
+ if (packets == PacketMode.Grim) {
abortBreak(pos)
+ stopBreak(pos)
}
+ startBreak(pos)
+ if (packets == PacketMode.NCP) abortBreak(pos)
if (packets == PacketMode.Grim || doubleBreak) {
stopBreak(pos)
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
index d1e83d558..e41daa91b 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
@@ -19,7 +19,7 @@ package com.lambda.module.modules.player
import com.google.gson.*
import com.lambda.brigadier.CommandResult
-import com.lambda.config.groups.IRotationConfig
+import com.lambda.config.groups.RotationConfig
import com.lambda.context.SafeContext
import com.lambda.core.TimerManager
import com.lambda.event.EventFlow.lambdaScope
@@ -79,7 +79,7 @@ object Replay : Module(
private val deviationThreshold by setting("Deviation threshold", 0.1, 0.1..5.0, 0.1, description = "The threshold for the deviation to cancel the replay.") { cancelOnDeviation }
private val lockCamera by setting("Lock Camera", true)
- private val rotationConfig = object : IRotationConfig.Instant {
+ private val rotationConfig = object : RotationConfig.Instant {
override val rotationMode = if (lockCamera) RotationMode.LOCK else RotationMode.SYNC
}
@@ -429,7 +429,7 @@ object Replay : Module(
this@Replay.warn("Recording too short. Minimum length: 5 ticks.")
return
}
- val file = FolderRegister.replay.locationBoundDirectory().resolve("$name.json")
+ val file = FolderRegister.replay.toFile().locationBoundDirectory().resolve("$name.json")
lambdaScope.launch(Dispatchers.IO) {
file.writeText(gsonCompact.toJson(recording))
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt
index 333295046..a08ea39e0 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt
@@ -26,7 +26,7 @@ import com.lambda.graphics.renderer.esp.DirectionMask
import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh
import com.lambda.graphics.renderer.esp.builders.build
import com.lambda.interaction.RotationManager.currentRotation
-import com.lambda.interaction.RotationManager.requestRotation
+import com.lambda.interaction.RotationManager.rotate
import com.lambda.interaction.blockplace.PlaceFinder.Companion.buildPlaceInfo
import com.lambda.interaction.blockplace.PlaceInfo
import com.lambda.interaction.blockplace.PlaceInteraction.placeBlock
@@ -36,9 +36,11 @@ import com.lambda.interaction.rotation.Rotation.Companion.dist
import com.lambda.interaction.rotation.Rotation.Companion.rotationTo
import com.lambda.interaction.rotation.Rotation.Companion.wrap
import com.lambda.interaction.rotation.RotationContext
-import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces
+import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces
+import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces
import com.lambda.module.Module
import com.lambda.module.modules.client.GuiSettings
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.module.tag.ModuleTag
import com.lambda.util.math.MathUtils.floorToInt
import com.lambda.util.math.VecUtils.dist
@@ -119,15 +121,15 @@ object Scaffold : Module(
}
init {
- requestRotation(
- onUpdate = {
+ rotate {
+ onUpdate {
lastRotation = null
- val info = updatePlaceInfo() ?: return@requestRotation null
- val rotation = rotate(info) ?: return@requestRotation null
+ val info = updatePlaceInfo() ?: return@onUpdate null
+ val rotation = rotate(info) ?: return@onUpdate null
RotationContext(rotation, rotationConfig)
}
- )
+ }
listen {
if (sneakTicks > 0) it.sneak = true
@@ -212,15 +214,19 @@ object Scaffold : Module(
// Dividing the surface by segments and iterating through them
val pointScan = mutableSetOf().apply {
- scanVisibleSurfaces(
- eyes = eye,
- box = Box(info.clickPos),
+ val box = Box(info.clickPos)
+ val sides = if (TaskFlowModule.interact.visibilityCheck) {
+ box.getVisibleSurfaces(eye)
+ } else Direction.entries.toSet()
+ scanSurfaces(
+ box,
+ sides,
resolution = interactionConfig.resolution
) { _, vec ->
- if (eye distSq vec > reachSq) return@scanVisibleSurfaces
+ if (eye distSq vec > reachSq) return@scanSurfaces
val rotation = eye.rotationTo(vec)
- castRotation(rotation, info) ?: return@scanVisibleSurfaces
+ castRotation(rotation, info) ?: return@scanSurfaces
add(rotation)
}
diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt
new file mode 100644
index 000000000..17aaf0a28
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 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.event.events.RenderEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.graphics.renderer.esp.builders.buildOutline
+import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure
+import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint
+import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.task.Task
+import com.lambda.task.TaskFlow.run
+import com.lambda.task.tasks.BuildTask.Companion.build
+import com.lambda.util.BaritoneUtils
+import net.minecraft.util.math.BlockBox
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Box
+import java.awt.Color
+
+object WorldEater : Module(
+ name = "WorldEater",
+ description = "Eats the world",
+ defaultTags = setOf(ModuleTag.PLAYER, ModuleTag.AUTOMATION)
+) {
+// private val height by setting("Height", 4, 1..10, 1)
+// private val width by setting("Width", 6, 1..30, 1)
+ private val pos1 by setting("Position 1", BlockPos(351, 104, 103))
+ private val pos2 by setting("Position 2", BlockPos(361, 70, 113))
+ private val layerSize by setting("Layer Size", 1, 1..10, 1)
+ private var runningTask: Task<*>? = null
+ private var area = BlockBox.create(pos1, pos2)
+ private val work = mutableListOf()
+
+ init {
+ onEnable {
+ area = BlockBox.create(pos1, pos2)
+ val layerRanges = (area.minY..area.maxY step layerSize).reversed()
+ work.addAll(layerRanges.mapNotNull { y ->
+ if (y == area.minY) return@mapNotNull null
+ BlockBox(area.minX, y - layerSize, area.minZ, area.maxX, y, area.maxZ)
+ })
+
+ buildLayer()
+ }
+
+ onDisable {
+ runningTask?.cancel()
+ runningTask = null
+ work.clear()
+ BaritoneUtils.cancel()
+ }
+
+ listen {
+ it.renderer.buildOutline(Box.enclosing(pos1, pos2), Color.BLUE)
+ }
+ }
+
+ private fun buildLayer() {
+ work.firstOrNull()?.let { box ->
+ runningTask = build {
+ box.toStructure(TargetState.Air)
+ .toBlueprint()
+ }.finally {
+ work.removeFirstOrNull()
+ buildLayer()
+ }.run()
+ } ?: disable()
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt
index 8bf724538..182b92c93 100644
--- a/common/src/main/kotlin/com/lambda/task/Task.kt
+++ b/common/src/main/kotlin/com/lambda/task/Task.kt
@@ -19,112 +19,49 @@ package com.lambda.task
import com.lambda.Lambda.LOG
import com.lambda.context.SafeContext
-import com.lambda.event.Event
-import com.lambda.event.EventFlow
-import com.lambda.event.Subscriber
+import com.lambda.event.EventFlow.unsubscribe
+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.TaskFlow
-import com.lambda.threading.runConcurrent
-import com.lambda.threading.runGameScheduled
+import com.lambda.module.modules.client.TaskFlowModule
import com.lambda.threading.runSafe
import com.lambda.util.Communication.logError
-import com.lambda.util.Communication.warn
import com.lambda.util.Nameable
-import com.lambda.util.text.buildText
-import com.lambda.util.text.color
-import com.lambda.util.text.literal
-import com.lambda.util.text.text
-import kotlinx.coroutines.delay
-import net.minecraft.text.Text
-import org.apache.commons.lang3.time.DurationFormatUtils
-import java.awt.Color
+import com.lambda.util.StringUtils.capitalize
-/**
- * A [Task] represents a time-critical activity.
- * It automates in-game activities through event-based programming, leveraging game events to control task flow.
- *
- * [Result] is the type of the result that the task will return when it completes successfully.
- * In case the task should not return any result, [Unit] can be used as the type.
- *
- * A [Task] can have event listeners that are active while the task is running.
- * The task will attempt to execute its subtasks sequentially, and can either keep listening
- * or stop receiving events while the subtasks are running.
- *
- * It supports a builder pattern, allowing you to chain configuration methods like [withDelay],
- * [withTimeout], [withMaxAttempts], [withRepeats], [onSuccess], [onRetry], [onTimeout], [onFailure], and [onRepeat]
- * to construct a [Task] instance.
- * This makes it easy to build complex flows of nested tasks.
- *
- * @property delay The delay before the task starts, in milliseconds.
- * @property timeout The maximum time that the task is allowed to run, in ticks.
- * @property tries The maximum number of attempts to execute the task before it is considered failed.
- * @property executions The number of times the task should be repeated.
- * @property onSuccess The action to be performed when the task completes successfully.
- * @property onRetry The action to be performed when the task is retried after a failure or timeout.
- * @property onTimeout The action to be performed when the task times out.
- * @property onRepeat The action to be performed each time the task is repeated.
- * @property onException The action to be performed when the task encounters an exception.
- */
-abstract class Task : Nameable {
- open var delay: Int = 0
- open var timeout: Int = Int.MAX_VALUE
- open var tries: Int = 0
- open var repeats: Int = 0
- open var cooldown: Int = TaskFlow.taskCooldown
- open var onStart: SafeContext.(Task) -> Unit = {}
- open var onSuccess: SafeContext.(Task, Result) -> Unit = { _, _ -> }
- open var onRetry: SafeContext.(Task) -> Unit = {}
- open var onTimeout: SafeContext.(Task) -> Unit = {}
- open var onRepeat: SafeContext.(Task, Result, Int) -> Unit = { _, _, _ -> }
- open var onException: SafeContext.(Task, Throwable) -> Unit = { _, _ -> }
-
- open var pausable = true
+typealias TaskGenerator = SafeContext.(R) -> Task<*>
+typealias TaskGeneratorOrNull = SafeContext.(R) -> Task<*>?
+typealias TaskGeneratorUnit = SafeContext.(R) -> Unit
+abstract class Task : Nameable, Muteable {
private var parent: Task<*>? = null
- private val root: Task<*> get() = parent?.root ?: this
- private val depth: Int get() = parent?.depth?.plus(1) ?: 0
-
- private var executions = 0
- private var attempted = 0
private val subTasks = mutableListOf>()
- private var state = State.IDLE
+ private var state = State.INIT
+ override val isMuted: Boolean get() = state == State.PAUSED || state == State.INIT
var age = 0
+ 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 isWaiting get() = state == State.WAITING
- private val isRunning get() = state == State.RUNNING
- val isFailed get() = state == State.FAILED
- val isCompleted get() = state == State.COMPLETED || state == State.COOLDOWN
- private val isRoot get() = parent == null
- override var name = this::class.simpleName ?: "Task"
- val identifier get() = "$name@${hashCode()}"
-
- // ToDo: Better color management
- private val primaryColor = Color(0, 255, 0, 100)
+ open var unpausable = false
- val syncListeners = Subscriber()
- private val concurrentListeners = Subscriber()
+ private var nextTask: TaskGenerator? = null
+ private var nextTaskOrNull: TaskGeneratorOrNull? = null
+ private var onFinish: TaskGeneratorUnit? = null
enum class State {
- IDLE,
+ INIT,
RUNNING,
- WAITING,
+ PAUSED,
CANCELLED,
FAILED,
- COOLDOWN,
- COMPLETED,
+ COMPLETED;
+
+ val display get() = name.lowercase().capitalize()
}
init {
- listen {
- parent?.let {
- it.age++
- }
- if (++age >= timeout) {
- onTimeout(this@Task)
- failure(TimeoutException(age, attempted))
- }
- }
+ listen { age++ }
}
/**
@@ -134,98 +71,117 @@ abstract class Task : Nameable {
@DslMarker
annotation class Ta5kBuilder
+ /**
+ * Invoked when the task starts execution.
+ *
+ * This method serves as a lifecycle hook and can be overridden to define
+ * custom behavior or initialization steps necessary before the task begins.
+ * It provides a `SafeContext` to access relevant context-sensitive properties
+ * or actions safely.
+ *
+ * By default, this method does not contain any logic. Subclasses may override
+ * it to implement specific functionality, such as logging, resource allocation,
+ * or preparing preconditions for task execution.
+ */
@Ta5kBuilder
open fun SafeContext.onStart() {}
+ /**
+ * This function is called when the task is canceled.
+ * It can be overridden for tasks that need to perform cleanup operations,
+ * such as cancelling a block breaking progress, releasing resources,
+ * or stopping any ongoing operations that were started by the task.
+ */
@Ta5kBuilder
- fun start(parent: Task<*>?, pauseParent: Boolean = true): Task {
- executions++
- val owner = parent ?: RootTask
- owner.subTasks.add(this)
+ open fun SafeContext.onCancel() {}
- LOG.info("${owner.identifier} started $identifier")
- this.parent = owner
- if (pauseParent && owner.isRunning && !owner.isRoot) {
- LOG.info("$identifier deactivating parent ${owner.identifier}")
- owner.deactivate()
+ /**
+ * 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 child. 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 child of itself" }
+ owner.subTasks.add(this)
+ parent = owner
+ LOG.info("${owner.name} started $name")
+ if (!unpausable || pauseParent) {
+ LOG.info("$name deactivating parent ${owner.name}")
+ if (owner !is TaskFlow) owner.deactivate()
}
+ state = State.RUNNING
+ runSafe { runCatching { onStart() }.onFailure { failure(it) } }
+ return this
+ }
- activate()
+ @Ta5kBuilder
+ fun success(result: Result) {
+ unsubscribe()
+ state = State.COMPLETED
runSafe {
- onStart(this@Task)
- onStart()
+ executeNextTask(result)
}
- return this
}
@Ta5kBuilder
- fun activate() {
- if (isRunning) return
- state = State.RUNNING
- startListening()
+ fun Task.success() {
+ success(Unit)
}
@Ta5kBuilder
- fun deactivate() {
- if (isWaiting) return
- state = State.WAITING
- stopListening()
+ fun activate() {
+ if (state != State.PAUSED) return
+ state = State.RUNNING
}
@Ta5kBuilder
- fun SafeContext.success(result: Result) {
- if (executions < repeats) {
- executions++
- LOG.info("Repeating $identifier $executions/$repeats...")
- onRepeat(this@Task, result, executions)
- reset()
- return
- }
-
- stopListening()
- if (cooldown > 0) {
- state = State.COOLDOWN
- runConcurrent {
- delay(cooldown.toLong())
- runGameScheduled {
- if (state == State.COOLDOWN) finish(result)
- }
- }
- } else finish(result)
+ fun deactivate() {
+ if (state != State.RUNNING) return
+ if (unpausable) return
+ state = State.PAUSED
}
- private fun SafeContext.finish(result: Result) {
- state = State.COMPLETED
- try {
- onSuccess(this@Task, result)
- } catch (e: ClassCastException) {
- result?.let {
- LOG.error("Failed to cast result of $identifier to ${it::class.simpleName}")
- }
- failure(e)
+ private fun SafeContext.executeNextTask(result: Result) {
+ nextTask?.let { taskGen ->
+ val task = taskGen(this, result)
+ nextTask = null
+ parent?.let { owner -> task.execute(owner) }
+ } ?: nextTaskOrNull?.let { taskGen ->
+ val task = taskGen(this, result)
+ nextTaskOrNull = null
+ parent?.let { owner -> task?.execute(owner) }
+ } ?: run {
+ onFinish?.invoke(this, result)
+ parent?.activate()
+ onFinish = null
}
-
- notifyParent()
- LOG.info("$identifier completed successfully after $attempted retries and $executions executions.")
}
@Ta5kBuilder
fun cancel() {
- if (state == State.COMPLETED) return
- if (state == State.CANCELLED) return
-
cancelSubTasks()
+ if (this is TaskFlow) return
+ if (state == State.COMPLETED || state == State.CANCELLED) return
state = State.CANCELLED
- stopListening()
- runSafe { onCancel() }
- LOG.info("$identifier was cancelled")
+ unsubscribe()
}
@Ta5kBuilder
fun cancelSubTasks() {
- subTasks.forEach {
- it.cancel()
- }
+ subTasks.forEach { it.cancel() }
+ }
+
+ fun clear() {
+ subTasks.forEach { it.clear() }
+ subTasks.clear()
}
@Ta5kBuilder
@@ -238,26 +194,15 @@ abstract class Task : Nameable {
e: Throwable,
stacktrace: MutableList> = mutableListOf()
) {
- if (attempted < tries) {
- attempted++
- warn("Failed task with error: ${e.message}, retrying ($attempted/$tries) ...")
- runSafe {
- onRetry(this@Task)
- }
- reset()
- return
- }
-
state = State.FAILED
- stopListening()
- runSafe { onException(this@Task, e) }
+ unsubscribe()
stacktrace.add(this)
parent?.failure(e, stacktrace) ?: run {
val message = buildString {
stacktrace.firstOrNull()?.let { first ->
- append("${first.identifier} failed: ${e.message}\n")
+ append("${first.name} failed: ${e.message}\n")
stacktrace.drop(1).forEach {
- append(" -> ${it.identifier}\n")
+ append(" -> ${it.name}\n")
}
}
}
@@ -267,301 +212,109 @@ abstract class Task : Nameable {
}
/**
- * This function is called when the task is canceled.
- * It should be overridden for tasks that need to perform cleanup operations,
- * such as cancelling a block breaking progress, releasing resources,
- * or stopping any ongoing operations that were started by the task.
- */
- @Ta5kBuilder
- open fun SafeContext.onCancel() {}
-
- private fun notifyParent() {
- parent?.let { par ->
- when {
- par.isCompleted -> {
- LOG.info("$identifier completed parent ${par.identifier}")
- par.notifyParent()
- }
-
- !par.isRunning -> {
- LOG.info("$identifier reactivated parent ${par.identifier}")
- par.activate()
- }
- }
- }
- }
-
- @Ta5kBuilder
- private fun reset() {
- age = 0
- }
-
- private fun startListening() {
- EventFlow.syncListeners.subscribe(syncListeners)
- EventFlow.concurrentListeners.subscribe(concurrentListeners)
- }
-
- private fun stopListening() {
- EventFlow.syncListeners.unsubscribe(syncListeners)
- EventFlow.concurrentListeners.unsubscribe(concurrentListeners)
- }
-
- /**
- * Sets the delay before the task starts.
+ * Specifies the next task to execute after the current task completes successfully.
*
- * @param delay The delay in ticks.
- * @return This task instance with the updated delay.
- */
- @Ta5kBuilder
- fun withDelay(delay: Int): Task {
- this.delay = delay
- return this
- }
-
- /**
- * Sets the timeout for a single attempt of the task
- *
- * @param timeout The timeout in ticks.
- * @return This task instance with the updated timeout.
- */
- @Ta5kBuilder
- fun withTimeout(timeout: Int): Task {
- this.timeout = timeout
- return this
- }
-
- /**
- * Sets the cooldown period for the task.
- *
- * The cooldown period is the time that the task will wait before it the next task can be executed.
- *
- * @param cooldown The cooldown period in milliseconds.
- * @return This task instance with the updated cooldown period.
- */
- @Ta5kBuilder
- fun withCooldown(cooldown: Int): Task {
- this.cooldown = cooldown
- return this
- }
-
- /**
- * Sets the maximum number of attempts to execute the task before it is considered failed.
+ * This method links the current task to the specified `task`, creating a sequential
+ * execution flow where the `task` will be executed immediately after the current task.
*
- * @param maxAttempts The maximum number of attempts.
- * @return This task instance with the updated maximum attempts.
+ * @param task The task that should be executed following the successful completion
+ * of the current task.
+ * @return The current task instance (`Task`) to allow method chaining.
*/
@Ta5kBuilder
- fun withMaxAttempts(maxAttempts: Int): Task {
- this.tries = maxAttempts
+ infix fun then(task: Task<*>): Task {
+ require(task != this) { "Cannot link a task to itself" }
+ nextTask = { task }
return this
}
/**
- * Sets the number of times the task should be repeated.
+ * Chains multiple tasks to be executed sequentially after the current task.
*
- * @param repeats The number of repeats.
- * @return This task instance with the updated number of repeats.
- */
- @Ta5kBuilder
- fun withRepeats(repeats: Int): Task {
- this.repeats = repeats
- return this
- }
-
- /**
- * Sets the action to be performed when the task starts.
+ * This method establishes an ordered execution flow between tasks, where each task
+ * in the provided list will trigger the execution of the next task upon completion.
+ * It effectively links the current task to the first task in the given array
+ * and ensures that the sequence is executed in order.
*
- * @param action The action to be performed.
- * @return The task instance with the updated start action.
+ * @param task A vararg list of tasks to be linked sequentially after the current task.
+ * These tasks are executed in the order they are provided.
+ * @return The current task instance (`Task`) to allow method chaining.
*/
@Ta5kBuilder
- fun onStart(action: SafeContext.(Task) -> Unit): Task {
- this.onStart = action
+ fun then(vararg task: Task<*>): Task {
+ (listOf(this) + task).zipWithNext { current, next ->
+ current then next
+ }
return this
}
/**
- * Sets the action to be performed when the task completes successfully.
+ * Adds a subsequent task to the current task's execution sequence.
*
- * @param action The action to be performed.
- * @return The task instance with the updated success action.
- */
- @Ta5kBuilder
- fun onSuccess(action: SafeContext.(Task, Result) -> Unit): Task {
- this.onSuccess = action
- return this
- }
-
- /**
- * This method allows you to chain another task that will be started upon the successful completion of the current task.
- * The action provided as a parameter will be used to create the next task.
- * The context of the action, the current task, and the result of the current task are passed as parameters to the action.
+ * This method specifies the next task to be executed after the current task
+ * completes successfully. The task is generated dynamically using the provided
+ * `TaskGenerator`. This allows chaining tasks together in a flexible manner.
*
- * @param action A lambda function that takes the current task and its result as parameters and returns the next task to be started.
- * @return The current task instance with the updated success action.
+ * @param taskGenerator A function that generates the next task based on the result
+ * of the current task. It takes a `SafeContext` and the result
+ * of type `R` as input and returns a new `Task`.
+ * @return The current task instance (`Task`) to allow method chaining.
*/
@Ta5kBuilder
- fun thenRun(owner: Task<*>?, action: SafeContext.(Task, Result) -> Task<*>): Task {
- this.onSuccess = { task, result ->
- action(this, task, result).start(owner)
- }
+ fun then(taskGenerator: TaskGenerator): Task {
+ require(nextTask == null) { "Cannot link multiple tasks to a single task" }
+ nextTask = taskGenerator
return this
}
/**
- * Sets the action to be performed when the task is retried after a failure or timeout.
+ * Adds a subsequent task to the current task's execution sequence conditionally.
*
- * @param action The action to be performed.
- * @return The task instance with the updated retry action.
- */
- @Ta5kBuilder
- fun onRetry(action: SafeContext.(Task) -> Unit): Task {
- this.onRetry = action
- return this
- }
-
- /**
- * Sets the action to be performed when the task times out.
+ * This method specifies the next task to be executed after the current task
+ * completes successfully. The next task is generated dynamically using the provided
+ * [TaskGeneratorOrNull]. This allows chaining tasks together flexibly,
+ * where the next task is conditionally determined or null.
*
- * @param action The action to be performed.
- * @return The task instance with the updated timeout action.
+ * @param taskGenerator A function that generates the next task based on the
+ * result of the current task. It takes a `SafeContext`
+ * and the result of type `R` as input and returns a new
+ * `Task` or null if no next task is required.
+ * @return The current task instance (`Task`) to allow method chaining.
*/
@Ta5kBuilder
- fun onTimeout(action: SafeContext.(Task) -> Unit): Task {
- this.onTimeout = action
+ fun thenOrNull(taskGenerator: TaskGeneratorOrNull): Task {
+ require(nextTask == null) { "Cannot link multiple tasks to a single task" }
+ nextTaskOrNull = taskGenerator
return this
}
/**
- * Sets the action to be performed when the task encounters an exception.
+ * Registers a finalization action to be executed after the current task completes.
*
- * @param action The action to be performed.
- * @return The task instance with the updated exception action.
- */
- @Ta5kBuilder
- fun onFailure(action: SafeContext.(Task, Throwable) -> Unit): Task {
- this.onException = action
- return this
- }
-
- /**
- * Sets the action to be performed each time the task is repeated.
+ * This method allows specifying a finalization function that runs upon completion
+ * of the task, regardless of its outcome (success or failure). It is typically used
+ * for cleanup operations or logging after a task finishes execution.
*
- * @param action The action to be performed.
- * @return The task instance with the updated repeat action.
+ * @param onFinish The finalization action to be executed. This function receives
+ * the task's result of type `R` within a `SafeContext`.
+ * @return The current task instance (`Task`) to allow method chaining.
*/
@Ta5kBuilder
- fun onRepeat(action: SafeContext.(Task, Result, Int) -> Unit): Task {
- this.onRepeat = action
- return this
- }
-
- @Ta5kBuilder
- inline fun withListener(
- crossinline action: SafeContext.(Task) -> Unit
- ): Task {
- listen {
- action(this@Task)
- }
- return this
- }
-
- @Ta5kBuilder
- fun withName(name: String): Task {
- this.name = name
+ fun finally(onFinish: TaskGeneratorUnit): Task {
+ require(this.onFinish == null) { "Cannot link multiple finally blocks to a single task" }
+ this.onFinish = onFinish
return this
}
- class TimeoutException(age: Int, attempts: Int) : Exception("Task timed out after $age ticks and $attempts attempts")
-
- companion object {
- val MAX_DEPTH = 20
- const val MAX_DEBUG_ENTRIES = 15
-
- @Ta5kBuilder
- fun emptyTask(
- name: String = "EmptyTask",
- ): Task = object : Task() {
- init { this.name = name }
- override fun SafeContext.onStart() { success(Unit) }
- }
-
- @Ta5kBuilder
- fun failTask(
- message: String
- ): Task = object : Task() {
- init { this.name = "FailTask" }
- override fun SafeContext.onStart() {
- failure(message)
- }
- }
-
- @Ta5kBuilder
- fun failTask(
- e: Throwable
- ): Task = object : Task() {
- init { this.name = "FailTask" }
- override fun SafeContext.onStart() {
- failure(e)
- }
- }
-
- @Ta5kBuilder
- fun buildTask(
- name: String = "Task",
- block: SafeContext.() -> Unit
- ) = object : Task() {
- init { this.name = name }
- override fun SafeContext.onStart() {
- try {
- success(block())
- } catch (e: Throwable) {
- failure(e)
- }
- }
- }
+ override fun toString() =
+ buildString { appendTaskTree(this@Task) }
- @Ta5kBuilder
- inline fun buildTaskWithReturn(
- crossinline block: SafeContext.() -> R
- ) = object : Task() {
- override fun SafeContext.onStart() {
- try {
- success(block())
- } catch (e: Throwable) {
- failure(e)
- }
- }
+ private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0) {
+ appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is TaskFlow) " [${task.state.display}]" else "")
+ if (!TaskFlowModule.showAllEntries && (task.state == State.COMPLETED || task.state == State.CANCELLED)) return
+ task.subTasks.forEach {
+ if (!TaskFlowModule.showAllEntries && task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach
+ appendTaskTree(it, level + 1)
}
}
-
- val info: Text
- get() = buildText {
- literal("Name ")
- color(primaryColor) { literal(name) }
-
- literal(" State ")
- color(primaryColor) { literal(state.name) }
-
- literal(" Runtime ")
- color(primaryColor) {
- literal(DurationFormatUtils.formatDuration(age * 50L, "HH:mm:ss,SSS").dropLast(1))
- }
-
- val display = subTasks.reversed().take(MAX_DEBUG_ENTRIES)
- display.forEach {
- literal("\n${" ".repeat(depth + 1)}")
- text(it.info)
- }
-
- val left = subTasks.size - display.size
- if (left > 0) {
- literal("\n${" ".repeat(depth + 1)}And ")
- color(primaryColor) {
- literal("$left")
- }
- literal(" more...")
- }
-
- }
}
diff --git a/common/src/main/kotlin/com/lambda/task/TaskFlow.kt b/common/src/main/kotlin/com/lambda/task/TaskFlow.kt
new file mode 100644
index 000000000..5d647bb4c
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/task/TaskFlow.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 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
+
+import com.lambda.threading.runSafe
+
+object TaskFlow : Task() {
+ override val name get() = "TaskFlow"
+
+ @Ta5kBuilder
+ fun Task<*>.run() = this.execute(this@TaskFlow)
+
+ @Ta5kBuilder
+ fun Task<*>.run(task: TaskGenerator) {
+ runSafe {
+ task(Unit).execute(this@run)
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/lambda/task/TaskResult.kt b/common/src/main/kotlin/com/lambda/task/TaskResult.kt
deleted file mode 100644
index a20aa21b5..000000000
--- a/common/src/main/kotlin/com/lambda/task/TaskResult.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2024 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
-
-/**
- * Represents the result of a task.
- *
- * A task result can be successful, failed, timed out, or canceled.
- *
- * @param Result The type of the result value for successful tasks. Covariant type parameter.
- */
-sealed class TaskResult {
-
- /**
- * Represents a successful task result.
- *
- * @param T The type of the result value.
- * @property value The result value.
- */
- data class Success(val value: T) : TaskResult