package net.knarcraft.stargate.config; import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.knarlib.util.FileHelper; import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.container.BlockChangeRequest; import net.knarcraft.stargate.listener.BungeeCordListener; import net.knarcraft.stargate.portal.Portal; import net.knarcraft.stargate.portal.PortalHandler; import net.knarcraft.stargate.portal.PortalRegistry; import net.knarcraft.stargate.portal.property.gate.GateHandler; import net.knarcraft.stargate.thread.BlockChangeThread; import net.knarcraft.stargate.utility.PortalFileHelper; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.configuration.MemorySection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.messaging.Messenger; import org.dynmap.DynmapAPI; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The stargate config is responsible for keeping track of all configuration values */ public final class StargateConfig { private final Queue activePortalsQueue = new ConcurrentLinkedQueue<>(); private final Queue openPortalsQueue = new ConcurrentLinkedQueue<>(); private final HashSet managedWorlds = new HashSet<>(); private StargateGateConfig stargateGateConfig; private MessageSender messageSender; private final LanguageLoader languageLoader; private EconomyConfig economyConfig; private final Logger logger; private final String dataFolderPath; private String gateFolder; private String portalFolder; private String languageName = "en"; private boolean isLoaded = false; private final Map configOptions; /** * Instantiates a new stargate config * * @param logger

The logger to use for logging errors

*/ public StargateConfig(@NotNull Logger logger) { this.logger = logger; configOptions = new HashMap<>(); dataFolderPath = Stargate.getInstance().getDataFolder().getPath().replaceAll("\\\\", "/"); portalFolder = dataFolderPath + "/portals/"; gateFolder = dataFolderPath + "/gates/"; languageLoader = new LanguageLoader(dataFolderPath + "/lang/"); } /** * Gets a direct reference to the config option map * *

This reference can be used to alter the value of config options. Values should only be altered after it's * been verified that the value is valid.

* * @return

A reference to the config options map

*/ @NotNull public Map getConfigOptionsReference() { return configOptions; } /** * Finish the config setup by loading languages, gates and portals, and loading economy if vault is loaded */ public void finishSetup() { this.loadConfig(); //Enable the required channels for Bungee support if (stargateGateConfig.enableBungee()) { startStopBungeeListener(true); } //Set the chosen language and reload the language loader languageLoader.setChosenLanguage(languageName); languageLoader.reload(); messageSender = new MessageSender(languageLoader); if (isDebuggingEnabled()) { languageLoader.debug(); } this.loadGates(); this.createMissingFolders(); this.loadAllPortals(); //Set up vault economy if vault has been loaded setupVaultEconomy(); //Set up dynmap DynmapAPI dynmapAPI = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap"); if (dynmapAPI != null) { try { DynmapManager.initialize(dynmapAPI); DynmapManager.addAllPortalMarkers(); } catch (NullPointerException exception) { logger.warning("Dynmap started in an invalid state. Check your log/console for dynmap-related " + "problems. Dynmap integration cannot be initialized."); } } this.isLoaded = true; } /** * Gets whether this configuration has been fully loaded * * @return

True if not fully loaded

*/ public boolean isNotLoaded() { return !this.isLoaded; } /** * Gets a copy of all loaded config options with its values * * @return

The loaded config options

*/ @NotNull public Map getConfigOptions() { return new HashMap<>(configOptions); } /** * Gets the queue of open portals * *

The open portals queue is used to close open portals after some time has passed

* * @return

The open portals queue

*/ @NotNull public Queue getOpenPortalsQueue() { return openPortalsQueue; } /** * Gets the queue of active portals * *

The active portals queue is used to de-activate portals after some time has passed

* * @return

The active portals queue

*/ @NotNull public Queue getActivePortalsQueue() { return activePortalsQueue; } /** * Gets whether debugging is enabled * * @return

Whether debugging is enabled

*/ public boolean isDebuggingEnabled() { return (boolean) configOptions.get(ConfigOption.DEBUG); } /** * Gets whether permission debugging is enabled * * @return

Whether permission debugging is enabled

*/ public boolean isPermissionDebuggingEnabled() { return (boolean) configOptions.get(ConfigOption.PERMISSION_DEBUG); } /** * Gets whether Dynmap integration is disabled * * @return

Whether Dynmap integration is disabled

*/ public boolean isDynmapDisabled() { return !((boolean) configOptions.get(ConfigOption.ENABLE_DYNMAP)); } /** * Gets whether Dynmap icons should be hidden by default * * @return

Whether Dynmap icons should be hidden by default

*/ public boolean hideDynmapIcons() { return (boolean) configOptions.get(ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN); } /** * Gets the object containing economy config values * * @return

The object containing economy config values

*/ @NotNull public EconomyConfig getEconomyConfig() { return this.economyConfig; } /** * Reloads all portals and files * * @param sender

The sender of the reload request

*/ public void reload(@NotNull CommandSender sender) { //Unload all saved data unload(); //Perform all block change requests to prevent mismatch if a gate's open-material changes. Changing the // closed-material still requires a restart. BlockChangeRequest firstElement = Stargate.getBlockChangeRequestQueue().peek(); while (firstElement != null) { BlockChangeThread.pollQueue(); firstElement = Stargate.getBlockChangeRequestQueue().peek(); } //Store the old enable bungee state in case it changes boolean oldEnableBungee = stargateGateConfig.enableBungee(); //Load all data load(); //Enable or disable the required channels for Bungee support if (oldEnableBungee != stargateGateConfig.enableBungee()) { startStopBungeeListener(stargateGateConfig.enableBungee()); } //Reload portal markers DynmapManager.addAllPortalMarkers(); messageSender.sendErrorMessage(sender, languageLoader.getString(Message.RELOADED)); } /** * Un-loads all loaded data */ private void unload() { //De-activate, close and unload all loaded portals unloadAllPortals(); //Clear all loaded gates GateHandler.clearGates(); } /** * Un-loads all loaded portals */ public void unloadAllPortals() { //De-activate all currently active portals for (Portal activePortal : activePortalsQueue) { activePortal.getPortalActivator().deactivate(); } //Force all portals to close closeAllOpenPortals(); PortalHandler.closeAllPortals(); //Clear queues and lists activePortalsQueue.clear(); openPortalsQueue.clear(); managedWorlds.clear(); //Clear all loaded portals PortalRegistry.clearPortals(); } /** * Clears the set of managed worlds */ public void clearManagedWorlds() { managedWorlds.clear(); } /** * Gets a copy of the set of managed worlds * * @return

The managed worlds

*/ @NotNull public Set getManagedWorlds() { return new HashSet<>(managedWorlds); } /** * Adds a world to the managed worlds * * @param worldName

The name of the world to manage

*/ public void addManagedWorld(@NotNull String worldName) { managedWorlds.add(worldName); } /** * Removes a world from the managed worlds * * @param worldName

The name of the world to stop managing

*/ public void removeManagedWorld(@NotNull String worldName) { managedWorlds.remove(worldName); } /** * Loads all necessary data */ private void load() { //Load the config from disk loadConfig(); //Load all gates loadGates(); //Load all portals loadAllPortals(); //Update the language loader in case the loaded language changed languageLoader.setChosenLanguage(languageName); languageLoader.reload(); if (isDebuggingEnabled()) { languageLoader.debug(); } //Load Economy support if enabled/clear if disabled reloadEconomy(); } /** * Starts the listener for listening to BungeeCord messages */ public void startStopBungeeListener(boolean start) { Messenger messenger = Bukkit.getMessenger(); String bungeeChannel = "BungeeCord"; if (start) { messenger.registerOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel); messenger.registerIncomingPluginChannel(Stargate.getInstance(), bungeeChannel, new BungeeCordListener()); } else { messenger.unregisterIncomingPluginChannel(Stargate.getInstance(), bungeeChannel); messenger.unregisterOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel); } } /** * Reloads economy by enabling or disabling it as necessary */ public void reloadEconomy() { EconomyConfig economyConfig = getEconomyConfig(); if (economyConfig.isEconomyEnabled() && economyConfig.getEconomy() == null) { setupVaultEconomy(); } else if (!economyConfig.isEconomyEnabled()) { economyConfig.disableEconomy(); } } /** * Forces all open portals to close */ public void closeAllOpenPortals() { for (Portal openPortal : openPortalsQueue) { openPortal.getPortalOpener().closePortal(false); } } /** * Gets whether admins should be alerted about new plugin updates * * @return

Whether admins should be alerted about new updates

*/ public boolean alertAdminsAboutUpdates() { return (boolean) configOptions.get(ConfigOption.ADMIN_UPDATE_ALERT); } /** * Loads all config values */ public void loadConfig() { Stargate.getInstance().reloadConfig(); FileConfiguration newConfig = Stargate.getInstance().getConfiguration(); boolean isMigrating = false; if (newConfig.getString("lang") != null || newConfig.getString("economy.taxAccount") == null) { migrateConfig(newConfig); isMigrating = true; Stargate.getInstance().reloadConfig(); newConfig = Stargate.getInstance().getConfiguration(); } //Copy missing default values if any values are missing newConfig.options().copyDefaults(true); //Load all options for (ConfigOption option : ConfigOption.values()) { Object optionValue; String configNode = option.getConfigNode(); //Load the option using its correct data type switch (option.getDataType()) { case STRING_LIST -> optionValue = newConfig.getStringList(configNode); case STRING -> { String value = newConfig.getString(configNode); optionValue = value != null ? value.trim() : ""; } case BOOLEAN -> optionValue = newConfig.getBoolean(configNode); case INTEGER -> optionValue = newConfig.getInt(configNode); case DOUBLE -> optionValue = newConfig.getDouble(configNode); default -> throw new IllegalArgumentException("Invalid config data type encountered"); } configOptions.put(option, optionValue); } //Get the language name from the config languageName = (String) configOptions.get(ConfigOption.LANGUAGE); //Get important folders from the config portalFolder = (String) configOptions.get(ConfigOption.PORTAL_FOLDER); if (portalFolder.isEmpty()) { portalFolder = dataFolderPath + "/portals/"; } else { portalFolder = replacePluginFolderPath(portalFolder); } Stargate.debug("StargateConfig::loadConfig", "Portal folder is " + portalFolder); gateFolder = (String) configOptions.get(ConfigOption.GATE_FOLDER); if (gateFolder.isEmpty()) { gateFolder = dataFolderPath + "/gates/"; } else { gateFolder = replacePluginFolderPath(gateFolder); } Stargate.debug("StargateConfig::loadConfig", "Gate folder is " + gateFolder); //If users have an outdated config, assume they also need to update their default gates if (isMigrating) { this.createMissingFolders(); GateHandler.writeDefaultGatesToFolder(gateFolder); } //Load all gate config values stargateGateConfig = new StargateGateConfig(configOptions); //Load all economy config values economyConfig = new EconomyConfig(configOptions); Stargate.getInstance().saveConfig(); } /** * Replaces "plugins/Stargate" in a folder path, and replaces it with the full path relative to the data folder * * @param input

The input string to replace in

* @return

The replaced path, or the input if not applicable

*/ @NotNull private String replacePluginFolderPath(@NotNull String input) { Pattern pattern = Pattern.compile("(?i)^plugins[\\\\/]Stargate"); Matcher matcher = pattern.matcher(input); if (matcher.find()) { return dataFolderPath + matcher.replaceAll(""); } else { return input; } } /** * Gets the object containing configuration values regarding gates * * @return

Gets the gate config

*/ @NotNull public StargateGateConfig getStargateGateConfig() { return stargateGateConfig; } /** * Loads all available gates */ public void loadGates() { GateHandler.loadGates(gateFolder); Stargate.logInfo(String.format("Loaded %s gate layouts", GateHandler.getGateCount())); } /** * Changes all configuration values from the old name to the new name * * @param currentConfiguration

The current config to back up

*/ private void migrateConfig(@NotNull FileConfiguration currentConfiguration) { String debugPath = "StargateConfig::migrateConfig"; //Save the old config just in case something goes wrong try { currentConfiguration.save(new File(dataFolderPath, "config.yml.old")); } catch (IOException exception) { Stargate.debug(debugPath, "Unable to save old backup and do migration"); return; } //Load old and new configuration Stargate.getInstance().reloadConfig(); FileConfiguration oldConfiguration = Stargate.getInstance().getConfig(); YamlConfiguration newConfiguration = StargateYamlConfiguration.loadConfiguration( FileHelper.getBufferedReaderFromInputStream( FileHelper.getInputStreamForInternalFile("/config.yml"))); //Read all available config migrations Map migrationFields; try { migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream( FileHelper.getInputStreamForInternalFile("/config-migrations.txt")), "=", ColorConversion.NORMAL); } catch (IOException exception) { Stargate.debug(debugPath, "Unable to load config migration file"); return; } //Replace old config names with the new ones for (String key : migrationFields.keySet()) { if (oldConfiguration.contains(key)) { String newPath = migrationFields.get(key); Object oldValue = oldConfiguration.get(key); if (!newPath.trim().isEmpty()) { oldConfiguration.set(newPath, oldValue); } oldConfiguration.set(key, null); } } // Copy all keys to the new config for (String key : oldConfiguration.getKeys(true)) { if (oldConfiguration.get(key) instanceof MemorySection) { continue; } Stargate.debug(debugPath, "Setting " + key + " to " + oldConfiguration.get(key)); newConfiguration.set(key, oldConfiguration.get(key)); } try { newConfiguration.save(new File(dataFolderPath, "config.yml")); } catch (IOException exception) { Stargate.debug(debugPath, "Unable to save migrated config"); } Stargate.getInstance().reloadConfig(); } /** * Loads economy from Vault */ private void setupVaultEconomy() { EconomyConfig economyConfig = getEconomyConfig(); if (economyConfig.setupEconomy(Stargate.getPluginManager()) && economyConfig.getEconomy() != null && economyConfig.getVault() != null) { String vaultVersion = economyConfig.getVault().getDescription().getVersion(); Stargate.logInfo(Stargate.replacePlaceholders(Stargate.getString(Message.VAULT_LOADED), "%version%", vaultVersion)); } } /** * Loads all portals in all un-managed worlds */ public void loadAllPortals() { for (World world : Stargate.getInstance().getServer().getWorlds()) { if (!managedWorlds.contains(world.getName())) { PortalFileHelper.loadAllPortals(world); managedWorlds.add(world.getName()); } } } /** * Creates missing folders */ private void createMissingFolders() { createMissingFolder(new File(gateFolder), "Unable to create gate directory"); createMissingFolder(new File(portalFolder), "Unable to create portal directory"); File newFile = new File(portalFolder, Stargate.getInstance().getServer().getWorlds().get(0).getName() + ".db"); if (!newFile.exists() && !newFile.getParentFile().exists() && !newFile.getParentFile().mkdirs()) { logger.severe("Unable to create portal database folder: " + newFile.getParentFile().getPath()); } } /** * Creates the given folder if it's missing * * @param folder

The folder to create

* @param errorMessage

The error message to display if unable to create the folder

*/ private void createMissingFolder(File folder, String errorMessage) { if (!folder.exists() && !folder.mkdirs()) { logger.severe(errorMessage); } } /** * Gets the folder all portals are stored in * * @return

The portal folder

*/ @NotNull public String getPortalFolder() { return portalFolder; } /** * Gets the folder storing gate files * *

The returned String path is the full path to the folder

* * @return

The folder storing gate files

*/ @NotNull public String getGateFolder() { return gateFolder; } /** * Gets the sender for sending messages to players * * @return

The sender for sending messages to players

*/ @NotNull public MessageSender getMessageSender() { return messageSender; } /** * Gets the language loader containing translated strings * * @return

The language loader

*/ @NotNull public LanguageLoader getLanguageLoader() { return languageLoader; } }