package net.mcft.copy.backpacks.config; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.io.File; import java.lang.reflect.Field; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; import net.mcft.copy.backpacks.BackpacksContent; import net.mcft.copy.backpacks.WearableBackpacks; import net.mcft.copy.backpacks.api.BackpackHelper; import net.mcft.copy.backpacks.misc.BackpackSize; import net.mcft.copy.backpacks.network.MessageSyncSettings; public class BackpacksConfig extends Configuration { // ==[ GENERAL ]== public final Setting<Boolean> equipAsChestArmor = new SettingBoolean(true) .setSynced((value) -> BackpackHelper.equipAsChestArmor = value) .setComment("If disabled, backpacks do not take up the player's chest armor equipment slot. Default: true."); public final Setting<Boolean> enableEquippedInteraction = new SettingBoolean(true) .setComment("If enabled, allows equipped backpacks to be opened by other players by right clicking the target's back. Default: true."); public final Setting<Boolean> enableSelfInteraction = new SettingBoolean(false).setSynced() .setComment("If enabled, allows players to open their own equipped backpack without requiring it to be placed first using a keybind. Default: false."); public final Setting<Boolean> dropAsBlockOnDeath = new SettingBoolean(true) .setComment("If enabled, places equipped backpacks as a block on death, instead of scattering the items all around. Default: true."); // ==[ BACKPACK ]== public final BackpackCategory backpack = new BackpackCategory(); public class BackpackCategory { public final Setting<Boolean> enabled = new SettingBoolean(true) .setRequiresMinecraftRestart(); public final Setting<Integer> durability = new SettingInteger(214).setValidRange(0, Integer.MAX_VALUE) .setRequired(enabled).setRecommended(equipAsChestArmor) .setSynced((value) -> BackpacksContent.BACKPACK.setMaxDamage(value)) .setComment("Durability of a normal backpack. Set to 0 for unbreakable. Default: 214.\n" + "Lowering this (including setting to 0) can cause issues with already damaged backpacks."); public final Setting<Integer> armor = new SettingInteger(2).setValidRange(0, 20) .setRequired(enabled).setRecommended(equipAsChestArmor).setSynced() .setConfigEntryClass("net.mcft.copy.backpacks.client.config.EntryArmor") .setComment("Armor points of a normal backpack. Valid values are 0 to 20. Default: 2.\n" + "Has no effect if equipAsChestArmor is disabled."); public final Setting<BackpackSize> size = new SettingBackpackSize(9, 4).setRequired(enabled) .setComment("Storage size of a normal backpack. Valid values are [1x1] to [17x6]. Default: [9x4].\n" + "Changing this doesn't affect placed or equipped backpacks until turned back into an item."); } private Map<String, Setting<?>> _settings = new LinkedHashMap<String, Setting<?>>(); public BackpacksConfig(File file) { super(file); // Add settings from this class as general category. addSettingsFromClass(this, Configuration.CATEGORY_GENERAL); // Iterate over category fields and add their settings. for (Field field : getClass().getFields()) { if ((field.getDeclaringClass() != getClass()) || !field.getType().getName().endsWith("Category")) continue; try { addSettingsFromClass(field.get(this), field.getName()); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } } } /** Iterates over the Setting fields on the specified * instance, initializing and adding them to _settings. */ private void addSettingsFromClass(Object instance, String category) { for (Field field : instance.getClass().getFields()) { if (!Setting.class.isAssignableFrom(field.getType())) continue; Setting<?> setting; try { setting = (Setting<?>)field.get(instance); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } setting.init(this, category, field.getName()); _settings.put(setting.getFullName(), setting); } } /** Returns a setting from this configuration with the specified full name. */ public Setting<?> getSetting(String fullName) { return _settings.get(fullName); } /** Returns a collection containing all settings from this configuration. */ public Collection<Setting<?>> getSettings() { return _settings.values(); } /** Returns a collection containing all settings from the specified category. */ public Collection<Setting<?>> getSettings(String category) { return getSettings().stream() .filter(setting -> setting.getCategory().equals(category)) .collect(Collectors.toList()); } // Loading / saving / initializing @Override public void load() { // By default, Configuration calls load when constructed. // At that time, members of this class are not yet initialized, so // instead we call load manually afterwards from the main mod class. if (_settings == null) return; super.load(); // Update config settings from old versions. if (getCategory("backpack").containsKey("rows")) { int rows = get("backpack", "rows", 4).getInt(); get("backpack", "size", "").set(new BackpackSize(9, rows).toString()); getCategory("backpack").remove("rows"); } getSettings().forEach(Setting::onPropertyLoaded); } @Override public void save() { // Our settings are very deliberately sorted, so group our settings by // category and set the property order to how we have ordered the fields. // This is also why we use LinkedHashMap: Keeps elements in insertion order. Map<String, List<Setting<?>>> byCategory = getSettings().stream().collect( Collectors.groupingBy(setting -> setting.getCategory(), LinkedHashMap::new, Collectors.toList())); for (Map.Entry<String, List<Setting<?>>> entry : byCategory.entrySet()) { String category = entry.getKey(); if (category.equals(Configuration.CATEGORY_GENERAL)) continue; List<String> order = entry.getValue().stream().map(Setting::getName).collect(Collectors.toList()); setCategoryPropertyOrder(category, order); } // Unfortunately ordering is not possible for categories in the config file itself. // Remove old config properties. getCategory(Configuration.CATEGORY_GENERAL).remove("enableHelpTooltips"); super.save(); } /** Called once after content has been initialized to call setting update methods. */ public void init() { getSettings().forEach(Setting::update); } // Event handling @SubscribeEvent public void onConfigChanged(OnConfigChangedEvent event) { if (!event.getModID().equals(WearableBackpacks.MOD_ID)) return; // Resyncronize the settings to all players. if (event.isWorldRunning()) WearableBackpacks.CHANNEL.sendToAll(MessageSyncSettings.create()); save(); } @SubscribeEvent public void onPlayerLogin(PlayerLoggedInEvent event) { // Synchronize settings with players when they join the world / server. WearableBackpacks.CHANNEL.sendTo(MessageSyncSettings.create(), event.player); } @SubscribeEvent public void onDisconnectedFromServer(ClientDisconnectionFromServerEvent event) { // Reset all synced values of the settings. getSettings().forEach(Setting::resetSynced); } }