001/* 002 * This file is part of Baritone. 003 * 004 * Baritone is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU Lesser General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * Baritone is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License 015 * along with Baritone. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package baritone.api.command.datatypes; 019 020import baritone.api.command.exception.CommandException; 021import baritone.api.command.helpers.TabCompleteHelper; 022import baritone.api.utils.BlockOptionalMeta; 023import net.minecraft.core.registries.BuiltInRegistries; 024import net.minecraft.resources.ResourceLocation; 025import net.minecraft.world.level.block.Block; 026import net.minecraft.world.level.block.state.properties.Property; 027 028import java.util.Set; 029import java.util.regex.Pattern; 030import java.util.stream.Collectors; 031import java.util.stream.Stream; 032 033public enum ForBlockOptionalMeta implements IDatatypeFor<BlockOptionalMeta> { 034 INSTANCE; 035 036 /** 037 * Matches (domain:)?name([(property=value)*])? but the input can be truncated at any position. 038 * domain and name are [a-z0-9_.-]+ and [a-z0-9/_.-]+ because that's what mc 1.13+ accepts. 039 * property and value use the same format as domain. 040 */ 041 // Good luck reading this. 042 private static Pattern PATTERN = Pattern.compile("(?:[a-z0-9_.-]+:)?(?:[a-z0-9/_.-]+(?:\\[(?:(?:[a-z0-9_.-]+=[a-z0-9_.-]+,)*(?:[a-z0-9_.-]+(?:=(?:[a-z0-9_.-]+(?:\\])?)?)?)?|\\])?)?)?"); 043 044 @Override 045 public BlockOptionalMeta get(IDatatypeContext ctx) throws CommandException { 046 return new BlockOptionalMeta(ctx.getConsumer().getString()); 047 } 048 049 @Override 050 public Stream<String> tabComplete(IDatatypeContext ctx) throws CommandException { 051 String arg = ctx.getConsumer().peekString(); 052 053 if (!PATTERN.matcher(arg).matches()) { 054 // Invalid format; we can't complete this. 055 ctx.getConsumer().getString(); 056 return Stream.empty(); 057 } 058 059 if (arg.endsWith("]")) { 060 // We are already done. 061 ctx.getConsumer().getString(); 062 return Stream.empty(); 063 } 064 065 if (!arg.contains("[")) { 066 // no properties so we are completing the block id 067 return ctx.getConsumer().tabCompleteDatatype(BlockById.INSTANCE); 068 } 069 070 ctx.getConsumer().getString(); 071 072 // destructuring assignment? Please? 073 String blockId, properties; 074 { 075 String[] parts = splitLast(arg, '['); 076 blockId = parts[0]; 077 properties = parts[1]; 078 } 079 080 Block block = BuiltInRegistries.BLOCK.getOptional(ResourceLocation.parse(blockId)).orElse(null); 081 if (block == null) { 082 // This block doesn't exist so there's no properties to complete. 083 return Stream.empty(); 084 } 085 086 String leadingProperties, lastProperty; 087 { 088 String[] parts = splitLast(properties, ','); 089 leadingProperties = parts[0]; 090 lastProperty = parts[1]; 091 } 092 093 if (!lastProperty.contains("=")) { 094 // The last property-value pair doesn't have a value yet so we are completing its name 095 Set<String> usedProps = Stream.of(leadingProperties.split(",")) 096 .map(pair -> pair.split("=")[0]) 097 .collect(Collectors.toSet()); 098 099 String prefix = arg.substring(0, arg.length() - lastProperty.length()); 100 return new TabCompleteHelper() 101 .append( 102 block.getStateDefinition() 103 .getProperties() 104 .stream() 105 .map(Property::getName) 106 ) 107 .filter(prop -> !usedProps.contains(prop)) 108 .filterPrefix(lastProperty) 109 .sortAlphabetically() 110 .map(prop -> prefix + prop) 111 .stream(); 112 } 113 114 String lastName, lastValue; 115 { 116 String[] parts = splitLast(lastProperty, '='); 117 lastName = parts[0]; 118 lastValue = parts[1]; 119 } 120 121 // We are completing the value of a property 122 String prefix = arg.substring(0, arg.length() - lastValue.length()); 123 124 Property<?> property = block.getStateDefinition().getProperty(lastName); 125 if (property == null) { 126 // The property does not exist so there's no values to complete 127 return Stream.empty(); 128 } 129 130 return new TabCompleteHelper() 131 .append(getValues(property)) 132 .filterPrefix(lastValue) 133 .sortAlphabetically() 134 .map(val -> prefix + val) 135 .stream(); 136 } 137 138 /** 139 * Always returns exactly two strings. 140 * If the separator is not found the FIRST returned string is empty. 141 */ 142 private static String[] splitLast(String string, char chr) { 143 int idx = string.lastIndexOf(chr); 144 if (idx == -1) { 145 return new String[]{"", string}; 146 } 147 return new String[]{string.substring(0, idx), string.substring(idx + 1)}; 148 } 149 150 // this shouldn't need to be a separate method? 151 private static <T extends Comparable<T>> Stream<String> getValues(Property<T> property) { 152 return property.getPossibleValues().stream().map(property::getName); 153 } 154}