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.utils; 019 020import baritone.api.BaritoneAPI; 021import baritone.api.Settings; 022import net.minecraft.client.Minecraft; 023import net.minecraft.core.Direction; 024import net.minecraft.core.Holder; 025import net.minecraft.core.Registry; 026import net.minecraft.core.Vec3i; 027import net.minecraft.core.registries.BuiltInRegistries; 028import net.minecraft.resources.ResourceLocation; 029import net.minecraft.world.item.Item; 030import net.minecraft.world.level.block.Block; 031import net.minecraft.world.level.block.Mirror; 032import net.minecraft.world.level.block.Rotation; 033 034import java.awt.*; 035import java.io.BufferedReader; 036import java.io.BufferedWriter; 037import java.io.IOException; 038import java.lang.reflect.ParameterizedType; 039import java.lang.reflect.Type; 040import java.nio.file.Files; 041import java.nio.file.NoSuchFileException; 042import java.nio.file.Path; 043import java.util.ArrayList; 044import java.util.List; 045import java.util.Map; 046import java.util.Objects; 047import java.util.function.Consumer; 048import java.util.function.Function; 049import java.util.regex.Matcher; 050import java.util.regex.Pattern; 051import java.util.stream.Collectors; 052import java.util.stream.Stream; 053 054public class SettingsUtil { 055 056 public static final String SETTINGS_DEFAULT_NAME = "settings.txt"; 057 private static final Pattern SETTING_PATTERN = Pattern.compile("^(?<setting>[^ ]+) +(?<value>.+)"); // key and value split by the first space 058 059 060 private static boolean isComment(String line) { 061 return line.startsWith("#") || line.startsWith("//"); 062 } 063 064 private static void forEachLine(Path file, Consumer<String> consumer) throws IOException { 065 try (BufferedReader scan = Files.newBufferedReader(file)) { 066 String line; 067 while ((line = scan.readLine()) != null) { 068 if (line.isEmpty() || isComment(line)) { 069 continue; 070 } 071 consumer.accept(line); 072 } 073 } 074 } 075 076 public static void readAndApply(Settings settings, String settingsName) { 077 try { 078 forEachLine(settingsByName(settingsName), line -> { 079 Matcher matcher = SETTING_PATTERN.matcher(line); 080 if (!matcher.matches()) { 081 Helper.HELPER.logDirect("Invalid syntax in setting file: " + line); 082 return; 083 } 084 085 String settingName = matcher.group("setting").toLowerCase(); 086 String settingValue = matcher.group("value"); 087 // TODO remove soonish 088 if ("allowjumpat256".equals(settingName)) { 089 settingName = "allowjumpatbuildlimit"; 090 } 091 try { 092 parseAndApply(settings, settingName, settingValue); 093 } catch (Exception ex) { 094 Helper.HELPER.logDirect("Unable to parse line " + line); 095 ex.printStackTrace(); 096 } 097 }); 098 } catch (NoSuchFileException ignored) { 099 Helper.HELPER.logDirect("Baritone settings file not found, resetting."); 100 } catch (Exception ex) { 101 Helper.HELPER.logDirect("Exception while reading Baritone settings, some settings may be reset to default values!"); 102 ex.printStackTrace(); 103 } 104 } 105 106 public static synchronized void save(Settings settings) { 107 try (BufferedWriter out = Files.newBufferedWriter(settingsByName(SETTINGS_DEFAULT_NAME))) { 108 for (Settings.Setting setting : modifiedSettings(settings)) { 109 out.write(settingToString(setting) + "\n"); 110 } 111 } catch (Exception ex) { 112 Helper.HELPER.logDirect("Exception thrown while saving Baritone settings!"); 113 ex.printStackTrace(); 114 } 115 } 116 117 private static Path settingsByName(String name) { 118 return Minecraft.getInstance().gameDirectory.toPath().resolve("baritone").resolve(name); 119 } 120 121 public static List<Settings.Setting> modifiedSettings(Settings settings) { 122 List<Settings.Setting> modified = new ArrayList<>(); 123 for (Settings.Setting setting : settings.allSettings) { 124 if (setting.value == null) { 125 System.out.println("NULL SETTING?" + setting.getName()); 126 continue; 127 } 128 if (setting.isJavaOnly()) { 129 continue; // NO 130 } 131 if (setting.value == setting.defaultValue) { 132 continue; 133 } 134 modified.add(setting); 135 } 136 return modified; 137 } 138 139 /** 140 * Gets the type of a setting and returns it as a string, with package names stripped. 141 * <p> 142 * For example, if the setting type is {@code java.util.List<java.lang.String>}, this function returns 143 * {@code List<String>}. 144 * 145 * @param setting The setting 146 * @return The type 147 */ 148 public static String settingTypeToString(Settings.Setting setting) { 149 return setting.getType().getTypeName() 150 .replaceAll("(?:\\w+\\.)+(\\w+)", "$1"); 151 } 152 153 public static <T> String settingValueToString(Settings.Setting<T> setting, T value) throws IllegalArgumentException { 154 Parser io = Parser.getParser(setting.getType()); 155 156 if (io == null) { 157 throw new IllegalStateException("Missing " + setting.getValueClass() + " " + setting.getName()); 158 } 159 160 return io.toString(setting.getType(), value); 161 } 162 163 public static String settingValueToString(Settings.Setting setting) throws IllegalArgumentException { 164 //noinspection unchecked 165 return settingValueToString(setting, setting.value); 166 } 167 168 public static String settingDefaultToString(Settings.Setting setting) throws IllegalArgumentException { 169 //noinspection unchecked 170 return settingValueToString(setting, setting.defaultValue); 171 } 172 173 public static String maybeCensor(int coord) { 174 if (BaritoneAPI.getSettings().censorCoordinates.value) { 175 return "<censored>"; 176 } 177 178 return Integer.toString(coord); 179 } 180 181 public static String settingToString(Settings.Setting setting) throws IllegalStateException { 182 if (setting.isJavaOnly()) { 183 return setting.getName(); 184 } 185 186 return setting.getName() + " " + settingValueToString(setting); 187 } 188 189 /** 190 * Deprecated. Use {@link Settings.Setting#isJavaOnly()} instead. 191 * 192 * @param setting The Setting 193 * @return true if the setting can not be set or read by the user 194 */ 195 @Deprecated 196 public static boolean javaOnlySetting(Settings.Setting setting) { 197 return setting.isJavaOnly(); 198 } 199 200 public static void parseAndApply(Settings settings, String settingName, String settingValue) throws IllegalStateException, NumberFormatException { 201 Settings.Setting setting = settings.byLowerName.get(settingName); 202 if (setting == null) { 203 throw new IllegalStateException("No setting by that name"); 204 } 205 Class intendedType = setting.getValueClass(); 206 ISettingParser ioMethod = Parser.getParser(setting.getType()); 207 Object parsed = ioMethod.parse(setting.getType(), settingValue); 208 if (!intendedType.isInstance(parsed)) { 209 throw new IllegalStateException(ioMethod + " parser returned incorrect type, expected " + intendedType + " got " + parsed + " which is " + parsed.getClass()); 210 } 211 setting.value = parsed; 212 } 213 214 private interface ISettingParser<T> { 215 216 T parse(Type type, String raw); 217 218 String toString(Type type, T value); 219 220 boolean accepts(Type type); 221 } 222 223 private enum Parser implements ISettingParser { 224 225 DOUBLE(Double.class, Double::parseDouble), 226 BOOLEAN(Boolean.class, Boolean::parseBoolean), 227 INTEGER(Integer.class, Integer::parseInt), 228 FLOAT(Float.class, Float::parseFloat), 229 LONG(Long.class, Long::parseLong), 230 STRING(String.class, String::new), 231 MIRROR(Mirror.class, Mirror::valueOf, Mirror::name), 232 ROTATION(Rotation.class, Rotation::valueOf, Rotation::name), 233 COLOR( 234 Color.class, 235 str -> new Color(Integer.parseInt(str.split(",")[0]), Integer.parseInt(str.split(",")[1]), Integer.parseInt(str.split(",")[2])), 236 color -> color.getRed() + "," + color.getGreen() + "," + color.getBlue() 237 ), 238 VEC3I( 239 Vec3i.class, 240 str -> new Vec3i(Integer.parseInt(str.split(",")[0]), Integer.parseInt(str.split(",")[1]), Integer.parseInt(str.split(",")[2])), 241 vec -> vec.getX() + "," + vec.getY() + "," + vec.getZ() 242 ), 243 BLOCK( 244 Block.class, 245 str -> BlockUtils.stringToBlockRequired(str.trim()), 246 BlockUtils::blockToString 247 ), 248 ITEM( 249 Item.class, 250 str -> BuiltInRegistries.ITEM.get(ResourceLocation.parse(str.trim())).map(Holder.Reference::value).orElse(null), 251 item -> BuiltInRegistries.ITEM.getKey(item).toString() 252 ), 253 LIST() { 254 @Override 255 public Object parse(Type type, String raw) { 256 Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; 257 Parser parser = Parser.getParser(elementType); 258 return Stream.of(raw.split(",")) 259 .map(s -> parser.parse(elementType, s)) 260 .collect(Collectors.toList()); 261 } 262 263 @Override 264 public String toString(Type type, Object value) { 265 Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; 266 Parser parser = Parser.getParser(elementType); 267 268 return ((List<?>) value).stream() 269 .map(o -> parser.toString(elementType, o)) 270 .collect(Collectors.joining(",")); 271 } 272 273 @Override 274 public boolean accepts(Type type) { 275 return List.class.isAssignableFrom(TypeUtils.resolveBaseClass(type)); 276 } 277 }, 278 MAPPING() { 279 @Override 280 public Object parse(Type type, String raw) { 281 Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0]; 282 Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1]; 283 Parser keyParser = Parser.getParser(keyType); 284 Parser valueParser = Parser.getParser(valueType); 285 286 return Stream.of(raw.split(",(?=[^,]*->)")) 287 .map(s -> s.split("->")) 288 .collect(Collectors.toMap(s -> keyParser.parse(keyType, s[0]), s -> valueParser.parse(valueType, s[1]))); 289 } 290 291 @Override 292 public String toString(Type type, Object value) { 293 Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0]; 294 Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1]; 295 Parser keyParser = Parser.getParser(keyType); 296 Parser valueParser = Parser.getParser(valueType); 297 298 return ((Map<?, ?>) value).entrySet().stream() 299 .map(o -> keyParser.toString(keyType, o.getKey()) + "->" + valueParser.toString(valueType, o.getValue())) 300 .collect(Collectors.joining(",")); 301 } 302 303 @Override 304 public boolean accepts(Type type) { 305 return Map.class.isAssignableFrom(TypeUtils.resolveBaseClass(type)); 306 } 307 }; 308 309 private final Class<?> cla$$; 310 private final Function<String, Object> parser; 311 private final Function<Object, String> toString; 312 313 Parser() { 314 this.cla$$ = null; 315 this.parser = null; 316 this.toString = null; 317 } 318 319 <T> Parser(Class<T> cla$$, Function<String, T> parser) { 320 this(cla$$, parser, Object::toString); 321 } 322 323 <T> Parser(Class<T> cla$$, Function<String, T> parser, Function<T, String> toString) { 324 this.cla$$ = cla$$; 325 this.parser = parser::apply; 326 this.toString = x -> toString.apply((T) x); 327 } 328 329 @Override 330 public Object parse(Type type, String raw) { 331 Object parsed = this.parser.apply(raw); 332 Objects.requireNonNull(parsed); 333 return parsed; 334 } 335 336 @Override 337 public String toString(Type type, Object value) { 338 return this.toString.apply(value); 339 } 340 341 @Override 342 public boolean accepts(Type type) { 343 return type instanceof Class && this.cla$$.isAssignableFrom((Class) type); 344 } 345 346 public static Parser getParser(Type type) { 347 return Stream.of(values()) 348 .filter(parser -> parser.accepts(type)) 349 .findFirst().orElse(null); 350 } 351 } 352}