package net.mcft.copy.backpacks.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import net.minecraft.client.resources.I18n;
import net.minecraft.nbt.NBTBase;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.mcft.copy.backpacks.WearableBackpacks;
import net.mcft.copy.backpacks.client.config.EntrySetting;
/** Represents a single configuration setting. */
public abstract class Setting<T> {
/** Controls whether get() returns getEntryValue() or the setting's value. */
private static boolean _checkEntryValue = false;
/** Default value, used when no config file is present. */
private final T _defaultValue;
/** Loaded "own" value, directly from the config file (or using the default). */
private T _value;
/** Holds the setting's Forge Configuration. */
private Configuration _config;
/** Holds the setting's Forge Configuration Property. */
private Property _property = null;
/** Holds the setting's current entry instance in the config GUI (if open). */
@SideOnly(Side.CLIENT)
private EntrySetting<T> _entry;
/** The setting category, for example "general". */
private String _category;
/** The setting name, for example "equipAsChestArmor". */
private String _name;
/** Stores a function which is called to determine if requirements are met. */
private BooleanSupplier _requireFunc = () -> true;
/** Whether changing the setting requires rejoining the current world. */
private boolean _requiresWorldRejoin = false;
/** Whether changing the setting requires restarting the Minecraft instance. */
private boolean _requiresMinecraftRestart = false;
/** Stores a function which is called to determine if recommendations are met (returns hint). */
private Supplier<List<String>> _recommendFunc = () -> null;
/** The setting's valid values as a string array, or null if none. */
private T[] _validValues = null;
/** Stores whether the setting will be synced to players joining a world. */
private boolean _doesSync = false;
/** Stores whether the setting is currently synced (_syncedValue stores current value). */
private boolean _isSynced = false;
/** Current synced value, returned from get() if _isSynced is true. */
private T _syncedValue = null;
/** Action fired when setting is updated (synced or changed ingame), null if none. */
private Consumer<T> _updateAction = null;
/** Holds the setting's custom config entry class to use in place of the default, if any. */
private String _entryClass = null;
/** The setting's comment used in the config file, if any. */
private String _comment = null;
public Setting(T defaultValue) {
_defaultValue = defaultValue;
}
/** Set the setting's configuration, category and name.
* This is called automatically. Category and name are taken from
* reflected field names. Keeps the constructor short and simple. */
protected void init(Configuration config, String category, String name) {
_config = config;
_category = category;
_name = name;
}
/** Sets a function that determines if the setting's requirements are met. */
public final Setting<T> setRequirement(BooleanSupplier requireFunc)
{ _requireFunc = requireFunc; return this; }
/** Sets the specified settings to be required for this setting. */
@SafeVarargs
public final Setting<T> setRequired(Setting<Boolean>... settings)
{ return setRequirement(() -> Arrays.asList(settings).stream().allMatch(setting -> setting.get())); }
/** Sets the setting to require rejoining the world after being changed. */
public Setting<T> setRequiresWorldRejoin() { _requiresWorldRejoin = true; return this; }
/** Sets the setting to require restarting the game after being changed. */
public Setting<T> setRequiresMinecraftRestart() { _requiresMinecraftRestart = true; return this; }
/** Sets a function that determines if the setting's recommendations are met. */
public final Setting<T> setRecommendation(Supplier<List<String>> recommendFunc)
{ _recommendFunc = recommendFunc; return this; }
/** Sets the specified setting to be recommended for this setting. */
@SafeVarargs
public final Setting<T> setRecommended(Setting<Boolean>... settings) {
// TODO: This should probably be moved somewhere else.
return setRecommendation(() -> {
if (Arrays.asList(settings).stream().allMatch(setting -> setting.get())) return null;
String key = "config." + WearableBackpacks.MOD_ID + "." + getFullName() + ".hint";
List<String> tooltip = new ArrayList<String>(Arrays.asList(
(TextFormatting.YELLOW + I18n.format(key)).split("\\\\n")));
for (Setting<Boolean> setting : settings) if (!setting.get())
tooltip.add(TextFormatting.AQUA + "[" + setting.getFullName() + " = false]");
return tooltip;
});
}
/** Sets the valid values for this setting. */
@SafeVarargs
public final Setting<T> setValidValues(T... values) { _validValues = values; return this; }
/** Sets the setting to be synchronized to players joining a world. */
public Setting<T> setSynced() { _doesSync = true; return this; }
/** Sets the setting to be synchronized to players joining a world.
* The specified action is fired when the setting is synced on the receiving player's side. */
public Setting<T> setSynced(Consumer<T> action) { setSynced(); return setUpdate(action); }
/** Sets the update function to be fired when the setting is updated (config changed or syncronized). */
public Setting<T> setUpdate(Consumer<T> action) { _updateAction = action; return this; }
/** Sets the setting's config entry class, to be used in place of the default. */
public Setting<T> setConfigEntryClass(String entryClass) { _entryClass = entryClass; return this; }
/** Sets the setting's comment, to be used in the config file. */
public Setting<T> setComment(String comment) { _comment = comment; return this; }
/** Returns the setting's category, for example "general". */
public String getCategory() { return _category; }
/** Returns the setting's name, for example "equipAsChestArmor". */
public String getName() { return _name; }
/** Returns the setting's full name, for example "general.equipAsChestArmor". */
public String getFullName() { return _category + "." + _name; }
/** Returns the setting's default value. */
public T getDefault() { return _defaultValue; }
/** Returns the setting's current value. */
public T get() { return (_checkEntryValue ? getEntryValue() : (_isSynced ? _syncedValue : _value)); }
/** Sets the setting's current value. */
public void set(T value) { _value = value; _property.set(Objects.toString(value)); }
/** Returns if this setting is enabled based on its requirements. */
public boolean isEnabled() { return _requireFunc.getAsBoolean(); }
/** Returns if this setting is enabled based on its requirements (uses config entry values). */
@SideOnly(Side.CLIENT)
public boolean isEnabledConfig() {
_checkEntryValue = true;
boolean enabled = isEnabled();
_checkEntryValue = false;
return enabled;
}
/** Returns whether changing the setting requires a world rejoin. */
public boolean requiresWorldRejoin() { return _requiresWorldRejoin; }
/** Returns whether changing the setting requires Minecraft to be restarted. */
public boolean requiresMinecraftRestart() { return _requiresMinecraftRestart; }
/** Returns a recommendation hint for this setting if
* not all recommendations are met, or null otherwise. */
@SideOnly(Side.CLIENT)
public List<String> getRecommendationHint() {
_checkEntryValue = true;
List<String> hintTooltip = _recommendFunc.get();
_checkEntryValue = false;
return hintTooltip;
}
/** Returns if the setting is synced to players when they join a (multiplayer/LAN) world. */
public boolean doesSync() { return _doesSync; }
/** Returns if the setting is currently synced and get()
* should return the synced instead of the "own" value. */
public boolean isSynced() { return _isSynced; }
/** Sets the setting's config entry class, to be used in place of the default. */
public String getConfigEntryClass() { return _entryClass; }
/** Returns the setting's comment, as used in the config file. */
public String getComment() { return _comment; }
/** Returns the setting's current entry value in the config GUI. */
private T getEntryValue() { return _entry.getValue(); }
/** Sets the setting's current config entry in the config GUI to the specified entry.
* Used for disabling config entries dynamically based on which settings they require. */
@SideOnly(Side.CLIENT)
public void setEntry(EntrySetting<T> entry) { _entry = entry; }
/** Resets the setting's current config entry in the config GUI. */
@SideOnly(Side.CLIENT)
public void resetEntry() { _entry = null; }
/** Calls the update action if present
* and any required setting is enabled. */
public void update() {
if ((_updateAction != null) && isEnabled())
_updateAction.accept(_value);
}
// Forge Configuration related
/** Grabs the Property object from the Forge Configuration object. */
protected abstract Property getPropertyFromConfig(Configuration config);
/** Returns the Forge config Property associated with this setting. */
public Property getProperty() {
if (_property == null) {
// Initialize the property if it hasn't been already.
_property = getPropertyFromConfig(_config);
_property.setRequiresWorldRestart(_requiresWorldRejoin);
_property.setRequiresMcRestart(_requiresMinecraftRestart);
if (_validValues != null)
_property.setValidValues(
(String[])Arrays.stream(_validValues)
.map(Object::toString).toArray());
}
return _property;
}
/** Attempts to parse the specified string as a value of this setting.
* Throws an exception if the specified string is not valid. */
public abstract T parse(String str);
/** Called when the Configuration is loaded. */
protected void onPropertyLoaded() {
_value = parse(getProperty().getString());
}
// Synchronization
/** Reads the synced value from the specified NBT tag. */
public void readSynced(NBTBase tag) {
_isSynced = true;
_syncedValue = read(tag);
if (_updateAction != null)
_updateAction.accept(_syncedValue);
}
/** Writes the own value to an NBT tag, returning it. */
public NBTBase writeSynced() { return write(_value); }
/** Resets the synced value when players exit the world / disconnect from a server. */
protected void resetSynced() { _isSynced = false; _syncedValue = null; }
public abstract T read(NBTBase tag);
public abstract NBTBase write(T value);
}