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