diff --git a/src/api/java/baritone/api/selection/ISelectionManager.java b/src/api/java/baritone/api/selection/ISelectionManager.java index d7513160..763714e7 100644 --- a/src/api/java/baritone/api/selection/ISelectionManager.java +++ b/src/api/java/baritone/api/selection/ISelectionManager.java @@ -1,6 +1,7 @@ package baritone.api.selection; import baritone.api.utils.BetterBlockPos; +import net.minecraft.util.EnumFacing; /** * The selection manager handles setting Baritone's selections. You can set the selection here, as well as retrieving @@ -46,7 +47,52 @@ public interface ISelectionManager { * For anything expecting only one selection, this method is provided. However, to enforce multi-selection support, * this method will only return a selection if there is ONLY one. * - * @return The only selection. + * @return The only selection, or null if there isn't only one. */ ISelection getOnlySelection(); + + /** + * This method will always return the last selection. ONLY use this if you want to, for example, modify the most + * recent selection based on user input. ALWAYS use {@link #getOnlySelection()} or, ideally, + * {@link #getSelections()} for retrieving the content of selections. + * + * @return The last selection, or null if it doesn't exist. + */ + ISelection getLastSelection(); + + /** + * Replaces the specified {@link ISelection} with one expanded in the specified direction by the specified number of + * blocks. Returns the new selection. + * + * @param selection The selection to expand. + * @param direction The direction to expand the selection. + * @param blocks How many blocks to expand it. + * @return The new selection, expanded as specified. + */ + ISelection expand(ISelection selection, EnumFacing direction, int blocks); + + /** + * Replaces the specified {@link ISelection} with one contracted in the specified direction by the specified number + * of blocks. + * + * Note that, for example, if the direction specified is UP, the bottom of the selection will be shifted up. If it + * is DOWN, the top of the selection will be shifted down. + * + * @param selection The selection to contract. + * @param direction The direction to contract the selection. + * @param blocks How many blocks to contract it. + * @return The new selection, contracted as specified. + */ + ISelection contract(ISelection selection, EnumFacing direction, int blocks); + + /** + * Replaces the specified {@link ISelection} with one shifted in the specified direction by the specified number of + * blocks. This moves the whole selection. + * + * @param selection The selection to shift. + * @param direction The direction to shift the selection. + * @param blocks How many blocks to shift it. + * @return The new selection, shifted as specified. + */ + ISelection shift(ISelection selection, EnumFacing direction, int blocks); } diff --git a/src/api/java/baritone/api/utils/command/datatypes/ForEnumFacing.java b/src/api/java/baritone/api/utils/command/datatypes/ForEnumFacing.java new file mode 100644 index 00000000..f65f1c55 --- /dev/null +++ b/src/api/java/baritone/api/utils/command/datatypes/ForEnumFacing.java @@ -0,0 +1,38 @@ +package baritone.api.utils.command.datatypes; + +import baritone.api.utils.command.helpers.arguments.ArgConsumer; +import baritone.api.utils.command.helpers.tabcomplete.TabCompleteHelper; +import net.minecraft.util.EnumFacing; + +import java.util.Arrays; +import java.util.Locale; +import java.util.stream.Stream; + +public class ForEnumFacing implements IDatatypeFor { + private final EnumFacing facing; + + public ForEnumFacing() { + facing = null; + } + + public ForEnumFacing(ArgConsumer consumer) { + facing = EnumFacing.valueOf(consumer.getString().toUpperCase(Locale.US)); + } + + @Override + public EnumFacing get() { + return facing; + } + + @Override + public Stream tabComplete(ArgConsumer consumer) { + return new TabCompleteHelper() + .append( + Arrays.stream(EnumFacing.values()) + .map(EnumFacing::getName) + .map(String::toLowerCase) + ) + .filterPrefix(consumer.getString()) + .stream(); + } +} diff --git a/src/api/java/baritone/api/utils/command/defaults/SelCommand.java b/src/api/java/baritone/api/utils/command/defaults/SelCommand.java index c696bd72..366ec9f2 100644 --- a/src/api/java/baritone/api/utils/command/defaults/SelCommand.java +++ b/src/api/java/baritone/api/utils/command/defaults/SelCommand.java @@ -24,18 +24,21 @@ import baritone.api.schematic.FillBomSchematic; import baritone.api.schematic.ShellSchematic; import baritone.api.schematic.WallsSchematic; import baritone.api.selection.ISelection; +import baritone.api.selection.ISelectionManager; import baritone.api.utils.BetterBlockPos; import baritone.api.utils.BlockOptionalMeta; import baritone.api.utils.IRenderer; import baritone.api.utils.ISchematic; import baritone.api.utils.command.Command; import baritone.api.utils.command.datatypes.ForBlockOptionalMeta; +import baritone.api.utils.command.datatypes.ForEnumFacing; import baritone.api.utils.command.datatypes.RelativeBlockPos; import baritone.api.utils.command.exception.CommandInvalidStateException; import baritone.api.utils.command.exception.CommandInvalidTypeException; import baritone.api.utils.command.helpers.arguments.ArgConsumer; import baritone.api.utils.command.helpers.tabcomplete.TabCompleteHelper; import net.minecraft.init.Blocks; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.Vec3i; @@ -43,11 +46,13 @@ import java.awt.Color; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; import static java.util.Arrays.asList; public class SelCommand extends Command { + private ISelectionManager manager = baritone.getSelectionManager(); private BetterBlockPos pos1 = null; public SelCommand() { @@ -75,22 +80,36 @@ public class SelCommand extends Command { pos1 = pos; logDirect("Position 1 has been set"); } else { - baritone.getSelectionManager().addSelection(pos1, pos); + manager.addSelection(pos1, pos); pos1 = null; logDirect("Selection added"); } } else if (action == Action.CLEAR) { + args.requireMax(0); pos1 = null; - logDirect(String.format( - "Removed %d selections", - baritone.getSelectionManager().removeAllSelections().length - )); - } else { + logDirect(String.format("Removed %d selections", manager.removeAllSelections().length)); + } else if (action == Action.UNDO) { + args.requireMax(0); + + if (pos1 != null) { + pos1 = null; + logDirect("Undid pos1"); + } else { + ISelection[] selections = manager.getSelections(); + + if (selections.length < 1) { + throw new CommandInvalidStateException("Nothing to undo!"); + } else { + pos1 = manager.removeSelection(selections[selections.length - 1]).pos1(); + logDirect("Undid pos2"); + } + } + } else if (action == Action.SET || action == Action.WALLS || action == Action.SHELL || action == Action.CLEARAREA) { BlockOptionalMeta type = action == Action.CLEARAREA ? new BlockOptionalMeta(Blocks.AIR) : args.getDatatypeFor(ForBlockOptionalMeta.class); args.requireMax(0); - ISelection[] selections = baritone.getSelectionManager().getSelections(); + ISelection[] selections = manager.getSelections(); if (selections.length == 0) { throw new CommandInvalidStateException("No selections"); @@ -125,6 +144,36 @@ public class SelCommand extends Command { baritone.getBuilderProcess().build("Fill", composite, origin); logDirect("Filling now"); + } else if (action == Action.EXPAND || action == Action.CONTRACT || action == Action.SHIFT) { + args.requireExactly(3); + TransformTarget transformTarget = TransformTarget.getByName(args.getString()); + + if (transformTarget == null) { + throw new CommandInvalidStateException("Invalid transform type"); + } + + EnumFacing direction = args.getDatatypeFor(ForEnumFacing.class); + int blocks = args.getAs(Integer.class); + + ISelection[] selections = manager.getSelections(); + + if (selections.length < 1) { + throw new CommandInvalidStateException("No selections found"); + } + + selections = transformTarget.transform(selections); + + for (ISelection selection : selections) { + if (action == Action.EXPAND) { + manager.expand(selection, direction, blocks); + } else if (action == Action.CONTRACT) { + manager.contract(selection, direction, blocks); + } else { + manager.shift(selection, direction, blocks); + } + } + + logDirect(String.format("Transformed %d selections", selections.length)); } } @@ -141,7 +190,27 @@ public class SelCommand extends Command { if (action != null) { if (action == Action.POS1 || action == Action.POS2) { - return args.tabCompleteDatatype(RelativeBlockPos.class); + if (args.hasAtMost(3)) { + return args.tabCompleteDatatype(RelativeBlockPos.class); + } + } else if (action == Action.SET || action == Action.WALLS || action == Action.CLEARAREA) { + if (args.hasExactlyOne()) { + return args.tabCompleteDatatype(ForBlockOptionalMeta.class); + } + } else if (action == Action.EXPAND || action == Action.CONTRACT || action == Action.SHIFT) { + if (args.hasExactlyOne()) { + return new TabCompleteHelper() + .append(TransformTarget.getAllNames()) + .filterPrefix(args.getString()) + .sortAlphabetically() + .stream(); + } else { + TransformTarget target = TransformTarget.getByName(args.getString()); + + if (target != null && args.hasExactlyOne()) { + return args.tabCompleteDatatype(ForEnumFacing.class); + } + } } } } @@ -152,21 +221,38 @@ public class SelCommand extends Command { @Override public List getLongDesc() { return asList( + "The sel command allows you to manipulate Baritone's selections, similarly to WorldEdit.", "", + "Using these selections, you can clear areas, fill them with blocks, or something else.", "", "Usage:", - "> " + "> sel pos1/p1/1 - Set position 1 to your current position.", + "> sel pos1/p1/1 - Set position 1 to a relative position.", + "> sel pos2/p2/2 - Set position 2 to your current position.", + "> sel pos2/p2/2 - Set position 2 to a relative position.", + "> sel clear/c - Clear the selection.", + "> sel undo/u - Undo the last action (setting positions, creating selections, etc.)", + "> sel walls/w [block] - Fill in the walls of the selection with a specified block, or the block in your hand.", + "> sel shell/sh [block] - The same as walls, but fills in a ceiling and floor too.", + "> sel cleararea/ca - Basically 'set air'." ); } enum Action { - POS1("pos1", "p1"), - POS2("pos2", "p2"), + POS1("pos1", "p1", "1"), + POS2("pos2", "p2", "2"), + CLEAR("clear", "c"), + UNDO("undo", "u"), + SET("set", "fill", "s", "f"), WALLS("walls", "w"), - SHELL("shell", "sh"), - CLEARAREA("cleararea", "ca"); + SHELL("shell", "shl"), + CLEARAREA("cleararea", "ca"), + + EXPAND("expand", "ex"), + CONTRACT("contact", "ct"), + SHIFT("shift", "sh"); private final String[] names; @@ -197,6 +283,46 @@ public class SelCommand extends Command { } } + enum TransformTarget { + ALL(sels -> sels, "all", "a"), + NEWEST(sels -> new ISelection[] {sels[0]}, "newest", "n"), + OLDEST(sels -> new ISelection[] {sels[sels.length - 1]}, "oldest", "o"); + + private final Function transform; + private final String[] names; + + TransformTarget(Function transform, String... names) { + this.transform = transform; + this.names = names; + } + + public ISelection[] transform(ISelection[] selections) { + return transform.apply(selections); + } + + public static TransformTarget getByName(String name) { + for (TransformTarget target : TransformTarget.values()) { + for (String alias : target.names) { + if (alias.equalsIgnoreCase(name)) { + return target; + } + } + } + + return null; + } + + public static String[] getAllNames() { + Set names = new HashSet<>(); + + for (TransformTarget target : TransformTarget.values()) { + names.addAll(asList(target.names)); + } + + return names.toArray(new String[0]); + } + } + @Override public void onRenderPass(RenderEvent event) { if (!settings.renderSelectionCorners.value || pos1 == null) { diff --git a/src/main/java/baritone/selection/Selection.java b/src/main/java/baritone/selection/Selection.java index 68b8021b..3b385503 100644 --- a/src/main/java/baritone/selection/Selection.java +++ b/src/main/java/baritone/selection/Selection.java @@ -116,9 +116,9 @@ public class Selection implements ISelection { @Override public ISelection contract(EnumFacing direction, int blocks) { if (isPos2(direction)) { - return new Selection(pos1.offset(direction, -blocks), pos2); + return new Selection(pos1.offset(direction, blocks), pos2); } else { - return new Selection(pos1, pos2.offset(direction, -blocks)); + return new Selection(pos1, pos2.offset(direction, blocks)); } } diff --git a/src/main/java/baritone/selection/SelectionManager.java b/src/main/java/baritone/selection/SelectionManager.java index b95c60b2..5fea4cba 100644 --- a/src/main/java/baritone/selection/SelectionManager.java +++ b/src/main/java/baritone/selection/SelectionManager.java @@ -3,12 +3,13 @@ package baritone.selection; import baritone.api.selection.ISelection; import baritone.api.selection.ISelectionManager; import baritone.api.utils.BetterBlockPos; +import net.minecraft.util.EnumFacing; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.LinkedList; +import java.util.ListIterator; public class SelectionManager implements ISelectionManager { - private final Set selections = new LinkedHashSet<>(); + private final LinkedList selections = new LinkedList<>(); private ISelection[] selectionsArr = new ISelection[0]; public SelectionManager() { @@ -54,7 +55,60 @@ public class SelectionManager implements ISelectionManager { @Override public synchronized ISelection getOnlySelection() { if (selections.size() == 1) { - return selections.iterator().next(); + return selections.peekFirst(); + } + + return null; + } + + @Override + public ISelection getLastSelection() { + return selections.peekLast(); + } + + @Override + public synchronized ISelection expand(ISelection selection, EnumFacing direction, int blocks) { + for (ListIterator it = selections.listIterator(); it.hasNext(); ) { + ISelection current = it.next(); + + if (current == selection) { + it.remove(); + it.add(current.expand(direction, blocks)); + resetSelectionsArr(); + return it.previous(); + } + } + + return null; + } + + @Override + public synchronized ISelection contract(ISelection selection, EnumFacing direction, int blocks) { + for (ListIterator it = selections.listIterator(); it.hasNext(); ) { + ISelection current = it.next(); + + if (current == selection) { + it.remove(); + it.add(current.contract(direction, blocks)); + resetSelectionsArr(); + return it.previous(); + } + } + + return null; + } + + @Override + public synchronized ISelection shift(ISelection selection, EnumFacing direction, int blocks) { + for (ListIterator it = selections.listIterator(); it.hasNext(); ) { + ISelection current = it.next(); + + if (current == selection) { + it.remove(); + it.add(current.shift(direction, blocks)); + resetSelectionsArr(); + return it.previous(); + } } return null;