From 92f40c36b86bc47da84e4ef03b96d71f0b40df52 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 11 Jun 2019 02:08:50 -0300 Subject: [PATCH] settings system --- src/main/java/carpet/CarpetServer.java | 13 + src/main/java/carpet/CarpetSettings.java | 44 ++-- .../java/carpet/commands/ParcetCommand.java | 209 ++++++++++++++++ src/main/java/carpet/settings/ParsedRule.java | 170 +++++++++++++ src/main/java/carpet/settings/Rule.java | 58 +++++ .../java/carpet/settings/RuleCategory.java | 13 + src/main/java/carpet/settings/Settings.java | 84 +++++++ .../java/carpet/settings/SettingsManager.java | 230 ++++++++++++++++++ src/main/java/carpet/settings/Validator.java | 63 +++++ 9 files changed, 863 insertions(+), 21 deletions(-) create mode 100644 src/main/java/carpet/commands/ParcetCommand.java create mode 100644 src/main/java/carpet/settings/ParsedRule.java create mode 100644 src/main/java/carpet/settings/Rule.java create mode 100644 src/main/java/carpet/settings/RuleCategory.java create mode 100644 src/main/java/carpet/settings/Settings.java create mode 100644 src/main/java/carpet/settings/SettingsManager.java create mode 100644 src/main/java/carpet/settings/Validator.java diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index 22f088c6..7c9d6cec 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -10,6 +10,7 @@ import carpet.commands.DistanceCommand; import carpet.commands.DrawCommand; import carpet.commands.InfoCommand; import carpet.commands.LogCommand; +import carpet.commands.ParcetCommand; import carpet.commands.PerimeterInfoCommand; import carpet.commands.PlayerCommand; import carpet.commands.ScriptCommand; @@ -18,6 +19,8 @@ import carpet.commands.TickCommand; import carpet.helpers.TickSpeed; import carpet.logging.LoggerRegistry; import carpet.script.CarpetScriptServer; +import carpet.settings.Settings; +import carpet.settings.SettingsManager; import carpet.utils.HUDController; import com.mojang.brigadier.CommandDispatcher; import net.minecraft.server.command.ServerCommandSource; @@ -28,12 +31,21 @@ public class CarpetServer // static for now - easier to handle all around the co public static final Random rand = new Random((int)((2>>16)*Math.random())); public static MinecraftServer minecraft_server; public static CarpetScriptServer scriptServer; + public static SettingsManager settingsManager; + static { + SettingsManager.parseSettingsClass(Settings.class); + //... + } public static void init(MinecraftServer server) //aka constructor of this static singleton class { CarpetServer.minecraft_server = server; } public static void onServerLoaded(MinecraftServer server) { + //new + settingsManager = new SettingsManager(server); + + //old CarpetSettings.apply_settings_from_conf(server); scriptServer = new CarpetScriptServer(); } @@ -65,6 +77,7 @@ public class CarpetServer // static for now - easier to handle all around the co PerimeterInfoCommand.register(dispatcher); DrawCommand.register(dispatcher); ScriptCommand.register(dispatcher); + ParcetCommand.register(dispatcher); //TestCommand.register(dispatcher); } diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 13a49e68..2a619272 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -7,6 +7,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.network.ServerPlayerEntity; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -214,19 +215,19 @@ public class CarpetSettings public static void apply_settings_from_conf(MinecraftServer server) { - Map conf = read_conf(server); - boolean is_locked = locked; + Pair,Boolean> conf = read_conf(server); locked = false; - if (is_locked) + if (conf.getRight()) { LOG.info("[CM]: Carpet Mod is locked by the administrator"); + disable_commands_by_default(); } - for (String key: conf.keySet()) + for (String key: conf.getLeft().keySet()) { - set(server.getCommandSource(), key, conf.get(key)); - LOG.info("[CM]: loaded setting "+key+" as "+conf.get(key)+" from carpet.conf"); + set(server.getCommandSource(), key, conf.getLeft().get(key)); + LOG.info("[CM]: loaded setting "+key+" as "+conf.getLeft().get(key)+" from carpet.conf"); } - locked = is_locked; + locked = conf.getRight(); } private static void disable_commands_by_default() { @@ -239,21 +240,21 @@ public class CarpetSettings } } - private static Map read_conf(MinecraftServer server) + private static Pair,Boolean> read_conf(MinecraftServer server) { try { File settings_file = server.getLevelStorage().resolveFile(server.getLevelName(), "carpet.conf"); BufferedReader b = new BufferedReader(new FileReader(settings_file)); String line = ""; + boolean confLocked = false; Map result = new HashMap(); while ((line = b.readLine()) != null) { line = line.replaceAll("\\r|\\n", ""); if ("locked".equalsIgnoreCase(line)) { - disable_commands_by_default(); - locked = true; + confLocked = true; } String[] fields = line.split("\\s+",2); if (fields.length > 1) @@ -272,16 +273,16 @@ public class CarpetSettings } } b.close(); - return result; + return Pair.of(result, confLocked); } catch(FileNotFoundException e) { - return new HashMap<>(); + return Pair.of(new HashMap<>(), false); } catch (IOException e) { e.printStackTrace(); - return new HashMap<>(); + return Pair.of(new HashMap<>(), false); } } @@ -311,9 +312,10 @@ public class CarpetSettings if (locked) return false; if (settings_store.containsKey(setting_name)) { - Map conf = read_conf(source.getMinecraftServer()); - conf.put(setting_name, string_value); - write_conf(source.getMinecraftServer(), conf); + Pair,Boolean> conf = read_conf(source.getMinecraftServer()); + conf.getLeft().put(setting_name, string_value); + write_conf(source.getMinecraftServer(), conf.getLeft()); // this may feels weird, but if conf + // is locked, it will never reach this point. set(source, setting_name,string_value); return true; } @@ -325,9 +327,9 @@ public class CarpetSettings if (locked) return false; if (settings_store.containsKey(setting_name)) { - Map conf = read_conf(source.getMinecraftServer()); - conf.remove(setting_name); - write_conf(source.getMinecraftServer(), conf); + Pair,Boolean> conf = read_conf(source.getMinecraftServer()); + conf.getLeft().remove(setting_name); + write_conf(source.getMinecraftServer(), conf.getLeft()); set(source, setting_name,get(setting_name).getDefault()); return true; } @@ -386,7 +388,7 @@ public class CarpetSettings public static CarpetSettingEntry[] find_nondefault(MinecraftServer server) { ArrayList res = new ArrayList(); - Map defaults = read_conf(server); + Map defaults = read_conf(server).getLeft(); for (String rule: settings_store.keySet().stream().sorted().collect(Collectors.toList())) { if (!settings_store.get(rule).isDefault() || defaults.containsKey(rule)) @@ -400,7 +402,7 @@ public class CarpetSettings { ArrayList res = new ArrayList(); if (locked) return res.toArray(new CarpetSettingEntry[0]); - Map defaults = read_conf(server); + Map defaults = read_conf(server).getLeft(); for (String rule: settings_store.keySet().stream().sorted().collect(Collectors.toList())) { if (defaults.containsKey(rule)) diff --git a/src/main/java/carpet/commands/ParcetCommand.java b/src/main/java/carpet/commands/ParcetCommand.java new file mode 100644 index 00000000..a614c7bb --- /dev/null +++ b/src/main/java/carpet/commands/ParcetCommand.java @@ -0,0 +1,209 @@ +package carpet.commands; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.settings.ParsedRule; +import carpet.settings.RuleCategory; +import carpet.settings.SettingsManager; +import carpet.utils.Messenger; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.text.BaseText; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; +import static net.minecraft.server.command.CommandSource.suggestMatching; + +public class ParcetCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = literal("parcet").requires((player) -> + player.hasPermissionLevel(2) && !CarpetServer.settingsManager.locked); + + literalargumentbuilder.executes((context)->listAllSettings(context.getSource())). + then(literal("list"). + executes( (c) -> listSettings(c.getSource(), + "All CarpetMod Settings", + SettingsManager.getRules())). + then(literal("defaults"). + executes( (c)-> listSettings(c.getSource(), + "Current CarpetMod Startup Settings from carpet.conf", + CarpetServer.settingsManager.findStartupOverrides()))). + then(argument("tag",StringArgumentType.word()). + suggests( (c, b)->suggestMatching(Arrays.stream(RuleCategory.values()).map(r -> r.lowerCase), b)). + executes( (c) -> listSettings(c.getSource(), + String.format("CarpetMod Settings matching \"%s\"", StringArgumentType.getString(c, "tag")), + CarpetServer.settingsManager.getRulesMatching(StringArgumentType.getString(c, "tag")))))); + + for (ParsedRule rule: SettingsManager.getRules()) + { + literalargumentbuilder.then(literal(rule.name).executes( (context) -> + displayRuleMenu(context.getSource(),rule))); + literalargumentbuilder.then(literal("removeDefault"). + requires(s -> !CarpetServer.settingsManager.locked). + then(literal(rule.name).executes((context) -> + removeDefault(context.getSource(), rule)))); + literalargumentbuilder.then(literal(rule.name). + requires(s -> !CarpetServer.settingsManager.locked). + then(argument("value", StringArgumentType.word()). + suggests((c, b)-> suggestMatching(rule.options,b)). + executes((context) -> + setRule(context.getSource(), rule, StringArgumentType.getString(context, "value"))))); + literalargumentbuilder.then(literal("setDefault"). + requires(s -> !CarpetServer.settingsManager.locked). + then(literal(rule.name). + then(argument("value", StringArgumentType.word()). + suggests((c, b)-> suggestMatching(rule.options,b)). + executes((context) -> + setDefault(context.getSource(), rule, StringArgumentType.getString(context, "value")))))); + } + dispatcher.register(literalargumentbuilder); + } + + private static int displayRuleMenu(ServerCommandSource source, ParsedRule rule) + { + PlayerEntity player; + try + { + player = source.getPlayer(); + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "w "+rule.name +" is set to: ","wb "+rule.getAsString()); + return 1; + } + + Messenger.m(player, ""); + Messenger.m(player, "wb "+rule.name,"!/carpet "+rule.name,"^g refresh"); + Messenger.m(player, "w "+rule.description); + + rule.extraInfo.forEach(s -> Messenger.m(player, "g "+s)); + + List tags = new ArrayList<>(); + tags.add(Messenger.c("w Tags: ")); + for (String t: Arrays.stream(RuleCategory.values()).sorted().map(c -> c.lowerCase).collect(Collectors.toList())) + { + tags.add(Messenger.c("c ["+t+"]", "^g list all "+t+" settings","!/carpet list "+t)); + tags.add(Messenger.c("w , ")); + } + tags.remove(tags.size()-1); + Messenger.m(player, tags.toArray(new Object[0])); + + Messenger.m(player, "w Current value: ",String.format("%s %s (%s value)",rule.getBoolValue()?"lb":"nb", rule.getAsString(),rule.isDefault()?"default":"modified")); + List options = new ArrayList<>(); + options.add(Messenger.c("w Options: ", "y [ ")); + for (String o: rule.options) + { + options.add(makeSetRuleButton(rule, o, false)); + options.add(Messenger.c("w ")); + } + options.remove(options.size()-1); + options.add(Messenger.c("y ]")); + Messenger.m(player, options.toArray(new Object[0])); + + return 1; + } + + private static int setRule(ServerCommandSource source, ParsedRule rule, String newValue) + { + if (rule.set(source, newValue) != null) + Messenger.m(source, "w "+rule.toString()+", ", "c [change permanently?]", + "^w Click to keep the settings in carpet.conf to save across restarts", + "?/carpet setDefault "+rule.name+" "+rule.getAsString()); + return 1; + } + private static int setDefault(ServerCommandSource source, ParsedRule rule, String defaultValue) + { + CarpetServer.settingsManager.setDefaultRule(source, rule.name, defaultValue); + Messenger.m(source ,"gi rule "+ rule.name+" will now default to "+ defaultValue); + return 1; + } + private static int removeDefault(ServerCommandSource source, ParsedRule rule) + { + CarpetServer.settingsManager.removeDefaultRule(source, rule.name); + Messenger.m(source ,"gi rule "+ rule.name+" defaults to Vanilla"); + return 1; + } + + + private static BaseText displayInteractiveSetting(ParsedRule rule) + { + List args = new ArrayList<>(); + args.add("w - "+rule.name+" "); + args.add("!/carpet "+rule.name); + args.add("^y "+rule.description); + for (String option: rule.options) + { + args.add(makeSetRuleButton(rule, option, true)); + args.add("w "); + } + args.remove(args.size()-1); + return Messenger.c(args.toArray(new Object[0])); + } + + private static BaseText makeSetRuleButton(ParsedRule rule, String option, boolean brackets) { + String color = ""; + if (option.equals(rule.defaultAsString) && !brackets) color += "u"; + color += option.equals(rule.getAsString()) ? "bl" : "y"; + String baseText = color + (brackets ? " [" : " ") + option + (brackets ? "]" : ""); + if (CarpetServer.settingsManager.locked) { + return Messenger.c(baseText, "^g Settings are locked"); + } + return Messenger.c(baseText, "^g Switch to " + option, "?/carpet " + rule.name + " " + option); + } + + private static int listSettings(ServerCommandSource source, String title, Collection> settings_list) + { + try + { + PlayerEntity player = source.getPlayer(); + Messenger.m(player,String.format("wb %s:",title)); + settings_list.forEach(e -> Messenger.m(player,displayInteractiveSetting(e))); + + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "w s:"+title); + settings_list.forEach(r -> Messenger.m(source, "w - "+ r.toString())); + } + return 1; + } + private static int listAllSettings(ServerCommandSource source) + { + listSettings(source, "Current CarpetMod Settings", CarpetServer.settingsManager.getNonDefault()); + + Messenger.m(source, "Carpet Mod version: "+ CarpetSettings.carpetVersion); + try + { + PlayerEntity player = source.getPlayer(); + List tags = new ArrayList<>(); + tags.add("w Browse Categories:\n"); + for (String t : Arrays.stream(RuleCategory.values()).map(r -> r.lowerCase).collect(Collectors.toList())) + { + tags.add("c [" + t+"]"); + tags.add("^g list all " + t + " settings"); + tags.add("!/carpet list " + t); + tags.add("w "); + } + tags.remove(tags.size() - 1); + Messenger.m(player, tags.toArray(new Object[0])); + } + catch (CommandSyntaxException e) + { + } + return 1; + } +} diff --git a/src/main/java/carpet/settings/ParsedRule.java b/src/main/java/carpet/settings/ParsedRule.java new file mode 100644 index 00000000..224b2895 --- /dev/null +++ b/src/main/java/carpet/settings/ParsedRule.java @@ -0,0 +1,170 @@ +package carpet.settings; + +import carpet.utils.Messenger; +import com.google.common.collect.ImmutableList; +import net.minecraft.server.command.ServerCommandSource; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public final class ParsedRule implements Comparable { + public final Rule rule; + public final Field field; + + public final String name; + public final String description; + public final ImmutableList extraInfo; + public final ImmutableList categories; + public final ImmutableList options; + public boolean isStrict; + public final Class type; + public final List> validators; + public final T defaultValue; + public final String defaultAsString; + + public static T callConstructor(Class cls) { + try { + Constructor constr = cls.getDeclaredConstructor(); + constr.setAccessible(true); + return constr.newInstance(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + ParsedRule(Field field, Rule rule) { + this.rule = rule; + this.field = field; + this.name = rule.name().isEmpty() ? field.getName() : rule.name(); + this.type = (Class) field.getType(); + this.description = rule.desc(); + this.isStrict = rule.strict(); + this.extraInfo = ImmutableList.copyOf(rule.extra()); + this.categories = ImmutableList.copyOf(rule.category()); + this.validators = new ArrayList<>(); + for (Class v : rule.validate()) + { + this.validators.add((Validator) callConstructor(v)); + } + if (categories.contains(RuleCategory.COMMANDS)) + this.validators.add(callConstructor(Validator._COMMAND.class)); + + this.defaultValue = get(); + this.defaultAsString = convertToString(this.defaultValue); + if (this.type == boolean.class) { + this.isStrict = true; // it has no sense otherwise + this.options = ImmutableList.of("true", "false"); + } else if (this.type.isEnum()) { + this.isStrict = true; // it has no sense otherwise + this.options = Arrays.stream(this.type.getEnumConstants()).map(e -> ((Enum) e).name().toLowerCase(Locale.ROOT)).collect(ImmutableList.toImmutableList()); + } else { + this.options = ImmutableList.copyOf(rule.options()); + } + if (isStrict) + this.validators.add(callConstructor(Validator._STRICT.class)); + } + + + public ParsedRule set(ServerCommandSource source, String value) { + //if locked - prevent + if (type == String.class) + { + return set(source, (T) value, value); + } + else if (type == boolean.class) + { + return set(source, (T) (Object) Boolean.parseBoolean(value), value); + } + else if (type == int.class) + { + return set(source, (T) (Object) Integer.parseInt(value), value); + } + else if (type.isEnum()) + { + String ucValue = value.toUpperCase(Locale.ROOT); + return set(source, (T) (Object) Enum.valueOf((Class) type, ucValue), value); + } + else + { + Messenger.m(source, "r Unknown type " + type.getSimpleName()); + return null; + } + } + + public ParsedRule set(ServerCommandSource source, T value, String stringValue) { + try { + for (Validator validator : this.validators) + { + boolean allowed = validator.validate(source, this, value, stringValue); + if (!allowed) + { + Messenger.m(source, "r Wrong value for " + name + ": " + stringValue); + return null; + } + } + this.field.set(null, value); + + } + catch (IllegalAccessException e) + { + Messenger.m(source, "r Unable to access setting for "+name); + } + return this; + } + + public T get() { + try { + return (T) this.field.get(null); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + + public String getAsString() { + return convertToString(get()); + } + + public boolean getBoolValue() { + if (type == boolean.class) return (Boolean) get(); + if (type.isAssignableFrom(Number.class)) return ((Number) get()).doubleValue() > 0; + return false; + } + + public boolean isDefault() { + return defaultValue.equals(get()); + } + + public void resetToDefault(ServerCommandSource source) { + set(source, defaultValue, defaultAsString); + } + + + private static String convertToString(Object value) { + if (value instanceof Enum) return ((Enum) value).name().toLowerCase(Locale.ROOT); + return value.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj.getClass() == ParsedRule.class && ((ParsedRule) obj).name.equals(this.name); + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public int compareTo(ParsedRule o) { + return this.name.compareTo(o.name); + } + + @Override + public String toString() { + return this.name + ": " + getAsString(); + } +} diff --git a/src/main/java/carpet/settings/Rule.java b/src/main/java/carpet/settings/Rule.java new file mode 100644 index 00000000..f49854c9 --- /dev/null +++ b/src/main/java/carpet/settings/Rule.java @@ -0,0 +1,58 @@ +package carpet.settings; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Any field in this class annotated with this class is interpreted as a carpet rule. + * The field must be static and have a type of one of: + * - boolean + * - int + * - double + * - String + * - a subclass of Enum + * The default value of the rule will be the initial value of the field. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@interface Rule { + /** + * The rule name, by default the same as the field name + */ + String name() default ""; // default same as field name + + /** + * A description of the rule + */ + String desc(); + + /** + * Extra information about the rule + */ + String[] extra() default {}; + + /** + * A list of categories the rule is in + */ + RuleCategory[] category(); + + /** + * Options to select in menu. + * Inferred for booleans and enums. Otherwise, must be present. + */ + String[] options() default {}; + + /** + * if a rule is not strict - can take any value, otherwise it needs to match + * any of the options + * For enums, its always strict + */ + boolean strict() default true; + + /** + * The class of the validator checked when the rule is changed. + */ + Class[] validate() default {}; +} diff --git a/src/main/java/carpet/settings/RuleCategory.java b/src/main/java/carpet/settings/RuleCategory.java new file mode 100644 index 00000000..990701aa --- /dev/null +++ b/src/main/java/carpet/settings/RuleCategory.java @@ -0,0 +1,13 @@ +package carpet.settings; + +import java.util.Locale; + +public enum RuleCategory { + BUGFIX, SURVIVAL, CREATIVE, EXPERIMENTAL, OPTIMIZATIONS, FEATURE, COMMANDS ; + + public final String lowerCase; + + RuleCategory() { + this.lowerCase = this.name().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/carpet/settings/Settings.java b/src/main/java/carpet/settings/Settings.java new file mode 100644 index 00000000..00228776 --- /dev/null +++ b/src/main/java/carpet/settings/Settings.java @@ -0,0 +1,84 @@ +package carpet.settings; + +import static carpet.settings.RuleCategory.BUGFIX; +import static carpet.settings.RuleCategory.COMMANDS; +import static carpet.settings.RuleCategory.CREATIVE; +import static carpet.settings.RuleCategory.EXPERIMENTAL; +import static carpet.settings.RuleCategory.FEATURE; +import static carpet.settings.RuleCategory.OPTIMIZATIONS; +import static carpet.settings.RuleCategory.SURVIVAL; + +public class Settings +{ + @Rule( + desc = "Fixes server crashing supposedly on falling behind 60s in ONE tick, yeah bs.", + extra = "Fixed 1.12 watchdog crash in 1.13 pre-releases, reintroduced with 1.13, GG.", + category = BUGFIX, + validate = Validator.WIP.class + ) + public static boolean watchdogCrashFix = false; + + @Rule( + desc = "Enables /c and /s commands to quickly switch between camera and survival modes", + extra = "/c and /s commands are available to all players regardless of their permission levels", + category = COMMANDS + ) + public static boolean commandCameramode = true; + + @Rule(desc = "fill/clone/setblock and structure blocks cause block updates", category = CREATIVE) + public static boolean fillUpdates = true; + + @Rule( + desc = "Customizable fill/clone volume limit", + options = {"32768", "250000", "1000000"}, + validate = Validator.POSITIVE_NUMBER.class, + category = CREATIVE + ) + public static int fillLimit = 32768; + + @Rule( + desc = "Hoppers pointing to wool will count items passing through them", + extra = { + "Enables /counter command, and actions while placing red and green carpets on wool blocks", + "Use /counter reset to reset the counter, and /counter to query", + "In survival, place green carpet on same color wool to query, red to reset the counters", + "Counters are global and shared between players, 16 channels available", + "Items counted are destroyed, count up to one stack per tick per hopper" + }, + category = COMMANDS + ) + public static boolean hopperCounters = false; + + @Rule(desc = "Explosions won't destroy blocks", category = CREATIVE) + public static boolean explosionNoBlockDamage = false; + + @Rule( + desc = "Silverfish drop a gravel item when breaking out of a block", + category = {FEATURE, EXPERIMENTAL} + ) + public static boolean silverFishDropGravel = false; + + @Rule(desc = "Shulkers will respawn in end cities", category = {FEATURE, EXPERIMENTAL}) + public static boolean shulkerSpawningInEndCities = false; + + @Rule( + desc = "Portals won't let a creative player go through instantly", + extra = "Holding obsidian in either hand won't let you through at all", + category = CREATIVE + ) + public static boolean portalCreativeDelay = false; + + @Rule(desc = "Pistons can push block entities, like hoppers, chests etc.", category = {FEATURE, EXPERIMENTAL}) + public static boolean movableTileEntities = false; + + @Rule( + desc = "Empty shulker boxes can stack to 64 when dropped on the ground", + extra = "To move them around between inventories, use shift click to move entire stacks", + category = SURVIVAL + ) + public static boolean stackableShulkerBoxes = false; + + @Rule(desc = "Optimizes spawning", category = {OPTIMIZATIONS, EXPERIMENTAL}) + public static boolean lagFreeSpawning = false; + +} diff --git a/src/main/java/carpet/settings/SettingsManager.java b/src/main/java/carpet/settings/SettingsManager.java new file mode 100644 index 00000000..34b8941a --- /dev/null +++ b/src/main/java/carpet/settings/SettingsManager.java @@ -0,0 +1,230 @@ +package carpet.settings; + +import carpet.CarpetSettings; +import com.google.common.collect.ImmutableList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class SettingsManager { + private static final Logger LOG = LogManager.getLogger(); + private static Map> rules = new HashMap<>(); + public boolean locked; + MinecraftServer server; + + public SettingsManager(MinecraftServer server) + { + this.server = server; + loadConfigurationFromConf(); + } + + + public static void parseSettingsClass(Class settingsClass) + { + for (Field f : settingsClass.getDeclaredFields()) + { + Rule rule = f.getAnnotation(Rule.class); + if (rule == null) continue; + ParsedRule parsed = new ParsedRule(f, rule); + CarpetSettings.LOG.error("REGISTERED RULE: "+parsed.name); + rules.put(parsed.name, parsed); + } + } + + + public ParsedRule getRule(String name) { + return rules.get(name); + } + + public static Collection> getRules() { + CarpetSettings.LOG.error("grabbing rules"); + List> r = rules.values().stream().sorted().collect(Collectors.toList()); + CarpetSettings.LOG.error("Got: "+r.size()+" rules"); + return r; + } + + public Collection> findStartupOverrides() + { + Set defaults = readSettingsFromConf().getLeft().keySet(); + return rules.values().stream().filter(r -> defaults.contains(r.name)). + sorted().collect(Collectors.toList()); + } + + + public Collection> getNonDefault() { + return rules.values().stream().filter(r -> !r.isDefault()).sorted().collect(Collectors.toList()); + } + + private File getFile() { + return server.getLevelStorage().resolveFile(server.getLevelName(), "carpet.conf"); + } + + public void disableBooleanFromCategory(RuleCategory category) { + for (ParsedRule rule : rules.values()) { + if (rule.type != boolean.class || !rule.categories.contains(category)) continue; + ((ParsedRule) rule).set(server.getCommandSource(), false, "false"); + } + } + + + + private void writeSettingsToConf(Map values) + { + if (locked) return; + try + { + FileWriter fw = new FileWriter(getFile()); + for (String key: values.keySet()) + { + fw.write(key+" "+values.get(key)+"\n"); + } + fw.close(); + } + catch (IOException e) + { + e.printStackTrace(); + LOG.error("[CM]: failed write the carpet.conf"); + } + ///todo is it really needed? resendCommandTree(); + } + + public void notifyPlayersCommandsChanged() + { + if (server.getPlayerManager() == null) + { + return; + } + for (ServerPlayerEntity entityplayermp : server.getPlayerManager().getPlayerList()) + { + server.getCommandManager().sendCommandTree(entityplayermp); + } + } + + private void loadConfigurationFromConf() + { + for (ParsedRule rule : rules.values()) rule.resetToDefault(server.getCommandSource()); + Pair,Boolean> conf = readSettingsFromConf(); + locked = false; + if (conf.getRight()) + { + LOG.info("[CM]: Carpet Mod is locked by the administrator"); + disableBooleanFromCategory(RuleCategory.COMMANDS); + } + for (String key: conf.getLeft().keySet()) + { + if (rules.get(key).set(server.getCommandSource(), conf.getLeft().get(key)) != null) + LOG.info("[CM]: loaded setting "+key+" as "+conf.getLeft().get(key)+" from carpet.conf"); + } + locked = conf.getRight(); + } + + + public Pair,Boolean> readSettingsFromConf() + { + try + { + BufferedReader reader = new BufferedReader(new FileReader(getFile())); + String line = ""; + boolean confLocked = false; + Map result = new HashMap(); + while ((line = reader.readLine()) != null) + { + line = line.replaceAll("\\r|\\n", ""); + if ("locked".equalsIgnoreCase(line)) + { + confLocked = true; + } + String[] fields = line.split("\\s+",2); + if (fields.length > 1) + { + if (!rules.containsKey(fields[0])) + { + LOG.error("[CM]: Setting " + fields[0] + " is not a valid - ignoring..."); + continue; + } + ParsedRule rule = rules.get(fields[0]); + + + + if (!(rule.options.contains(fields[1])) && rule.isStrict) + { + LOG.error("[CM]: The value of " + fields[1] + " for " + fields[0] + " is not valid - ignoring..."); + continue; + } + result.put(fields[0],fields[1]); + + } + } + reader.close(); + return Pair.of(result, confLocked); + } + catch(FileNotFoundException e) + { + return Pair.of(new HashMap<>(), false); + } + catch (IOException e) + { + e.printStackTrace(); + return Pair.of(new HashMap<>(), false); + } + } + + // stores different defaults in the file + public boolean setDefaultRule(ServerCommandSource source, String settingName, String stringValue) + { + if (locked) return false; + if (rules.containsKey(settingName)) + { + Pair,Boolean> conf = readSettingsFromConf(); + conf.getLeft().put(settingName, stringValue); + writeSettingsToConf(conf.getLeft()); // this may feels weird, but if conf + // is locked, it will never reach this point. + rules.get(settingName).set(source,stringValue); + return true; + } + return false; + } + // removes overrides of the default values in the file + public boolean removeDefaultRule(ServerCommandSource source, String settingName) + { + if (locked) return false; + if (rules.containsKey(settingName)) + { + Pair,Boolean> conf = readSettingsFromConf(); + conf.getLeft().remove(settingName); + writeSettingsToConf(conf.getLeft()); + rules.get(settingName).resetToDefault(source); + return true; + } + return false; + } + + public Collection> getRulesMatching(Predicate> predicate) { + return rules.values().stream().filter(predicate).collect(ImmutableList.toImmutableList()); + } + + public Collection> getRulesMatching(String search) { + String lcSearch = search.toLowerCase(Locale.ROOT); + return getRulesMatching(rule -> { + if (rule.name.toLowerCase(Locale.ROOT).contains(lcSearch)) return true; + for (RuleCategory c : rule.categories) if (c.lowerCase.equals(search)) return true; + return false; + }); + } +} diff --git a/src/main/java/carpet/settings/Validator.java b/src/main/java/carpet/settings/Validator.java new file mode 100644 index 00000000..9ab287ab --- /dev/null +++ b/src/main/java/carpet/settings/Validator.java @@ -0,0 +1,63 @@ +package carpet.settings; + +import carpet.CarpetServer; +import carpet.utils.Messenger; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.Locale; + +public abstract class Validator { + /** + * Validate the new value of a rule + * @return true if valid, false if new rule invalid. + */ + abstract boolean validate(ServerCommandSource source, ParsedRule currentRule, T newValue, String string); + + + public static class POSITIVE_NUMBER extends Validator { + @Override + public boolean validate(ServerCommandSource source, ParsedRule currentRule, T newValue, String string) { + return newValue.doubleValue() > 0; + } + } + + public static class _COMMAND extends Validator { + @Override + public boolean validate(ServerCommandSource source, ParsedRule currentRule, T newValue, String string) { + if (CarpetServer.settingsManager != null) + CarpetServer.settingsManager.notifyPlayersCommandsChanged(); + return true; + } + } + + public static class WIP extends Validator { + @Override + public boolean validate(ServerCommandSource source, ParsedRule currentRule, T newValue, String string) { + if (source != null) + try + { + Messenger.m(source, "r Work in progress - limited or no functionality available for "+currentRule.name); + } + catch (NullPointerException ignored) { } + return true; + } + } + public static class _STRICT extends Validator { + @Override + public boolean validate(ServerCommandSource source, ParsedRule currentRule, T newValue, String string) { + if (!currentRule.options.contains(string.toLowerCase(Locale.ROOT))) + { + if (source != null) + try + { + Messenger.m(source, "r Valid options: " + currentRule.options.toString()); + } + catch (NullPointerException ignored) + { + } + return false; + } + return true; + } + } +}