package de.cowtipper.cowlection.config; import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.command.TabCompletableCommand; import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.command.ICommand; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.Util; import net.minecraftforge.client.ClientCommandHandler; import net.minecraftforge.common.ForgeModContainer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; import net.minecraftforge.fml.client.FMLConfigGuiFactory; import net.minecraftforge.fml.client.event.ConfigChangedEvent; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.apache.commons.lang3.ArrayUtils; import java.io.File; import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * Mod configuration via ingame gui *

* Based on TheGreyGhost's MinecraftByExample * * @see ForgeModContainer * @see FMLConfigGuiFactory */ public class MooConfig { static final String CATEGORY_LOGS_SEARCH = "logssearch"; // main config public static boolean doUpdateCheck; public static boolean showBestFriendNotifications; public static boolean showFriendNotifications; public static boolean showGuildNotifications; public static boolean doBestFriendsOnlineCheck; public static boolean showAdvancedTooltips; public static String[] tabCompletableNamesCommands; private static String numeralSystem; // SkyBlock dungeon public static int[] dungClassRange; public static boolean dungFilterPartiesWithDupes; public static String dungPartyFinderArmorLookup; public static String dungItemQualityPos; public static boolean dungOverlayEnabled; public static int dungOverlayGuiScale; public static int dungOverlayPositionX; public static int dungOverlayPositionY; // logs search config public static String[] logsDirs; private static String defaultStartDate; // other stuff public static String moo; private static Configuration cfg = null; private final Cowlection main; private List propOrderGeneral; private List propOrderLogsSearch; public MooConfig(Cowlection main, Configuration configuration) { this.main = main; cfg = configuration; initConfig(); } static Configuration getConfig() { return cfg; } private void initConfig() { syncFromFile(); MinecraftForge.EVENT_BUS.register(new ConfigEventHandler()); } /** * Load the configuration values from the configuration file */ private void syncFromFile() { syncConfig(true, true); } /** * Save the GUI-altered values to disk */ private void syncFromGui() { syncConfig(false, true); } /** * Save the Configuration variables (fields) to disk */ public void syncFromFields() { syncConfig(false, false); } public static LocalDate calculateStartDate() { try { // date format: yyyy-mm-dd return LocalDate.parse(defaultStartDate); } catch (DateTimeParseException e) { // fallthrough } try { int months = Integer.parseInt(defaultStartDate); return LocalDate.now().minus(months, ChronoUnit.MONTHS); } catch (NumberFormatException e) { // default: 1 month return LocalDate.now().minus(1, ChronoUnit.MONTHS); } } /** * Synchronise the three copies of the data * 1) loadConfigFromFile && readFieldsFromConfig -> initialise everything from the disk file * 2) !loadConfigFromFile && readFieldsFromConfig -> copy everything from the config file (altered by GUI) * 3) !loadConfigFromFile && !readFieldsFromConfig -> copy everything from the native fields * * @param loadConfigFromFile if true, load the config field from the configuration file on disk * @param readFieldsFromConfig if true, reload the member variables from the config field */ private void syncConfig(boolean loadConfigFromFile, boolean readFieldsFromConfig) { if (loadConfigFromFile) { cfg.load(); } // config section: main configuration propOrderGeneral = new ArrayList<>(); Property propDoUpdateCheck = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "doUpdateCheck", true, "Check for mod updates?"), true); Property propShowBestFriendNotifications = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "showBestFriendNotifications", true, "Set to true to receive best friends' login/logout messages, set to false hide them."), true); Property propShowFriendNotifications = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "showFriendNotifications", true, "Set to true to receive friends' login/logout messages, set to false hide them."), true); Property propShowGuildNotifications = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "showGuildNotifications", true, "Set to true to receive guild members' login/logout messages, set to false hide them."), true); Property propDoBestFriendsOnlineCheck = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "doBestFriendsOnlineCheck", true, "Set to true to check best friends' online status when joining a server, set to false to disable."), true); Property propShowAdvancedTooltips = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "showAdvancedTooltips", true, "Set to true to show advanced tooltips, set to false show default tooltips."), true); Property propNumeralSystem = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "numeralSystem", "Arabic numerals: 1, 4, 10", "Use Roman or Arabic numeral system?", new String[]{"Arabic numerals: 1, 4, 10", "Roman numerals: I, IV, X"}), true); Property propTabCompletableNamesCommands = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "tabCompletableNamesCommands", new String[]{"party", "p", "invite", "visit", "ah", "ignore", "msg", "tell", "w", "boop", "profile", "friend", "friends", "f"}, "List of commands with a Tab-completable username argument."), true) .setValidationPattern(Pattern.compile("^[A-Za-z]+$")); Property propMoo = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "moo", "", "The answer to life the universe and everything. Don't edit this entry manually!", Utils.VALID_UUID_PATTERN), false); // SkyBlock dungeon Property propDungClassRange = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungClassRange", new int[]{-1, -1}, "Accepted level range for the dungeon party finder. Set to -1 to disable"), true) .setMinValue(-1).setIsListLengthFixed(true); Property propDungFilterPartiesWithDupes = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungFilterPartiesWithDupes", false, "Mark parties with duplicated classes?"), true); Property propDungPartyFinderArmorLookup = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungPartyFinderArmorLookup", "as a tooltip", "Show armor of player joining via party finder as a tooltip or in chat?", new String[]{"as a tooltip", "in chat", "disabled"}), true); Property propDungItemQualityPos = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungItemQualityPos", "top", "Position of item quality in tooltip", new String[]{"top", "bottom"}), true); Property propDungOverlayEnabled = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungOverlayEnabled", true, "Enable Dungeon performance overlay?"), false); Property propDungOverlayPositionX = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungGuiPositionX", 5, "Dungeon performance overlay position: x value", -1, 10000), false); Property propDungOverlayPositionY = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungGuiPositionY", 5, "Dungeon performance overlay position: y value", -1, 5000), false); Property propDungOverlayGuiScale = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "dungOverlayGuiScale", 100, "Dungeon performance overlay GUI scale", 50, 200), false); cfg.setCategoryPropertyOrder(Configuration.CATEGORY_CLIENT, propOrderGeneral); // config section: log files search propOrderLogsSearch = new ArrayList<>(); Property propLogsDirs = addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, "logsDirs", resolveDefaultLogsDirs(), "Directories with Minecraft log files"), true, CATEGORY_LOGS_SEARCH); Property propDefaultStartDate = addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, "defaultStartDate", "3", "Default start date (a number means X months ago, alternatively a fixed date à la yyyy-mm-dd can be used)"), true) .setValidationPattern(Pattern.compile("^[1-9][0-9]{0,2}|(2[0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))$")); cfg.setCategoryPropertyOrder(CATEGORY_LOGS_SEARCH, propOrderLogsSearch); // 'manual' replacement for propTabCompletableNamesCommands.hasChanged() boolean modifiedTabCompletableCommandsList = false; String[] tabCompletableCommandsPreChange = tabCompletableNamesCommands != null ? tabCompletableNamesCommands.clone() : null; if (readFieldsFromConfig) { // main config doUpdateCheck = propDoUpdateCheck.getBoolean(); showBestFriendNotifications = propShowBestFriendNotifications.getBoolean(); showFriendNotifications = propShowFriendNotifications.getBoolean(); showGuildNotifications = propShowGuildNotifications.getBoolean(); doBestFriendsOnlineCheck = propDoBestFriendsOnlineCheck.getBoolean(); showAdvancedTooltips = propShowAdvancedTooltips.getBoolean(); numeralSystem = propNumeralSystem.getString(); tabCompletableNamesCommands = propTabCompletableNamesCommands.getStringList(); moo = propMoo.getString(); // SkyBlock dungeon dungClassRange = propDungClassRange.getIntList(); dungFilterPartiesWithDupes = propDungFilterPartiesWithDupes.getBoolean(); dungPartyFinderArmorLookup = propDungPartyFinderArmorLookup.getString(); dungItemQualityPos = propDungItemQualityPos.getString(); dungOverlayEnabled = propDungOverlayEnabled.getBoolean(); dungOverlayPositionX = propDungOverlayPositionX.getInt(); dungOverlayPositionY = propDungOverlayPositionY.getInt(); dungOverlayGuiScale = propDungOverlayGuiScale.getInt(); // logs search config logsDirs = propLogsDirs.getStringList(); defaultStartDate = propDefaultStartDate.getString().trim(); if (!Arrays.equals(tabCompletableCommandsPreChange, tabCompletableNamesCommands)) { modifiedTabCompletableCommandsList = true; } } // main config propDoUpdateCheck.set(doUpdateCheck); propShowBestFriendNotifications.set(showBestFriendNotifications); propShowFriendNotifications.set(showFriendNotifications); propShowGuildNotifications.set(showGuildNotifications); propDoBestFriendsOnlineCheck.set(doBestFriendsOnlineCheck); propShowAdvancedTooltips.set(showAdvancedTooltips); propNumeralSystem.set(numeralSystem); propTabCompletableNamesCommands.set(tabCompletableNamesCommands); propMoo.set(moo); // SkyBlock dungeon propDungClassRange.set(dungClassRange); propDungFilterPartiesWithDupes.set(dungFilterPartiesWithDupes); propDungPartyFinderArmorLookup.set(dungPartyFinderArmorLookup); propDungItemQualityPos.set(dungItemQualityPos); propDungOverlayEnabled.set(dungOverlayEnabled); propDungOverlayPositionX.set(dungOverlayPositionX); propDungOverlayPositionY.set(dungOverlayPositionY); propDungOverlayGuiScale.set(dungOverlayGuiScale); // logs search config propLogsDirs.set(logsDirs); propDefaultStartDate.set(defaultStartDate); if (cfg.hasChanged()) { boolean isPlayerIngame = Minecraft.getMinecraft().thePlayer != null; if (modifiedTabCompletableCommandsList) { if (isPlayerIngame) { main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Added or removed commands with tab-completable usernames take effect after a game restart! If player names cannot be tab-completed for a command after a game restart, check the capitalization of the command name."); } Map clientCommandsMap = ClientCommandHandler.instance.getCommands(); List removedCommands = new ArrayList<>(); for (String tabCompletableCommandName : tabCompletableNamesCommands) { ICommand possibleClientCommand = clientCommandsMap.get(tabCompletableCommandName); if (possibleClientCommand != null && !(possibleClientCommand instanceof TabCompletableCommand)) { // tried to add a client side command to tab-completable commands; however, this would overwrite the original command removedCommands.add(tabCompletableCommandName); } } if (removedCommands.size() > 0) { if (isPlayerIngame) { main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, " ⚠ " + EnumChatFormatting.GOLD + "Client-side commands from other mods cannot be added to commands with tab-completable usernames. " + EnumChatFormatting.RED + "This would overwrite the other command! Therefore the following commands have been removed from the list of commands with tab-completable usernames: " + EnumChatFormatting.GOLD + String.join(EnumChatFormatting.RED + ", " + EnumChatFormatting.GOLD, removedCommands)); } tabCompletableNamesCommands = (String[]) ArrayUtils.removeElements(tabCompletableNamesCommands, removedCommands.toArray()); propTabCompletableNamesCommands.set(tabCompletableNamesCommands); } } if (isPlayerIngame && dungClassRange[0] > -1 && dungClassRange[1] > -1 && dungClassRange[0] > dungClassRange[1]) { main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Dungeon class range minimum value cannot be higher than the maximum value."); } cfg.save(); } } private Property addConfigEntry(Property property, boolean showInGui, String category) { if (showInGui) { property.setLanguageKey(Cowlection.MODID + ".config." + property.getName()); } else { property.setShowInGui(false); } if (CATEGORY_LOGS_SEARCH.equals(category)) { propOrderLogsSearch.add(property.getName()); } else { // == Configuration.CATEGORY_CLIENT: propOrderGeneral.add(property.getName()); } return property; } private Property addConfigEntry(Property property, boolean showInGui) { return addConfigEntry(property, showInGui, Configuration.CATEGORY_CLIENT); } /** * Tries to find/resolve default directories containing minecraft logfiles (in .log.gz format) * * @return list of /logs/ directories */ private String[] resolveDefaultLogsDirs() { List logsDirs = new ArrayList<>(); File currentMcLogsDirFile = new File(Minecraft.getMinecraft().mcDataDir, "logs"); if (currentMcLogsDirFile.exists() && currentMcLogsDirFile.isDirectory()) { String currentMcLogsDir = Utils.toRealPath(currentMcLogsDirFile); logsDirs.add(currentMcLogsDir); } String defaultMcLogsDir = System.getProperty("user.home"); Util.EnumOS osType = Util.getOSType(); // default directories for .minecraft: https://minecraft.gamepedia.com/.minecraft switch (osType) { case WINDOWS: defaultMcLogsDir += "\\AppData\\Roaming\\.minecraft\\logs"; break; case OSX: defaultMcLogsDir += "/Library/Application Support/minecraft/logs"; break; default: defaultMcLogsDir += "/.minecraft/logs"; } File defaultMcLogsDirFile = new File(defaultMcLogsDir); if (defaultMcLogsDirFile.exists() && defaultMcLogsDirFile.isDirectory() && !currentMcLogsDirFile.equals(defaultMcLogsDirFile)) { logsDirs.add(Utils.toRealPath(defaultMcLogsDirFile)); } return logsDirs.toArray(new String[]{}); } /** * Should login/logout notifications be modified and thus monitored? * * @return true if notifications should be monitored */ public static boolean doMonitorNotifications() { return showBestFriendNotifications || !showFriendNotifications || !showGuildNotifications; } public static boolean useRomanNumerals() { return numeralSystem.startsWith("Roman"); } public static boolean isDungItemQualityAtTop() { return dungItemQualityPos.equals("top"); } public static boolean showArmorLookupInChat() { return "in chat".equals(dungPartyFinderArmorLookup); } public class ConfigEventHandler { @SubscribeEvent(priority = EventPriority.NORMAL) public void onEvent(ConfigChangedEvent.OnConfigChangedEvent e) { if (Cowlection.MODID.equals(e.modID)) { syncFromGui(); } } } }