From 518177278696efa03bdc3a56b461502a0c2e0afb Mon Sep 17 00:00:00 2001
From: ZacSharp <68165024+ZacSharp@users.noreply.github.com>
Date: Sun, 4 Jan 2026 22:17:24 +0100
Subject: [PATCH 1/3] Command data type to parse block tags as
`BlockOptionalMetaLookup`
---
.../datatypes/ForBlockOptionalMetaLookup.java | 187 ++++++++++++++++++
.../command/defaults/MineCommand.java | 4 +-
2 files changed, 189 insertions(+), 2 deletions(-)
create mode 100644 src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java
diff --git a/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java b/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java
new file mode 100644
index 000000000..96c5de47a
--- /dev/null
+++ b/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java
@@ -0,0 +1,187 @@
+/*
+ * This file is part of Baritone.
+ *
+ * Baritone is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Baritone is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Baritone. If not, see .
+ */
+
+package baritone.api.command.datatypes;
+
+import baritone.api.command.exception.CommandException;
+import baritone.api.command.helpers.TabCompleteHelper;
+import baritone.api.utils.BlockOptionalMeta;
+import baritone.api.utils.BlockOptionalMetaLookup;
+import com.google.common.collect.ImmutableMap;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.level.block.Block;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public enum ForBlockOptionalMetaLookup implements IDatatypeFor {
+ INSTANCE;
+
+ /**
+ * Matches #?(domain:)?name([(property=value)*])? but the input can be truncated at any position.
+ * domain and name are [a-z0-9_.-]+ and [a-z0-9/_.-]+ because that's what mc 1.13+ accepts.
+ * property and value use the same format as domain.
+ */
+ // Good luck reading this.
+ private static final Pattern PREFIX_PATTERN = Pattern.compile("#?(?:[a-z0-9_.-]+:)?(?:[a-z0-9/_.-]+(?:\\[(?:(?:[a-z0-9_.-]+=[a-z0-9_.-]+,)*(?:[a-z0-9_.-]+(?:=(?:[a-z0-9_.-]+(?:\\])?)?)?)?|\\])?)?)?");
+
+ // id(\[properties?\])? where id and properties are any text with at least one character
+ private static final Pattern FULL_PATTERN = Pattern.compile("^(?.+?)(?:\\[(?.+?)?\\])?$");
+
+ @Override
+ public BlockOptionalMetaLookup get(IDatatypeContext ctx) throws CommandException {
+ String raw = ctx.getConsumer().getString();
+ Matcher matcher = FULL_PATTERN.matcher(raw);
+
+ if (!matcher.find()) {
+ throw new IllegalArgumentException("invalid block/tag selector");
+ }
+
+ if (!raw.startsWith("#")) { // plain block
+ return new BlockOptionalMetaLookup(new BlockOptionalMeta(raw));
+ }
+
+ ResourceLocation tagName = ResourceLocation.tryParse(matcher.group("id").substring(1));
+ TagKey tag = TagKey.create(Registries.BLOCK, tagName);
+
+ String props = Optional.ofNullable(matcher.group("properties")).orElse("");
+ Map properties = props.equals("") ? Collections.emptyMap() : parseProperties(props);
+
+ return new BlockOptionalMetaLookup(BuiltInRegistries.BLOCK.getTag(tag)
+ .orElseThrow(() -> new IllegalArgumentException("No tag by that name"))
+ .stream()
+ .map(holder -> holder.value())
+ .filter(block -> properties.keySet().stream()
+ .allMatch(name -> Optional.of(block)
+ .map(b -> b.getStateDefinition().getProperty(name))
+ .map(p -> p.getValue(properties.get(name)))
+ .isPresent()
+ )
+ )
+ .map(BuiltInRegistries.BLOCK::getKey)
+ .map(id -> new BlockOptionalMeta(id + "[" + props + "]"))
+ .toArray(BlockOptionalMeta[]::new)
+ );
+ }
+
+ @Override
+ public Stream tabComplete(IDatatypeContext ctx) throws CommandException {
+ String arg = ctx.getConsumer().peekString();
+
+ if (!PREFIX_PATTERN.matcher(arg).matches()) {
+ // Invalid format; we can't complete this.
+ return Stream.empty();
+ }
+
+ if (!arg.startsWith("#")) {
+ return ForBlockOptionalMeta.INSTANCE.tabComplete(ctx);
+ }
+
+ if (arg.endsWith("]")) {
+ // We are already done.
+ return Stream.empty();
+ }
+
+ if (!arg.contains("[")) {
+ // no properties so we are completing the block id
+ return new TabCompleteHelper()
+ .append(BuiltInRegistries.BLOCK.getTags()
+ .map(pair -> pair.getFirst().location().toString()))
+ .filterPrefixNamespaced(arg.substring(1))
+ .sortAlphabetically()
+ .map(name -> "#" + name)
+ .stream();
+ }
+
+ String tagId = splitLast(arg.substring(1), '[')[0];
+ String properties = splitLast(arg, '[')[1];
+
+ TagKey tag = TagKey.create(Registries.BLOCK, ResourceLocation.tryParse(tagId));
+ Set blocks = BuiltInRegistries.BLOCK
+ .getTag(tag)
+ .get()
+ .stream()
+ .map(h -> h.value())
+ .collect(Collectors.toSet());
+
+ String previousProps = splitLast(properties, ',')[0];
+ String lastProp = splitLast(properties, ',')[1];
+
+ if (!lastProp.contains("=")) {
+ // The last property-value pair doesn't have a value yet so we are completing its name
+ Set usedProps = Stream.of(previousProps.split(","))
+ .map(pair -> pair.split("=")[0])
+ .collect(Collectors.toSet());
+
+ return blocks.stream()
+ .flatMap(b -> b.getStateDefinition()
+ .getProperties()
+ .stream()
+ .map(p -> p.getName())
+ )
+ .filter(prop -> !usedProps.contains(prop))
+ .filter(prop -> prop.startsWith(lastProp))
+ .distinct()
+ .sorted(String.CASE_INSENSITIVE_ORDER)
+ .map(prop -> arg.substring(0, arg.length() - lastProp.length()) + prop);
+ }
+
+ String lastName = splitLast(lastProp, '=')[0];
+ String lastValue = splitLast(lastProp, '=')[1];
+
+ return blocks.stream()
+ .map(b -> b.getStateDefinition().getProperty(lastName))
+ .filter(p -> p != null)
+ .flatMap(p -> p.getPossibleValues().stream().map(p::getName))
+ .filter(v -> v.startsWith(lastValue))
+ .distinct()
+ .map(v -> arg.substring(0, arg.length() - lastValue.length()) + v);
+ }
+
+ private static Map parseProperties(String raw) {
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ for (String pair : raw.split(",")) {
+ String[] parts = pair.split("=");
+ if (parts.length != 2) {
+ throw new IllegalArgumentException(String.format("\"%s\" is not a valid property-value pair", pair));
+ }
+ builder.put(parts[0], parts[1]);
+ }
+ return builder.build();
+ }
+
+ /**
+ * Always returns exactly two strings.
+ * If the separator is not found the FIRST returned string is empty.
+ */
+ private static String[] splitLast(String string, char chr) {
+ int idx = string.lastIndexOf(chr);
+ if (idx == -1) {
+ return new String[]{"", string};
+ }
+ return new String[]{string.substring(0, idx), string.substring(idx + 1)};
+ }
+}
diff --git a/src/main/java/baritone/command/defaults/MineCommand.java b/src/main/java/baritone/command/defaults/MineCommand.java
index eb2c22c6d..e2ab5bfd8 100644
--- a/src/main/java/baritone/command/defaults/MineCommand.java
+++ b/src/main/java/baritone/command/defaults/MineCommand.java
@@ -53,9 +53,9 @@ public void execute(String label, IArgConsumer args) throws CommandException {
public Stream tabComplete(String label, IArgConsumer args) throws CommandException {
args.getAsOrDefault(Integer.class, 0);
while (args.has(2)) {
- args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE);
+ args.getDatatypeFor(ForBlockOptionalMetaLookup.INSTANCE);
}
- return args.tabCompleteDatatype(ForBlockOptionalMeta.INSTANCE);
+ return args.tabCompleteDatatype(ForBlockOptionalMetaLookup.INSTANCE);
}
@Override
From 78eb755115369a8e6c34d81fa533dd4cd1bac7e4 Mon Sep 17 00:00:00 2001
From: ZacSharp <68165024+ZacSharp@users.noreply.github.com>
Date: Sun, 4 Jan 2026 22:17:37 +0100
Subject: [PATCH 2/3] Merge tab completion logic for blocks and tags
---
.../datatypes/ForBlockOptionalMeta.java | 118 +-----------------
.../datatypes/ForBlockOptionalMetaLookup.java | 71 ++++++-----
2 files changed, 40 insertions(+), 149 deletions(-)
diff --git a/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMeta.java b/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMeta.java
index 5b949cd63..4c5006c39 100644
--- a/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMeta.java
+++ b/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMeta.java
@@ -18,29 +18,14 @@
package baritone.api.command.datatypes;
import baritone.api.command.exception.CommandException;
-import baritone.api.command.helpers.TabCompleteHelper;
import baritone.api.utils.BlockOptionalMeta;
-import net.minecraft.core.registries.BuiltInRegistries;
-import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
-import net.minecraft.world.level.block.state.properties.Property;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
public enum ForBlockOptionalMeta implements IDatatypeFor {
INSTANCE;
- /**
- * Matches (domain:)?name([(property=value)*])? but the input can be truncated at any position.
- * domain and name are [a-z0-9_.-]+ and [a-z0-9/_.-]+ because that's what mc 1.13+ accepts.
- * property and value use the same format as domain.
- */
- // Good luck reading this.
- private static Pattern PATTERN = Pattern.compile("(?:[a-z0-9_.-]+:)?(?:[a-z0-9/_.-]+(?:\\[(?:(?:[a-z0-9_.-]+=[a-z0-9_.-]+,)*(?:[a-z0-9_.-]+(?:=(?:[a-z0-9_.-]+(?:\\])?)?)?)?|\\])?)?)?");
-
@Override
public BlockOptionalMeta get(IDatatypeContext ctx) throws CommandException {
return new BlockOptionalMeta(ctx.getConsumer().getString());
@@ -48,107 +33,6 @@ public BlockOptionalMeta get(IDatatypeContext ctx) throws CommandException {
@Override
public Stream tabComplete(IDatatypeContext ctx) throws CommandException {
- String arg = ctx.getConsumer().peekString();
-
- if (!PATTERN.matcher(arg).matches()) {
- // Invalid format; we can't complete this.
- ctx.getConsumer().getString();
- return Stream.empty();
- }
-
- if (arg.endsWith("]")) {
- // We are already done.
- ctx.getConsumer().getString();
- return Stream.empty();
- }
-
- if (!arg.contains("[")) {
- // no properties so we are completing the block id
- return ctx.getConsumer().tabCompleteDatatype(BlockById.INSTANCE);
- }
-
- ctx.getConsumer().getString();
-
- // destructuring assignment? Please?
- String blockId, properties;
- {
- String[] parts = splitLast(arg, '[');
- blockId = parts[0];
- properties = parts[1];
- }
-
- Block block = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(blockId)).orElse(null);
- if (block == null) {
- // This block doesn't exist so there's no properties to complete.
- return Stream.empty();
- }
-
- String leadingProperties, lastProperty;
- {
- String[] parts = splitLast(properties, ',');
- leadingProperties = parts[0];
- lastProperty = parts[1];
- }
-
- if (!lastProperty.contains("=")) {
- // The last property-value pair doesn't have a value yet so we are completing its name
- Set usedProps = Stream.of(leadingProperties.split(","))
- .map(pair -> pair.split("=")[0])
- .collect(Collectors.toSet());
-
- String prefix = arg.substring(0, arg.length() - lastProperty.length());
- return new TabCompleteHelper()
- .append(
- block.getStateDefinition()
- .getProperties()
- .stream()
- .map(Property::getName)
- )
- .filter(prop -> !usedProps.contains(prop))
- .filterPrefix(lastProperty)
- .sortAlphabetically()
- .map(prop -> prefix + prop)
- .stream();
- }
-
- String lastName, lastValue;
- {
- String[] parts = splitLast(lastProperty, '=');
- lastName = parts[0];
- lastValue = parts[1];
- }
-
- // We are completing the value of a property
- String prefix = arg.substring(0, arg.length() - lastValue.length());
-
- Property> property = block.getStateDefinition().getProperty(lastName);
- if (property == null) {
- // The property does not exist so there's no values to complete
- return Stream.empty();
- }
-
- return new TabCompleteHelper()
- .append(getValues(property))
- .filterPrefix(lastValue)
- .sortAlphabetically()
- .map(val -> prefix + val)
- .stream();
- }
-
- /**
- * Always returns exactly two strings.
- * If the separator is not found the FIRST returned string is empty.
- */
- private static String[] splitLast(String string, char chr) {
- int idx = string.lastIndexOf(chr);
- if (idx == -1) {
- return new String[]{"", string};
- }
- return new String[]{string.substring(0, idx), string.substring(idx + 1)};
- }
-
- // this shouldn't need to be a separate method?
- private static > Stream getValues(Property property) {
- return property.getPossibleValues().stream().map(property::getName);
+ return ForBlockOptionalMetaLookup.tabComplete(ctx, false);
}
}
diff --git a/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java b/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java
index 96c5de47a..ca255c8f8 100644
--- a/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java
+++ b/src/api/java/baritone/api/command/datatypes/ForBlockOptionalMetaLookup.java
@@ -53,27 +53,17 @@ public enum ForBlockOptionalMetaLookup implements IDatatypeFor tag = TagKey.create(Registries.BLOCK, tagName);
-
String props = Optional.ofNullable(matcher.group("properties")).orElse("");
Map properties = props.equals("") ? Collections.emptyMap() : parseProperties(props);
- return new BlockOptionalMetaLookup(BuiltInRegistries.BLOCK.getTag(tag)
- .orElseThrow(() -> new IllegalArgumentException("No tag by that name"))
+ return new BlockOptionalMetaLookup(parseTagOrBlockId(matcher.group("id"))
.stream()
- .map(holder -> holder.value())
.filter(block -> properties.keySet().stream()
.allMatch(name -> Optional.of(block)
.map(b -> b.getStateDefinition().getProperty(name))
@@ -89,6 +79,10 @@ public BlockOptionalMetaLookup get(IDatatypeContext ctx) throws CommandException
@Override
public Stream tabComplete(IDatatypeContext ctx) throws CommandException {
+ return tabComplete(ctx, true);
+ }
+
+ static Stream tabComplete(IDatatypeContext ctx, boolean allowTags) throws CommandException {
String arg = ctx.getConsumer().peekString();
if (!PREFIX_PATTERN.matcher(arg).matches()) {
@@ -96,8 +90,8 @@ public Stream tabComplete(IDatatypeContext ctx) throws CommandException
return Stream.empty();
}
- if (!arg.startsWith("#")) {
- return ForBlockOptionalMeta.INSTANCE.tabComplete(ctx);
+ if (arg.startsWith("#") && !allowTags) {
+ return Stream.empty();
}
if (arg.endsWith("]")) {
@@ -106,27 +100,24 @@ public Stream tabComplete(IDatatypeContext ctx) throws CommandException
}
if (!arg.contains("[")) {
- // no properties so we are completing the block id
- return new TabCompleteHelper()
- .append(BuiltInRegistries.BLOCK.getTags()
- .map(pair -> pair.getFirst().location().toString()))
- .filterPrefixNamespaced(arg.substring(1))
- .sortAlphabetically()
- .map(name -> "#" + name)
- .stream();
+ // no properties so we are completing the block or tag id
+ TabCompleteHelper helper = new TabCompleteHelper();
+ if (arg.startsWith("#") || arg.equals("") && allowTags) {
+ helper.append(BuiltInRegistries.BLOCK.getTags()
+ .map(pair -> pair.getFirst().location().toString()))
+ .filterPrefixNamespaced(arg.equals("") ? "" : arg.substring(1))
+ .sortAlphabetically()
+ .map(name -> "#" + name);
+ }
+ if (!arg.startsWith("#")) {
+ helper.append(ctx.getConsumer().tabCompleteDatatype(BlockById.INSTANCE));
+ }
+ return helper.stream();
}
- String tagId = splitLast(arg.substring(1), '[')[0];
- String properties = splitLast(arg, '[')[1];
-
- TagKey tag = TagKey.create(Registries.BLOCK, ResourceLocation.tryParse(tagId));
- Set blocks = BuiltInRegistries.BLOCK
- .getTag(tag)
- .get()
- .stream()
- .map(h -> h.value())
- .collect(Collectors.toSet());
+ Set blocks = parseTagOrBlockId(splitLast(arg, '[')[0]);
+ String properties = splitLast(arg, '[')[1];
String previousProps = splitLast(properties, ',')[0];
String lastProp = splitLast(properties, ',')[1];
@@ -173,6 +164,22 @@ private static Map parseProperties(String raw) {
return builder.build();
}
+ private static Set parseTagOrBlockId(String id) {
+ ResourceLocation loc = new ResourceLocation(id.replaceAll("^#", ""));
+ if (id.startsWith("#")) {
+ return BuiltInRegistries.BLOCK
+ .getTag(TagKey.create(Registries.BLOCK, loc))
+ .orElseThrow(() -> new IllegalArgumentException("No tag " + id))
+ .stream()
+ .map(h -> h.value())
+ .collect(Collectors.toSet());
+ } else {
+ return BuiltInRegistries.BLOCK.getOptional(loc)
+ .map(Set::of)
+ .orElseThrow(() -> new IllegalArgumentException("No block " + id));
+ }
+ }
+
/**
* Always returns exactly two strings.
* If the separator is not found the FIRST returned string is empty.
From f56f6322efe213c609df60f21940dcbe156d8a97 Mon Sep 17 00:00:00 2001
From: ZacSharp <68165024+ZacSharp@users.noreply.github.com>
Date: Sun, 4 Jan 2026 22:17:49 +0100
Subject: [PATCH 3/3] Allow using block tags where multiple blocks were allowed
already
---
src/main/java/baritone/command/defaults/MineCommand.java | 8 ++++++--
src/main/java/baritone/command/defaults/SelCommand.java | 6 +++++-
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/main/java/baritone/command/defaults/MineCommand.java b/src/main/java/baritone/command/defaults/MineCommand.java
index e2ab5bfd8..b4e545e4f 100644
--- a/src/main/java/baritone/command/defaults/MineCommand.java
+++ b/src/main/java/baritone/command/defaults/MineCommand.java
@@ -21,8 +21,9 @@
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
-import baritone.api.command.datatypes.ForBlockOptionalMeta;
+import baritone.api.command.datatypes.ForBlockOptionalMetaLookup;
import baritone.api.command.exception.CommandException;
+import baritone.api.command.exception.CommandInvalidStateException;
import baritone.api.utils.BlockOptionalMeta;
import java.util.ArrayList;
@@ -42,7 +43,10 @@ public void execute(String label, IArgConsumer args) throws CommandException {
args.requireMin(1);
List boms = new ArrayList<>();
while (args.hasAny()) {
- boms.add(args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE));
+ boms.addAll(args.getDatatypeFor(ForBlockOptionalMetaLookup.INSTANCE).blocks());
+ }
+ if (boms.size() == 0) {
+ throw new CommandInvalidStateException("No target blocks specified");
}
BaritoneAPI.getProvider().getWorldScanner().repack(ctx);
logDirect(String.format("Mining %s", boms.toString()));
diff --git a/src/main/java/baritone/command/defaults/SelCommand.java b/src/main/java/baritone/command/defaults/SelCommand.java
index e789dcd97..3b5d76a2e 100644
--- a/src/main/java/baritone/command/defaults/SelCommand.java
+++ b/src/main/java/baritone/command/defaults/SelCommand.java
@@ -23,6 +23,7 @@
import baritone.api.command.argument.IArgConsumer;
import baritone.api.command.datatypes.ForAxis;
import baritone.api.command.datatypes.ForBlockOptionalMeta;
+import baritone.api.command.datatypes.ForBlockOptionalMetaLookup;
import baritone.api.command.datatypes.ForDirection;
import baritone.api.command.datatypes.RelativeBlockPos;
import baritone.api.command.exception.CommandException;
@@ -133,9 +134,12 @@ public void execute(String label, IArgConsumer args) throws CommandException {
List replacesList = new ArrayList<>();
replacesList.add(type);
while (args.has(2)) {
- replacesList.add(args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE));
+ replacesList.addAll(args.getDatatypeFor(ForBlockOptionalMetaLookup.INSTANCE).blocks());
}
type = args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE);
+ if (replacesList.size() == 0) {
+ throw new CommandInvalidStateException("No target blocks specified");
+ }
replaces = new BlockOptionalMetaLookup(replacesList.toArray(new BlockOptionalMeta[0]));
alignment = null;
} else if (action == Action.CYLINDER || action == Action.HCYLINDER) {