diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIController.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIController.java index a9bfd1fc..dd1b01c5 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIController.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIController.java @@ -19,6 +19,7 @@ import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; +import net.famzangl.minecraft.minebot.ai.command.StackBuilder; import net.famzangl.minecraft.minebot.ai.net.MinebotNetHandler; import net.famzangl.minecraft.minebot.ai.net.NetworkHelper; import net.famzangl.minecraft.minebot.ai.path.world.BlockBoundsCache; @@ -58,6 +59,8 @@ * */ public class AIController extends AIHelper implements IAIControllable { + private final StackBuilder stackBuilder = new StackBuilder(); + private final class UngrabMouseHelper extends MouseHelper { public UngrabMouseHelper(Minecraft minecraftIn) { super(minecraftIn); @@ -87,26 +90,6 @@ public void ungrabMouse() { InputMappings.getInputByName("key.keyboard.u").getKeyCode(), "Command Mod"); static { - // final KeyBinding mine = new KeyBinding("Farm ores", - // Keyboard.getKeyIndex("K"), "Command Mod"); - // final KeyBinding lumberjack = new KeyBinding("Farm wood", - // Keyboard.getKeyIndex("J"), "Command Mod"); - // final KeyBinding build_rail = new KeyBinding("Build Minecart tracks", - // Keyboard.getKeyIndex("H"), "Command Mod"); - // final KeyBinding mobfarm = new KeyBinding("Farm mobs", - // Keyboard.getKeyIndex("M"), "Command Mod"); - // final KeyBinding plant = new KeyBinding("Plant seeds", - // Keyboard.getKeyIndex("P"), "Command Mod"); - // uses.put(mine, new MineStrategy()); - // uses.put(lumberjack, new LumberjackStrategy()); - // uses.put(build_rail, new LayRailStrategy()); - // uses.put(mobfarm, new EnchantStrategy()); - // uses.put(plant, new PlantStrategy()); - // ClientRegistry.registerKeyBinding(mine); - // ClientRegistry.registerKeyBinding(lumberjack); - // ClientRegistry.registerKeyBinding(build_rail); - // ClientRegistry.registerKeyBinding(mobfarm); - // ClientRegistry.registerKeyBinding(plant); ClientRegistry.registerKeyBinding(stop); ClientRegistry.registerKeyBinding(ungrab); } @@ -400,31 +383,15 @@ public int requestUseStrategy(AIStrategy strategy) { @Override public int requestUseStrategy(AIStrategy strategy, SafeStrategyRule rule) { - LOGGER.trace(MARKER_STRATEGY, "Request to use strategy " + strategy + " using saferule " + rule); - - requestedStrategy = makeSafe(strategy, rule); - - return 1; - } - private AIStrategy makeSafe(AIStrategy strategy, SafeStrategyRule safeRule) { - if (safeRule == SafeStrategyRule.NONE) { - return strategy; + if (stackBuilder.collect(strategy, rule)) { + // We are in strack building mode (/minebot stack), only schedule strategy + LOGGER.debug(MARKER_STRATEGY, "Scheduled strategy for stack: {}", strategy); + AIChatController.addChatLine("Strategy scheduled. To start, use: /minebot stack done"); } else { - final StrategyStack stack = new StrategyStack(); - stack.addStrategy(new AbortOnDeathStrategy()); - if (safeRule == SafeStrategyRule.DEFEND_MINING) { - stack.addStrategy(new DoNotSuffocateStrategy()); - } - stack.addStrategy(new DamageTakenStrategy()); - stack.addStrategy(new PlayerComesActionStrategy()); - stack.addStrategy(new CreeperComesActionStrategy()); - stack.addStrategy(new EatStrategy()); - if (safeRule == SafeStrategyRule.DEFEND_MINING) { - stack.addStrategy(new PlaceTorchStrategy()); - } - stack.addStrategy(strategy); - return new StackStrategy(stack); + LOGGER.debug(MARKER_STRATEGY, "Request to use strategy {} using saferule {}", strategy, rule); + requestedStrategy = rule.makeSafe(strategy); } + return 1; } // @SubscribeEvent @@ -461,4 +428,9 @@ public NetworkHelper getNetworkHelper() { return networkHelper; } + + @Override + public StackBuilder getStackBuilder() { + return stackBuilder; + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIHelper.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIHelper.java index 746563dc..cf12a350 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIHelper.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/AIHelper.java @@ -39,7 +39,7 @@ import net.minecraft.util.Direction; import net.minecraft.util.MovementInput; import net.minecraft.util.math.*; -import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.LightType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -117,8 +117,9 @@ private static double randBetween(double a, double b) { protected void invalidateChunkCache() { if (minecraftWorld == null - || getMinecraft().world != minecraftWorld.getBackingWorld()) { - if (getMinecraft().world == null) { + || getMinecraft().world != minecraftWorld.getBackingWorld() + || getMinecraft().player != minecraftWorld.getBackingPlayer()) { + if (getMinecraft().world == null || getMinecraft().player == null) { minecraftWorld = null; } else { minecraftWorld = new WorldData( @@ -1036,9 +1037,7 @@ public static Direction getDirectionFor(BlockPos delta) { // TODO: Move this to WorldData public int getLightAt(BlockPos pos) { - final Chunk chunk = getMinecraft().world.getChunk( - pos.getX() >> 4, pos.getZ() >> 4); - return chunk.getLightValue(pos); + return getMinecraft().world.getLightFor(LightType.BLOCK, pos); } public void setActiveMapReader(MapReader activeMapReader) { diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/PathFinderField.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/PathFinderField.java index ced1c294..f313c20d 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/PathFinderField.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/PathFinderField.java @@ -81,6 +81,7 @@ public class PathFinderField implements Comparator { */ private int[] field; private long startTime; + protected int statsVisited; public PathFinderField() { } @@ -235,6 +236,7 @@ protected boolean searchSomethingAround(int cx, int cy, int cz) { } else { currentDest = null; } + statsVisited = 0; isRunning = true; } LOGGER.trace(MARKER_PATH, "Start path finding."); @@ -288,6 +290,7 @@ protected boolean searchSomethingAround(int cx, int cy, int cz) { } } setVisited(currentNode); + statsVisited++; } if (pqEmpty()) { if (currentDest != null) { @@ -343,7 +346,7 @@ protected int distanceFor(int from, int to) { } protected void noPathFound() { - LOGGER.info(MARKER_PATH, "Path finder did not find a path."); + LOGGER.info(MARKER_PATH, "Path finder did not find a path. Positions visited: {}", statsVisited); } private void planPathTo(int currentNode, int origX, int origY, int origZ) { diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/CommandRegistry.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/CommandRegistry.java index 436e89a4..d9414d8e 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/CommandRegistry.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/CommandRegistry.java @@ -175,6 +175,11 @@ public int requestUseStrategy(AIStrategy strategy) { return 0; } + @Override + public StackBuilder getStackBuilder() { + throw new UnsupportedOperationException("Cannot use /minebot stack in scripts"); + } + public AIStrategy get() { if (strategy == null) { throw new IllegalStateException("No strategy has been set"); diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/IAIControllable.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/IAIControllable.java index 4697f9ce..4b1f470f 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/IAIControllable.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/IAIControllable.java @@ -53,4 +53,9 @@ default int requestUseStrategy(AIStrategy strategy, SafeStrategyRule rule) { return requestUseStrategy(strategy); } + /** + * The stack builder. Used for stacking tasks on the command line + * @return A stack builder instance + */ + StackBuilder getStackBuilder(); } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/SafeStrategyRule.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/SafeStrategyRule.java index e0463567..5b9215d1 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/SafeStrategyRule.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/SafeStrategyRule.java @@ -16,8 +16,44 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.command; +import net.famzangl.minecraft.minebot.ai.strategy.*; + public enum SafeStrategyRule { - NONE, - DEFEND, - DEFEND_MINING + NONE { + @Override + public AIStrategy makeSafe(AIStrategy strategy) { + // NOP + return strategy; + } + }, + DEFEND { + @Override + public AIStrategy makeSafe(AIStrategy strategy) { + final StrategyStack stack = new StrategyStack(); + stack.addStrategy(new AbortOnDeathStrategy()); + stack.addStrategy(new DamageTakenStrategy()); + stack.addStrategy(new PlayerComesActionStrategy()); + stack.addStrategy(new CreeperComesActionStrategy()); + stack.addStrategy(new EatStrategy()); + stack.addStrategy(strategy); + return new StackStrategy(stack); + } + }, + DEFEND_MINING { + @Override + public AIStrategy makeSafe(AIStrategy strategy) { + final StrategyStack stack = new StrategyStack(); + stack.addStrategy(new AbortOnDeathStrategy()); + stack.addStrategy(new DoNotSuffocateStrategy()); + stack.addStrategy(new DamageTakenStrategy()); + stack.addStrategy(new PlayerComesActionStrategy()); + stack.addStrategy(new CreeperComesActionStrategy()); + stack.addStrategy(new EatStrategy()); + stack.addStrategy(new PlaceTorchStrategy()); + stack.addStrategy(strategy); + return new StackStrategy(stack); + } + }; + + public abstract AIStrategy makeSafe(AIStrategy strategy); } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/StackBuilder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/StackBuilder.java new file mode 100644 index 00000000..e9a2d789 --- /dev/null +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/command/StackBuilder.java @@ -0,0 +1,61 @@ +package net.famzangl.minecraft.minebot.ai.command; + +import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; +import net.famzangl.minecraft.minebot.ai.strategy.StackStrategy; +import net.famzangl.minecraft.minebot.ai.strategy.StrategyStack; + +import java.util.ArrayList; + +/** + * Schedule a stack of strategies and start using them + */ +public class StackBuilder { + + private boolean collecting; + private ArrayList collected = new ArrayList<>(); + private SafeStrategyRule hardestSafeRule = SafeStrategyRule.NONE; + + public void startCollecting() { + this.collecting = true; + collected.clear(); + hardestSafeRule = SafeStrategyRule.NONE; + } + + public boolean collect(AIStrategy strategy, SafeStrategyRule rule) { + if (collecting) { + collected.add(strategy); + if (rule.ordinal() > hardestSafeRule.ordinal()) { + hardestSafeRule = rule; + } + return true; + } else { + return false; + } + } + + public AIStrategy getStrategy() { + collecting = false; + StrategyStack stack = new StrategyStack(); + collected.forEach(stack::addStrategy); + + collected.clear(); // < to save memory + return new StackStrategy(stack); + } + + public void abort() { + collecting = false; + collected.clear(); // < to save memory + } + + public boolean hasCollectedAnyStrategies() { + return !collected.isEmpty(); + } + + public boolean isCollecting() { + return collecting; + } + + public SafeStrategyRule getSafeRule() { + return hardestSafeRule; + } +} diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandCraft.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandCraft.java index a38e9371..0a46f6db 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandCraft.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandCraft.java @@ -16,66 +16,32 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.commands; -import net.famzangl.minecraft.minebot.ai.AIHelper; -import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.AICommandInvocation; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter.BlockFilter; -import net.famzangl.minecraft.minebot.ai.command.ParameterType; -import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; -import net.famzangl.minecraft.minebot.ai.path.world.BlockSets; -import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; +import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; import net.famzangl.minecraft.minebot.ai.strategy.CraftStrategy; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; +import net.minecraft.command.arguments.ItemArgument; -@AICommand(helpText = "Crafts items of the given type.", name = "minebot") public class CommandCraft { - /** - * Blocks that can not be crafted. - */ - private static final BlockSet simpleBlocks = BlockSet.builder().add( - Blocks.BREWING_STAND, Blocks.NETHER_WART, - Blocks.CAULDRON, Blocks.FLOWER_POT, Blocks.WHEAT, Blocks.SUGAR_CANE, - Blocks.CAKE, Blocks.SKELETON_SKULL, Blocks.SKELETON_WALL_SKULL, Blocks.WITHER_SKELETON_SKULL, - Blocks.WITHER_SKELETON_WALL_SKULL, Blocks.PISTON_HEAD, - Blocks.MOVING_PISTON, Blocks.REDSTONE_WIRE, - Blocks.PUMPKIN_STEM, - Blocks.TRIPWIRE, - Blocks.MELON_STEM, - Blocks.REDSTONE_WIRE, - Blocks.IRON_DOOR) - .add(BlockSets.AIR) - .add(BlockSets.WALL_SIGN) - .add(BlockSets.WOOL) - .add(BlockSets.BED) - .add(BlockSets.WOODEN_DOR).build().invert(); + public static void register(LiteralArgumentBuilder dispatcher) { + dispatcher.then( + Commands.literal("craft") + .then(Commands.optional( + Commands.argument("type", ItemArgument.item()), + __ -> 1, + "count", + IntegerArgumentType.integer(1, 64), + Integer.class, + (builder, count) -> + builder.executes( + context -> context.getSource().requestUseStrategy(new CraftStrategy(count.get(context), + ItemArgument.getItem(context, "type").getItem()), SafeStrategyRule.DEFEND) + ) + ) + ) - public static final class MyBlockFilter extends BlockFilter { - @Override - public boolean matches(BlockState b) { - return simpleBlocks.contains(b); - } + ); } - - @AICommandInvocation() - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "craft", description = "") String nameArg, - @AICommandParameter(type = ParameterType.NUMBER, description = "Item count") int itemCount, - @AICommandParameter(type = ParameterType.BLOCK_STATE, description = "Block", blockFilter = MyBlockFilter.class) BlockState itemType) { - return new CraftStrategy(itemCount, itemType); - } - - /* TODO - @AICommandInvocation() - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "craft", description = "") String nameArg, - @AICommandParameter(type = ParameterType.NUMBER, description = "Item count") int itemCount, - @AICommandParameter(type = ParameterType.NUMBER, description = "Item type") int itemType, - @AICommandParameter(type = ParameterType.NUMBER, description = "Item subtype", optional = true) Integer itemSubtype) { - return new CraftStrategy(itemCount, itemType, itemSubtype == null ? 0 : itemSubtype); - } */ } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFillArea.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFillArea.java index f19a5c49..07c60df1 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFillArea.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFillArea.java @@ -19,7 +19,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.CommandEvaluationException; import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; import net.famzangl.minecraft.minebot.ai.path.FillAreaPathfinder; @@ -43,11 +42,7 @@ public static void register(LiteralArgumentBuilder dispatcher, .executes( context -> { BlockCuboid area = CommandClearArea.getArea(context.getSource().getAiHelper()); - if (area != null) { - return requestUseStrategy(context, area); - } else { - throw new CommandEvaluationException("No area has been set yet. Set an area to fill using /minebot posN"); - } + return requestUseStrategy(context, area); } ) ) diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFish.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFish.java index cb985bda..3577bf38 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFish.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandFish.java @@ -27,7 +27,7 @@ public class CommandFish { public static void register(LiteralArgumentBuilder dispatcher) { dispatcher.then( - Commands.literal("feed").executes(context -> context.getSource().requestUseStrategy(new FishStrategy(), SafeStrategyRule.DEFEND)) + Commands.literal("fish").executes(context -> context.getSource().requestUseStrategy(new FishStrategy(), SafeStrategyRule.DEFEND)) ); } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandPathfind.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandPathfind.java index bca9218f..81c5862b 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandPathfind.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandPathfind.java @@ -17,9 +17,11 @@ package net.famzangl.minecraft.minebot.ai.commands; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; import net.famzangl.minecraft.minebot.ai.path.GoToPathfinder; +import net.famzangl.minecraft.minebot.ai.path.GoToPathfinderDestructive; import net.famzangl.minecraft.minebot.ai.strategy.PathFinderStrategy; import net.minecraft.command.arguments.BlockPosArgument; import net.minecraft.util.math.BlockPos; @@ -29,12 +31,15 @@ public static void register(LiteralArgumentBuilder dispatcher) dispatcher.then( Commands.literal("pathfind").then( Commands.argument("to", BlockPosArgument.blockPos()) - .executes(context -> { - BlockPos position = Commands.getBlockPos(context, "to"); - return context.getSource().requestUseStrategy(new PathFinderStrategy(new GoToPathfinder(position), "Go to " - + position.getX() + "," + position.getY() + "," + position.getZ()), SafeStrategyRule.DEFEND); - }) + .executes(context -> runPathfind(context, false)) + .then(Commands.literal("destructive").executes(context -> runPathfind(context, true))) ) ); } + + private static int runPathfind(CommandContext context, boolean destroy) { + BlockPos position = Commands.getBlockPos(context, "to"); + return context.getSource().requestUseStrategy(new PathFinderStrategy(destroy ? new GoToPathfinderDestructive(position) : new GoToPathfinder(position), "Go to " + + position.getX() + "," + position.getY() + "," + position.getZ()), destroy ? SafeStrategyRule.DEFEND_MINING : SafeStrategyRule.DEFEND); + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandRespawn.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandRespawn.java index 124f2fea..b2f0e379 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandRespawn.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandRespawn.java @@ -16,23 +16,20 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.commands; -import net.famzangl.minecraft.minebot.ai.AIHelper; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.AICommandInvocation; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter; -import net.famzangl.minecraft.minebot.ai.command.ParameterType; -import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; -import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.strategy.RespawnStrategy; @AICommand(helpText = "Respawn if you have died.", name = "minebot") public class CommandRespawn { - @AICommandInvocation(safeRule = SafeStrategyRule.NONE) - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "respawn", description = "") String nameArg) { - return new RespawnStrategy(); + public static void register(LiteralArgumentBuilder dispatcher) { + dispatcher.then( + Commands.literal("respawn") + .executes(context -> + context.getSource().requestUseStrategy(new RespawnStrategy()) + )); } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandResume.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandResume.java index e0cf5e79..e4f73ec6 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandResume.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandResume.java @@ -1,21 +1,17 @@ package net.famzangl.minecraft.minebot.ai.commands; -import net.famzangl.minecraft.minebot.ai.AIHelper; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.AICommandInvocation; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter; -import net.famzangl.minecraft.minebot.ai.command.ParameterType; -import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; -import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; @AICommand(helpText = "Resume the last thing that was aborted.", name = "minebot") public class CommandResume { - @AICommandInvocation(safeRule = SafeStrategyRule.NONE) - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "resume", description = "") String nameArg) { - AIStrategy toResume = helper.getResumeStrategy(); - return toResume; + public static void register(LiteralArgumentBuilder dispatcher) { + dispatcher.then( + Commands.literal("resume") + .executes(context -> + context.getSource().requestUseStrategy(context.getSource().getAiHelper().getResumeStrategy()) + )); } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStack.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStack.java new file mode 100644 index 00000000..f1d567f8 --- /dev/null +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStack.java @@ -0,0 +1,57 @@ +package net.famzangl.minecraft.minebot.ai.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.famzangl.minecraft.minebot.ai.command.AIChatController; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; +import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; + +public class CommandStack { + + public static void register(LiteralArgumentBuilder dispatcher) { + dispatcher.then( + Commands.literal("stack") + .then( + Commands.literal("begin") + .executes(CommandStack::runStart) + ) + .then( + Commands.literal("done") + .executes(CommandStack::runDone) + ) + .then( + Commands.literal("abort") + .executes(CommandStack::runAbort) + ) + ); + } + + private static int runStart(CommandContext context) { + context.getSource().getStackBuilder().startCollecting(); + if (context.getSource().getStackBuilder().isCollecting() && context.getSource().getStackBuilder().hasCollectedAnyStrategies()) { + AIChatController.addChatLine("Dropped all old stack contents."); + } + AIChatController.addChatLine("Enter the minebot commands you want to have stacked (run in parallel)."); + AIChatController.addChatLine("When done, enter: /minebot stack done"); + AIChatController.addChatLine("To abort, enter: /minebot stack abort"); + return 1; + } + + private static int runDone(CommandContext context) { + if (!context.getSource().getStackBuilder().hasCollectedAnyStrategies()) { + AIChatController.addChatLine("No commands scheduled. Aborting"); + return 0; + } else { + AIStrategy newStrategy = context.getSource().getStackBuilder().getStrategy(); + return context.getSource().requestUseStrategy(newStrategy, context.getSource().getStackBuilder().getSafeRule()); + } + } + + private static int runAbort(CommandContext context) { + context.getSource().getStackBuilder().abort(); + if (context.getSource().getStackBuilder().isCollecting()) { + AIChatController.addChatLine("Aborted"); + } + return 0; + } +} diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStore.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStore.java index a6a1877c..7cf578c3 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStore.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandStore.java @@ -16,21 +16,19 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.commands; -import net.famzangl.minecraft.minebot.ai.AIHelper; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.AICommandInvocation; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter; -import net.famzangl.minecraft.minebot.ai.command.ParameterType; -import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; -import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.strategy.StoreStrategy; @AICommand(name = "minebot", helpText = "Store to chests.") public class CommandStore { - @AICommandInvocation(safeRule = SafeStrategyRule.DEFEND) - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "store", description = "") String nameArg) { - return new StoreStrategy(); + + public static void register(LiteralArgumentBuilder dispatcher) { + dispatcher.then( + Commands.literal("store") + .executes(context -> + context.getSource().requestUseStrategy(new StoreStrategy()) + )); } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandTunnel.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandTunnel.java index f442ad27..b38822ea 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandTunnel.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/CommandTunnel.java @@ -16,63 +16,122 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.commands; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.famzangl.minecraft.minebot.ai.AIHelper; -import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.AICommandInvocation; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter; -import net.famzangl.minecraft.minebot.ai.command.ParameterType; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; import net.famzangl.minecraft.minebot.ai.path.TunnelPathFinder; import net.famzangl.minecraft.minebot.ai.path.TunnelPathFinder.TorchSide; -import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; import net.famzangl.minecraft.minebot.ai.strategy.PathFinderStrategy; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; -@AICommand(helpText = "Build a tunnel with the given profile", name = "minebot") +import java.util.EnumSet; + +/** + * Build a tunnel with the given profile + */ public class CommandTunnel { - @AICommandInvocation(safeRule = SafeStrategyRule.DEFEND_MINING) - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "tunnel", description = "") String nameArg, - @AICommandParameter(type = ParameterType.ENUM, description = "direction", optional = true) Direction inDirection, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "branches", description = "add sideward branches", optional = true) String sidewardBranches, - @AICommandParameter(type = ParameterType.ENUM, description = "torch side", optional = true) TorchSide torches, - @AICommandParameter(type = ParameterType.NUMBER, description = "max length", optional = true) Integer length) { - return run(helper, nameArg, inDirection, sidewardBranches != null ? -1 - : 0, 0, torches, length); - } + enum TunnelModeEnum implements TunnelPathFinder.TunnelMode { + TUNNEL_1x2(0, 0), + TUNNEL_1x3(0, 1), + TUNNEL_1x4(0, 2), + TUNNEL_1x5(0, 3), + TUNNEL_1x6(0, 4), + TUNNEL_1x7(0, 5), + TUNNEL_3x2(1, 0), + TUNNEL_3x3(1, 1), + TUNNEL_3x4(1, 2), + TUNNEL_3x5(1, 3), + TUNNEL_3x6(1, 4), + TUNNEL_5x2(2, 0), + TUNNEL_5x3(2, 1), + TUNNEL_5x4(2, 2), + TUNNEL_5x5(2, 3), + TUNNEL_5x6(2, 4), + TUNNEL_7x2(3, 0), + TUNNEL_7x3(3, 1), + TUNNEL_7x4(3, 2), + TUNNEL_7x5(3, 3), + TUNNEL_9x2(4, 0), + TUNNEL_9x3(4, 1), + TUNNEL_9x4(4, 2), + TUNNEL_BRANCHES(true); + + private final int addToSide; + private final int addToTop; + private final boolean branches; + + TunnelModeEnum(int addToSide, int addToTop) { + this.addToSide = addToSide; + this.addToTop = addToTop; + this.branches = false; + } + + TunnelModeEnum(boolean branches) { + this.addToSide = 0; + this.addToTop = 0; + this.branches = branches; + } + + @Override + public int getAddToSide() { + return addToSide; + } - @AICommandInvocation(safeRule = SafeStrategyRule.DEFEND_MINING) - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "tunnel", description = "") String nameArg, - @AICommandParameter(type = ParameterType.ENUM, description = "direction", optional = true) Direction inDirection, - @AICommandParameter(type = ParameterType.NUMBER, description = "add to side") int addToSide, - @AICommandParameter(type = ParameterType.NUMBER, description = "add to top") int addToTop, - @AICommandParameter(type = ParameterType.ENUM, description = "torch side", optional = true) TorchSide torches, - @AICommandParameter(type = ParameterType.NUMBER, description = "max length", optional = true) Integer length) { - if (inDirection == null) { - inDirection = helper.getLookDirection(); - } + @Override + public int getAddToTop() { + return addToTop; + } - if (torches == null) { - torches = TorchSide.NONE; - } + @Override + public boolean addBranches() { + return branches; + } + } - if (length == null) { - length = -1; - } + public static void register(LiteralArgumentBuilder dispatcher) { + dispatcher.then(Commands.optional( + Commands.literal("tunnel"), + context -> context.getSource().getAiHelper().getLookDirection(), + "direction", + EnumArgument.of(Direction.EAST, Direction.NORTH, Direction.WEST, Direction.SOUTH), + Direction.class, + (builder, direction) -> + Commands.optional(builder, + __ -> TunnelModeEnum.TUNNEL_1x2, + "mode", + EnumArgument.of(EnumSet.allOf(TunnelModeEnum.class), e -> e.name().replace("TUNNEL_", "")), + TunnelModeEnum.class, - final BlockPos pos = helper.getPlayerPosition(); - final TunnelPathFinder tunnel = new TunnelPathFinder( - inDirection.getXOffset(), inDirection.getZOffset(), - pos.getX(), pos.getY(), pos.getZ(), addToSide, addToTop, - torches, length); - return new PathFinderStrategy(tunnel, null) { - public String getDescription(AIHelper helper) { - return "Tunneling " + tunnel.getProgress(); - } - }; - } + (builder2, tunnelMode) -> + Commands.optional(builder2, + __ -> TorchSide.TORCH_NONE, + "torch_side", + EnumArgument.of(TorchSide.class), + TorchSide.class, + (builder3, torchSide) -> + Commands.optional(builder3, + __ -> -1, + "length", + IntegerArgumentType.integer(1), + Integer.class, + (builder4, length) -> builder4.executes(context -> { + Direction inDirection = direction.get(context); + final BlockPos pos = context.getSource().getAiHelper().getPlayerPosition(); + final TunnelPathFinder tunnel = new TunnelPathFinder( + inDirection.getXOffset(), inDirection.getZOffset(), + pos.getX(), pos.getY(), pos.getZ(), tunnelMode.get(context), + torchSide.get(context), length.get(context)); + return context.getSource().requestUseStrategy(new PathFinderStrategy(tunnel, null) { + public String getDescription(AIHelper helper) { + return "Tunneling " + tunnel.getProgress(); + } + }, SafeStrategyRule.DEFEND_MINING); + }) + ) + )) + )); + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/Commands.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/Commands.java index 7aba91e2..a9f42334 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/Commands.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/Commands.java @@ -6,6 +6,7 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.famzangl.minecraft.minebot.ai.command.IAIControllable; +import net.famzangl.minecraft.minebot.build.commands.CommandClearArea; import net.minecraft.command.arguments.ILocationArgument; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; @@ -83,13 +84,19 @@ public static void register(LiteralArgumentBuilder minebot, CommandAirbridge.register(minebot); CommandBuildRail.register(minebot); CommandBuildWay.register(minebot); + CommandCraft.register(minebot); CommandEat.register(minebot); CommandFeed.register(minebot); CommandFish.register(minebot); CommandFillArea.register(minebot, minebuild); - CommandPathfind.register(minebot); + CommandClearArea.register(minebot, minebuild); CommandLumberjack.register(minebot); CommandMine.register(minebot); CommandPathfind.register(minebot); + CommandRespawn.register(minebot); + CommandResume.register(minebot); + CommandStack.register(minebot); + CommandStore.register(minebot); + CommandTunnel.register(minebot); } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/EnumArgument.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/EnumArgument.java index 75473a31..c4ba6d0d 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/EnumArgument.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/commands/EnumArgument.java @@ -15,18 +15,23 @@ import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; public class EnumArgument> implements ArgumentType { private final EnumSet constants; + private final Function getName; public static > EnumArgument of(Class allOf) { return of(EnumSet.allOf(allOf)); } public static > EnumArgument of(EnumSet set) { - return new EnumArgument<>(set); + return of(set, Enum::name); + } + public static > EnumArgument of(EnumSet set, Function getName) { + return new EnumArgument<>(set, getName); } @SafeVarargs @@ -34,7 +39,8 @@ public static > EnumArgument of(T first, T... remaining) { return of(EnumSet.of(first, remaining)); } - public EnumArgument(EnumSet allOf) { + private EnumArgument(EnumSet allOf, Function getName) { + this.getName = getName; if (allOf.size() == 0) { throw new IllegalArgumentException("At least one enum constant needs to be provided"); } @@ -47,7 +53,7 @@ public T parse(StringReader reader) throws CommandSyntaxException { String value = reader.readUnquotedString(); Optional result = constants .stream() - .filter(it -> it.name().equalsIgnoreCase(value)) + .filter(it -> getName.apply(it).equalsIgnoreCase(value)) .findFirst(); if (result.isPresent()) { return result.get(); @@ -71,7 +77,7 @@ public Collection getExamples() { private Stream constantNames() { return constants .stream() - .map(it -> it.name().toLowerCase(Locale.US)); + .map(it -> getName.apply(it).toLowerCase(Locale.US)); } @Override diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/ClearAreaPathfinder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/ClearAreaPathfinder.java index 7097676e..ef4d40ea 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/ClearAreaPathfinder.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/ClearAreaPathfinder.java @@ -25,7 +25,6 @@ import net.famzangl.minecraft.minebot.ai.utils.BlockCuboid; import net.famzangl.minecraft.minebot.ai.utils.BlockFilteredArea; import net.famzangl.minecraft.minebot.ai.utils.AreaIntersection; -import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.util.math.BlockPos; @@ -51,15 +50,12 @@ public enum ClearMode { private final BlockSet clearedBlocks; public ClearAreaPathfinder(BlockCuboid area, - BlockState block, ClearMode mode) { + BlockSet toClean, ClearMode mode) { this.area = area; this.mode = mode; topY = area.getMax().getY(); - if (block == null) { - clearedBlocks = CLEARED_BLOCKS; - } else { - clearedBlocks = BlockSet.builder().add(block).build().invert(); - } + + clearedBlocks = BlockSet.builder().add(toClean.invert()).add(CLEARED_BLOCKS).build(); } @Override diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/FillAreaPathfinder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/FillAreaPathfinder.java index e0980bda..72240f8b 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/FillAreaPathfinder.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/FillAreaPathfinder.java @@ -16,6 +16,7 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.path; +import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.command.AICommandParameter.BlockFilter; import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; import net.famzangl.minecraft.minebot.ai.path.world.BlockSets; @@ -81,6 +82,8 @@ protected void noPathFound() { currentFillLayer++; LOGGER.debug(MARKER_FILL, "Advance to layer " + currentFillLayer); addTask(new WaitTask()); + } else { + AIChatController.addChatLine("Done filling area"); } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinder.java index a8ced41f..600821b4 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinder.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinder.java @@ -16,6 +16,7 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.path; +import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; import net.famzangl.minecraft.minebot.ai.utils.BlockArea; import net.famzangl.minecraft.minebot.ai.utils.BlockCuboid; @@ -24,25 +25,33 @@ import org.apache.logging.log4j.Logger; public class GoToPathfinder extends WalkingPathfinder { - private static final Logger LOGGER = LogManager.getLogger(FillAreaPathfinder.class); + private static final Logger LOGGER = LogManager.getLogger(GoToPathfinder.class); + private static final int HORIZONTAL_SEARCH_MIN = (int) (HORIZONTAL_SEARCH_DISTANCE * .8); + private static final int VERTICAL_SEARCH_MIN = (int) (VERTICAL_SEARCH_DISTANCE * .8); - private final BlockPos position; - int HORIZONTAL_SEARCH_MIN = (int) (HORIZONTAL_SEARCH_DISTANCE * .8); - int VERTICAL_SEARCH_MIN = (int) (VERTICAL_SEARCH_DISTANCE * .8); + private final BlockPos destination; private BlockArea targetArea; - public GoToPathfinder(BlockPos position) { - this.position = position; + public GoToPathfinder(BlockPos destination) { + this.destination = destination; } - + @Override protected boolean runSearch(BlockPos playerPosition) { - if (playerPosition.equals(position)) { + if (playerPosition.equals(destination)) { return true; } - BlockPos posDiff = position.subtract(playerPosition); + targetArea = computeTargetArea(playerPosition, destination); + + LOGGER.debug("Pathfinder target area is: {}", targetArea); + + return super.runSearch(playerPosition); + } + + static BlockArea computeTargetArea(BlockPos playerPosition, BlockPos destination) { + BlockPos posDiff = destination.subtract(playerPosition); BlockArea area = new BlockCuboid<>( - position, position + destination, destination ); // X if (posDiff.getX() > HORIZONTAL_SEARCH_MIN) { @@ -69,7 +78,7 @@ protected boolean runSearch(BlockPos playerPosition) { area = area.unionWith( new BlockCuboid<>( playerPosition.add(-HORIZONTAL_SEARCH_DISTANCE, -VERTICAL_SEARCH_MIN, -HORIZONTAL_SEARCH_DISTANCE), - playerPosition.add(HORIZONTAL_SEARCH_DISTANCE, -VERTICAL_SEARCH_DISTANCE, -HORIZONTAL_SEARCH_DISTANCE) + playerPosition.add(HORIZONTAL_SEARCH_DISTANCE, -VERTICAL_SEARCH_DISTANCE, HORIZONTAL_SEARCH_DISTANCE) )); } @@ -87,15 +96,26 @@ protected boolean runSearch(BlockPos playerPosition) { playerPosition.add(HORIZONTAL_SEARCH_DISTANCE, VERTICAL_SEARCH_DISTANCE, -HORIZONTAL_SEARCH_DISTANCE) )); } - targetArea = area; - - LOGGER.debug("Pathfinder target area is: {}", area); - - return super.runSearch(playerPosition); + return area; } @Override protected float rateDestination(int distance, int x, int y, int z) { return targetArea.contains(world, x, y, z) ? distance : -1; } + + @Override + protected void noPathFound() { + if (statsVisited < 50) { + AIChatController.addChatLine("Cannot reach destination. Cannot go far from start position."); + } + } + + @Override + public String toString() { + return "GoToPathfinder{" + + "destination=" + destination + + ", targetArea=" + targetArea + + '}'; + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinderDestructive.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinderDestructive.java new file mode 100644 index 00000000..19cbfb01 --- /dev/null +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/GoToPathfinderDestructive.java @@ -0,0 +1,53 @@ +package net.famzangl.minecraft.minebot.ai.path; + +import net.famzangl.minecraft.minebot.ai.command.AIChatController; +import net.famzangl.minecraft.minebot.ai.path.world.WorldData; +import net.famzangl.minecraft.minebot.ai.utils.BlockArea; +import net.minecraft.util.math.BlockPos; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Same as the {@link GoToPathfinder}, digging instead of walking + */ +public class GoToPathfinderDestructive extends MovePathFinder { + private static final Logger LOGGER = LogManager.getLogger(GoToPathfinder.class); + + private final BlockPos destination; + private BlockArea targetArea; + + public GoToPathfinderDestructive(BlockPos destination) { + this.destination = destination; + } + + @Override + protected boolean runSearch(BlockPos playerPosition) { + if (playerPosition.equals(destination)) { + return true; + } + + targetArea = GoToPathfinder.computeTargetArea(playerPosition, destination); + LOGGER.debug("Destructive pathfind target area is: {}", targetArea); + return super.runSearch(playerPosition); + } + + @Override + protected float rateDestination(int distance, int x, int y, int z) { + return targetArea.contains(world, x, y, z) ? distance : -1; + } + + @Override + protected void noPathFound() { + if (statsVisited < 50) { + AIChatController.addChatLine("Cannot reach destination. Cannot go far from start position."); + } + } + + @Override + public String toString() { + return "GoToPathfinderDestructive{" + + "destination=" + destination + + ", targetArea=" + targetArea + + '}'; + } +} diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/MovePathFinder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/MovePathFinder.java index 1af55cb9..196f36ea 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/MovePathFinder.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/MovePathFinder.java @@ -31,6 +31,8 @@ import net.minecraft.block.Blocks; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.LinkedList; @@ -44,6 +46,8 @@ * */ public class MovePathFinder extends PathFinderField { + private static final Logger LOGGER = LogManager.getLogger(MovePathFinder.class); + /** * Blocks that are destructable faster. */ @@ -244,11 +248,6 @@ private boolean checkHeadBlock(int currentNode, int cx, int cy, int cz) { return BlockSets.SAFE_CEILING.isAt(world, cx, cy + 2, cz); } - @Override - protected void noPathFound() { - super.noPathFound(); - } - @Override protected void foundPath(LinkedList path) { super.foundPath(path); @@ -258,15 +257,15 @@ protected void foundPath(LinkedList path) { while (!path.isEmpty()) { BlockPos nextPos = path.removeFirst(); Direction moveDirection = direction(currentPos, nextPos); - int stepsAdded = 0; + int fastMoveStepsAdded = 0; while (path.peekFirst() != null && isAreaClear(currentPos, path.peekFirst()) - && stepsAdded < 40) { + && fastMoveStepsAdded < 40) { nextPos = path.removeFirst(); - stepsAdded++; + fastMoveStepsAdded++; } final BlockPos peeked = path.peekFirst(); - if (stepsAdded > 0) { + if (fastMoveStepsAdded > 0) { addHorizontalFastMove(currentPos, nextPos); } else if (moveDirection == Direction.UP && peeked != null && nextPos.subtract(peeked).getY() == 0) { @@ -298,8 +297,7 @@ private void addJumpMoveTask(BlockPos nextPos, final BlockPos peeked) { } private void addHorizontalFastMove(BlockPos currentPos, BlockPos nextPos) { - System.out.println("Shortcut from " + currentPos + " to " - + nextPos); + LOGGER.debug("Using fast move from " + currentPos + " to " + nextPos); addTask(new WalkTowardsTask(nextPos.getX(), nextPos.getZ(), currentPos)); } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TreePathFinder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TreePathFinder.java index f9e1eb59..363cc232 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TreePathFinder.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TreePathFinder.java @@ -17,6 +17,7 @@ package net.famzangl.minecraft.minebot.ai.path; import net.famzangl.minecraft.minebot.ai.AIHelper; +import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; import net.famzangl.minecraft.minebot.ai.path.world.BlockSets; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; @@ -461,4 +462,12 @@ protected boolean runSearch(BlockPos playerPosition) { } return super.runSearch(playerPosition); } + + @Override + protected void noPathFound() { + super.noPathFound(); + if (statsVisited < 50) { + AIChatController.addChatLine("No more more trees found. The area the bot can walk to is pretty small."); + } + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TunnelPathFinder.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TunnelPathFinder.java index 9b33f276..b41f3fb2 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TunnelPathFinder.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/TunnelPathFinder.java @@ -20,6 +20,7 @@ import net.famzangl.minecraft.minebot.ai.BlockItemFilter; import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; import net.famzangl.minecraft.minebot.ai.path.world.BlockSets; +import net.famzangl.minecraft.minebot.ai.path.world.WorldData; import net.famzangl.minecraft.minebot.ai.path.world.WorldWithDelta; import net.famzangl.minecraft.minebot.ai.task.DestroyInRangeTask; import net.famzangl.minecraft.minebot.ai.task.PlaceTorchSomewhereTask; @@ -117,8 +118,11 @@ public String toString() { * */ public enum TorchSide { - NONE(false, false, false), LEFT(true, false, false), RIGHT(false, true, - false), BOTH(true, true, false), FLOOR(false, false, true); + TORCH_NONE(false, false, false), + TORCH_LEFT(true, false, false), + TORCH_RIGHT(false, true,false), + TORCH_BOTH(true, true, false), + TORCH_FLOOR(false, false, true); private final boolean left; private final boolean right; @@ -131,35 +135,40 @@ private TorchSide(boolean left, boolean right, boolean floor) { } } + public interface TunnelMode { + default int getAddToSide() { + return 0; + } + default int getAddToTop() { + return 0; + } + default boolean addBranches() { + return false; + } + } + private final static BlockSet FREE_TUNNEL_BLOCKS = BlockSet.builder().add(BlockSets.AIR) .add(BlockSets.TORCH).build(); private BitSet finishedTunnels = new BitSet(); private BitSet inQueueTunnels = new BitSet(); + private final TunnelMode tunnelMode; + /** * How often did we attempt to tunnel at a given position? */ private Hashtable tunnelPositionStartCount = new Hashtable(); - /** - * How much more to tunnel to the side. - */ - private final int addToSide; - private final int addToTop; private final TorchSide torches; - private final boolean addBranches; - private int currentStepNumber = 0; private final Object currentStepNumberMutex = new Object(); public TunnelPathFinder(int dx, int dz, int cx, int cy, int cz, - int addToSide, int addToTop, TorchSide torches, int length) { + TunnelMode tunnelMode, TorchSide torches, int length) { super(dx, dz, cx, cy, cz, length); - this.addToSide = Math.max(0, addToSide); - this.addToTop = addToTop; + this.tunnelMode = tunnelMode; this.torches = torches; - this.addBranches = addToSide < 0; } @Override @@ -223,14 +232,14 @@ protected void addTasksForTarget(BlockPos currentPos) { addTask(new MarkAsReachedTask(stepNumber)); BlockPos p1, p2; if (dx == 0) { - p1 = currentPos.add(addToSide, 0, 0); - p2 = currentPos.add(-addToSide, 1 + addToTop, 0); + p1 = currentPos.add(tunnelMode.getAddToSide(), 0, 0); + p2 = currentPos.add(-tunnelMode.getAddToSide(), 1 + tunnelMode.getAddToTop(), 0); } else { - p1 = currentPos.add(0, 0, addToSide); - p2 = currentPos.add(0, 1 + addToTop, -addToSide); + p1 = currentPos.add(0, 0, tunnelMode.getAddToSide()); + p2 = currentPos.add(0, 1 + tunnelMode.getAddToTop(), -tunnelMode.getAddToSide()); } - BlockCuboid tunnelArea = new BlockCuboid(p1, p2); - BlockFilteredArea area = new BlockFilteredArea(tunnelArea, + BlockCuboid tunnelArea = new BlockCuboid<>(p1, p2); + BlockFilteredArea area = new BlockFilteredArea<>(tunnelArea, FREE_TUNNEL_BLOCKS.invert()); addTask(new DestroyInRangeTask(area)); @@ -249,15 +258,15 @@ protected void addTasksForTarget(BlockPos currentPos) { } } final boolean isBranchStep = stepNumber % 4 == 2; - if (addBranches && isBranchStep) { + if (tunnelMode.addBranches() && isBranchStep) { addBranchTask(currentPos, -dz, dx); addBranchTask(currentPos, dz, -dx); } addTask(new MarkAsDoneTask(stepNumber)); } - private boolean containsTorches(BlockCuboid tunnelArea) { - BlockFilteredArea torchArea = new BlockFilteredArea(tunnelArea, + private boolean containsTorches(BlockCuboid tunnelArea) { + BlockFilteredArea torchArea = new BlockFilteredArea<>(tunnelArea, BlockSets.TORCH); return torchArea.getVolume(world) > 0; } @@ -282,10 +291,10 @@ private boolean isSafeBranchPos(BlockPos pos) { private void addTorchesTask(BlockPos currentPos, int dirX, int dirZ) { final ArrayList positions = new ArrayList(); - positions.add(new BlockPos(currentPos.getX() + dirX * addToSide, currentPos - .getY() + 1, currentPos.getZ() + dirZ * addToSide)); + positions.add(new BlockPos(currentPos.getX() + dirX * tunnelMode.getAddToSide(), currentPos + .getY() + 1, currentPos.getZ() + dirZ * tunnelMode.getAddToSide())); - for (int i = addToSide; i >= 0; i--) { + for (int i = tunnelMode.getAddToSide(); i >= 0; i--) { positions.add(new BlockPos(currentPos.getX() + dirX * i, currentPos .getY(), currentPos.getZ() + dirZ * i)); } @@ -305,8 +314,7 @@ public String getProgress() { @Override public String toString() { - return "TunnelPathFinder [addToSide=" + addToSide + ", addToTop=" - + addToTop + ", dx=" + dx + ", dz=" + dz + ", cx=" + cx + return "TunnelPathFinder [tunnelMode=" + tunnelMode + ", dx=" + dx + ", dz=" + dz + ", cx=" + cx + ", cy=" + cy + ", cz=" + cz + ", torches=" + torches + ", length=" + length + "]"; } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockBounds.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockBounds.java index bb1ec802..da257927 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockBounds.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockBounds.java @@ -49,7 +49,7 @@ public BlockBounds(AxisAlignedBB axisAlignedBB) { public static final BlockBounds UNKNOWN_BLOCK = new BlockBounds(0, 1); public static final BlockBounds FULL_BLOCK = new BlockBounds(0, 1); - private static final BlockSet FULL_BLOCKS = BlockSet.builder().add(BlockSets.SIMPLE_CUBE).add(BlockSets.AIR).build(); + private static final BlockSet FULL_BLOCKS = BlockSet.builder().add(BlockSets.SIMPLE_CUBE).add(BlockSets.LEAVES).add(BlockSets.AIR).build(); public static final BlockBounds LOWER_HALF_BLOCK = new BlockBounds(0, 0.5); private static final BlockSet LOWER_HALF_BLOCKS = BlockSets.LOWER_SLABS; public static final BlockBounds UPPER_HALF_BLOCK = new BlockBounds(0.5, 1); diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSet.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSet.java index 4c9ce25b..e0dad3c1 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSet.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSet.java @@ -29,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import java.util.stream.IntStream; /** @@ -216,13 +217,20 @@ public boolean hasNext() { if (nextId < 0) { scanNext(); } - return nextId < set.length * 64; + return nextId < getMaxIndex(); + } + + /** + * @return 1 more than the maximum block state id this iterator could return + */ + private int getMaxIndex() { + return remaining ? Block.BLOCK_STATE_IDS.size() : set.length * 64; } private void scanNext() { do { nextId++; - } while (nextId < set.length * 64 + } while (nextId < getMaxIndex() && !contains(nextId)); } @@ -244,7 +252,15 @@ public void remove() { }; } - public static class Builder { + public BlockSet minus(BlockSet other) { + return BlockSet.builder().add(this.invert()).add(other).build().invert(); + } + + public BlockSet intersect(BlockSet other) { + return BlockSet.builder().add(this.invert()).add(other.invert()).build().invert(); + } + + public static class Builder { // May contain duplicates => we don't care private final List statesToAdd = new ArrayList<>(); private Builder() { @@ -287,8 +303,22 @@ public Builder add(Block... blocks) { } public Builder add(BlockSet blockSet) { - for (BlockState stateId: blockSet) { - add(stateId); + for (BlockState state: blockSet) { + add(state); + } + return this; + } + + public Builder add(Predicate condition) { + return add(BlockSets.EMPTY.invert()); + } + + public Builder add(BlockSet set, Predicate condition) { + if (Block.BLOCK_STATE_IDS.size() == 0) { + throw new IllegalStateException("Block states not initialized yet."); + } + for (BlockState state: set) { + add(state); } return this; } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSets.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSets.java index b3cec521..aa3d638d 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSets.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/BlockSets.java @@ -2,6 +2,7 @@ import net.minecraft.block.Block; import net.minecraft.block.Blocks; +import net.minecraft.block.LeavesBlock; import net.minecraft.block.SlabBlock; import net.minecraft.state.properties.SlabType; import net.minecraft.util.math.BlockPos; @@ -278,7 +279,7 @@ public class BlockSets { Blocks.ACACIA_FENCE_GATE).build(); /** - * All leaves. FIXME: Only consider leaves that do not decay as safe ground. + * All leaves. */ public static final BlockSet LEAVES = BlockSet.builder().add( Blocks.OAK_LEAVES, @@ -288,6 +289,14 @@ public class BlockSets { Blocks.DARK_OAK_LEAVES, Blocks.ACACIA_LEAVES).build(); + /** + * Only consider leaves that do not decay as safe ground. + */ + public static final BlockSet PERSISTENT_LEAVES = BlockSet.builder().add( + LEAVES, + state -> state.get(LeavesBlock.PERSISTENT) + ).build(); + public static final BlockSet PLANKS = BlockSet.builder().add( Blocks.OAK_PLANKS, Blocks.SPRUCE_PLANKS, @@ -405,8 +414,7 @@ public class BlockSets { Blocks.END_ROD, Blocks.BEETROOTS, Blocks.DEAD_BUSH, - Blocks.ROSE_BUSH, - Blocks.GRASS_BLOCK).add(RAILS).add(CARPET).add(SAPLING).add(SIGN).add(WALL_SIGN).build(); + Blocks.ROSE_BUSH).add(RAILS).add(CARPET).add(SAPLING).add(SIGN).add(WALL_SIGN).build(); /** * Blocks that fall down. @@ -462,6 +470,8 @@ public class BlockSets { * Blocks we can just walk over/next to without problems. */ public static final BlockSet SIMPLE_CUBE = BlockSet.builder().add( + Blocks.ANDESITE, + Blocks.GRANITE, Blocks.BEDROCK, Blocks.END_STONE, Blocks.END_STONE_BRICKS, @@ -514,7 +524,7 @@ public class BlockSets { Blocks.DIORITE, Blocks.POLISHED_ANDESITE, Blocks.POLISHED_DIORITE, - Blocks.POLISHED_GRANITE).add(WOOL).add(LEAVES) + Blocks.POLISHED_GRANITE).add(WOOL).add(PERSISTENT_LEAVES) .add(LOGS).add(STRIPPED_LOGS).add(STRIPPED_WOOD) .add(CONCRETE).add(STAINED_GLASS).add(TERRACOTTA).add(GLAZED_TERRACOTTA).add(PLANKS).add(DOUBLE_SLABS).build(); @@ -534,13 +544,15 @@ public class BlockSets { public static final BlockSet SAFE_GROUND = BlockSet.builder().add(SIMPLE_CUBE).add(FALLING).add(HALF_SLABS).build(); public static final BlockSet SAFE_SIDE = - BlockSet.builder().add(explicitSafeSideBlocks).add(SAFE_GROUND).add(FEET_CAN_WALK_THROUGH).add(AIR).build(); + BlockSet.builder().add(explicitSafeSideBlocks).add(SAFE_GROUND).add(LEAVES).add(FEET_CAN_WALK_THROUGH).add(AIR).build(); public static final BlockSet SAFE_CEILING = BlockSet.builder() .add(STAIRS) .add(HALF_SLABS) .add(FEET_CAN_WALK_THROUGH) .add(SIMPLE_CUBE) + // Decaying leaves are usually not considered safe + .add(LEAVES) .add(AIR).add(Blocks.VINE, Blocks.CACTUS).build(); /** diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/RecordingWorld.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/RecordingWorld.java index 258b1180..e72a4104 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/RecordingWorld.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/RecordingWorld.java @@ -1,10 +1,16 @@ package net.famzangl.minecraft.minebot.ai.path.world; -import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.math.BlockPos; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +/** + * Records how long (in game ticks) the changes to the world will take. Used to determine the timeout. + */ public class RecordingWorld extends WorldWithDelta { + private static final Logger LOGGER = LogManager.getLogger(RecordingWorld.class); private static final int TIME_TO_PLACE = 5; private static final int TIME_TO_FACE = 3; @@ -17,30 +23,35 @@ public RecordingWorld(WorldData currentWorld, PlayerEntity blockBreaker) { } @Override - public void setBlock(int x, int y, int z, int blockId, int meta) { - int currentBlock = getBlockStateId(x, y, z); - if (currentBlock >> 4 != 0) { - timeInTicks += getTimeToDestroy(new BlockPos(x, y, z), currentBlock); + public void setBlock(BlockPos pos, BlockState block) { + BlockState currentBlock = getBlockState(pos); + // Destroy the old one + if (!BlockSets.AIR.contains(currentBlock)) { + timeInTicks += getTimeToDestroy(pos, currentBlock); } - - if (blockId != 0) { + + // Place the new one + if (!BlockSets.AIR.contains(block)) { + LOGGER.debug("Determined time to place block {} at {}: {}", block, pos, TIME_TO_PLACE); timeInTicks += TIME_TO_PLACE; } - super.setBlock(x, y, z, blockId, meta); + super.setBlock(pos, block); } - private int getTimeToDestroy(BlockPos blockPos, int blockWithMeta) { - Block block = Block.BLOCK_STATE_IDS.getByValue(blockWithMeta >> 4).getBlock(); + private int getTimeToDestroy(BlockPos blockPos, BlockState blockState) { //how much damage to give the block per tick. The block is destroyed on damage >= 1; - // TODO: Use real block state - float hardness = block.getPlayerRelativeBlockHardness(block.getDefaultState(), blockBreaker, getBackingWorld(), blockPos); - System.out.println("TIME to destroy block at " + blockPos + ": " + 1 / hardness); - return (int) Math.round(1 / hardness) + TIME_TO_FACE; + float hardness = blockState.getPlayerRelativeBlockHardness( + blockBreaker, getBackingWorld(), blockPos + ); + LOGGER.debug("Determined time to destroy block {} at {}: {}", blockState, blockPos, 1 / hardness); + return Math.round(1 / hardness) + TIME_TO_FACE; } @Override public void setPlayerPosition(BlockPos playerPosition) { - timeInTicks += timeToWalk(playerPosition, getPlayerPosition()); + int walkingTime = timeToWalk(playerPosition, getPlayerPosition()); + LOGGER.debug("Determined time walk from {} to {}: {}", getPlayerPosition(), playerPosition, walkingTime); + this.timeInTicks += walkingTime; super.setPlayerPosition(playerPosition); } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldData.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldData.java index 83f61c40..80a40b54 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldData.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldData.java @@ -193,6 +193,10 @@ public ClientWorld getBackingWorld() { return theWorld; } + public ClientPlayerEntity getBackingPlayer() { + return thePlayerToGetPositionFrom; + } + public int getBlockStateId(BlockPos pos) { return getBlockStateId(pos.getX(), pos.getY(), pos.getZ()); } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldWithDelta.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldWithDelta.java index fa03559a..98092db7 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldWithDelta.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/path/world/WorldWithDelta.java @@ -121,20 +121,6 @@ public void setBlock(BlockPos pos, Block block) { setBlock(pos, block.getDefaultState()); } - @Deprecated - public void setBlock(BlockPos pos, int blockId, int meta) { - setBlock(pos.getX(), pos.getY(), pos.getZ(), blockId, meta); - } - - @Deprecated - public void setBlock(int x, int y, int z, int blockId, int meta) { - if (blockId > 0xfff || meta > 0xf) { - throw new IllegalArgumentException("block id/meta " + blockId + ":" - + meta + " out of range."); - } - setBlockIdAndMeta(x, y, z, blockId << 4 | meta); - } - public void setBlock(BlockPos pos, BlockState block) { setBlockIdAndMeta(pos.getX(), pos.getY(), pos.getZ(), BlockSet.getStateId(block)); } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/BlockRangeScanner.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/BlockRangeScanner.java index 6fa37c1a..0983f422 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/BlockRangeScanner.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/BlockRangeScanner.java @@ -18,9 +18,7 @@ import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; -import net.famzangl.minecraft.minebot.ai.utils.BlockArea.AreaVisitor; import net.famzangl.minecraft.minebot.ai.utils.BlockCuboid; -import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.util.math.BlockPos; @@ -55,7 +53,7 @@ public void addHandler(BlockHandler h) { handlers.add(h); for (BlockState block : h.getIds()) { int i = BlockSet.getStateId(block); - if (handlersCache.length < i) { + if (handlersCache.length <= i) { handlersCache = Arrays.copyOf(handlersCache, Math.max(handlersCache.length * 2, i + 1)); } handlersCache[i] = h; @@ -65,10 +63,10 @@ public void addHandler(BlockHandler h) { public void scanArea(WorldData world) { BlockCuboid area = new BlockCuboid<>(center.add(-HORIZONTAL_SCAN, -VERTICAL_SCAN, -HORIZONTAL_SCAN), center.add(HORIZONTAL_SCAN, VERTICAL_SCAN, HORIZONTAL_SCAN)); area.accept((world1, x, y, z) -> { - int id = world1.getBlockStateId(x, y, z); - BlockHandler handler = handlersCache[id]; + int blockStateId = world1.getBlockStateId(x, y, z); + BlockHandler handler = handlersCache.length > blockStateId ? handlersCache[blockStateId] : null; if (handler != null) { - handler.scanBlock(world1, id, x, y, z); + handler.scanBlock(world1, blockStateId, x, y, z); } }, world); for (BlockHandler handler : handlers) { diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/ChestBlockHandler.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/ChestBlockHandler.java index 0992b725..a5820294 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/ChestBlockHandler.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/ChestBlockHandler.java @@ -18,25 +18,34 @@ import net.famzangl.minecraft.minebot.ai.ItemFilter; import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; +import net.famzangl.minecraft.minebot.ai.path.world.BlockSets; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; import net.famzangl.minecraft.minebot.ai.scanner.ChestBlockHandler.ChestData; -import net.famzangl.minecraft.minebot.ai.utils.PrivateFieldUtils; +import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; -import net.minecraft.entity.item.HangingEntity; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.WallSignBlock; import net.minecraft.entity.item.ItemFrameEntity; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.state.properties.ChestType; +import net.minecraft.tileentity.SignTileEntity; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.ITextComponent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class ChestBlockHandler extends RangeBlockHandler { + private static final Logger LOGGER = LogManager.getLogger(RangeBlockHandler.class); + private static final BlockSet CHEST = BlockSet.builder().add(Blocks.CHEST, Blocks.TRAPPED_CHEST).build(); @@ -76,12 +85,18 @@ public boolean isItemAllowed(ItemStack stack) { return false; } - public boolean couldPutItem(ItemStack stack) { - return !isFullFor(stack) && isItemAllowed(stack); + public boolean couldPutItem(ItemStack stack, WorldData world) { + return !isFullFor(stack) && canOpen(world) && isItemAllowed(stack); + } + + public boolean couldTakeItem(ItemStack stack, WorldData world) { + return !isEmptyFor(stack) && canOpen(world) && isItemAllowed(stack); } - public boolean couldTakeItem(ItemStack stack) { - return !isEmptyFor(stack) && isItemAllowed(stack); + private boolean canOpen(WorldData world) { + // Won't use world state, but we assume that bot will not add / remove blocks above chest + return !ChestBlock.isBlocked(world.getBackingWorld(), pos) + && secondaryPos == null || !ChestBlock.isBlocked(world.getBackingWorld(), secondaryPos); } private boolean isEmptyFor(ItemStack stack) { @@ -104,11 +119,8 @@ public boolean isFullFor(ItemStack stack) { } public static class ChestData extends AbstractChestData { - private final int chestBlockId; - - public ChestData(BlockPos pos, int chestBlockId) { + public ChestData(BlockPos pos) { super(pos); - this.chestBlockId = chestBlockId; } @Override @@ -142,11 +154,6 @@ public String toString() { + "]"; } - public void allowItem(final ItemStack displayed) { - allowedItems.add(new SameItemFilter(displayed)); - persistentStatus.update(this); - } - public void markAsFullFor(ItemStack stack, boolean full) { removeFrom(fullItems, stack); if (full) { @@ -170,24 +177,48 @@ public void setSecondaryPos(BlockPos secondaryPos) { } private void removeFrom(ArrayList list, ItemStack stack) { - Iterator iterator = list.iterator(); - while (iterator.hasNext()) { - ItemFilter filter = iterator.next(); - if (filter.matches(stack)) { - iterator.remove(); - } - } - } - - public boolean isOfType(int id) { - return id == chestBlockId; + list.removeIf(filter -> filter.matches(stack)); } public void registerByItemFrame(ItemFrameEntity frame) { ItemStack displayed = frame.getDisplayedItem(); - if (displayed != null && !displayed.isEmpty()) { - allowItem(displayed); + if (!displayed.isEmpty()) { + allowedItems.add(new SameItemFilter(displayed)); + LOGGER.debug("Allow item by item frame for {}: {}", pos, displayed.getItem()); + persistentStatus.update(this); + } + } + + public void registerBySign(TileEntity tileEntity) { + if (!(tileEntity instanceof SignTileEntity)) { + throw new IllegalArgumentException("Expected a sign tile entity to be passed for chest at " + pos + " but got " + tileEntity); } + + Set lines = Stream.of(((SignTileEntity) tileEntity).signText) + .map(ITextComponent::getString) + .map(str -> str.trim().toLowerCase(Locale.US)) + .collect(Collectors.toSet()); + Set words = Stream.of(String.join(" ", lines) + .replaceAll("\\W+", " ") + .trim() + .split("\\s+")) + .collect(Collectors.toSet()); + + LOGGER.debug("Registered text from sign for chest at {}. Words: {}, Lines: {}", pos, words, lines); + + // Text needs to contain item name + allowedItems.add(itemToTest -> + itemToTest.getItem() != Items.AIR && ( + // Display name as full line => supports i18n + lines.contains(itemToTest.getDisplayName().getString().toLowerCase(Locale.US)) + // minecraft:xxx + || + itemToTest.getItem().getRegistryName() != null && ( + words.contains(itemToTest.getItem().getRegistryName().toString().toLowerCase(Locale.US)) + // only xxx + || words.contains(itemToTest.getItem().getRegistryName().getPath().toLowerCase(Locale.US))) + )); + persistentStatus.update(this); } } @@ -246,64 +277,63 @@ protected Collection> getTargetPositions() { public void scanBlock(WorldData world, int id, int x, int y, int z) { if (CHEST.isAt(world, x, y, z)) { BlockPos myPos = new BlockPos(x, y, z); - ChestData chest = getChestAt(myPos, id); - scanForItemFrames(world, x, y, z, chest); + ChestData chest = getChestAt(world, myPos); + scanForItemFrames(world, myPos, chest); } } - private void scanForItemFrames(WorldData world, int x, int y, int z, + private void scanForItemFrames(WorldData world, BlockPos pos, ChestData chest) { - BlockPos myPos = new BlockPos(x, y, z); - AxisAlignedBB abb = new AxisAlignedBB(x - 1, y, z - 1, x + 2, y + 1, - z + 2); + AxisAlignedBB abb = new AxisAlignedBB(pos.add(-1, 0, -1), pos.add(2, 1, 2)); List frames = world.getBackingWorld() .getEntitiesWithinAABB(ItemFrameEntity.class, abb); for (ItemFrameEntity frame : frames) { - Direction direction = getDirection(frame); - if (direction == null) { - continue; - } - BlockPos pos = PrivateFieldUtils.getFieldValue(frame, - HangingEntity.class, BlockPos.class); - Direction dir = PrivateFieldUtils.getFieldValue(frame, - HangingEntity.class, Direction.class); - if (pos.offset(dir, -1).equals(myPos)) { + BlockPos attachedTo = frame.getHangingPosition().offset(frame.getHorizontalFacing().getOpposite()); + if (attachedTo.equals(pos)) { // Frame attached to this chest chest.registerByItemFrame(frame); } } + + for (Direction dir : new Direction[] { Direction.EAST, Direction.SOUTH, Direction.NORTH, Direction.WEST }) { + BlockPos signPos = pos.offset(dir); + if (BlockSets.WALL_SIGN.isAt(world, signPos)) { + // Wall sign + BlockState state = world.getBlockState(signPos); + BlockPos attachedTo = signPos.offset(state.get(WallSignBlock.FACING).getOpposite()); + if (attachedTo.equals(pos)) { + chest.registerBySign(world.getBackingWorld().getTileEntity(signPos)); + } + } + } } - private ChestData getChestAt(BlockPos pos, int id) { + private ChestData getChestAt(WorldData world, BlockPos pos) { ChestData chest = null; - for (Direction d : new Direction[] { Direction.UP, Direction.NORTH, - Direction.SOUTH, Direction.EAST, Direction.WEST }) { - BlockPos blockPos = pos.add(d.getXOffset(), 0, d.getZOffset()); - ChestData attempted = chests.get(blockPos); - if (attempted != null && attempted.isOfType(id)) { + + // Merged chests => handle them as one + BlockState chestState = world.getBlockState(pos); + if (chestState.get(ChestBlock.TYPE) != ChestType.SINGLE) { + Direction directionAttached = ChestBlock.getDirectionToAttached(chestState); + BlockPos otherChestPos = pos.offset(directionAttached); + ChestData attempted = chests.get(otherChestPos); + if (attempted != null) { chest = attempted; if (!chest.pos.equals(pos)) { + LOGGER.debug("Merging chest at {} with {}", chest.pos, pos); chest.setSecondaryPos(pos); } } } + if (chest == null) { - chest = new ChestData(pos, id); + chest = new ChestData(pos); chests.put(pos, chest); + LOGGER.debug("Found new chest at {}", pos); } return chest; } - /** - * Gets the direction of whatever this frame is attached to - * - * @param itemFrame - * @return - */ - private Direction getDirection(ItemFrameEntity itemFrame) { - return itemFrame.getHorizontalFacing(); - } - public int getExpectedPutRating(BlockPos pos, ItemStack stack) { if (persistentStatus.isFullFor(pos, stack)) { return 10; diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/SameItemFilter.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/SameItemFilter.java index da23c3e3..1e6524da 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/SameItemFilter.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/scanner/SameItemFilter.java @@ -38,9 +38,6 @@ public boolean matches(ItemStack itemStack) { return false; } else if (itemStack.getItem() != displayed.getItem()) { return false; - } else if (!itemStack.isDamageable() - && itemStack.getDamage() != displayed.getDamage()) { - return false; } else if (!ItemStack.areItemStackTagsEqual(itemStack, displayed)) { return false; } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/AbortOnDeathStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/AbortOnDeathStrategy.java index e7ff7b08..828f9ef1 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/AbortOnDeathStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/AbortOnDeathStrategy.java @@ -45,4 +45,8 @@ public String getDescription(AIHelper helper) { return "Stop when dead."; } + @Override + public String toString() { + return "AbortOnDeathStrategy{}"; + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/CraftStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/CraftStrategy.java index cb1eb885..60af85c8 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/CraftStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/CraftStrategy.java @@ -16,7 +16,10 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.ai.strategy; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import net.famzangl.minecraft.minebot.ai.AIHelper; +import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.enchanting.CloseScreenTask; import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; @@ -26,30 +29,28 @@ import net.famzangl.minecraft.minebot.ai.task.UseItemOnBlockAtTask; import net.famzangl.minecraft.minebot.ai.task.WaitTask; import net.famzangl.minecraft.minebot.ai.task.error.TaskError; -import net.famzangl.minecraft.minebot.ai.task.inventory.ItemCountList; import net.famzangl.minecraft.minebot.ai.task.inventory.ItemWithSubtype; import net.famzangl.minecraft.minebot.ai.task.inventory.PutOnCraftingTableTask; import net.famzangl.minecraft.minebot.ai.task.inventory.TakeResultItem; -import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.client.gui.recipebook.RecipeList; import net.minecraft.client.gui.screen.inventory.ContainerScreen; import net.minecraft.client.gui.screen.inventory.CraftingScreen; +import net.minecraft.client.util.ClientRecipeBook; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.item.crafting.IRecipe; -import net.minecraft.item.crafting.Ingredient; -import net.minecraft.item.crafting.ShapedRecipe; +import net.minecraft.item.crafting.IRecipePlacer; +import net.minecraft.item.crafting.RecipeItemHelper; import net.minecraft.util.math.BlockPos; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Hashtable; -import java.util.List; +import javax.annotation.Nonnull; +import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -66,131 +67,156 @@ public class CraftStrategy extends PathFinderStrategy { private static final Logger LOGGER = LogManager .getLogger(CraftStrategy.class); + /** + * Apply all RecipeWithListAndCount in that order (used for multiple crafting steps) + */ public static final class CraftingPossibility { - public static final int SUBTYPE_IGNORED = 32767; private final ItemWithSubtype[][][] slots = new ItemWithSubtype[3][3][]; + private List recipesToApply; - public CraftingPossibility(IRecipe recipe) { - LOGGER.trace(MARKER_RECIPE, "Parsing recipe: " + recipe); - if (recipe instanceof ShapedRecipe) { - ShapedRecipe shapedRecipes = (ShapedRecipe) recipe; - LOGGER.trace(MARKER_RECIPE, "Interpreting ShapedRecipe: " - + shapedRecipes.getRecipeOutput().getItem() - .getRegistryName()); - int width = shapedRecipes.getRecipeWidth(); - int height = shapedRecipes.getRecipeHeight(); - - LOGGER.trace(MARKER_RECIPE, "Found items of size " + width - + "x" + height + ": " + shapedRecipes.getIngredients()); - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - Ingredient itemStack = shapedRecipes.getIngredients().get(x + y * width); - if (itemStack != null && itemStack.getMatchingStacks().length > 0) { - this.slots[x][y] = Stream.of(itemStack.getMatchingStacks()) - .map(ItemWithSubtype::new) - .toArray(ItemWithSubtype[]::new); - } - } - } - LOGGER.trace(MARKER_RECIPE, "Slots " + Arrays.toString(slots)); - }/* TODO: Still needed? else if (recipe instanceof ShapedOreRecipe) { - ShapedOreRecipe shapedRecipes = (ShapedOreRecipe) recipe; - try { - // Width is the first integer field. - int width = PrivateFieldUtils.getField(shapedRecipes, ShapedOreRecipe.class, Integer.TYPE).getInt(shapedRecipes); - for (int x = 0; x < width; x++) { - int height = shapedRecipes.getHeight(); - for (int y = 0; y < height; y++) { - //TODO: Test - Object itemStack = shapedRecipes.getIngredients().get(x + y - * width); - if (itemStack instanceof ItemStack) { - this.slots[x][y] = new ItemWithSubtype[] { new ItemWithSubtype( - (ItemStack) itemStack) }; - } else if (itemStack instanceof List) { - List list = (List) itemStack; - this.slots[x][y] = list.stream() - .map(ItemWithSubtype::new) - .toArray(ItemWithSubtype[]::new); - } else if (itemStack != null) { - LOGGER.error(MARKER_RECIPE, "Cannot handle " + itemStack.getClass()); - throw new IllegalArgumentException("Cannot handle " + itemStack.getClass()); - } - } - } - } catch (SecurityException e) { - throw new IllegalArgumentException("Cannot access " + recipe); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot access " + recipe); - } - } */ else { - LOGGER.error(MARKER_RECIPE, - "An item recipe has been found but the item cannot be crafted. The class " - + recipe.getClass().getCanonicalName() - + " cannot be understood."); - throw new IllegalArgumentException("Cannot (yet) craft " + recipe); - } + public CraftingPossibility(List recipesToApply) { + this.recipesToApply = recipesToApply; + } + + public List getRecipesToApply() { + return recipesToApply; } - public ItemCountList getRequiredItems(int count) { - ItemCountList list = new ItemCountList(); - for (ItemWithSubtype[][] subtypes : slots) { - for (ItemWithSubtype[] subtype : subtypes) { - list.add(subtype[0], count); + @Override + public String toString() { + return "CraftingPossibility [slots=" + Arrays.deepToString(slots) + "]"; + } + + } + + public static class RecipePlacement{ + private final ItemStack[][] stacksToPlace; + + public RecipePlacement(int width, int height) { + this.stacksToPlace = new ItemStack[height][width]; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + stacksToPlace[y][x] = new ItemStack(() -> Items.AIR, 0); } } - LOGGER.trace(MARKER_RECIPE, "Items required for " + this + ": " - + list); - return list; - } - - public boolean goodForPosition(ItemWithSubtype item, int x, int y) { - if (slots[x][y] != null) { - if (item == null) { - return false; - } else { - ItemWithSubtype genericItem = item; - return Stream.of(slots[x][y]).anyMatch( - slot -> slot.equals(item) || slot.equals(genericItem) - ); + } + + public int getWidht() { + return stacksToPlace[0].length; + } + + public int getHeight() { + return stacksToPlace.length; + } + + static RecipePlacement of(int size, IRecipe recipe, Iterator ingredients, int count) { + RecipePlacement placement = new RecipePlacement(size, size); + + new IRecipePlacer(){ + @Override + public void setSlotContents(Iterator ingredients, int slotIn, int maxAmount, int y, int x) { + ItemStack itemstack = RecipeItemHelper.unpack(ingredients.next()); + LOGGER.trace("Placing stack at {},{}: {}", x, y, itemstack); + placement.stacksToPlace[y][x] = itemstack; } + }.placeRecipe(size, size, -1, recipe, ingredients, count); + + return placement; + } + + @Nonnull + public ItemStack getStack(int x, int y) { + return stacksToPlace[y][x]; + } + + @Override + public String toString() { + return "RecipePlacement{" + + "stacksToPlace=" + Arrays.toString(stacksToPlace) + + '}'; + } + + public int getSlotsWithType(Item item) { + return (int) Stream.of(stacksToPlace) + .flatMap(Stream::of) + .filter(it -> it.getItem() == item) + .count(); + } + } + + /** + * A recipe and how often it should be used + */ + public static class RecipeWithListAndCount{ + private final RecipeList list; + private final IRecipe recipe; + private final int count; + private final RecipeItemHelper helper = new RecipeItemHelper(); + + public RecipeWithListAndCount(RecipeList list, IRecipe recipe, int count) { + this.list = list; + this.recipe = recipe; + this.count = count; + } + + public Optional getPlacement(AIHelper aiHelper) { + LOGGER.trace(MARKER_RECIPE, "Attempt to place recipe {} ", recipe); + + helper.clear(); + // Now determine all te items we have in the inventory + aiHelper.getMinecraft().player.inventory.accountStacks(helper); + int realMaxCount = Math.min(count, helper.getBiggestCraftableStack(recipe, null)); + LOGGER.trace(MARKER_RECIPE, "Requested {} stacks. Attempting to caraft {} stacks.", count, realMaxCount); + + IntList intlist = new IntArrayList(); + if (helper.canCraft(recipe, intlist, realMaxCount)) { + return Optional.of(RecipePlacement.of(3, recipe, intlist.iterator(), realMaxCount)); } else { - return item == null; + return Optional.empty(); } } @Override public String toString() { - return "CraftingPossibility [slots=" + Arrays.deepToString(slots) + "]"; + return "RecipeWithListAndCount{" + + "list=" + list + + ", recipe=" + recipe + + ", count=" + count + + '}'; } - } public static final class CraftingWish { private final int amount; - private final ItemWithSubtype item; + private final Item item; - public CraftingWish(int amount, ItemWithSubtype item) { + public CraftingWish(int amount, Item item) { this.amount = amount; this.item = item; } - public List getPossibility() { - return Collections.emptyList(); - /* TODO try { - return CraftingManager.REGISTRY.getKeys().stream() - .map(id -> CraftingManager.REGISTRY.getObject(id)) - .filter(recipe -> { - ItemStack out = recipe.getRecipeOutput(); - return out != null && new ItemWithSubtype(out).equals(item); - }) - .map(CraftingPossibility::new) - .collect(Collectors.toList()); - } catch (IllegalArgumentException e) { - System.err.println("Cannot craft: " + e.getMessage()); - return Collections.emptyList(); - }*/ + public List getPossibility(AIHelper helper) { + ClientRecipeBook book = helper.getMinecraft().player.getRecipeBook(); + // Minecraft stores a list of list of recipies. + List available = book.getRecipes(); + + // Now find the one we need + List found = new ArrayList<>(); + available.forEach(list -> + list.getRecipes().forEach(recipe -> { + ItemStack out = recipe.getRecipeOutput(); + if (out.getItem() == item) { + found.add(new RecipeWithListAndCount(list, recipe, + // ceil + (amount + out.getCount() - 1) / out.getCount())); + } + })); + + LOGGER.debug(MARKER_RECIPE, "For crafting {}, the following recipes were found: {}", item, found); + + + return found.stream().map(it -> new CraftingPossibility(Collections.singletonList(it))).collect(Collectors.toList()); } @Override @@ -301,19 +327,23 @@ protected void addTasksForTarget(BlockPos currentPos) { ArrayList tables = craftingTableHandler .getReachableForPos(currentPos); CraftingTableData table = tables.get(0); - List possibilities = wish.getPossibility(); + List possibilities = wish.getPossibility(helper); LOGGER.trace("Crafting one of: " + possibilities); - ItemWithSubtype[][] grid = getCraftablePossibility(helper, - possibilities); - if (grid == null) { + Optional grid = possibilities + .stream() + // For now, only support one recipe. TODO: Find out how to best support multiple of them + .map(p -> p.getRecipesToApply().get(0)) + .map(p -> p.getPlacement(helper)) + .flatMap(optional -> optional.isPresent() ? Stream.of(optional.get()) : Stream.of()) + .findAny(); + if (!grid.isPresent()) { failed = true; - System.err.println("Could not find any way to craft this."); - // FIXME: Desync. Error. + AIChatController.addChatLine("Could not find a way to craft this item"); return; } - LOGGER.trace(MARKER_RECIPE, "Crafting: " + Arrays.deepToString(grid)); + LOGGER.trace(MARKER_RECIPE, "Crafting: {}", grid.get()); addTask(new UseItemOnBlockAtTask(table.pos) { @Override @@ -327,106 +357,44 @@ public boolean isFinished(AIHelper aiHelper) { && aiHelper.getMinecraft().currentScreen instanceof CraftingScreen; } }); - addCraftTaks(grid); + addCraftTaks(grid.get()); addTask(new WaitTask(5)); addTask(new TakeResultItem(CraftingScreen.class, 0)); addTask(new WaitTask(5)); addTask(new CloseScreenTask()); } - private void addCraftTaks(ItemWithSubtype[][] grid) { + private void addCraftTaks(RecipePlacement grid) { int missing = getMissing(); if (missing <= 0) { return; } for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { - if (grid[x][y] != null) { - int inventoryTotal = countInInventory(grid[x][y]); - int slotCount = countInGrid(grid, grid[x][y]); - int itemCount = Math.min(inventoryTotal / slotCount, + ItemStack stack = grid.getStack(x, y); + if (stack.getItem() != Items.AIR) { + int inventoryTotal = countInInventory(stack.getItem()); + int slotsWithThatType = grid.getSlotsWithType(stack.getItem()); + int itemCount = Math.min(inventoryTotal / slotsWithThatType, missing); addTask(new PutOnCraftingTableTask(y * 3 + x, - grid[x][y], itemCount)); + stack.getItem(), itemCount)); addTask(new WaitTask(3)); } } } } - private int countInGrid(ItemWithSubtype[][] grid, - ItemWithSubtype itemWithSubtype) { - int count = 0; - for (ItemWithSubtype[] ss : grid) { - for (ItemWithSubtype s : ss) { - if (itemWithSubtype.equals(s)) { - count++; - } - } - } - return count; - } - - private int countInInventory(ItemWithSubtype itemWithSubtype) { + private int countInInventory(Item itemWithSubtype) { int count = 0; for (ItemStack stack : helper.getMinecraft().player.inventory.mainInventory) { - if (itemWithSubtype.equals(ItemWithSubtype.fromStack(stack))) { - count += stack.getMaxStackSize(); + if (itemWithSubtype.equals(stack.getItem())) { + count += stack.getCount(); } } return count; } - /** - * Gets an array of items that specifies how they need to be placed on - * the crafting grid. - * - * @param aiHelper - * @param possibilities - * @return - */ - private ItemWithSubtype[][] getCraftablePossibility(AIHelper aiHelper, - List possibilities) { - for (CraftingPossibility possibility : possibilities) { - ItemWithSubtype[][] assignedSlots = new ItemWithSubtype[3][3]; - // TODO: Order this in a better way. We need to have multiples of our item count first. - for (ItemStack stack : aiHelper.getMinecraft().player.inventory.mainInventory) { - if (stack == null) { - continue; - } - ItemWithSubtype item = new ItemWithSubtype(stack); - int leftOver = stack.getMaxStackSize(); - for (int x = 0; x < 3 && leftOver > 0; x++) { - for (int y = 0; y < 3 && leftOver > 0; y++) { - if (possibility.goodForPosition(item, x, y) - && assignedSlots[x][y] == null) { - assignedSlots[x][y] = item; - leftOver--; - LOGGER.trace("Placing at " + x + "," + y + ": " - + item); - } - } - } - } - boolean allGood = true; - for (int x = 0; x < 3; x++) { - for (int y = 0; y < 3; y++) { - if (!possibility.goodForPosition(assignedSlots[x][y], x, y)) { - allGood = false; - LOGGER.warn(MARKER_RECIPE, "Placed wrong item at " - + x + "," + y + ": " + assignedSlots[x][y]); - } - } - } - if (allGood) { - return assignedSlots; - } - } - LOGGER.warn("Could not find any way to craft any of " - + possibilities); - return null; - } - @Override public String toString() { return "CraftingTableFinder [wish=" + wish + ", failed=" + failed @@ -434,17 +402,11 @@ public String toString() { } } - public CraftStrategy(int amount, ItemWithSubtype item) { + public CraftStrategy(int amount, Item item) { super(new CraftingTableFinder(new CraftingWish(amount, item)), "Crafting"); } - public CraftStrategy(int amount, BlockState itemType) { - super(null, null); - throw new UnsupportedOperationException("TODO"); - // TODO this(amount, new ItemWithSubtype(itemType)); - } - @Override public void searchTasks(AIHelper helper) { // If chest open, close it. diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/DoNotSuffocateStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/DoNotSuffocateStrategy.java index d6cf547c..45e57d09 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/DoNotSuffocateStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/DoNotSuffocateStrategy.java @@ -78,7 +78,11 @@ private boolean safeFeet(AIHelper helper, BlockPos p) { @Override public String getDescription(AIHelper helper) { - return "Do not suffocate in walls."; + return "Destroy block the player is hanging in"; } + @Override + public String toString() { + return "DoNotSuffocateStrategy{}"; + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/PlaceTorchStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/PlaceTorchStrategy.java index 6ac7f5ca..887e2a13 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/PlaceTorchStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/PlaceTorchStrategy.java @@ -60,7 +60,10 @@ public String toString() { } } - private static final BlockSet CAN_PLACE_ON = BlockSets.SIMPLE_CUBE; + // TODO: Instead, use net.minecraft.block.Block.hasEnoughSolidSide valid block states. + private static final BlockSet CAN_PLACE_ON = BlockSets.SIMPLE_CUBE + // Cannot place a torch on leaves + .minus(BlockSets.LEAVES); private static final BlockItemFilter TORCH_FILTER = new BlockItemFilter( Blocks.TORCH); diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/RunFileStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/RunFileStrategy.java index 89b6cbb1..ad4077b1 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/RunFileStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/RunFileStrategy.java @@ -3,6 +3,7 @@ import net.famzangl.minecraft.minebot.ai.AIHelper; import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.command.IAIControllable; +import net.famzangl.minecraft.minebot.ai.command.StackBuilder; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.ChatScreen; @@ -28,6 +29,11 @@ public int requestUseStrategy(AIStrategy strategy) { return 0; } + @Override + public StackBuilder getStackBuilder() { + throw new UnsupportedOperationException("Cannot use /minebot stack inside a script. Use stack: instead"); + } + @Override public Minecraft getMinecraft() { return controlled.getMinecraft(); diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/StoreStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/StoreStrategy.java index 5954b916..9b4d937d 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/StoreStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/StoreStrategy.java @@ -75,7 +75,7 @@ protected float rateDestination(int distance, int x, int y, int z) { if (chests != null) { for (ChestData c : chests) { for (ItemStack stack : helper.getMinecraft().player.inventory.mainInventory) { - if (c.couldPutItem(stack)) { + if (c.couldPutItem(stack, helper.getWorld())) { return distance + chestBlockHandler.getExpectedPutRating(c.getPos(), stack); } } @@ -93,7 +93,7 @@ protected void addTasksForTarget(BlockPos currentPos) { NonNullList inventory = helper.getMinecraft().player.inventory.mainInventory; for (int i = 0; i < inventory.size(); i++) { final ItemStack stack = inventory.get(i); - if (c.couldPutItem(stack)) { + if (c.couldPutItem(stack, helper.getWorld())) { if (!chestOpen) { addTask(new OpenChestTask(c.getSecondaryPos(), c.getPos())); diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/UnstoreStrategy.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/UnstoreStrategy.java index 07092852..32f57b4f 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/UnstoreStrategy.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/strategy/UnstoreStrategy.java @@ -18,6 +18,7 @@ import net.famzangl.minecraft.minebot.ai.AIHelper; import net.famzangl.minecraft.minebot.ai.enchanting.CloseScreenTask; +import net.famzangl.minecraft.minebot.ai.path.world.WorldData; import net.famzangl.minecraft.minebot.ai.scanner.BlockRangeFinder; import net.famzangl.minecraft.minebot.ai.scanner.BlockRangeScanner; import net.famzangl.minecraft.minebot.ai.scanner.ChestBlockHandler; @@ -48,14 +49,14 @@ public Wishlist(InventoryDefinition wantedInventory) { this.wantedInventory = wantedInventory; } - public boolean couldUseOneOf(ChestData chestData) { + public boolean couldUseOneOf(ChestData chestData, WorldData world) { for (int i = 0; i < 36; i++) { InventorySlot slot = wantedInventory.getSlot(i); if (slot.isEmpty() || noMoreWork[i]) { continue; } - if (chestData.couldTakeItem(slot.getFakeMcStack())) { + if (chestData.couldTakeItem(slot.getFakeMcStack(), world)) { return true; } } @@ -63,11 +64,11 @@ public boolean couldUseOneOf(ChestData chestData) { } public ArrayList getTakeTasks(List inventory, - ChestData chestData) { + ChestData chestData, WorldData world) { ArrayList tasks = new ArrayList(); for (int inventorySlot = 0; inventorySlot < 36; inventorySlot++) { InventorySlot slot = wantedInventory.getSlot(inventorySlot); - if (slot.isEmpty() || !chestData.couldTakeItem(slot.getFakeMcStack())) { + if (slot.isEmpty() || !chestData.couldTakeItem(slot.getFakeMcStack(), world)) { continue; } ItemStack itemInSlot = inventory.get(inventorySlot); @@ -186,7 +187,7 @@ protected float rateDestination(int distance, int x, int y, int z) { .getReachableForPos(new BlockPos(x, y, z)); if (chests != null) { for (ChestData chestData : chests) { - if (list.couldUseOneOf(chestData)) { + if (list.couldUseOneOf(chestData, world)) { return distance; } } @@ -200,7 +201,7 @@ protected void addTasksForTarget(BlockPos currentPos) { .getReachableForPos(currentPos); for (final ChestData chestData : chests) { NonNullList inventory = helper.getMinecraft().player.inventory.mainInventory; - ArrayList tasks = list.getTakeTasks(inventory, chestData); + ArrayList tasks = list.getTakeTasks(inventory, chestData, helper.getWorld()); if (!tasks.isEmpty()) { addTask(new OpenChestTask(chestData.getSecondaryPos(), chestData.getPos())); diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/AITask.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/AITask.java index 259a02d5..ff870898 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/AITask.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/AITask.java @@ -92,15 +92,6 @@ public int getGameTickTimeout(AIHelper helper) { * @return The expected game tick timeout. */ protected int computeGameTickTimeout(AIHelper helper) { - // List blocks = getBlocksToDestory(helper.getWorld()); - // if (blocks.isEmpty()) { - // return 20 * 5; - // } else { - // int time = 5; - // for (BlockPos b : blocks) { - // time += getTimeToMine(helper.getWorld(), b); - // } - // } RecordingWorld world = new RecordingWorld(helper.getWorld(), helper.getMinecraft().player); if (applyToDelta(world)) { diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/error/StrategyDeactivatedError.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/error/StrategyDeactivatedError.java index b736b832..d3a73ad3 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/error/StrategyDeactivatedError.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/error/StrategyDeactivatedError.java @@ -27,4 +27,8 @@ public boolean shouldBeDisplayed() { return false; } + @Override + public String toString() { + return "StrategyDeactivatedError{}"; + } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/inventory/PutOnCraftingTableTask.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/inventory/PutOnCraftingTableTask.java index fa28cfa9..9750cd6a 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/inventory/PutOnCraftingTableTask.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/inventory/PutOnCraftingTableTask.java @@ -17,7 +17,9 @@ package net.famzangl.minecraft.minebot.ai.task.inventory; import net.famzangl.minecraft.minebot.ai.AIHelper; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.util.NonNullList; /** @@ -32,12 +34,18 @@ public class PutOnCraftingTableTask extends MoveInInventoryTask { * Slot 0..8 */ private final int craftingSlot; - private final ItemWithSubtype item; + private final Item item; private final int itemCount; - public PutOnCraftingTableTask(int craftingSlot, ItemWithSubtype item, + public PutOnCraftingTableTask(int craftingSlot, Item item, int itemCount) { super(); + if (item == Items.AIR) { + throw new IllegalArgumentException("Cannot place air"); + } + if (itemCount <= 0) { + throw new IllegalArgumentException("Count out of range: " + itemCount); + } this.craftingSlot = craftingSlot; this.item = item; this.itemCount = itemCount; @@ -48,7 +56,7 @@ protected int getFromStack(AIHelper aiHelper) { NonNullList mainInventory = aiHelper.getMinecraft().player.inventory.mainInventory; int inventorySlot = -1; for (int i = 0; i < mainInventory.size(); i++) { - if (item.equals(ItemWithSubtype.fromStack(mainInventory.get(i)))) { + if (item.equals(mainInventory.get(i).getItem())) { inventorySlot = i; } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/move/UpwardsMoveTask.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/move/UpwardsMoveTask.java index 29e7732d..ef776307 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/move/UpwardsMoveTask.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/move/UpwardsMoveTask.java @@ -59,19 +59,14 @@ public void runTick(AIHelper aiHelper, TaskOperations taskOperations) { obsidianMining = true; } aiHelper.faceAndDestroy(pos.add(0, 1, 0)); - } else if (!BlockSets.AIR.isAt(aiHelper.getWorld(), pos.add(0, -1, 0))) { + } else if (!BlockSets.AIR.isAt(aiHelper.getWorld(), pos.add(0, -1, 0)) + && !(isAtDesiredHeight(aiHelper) && hasPlacedBlock)) { aiHelper.faceAndDestroy(pos.add(0, -1, 0)); } else { super.runTick(aiHelper, taskOperations); } } -// @Override -// public int getGameTickTimeout(AIHelper helper) { -// return super.getGameTickTimeout(helper) -// + (obsidianMining ? HorizontalMoveTask.OBSIDIAN_TIME : 0); -// } - @Override public String toString() { return "UpwardsMoveTask [pos=" + pos + "]"; diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/JumpingPlaceBlockAtFloorTask.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/JumpingPlaceBlockAtFloorTask.java index 4f36625d..13934b7a 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/JumpingPlaceBlockAtFloorTask.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/JumpingPlaceBlockAtFloorTask.java @@ -39,7 +39,7 @@ protected int getRelativePlaceAtY() { @Override public boolean isFinished(AIHelper aiHelper) { - return aiHelper.isStandingOn(pos) && super.isFinished(aiHelper); + return hasPlacedBlock && isAtDesiredHeight(aiHelper) && super.isFinished(aiHelper); } @Override diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/PlaceBlockAtFloorTask.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/PlaceBlockAtFloorTask.java index 57405195..a23a6a18 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/PlaceBlockAtFloorTask.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/ai/task/place/PlaceBlockAtFloorTask.java @@ -33,6 +33,7 @@ public class PlaceBlockAtFloorTask extends AITask { private int faceTimer; protected final BlockPos pos; private Vec3d positionToFace; + protected boolean hasPlacedBlock; /** * @@ -120,6 +121,7 @@ protected void reFace(AIHelper aiHelper) { protected void tryPlaceBlock(AIHelper aiHelper) { if (isAtDesiredHeight(aiHelper) && isFacingRightBlock(aiHelper)) { aiHelper.overrideUseItem(); + hasPlacedBlock = true; } } diff --git a/Minebot/src/main/java/net/famzangl/minecraft/minebot/build/commands/CommandClearArea.java b/Minebot/src/main/java/net/famzangl/minecraft/minebot/build/commands/CommandClearArea.java index 9785a904..342b49de 100644 --- a/Minebot/src/main/java/net/famzangl/minecraft/minebot/build/commands/CommandClearArea.java +++ b/Minebot/src/main/java/net/famzangl/minecraft/minebot/build/commands/CommandClearArea.java @@ -16,80 +16,128 @@ *******************************************************************************/ package net.famzangl.minecraft.minebot.build.commands; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import net.famzangl.minecraft.minebot.ai.AIHelper; -import net.famzangl.minecraft.minebot.ai.command.AIChatController; -import net.famzangl.minecraft.minebot.ai.command.AICommand; -import net.famzangl.minecraft.minebot.ai.command.AICommandInvocation; -import net.famzangl.minecraft.minebot.ai.command.AICommandParameter; -import net.famzangl.minecraft.minebot.ai.command.ParameterType; +import net.famzangl.minecraft.minebot.ai.command.CommandEvaluationException; +import net.famzangl.minecraft.minebot.ai.command.IAIControllable; import net.famzangl.minecraft.minebot.ai.command.SafeStrategyRule; +import net.famzangl.minecraft.minebot.ai.commands.Commands; +import net.famzangl.minecraft.minebot.ai.commands.EnumArgument; import net.famzangl.minecraft.minebot.ai.path.ClearAreaPathfinder; import net.famzangl.minecraft.minebot.ai.path.ClearAreaPathfinder.ClearMode; +import net.famzangl.minecraft.minebot.ai.path.world.BlockSet; +import net.famzangl.minecraft.minebot.ai.path.world.BlockSets; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; -import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; import net.famzangl.minecraft.minebot.ai.strategy.PathFinderStrategy; import net.famzangl.minecraft.minebot.ai.utils.BlockCuboid; -import net.minecraft.block.BlockState; +import net.minecraft.command.arguments.BlockPosArgument; +import net.minecraft.command.arguments.BlockStateArgument; +import net.minecraft.command.arguments.BlockStateInput; +import net.minecraft.command.arguments.ILocationArgument; import net.minecraft.util.math.BlockPos; -@AICommand(helpText = "Clears the selected area.", name = "minebuild") +import java.util.function.Function; + +/** + * Clear a selected / specified area + */ public class CommandClearArea { - private static final class ClearAreaStrategy extends PathFinderStrategy { - private String progress = "?"; - private boolean done = false; - private final ClearAreaPathfinder pathFinder; + private static final class ClearAreaStrategy extends PathFinderStrategy { + private String progress = "?"; + private boolean done = false; + private final ClearAreaPathfinder pathFinder; + + private ClearAreaStrategy(ClearAreaPathfinder pathFinder) { + super(pathFinder, ""); + this.pathFinder = pathFinder; + } + + @Override + public void searchTasks(AIHelper helper) { + final int max = pathFinder.getAreaSize(); + if (max <= 100000) { + float toClearCount = pathFinder.getToClearCount(helper); + progress = 100 - Math.round(100f * toClearCount / max) + "%"; + done = toClearCount == 0; + } + if (!done) { + super.searchTasks(helper); + } + } + + @Override + public String getDescription(AIHelper helper) { + return "Clear area: " + progress; + } + + @Override + public boolean hasFailed() { + return !done; + } + } - private ClearAreaStrategy(ClearAreaPathfinder pathFinder) { - super(pathFinder, ""); - this.pathFinder = pathFinder; - } + public static void register(LiteralArgumentBuilder dispatcher, LiteralArgumentBuilder minebuild) { + // /minebuild clear uses the area set by minebuild + LiteralArgumentBuilder normalClearCommand = Commands.literal("clear"); + generateCommand(normalClearCommand, context -> getArea(context.getSource().getAiHelper())); + minebuild.then(normalClearCommand); - @Override - public void searchTasks(AIHelper helper) { - final int max = pathFinder.getAreaSize(); - if (max <= 100000) { - float toClearCount = pathFinder.getToClearCount(helper); - progress = 100 - Math.round(100f * toClearCount / max) + "%"; - done = toClearCount == 0; - } - super.searchTasks(helper); - } + // Minecraft syntax: /minebot clear [from] [to] + RequiredArgumentBuilder clearWithRange = Commands.argument("to", BlockPosArgument.blockPos()); + generateCommand(clearWithRange, context -> new BlockCuboid<>( + Commands.getBlockPos(context, "from"), + Commands.getBlockPos(context, "to") + )); + dispatcher.then( + Commands.literal("clear") + .then( + Commands.argument("from", BlockPosArgument.blockPos()).then(clearWithRange))); + } - @Override - public String getDescription(AIHelper helper) { - return "Clear area: " + progress; - } + /** + * Add mode and block type parameters + * @param base base command + */ + private static void generateCommand(ArgumentBuilder base, + Function, BlockCuboid> areaGetter) { + Commands.optional(base, + __ -> BlockSets.EMPTY.invert(), + "block", + BlockStateArgument.blockState(), + BlockStateInput.class, + (blockStateInput, context) -> BlockSet.builder().add(blockStateInput.getState()).build(), + (withBlock, block) -> Commands.optional( + withBlock, + __ -> ClearMode.VISIT_EVERY_POS, + "mode", + EnumArgument.of(ClearMode.class), + ClearMode.class, + (builder, mode) -> builder.executes( + context -> requestUseStrategy(context, areaGetter.apply(context), block, mode) + ) + ) + ); + } - @Override - public boolean hasFailed() { - return !done; - } - } + private static int requestUseStrategy(CommandContext context, BlockCuboid area, + Commands.ArgExecutorSupplier toClear, + Commands.ArgExecutorSupplier mode) { + return context.getSource().requestUseStrategy( + new ClearAreaStrategy(new ClearAreaPathfinder(area, toClear.get(context), mode.get(context))), + SafeStrategyRule.DEFEND_MINING); + } - @AICommandInvocation(safeRule = SafeStrategyRule.DEFEND_MINING) - public static AIStrategy run( - AIHelper helper, - @AICommandParameter(type = ParameterType.FIXED, fixedName = "clear", description = "") String nameArg, - @AICommandParameter(type = ParameterType.BLOCK_STATE, description = "restrict to block", optional = true) BlockState block, - @AICommandParameter(type = ParameterType.ENUM, description = "clear mode", optional = true) ClearMode mode) { - BlockCuboid area = getArea(helper); - if (area != null) { - return new ClearAreaStrategy(new ClearAreaPathfinder(area, block, - mode == null ? ClearMode.VISIT_EVERY_POS : mode)); - } else { - return null; - } - } - - public static BlockCuboid getArea(AIHelper helper) { - final BlockPos pos1 = helper.getPos1(); - final BlockPos pos2 = helper.getPos2(); - if (pos1 == null || pos2 == null) { - AIChatController.addChatLine("Set positions first."); - return null; - } else { - return new BlockCuboid<>(pos1, pos2); - } - } + public static BlockCuboid getArea(AIHelper helper) { + final BlockPos pos1 = helper.getPos1(); + final BlockPos pos2 = helper.getPos2(); + if (pos1 == null || pos2 == null) { + throw new CommandEvaluationException("No area has been set yet. Set an area to fill using /minebot posN"); + } else { + return new BlockCuboid<>(pos1, pos2); + } + } }