Files
Stargate/src/main/java/net/knarcraft/stargate/config/StargateConfig.java

623 lines
20 KiB
Java

package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ConfigHelper;
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.file.FileConfiguration;
import org.bukkit.plugin.messaging.Messenger;
import org.dynmap.DynmapAPI;
import org.jetbrains.annotations.NotNull;
import java.io.File;
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<Portal> activePortalsQueue = new ConcurrentLinkedQueue<>();
private final Queue<Portal> openPortalsQueue = new ConcurrentLinkedQueue<>();
private final HashSet<String> managedWorlds = new HashSet<>();
private StargateGateConfig stargateGateConfig;
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<ConfigOption, Object> configOptions;
/**
* Instantiates a new stargate config
*
* @param logger <p>The logger to use for logging errors</p>
*/
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
*
* <p>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.</p>
*
* @return <p>A reference to the config options map</p>
*/
@NotNull
public Map<ConfigOption, Object> 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();
// Update prefix of the format builder
SGFormatBuilder.setStringFormatter(getStringFormatter());
if (isDebuggingEnabled()) {
languageLoader.debug();
}
this.loadGates();
this.createMissingFolders();
this.loadAllPortals();
//Set up vault economy if vault has been loaded
setupVaultEconomy();
//Set up dynmap
try {
DynmapAPI dynmapAPI = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap");
if (dynmapAPI != null) {
try {
DynmapManager.initialize(dynmapAPI);
DynmapManager.addAllPortalMarkers();
} catch (NullPointerException ignored) {
logger.warning("Dynmap started in an invalid state. Check your log/console for dynmap-related " +
"problems. Dynmap integration cannot be initialized.");
}
}
} catch (NoClassDefFoundError error) {
logger.warning("Dynmap seems to be unavailable, even though its API is registered. Dynmap " +
"integration is disabled.");
DynmapManager.disable();
}
this.isLoaded = true;
}
/**
* Gets whether this configuration has been fully loaded
*
* @return <p>True if not fully loaded</p>
*/
public boolean isNotLoaded() {
return !this.isLoaded;
}
/**
* Gets a copy of all loaded config options with its values
*
* @return <p>The loaded config options</p>
*/
@NotNull
public Map<ConfigOption, Object> getConfigOptions() {
return new HashMap<>(configOptions);
}
/**
* Gets the queue of open portals
*
* <p>The open portals queue is used to close open portals after some time has passed</p>
*
* @return <p>The open portals queue</p>
*/
@NotNull
public Queue<Portal> getOpenPortalsQueue() {
return openPortalsQueue;
}
/**
* Gets the queue of active portals
*
* <p>The active portals queue is used to de-activate portals after some time has passed</p>
*
* @return <p>The active portals queue</p>
*/
@NotNull
public Queue<Portal> getActivePortalsQueue() {
return activePortalsQueue;
}
/**
* Gets whether debugging is enabled
*
* @return <p>Whether debugging is enabled</p>
*/
public boolean isDebuggingEnabled() {
return (boolean) configOptions.get(ConfigOption.DEBUG);
}
/**
* Gets whether permission debugging is enabled
*
* @return <p>Whether permission debugging is enabled</p>
*/
public boolean isPermissionDebuggingEnabled() {
return (boolean) configOptions.get(ConfigOption.PERMISSION_DEBUG);
}
/**
* Gets whether Dynmap integration is disabled
*
* @return <p>Whether Dynmap integration is disabled</p>
*/
public boolean isDynmapDisabled() {
return !((boolean) configOptions.get(ConfigOption.ENABLE_DYNMAP));
}
/**
* Gets whether Dynmap icons should be hidden by default
*
* @return <p>Whether Dynmap icons should be hidden by default</p>
*/
public boolean hideDynmapIcons() {
return (boolean) configOptions.get(ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN);
}
/**
* Gets the object containing economy config values
*
* @return <p>The object containing economy config values</p>
*/
@NotNull
public EconomyConfig getEconomyConfig() {
return this.economyConfig;
}
/**
* Reloads all portals and files
*
* @param sender <p>The sender of the reload request</p>
*/
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.getControlBlockUpdateRequestQueue().peek();
while (firstElement != null) {
BlockChangeThread.pollQueue();
firstElement = Stargate.getControlBlockUpdateRequestQueue().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();
// Update prefix of the format builder
SGFormatBuilder.setStringFormatter(getStringFormatter());
new SGFormatBuilder(Message.RELOADED).error(sender);
}
/**
* 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 <p>The managed worlds</p>
*/
@NotNull
public Set<String> getManagedWorlds() {
return new HashSet<>(managedWorlds);
}
/**
* Adds a world to the managed worlds
*
* @param worldName <p>The name of the world to manage</p>
*/
public void addManagedWorld(@NotNull String worldName) {
managedWorlds.add(worldName);
}
/**
* Removes a world from the managed worlds
*
* @param worldName <p>The name of the world to stop managing</p>
*/
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 <p>Whether admins should be alerted about new updates</p>
*/
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().getConfig();
boolean isMigrating = false;
if (newConfig.getString("lang") != null || newConfig.getString("economy.taxAccount") == null) {
ConfigHelper.migrateConfig(Stargate.getInstance());
isMigrating = true;
Stargate.getInstance().reloadConfig();
newConfig = Stargate.getInstance().getConfig();
}
//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 -> optionValue = newConfig.getString(configNode, (String) option.getDefaultValue()).trim();
case BOOLEAN -> optionValue = newConfig.getBoolean(configNode, (boolean) option.getDefaultValue());
case INTEGER -> optionValue = newConfig.getInt(configNode, (int) option.getDefaultValue());
case DOUBLE -> optionValue = newConfig.getDouble(configNode, (double) option.getDefaultValue());
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 <p>The input string to replace in</p>
* @return <p>The replaced path, or the input if not applicable</p>
*/
@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 <p>Gets the gate config</p>
*/
@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()));
}
/**
* 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(new SGFormatBuilder(Message.VAULT_LOADED).replace("%version%", vaultVersion).toString());
}
}
/**
* 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 <p>The folder to create</p>
* @param errorMessage <p>The error message to display if unable to create the folder</p>
*/
private void createMissingFolder(File folder, String errorMessage) {
if (!folder.exists() && !folder.mkdirs()) {
logger.severe(errorMessage);
}
}
/**
* Gets the folder all portals are stored in
*
* @return <p>The portal folder</p>
*/
@NotNull
public String getPortalFolder() {
return portalFolder;
}
/**
* Gets the folder storing gate files
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder storing gate files</p>
*/
@NotNull
public String getGateFolder() {
return gateFolder;
}
/**
* Gets the language loader containing translated strings
*
* @return <p>The language loader</p>
*/
@NotNull
public LanguageLoader getLanguageLoader() {
return languageLoader;
}
/**
* Gets the string formatter to use
*/
@NotNull
private StringFormatter getStringFormatter() {
// In order to allow automatic customization of prefix color, parse it properly
String rawPrefix = getLanguageLoader().getString(Message.PREFIX);
String colorPattern = "(?:[&§][a-fA-F0-9klmnor]|&?#[0-9a-fA-F]{6}|§x(?:§[a-fA-F0-9]){6})*";
Pattern pattern = Pattern.compile("(" + colorPattern + "\\[" + colorPattern + ")(\\w+)(" +
colorPattern + "]" + colorPattern + ")");
return getStringFormatter(rawPrefix, pattern);
}
/**
* Gets the string formatter to use
*
* @param rawPrefix <p>The formatter prefix to parse</p>
* @param pattern <p>The pattern to use for parsing</p>
*/
private static @NotNull StringFormatter getStringFormatter(String rawPrefix, Pattern pattern) {
String prefix = rawPrefix;
String namePrefix = "[";
String nameSuffix = "]";
Matcher matcher = pattern.matcher(rawPrefix);
if (matcher.find()) {
namePrefix = matcher.group(1).trim();
prefix = matcher.group(2).trim();
nameSuffix = matcher.group(3).trim();
}
StringFormatter stringFormatter = new StringFormatter(prefix, new Translator());
stringFormatter.setColorConversion(ColorConversion.RGB);
stringFormatter.setNamePrefix(namePrefix);
stringFormatter.setNameSuffix(nameSuffix);
return stringFormatter;
}
}