Settings rework and encapsulation (#1330)
* Settings rework and encapsulation * Settings overhaul 3 Move everything in the new system to a new API package Require rules using the new system to specify descriptions in their translation files Implement temporary system on top of current translations to allow that to work properly * Fix getRules fallback * Final touches to settings refactor * Mention that set shouldn't throw and remove comment * Change translation keys to unify with translation PR * Deprecate printAllRulesToLog to allow using any PrintStream * Remove restriction of CarpetRules being Comparable And fix overreaction in a deprecation message * Bring back user input to rule observers * Make Validator deprecation warning not spam on use * Remove unneeded abstract methods in CarpetRule already inherited from CarpetRule They literally do nothing, don't even force them to be implemented, and may be confusing as to why they're there or as if they are needed to be implemented * Fix legacy SettingsManager field not being assigned correctly * Finish fixing conflicts from future merge * Actually fix remaining conflicts from future merge (missing imports) * Import the newer rule in SettingsManager * Refactor Validators * Don't spam deprecation warnings on custom managers * Get out the var * Fix missing warning in old rule observers * Update src/main/java/carpet/api/settings/Rule.java * Remove `@ApiStatus.Experimental` annotations * Fix incorrect `@deprecated` javadoc tags By removing them given the class is deprecated anyway and else there's too much noise * Add missing static import * Remove TypedField and use a regular field I liked that class tbh, but it's true that it wasn't really needed * Move `field` assignment out of for removal * Fix not following contract of `type()` returning boxed value Previously handled by TypedField * Fix few incorrect translation keys * Remove resaving of comments when saving config files Comments ignoring is still a thing when reading, so `locked` configs for example will keep comments * Condense rule load messages in one * Only print loaded settings message when at least one setting was changed * Fix legacy validator detector * Tweak and add more context to response to invalid values * Fix strict validator not actually erroring on invalid input
This commit is contained in:
1 parent
f5a92ebb8e
commit
0a972d6c56
44 files changed
+2687
-1616
No files matched your search
@@ -1,5 +1,20 @@
|
||||
package carpet.settings;
|
||||
|
||||
public interface Condition {
|
||||
import carpet.CarpetSettings;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link carpet.api.settings.Rule.Condition} instead
|
||||
*
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public interface Condition extends carpet.api.settings.Rule.Condition {
|
||||
boolean isTrue();
|
||||
|
||||
@Override
|
||||
default boolean shouldRegister() {
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension is referencing outdated Condition class! Class that caused this was: %s
|
||||
This won't be supported in later Carpet versions and will crash the game!""".formatted(getClass().getName()));
|
||||
return isTrue();
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,231 @@
|
||||
package carpet.settings;
|
||||
|
||||
import carpet.utils.Translations;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.InvalidRuleValueException;
|
||||
import carpet.api.settings.RuleHelper;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import carpet.api.settings.Validators;
|
||||
import carpet.utils.Messenger;
|
||||
import carpet.utils.TranslationKeys;
|
||||
import carpet.utils.Translations;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
|
||||
import static carpet.utils.Translations.tr;
|
||||
|
||||
/**
|
||||
* A parsed Carpet rule, with its field, name, value, and other useful stuff.
|
||||
* A Carpet rule parsed from a field, with its name, value, and other useful stuff.
|
||||
*
|
||||
* It is generated from the fields with the {@link Rule} annotation
|
||||
* It is used for the fields with the {@link Rule} annotation
|
||||
* when being parsed by {@link SettingsManager#parseSettingsClass(Class)}.
|
||||
*
|
||||
* @param <T> The field's type
|
||||
* @param <T> The field's (and rule's) type
|
||||
* @deprecated Use the type {@link CarpetRule} instead
|
||||
*/
|
||||
public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
@Deprecated(forRemoval = true) // to move to api.settings package and visibility to package private
|
||||
public final class ParsedRule<T> implements CarpetRule<T>, Comparable<ParsedRule<?>> {
|
||||
private static final Map<Class<?>, FromStringConverter<?>> CONVERTER_MAP = Map.ofEntries(
|
||||
Map.entry(String.class, str -> str),
|
||||
Map.entry(Boolean.class, str -> {
|
||||
return switch (str) {
|
||||
case "true" -> true;
|
||||
case "false" -> false;
|
||||
default -> throw new InvalidRuleValueException("Invalid boolean value");
|
||||
};
|
||||
}),
|
||||
numericalConverter(Integer.class, Integer::parseInt),
|
||||
numericalConverter(Double.class, Double::parseDouble),
|
||||
numericalConverter(Long.class, Long::parseLong),
|
||||
numericalConverter(Float.class, Float::parseFloat)
|
||||
);
|
||||
/**
|
||||
* @deprecated No replacement for this, since a {@link CarpetRule} may not always use a {@link Field}.
|
||||
* Use {@link #value()} to access the rule's value
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final Field field;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#name()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final String name;
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#translatedDescription(CarpetRule)}, or get it from the translation system
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public final String description;
|
||||
public final String scarpetApp;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#extraInfo()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public final List<String> extraInfo;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#categories()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final List<String> categories;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#suggestions()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private (and rename?)
|
||||
public final List<String> options;
|
||||
/**
|
||||
* @deprecated No replacement for this
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to pckg private (for printRulesToLog, or get a different way)
|
||||
public boolean isStrict;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#canBeToggledClientSide()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private (and maybe rename?)
|
||||
public boolean isClient;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#type()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private (or remove and delegate to typedfield?)
|
||||
public final Class<T> type;
|
||||
public final List<Validator<T>> validators;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#defaultValue()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final T defaultValue;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#settingsManager()} instead.
|
||||
* This field may be {@code null} if the settings manager isn't an instance of the old type
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove in favour of realSettingsManager
|
||||
public final carpet.settings.SettingsManager settingsManager;
|
||||
/**
|
||||
* @deprecated No replacement for this. A Carpet rule may not use {@link Validator}
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove (in favour of realValidators)
|
||||
public final List<Validator<T>> validators;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#defaultValue()} and pass it to {@link RuleHelper#toRuleString(Object)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public final String defaultAsString;
|
||||
public final SettingsManager settingsManager;
|
||||
/**
|
||||
* @deprecated No replacement for this, Scarpet Rules should be managed by the rule implementation
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private/subclass
|
||||
public final String scarpetApp;
|
||||
private final FromStringConverter<T> converter;
|
||||
private final SettingsManager realSettingsManager; // to rename to settingsManager
|
||||
/**
|
||||
* If you reference this field I'll steal your kneecaps
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public final List<carpet.api.settings.Validator<T>> realValidators; // to rename to validators and to package private for printRulesToLog
|
||||
private final boolean isLegacy; // to remove, only used for fallbacks
|
||||
|
||||
@FunctionalInterface
|
||||
interface FromStringConverter<T> {
|
||||
T convert(String value) throws InvalidRuleValueException;
|
||||
}
|
||||
|
||||
record RuleAnnotation(boolean isLegacy, String name, String desc, String[] extra, String[] category, String[] options, boolean strict, String appSource, Class<? extends carpet.api.settings.Validator>[] validators) {
|
||||
}
|
||||
|
||||
ParsedRule(Field field, Rule rule, SettingsManager settingsManager)
|
||||
/**
|
||||
* If you call this method I'll steal your kneecaps
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T> ParsedRule<T> of(Field field, SettingsManager settingsManager) {
|
||||
RuleAnnotation rule;
|
||||
if (settingsManager instanceof carpet.settings.SettingsManager && field.isAnnotationPresent(Rule.class)) { // Legacy path
|
||||
Rule a = field.getAnnotation(Rule.class);
|
||||
rule = new RuleAnnotation(true, a.name(), a.desc(), a.extra(), a.category(), a.options(), a.strict(), a.appSource(), a.validate());
|
||||
} else if (field.isAnnotationPresent(carpet.api.settings.Rule.class)) {
|
||||
carpet.api.settings.Rule a = field.getAnnotation(carpet.api.settings.Rule.class);
|
||||
rule = new RuleAnnotation(false, null, null, null, a.categories(), a.options(), a.strict(), a.appSource(), a.validators());
|
||||
} else {
|
||||
// Don't allow to use old rule types in custom AND migrated settings manager
|
||||
throw new IllegalArgumentException("Old rule annotation is only supported in legacy SettngsManager!");
|
||||
}
|
||||
return new ParsedRule<>(field, rule, settingsManager);
|
||||
}
|
||||
|
||||
private ParsedRule(Field field, RuleAnnotation rule, SettingsManager settingsManager)
|
||||
{
|
||||
this.isLegacy = rule.isLegacy();
|
||||
this.name = !isLegacy || rule.name().isEmpty() ? field.getName() : rule.name();
|
||||
this.field = field;
|
||||
this.name = rule.name().isEmpty() ? field.getName() : rule.name();
|
||||
this.type = (Class<T>) field.getType();
|
||||
this.description = rule.desc();
|
||||
@SuppressWarnings("unchecked") // We are "defining" T here
|
||||
Class<T> type = (Class<T>)ClassUtils.primitiveToWrapper(field.getType());
|
||||
this.type = type;
|
||||
this.isStrict = rule.strict();
|
||||
this.extraInfo = List.of(rule.extra());
|
||||
this.categories = List.of(rule.category());
|
||||
this.scarpetApp = rule.appSource();
|
||||
this.settingsManager = settingsManager;
|
||||
this.validators = new ArrayList<>();
|
||||
for (Class v : rule.validate())
|
||||
this.validators.add((Validator<T>) callConstructor(v));
|
||||
this.realSettingsManager = settingsManager;
|
||||
if (!(settingsManager instanceof carpet.settings.SettingsManager)) {
|
||||
// this is awkward... but people using a custom, new (extends only new api) manager should not be using this anyway but the interface method
|
||||
this.settingsManager = null;
|
||||
} else {
|
||||
this.settingsManager = (carpet.settings.SettingsManager) settingsManager;
|
||||
}
|
||||
this.realValidators = Stream.of(rule.validators()).map(this::instantiateValidator).collect(Collectors.toList());
|
||||
this.defaultValue = value();
|
||||
FromStringConverter<T> converter0 = null;
|
||||
|
||||
if (categories.contains(RuleCategory.COMMAND))
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._COMMAND.class));
|
||||
this.realValidators.add(new Validator._COMMAND<T>());
|
||||
if (this.type == String.class)
|
||||
{
|
||||
this.isStrict = false;
|
||||
this.validators.add((Validator<T>) callConstructor(Validator._COMMAND_LEVEL_VALIDATOR.class));
|
||||
this.realValidators.add(instantiateValidator(Validators.CommandLevel.class));
|
||||
}
|
||||
}
|
||||
if (!scarpetApp.isEmpty())
|
||||
{
|
||||
this.validators.add((Validator<T>) callConstructor(Validator._SCARPET.class));
|
||||
}
|
||||
|
||||
this.isClient = categories.contains(RuleCategory.CLIENT);
|
||||
if (this.isClient)
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._CLIENT.class));
|
||||
this.realValidators.add(new Validator._CLIENT<>());
|
||||
}
|
||||
this.defaultValue = get();
|
||||
this.defaultAsString = convertToString(this.defaultValue);
|
||||
|
||||
if (!scarpetApp.isEmpty())
|
||||
{
|
||||
this.realValidators.add(new Validator.ScarpetValidator<>());
|
||||
}
|
||||
|
||||
if (rule.options().length > 0)
|
||||
{
|
||||
this.options = List.of(rule.options());
|
||||
}
|
||||
else if (this.type == boolean.class){
|
||||
this.options = List.of("true","false");
|
||||
else if (this.type == Boolean.class) {
|
||||
this.options = List.of("true", "false");
|
||||
}
|
||||
else if(this.type == String.class && categories.contains(RuleCategory.COMMAND))
|
||||
else if (this.type == String.class && categories.contains(RuleCategory.COMMAND))
|
||||
{
|
||||
this.options = List.of("true", "false", "ops");
|
||||
this.options = Validators.CommandLevel.OPTIONS;
|
||||
}
|
||||
else if (this.type.isEnum())
|
||||
{
|
||||
this.options = Arrays.stream(this.type.getEnumConstants()).map(e -> ((Enum) e).name().toLowerCase(Locale.ROOT)).collect(Collectors.toUnmodifiableList());
|
||||
this.options = Arrays.stream(this.type.getEnumConstants()).map(e -> ((Enum<?>) e).name().toLowerCase(Locale.ROOT)).collect(Collectors.toUnmodifiableList());
|
||||
converter0 = str -> {
|
||||
try {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"}) // Raw necessary because of signature. Unchecked because compiler doesn't know T extends Enum
|
||||
T ret = (T)Enum.valueOf((Class<? extends Enum>) type, str.toUpperCase(Locale.ROOT));
|
||||
return ret;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidRuleValueException("Valid values for this rule are: " + this.options);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -93,157 +233,83 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
}
|
||||
if (isStrict && !this.options.isEmpty())
|
||||
{
|
||||
if (this.type == boolean.class || this.type == int.class || this.type == double.class || this.type == float.class)
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._STRICT_IGNORECASE.class));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._STRICT.class));
|
||||
}
|
||||
this.realValidators.add(0, new Validator.StrictValidator<>()); // at 0 prevents validators with side effects from running when invalid
|
||||
}
|
||||
if (converter0 == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
FromStringConverter<T> converterFromMap = (FromStringConverter<T>)CONVERTER_MAP.get(type);
|
||||
if (converterFromMap == null) throw new UnsupportedOperationException("Unsupported type for ParsedRule" + type);
|
||||
converter0 = converterFromMap;
|
||||
}
|
||||
this.converter = converter0;
|
||||
|
||||
// Language "constants"
|
||||
String nameKey = TranslationKeys.RULE_NAME_PATTERN.formatted(settingsManager().identifier(), name());
|
||||
String descKey = TranslationKeys.RULE_DESC_PATTERN.formatted(settingsManager().identifier(), name());
|
||||
String extraPrefix = TranslationKeys.RULE_EXTRA_PREFIX_PATTERN.formatted(settingsManager().identifier(), name());
|
||||
|
||||
// to remove
|
||||
this.description = isLegacy ? rule.desc() : Objects.requireNonNull(Translations.trOrNull(descKey), "No language key provided for " + descKey);
|
||||
this.extraInfo = isLegacy ? List.of(rule.extra()) : getTranslationArray(extraPrefix);
|
||||
this.defaultAsString = RuleHelper.toRuleString(this.defaultValue);
|
||||
this.validators = realValidators.stream().filter(Validator.class::isInstance).map(v -> (Validator<T>) v).toList();
|
||||
if (!isLegacy && !validators.isEmpty()) throw new IllegalArgumentException("Can't use legacy validators with new rules!");
|
||||
|
||||
// Language fallbacks - Also asserts the strings will be available in non-english languages, given current system has no fallback
|
||||
if (isLegacy && !rule.name().isEmpty()) Translations.registerFallbackTranslation(nameKey, name);
|
||||
Translations.registerFallbackTranslation(descKey, description);
|
||||
Iterator<String> infoIterator = extraInfo.iterator();
|
||||
for (int i = 0; infoIterator.hasNext(); i++) {
|
||||
Translations.registerFallbackTranslation(extraPrefix + i, infoIterator.next());
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T callConstructor(Class<T> cls)
|
||||
@SuppressWarnings({"unchecked", "rawtypes"}) // Needed because of the annotation
|
||||
private carpet.api.settings.Validator<T> instantiateValidator(Class<? extends carpet.api.settings.Validator> cls)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor<T> constr = cls.getDeclaredConstructor();
|
||||
Constructor<? extends carpet.api.settings.Validator> constr = cls.getDeclaredConstructor();
|
||||
constr.setAccessible(true);
|
||||
return constr.newInstance();
|
||||
}
|
||||
catch (ReflectiveOperationException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public ParsedRule<T> set(CommandSourceStack source, String value)
|
||||
@Override
|
||||
public void set(CommandSourceStack source, String value) throws InvalidRuleValueException
|
||||
{
|
||||
if (settingsManager != null && settingsManager.locked)
|
||||
return null;
|
||||
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 == double.class)
|
||||
{
|
||||
return set(source, (T) (Object) Double.parseDouble(value), value);
|
||||
}
|
||||
else if (type.isEnum())
|
||||
{
|
||||
String ucValue = value.toUpperCase(Locale.ROOT);
|
||||
return set(source, (T) (Object) Enum.valueOf((Class<? extends Enum>) type, ucValue), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messenger.m(source, "r Unknown type " + type.getSimpleName());
|
||||
return null;
|
||||
}
|
||||
set(source, converter.convert(value), value);
|
||||
}
|
||||
|
||||
ParsedRule<T> set(CommandSourceStack source, T value, String stringValue)
|
||||
private void set(CommandSourceStack source, T value, String userInput) throws InvalidRuleValueException
|
||||
{
|
||||
try
|
||||
for (carpet.api.settings.Validator<T> validator : this.realValidators)
|
||||
{
|
||||
for (Validator<T> validator : this.validators)
|
||||
{
|
||||
value = validator.validate(source, this, value, stringValue);
|
||||
if (value == null)
|
||||
{
|
||||
if (source != null)
|
||||
{
|
||||
validator.notifyFailure(source, this, stringValue);
|
||||
if (validator.description() != null)
|
||||
Messenger.m(source, "r " + validator.description());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
value = validator.validate(source, this, value, userInput); // should this recalculate the string? Another validator may have changed value
|
||||
if (value == null) {
|
||||
if (source != null) validator.notifyFailure(source, this, userInput);
|
||||
throw new InvalidRuleValueException();
|
||||
}
|
||||
if (!value.equals(get()) || source == null)
|
||||
{
|
||||
}
|
||||
if (!value.equals(value()) || source == null)
|
||||
{
|
||||
try {
|
||||
this.field.set(null, value);
|
||||
if (source != null) settingsManager.notifyRuleChanged(source, this, stringValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Couldn't access field for rule: " + name, e);
|
||||
}
|
||||
if (source != null) settingsManager().notifyRuleChanged(source, this, userInput);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
Messenger.m(source, "r Unable to access setting for "+name);
|
||||
return null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, in its type
|
||||
*/
|
||||
public T get()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T) this.field.get(null);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, as a {@link String}
|
||||
*/
|
||||
public String getAsString()
|
||||
{
|
||||
return convertToString(get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, converted to a {@link boolean}.
|
||||
* It will only return {@link true} if it's a true {@link boolean} or
|
||||
* a number greater than zero.
|
||||
*/
|
||||
public boolean getBoolValue()
|
||||
{
|
||||
if (type == boolean.class) return (Boolean) get();
|
||||
if (ClassUtils.primitiveToWrapper(type).isAssignableFrom(Number.class)) return ((Number) get()).doubleValue() > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Wether or not this {@link ParsedRule} is in its default value
|
||||
*/
|
||||
public boolean isDefault()
|
||||
{
|
||||
return defaultValue.equals(get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this rule to its default value
|
||||
*/
|
||||
public void resetToDefault(CommandSourceStack 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);
|
||||
return obj instanceof ParsedRule && ((ParsedRule<?>) obj).name.equals(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -253,55 +319,187 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ParsedRule o)
|
||||
public String toString()
|
||||
{
|
||||
return this.name.compareTo(o.name);
|
||||
return this.name + ": " + RuleHelper.toRuleString(value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.name + ": " + getAsString();
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private String translationKey()
|
||||
{
|
||||
return String.format("rule.%s.name", name);
|
||||
@Override
|
||||
public List<Component> extraInfo() {
|
||||
return getTranslationArray(TranslationKeys.RULE_EXTRA_PREFIX_PATTERN.formatted(settingsManager().identifier(), name()))
|
||||
.stream()
|
||||
.map(str -> Messenger.c("g " + str))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<String> getTranslationArray(String prefix) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
for (int i = 0; Translations.hasTranslation(prefix + i); i++) {
|
||||
ret.add(Translations.tr(prefix + i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> categories() {
|
||||
return categories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> suggestions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SettingsManager settingsManager() {
|
||||
return realSettingsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // T comes from the field
|
||||
public T value() {
|
||||
try {
|
||||
return (T) field.get(null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Can't happen at regular runtime because we'd have thrown it on construction
|
||||
throw new IllegalArgumentException("Couldn't access field for rule: " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeToggledClientSide() {
|
||||
return isClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(CommandSourceStack source, T value) throws InvalidRuleValueException {
|
||||
set(source, value, RuleHelper.toRuleString(value));
|
||||
}
|
||||
|
||||
private static <T> Map.Entry<Class<T>, FromStringConverter<T>> numericalConverter(Class<T> outputClass, Function<String, T> converter) {
|
||||
return Map.entry(outputClass, str -> {
|
||||
try {
|
||||
return converter.apply(str);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidRuleValueException("Invalid number for rule");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TO REMOVE
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#value()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public T get()
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#toRuleString(Object) RuleHelper.convertToRuleString(rule.value())}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getAsString()
|
||||
{
|
||||
return RuleHelper.toRuleString(value());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, converted to a {@link boolean}.
|
||||
* It will only return {@link true} if it's a true {@link boolean} or
|
||||
* a number greater than zero.
|
||||
* @deprecated Use {@link RuleHelper#getBooleanValue(CarpetRule)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean getBoolValue()
|
||||
{
|
||||
return RuleHelper.getBooleanValue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#isInDefaultValue(CarpetRule)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean isDefault()
|
||||
{
|
||||
return RuleHelper.isInDefaultValue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#resetToDefault(CarpetRule, CommandSourceStack)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void resetToDefault(CommandSourceStack source)
|
||||
{
|
||||
RuleHelper.resetToDefault(this, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Forcing {@link Comparable} isn't a thing on {@link CarpetRule}s. Instead, pass a comparator by name to your
|
||||
* sort methods, you can get one by calling {@code Comparator.comparing(CarpetRule::name)}
|
||||
*/
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
public int compareTo(ParsedRule<?> o)
|
||||
{
|
||||
if (!warnedComparable) {
|
||||
warnedComparable = true;
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension is relying on carpet rules to be comparable! This is not true for all carpet rules anymore, \
|
||||
and will crash the game in future versions or if an extension adds non-comparable rules!
|
||||
Fixing it is as simple as passing Comparator.comparing(CarpetRule::name) to the sorting method!""",
|
||||
new Throwable("Location:").fillInStackTrace());
|
||||
}
|
||||
return this.name.compareTo(o.name);
|
||||
}
|
||||
private static boolean warnedComparable = false;
|
||||
|
||||
/**
|
||||
* @return A {@link String} being the translated {@link ParsedRule#name} of this rule,
|
||||
* in Carpet's configured language.
|
||||
* @deprecated Use {@link RuleHelper#translatedName(CarpetRule)} instead
|
||||
*/
|
||||
public String translatedName(){
|
||||
String key = translationKey();
|
||||
return Translations.hasTranslation(key) ? tr(key) + String.format(" (%s)", name): name;
|
||||
@Deprecated(forRemoval = true)
|
||||
public String translatedName() {
|
||||
return RuleHelper.translatedName(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being the translated {@link ParsedRule#description} of this rule,
|
||||
* @return A {@link String} being the translated {@link ParsedRule#description description} of this rule,
|
||||
* in Carpet's configured language.
|
||||
* @deprecated Use {@link RuleHelper#translatedDescription(CarpetRule)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public String translatedDescription()
|
||||
{
|
||||
return tr(String.format("rule.%s.desc", (name)), description);
|
||||
return RuleHelper.translatedDescription(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being the translated {@link ParsedRule#extraInfo} of this
|
||||
* {@link ParsedRule}, in Carpet's configured language.
|
||||
* @return A {@link String} being the translated {@link ParsedRule#extraInfo extraInfo} of this
|
||||
* {@link ParsedRule}, in Carpet's configured language.
|
||||
* @deprecated Use {@link CarpetRule#extraInfo()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public List<String> translatedExtras()
|
||||
{
|
||||
if (!Translations.hasTranslations()) return extraInfo;
|
||||
String keyBase = String.format("rule.%s.extra.", name);
|
||||
List<String> extras = new ArrayList<>();
|
||||
int i = 0;
|
||||
while (Translations.hasTranslation(keyBase+i))
|
||||
{
|
||||
extras.add(Translations.tr(keyBase+i));
|
||||
i++;
|
||||
}
|
||||
return (extras.isEmpty()) ? extraInfo : extras;
|
||||
return extraInfo().stream().map(Component::getString).toList();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,10 @@ import java.lang.annotation.Target;
|
||||
* - String
|
||||
* - a subclass of Enum
|
||||
* The default value of the rule will be the initial value of the field.
|
||||
*
|
||||
* @deprecated Use {@link carpet.api.settings.Rule} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Rule
|
||||
@@ -55,18 +58,18 @@ public @interface Rule
|
||||
/**
|
||||
* If specified, the rule will automatically enable or disable
|
||||
* a builtin Scarpet Rule App with this name.
|
||||
* Consider telling the rule name so users can edit globals
|
||||
* (in case there are relevant globals to edit ofc)
|
||||
*/
|
||||
String appSource() default "";
|
||||
|
||||
/**
|
||||
* The class of the validator checked when the rule is changed.
|
||||
*/
|
||||
Class<? extends Validator>[] validate() default {};
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends carpet.api.settings.Validator>[] validate() default {};
|
||||
|
||||
/**
|
||||
* The class of the condition checked when the rule is parsed.
|
||||
* The class of the condition checked when the rule is parsed, before being added
|
||||
* to the Settings Manager.
|
||||
*/
|
||||
Class<? extends Condition>[] condition() default {};
|
||||
Class<? extends carpet.api.settings.Rule.Condition>[] condition() default {};
|
||||
}
|
||||
@@ -1,22 +1,59 @@
|
||||
package carpet.settings;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link carpet.api.settings.RuleCategory} instead. Should be as simple as changing to that in the imports
|
||||
*
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public class RuleCategory
|
||||
{
|
||||
public static final String BUGFIX = "bugfix";
|
||||
public static final String SURVIVAL = "survival";
|
||||
public static final String CREATIVE = "creative";
|
||||
public static final String EXPERIMENTAL = "experimental";
|
||||
public static final String OPTIMIZATION = "optimization";
|
||||
public static final String FEATURE = "feature";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String TNT = "tnt";
|
||||
public static final String DISPENSER = "dispenser";
|
||||
public static final String SCARPET = "scarpet";
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#BUGFIX} instead
|
||||
*/
|
||||
public static final String BUGFIX = carpet.api.settings.RuleCategory.BUGFIX;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#SURVIVAL} instead
|
||||
*/
|
||||
public static final String SURVIVAL = carpet.api.settings.RuleCategory.SURVIVAL;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#CREATIVE} instead
|
||||
*/
|
||||
public static final String CREATIVE = carpet.api.settings.RuleCategory.CREATIVE;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#EXPERIMENTAL} instead
|
||||
*/
|
||||
public static final String EXPERIMENTAL = carpet.api.settings.RuleCategory.EXPERIMENTAL;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#OPTIMIZATION} instead
|
||||
*/
|
||||
public static final String OPTIMIZATION = carpet.api.settings.RuleCategory.OPTIMIZATION;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#FEATURE} instead
|
||||
*/
|
||||
public static final String FEATURE = carpet.api.settings.RuleCategory.FEATURE;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#COMMAND} instead
|
||||
*/
|
||||
public static final String COMMAND = carpet.api.settings.RuleCategory.COMMAND;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#TNT} instead
|
||||
*/
|
||||
public static final String TNT = carpet.api.settings.RuleCategory.TNT;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#DISPENSER} instead
|
||||
*/
|
||||
public static final String DISPENSER = carpet.api.settings.RuleCategory.DISPENSER;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#SCARPET} instead
|
||||
*/
|
||||
public static final String SCARPET = carpet.api.settings.RuleCategory.SCARPET;
|
||||
/**
|
||||
* Rules with this {@link RuleCategory} will have a client-side
|
||||
* counterpart, so they can be set independently without the server
|
||||
* being Carpet's
|
||||
* @deprecated Use {@link carpet.api.settings.RuleCategory#CLIENT} instead
|
||||
*/
|
||||
public static final String CLIENT = "client";
|
||||
@Deprecated(forRemoval = true)
|
||||
public static final String CLIENT = carpet.api.settings.RuleCategory.CLIENT;
|
||||
|
||||
}
|
||||
@@ -2,84 +2,29 @@ package carpet.settings;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.utils.Translations;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.Sets;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.utils.CommandHelper;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.commands.CommandBuildContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.TickTask;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import org.apache.logging.log4j.util.TriConsumer;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static carpet.utils.Translations.tr;
|
||||
import static carpet.script.CarpetEventServer.Event.CARPET_RULE_CHANGES;
|
||||
import static net.minecraft.commands.SharedSuggestionProvider.suggest;
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
/**
|
||||
* Manages and parses Carpet rules with their own command.
|
||||
* @see #SettingsManager(String, String, String)
|
||||
*
|
||||
* @deprecated Use {@link carpet.api.settings.SettingsManager} instead
|
||||
*/
|
||||
public class SettingsManager
|
||||
@Deprecated(forRemoval = true)
|
||||
public class SettingsManager extends carpet.api.settings.SettingsManager
|
||||
{
|
||||
private Map<String, ParsedRule<?>> rules = new HashMap<>();
|
||||
/**
|
||||
* Whether or not this {@link SettingsManager} is locked. That is specified
|
||||
* by writing "locked" at the beginning of the settings file. May not
|
||||
* correctly react to changes.
|
||||
* @deprecated Use {@link #locked()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) //to private (or protected?)
|
||||
public boolean locked;
|
||||
private final String version;
|
||||
private final String identifier;
|
||||
private final String fancyName;
|
||||
private MinecraftServer server;
|
||||
private List<TriConsumer<CommandSourceStack, ParsedRule<?>, String>> observers = new ArrayList<>();
|
||||
private static List<TriConsumer<CommandSourceStack, ParsedRule<?>, String>> staticObservers = new ArrayList<>();
|
||||
static record ConfigReadResult(Map<String, String> ruleMap, boolean locked) {}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SettingsManager} without a fancy name.
|
||||
@@ -87,101 +32,56 @@ public class SettingsManager
|
||||
*
|
||||
* @param version A {@link String} with the mod's version
|
||||
* @param identifier A {@link String} with the mod's id, will be the command name
|
||||
* @deprecated This type is deprecated, use {@link carpet.api.settings.SettingsManager#SettingsManager(String, String, String)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public SettingsManager(String version, String identifier)
|
||||
{
|
||||
this.version = version;
|
||||
this.identifier = identifier;
|
||||
this.fancyName = identifier;
|
||||
this(version, identifier, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SettingsManager} with a fancy name
|
||||
* @see #SettingsManager(String, String)
|
||||
*
|
||||
* @param version A {@link String} with the mod's version
|
||||
* @param identifier A {@link String} with the mod's id, will be the command name
|
||||
* @param fancyName A {@link String} being the mod's fancy name.
|
||||
*
|
||||
* @deprecated This type is deprecated, use {@link carpet.api.settings.SettingsManager#SettingsManager(String, String, String)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public SettingsManager(String version, String identifier, String fancyName)
|
||||
{
|
||||
this.version = version;
|
||||
this.identifier = identifier;
|
||||
this.fancyName = fancyName;
|
||||
super(version, identifier, fancyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being this {@link SettingsManager}'s
|
||||
* identifier, which is also the command name
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a {@link MinecraftServer} to this {@link SettingsManager}.<br>
|
||||
* This is handled automatically by Carpet
|
||||
* @param server The {@link MinecraftServer} instance to be attached
|
||||
*/
|
||||
public void attachServer(MinecraftServer server)
|
||||
{
|
||||
this.server = server;
|
||||
loadConfigurationFromConf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the {@link MinecraftServer} of this {@link SettingsManager} and
|
||||
* resets its {@link ParsedRule}s to their default values.<br>
|
||||
* This is handled automatically by Carpet
|
||||
*/
|
||||
public void detachServer()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values()) rule.resetToDefault(null);
|
||||
server = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all annotated fields with the {@link Rule} annotation
|
||||
* to this {@link SettingsManager} in order to handle them.
|
||||
* @param settingsClass The class that will be analyzed
|
||||
*/
|
||||
public void parseSettingsClass(Class settingsClass)
|
||||
{
|
||||
rule: for (Field f : settingsClass.getDeclaredFields())
|
||||
{
|
||||
Rule rule = f.getAnnotation(Rule.class);
|
||||
if (rule == null) continue;
|
||||
ParsedRule<?> parsed = new ParsedRule<>(f, rule, this);
|
||||
for (Class<? extends Condition> condition : rule.condition()) {
|
||||
try
|
||||
{
|
||||
Constructor<?> constr = condition.getDeclaredConstructor();
|
||||
constr.setAccessible(true);
|
||||
if (!((Condition) constr.newInstance()).isTrue())
|
||||
continue rule;
|
||||
}
|
||||
catch (ReflectiveOperationException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
rules.put(parsed.name, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom rule observer to changes in rules from
|
||||
* <b>this specific</b> {@link SettingsManager} instance.
|
||||
* @see SettingsManager#addGlobalRuleObserver(TriConsumer)
|
||||
* <p>Adds a custom rule observer to changes in rules from
|
||||
* <b>this specific</b> {@link SettingsManager} instance.</p>
|
||||
*
|
||||
* <p>This <b>outdated</b> method may not be able to listen to all {@link CarpetRule} implementations</p>
|
||||
*
|
||||
* @see SettingsManager#registerGlobalRuleObserver(RuleObserver)
|
||||
*
|
||||
* @param observer A {@link TriConsumer} that will be called with
|
||||
* the used {@link CommandSourceStack}, the changed
|
||||
* {@link ParsedRule} and a {@link String} being the
|
||||
* value that the user typed.
|
||||
* @deprecated Use {@link SettingsManager#registerRuleObserver(RuleObserver)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true) //to remove
|
||||
public void addRuleObserver(TriConsumer<CommandSourceStack, ParsedRule<?>, String> observer)
|
||||
{
|
||||
observers.add(observer);
|
||||
registerRuleObserver((source, rule, stringValue) -> {
|
||||
if (rule instanceof ParsedRule<?> pr)
|
||||
observer.accept(source, pr, stringValue);
|
||||
else
|
||||
CarpetSettings.LOG.warn("Failed to notify observer '" + observer.getClass().getName() + "' about rule change");
|
||||
});
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension added outdated rule observer, this is deprecated and will crash in later carpet versions \
|
||||
(way before the rest of the old settings api because of relying on log4j)!
|
||||
The observer class name is '%s'""".formatted(observer.getClass().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,187 +93,87 @@ public class SettingsManager
|
||||
* the used {@link CommandSourceStack}, the changed
|
||||
* {@link ParsedRule} and a {@link String} being the
|
||||
* value that the user typed.
|
||||
* @deprecated Use {@link SettingsManager#registerRuleObserver(RuleObserver)} instead, given this one can't deal with {@link CarpetRule}
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove. This isn't really used anywhere
|
||||
public static void addGlobalRuleObserver(TriConsumer<CommandSourceStack, ParsedRule<?>, String> observer)
|
||||
{
|
||||
staticObservers.add(observer);
|
||||
registerGlobalRuleObserver((source, rule, stringValue) -> {
|
||||
if (rule instanceof ParsedRule<?> pr)
|
||||
observer.accept(source, pr, stringValue);
|
||||
else
|
||||
CarpetSettings.LOG.warn("Failed to notify observer '" + observer.getClass().getName() + "' about rule change");
|
||||
});
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension added outdated rule observer, this is deprecated and will crash in later carpet versions \
|
||||
(way before the rest of the old settings api because of relying on log4j)!
|
||||
The observer class name is '%s'""".formatted(observer.getClass().getName()));
|
||||
}
|
||||
|
||||
|
||||
void notifyRuleChanged(CommandSourceStack source, ParsedRule<?> rule, String userTypedValue)
|
||||
{
|
||||
observers.forEach(observer -> observer.accept(source, rule, userTypedValue));
|
||||
staticObservers.forEach(observer -> observer.accept(source, rule, userTypedValue));
|
||||
ServerNetworkHandler.updateRuleWithConnectedClients(rule);
|
||||
switchScarpetRuleIfNeeded(source, rule);
|
||||
if (CARPET_RULE_CHANGES.isNeeded()) CARPET_RULE_CHANGES.onCarpetRuleChanges(rule, source);
|
||||
}
|
||||
|
||||
private void switchScarpetRuleIfNeeded(CommandSourceStack source, ParsedRule<?> rule)
|
||||
{
|
||||
if (!rule.scarpetApp.isEmpty())
|
||||
{
|
||||
if (rule.getBoolValue() || (rule.type == String.class && !rule.get().equals("false")))
|
||||
{
|
||||
CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, s -> canUseCommand(s, rule.get()), false, false, true, null);
|
||||
} else {
|
||||
CarpetServer.scriptServer.removeScriptHost(source, rule.scarpetApp, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Scarpet rules in this {@link SettingsManager}, if any.<br>
|
||||
* This is handled automatically by Carpet.
|
||||
* @deprecated Use {@link #identifier()} instead
|
||||
*/
|
||||
public void initializeScarpetRules() {
|
||||
for (ParsedRule<?> rule : rules.values())
|
||||
{
|
||||
if (!rule.scarpetApp.isEmpty()) {
|
||||
switchScarpetRuleIfNeeded(server.createCommandSourceStack(), rule);
|
||||
}
|
||||
}
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getIdentifier() {
|
||||
return identifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An {@link Iterable} list of all categories
|
||||
* from rules in this {@link SettingsManager}
|
||||
*/
|
||||
public Iterable<String> getCategories()
|
||||
{
|
||||
Set<String> categories = new HashSet<>();
|
||||
getRules().stream().map(r -> r.categories).forEach(categories::addAll);
|
||||
return categories;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a registered rule in this {@link SettingsManager} by its name.
|
||||
* <p>Gets a registered {@link ParsedRule} in this {@link SettingsManager} by its name.</p>
|
||||
* @param name The rule name
|
||||
* @return The {@link ParsedRule} with that name
|
||||
* @deprecated Use {@link #getCarpetRule(String)} instead. This method is not able to return rules not implemented by {@link ParsedRule}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public ParsedRule<?> getRule(String name)
|
||||
{
|
||||
return rules.get(name);
|
||||
return getCarpetRule(name) instanceof ParsedRule<?> pr ? pr : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Collection} of the registered rules in this
|
||||
* {@link SettingsManager}.
|
||||
* @return A {@link Collection} of the registered {@link ParsedRule}s in this {@link SettingsManager}.
|
||||
* @deprecated Use {@link #getCarpetRules()} instead. This method won't be able to return rules not implemented by {@link ParsedRule}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public Collection<ParsedRule<?>> getRules()
|
||||
{
|
||||
return rules.values().stream().sorted().collect(Collectors.toList());
|
||||
return List.of(getCarpetRules().stream().filter(ParsedRule.class::isInstance).map(ParsedRule.class::cast).toArray(ParsedRule[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Collection} of {@link ParsedRule}s that have defaults
|
||||
* for this world different than the rule's default value.
|
||||
* @deprecated Use {@link #dumpAllRulesToStream(java.io.PrintStream, String)} instead
|
||||
*/
|
||||
public Collection<ParsedRule<?>> findStartupOverrides()
|
||||
{
|
||||
Set<String> defaults = readSettingsFromConf(getFile()).ruleMap().keySet();
|
||||
return rules.values().stream().filter(r -> defaults.contains(r.name)).
|
||||
sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A collection of {@link ParsedRule} that are not in
|
||||
* their default value
|
||||
*/
|
||||
public Collection<ParsedRule<?>> getNonDefault()
|
||||
{
|
||||
return rules.values().stream().filter(r -> !r.isDefault()).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Path getFile()
|
||||
{
|
||||
return server.getWorldPath(LevelResource.ROOT).resolve(identifier+".conf");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables all {@link ParsedRule} with the {@link RuleCategory#COMMAND} category,
|
||||
* called when the {@link SettingsManager} is {@link #locked}.
|
||||
*/
|
||||
public void disableBooleanCommands()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values())
|
||||
{
|
||||
if (!rule.categories.contains(RuleCategory.COMMAND))
|
||||
continue;
|
||||
if (rule.type == boolean.class)
|
||||
((ParsedRule<Boolean>) rule).set(server.createCommandSourceStack(), false, "false");
|
||||
if (rule.type == String.class && rule.options.contains("false"))
|
||||
((ParsedRule<String>) rule).set(server.createCommandSourceStack(), "false", "false");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void writeSettingsToConf(Map<String, String> values)
|
||||
{
|
||||
if (locked)
|
||||
return;
|
||||
try (BufferedWriter fw = Files.newBufferedWriter(getFile()))
|
||||
{
|
||||
for (String key: values.keySet())
|
||||
{
|
||||
fw.write(key+" "+values.get(key));
|
||||
fw.newLine();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: failed write "+identifier+".conf config file", e);
|
||||
}
|
||||
///todo is it really needed? resendCommandTree();
|
||||
@Deprecated(forRemoval = true)
|
||||
public int printAllRulesToLog(String category) {
|
||||
return dumpAllRulesToStream(System.out, category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all players that the commands changed by resending the command tree.
|
||||
* @deprecated While there's not an API replacement for this (at least yet),
|
||||
* you can use {@link CommandHelper#notifyPlayersCommandsChanged(MinecraftServer)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void notifyPlayersCommandsChanged()
|
||||
{
|
||||
if (server == null || server.getPlayerList() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server.tell(new TickTask(this.server.getTickCount(), () ->
|
||||
{
|
||||
try {
|
||||
for (ServerPlayer entityplayermp : server.getPlayerList().getPlayers()) {
|
||||
server.getCommands().sendCommands(entityplayermp);
|
||||
}
|
||||
}
|
||||
catch (NullPointerException ignored) {}
|
||||
}));
|
||||
CommandHelper.notifyPlayersCommandsChanged(CarpetServer.minecraft_server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the the {@link CommandSourceStack} can execute
|
||||
* Returns whether the {@link CommandSourceStack} can execute
|
||||
* a command given the required permission level, according to
|
||||
* Carpet's standard for permissions.
|
||||
* @param source The origin {@link CommandSourceStack}
|
||||
* @param commandLevel A {@link String} being the permission level (either 0-4, a
|
||||
* {@link boolean} value or "ops".
|
||||
* @param commandLevel The permission level
|
||||
* @return Whether or not the {@link CommandSourceStack} meets the required level
|
||||
*
|
||||
* @deprecated While there's not an API replacement for this (at least yet),
|
||||
* you can use {@link CommandHelper#canUseCommand(CommandSourceStack, Object)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static boolean canUseCommand(CommandSourceStack source, Object commandLevel)
|
||||
{
|
||||
if (commandLevel instanceof Boolean) return (Boolean) commandLevel;
|
||||
String commandLevelString = commandLevel.toString();
|
||||
switch (commandLevelString)
|
||||
{
|
||||
case "true": return true;
|
||||
case "false": return false;
|
||||
case "ops": return source.hasPermission(2); // typical for other cheaty commands
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
return source.hasPermission(Integer.parseInt(commandLevelString));
|
||||
}
|
||||
return false;
|
||||
return CommandHelper.canUseCommand(source, commandLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,7 +181,13 @@ public class SettingsManager
|
||||
* Carpet's standard for permissions (either 0-4, a {@link boolean},
|
||||
* or "ops".
|
||||
* @return An {@link int} with the translated Vanilla permission level
|
||||
*
|
||||
* @deprecated While there's not an API replacement for this (at least yet),
|
||||
* you can use the similar {@link CommandHelper#canUseCommand(CommandSourceStack, Object)} instead
|
||||
* @apiNote Note that this method returns {@code 2} for {@code false} and {@code 0} for {@code true}.
|
||||
* You probably want to use {@link #canUseCommand(CommandSourceStack, Object)}e
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static int getCommandLevel(String commandLevel)
|
||||
{
|
||||
switch (commandLevel)
|
||||
@@ -398,183 +204,6 @@ public class SettingsManager
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private void loadConfigurationFromConf()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values()) rule.resetToDefault(server.createCommandSourceStack());
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
locked = false;
|
||||
if (conf.locked())
|
||||
{
|
||||
CarpetSettings.LOG.info("[CM]: "+fancyName+" features are locked by the administrator");
|
||||
disableBooleanCommands();
|
||||
}
|
||||
for (String key: conf.ruleMap().keySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (rules.get(key).set(server.createCommandSourceStack(), conf.ruleMap().get(key)) != null)
|
||||
CarpetSettings.LOG.info("[CM]: loaded setting " + key + " as " + conf.ruleMap().get(key) + " from " + identifier + ".conf");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM Error]: Failed to load setting: "+key+", "+exc);
|
||||
}
|
||||
}
|
||||
locked = conf.locked();
|
||||
}
|
||||
|
||||
|
||||
private ConfigReadResult readSettingsFromConf(Path path)
|
||||
{
|
||||
try (BufferedReader reader = Files.newBufferedReader(path))
|
||||
{
|
||||
String line = "";
|
||||
boolean confLocked = false;
|
||||
Map<String,String> result = new HashMap<String, String>();
|
||||
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]))
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: "+fancyName+" Setting " + fields[0] + " is not a valid - ignoring...");
|
||||
continue;
|
||||
}
|
||||
ParsedRule rule = rules.get(fields[0]);
|
||||
|
||||
if (!(rule.options.contains(fields[1])) && rule.isStrict)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: The value of " + fields[1] + " for " + fields[0] + "("+fancyName+") is not valid - ignoring...");
|
||||
continue;
|
||||
}
|
||||
result.put(fields[0],fields[1]);
|
||||
|
||||
}
|
||||
}
|
||||
return new ConfigReadResult(result, confLocked);
|
||||
}
|
||||
catch (NoSuchFileException e)
|
||||
{
|
||||
if (path.equals(getFile()) && FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
|
||||
{
|
||||
Path defaultsPath = FabricLoader.getInstance().getConfigDir().resolve("carpet/default_"+identifier+".conf");
|
||||
try {
|
||||
if (Files.notExists(defaultsPath))
|
||||
{
|
||||
Files.createDirectories(defaultsPath.getParent());
|
||||
Files.createFile(defaultsPath);
|
||||
}
|
||||
return readSettingsFromConf(defaultsPath);
|
||||
} catch (IOException e2) {
|
||||
CarpetSettings.LOG.error("Exception when loading fallback default config: ", e2);
|
||||
}
|
||||
}
|
||||
return new ConfigReadResult(new HashMap<>(), false);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
CarpetSettings.LOG.error("Exception while loading Carpet rules from config", e);
|
||||
return new ConfigReadResult(new HashMap<>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ParsedRule<?>> getRulesMatching(String search) {
|
||||
String lcSearch = search.toLowerCase(Locale.ROOT);
|
||||
return rules.values().stream().filter(rule ->
|
||||
{
|
||||
if (rule.name.toLowerCase(Locale.ROOT).contains(lcSearch)) return true; // substring match, case insensitive
|
||||
for (String c : rule.categories) if (c.equals(search)) return true; // category exactly, case sensitive
|
||||
return Sets.newHashSet(rule.description.toLowerCase(Locale.ROOT).split("\\W+")).contains(lcSearch); // contains full term in description, but case insensitive
|
||||
}).sorted().collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to pretty print in markdown (useful for Github wiki/readme) the current
|
||||
* registered rules for a category to the log. Contains the name, description,
|
||||
* categories, type, defaults, wether or not they are strict, their suggested
|
||||
* values, and the descriptions of their validators.
|
||||
* @param category A {@link String} being the category to print, {@link null} to print
|
||||
* all registered rules.
|
||||
* @return {@link int} 1 if things didn't fail, and all the info to System.out
|
||||
*/
|
||||
public int printAllRulesToLog(String category)
|
||||
{
|
||||
PrintStream ps = System.out;
|
||||
ps.println("# "+fancyName+" Settings");
|
||||
for (Map.Entry<String, ParsedRule<?>> e : new TreeMap<>(rules).entrySet())
|
||||
{
|
||||
ParsedRule<?> rule = e.getValue();
|
||||
if (category != null && !rule.categories.contains(category))
|
||||
continue;
|
||||
ps.println("## " + rule.name);
|
||||
ps.println(rule.description+" ");
|
||||
for (String extra : rule.extraInfo)
|
||||
ps.println(extra + " ");
|
||||
ps.println("* Type: `" + rule.type.getSimpleName() + "` ");
|
||||
ps.println("* Default value: `" + rule.defaultAsString + "` ");
|
||||
String optionString = rule.options.stream().map(s -> "`" + s + "`").collect(Collectors.joining(", "));
|
||||
if (!optionString.isEmpty()) ps.println((rule.isStrict?"* Required":"* Suggested")+" options: " + optionString + " ");
|
||||
ps.println("* Categories: " + rule.categories.stream().map(s -> "`" + s.toUpperCase(Locale.ROOT) + "`").collect(Collectors.joining(", ")) + " ");
|
||||
boolean preamble = false;
|
||||
for (Validator<?> validator : rule.validators)
|
||||
{
|
||||
if(validator.description() != null)
|
||||
{
|
||||
if (!preamble)
|
||||
{
|
||||
ps.println("* Additional notes: ");
|
||||
preamble = true;
|
||||
}
|
||||
ps.println(" * "+validator.description()+" ");
|
||||
}
|
||||
}
|
||||
ps.println(" ");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
private ParsedRule<?> contextRule(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException
|
||||
{
|
||||
String ruleName = StringArgumentType.getString(ctx, "rule");
|
||||
ParsedRule<?> rule = getRule(ruleName);
|
||||
if (rule == null)
|
||||
throw new SimpleCommandExceptionType(Messenger.c("rb "+ tr("ui.unknown_rule","Unknown rule")+": "+ruleName)).create();
|
||||
return rule;
|
||||
}
|
||||
|
||||
static CompletableFuture<Suggestions> suggestMatchingContains(Stream<String> stream, SuggestionsBuilder suggestionsBuilder) {
|
||||
List<String> regularSuggestionList = new ArrayList<>();
|
||||
List<String> smartSuggestionList = new ArrayList<>();
|
||||
String query = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
|
||||
stream.forEach((listItem) -> {
|
||||
// Regex camelCase Search
|
||||
var words = Arrays.stream(listItem.split("(?<!^)(?=[A-Z])")).map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList());
|
||||
var prefixes = new ArrayList<String>(words.size());
|
||||
for (int i = 0; i < words.size(); i++)
|
||||
prefixes.add(String.join("",words.subList(i, words.size())));
|
||||
if (prefixes.stream().anyMatch(s -> s.startsWith(query))) {
|
||||
smartSuggestionList.add(listItem);
|
||||
}
|
||||
// Regular prefix matching, reference: CommandSource.suggestMatching
|
||||
if (SharedSuggestionProvider.matchesSubStr(query, listItem.toLowerCase(Locale.ROOT))) {
|
||||
regularSuggestionList.add(listItem);
|
||||
}
|
||||
});
|
||||
var filteredSuggestionList = regularSuggestionList.isEmpty() ? smartSuggestionList : regularSuggestionList;
|
||||
Objects.requireNonNull(suggestionsBuilder);
|
||||
filteredSuggestionList.forEach(suggestionsBuilder::suggest);
|
||||
return suggestionsBuilder.buildFuture();
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public void registerCommand(CommandDispatcher<CommandSourceStack> dispatcher)
|
||||
{
|
||||
@@ -582,216 +211,4 @@ public class SettingsManager
|
||||
context.missingTagAccessPolicy(CommandBuildContext.MissingTagAccessPolicy.RETURN_EMPTY);
|
||||
registerCommand(dispatcher, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the settings command for this {@link SettingsManager}.<br>
|
||||
* It is handled automatically by Carpet.
|
||||
* @param dispatcher The current {@link CommandDispatcher}
|
||||
* @param commandBuildContext The current {@link CommandBuildContext}
|
||||
*/
|
||||
public void registerCommand(final CommandDispatcher<CommandSourceStack> dispatcher, final CommandBuildContext commandBuildContext)
|
||||
{
|
||||
if (dispatcher.getRoot().getChildren().stream().anyMatch(node -> node.getName().equalsIgnoreCase(identifier)))
|
||||
{
|
||||
CarpetSettings.LOG.error("Failed to add settings command for " + identifier + ". It is masking previous command.");
|
||||
return;
|
||||
}
|
||||
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal(identifier).requires((player) ->
|
||||
canUseCommand(player, CarpetSettings.carpetCommandPermissionLevel) && !locked);
|
||||
|
||||
literalargumentbuilder.executes((context)-> listAllSettings(context.getSource())).
|
||||
then(literal("list").
|
||||
executes( (c) -> listSettings(c.getSource(), String.format(tr("ui.all_%(mod)s_settings","All %s Settings"), fancyName),
|
||||
getRules())).
|
||||
then(literal("defaults").
|
||||
executes( (c)-> listSettings(c.getSource(),
|
||||
String.format(tr("ui.current_%(mod)s_startup_settings_from_%(conf)s","Current %s Startup Settings from %s"), fancyName, (identifier+".conf")),
|
||||
findStartupOverrides()))).
|
||||
then(argument("tag",StringArgumentType.word()).
|
||||
suggests( (c, b)->suggest(getCategories(), b)).
|
||||
executes( (c) -> listSettings(c.getSource(),
|
||||
String.format(tr("ui.%(mod)s_settings_matching_'%(query)s'","%s Settings matching \"%s\""), fancyName, tr("category." + StringArgumentType.getString(c, "tag"),StringArgumentType.getString(c, "tag"))),
|
||||
getRulesMatching(StringArgumentType.getString(c, "tag")))))).
|
||||
then(literal("removeDefault").
|
||||
requires(s -> !locked).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRules().stream().map(r -> r.name), b)).
|
||||
executes((c) -> removeDefault(c.getSource(), contextRule(c))))).
|
||||
then(literal("setDefault").
|
||||
requires(s -> !locked).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRules().stream().map(r -> r.name), b)).
|
||||
then(argument("value", StringArgumentType.greedyString()).
|
||||
suggests((c, b)-> suggest(contextRule(c).options, b)).
|
||||
executes((c) -> setDefault(c.getSource(), contextRule(c), StringArgumentType.getString(c, "value")))))).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRules().stream().map(r -> r.name), b)).
|
||||
requires(s -> !locked ).
|
||||
executes( (c) -> displayRuleMenu(c.getSource(), contextRule(c))).
|
||||
then(argument("value", StringArgumentType.greedyString()).
|
||||
suggests((c, b)-> suggest(contextRule(c).options,b)).
|
||||
executes((c) -> setRule(c.getSource(), contextRule(c), StringArgumentType.getString(c, "value")))));
|
||||
|
||||
dispatcher.register(literalargumentbuilder);
|
||||
}
|
||||
|
||||
private int displayRuleMenu(CommandSourceStack source, ParsedRule<?> rule)
|
||||
{
|
||||
Player player;
|
||||
String displayName = rule.translatedName();
|
||||
|
||||
Messenger.m(source, "");
|
||||
Messenger.m(source, "wb "+ displayName ,"!/"+identifier+" "+rule.name,"^g refresh");
|
||||
Messenger.m(source, "w "+ rule.translatedDescription());
|
||||
|
||||
rule.translatedExtras().forEach(s -> Messenger.m(source, "g "+s));
|
||||
|
||||
List<Component> tags = new ArrayList<>();
|
||||
tags.add(Messenger.c("w "+ tr("ui.tags", "Tags")+": "));
|
||||
for (String t: rule.categories)
|
||||
{
|
||||
String translated = tr("category." + t, t);
|
||||
tags.add(Messenger.c("c ["+ translated +"]", "^g "+ String.format(tr("list_all_%s_settings","list all %s settings"), translated),"!/"+identifier+" list "+t));
|
||||
tags.add(Messenger.c("w , "));
|
||||
}
|
||||
tags.remove(tags.size()-1);
|
||||
Messenger.m(source, tags.toArray(new Object[0]));
|
||||
|
||||
Messenger.m(source, "w "+ tr("ui.current_value", "Current value")+": ",String.format("%s %s (%s value)",rule.getBoolValue()?"lb":"nb", rule.getAsString(),rule.isDefault()?"default":"modified"));
|
||||
List<Component> 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(source, options.toArray(new Object[0]));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int setRule(CommandSourceStack source, ParsedRule<?> rule, String newValue)
|
||||
{
|
||||
if (rule.set(source, newValue) != null)
|
||||
Messenger.m(source, "w "+rule.toString()+", ", "c ["+ tr("ui.change_permanently","change permanently")+"?]",
|
||||
"^w "+String.format(tr("ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts","Click to keep the settings in %s to save across restarts"), identifier+".conf"),
|
||||
"?/"+identifier+" setDefault "+rule.name+" "+rule.getAsString());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// stores different defaults in the file
|
||||
private int setDefault(CommandSourceStack source, ParsedRule<?> rule, String stringValue)
|
||||
{
|
||||
if (locked) return 0;
|
||||
if (!rules.containsKey(rule.name)) return 0;
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
conf.ruleMap().put(rule.name, stringValue);
|
||||
writeSettingsToConf(conf.ruleMap()); // this may feels weird, but if conf
|
||||
// is locked, it will never reach this point.
|
||||
rule.set(source,stringValue);
|
||||
Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_will_now_default_to_%(value)s","Rule %s will now default to %s"), rule.translatedName(), stringValue));
|
||||
return 1;
|
||||
}
|
||||
// removes overrides of the default values in the file
|
||||
private int removeDefault(CommandSourceStack source, ParsedRule<?> rule)
|
||||
{
|
||||
if (locked) return 0;
|
||||
if (!rules.containsKey(rule.name)) return 0;
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
conf.ruleMap().remove(rule.name);
|
||||
writeSettingsToConf(conf.ruleMap());
|
||||
rules.get(rule.name).resetToDefault(source);
|
||||
Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_not_set_restart","Rule %s will now not be set on restart"), rule.translatedName()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private Component displayInteractiveSetting(ParsedRule<?> rule)
|
||||
{
|
||||
String displayName = rule.translatedName();
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add("w - "+ displayName +" ");
|
||||
args.add("!/"+identifier+" "+rule.name);
|
||||
args.add("^y "+rule.translatedDescription());
|
||||
for (String option: rule.options)
|
||||
{
|
||||
args.add(makeSetRuleButton(rule, option, true));
|
||||
args.add("w ");
|
||||
}
|
||||
if (!rule.options.contains(rule.getAsString()))
|
||||
{
|
||||
args.add(makeSetRuleButton(rule, rule.getAsString(), true));
|
||||
args.add("w ");
|
||||
}
|
||||
args.remove(args.size()-1);
|
||||
return Messenger.c(args.toArray(new Object[0]));
|
||||
}
|
||||
|
||||
private Component makeSetRuleButton(ParsedRule<?> rule, String option, boolean brackets)
|
||||
{
|
||||
String style = rule.isDefault()?"g":(option.equalsIgnoreCase(rule.defaultAsString)?"e":"y");
|
||||
if (option.equalsIgnoreCase(rule.getAsString()))
|
||||
{
|
||||
style = style + "u";
|
||||
if (option.equalsIgnoreCase(rule.defaultAsString))
|
||||
style = style + "b";
|
||||
}
|
||||
String baseText = style + (brackets ? " [" : " ") + option + (brackets ? "]" : "");
|
||||
if (locked)
|
||||
return Messenger.c(baseText, "^g "+fancyName+" " + tr("ui.settings_are_locked","settings are locked"));
|
||||
if (option.equalsIgnoreCase(rule.getAsString()))
|
||||
return Messenger.c(baseText);
|
||||
return Messenger.c(baseText, "^g "+ tr("ui.switch_to","Switch to") +" " + option+(option.equalsIgnoreCase(rule.defaultAsString)?" (default)":""), "?/"+identifier+" " + rule.name + " " + option);
|
||||
}
|
||||
|
||||
private int listSettings(CommandSourceStack source, String title, Collection<ParsedRule<?>> settings_list)
|
||||
{
|
||||
|
||||
Messenger.m(source,String.format("wb %s:",title));
|
||||
settings_list.forEach(e -> Messenger.m(source,displayInteractiveSetting(e)));
|
||||
return settings_list.size();
|
||||
}
|
||||
private int listAllSettings(CommandSourceStack source)
|
||||
{
|
||||
int count = listSettings(source, String.format(tr("ui.current_%(mod)s_settings","Current %s Settings"), fancyName), getNonDefault());
|
||||
|
||||
if (version != null)
|
||||
Messenger.m(source, "g "+fancyName+" "+ tr("ui.version", "version") + ": "+ version);
|
||||
|
||||
List<Object> tags = new ArrayList<>();
|
||||
tags.add("w " + tr("ui.browse_categories", "Browse Categories") + ":\n");
|
||||
for (String t : getCategories())
|
||||
{
|
||||
String catKey = "category." + t;
|
||||
String translated = tr(catKey, t);
|
||||
String translatedPlus = Translations.hasTranslation(catKey) ? String.format("%s (%s)",tr(catKey, t), t) : t;
|
||||
tags.add("c [" + translated +"]");
|
||||
tags.add("^g " + String.format(tr("ui.list_all_%(cat)s_settings","list all %s settings"), translatedPlus));
|
||||
tags.add("!/"+identifier+" list " + t);
|
||||
tags.add("w ");
|
||||
}
|
||||
tags.remove(tags.size() - 1);
|
||||
Messenger.m(source, tags.toArray(new Object[0]));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void inspectClientsideCommand(CommandSourceStack source, String string)
|
||||
{
|
||||
if (string.startsWith("/"+identifier+" "))
|
||||
{
|
||||
String[] res = string.split("\\s+", 3);
|
||||
if (res.length == 3)
|
||||
{
|
||||
String setting = res[1];
|
||||
String strOption = res[2];
|
||||
if (rules.containsKey(setting) && rules.get(setting).isClient)
|
||||
{
|
||||
rules.get(setting).set(source, strOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,82 @@
|
||||
package carpet.settings;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.Validators;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
public abstract class Validator<T>
|
||||
/**
|
||||
* @deprecated Use {@link carpet.api.settings.Validator} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public abstract class Validator<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
{
|
||||
// Print deprecation warning once while instantiating the class
|
||||
CarpetSettings.LOG.warn("""
|
||||
Validator '%s' is implementing the old Validator class! This class is deprecated and will be removed \
|
||||
and crash in later Carpet versions!""".formatted(getClass().getName()));
|
||||
}
|
||||
/**
|
||||
* Validate the new value of a rule
|
||||
* @return true if valid, false if new rule invalid.
|
||||
* @return a value if the given one was valid or could be cleanly adapted, null if new value is invalid.
|
||||
*/
|
||||
public abstract T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string);
|
||||
public String description() {return null;}
|
||||
public void notifyFailure(CommandSourceStack source, ParsedRule<T> currentRule, String providedValue)
|
||||
{
|
||||
Messenger.m(source, "r Wrong value for " + currentRule.name + ": " + providedValue);
|
||||
@Override
|
||||
public final T validate(CommandSourceStack source, CarpetRule<T> changingRule, T newValue, String stringInput) {
|
||||
// Compatibility code
|
||||
if (!(changingRule instanceof ParsedRule<T> parsedRule))
|
||||
// Throwing here is not an issue because Carpet's current implementation only calls validators with ParsedRule.
|
||||
// This would be thrown if a different implementation tries to use it, and then it's their issue in multiple ways
|
||||
throw new IllegalArgumentException("Passed a non-ParsedRule to a validator using the outdated method!");
|
||||
return validate(source, parsedRule, newValue, stringInput);
|
||||
}
|
||||
/**
|
||||
* @deprecated Implement {@link #validate(CommandSourceStack, CarpetRule, Object, String)} instead! It will get abstract soon!
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public abstract T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string);
|
||||
|
||||
public static class _COMMAND<T> extends Validator<T>
|
||||
/**
|
||||
* @deprecated Use {@link Validators.CommandLevel} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public static class _COMMAND_LEVEL_VALIDATOR extends Validators.CommandLevel {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Validators.NonNegativeNumber} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public static class NONNEGATIVE_NUMBER<T extends Number> extends Validators.NonNegativeNumber<T> {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Validators.Probablity} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public static class PROBABILITY <T extends Number> extends Validators.Probablity<T> {}
|
||||
|
||||
// The ones below are part of the implementation of ParsedRule or printRulesToLog, so they need to be close to it to stay hidden
|
||||
// They will need to be moved when moving ParsedRule
|
||||
|
||||
static class _COMMAND<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (CarpetServer.settingsManager != null && source != null)
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
if (source != null)
|
||||
CommandHelper.notifyPlayersCommandsChanged(source.getServer());
|
||||
return newValue;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "It has an accompanying command";}
|
||||
}
|
||||
|
||||
public static class _CLIENT<T> extends Validator<T>
|
||||
// maybe remove this one and make printRulesToLog check for canBeToggledClientSide instead
|
||||
static class _CLIENT<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -45,25 +85,10 @@ public abstract class Validator<T>
|
||||
"In these situations (on vanilla servers) it will only affect the executing player, so each player needs to type it" +
|
||||
" separately for the desired effect";}
|
||||
}
|
||||
|
||||
public static class _COMMAND_LEVEL_VALIDATOR extends Validator<String> {
|
||||
private static List<String> OPTIONS = List.of("true", "false", "ops", "0", "1", "2", "3", "4");
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String userString) {
|
||||
if (!OPTIONS.contains(userString.toLowerCase(Locale.ROOT)))
|
||||
{
|
||||
Messenger.m(source, "r Valid options for command type rules is 'true' or 'false'");
|
||||
Messenger.m(source, "r Optionally you can choose 'ops' to only allow operators");
|
||||
Messenger.m(source, "r or provide a custom required permission level");
|
||||
return null;
|
||||
}
|
||||
return userString.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
@Override public String description() { return "Can be limited to 'ops' only, or a custom permission level";}
|
||||
}
|
||||
|
||||
public static class _SCARPET<T> extends Validator<T> {
|
||||
static class ScarpetValidator<T> extends carpet.api.settings.Validator<T> { //TODO remove? The additional info isn't that useful tbh
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -72,65 +97,17 @@ public abstract class Validator<T>
|
||||
}
|
||||
}
|
||||
|
||||
public static class WIP<T> extends Validator<T>
|
||||
static class StrictValidator<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
Messenger.m(source, "r "+currentRule.name+" is missing a few bits - we are still working on it.");
|
||||
return newValue;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "A few bits still needs implementing - we are working on it";}
|
||||
}
|
||||
public static class _STRICT<T> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (!currentRule.options.contains(string))
|
||||
if (!currentRule.suggestions().contains(string))
|
||||
{
|
||||
Messenger.m(source, "r Valid options: " + currentRule.options.toString());
|
||||
Messenger.m(source, "r Valid options: " + currentRule.suggestions().toString());
|
||||
return null;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class _STRICT_IGNORECASE<T> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (!currentRule.options.stream().map(s->s.toLowerCase(Locale.ROOT)).collect(Collectors.toSet())
|
||||
.contains(string.toLowerCase(Locale.ROOT)))
|
||||
{
|
||||
Messenger.m(source, "r Valid options (case insensitive): " + currentRule.options.toString());
|
||||
return null;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NONNEGATIVE_NUMBER <T extends Number> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue.doubleValue() >= 0 ? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "Must be a positive number";}
|
||||
}
|
||||
|
||||
public static class PROBABILITY <T extends Number> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return (newValue.doubleValue() >= 0 && newValue.doubleValue() <= 1 )? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "Must be between 0 and 1";}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user