From aa86e6c3ab8c5c8dc6cd7f3e212faf3c7e6edc1e Mon Sep 17 00:00:00 2001 From: Esophose Date: Thu, 24 Oct 2019 13:13:04 -0600 Subject: [PATCH 01/12] Gradle build file --- .github/workflows/gradle.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..0e12312 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,17 @@ +name: Java CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Gradle + run: ./gradlew build From 899ad56d8a9c81c3cdf06bb2c0dd04c0c7762293 Mon Sep 17 00:00:00 2001 From: Esophose Date: Thu, 28 Nov 2019 11:40:09 -0700 Subject: [PATCH 02/12] Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 45e7ff1..a7f8c26 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ hs_err_pid* build/ target/ *.iml -/bin/ \ No newline at end of file +/bin/ +/doc/ +/images/ From 89a6e89bafbeae1f85d03c69a1f9805b5bdc64e2 Mon Sep 17 00:00:00 2001 From: Esophose Date: Thu, 28 Nov 2019 11:43:21 -0700 Subject: [PATCH 03/12] Add executable permission --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From e712532b58388201c3172744f2880aeb8cf595bf Mon Sep 17 00:00:00 2001 From: Esophose Date: Thu, 28 Nov 2019 11:47:21 -0700 Subject: [PATCH 04/12] Fix missing gradle wrapper file --- .gitignore | 24 ----------------------- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 ++-- 4 files changed, 3 insertions(+), 27 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/.gitignore b/.gitignore index a7f8c26..539988a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,3 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.settings -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* /.gradle/ /.settings/ .idea/ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5c2d1cf016b3885f6930543d57b744ea8c220a1a GIT binary patch literal 55616 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 Date: Thu, 28 Nov 2019 11:56:11 -0700 Subject: [PATCH 05/12] Fix possible null pointer when shooting arrows into portals --- .../esophose/playerparticles/styles/ParticleStyleArrows.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleArrows.java b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleArrows.java index 7f69732..74f699e 100644 --- a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleArrows.java +++ b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleArrows.java @@ -27,7 +27,7 @@ public class ParticleStyleArrows implements ParticleStyle, Listener { int count = 0; for (int i = this.arrows.size() - 1; i >= 0; i--) { // Loop backwards so the last-fired arrows are the ones that have particles if they go over the max Projectile arrow = this.arrows.get(i); - if (((Player) arrow.getShooter()).getUniqueId().equals(particle.getOwnerUniqueId())) { + if (arrow.getShooter() != null && ((Player) arrow.getShooter()).getUniqueId().equals(particle.getOwnerUniqueId())) { particles.add(new PParticle(arrow.getLocation(), 0.05F, 0.05F, 0.05F, 0.0F)); count++; } @@ -45,7 +45,7 @@ public class ParticleStyleArrows implements ParticleStyle, Listener { public void updateTimers() { for (int i = this.arrows.size() - 1; i >= 0; i--) { Projectile arrow = this.arrows.get(i); - if (arrow.getTicksLived() >= 1200 || arrow.isDead() || !arrow.isValid()) + if (arrow.getTicksLived() >= 1200 || arrow.isDead() || !arrow.isValid() || arrow.getShooter() == null) this.arrows.remove(i); } } From 0ede905feff987bae793efeaf6f26d5f1d9035be Mon Sep 17 00:00:00 2001 From: Esophose Date: Mon, 9 Dec 2019 12:04:06 -0700 Subject: [PATCH 06/12] WIP: Refactoring by removing statics and moving all managers --- build.gradle | 1 + .../playerparticles/PlayerParticles.java | 247 +++----- .../api/PlayerParticlesAPI.java | 7 + .../command/AddCommandModule.java | 26 +- .../command/CommandModule.java | 6 +- .../command/DataCommandModule.java | 13 +- .../command/DefaultCommandModule.java | 10 +- .../command/EditCommandModule.java | 24 +- .../command/EffectsCommandModule.java | 7 +- .../command/FixedCommandModule.java | 82 +-- .../command/GUICommandModule.java | 25 +- .../command/GroupCommandModule.java | 40 +- .../command/HelpCommandModule.java | 9 +- .../command/ListCommandModule.java | 4 +- .../command/OtherCommandModule.java | 35 +- .../command/ParticleCommandHandler.java | 168 ------ .../command/ReloadCommandModule.java | 10 +- .../command/RemoveCommandModule.java | 13 +- .../command/ResetCommandModule.java | 7 +- .../command/StylesCommandModule.java | 7 +- .../command/ToggleCommandModule.java | 7 +- .../command/VersionCommandModule.java | 6 +- .../command/WorldsCommandModule.java | 10 +- .../config/CommentedConfigurationSection.java | 378 ++++++++++++ .../config/CommentedFileConfiguration.java | 91 +++ .../CommentedFileConfigurationHelper.java | 223 +++++++ .../database/DataMigration.java | 31 + .../database/DatabaseConnector.java | 10 +- ...baseConnector.java => MySQLConnector.java} | 32 +- .../database/SQLiteConnector.java | 59 ++ .../database/SqliteDatabaseConnector.java | 49 -- .../migrations/_1_InitialMigration.java | 91 +++ .../playerparticles/gui/GuiInventory.java | 4 +- .../gui/GuiInventoryDefault.java | 38 +- .../gui/GuiInventoryEditData.java | 13 +- .../gui/GuiInventoryEditEffect.java | 8 +- .../gui/GuiInventoryEditParticle.java | 30 +- .../gui/GuiInventoryEditStyle.java | 8 +- .../gui/GuiInventoryLoadPresetGroups.java | 16 +- .../gui/GuiInventoryManageGroups.java | 22 +- .../gui/GuiInventoryManageParticles.java | 28 +- .../gui/hook/PlayerChatHook.java | 4 +- .../gui/hook/PlayerChatHookData.java | 15 +- .../playerparticles/locale/EnglishLocale.java | 303 ++++++++++ .../playerparticles/locale/FrenchLocale.java | 302 ++++++++++ .../playerparticles/locale/GermanLocale.java | 302 ++++++++++ .../playerparticles/locale/Locale.java | 13 + .../playerparticles/locale/RussianLocale.java | 287 ++++++++++ .../locale/VietnameseLocale.java | 302 ++++++++++ .../manager/CommandManager.java | 208 +++++++ .../manager/ConfigurationManager.java | 216 +++++++ .../playerparticles/manager/DataManager.java | 176 +++--- .../manager/DataMigrationManager.java | 115 ++++ .../GuiManager.java} | 60 +- .../playerparticles/manager/LangManager.java | 542 ------------------ .../manager/LocaleManager.java | 137 +++++ .../playerparticles/manager/Manager.java | 23 + .../manager/ParticleGroupPresetManager.java | 57 +- .../manager/ParticleManager.java | 136 +++-- .../manager/ParticleStyleManager.java | 102 ++++ .../manager/PermissionManager.java | 132 +++-- .../manager/PluginUpdateManager.java | 61 ++ .../manager/SettingManager.java | 22 +- .../particles/PPlayerMovementListener.java | 18 +- .../particles/ParticleEffect.java | 7 +- .../particles/ParticleGroup.java | 9 +- .../particles/ParticleGroupPreset.java | 9 +- .../particles/ParticlePair.java | 11 +- .../playerparticles/styles/DefaultStyles.java | 92 ++- .../styles/ParticleStyleBlockBreak.java | 24 +- .../styles/ParticleStyleBlockPlace.java | 7 +- .../styles/ParticleStyleCelebration.java | 53 +- .../styles/ParticleStyleHurt.java | 9 +- .../styles/ParticleStyleMove.java | 7 +- .../styles/ParticleStyleSwords.java | 11 +- .../styles/api/ParticleStyle.java | 4 +- .../styles/api/ParticleStyleManager.java | 94 --- .../playerparticles/updater/DataUpdater.java | 82 --- .../updater/PluginUpdateListener.java | 28 - .../playerparticles/util/Metrics.java | 2 +- .../playerparticles/util/NMSUtil.java | 4 +- .../playerparticles/util/ParticleUtils.java | 12 +- .../util/StringPlaceholders.java | 79 +++ .../{updater => util}/Updater.java | 2 +- src/main/resources/config.yml | 11 +- src/main/resources/lang/vi_VN.lang | 2 +- 86 files changed, 4210 insertions(+), 1777 deletions(-) create mode 100644 src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java delete mode 100644 src/main/java/dev/esophose/playerparticles/command/ParticleCommandHandler.java create mode 100644 src/main/java/dev/esophose/playerparticles/config/CommentedConfigurationSection.java create mode 100644 src/main/java/dev/esophose/playerparticles/config/CommentedFileConfiguration.java create mode 100644 src/main/java/dev/esophose/playerparticles/config/CommentedFileConfigurationHelper.java create mode 100644 src/main/java/dev/esophose/playerparticles/database/DataMigration.java rename src/main/java/dev/esophose/playerparticles/database/{MySqlDatabaseConnector.java => MySQLConnector.java} (52%) create mode 100644 src/main/java/dev/esophose/playerparticles/database/SQLiteConnector.java delete mode 100644 src/main/java/dev/esophose/playerparticles/database/SqliteDatabaseConnector.java create mode 100644 src/main/java/dev/esophose/playerparticles/database/migrations/_1_InitialMigration.java create mode 100644 src/main/java/dev/esophose/playerparticles/locale/EnglishLocale.java create mode 100644 src/main/java/dev/esophose/playerparticles/locale/FrenchLocale.java create mode 100644 src/main/java/dev/esophose/playerparticles/locale/GermanLocale.java create mode 100644 src/main/java/dev/esophose/playerparticles/locale/Locale.java create mode 100644 src/main/java/dev/esophose/playerparticles/locale/RussianLocale.java create mode 100644 src/main/java/dev/esophose/playerparticles/locale/VietnameseLocale.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/CommandManager.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/ConfigurationManager.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/DataMigrationManager.java rename src/main/java/dev/esophose/playerparticles/{gui/GuiHandler.java => manager/GuiManager.java} (77%) delete mode 100644 src/main/java/dev/esophose/playerparticles/manager/LangManager.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/LocaleManager.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/Manager.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/ParticleStyleManager.java create mode 100644 src/main/java/dev/esophose/playerparticles/manager/PluginUpdateManager.java delete mode 100644 src/main/java/dev/esophose/playerparticles/styles/api/ParticleStyleManager.java delete mode 100644 src/main/java/dev/esophose/playerparticles/updater/DataUpdater.java delete mode 100644 src/main/java/dev/esophose/playerparticles/updater/PluginUpdateListener.java create mode 100644 src/main/java/dev/esophose/playerparticles/util/StringPlaceholders.java rename src/main/java/dev/esophose/playerparticles/{updater => util}/Updater.java (99%) diff --git a/build.gradle b/build.gradle index 3b507dc..b0c8147 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ plugins { sourceCompatibility = 1.8 targetCompatibility = 1.8 +compileJava.options.encoding = 'UTF-8' group = 'dev.esophose.playerparticles' version = '7.0' diff --git a/src/main/java/dev/esophose/playerparticles/PlayerParticles.java b/src/main/java/dev/esophose/playerparticles/PlayerParticles.java index e997821..e1106bb 100644 --- a/src/main/java/dev/esophose/playerparticles/PlayerParticles.java +++ b/src/main/java/dev/esophose/playerparticles/PlayerParticles.java @@ -15,170 +15,108 @@ package dev.esophose.playerparticles; -import java.io.File; - -import dev.esophose.playerparticles.command.ParticleCommandHandler; -import dev.esophose.playerparticles.database.DatabaseConnector; -import dev.esophose.playerparticles.database.MySqlDatabaseConnector; -import dev.esophose.playerparticles.database.SqliteDatabaseConnector; -import dev.esophose.playerparticles.gui.GuiHandler; import dev.esophose.playerparticles.gui.hook.PlayerChatHook; -import dev.esophose.playerparticles.manager.LangManager; -import dev.esophose.playerparticles.manager.ParticleGroupPresetManager; +import dev.esophose.playerparticles.manager.ConfigurationManager; +import dev.esophose.playerparticles.manager.DataMigrationManager; +import dev.esophose.playerparticles.manager.Manager; import dev.esophose.playerparticles.manager.ParticleManager; -import dev.esophose.playerparticles.manager.SettingManager; -import dev.esophose.playerparticles.manager.SettingManager.PSetting; +import dev.esophose.playerparticles.manager.PluginUpdateManager; +import dev.esophose.playerparticles.manager.SettingManager.Setting; import dev.esophose.playerparticles.particles.PPlayerMovementListener; -import dev.esophose.playerparticles.styles.DefaultStyles; -import dev.esophose.playerparticles.updater.DataUpdater; -import dev.esophose.playerparticles.updater.PluginUpdateListener; -import dev.esophose.playerparticles.updater.Updater; import dev.esophose.playerparticles.util.Metrics; +import java.io.File; +import java.util.HashMap; +import java.util.Map; import org.bukkit.Bukkit; -import org.bukkit.command.PluginCommand; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; +/** + * @author Esophose + */ public class PlayerParticles extends JavaPlugin { /** * The running instance of PlayerParticles on the server */ - private static PlayerParticles pluginInstance; + private static PlayerParticles INSTANCE; - /** - * The version a new update has, will be null if the config has it disabled - * or if there is no new version + /* + * The plugin managers */ - private String updateVersion = null; - - /** - * The database connection manager - */ - private DatabaseConnector databaseConnector = null; - - /** - * The task that spawns the particles - */ - private BukkitTask particleTask = null; + private Map, Manager> managers; /** * Executes essential tasks for starting up the plugin */ + @Override public void onEnable() { - pluginInstance = (PlayerParticles) Bukkit.getServer().getPluginManager().getPlugin("PlayerParticles"); - - this.registerCommands(); + INSTANCE = this; PluginManager pm = Bukkit.getPluginManager(); - pm.registerEvents(new ParticleManager(), this); - pm.registerEvents(new PluginUpdateListener(), this); - pm.registerEvents(new GuiHandler(), this); pm.registerEvents(new PPlayerMovementListener(), this); pm.registerEvents(new PlayerChatHook(), this); - this.saveDefaultConfig(); - double configVersion = PSetting.VERSION.getDouble(); - double currentVersion = Double.parseDouble(this.getDescription().getVersion()); - boolean updatePluginSettings = configVersion < currentVersion; - if (updatePluginSettings) { - this.configureDatabase(PSetting.DATABASE_ENABLE.getBoolean()); - DataUpdater.updateData(configVersion, currentVersion); - this.databaseConnector.closeConnection(); - this.databaseConnector = null; - - File configFile = new File(this.getDataFolder(), "config.yml"); - if (configFile.exists()) { - configFile.delete(); - } - this.saveDefaultConfig(); - this.reloadConfig(); - this.getLogger().warning("The config.yml has been updated to v" + this.getDescription().getVersion() + "!"); - } - - if (PSetting.CHECK_UPDATES.getBoolean()) { - Bukkit.getScheduler().runTaskAsynchronously(this, () -> { - try { // This can throw an exception if the server has no internet connection or if the Curse API is down - Updater updater = new Updater(pluginInstance, 82823, getFile(), Updater.UpdateType.NO_DOWNLOAD, false); - if (Double.parseDouble(updater.getLatestName().replaceAll("PlayerParticles v", "")) > Double.parseDouble(getPlugin().getDescription().getVersion())) { - updateVersion = updater.getLatestName().replaceAll("PlayerParticles v", ""); - getLogger().info("An update (v" + updateVersion + ") is available! You are running v" + getPlugin().getDescription().getVersion()); - } - } catch (Exception e) { - getLogger().warning("An error occurred checking for an update. There is either no established internet connection or the Curse API is down."); - } - }); - } + this.managers = new HashMap<>(); - if (PSetting.SEND_METRICS.getBoolean()) { + if (Setting.SEND_METRICS.getBoolean()) new Metrics(this); - } - - this.reload(updatePluginSettings); + + this.reload(); } - /** - * Clean up database connection if it's open - * Close all users with an open PlayerParticles GUI - */ + @Override public void onDisable() { - this.databaseConnector.closeConnection(); - GuiHandler.forceCloseAllOpenGUIs(); + this.managers.values().forEach(Manager::disable); + this.managers.clear(); } - - /** - * Registers the commands for the plugin - */ - private void registerCommands() { - ParticleCommandHandler pch = new ParticleCommandHandler(); - PluginCommand pp = this.getCommand("pp"); - PluginCommand ppo = this.getCommand("ppo"); - if (pp == null || ppo == null) { - Bukkit.getPluginManager().disablePlugin(this); - return; + /** + * Gets a manager instance + * + * @param managerClass The class of the manager instance to get + * @param The manager type + * @return The manager instance or null if one does not exist + */ + @SuppressWarnings("unchecked") + public T getManager(Class managerClass) { + if (this.managers.containsKey(managerClass)) + return (T) this.managers.get(managerClass); + + try { + T manager = managerClass.getConstructor(this.getClass()).newInstance(this); + this.managers.put(managerClass, manager); + manager.reload(); + return manager; + } catch (ReflectiveOperationException ex) { + ex.printStackTrace(); + return null; } - - pp.setTabCompleter(pch); - pp.setExecutor(pch); - ppo.setTabCompleter(pch); - ppo.setExecutor(pch); + } + + /** + * Returns the file which contains this plugin + * Exposes the JavaPlugin.getFile() method + * + * @return File containing this plugin + */ + public File getJarFile() { + return this.getFile(); } /** - * Reloads the settings of the plugin - * - * @param updatePluginSettings True if the settings should be updated to the latest version of the plugin + * Reloads the plugin */ - public void reload(boolean updatePluginSettings) { - this.reloadConfig(); - - // If not null, plugin is already loaded - if (this.particleTask != null) { - this.particleTask.cancel(); - this.particleTask = null; - this.databaseConnector.closeConnection(); - this.databaseConnector = null; - GuiHandler.forceCloseAllOpenGUIs(); - } else { - DefaultStyles.registerStyles(); // Only ever load styles once - } - - // This runs before the SettingManager is reloaded, the credentials will not be stored in memory for more than a few milliseconds - this.configureDatabase(PSetting.DATABASE_ENABLE.getBoolean()); - DataUpdater.tryCreateTables(); - - SettingManager.reload(); - LangManager.reload(updatePluginSettings); - ParticleGroupPresetManager.reload(); - - GuiHandler.setup(); + public void reload() { + this.managers.values().forEach(Manager::disable); + this.managers.clear(); + + this.getManager(ConfigurationManager.class); + this.getManager(DataMigrationManager.class); + this.getManager(PluginUpdateManager.class); + this.getManager(ParticleManager.class); + PlayerChatHook.setup(); - - ParticleManager.refreshData(); - this.startParticleTask(); } /** @@ -186,61 +124,8 @@ public class PlayerParticles extends JavaPlugin { * * @return The PlayerParticles plugin instance */ - public static PlayerParticles getPlugin() { - return pluginInstance; - } - - /** - * Gets the DatabaseConnector that allows querying the database - * - * @return The DatabaseConnector - */ - public DatabaseConnector getDBConnector() { - return this.databaseConnector; - } - - /** - * Gets the latest available version of the plugin - * Will be null if no update is available - * - * @return The latest version available for the plugin - */ - public String getUpdateVersion() { - return this.updateVersion; - } - - /** - * Determines if we should use MySQL or SQLite based on the useMySql parameter and if we were able to connect successfully - * - * @param useMySql If we should use MySQL as the database type, if false, uses SQLite - */ - private void configureDatabase(boolean useMySql) { - if (useMySql) { - this.databaseConnector = new MySqlDatabaseConnector(); - } else { - try { - Class.forName("org.sqlite.JDBC"); // This is required to put here for Spigot 1.9 and 1.10 for some reason - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - this.databaseConnector = new SqliteDatabaseConnector(this.getDataFolder().getAbsolutePath()); - } - - if (!this.databaseConnector.isInitialized()) { - this.getLogger().severe("Unable to connect to the MySQL database! Is your login information correct? Falling back to SQLite database instead."); - this.configureDatabase(false); - } - } - - /** - * Starts the task responsible for spawning particles - * Run in the synchronous task so it starts after all plugins have loaded, including extensions - */ - private void startParticleTask() { - Bukkit.getScheduler().runTaskLater(pluginInstance, () -> { - long ticks = PSetting.TICKS_PER_PARTICLE.getLong(); - this.particleTask = new ParticleManager().runTaskTimer(pluginInstance, 5, ticks); - }, 1); + public static PlayerParticles getInstance() { + return INSTANCE; } } diff --git a/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java b/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java new file mode 100644 index 0000000..16d317e --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java @@ -0,0 +1,7 @@ +package dev.esophose.playerparticles.api; + +public final class PlayerParticlesAPI { + + + +} diff --git a/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java index 991267a..d02311a 100644 --- a/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java @@ -3,8 +3,9 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.styles.api.ParticleStyle; -import dev.esophose.playerparticles.styles.api.ParticleStyleManager; +import dev.esophose.playerparticles.manager.ParticleStyleManager; import dev.esophose.playerparticles.util.ParticleUtils; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -29,8 +30,10 @@ public class AddCommandModule implements CommandModule { CommandModule.printUsage(pplayer, this); return; } + + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); - int maxParticlesAllowed = PermissionManager.getMaxParticlesAllowed(pplayer.getPlayer()); + int maxParticlesAllowed = permissionManager.getMaxParticlesAllowed(pplayer.getPlayer()); if (pplayer.getActiveParticles().size() >= maxParticlesAllowed) { LangManager.sendMessage(pplayer, Lang.ADD_REACHED_MAX, maxParticlesAllowed); return; @@ -40,7 +43,7 @@ public class AddCommandModule implements CommandModule { if (effect == null) { LangManager.sendMessage(pplayer, Lang.EFFECT_INVALID, args[0]); return; - } else if (!PermissionManager.hasEffectPermission(pplayer.getPlayer(), effect)) { + } else if (!permissionManager.hasEffectPermission(pplayer.getPlayer(), effect)) { LangManager.sendMessage(pplayer, Lang.EFFECT_NO_PERMISSION, effect.getName()); return; } @@ -49,7 +52,7 @@ public class AddCommandModule implements CommandModule { if (style == null) { LangManager.sendMessage(pplayer, Lang.STYLE_INVALID, args[1]); return; - } else if (!PermissionManager.hasStylePermission(pplayer.getPlayer(), style)) { + } else if (!permissionManager.hasStylePermission(pplayer.getPlayer(), style)) { LangManager.sendMessage(pplayer, Lang.STYLE_NO_PERMISSION, args[1]); return; } @@ -131,11 +134,11 @@ public class AddCommandModule implements CommandModule { ParticleGroup group = pplayer.getActiveParticleGroup(); ParticlePair newParticle = new ParticlePair(pplayer.getUniqueId(), pplayer.getNextActiveParticleId(), effect, style, itemData, blockData, colorData, noteColorData); group.getParticles().add(newParticle); - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), group); LangManager.sendMessage(pplayer, Lang.ADD_PARTICLE_APPLIED, newParticle.getEffect().getName(), newParticle.getStyle().getName(), newParticle.getDataString()); - if (ParticleStyleManager.isCustomHandled(newParticle.getStyle())) { + if (PlayerParticles.getInstance().getManager(ParticleStyleManager.class).isCustomHandled(newParticle.getStyle())) { LangManager.sendMessage(pplayer, Lang.STYLE_EVENT_SPAWNING_INFO, newParticle.getStyle().getName()); } } @@ -143,12 +146,13 @@ public class AddCommandModule implements CommandModule { public List onTabComplete(PPlayer pplayer, String[] args) { Player p = pplayer.getPlayer(); List matches = new ArrayList<>(); + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); if (args.length <= 1) { // Effect name - if (args.length == 0) matches = PermissionManager.getEffectNamesUserHasPermissionFor(p); - else StringUtil.copyPartialMatches(args[0], PermissionManager.getEffectNamesUserHasPermissionFor(p), matches); + if (args.length == 0) matches = permissionManager.getEffectNamesUserHasPermissionFor(p); + else StringUtil.copyPartialMatches(args[0], permissionManager.getEffectNamesUserHasPermissionFor(p), matches); } else if (args.length == 2) { // Style name - StringUtil.copyPartialMatches(args[1], PermissionManager.getStyleNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[1], permissionManager.getStyleNamesUserHasPermissionFor(p), matches); } else { // Data ParticleEffect effect = ParticleEffect.fromName(args[0]); if (effect != null) { @@ -191,8 +195,8 @@ public class AddCommandModule implements CommandModule { return "add"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_ADD; + public String getDescriptionKey() { + return "command-description-add"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/CommandModule.java b/src/main/java/dev/esophose/playerparticles/command/CommandModule.java index 6b67758..e4846fa 100644 --- a/src/main/java/dev/esophose/playerparticles/command/CommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/CommandModule.java @@ -33,11 +33,11 @@ public interface CommandModule { String getName(); /** - * Gets the Lang description of this command + * Gets the locale description key of this command * - * @return The description of this command + * @return The locale description key of this command */ - Lang getDescription(); + String getDescriptionKey(); /** * Gets any arguments this command has diff --git a/src/main/java/dev/esophose/playerparticles/command/DataCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/DataCommandModule.java index 18264e9..0e842b9 100644 --- a/src/main/java/dev/esophose/playerparticles/command/DataCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/DataCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import org.bukkit.util.StringUtil; import dev.esophose.playerparticles.manager.LangManager; @@ -43,10 +44,14 @@ public class DataCommandModule implements CommandModule { } public List onTabComplete(PPlayer pplayer, String[] args) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); List matches = new ArrayList<>(); if (args.length <= 1) { - if (args.length == 0) matches = PermissionManager.getEffectNamesUserHasPermissionFor(pplayer.getPlayer()); - else StringUtil.copyPartialMatches(args[0], PermissionManager.getEffectNamesUserHasPermissionFor(pplayer.getPlayer()), matches); + if (args.length == 0) { + matches = permissionManager.getEffectNamesUserHasPermissionFor(pplayer.getPlayer()); + } else { + StringUtil.copyPartialMatches(args[0], permissionManager.getEffectNamesUserHasPermissionFor(pplayer.getPlayer()), matches); + } } return matches; } @@ -55,8 +60,8 @@ public class DataCommandModule implements CommandModule { return "data"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_DATA; + public String getDescriptionKey() { + return "command-description-data"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/DefaultCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/DefaultCommandModule.java index 1d74de1..1d8847e 100644 --- a/src/main/java/dev/esophose/playerparticles/command/DefaultCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/DefaultCommandModule.java @@ -3,6 +3,8 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; +import dev.esophose.playerparticles.manager.CommandManager; import org.bukkit.util.StringUtil; import dev.esophose.playerparticles.manager.LangManager.Lang; @@ -12,12 +14,12 @@ public class DefaultCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { // The default command just opens the GUI, execute the GUICommandModule - ParticleCommandHandler.findMatchingCommand("gui").onCommandExecute(pplayer, new String[] { "_byDefault_" }); + PlayerParticles.getInstance().getManager(CommandManager.class).findMatchingCommand("gui").onCommandExecute(pplayer, new String[] { "_byDefault_" }); } public List onTabComplete(PPlayer pplayer, String[] args) { List matches = new ArrayList<>(); - List commandNames = ParticleCommandHandler.getCommandNames(); + List commandNames = PlayerParticles.getInstance().getManager(CommandManager.class).getCommandNames(); if (args.length == 0) return commandNames; @@ -30,8 +32,8 @@ public class DefaultCommandModule implements CommandModule { return ""; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_DEFAULT; + public String getDescriptionKey() { + return "command-description-default"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java index b873cdd..375f74c 100644 --- a/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.styles.api.ParticleStyle; import dev.esophose.playerparticles.util.ParticleUtils; import org.bukkit.Material; @@ -78,7 +79,7 @@ public class EditCommandModule implements CommandModule { if (effect == null) { LangManager.sendMessage(pplayer, Lang.EFFECT_INVALID, args[0]); return; - } else if (!PermissionManager.hasEffectPermission(pplayer.getPlayer(), effect)) { + } else if (!PlayerParticles.getInstance().getManager(PermissionManager.class).hasEffectPermission(pplayer.getPlayer(), effect)) { LangManager.sendMessage(pplayer, Lang.EFFECT_NO_PERMISSION, effect.getName()); return; } @@ -91,7 +92,7 @@ public class EditCommandModule implements CommandModule { } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), group); LangManager.sendMessage(pplayer, Lang.EDIT_SUCCESS_EFFECT, id, effect.getName()); } @@ -107,7 +108,7 @@ public class EditCommandModule implements CommandModule { if (style == null) { LangManager.sendMessage(pplayer, Lang.STYLE_INVALID, args[0]); return; - } else if (!PermissionManager.hasStylePermission(pplayer.getPlayer(), style)) { + } else if (!PlayerParticles.getInstance().getManager(PermissionManager.class).hasStylePermission(pplayer.getPlayer(), style)) { LangManager.sendMessage(pplayer, Lang.STYLE_NO_PERMISSION, style.getName()); return; } @@ -119,8 +120,8 @@ public class EditCommandModule implements CommandModule { break; } } - - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), group); LangManager.sendMessage(pplayer, Lang.EDIT_SUCCESS_STYLE, id, style.getName()); } @@ -218,12 +219,13 @@ public class EditCommandModule implements CommandModule { break; } } - - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), group); LangManager.sendMessage(pplayer, Lang.EDIT_SUCCESS_DATA, id, updatedDataString); } public List onTabComplete(PPlayer pplayer, String[] args) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); Player p = pplayer.getPlayer(); List matches = new ArrayList<>(); List ids = new ArrayList<>(); @@ -253,11 +255,11 @@ public class EditCommandModule implements CommandModule { switch (args[1].toLowerCase()) { case "effect": if (args.length == 3) - StringUtil.copyPartialMatches(args[2], PermissionManager.getEffectNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[2], permissionManager.getEffectNamesUserHasPermissionFor(p), matches); break; case "style": if (args.length == 3) - StringUtil.copyPartialMatches(args[2], PermissionManager.getStyleNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[2], permissionManager.getStyleNamesUserHasPermissionFor(p), matches); break; case "data": ParticleEffect effect = pplayer.getActiveParticle(id).getEffect(); @@ -302,8 +304,8 @@ public class EditCommandModule implements CommandModule { return "edit"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_EDIT; + public String getDescriptionKey() { + return "command-description-edit"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/EffectsCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/EffectsCommandModule.java index c4ba7d6..633dc59 100644 --- a/src/main/java/dev/esophose/playerparticles/command/EffectsCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/EffectsCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import org.bukkit.entity.Player; import dev.esophose.playerparticles.manager.LangManager; @@ -15,7 +16,7 @@ public class EffectsCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { Player p = pplayer.getPlayer(); - List effectList = PermissionManager.getEffectNamesUserHasPermissionFor(p); + List effectList = PlayerParticles.getInstance().getManager(PermissionManager.class).getEffectNamesUserHasPermissionFor(p); if (effectList.isEmpty()) { LangManager.sendMessage(pplayer, Lang.EFFECT_LIST_EMPTY); return; @@ -41,8 +42,8 @@ public class EffectsCommandModule implements CommandModule { return "effects"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_EFFECTS; + public String getDescriptionKey() { + return "command-description-effects"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java index 0d79a89..fd29759 100644 --- a/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java @@ -1,21 +1,6 @@ package dev.esophose.playerparticles.command; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import dev.esophose.playerparticles.styles.api.ParticleStyle; -import dev.esophose.playerparticles.util.ParticleUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; - +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; @@ -28,13 +13,28 @@ import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.styles.api.ParticleStyle; +import dev.esophose.playerparticles.util.ParticleUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class FixedCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { Player p = pplayer.getPlayer(); - if (!PermissionManager.canUseFixedEffects(p)) { + if (!PlayerParticles.getInstance().getManager(PermissionManager.class).canUseFixedEffects(p)) { LangManager.sendMessage(pplayer, Lang.FIXED_NO_PERMISSION); return; } @@ -92,7 +92,8 @@ public class FixedCommandModule implements CommandModule { * @param args The command arguments */ private void handleCreate(PPlayer pplayer, Player p, String[] args) { - boolean reachedMax = PermissionManager.hasPlayerReachedMaxFixedEffects(pplayer); + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); + boolean reachedMax = permissionManager.hasPlayerReachedMaxFixedEffects(pplayer); if (reachedMax) { LangManager.sendMessage(pplayer, Lang.FIXED_MAX_REACHED); return; @@ -155,7 +156,7 @@ public class FixedCommandModule implements CommandModule { } double distanceFromEffect = p.getLocation().distance(new Location(p.getWorld(), xPos, yPos, zPos)); - int maxCreationDistance = PermissionManager.getMaxFixedEffectCreationDistance(); + int maxCreationDistance = permissionManager.getMaxFixedEffectCreationDistance(); if (maxCreationDistance != 0 && distanceFromEffect > maxCreationDistance) { LangManager.sendMessage(pplayer, Lang.FIXED_CREATE_OUT_OF_RANGE, maxCreationDistance); return; @@ -165,7 +166,7 @@ public class FixedCommandModule implements CommandModule { if (effect == null) { LangManager.sendMessage(pplayer, Lang.FIXED_CREATE_EFFECT_INVALID, args[3]); return; - } else if (!PermissionManager.hasEffectPermission(p, effect)) { + } else if (!permissionManager.hasEffectPermission(p, effect)) { LangManager.sendMessage(pplayer, Lang.FIXED_CREATE_EFFECT_NO_PERMISSION, effect.getName()); return; } @@ -174,7 +175,7 @@ public class FixedCommandModule implements CommandModule { if (style == null) { LangManager.sendMessage(pplayer, Lang.FIXED_CREATE_STYLE_INVALID, args[4]); return; - } else if (!PermissionManager.hasStylePermission(p, style)) { + } else if (!permissionManager.hasStylePermission(p, style)) { LangManager.sendMessage(pplayer, Lang.FIXED_CREATE_STYLE_NO_PERMISSION, args[4]); return; } @@ -271,7 +272,7 @@ public class FixedCommandModule implements CommandModule { FixedParticleEffect fixedEffect = new FixedParticleEffect(p.getUniqueId(), nextFixedEffectId, p.getLocation().getWorld().getName(), xPos, yPos, zPos, particle); LangManager.sendMessage(pplayer, Lang.FIXED_CREATE_SUCCESS); - DataManager.saveFixedEffect(fixedEffect); + PlayerParticles.getInstance().getManager(DataManager.class).saveFixedEffect(fixedEffect); } /** @@ -282,6 +283,8 @@ public class FixedCommandModule implements CommandModule { * @param args The command arguments */ private void handleEdit(PPlayer pplayer, Player p, String[] args) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); + if (args.length < 3) { LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_MISSING_ARGS); return; @@ -348,7 +351,7 @@ public class FixedCommandModule implements CommandModule { } double distanceFromEffect = p.getLocation().distance(new Location(p.getWorld(), xPos, yPos, zPos)); - int maxCreationDistance = PermissionManager.getMaxFixedEffectCreationDistance(); + int maxCreationDistance = permissionManager.getMaxFixedEffectCreationDistance(); if (maxCreationDistance != 0 && distanceFromEffect > maxCreationDistance) { LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_OUT_OF_RANGE, maxCreationDistance); return; @@ -361,7 +364,7 @@ public class FixedCommandModule implements CommandModule { if (effect == null) { LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_EFFECT_INVALID, args[2]); return; - } else if (!PermissionManager.hasEffectPermission(pplayer.getPlayer(), effect)) { + } else if (!permissionManager.hasEffectPermission(pplayer.getPlayer(), effect)) { LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_EFFECT_NO_PERMISSION, effect.getName()); return; } @@ -374,7 +377,7 @@ public class FixedCommandModule implements CommandModule { if (style == null) { LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_STYLE_INVALID, args[2]); return; - } else if (!PermissionManager.hasStylePermission(pplayer.getPlayer(), style)) { + } else if (!permissionManager.hasStylePermission(pplayer.getPlayer(), style)) { LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_STYLE_NO_PERMISSION, style.getName()); return; } else if (!style.canBeFixed()) { @@ -480,7 +483,7 @@ public class FixedCommandModule implements CommandModule { return; } - DataManager.updateFixedEffect(fixedEffect); + PlayerParticles.getInstance().getManager(DataManager.class).updateFixedEffect(fixedEffect); LangManager.sendMessage(pplayer, Lang.FIXED_EDIT_SUCCESS, editType, id); } @@ -506,7 +509,7 @@ public class FixedCommandModule implements CommandModule { } if (pplayer.getFixedEffectById(id) != null) { - DataManager.removeFixedEffect(pplayer.getUniqueId(), id); + PlayerParticles.getInstance().getManager(DataManager.class).removeFixedEffect(pplayer.getUniqueId(), id); LangManager.sendMessage(pplayer, Lang.FIXED_REMOVE_SUCCESS, id); } else { LangManager.sendMessage(pplayer, Lang.FIXED_REMOVE_INVALID, id); @@ -591,7 +594,11 @@ public class FixedCommandModule implements CommandModule { * @param args The command arguments */ private void handleClear(PPlayer pplayer, Player p, String[] args) { - if (!PermissionManager.canClearFixedEffects(p)) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); + ParticleManager particleManager = PlayerParticles.getInstance().getManager(ParticleManager.class); + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + + if (!permissionManager.canClearFixedEffects(p)) { LangManager.sendMessage(pplayer, Lang.FIXED_CLEAR_NO_PERMISSION); return; } @@ -611,18 +618,19 @@ public class FixedCommandModule implements CommandModule { ArrayList fixedEffectsToRemove = new ArrayList<>(); - for (PPlayer ppl : ParticleManager.getPPlayers()) + for (PPlayer ppl : particleManager.getPPlayers()) for (FixedParticleEffect fixedEffect : ppl.getFixedParticles()) - if (fixedEffect.getLocation().getWorld().equals(p.getLocation().getWorld()) && fixedEffect.getLocation().distance(p.getLocation()) <= radius) + if (fixedEffect.getLocation().getWorld() == p.getLocation().getWorld() && fixedEffect.getLocation().distance(p.getLocation()) <= radius) fixedEffectsToRemove.add(fixedEffect); for (FixedParticleEffect fixedEffect : fixedEffectsToRemove) - DataManager.removeFixedEffect(fixedEffect.getOwnerUniqueId(), fixedEffect.getId()); + dataManager.removeFixedEffect(fixedEffect.getOwnerUniqueId(), fixedEffect.getId()); LangManager.sendMessage(pplayer, Lang.FIXED_CLEAR_SUCCESS, fixedEffectsToRemove.size(), radius); } public List onTabComplete(PPlayer pplayer, String[] args) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); Player p = pplayer.getPlayer(); List matches = new ArrayList<>(); @@ -657,9 +665,9 @@ public class FixedCommandModule implements CommandModule { } if (args.length == 5) { - StringUtil.copyPartialMatches(args[4], PermissionManager.getEffectNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[4], permissionManager.getEffectNamesUserHasPermissionFor(p), matches); } else if (args.length == 6) { - StringUtil.copyPartialMatches(args[5], PermissionManager.getFixableStyleNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[5], permissionManager.getFixableStyleNamesUserHasPermissionFor(p), matches); } else if (args.length >= 7) { ParticleEffect effect = ParticleEffect.fromName(args[4]); if (effect != null) { @@ -717,9 +725,9 @@ public class FixedCommandModule implements CommandModule { } StringUtil.copyPartialMatches(args[args.length - 1], possibleValues, matches); } else if (property.equals("effect") && args.length == 4) { - StringUtil.copyPartialMatches(args[3], PermissionManager.getEffectNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[3], permissionManager.getEffectNamesUserHasPermissionFor(p), matches); } else if (property.equals("style") && args.length == 4) { - StringUtil.copyPartialMatches(args[3], PermissionManager.getFixableStyleNamesUserHasPermissionFor(p), matches); + StringUtil.copyPartialMatches(args[3], permissionManager.getFixableStyleNamesUserHasPermissionFor(p), matches); } else if (property.equals("data")) { int id = -1; try { @@ -781,8 +789,8 @@ public class FixedCommandModule implements CommandModule { return "fixed"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_FIXED; + public String getDescriptionKey() { + return "command-description-fixed"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/GUICommandModule.java b/src/main/java/dev/esophose/playerparticles/command/GUICommandModule.java index ae28180..237fb45 100644 --- a/src/main/java/dev/esophose/playerparticles/command/GUICommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/GUICommandModule.java @@ -1,15 +1,16 @@ package dev.esophose.playerparticles.command; +import dev.esophose.playerparticles.PlayerParticles; +import dev.esophose.playerparticles.manager.GuiManager; +import dev.esophose.playerparticles.manager.LangManager; +import dev.esophose.playerparticles.manager.LangManager.Lang; +import dev.esophose.playerparticles.manager.PermissionManager; +import dev.esophose.playerparticles.manager.SettingManager.Setting; +import dev.esophose.playerparticles.particles.PPlayer; + import java.util.ArrayList; import java.util.List; -import dev.esophose.playerparticles.gui.GuiHandler; -import dev.esophose.playerparticles.manager.LangManager; -import dev.esophose.playerparticles.manager.LangManager.Lang; -import dev.esophose.playerparticles.manager.SettingManager.PSetting; -import dev.esophose.playerparticles.manager.PermissionManager; -import dev.esophose.playerparticles.particles.PPlayer; - public class GUICommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { @@ -18,7 +19,7 @@ public class GUICommandModule implements CommandModule { byDefault = true; } - if (GuiHandler.isGuiDisabled()) { + if (GuiManager.isGuiDisabled()) { if (byDefault) { LangManager.sendMessage(pplayer, Lang.COMMAND_ERROR_UNKNOWN); } else { @@ -27,7 +28,7 @@ public class GUICommandModule implements CommandModule { return; } - if (!PSetting.GUI_PRESETS_ONLY.getBoolean() && PermissionManager.getEffectNamesUserHasPermissionFor(pplayer.getPlayer()).isEmpty()) { + if (!Setting.GUI_PRESETS_ONLY.getBoolean() && PlayerParticles.getInstance().getManager(PermissionManager.class).getEffectNamesUserHasPermissionFor(pplayer.getPlayer()).isEmpty()) { if (byDefault) { LangManager.sendMessage(pplayer, Lang.COMMAND_ERROR_NO_EFFECTS); } else { @@ -36,7 +37,7 @@ public class GUICommandModule implements CommandModule { return; } - GuiHandler.openDefault(pplayer); + GuiManager.openDefault(pplayer); } public List onTabComplete(PPlayer pplayer, String[] args) { @@ -47,8 +48,8 @@ public class GUICommandModule implements CommandModule { return "gui"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_GUI; + public String getDescriptionKey() { + return "command-description-gui"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/GroupCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/GroupCommandModule.java index b9a14c5..d97542c 100644 --- a/src/main/java/dev/esophose/playerparticles/command/GroupCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/GroupCommandModule.java @@ -1,22 +1,22 @@ package dev.esophose.playerparticles.command; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; - +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; import dev.esophose.playerparticles.manager.LangManager; -import dev.esophose.playerparticles.manager.ParticleGroupPresetManager; import dev.esophose.playerparticles.manager.LangManager.Lang; +import dev.esophose.playerparticles.manager.ParticleGroupPresetManager; import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticleGroupPreset; import dev.esophose.playerparticles.particles.ParticlePair; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; public class GroupCommandModule implements CommandModule { @@ -87,7 +87,7 @@ public class GroupCommandModule implements CommandModule { } // Check if they are creating a new group, if they are, check that they haven't gone over their limit - if (pplayer.getParticleGroupByName(groupName) == null && PermissionManager.hasPlayerReachedMaxGroups(pplayer)) { + if (pplayer.getParticleGroupByName(groupName) == null && PlayerParticles.getInstance().getManager(PermissionManager.class).hasPlayerReachedMaxGroups(pplayer)) { LangManager.sendMessage(pplayer, Lang.GROUP_SAVE_REACHED_MAX); return; } @@ -105,7 +105,7 @@ public class GroupCommandModule implements CommandModule { } // Apply changes and notify player - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), group); if (groupUpdated) { LangManager.sendMessage(pplayer, Lang.GROUP_SAVE_SUCCESS_OVERWRITE, groupName); } else { @@ -131,7 +131,7 @@ public class GroupCommandModule implements CommandModule { ParticleGroup group = pplayer.getParticleGroupByName(groupName); if (group == null) { // Didn't find a saved group, look at the presets - ParticleGroupPreset presetGroup = ParticleGroupPresetManager.getPresetGroup(groupName); + ParticleGroupPreset presetGroup = PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroup(groupName); if (presetGroup == null) { LangManager.sendMessage(pplayer, Lang.GROUP_INVALID, groupName); return; @@ -158,7 +158,7 @@ public class GroupCommandModule implements CommandModule { activeGroup.getParticles().add(particle.clone()); // Update group and notify player - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), activeGroup); if (!isPreset) LangManager.sendMessage(pplayer, Lang.GROUP_LOAD_SUCCESS, activeGroup.getParticles().size(), groupName); @@ -182,7 +182,7 @@ public class GroupCommandModule implements CommandModule { ParticleGroup group = pplayer.getParticleGroupByName(groupName); if (group == null) { // Didn't find a saved group, look at the presets - ParticleGroupPreset presetGroup = ParticleGroupPresetManager.getPresetGroup(groupName); + ParticleGroupPreset presetGroup = PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroup(groupName); if (presetGroup == null) { LangManager.sendMessage(pplayer, Lang.GROUP_INVALID, groupName); @@ -193,7 +193,7 @@ public class GroupCommandModule implements CommandModule { } // Delete the group and notify player - DataManager.removeParticleGroup(pplayer.getUniqueId(), group); + PlayerParticles.getInstance().getManager(DataManager.class).removeParticleGroup(pplayer.getUniqueId(), group); LangManager.sendMessage(pplayer, Lang.GROUP_REMOVE_SUCCESS, groupName); } @@ -213,7 +213,7 @@ public class GroupCommandModule implements CommandModule { ParticleGroup group = pplayer.getParticleGroupByName(groupName); if (group == null) { // Didn't find a saved group, look at the presets - ParticleGroupPreset presetGroup = ParticleGroupPresetManager.getPresetGroup(groupName); + ParticleGroupPreset presetGroup = PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroup(groupName); if (presetGroup == null) { LangManager.sendMessage(pplayer, Lang.GROUP_INVALID, groupName); return; @@ -254,7 +254,7 @@ public class GroupCommandModule implements CommandModule { groupsList = new StringBuilder(groupsList.substring(0, groupsList.length() - 2)); StringBuilder presetsList = new StringBuilder(); - for (ParticleGroupPreset group : ParticleGroupPresetManager.getPresetGroupsForPlayer(pplayer.getPlayer())) + for (ParticleGroupPreset group : PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroupsForPlayer(pplayer.getPlayer())) presetsList.append(group.getGroup().getName()).append(", "); if (presetsList.toString().endsWith(", ")) @@ -290,7 +290,7 @@ public class GroupCommandModule implements CommandModule { if (!group.getName().equals(ParticleGroup.DEFAULT_NAME)) groupNames.add(group.getName()); if (!args[0].equals("remove")) - for (ParticleGroupPreset group : ParticleGroupPresetManager.getPresetGroupsForPlayer(pplayer.getPlayer())) + for (ParticleGroupPreset group : PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroupsForPlayer(pplayer.getPlayer())) groupNames.add(group.getGroup().getName()); StringUtil.copyPartialMatches(args[1], groupNames, matches); } @@ -303,8 +303,8 @@ public class GroupCommandModule implements CommandModule { return "group"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_GROUP; + public String getDescriptionKey() { + return "command-description-group"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/HelpCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/HelpCommandModule.java index 483cb78..135be59 100644 --- a/src/main/java/dev/esophose/playerparticles/command/HelpCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/HelpCommandModule.java @@ -3,15 +3,18 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; +import dev.esophose.playerparticles.manager.CommandManager; +import dev.esophose.playerparticles.manager.ParticleGroupPresetManager; import dev.esophose.playerparticles.particles.PPlayer; public class HelpCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { LangManager.sendMessage(pplayer, Lang.COMMAND_DESCRIPTIONS); - List cmds = ParticleCommandHandler.getCommands(); + List cmds = PlayerParticles.getInstance().getManager(CommandManager.class).getCommands(); for (CommandModule cmd : cmds) if (!(cmd instanceof DefaultCommandModule)) CommandModule.printUsageWithDescription(pplayer, cmd); @@ -26,8 +29,8 @@ public class HelpCommandModule implements CommandModule { return "help"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_HELP; + public String getDescriptionKey() { + return "command-description-help"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/ListCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/ListCommandModule.java index e339866..48f372a 100644 --- a/src/main/java/dev/esophose/playerparticles/command/ListCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/ListCommandModule.java @@ -38,8 +38,8 @@ public class ListCommandModule implements CommandModule { return "list"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_LIST; + public String getDescriptionKey() { + return "command-description-list"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/OtherCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/OtherCommandModule.java index 6db1829..f4506b9 100644 --- a/src/main/java/dev/esophose/playerparticles/command/OtherCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/OtherCommandModule.java @@ -1,25 +1,28 @@ package dev.esophose.playerparticles.command; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; - +import dev.esophose.playerparticles.PlayerParticles; +import dev.esophose.playerparticles.manager.CommandManager; import dev.esophose.playerparticles.manager.DataManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.particles.OtherPPlayer; import dev.esophose.playerparticles.particles.PPlayer; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; public class OtherCommandModule implements CommandModuleSecondary { public void onCommandExecute(CommandSender sender, String[] args) { - if (!PermissionManager.canOverride(sender)) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); + + if (!permissionManager.canOverride(sender)) { LangManager.sendCommandSenderMessage(sender, Lang.OTHER_NO_PERMISSION); return; } @@ -35,19 +38,19 @@ public class OtherCommandModule implements CommandModuleSecondary { return; } - CommandModule commandModule = ParticleCommandHandler.findMatchingCommand(args[1]); + CommandModule commandModule = PlayerParticles.getInstance().getManager(CommandManager.class).findMatchingCommand(args[1]); if (commandModule == null) { LangManager.sendCommandSenderMessage(sender, Lang.OTHER_UNKNOWN_COMMAND, args[1]); return; } - if (commandModule.requiresEffects() && PermissionManager.getEffectNamesUserHasPermissionFor(other).isEmpty()) { + if (commandModule.requiresEffects() && permissionManager.getEffectNamesUserHasPermissionFor(other).isEmpty()) { LangManager.sendCommandSenderMessage(sender, Lang.OTHER_SUCCESS, other.getName()); LangManager.sendCommandSenderMessage(sender, Lang.COMMAND_ERROR_NO_EFFECTS); return; } - DataManager.getPPlayer(other.getUniqueId(), (pplayer) -> { + PlayerParticles.getInstance().getManager(DataManager.class).getPPlayer(other.getUniqueId(), (pplayer) -> { OtherPPlayer otherPPlayer = new OtherPPlayer(sender, pplayer); LangManager.sendCommandSenderMessage(sender, Lang.OTHER_SUCCESS, other.getName()); @@ -68,14 +71,14 @@ public class OtherCommandModule implements CommandModuleSecondary { if (args.length == 0) completions = playerNames; else StringUtil.copyPartialMatches(args[0], playerNames, completions); } else if (args.length == 2) { - List commandNames = ParticleCommandHandler.getCommandNames(); + List commandNames = PlayerParticles.getInstance().getManager(CommandManager.class).getCommandNames(); StringUtil.copyPartialMatches(args[1], commandNames, completions); } else { Player otherPlayer = Bukkit.getPlayer(args[0]); if (otherPlayer != null) { - PPlayer other = DataManager.getPPlayer(otherPlayer.getUniqueId()); + PPlayer other = PlayerParticles.getInstance().getManager(DataManager.class).getPPlayer(otherPlayer.getUniqueId()); if (other != null) { - CommandModule commandModule = ParticleCommandHandler.findMatchingCommand(args[1]); + CommandModule commandModule = PlayerParticles.getInstance().getManager(CommandManager.class).findMatchingCommand(args[1]); if (commandModule != null) { String[] cmdArgs = Arrays.copyOfRange(args, 2, args.length); completions = commandModule.onTabComplete(other, cmdArgs); diff --git a/src/main/java/dev/esophose/playerparticles/command/ParticleCommandHandler.java b/src/main/java/dev/esophose/playerparticles/command/ParticleCommandHandler.java deleted file mode 100644 index 9dda352..0000000 --- a/src/main/java/dev/esophose/playerparticles/command/ParticleCommandHandler.java +++ /dev/null @@ -1,168 +0,0 @@ -package dev.esophose.playerparticles.command; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import dev.esophose.playerparticles.particles.OtherPPlayer; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; - -import dev.esophose.playerparticles.manager.DataManager; -import dev.esophose.playerparticles.manager.LangManager; -import dev.esophose.playerparticles.manager.LangManager.Lang; -import dev.esophose.playerparticles.manager.PermissionManager; -import dev.esophose.playerparticles.particles.PPlayer; - -import net.md_5.bungee.api.ChatColor; - -public class ParticleCommandHandler implements CommandExecutor, TabCompleter { - - /** - * A list of all commands - */ - private static List commands; - private static CommandModuleSecondary ppoCommand; - - static { - commands = new ArrayList<>(); - - commands.add(new AddCommandModule()); - commands.add(new DataCommandModule()); - commands.add(new DefaultCommandModule()); - commands.add(new EditCommandModule()); - commands.add(new EffectsCommandModule()); - commands.add(new FixedCommandModule()); - commands.add(new GroupCommandModule()); - commands.add(new GUICommandModule()); - commands.add(new HelpCommandModule()); - commands.add(new ListCommandModule()); - commands.add(new ReloadCommandModule()); - commands.add(new RemoveCommandModule()); - commands.add(new ResetCommandModule()); - commands.add(new StylesCommandModule()); - commands.add(new ToggleCommandModule()); - commands.add(new VersionCommandModule()); - commands.add(new WorldsCommandModule()); - - ppoCommand = new OtherCommandModule(); - } - - /** - * Finds a matching CommandModule by its name - * - * @param commandName The command name - * @return The found CommandModule, otherwise null - */ - public static CommandModule findMatchingCommand(String commandName) { - for (CommandModule commandModule : commands) - if (commandModule.getName().equalsIgnoreCase(commandName)) - return commandModule; - return null; - } - - /** - * Get a list of all available commands - * - * @return A List of all CommandModules registered - */ - public static List getCommands() { - return commands; - } - - /** - * Get all available command names - * - * @return All available command names - */ - public static List getCommandNames() { - List commandNames = new ArrayList<>(); - for (CommandModule cmd : commands) - commandNames.add(cmd.getName()); - return commandNames; - } - - /** - * Called when a player executes a PlayerParticles command - * Checks what PlayerParticles command it is and calls the corresponding module - * - * @param sender Who executed the command - * @param cmd The command - * @param label The command label - * @param args The arguments following the command - * @return true - */ - public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { - if (cmd.getName().equalsIgnoreCase("pp")) { - String commandName = args.length > 0 ? args[0] : ""; - CommandModule commandModule = findMatchingCommand(commandName); - if (commandModule == null) { - sender.sendMessage(LangManager.getText(Lang.COMMAND_ERROR_UNKNOWN)); - return true; - } - - String[] cmdArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]; - - if (!commandModule.canConsoleExecute()) { - if (!(sender instanceof Player)) { - sender.sendMessage(ChatColor.RED + "Error: This command can only be executed by a player."); - return true; - } - } else { - commandModule.onCommandExecute(new OtherPPlayer(sender), cmdArgs); - return true; - } - - Player p = (Player) sender; - - DataManager.getPPlayer(p.getUniqueId(), (pplayer) -> { - if (commandModule.requiresEffects() && PermissionManager.getEffectNamesUserHasPermissionFor(p).isEmpty()) { - LangManager.sendMessage(pplayer, Lang.COMMAND_ERROR_NO_EFFECTS); - } else { - commandModule.onCommandExecute(pplayer, cmdArgs); - } - }); - } else if (cmd.getName().equalsIgnoreCase("ppo")) { - ppoCommand.onCommandExecute(sender, args); - } - - return true; - } - - /** - * Activated when a user pushes tab in chat prefixed with /pp - * - * @param sender The sender that hit tab, should always be a player - * @param cmd The command the player is executing - * @param alias The possible alias for the command - * @param args All arguments following the command - * @return A list of commands available to the sender - */ - public List onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) { - if (cmd.getName().equalsIgnoreCase("pp")) { - if (!(sender instanceof Player)) return new ArrayList<>(); - - PPlayer pplayer = DataManager.getPPlayer(((Player) sender).getUniqueId()); - if (pplayer == null) return new ArrayList<>(); - - if (args.length <= 1) { - CommandModule commandModule = findMatchingCommand(""); // Get the default command module - return commandModule.onTabComplete(pplayer, args); - } else { - CommandModule commandModule = findMatchingCommand(args[0]); - if (commandModule != null) { - String[] cmdArgs = Arrays.copyOfRange(args, 1, args.length); - return commandModule.onTabComplete(pplayer, cmdArgs); - } - } - } else if (cmd.getName().equalsIgnoreCase("ppo")) { - return ppoCommand.onTabComplete(sender, args); - } - - return new ArrayList<>(); - } - -} diff --git a/src/main/java/dev/esophose/playerparticles/command/ReloadCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/ReloadCommandModule.java index 7a11e35..5169650 100644 --- a/src/main/java/dev/esophose/playerparticles/command/ReloadCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/ReloadCommandModule.java @@ -12,10 +12,10 @@ import dev.esophose.playerparticles.particles.PPlayer; public class ReloadCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { - if (PermissionManager.canReloadPlugin(pplayer.getMessageDestination())) { - PlayerParticles.getPlugin().reload(false); + if (PlayerParticles.getInstance().getManager(PermissionManager.class).canReloadPlugin(pplayer.getMessageDestination())) { + PlayerParticles.getInstance().reload(); LangManager.sendMessage(pplayer, Lang.RELOAD_SUCCESS); - PlayerParticles.getPlugin().getLogger().info("Reloaded configuration."); + PlayerParticles.getInstance().getLogger().info("Reloaded configuration."); } else { LangManager.sendMessage(pplayer, Lang.RELOAD_NO_PERMISSION); } @@ -29,8 +29,8 @@ public class ReloadCommandModule implements CommandModule { return "reload"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_RELOAD; + public String getDescriptionKey() { + return "command-description-reload"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/RemoveCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/RemoveCommandModule.java index 1b1d938..d9992f0 100644 --- a/src/main/java/dev/esophose/playerparticles/command/RemoveCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/RemoveCommandModule.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.styles.api.ParticleStyle; import org.apache.commons.lang.StringUtils; import org.bukkit.util.StringUtil; @@ -20,6 +21,8 @@ import dev.esophose.playerparticles.particles.ParticlePair; public class RemoveCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + if (args.length == 0) { LangManager.sendMessage(pplayer, Lang.REMOVE_NO_ARGS); return; @@ -54,7 +57,7 @@ public class RemoveCommandModule implements CommandModule { return; } - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + dataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); LangManager.sendMessage(pplayer, Lang.REMOVE_ID_SUCCESS, id); } else { // Removing by effect/style name ParticleEffect effect = ParticleEffect.fromName(args[0]); @@ -71,7 +74,7 @@ public class RemoveCommandModule implements CommandModule { } if (removedCount > 0) { - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + dataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); LangManager.sendMessage(pplayer, Lang.REMOVE_EFFECT_SUCCESS, removedCount, effect.getName()); } else { LangManager.sendMessage(pplayer, Lang.REMOVE_EFFECT_NONE, effect.getName()); @@ -87,7 +90,7 @@ public class RemoveCommandModule implements CommandModule { } if (removedCount > 0) { - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + dataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); LangManager.sendMessage(pplayer, Lang.REMOVE_STYLE_SUCCESS, removedCount, style.getName()); } else { LangManager.sendMessage(pplayer, Lang.REMOVE_STYLE_NONE, style.getName()); @@ -118,8 +121,8 @@ public class RemoveCommandModule implements CommandModule { return "remove"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_REMOVE; + public String getDescriptionKey() { + return "command-description-remove"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/ResetCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/ResetCommandModule.java index ce67131..bfa8cf2 100644 --- a/src/main/java/dev/esophose/playerparticles/command/ResetCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/ResetCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; @@ -13,7 +14,7 @@ public class ResetCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { int particleCount = pplayer.getActiveParticles().size(); - DataManager.saveParticleGroup(pplayer.getUniqueId(), ParticleGroup.getDefaultGroup()); + PlayerParticles.getInstance().getManager(DataManager.class).saveParticleGroup(pplayer.getUniqueId(), ParticleGroup.getDefaultGroup()); LangManager.sendMessage(pplayer, Lang.RESET_SUCCESS, particleCount); } @@ -25,8 +26,8 @@ public class ResetCommandModule implements CommandModule { return "reset"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_RESET; + public String getDescriptionKey() { + return "command-description-reset"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/StylesCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/StylesCommandModule.java index 897c9b0..a060fca 100644 --- a/src/main/java/dev/esophose/playerparticles/command/StylesCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/StylesCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import org.bukkit.entity.Player; import dev.esophose.playerparticles.manager.LangManager; @@ -15,7 +16,7 @@ public class StylesCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { Player p = pplayer.getPlayer(); - List styleNames = PermissionManager.getStyleNamesUserHasPermissionFor(p); + List styleNames = PlayerParticles.getInstance().getManager(PermissionManager.class).getStyleNamesUserHasPermissionFor(p); StringBuilder toSend = new StringBuilder(); for (String name : styleNames) { toSend.append(name).append(", "); @@ -36,8 +37,8 @@ public class StylesCommandModule implements CommandModule { return "styles"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_STYLES; + public String getDescriptionKey() { + return "command-description-styles"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/ToggleCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/ToggleCommandModule.java index 7f58b0d..cb73c22 100644 --- a/src/main/java/dev/esophose/playerparticles/command/ToggleCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/ToggleCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; @@ -12,7 +13,7 @@ public class ToggleCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { boolean canSee = pplayer.canSeeParticles(); - DataManager.updateSettingParticlesHidden(pplayer.getUniqueId(), canSee); + PlayerParticles.getInstance().getManager(DataManager.class).updateSettingParticlesHidden(pplayer.getUniqueId(), canSee); if (canSee) { LangManager.sendMessage(pplayer, Lang.TOGGLE_OFF); @@ -29,8 +30,8 @@ public class ToggleCommandModule implements CommandModule { return "toggle"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_TOGGLE; + public String getDescriptionKey() { + return "command-description-toggle"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/VersionCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/VersionCommandModule.java index 7b92e5a..4326110 100644 --- a/src/main/java/dev/esophose/playerparticles/command/VersionCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/VersionCommandModule.java @@ -13,7 +13,7 @@ import dev.esophose.playerparticles.particles.PPlayer; public class VersionCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { - LangManager.sendCustomMessage(pplayer, ChatColor.YELLOW + "Running PlayerParticles " + ChatColor.AQUA + "v" + PlayerParticles.getPlugin().getDescription().getVersion()); + LangManager.sendCustomMessage(pplayer, ChatColor.YELLOW + "Running PlayerParticles " + ChatColor.AQUA + "v" + PlayerParticles.getInstance().getDescription().getVersion()); LangManager.sendCustomMessage(pplayer, ChatColor.YELLOW + "Plugin created by: " + ChatColor.AQUA + "Esophose"); } @@ -25,8 +25,8 @@ public class VersionCommandModule implements CommandModule { return "version"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_VERSION; + public String getDescriptionKey() { + return "command-description-version"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/command/WorldsCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/WorldsCommandModule.java index eec1824..311997b 100644 --- a/src/main/java/dev/esophose/playerparticles/command/WorldsCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/WorldsCommandModule.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.command; import java.util.ArrayList; import java.util.List; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.PermissionManager; @@ -11,13 +12,14 @@ import dev.esophose.playerparticles.particles.PPlayer; public class WorldsCommandModule implements CommandModule { public void onCommandExecute(PPlayer pplayer, String[] args) { - if (PermissionManager.getDisabledWorlds() == null || PermissionManager.getDisabledWorlds().isEmpty()) { + PermissionManager permissionManager = PlayerParticles.getInstance().getManager(PermissionManager.class); + if (permissionManager.getDisabledWorlds() == null || permissionManager.getDisabledWorlds().isEmpty()) { LangManager.sendMessage(pplayer, Lang.DISABLED_WORLDS_NONE); return; } StringBuilder worlds = new StringBuilder(); - for (String s : PermissionManager.getDisabledWorlds()) { + for (String s : permissionManager.getDisabledWorlds()) { worlds.append(s).append(", "); } if (worlds.length() > 2) worlds = new StringBuilder(worlds.substring(0, worlds.length() - 2)); @@ -33,8 +35,8 @@ public class WorldsCommandModule implements CommandModule { return "worlds"; } - public Lang getDescription() { - return Lang.COMMAND_DESCRIPTION_WORLDS; + public String getDescriptionKey() { + return "command-description-worlds"; } public String getArguments() { diff --git a/src/main/java/dev/esophose/playerparticles/config/CommentedConfigurationSection.java b/src/main/java/dev/esophose/playerparticles/config/CommentedConfigurationSection.java new file mode 100644 index 0000000..8c69c41 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/config/CommentedConfigurationSection.java @@ -0,0 +1,378 @@ +package dev.esophose.playerparticles.config; + +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class CommentedConfigurationSection implements ConfigurationSection { + + protected ConfigurationSection config; + + public CommentedConfigurationSection(ConfigurationSection configuration) { + this.config = configuration; + } + + /** + * Gets a defaulted boolean value. These accept values of either "default", true, or false + * + * @param path The value key + * @return null for "default", otherwise true or false + */ + public Boolean getDefaultedBoolean(String path) { + if (this.isBoolean(path)) { + return this.getBoolean(path); + } else if (this.isString(path)) { + String stringValue = this.getString(path); + if (stringValue != null && stringValue.equalsIgnoreCase("default")) + return null; + } + + return null; + } + + /** + * Gets a defaulted boolean value. These accept values of either "default", true, or false + * + * @param path The value key + * @param def The value to return if the key is not found + * @return null for "default", otherwise true or false + */ + public Boolean getDefaultedBoolean(String path, Boolean def) { + Object value = this.get(path); + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { + String stringValue = (String) value; + if (stringValue.equalsIgnoreCase("default")) + return null; + } + + if (value == null) + return def; + + return null; + } + + @Override + public Set getKeys(boolean b) { + return this.config.getKeys(b); + } + + @Override + public Map getValues(boolean b) { + return this.config.getValues(b); + } + + @Override + public boolean contains(String s) { + return this.config.contains(s); + } + + @Override + public boolean contains(String s, boolean b) { + return this.config.contains(s, b); + } + + @Override + public boolean isSet(String s) { + return this.config.isSet(s); + } + + @Override + public String getCurrentPath() { + return this.config.getCurrentPath(); + } + + @Override + public String getName() { + return this.config.getName(); + } + + @Override + public Configuration getRoot() { + return this.config.getRoot(); + } + + @Override + public ConfigurationSection getParent() { + return this.config.getParent(); + } + + @Override + public Object get(String s) { + return this.config.get(s); + } + + @Override + public Object get(String s, Object o) { + return this.config.get(s, o); + } + + @Override + public void set(String s, Object o) { + this.config.set(s, o); + } + + @Override + public CommentedConfigurationSection createSection(String s) { + return new CommentedConfigurationSection(this.config.createSection(s)); + } + + @Override + public CommentedConfigurationSection createSection(String s, Map map) { + return new CommentedConfigurationSection(this.config.createSection(s, map)); + } + + @Override + public String getString(String s) { + return this.config.getString(s); + } + + @Override + public String getString(String s, String s1) { + return this.config.getString(s, s1); + } + + @Override + public boolean isString(String s) { + return this.config.isString(s); + } + + @Override + public int getInt(String s) { + return this.config.getInt(s); + } + + @Override + public int getInt(String s, int i) { + return this.config.getInt(s, i); + } + + @Override + public boolean isInt(String s) { + return this.config.isInt(s); + } + + @Override + public boolean getBoolean(String s) { + return this.config.getBoolean(s); + } + + @Override + public boolean getBoolean(String s, boolean b) { + return this.config.getBoolean(s, b); + } + + @Override + public boolean isBoolean(String s) { + return this.config.isBoolean(s); + } + + @Override + public double getDouble(String s) { + return this.config.getDouble(s); + } + + @Override + public double getDouble(String s, double v) { + return this.config.getDouble(s, v); + } + + @Override + public boolean isDouble(String s) { + return this.config.isDouble(s); + } + + @Override + public long getLong(String s) { + return this.config.getLong(s); + } + + @Override + public long getLong(String s, long l) { + return this.config.getLong(s, l); + } + + @Override + public boolean isLong(String s) { + return this.config.isLong(s); + } + + @Override + public List getList(String s) { + return this.config.getList(s); + } + + @Override + public List getList(String s, List list) { + return this.config.getList(s, list); + } + + @Override + public boolean isList(String s) { + return this.config.isList(s); + } + + @Override + public List getStringList(String s) { + return this.config.getStringList(s); + } + + @Override + public List getIntegerList(String s) { + return this.config.getIntegerList(s); + } + + @Override + public List getBooleanList(String s) { + return this.config.getBooleanList(s); + } + + @Override + public List getDoubleList(String s) { + return this.config.getDoubleList(s); + } + + @Override + public List getFloatList(String s) { + return this.config.getFloatList(s); + } + + @Override + public List getLongList(String s) { + return this.config.getLongList(s); + } + + @Override + public List getByteList(String s) { + return this.config.getByteList(s); + } + + @Override + public List getCharacterList(String s) { + return this.config.getCharacterList(s); + } + + @Override + public List getShortList(String s) { + return this.config.getShortList(s); + } + + @Override + public List> getMapList(String s) { + return this.config.getMapList(s); + } + + @Override + public T getObject(String s, Class aClass) { + return this.config.getObject(s, aClass); + } + + @Override + public T getObject(String s, Class aClass, T t) { + return this.config.getObject(s, aClass, t); + } + + @Override + public T getSerializable(String s, Class aClass) { + return this.config.getSerializable(s, aClass); + } + + @Override + public T getSerializable(String s, Class aClass, T t) { + return this.config.getSerializable(s, aClass, t); + } + + @Override + public Vector getVector(String s) { + return this.config.getVector(s); + } + + @Override + public Vector getVector(String s, Vector vector) { + return this.config.getVector(s, vector); + } + + @Override + public boolean isVector(String s) { + return this.config.isVector(s); + } + + @Override + public OfflinePlayer getOfflinePlayer(String s) { + return this.config.getOfflinePlayer(s); + } + + @Override + public OfflinePlayer getOfflinePlayer(String s, OfflinePlayer offlinePlayer) { + return this.config.getOfflinePlayer(s, offlinePlayer); + } + + @Override + public boolean isOfflinePlayer(String s) { + return this.config.isOfflinePlayer(s); + } + + @Override + public ItemStack getItemStack(String s) { + return this.config.getItemStack(s); + } + + @Override + public ItemStack getItemStack(String s, ItemStack itemStack) { + return this.config.getItemStack(s, itemStack); + } + + @Override + public boolean isItemStack(String s) { + return this.config.isItemStack(s); + } + + @Override + public Color getColor(String s) { + return this.config.getColor(s); + } + + @Override + public Color getColor(String s, Color color) { + return this.config.getColor(s, color); + } + + @Override + public boolean isColor(String s) { + return this.config.isColor(s); + } + + @Override + public CommentedConfigurationSection getConfigurationSection(String s) { + ConfigurationSection section = this.config.getConfigurationSection(s); + if (section == null) + return this.createSection(s); + + return new CommentedConfigurationSection(section); + } + + @Override + public boolean isConfigurationSection(String s) { + return this.config.isConfigurationSection(s); + } + + @Override + public CommentedConfigurationSection getDefaultSection() { + return new CommentedConfigurationSection(this.config.getDefaultSection()); + } + + @Override + public void addDefault(String s, Object o) { + this.config.addDefault(s, o); + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/config/CommentedFileConfiguration.java b/src/main/java/dev/esophose/playerparticles/config/CommentedFileConfiguration.java new file mode 100644 index 0000000..18fe25a --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/config/CommentedFileConfiguration.java @@ -0,0 +1,91 @@ +package dev.esophose.playerparticles.config; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; +import org.yaml.snakeyaml.DumperOptions; + +import java.io.File; +import java.io.Reader; +import java.lang.reflect.Field; + +public class CommentedFileConfiguration extends CommentedConfigurationSection { + + private int comments; + private CommentedFileConfigurationHelper helper; + private File file; + + public CommentedFileConfiguration(Reader configStream, File configFile, int comments, JavaPlugin plugin) { + super(YamlConfiguration.loadConfiguration(configStream)); + this.comments = comments; + this.helper = new CommentedFileConfigurationHelper(plugin); + this.file = configFile; + } + + public static CommentedFileConfiguration loadConfiguration(JavaPlugin plugin, File file) { + return new CommentedFileConfigurationHelper(plugin).getNewConfig(file); + } + + public void set(String path, Object value, String... comments) { + if (!this.contains(path)) { + int subpathIndex = path.lastIndexOf('.'); + String subpath = subpathIndex == -1 ? "" : path.substring(0, subpathIndex) + '.'; + + for (String comment : comments) { + this.set(subpath + this.helper.getPluginName() + "_COMMENT_" + this.comments, " " + comment); + this.comments++; + } + } + + this.set(path, value); + } + + public void addComments(String... comments) { + for (String comment : comments) { + this.set(this.helper.getPluginName() + "_COMMENT_" + this.comments, " " + comment); + this.comments++; + } + } + + public void reloadConfig() { + this.config = YamlConfiguration.loadConfiguration(this.helper.getConfigContent(this.file)); + } + + public void save() { + this.save(false); + } + + public void save(boolean compactLines) { + String config = this.getConfigAsString(); + this.helper.saveConfig(config, this.file, compactLines); + } + + public void save(File file) { + this.save(file, false); + } + + public void save(File file, boolean compactLines) { + String config = this.getConfigAsString(); + this.helper.saveConfig(config, file, compactLines); + } + + private String getConfigAsString() { + if (!(this.config instanceof YamlConfiguration)) + throw new UnsupportedOperationException("Cannot get config string of non-YamlConfiguration"); + + YamlConfiguration yamlConfiguration = (YamlConfiguration) this.config; + + // Edit the configuration to how we want it + try { + Field field_yamlOptions = YamlConfiguration.class.getDeclaredField("yamlOptions"); + field_yamlOptions.setAccessible(true); + DumperOptions yamlOptions = (DumperOptions) field_yamlOptions.get(yamlConfiguration); + yamlOptions.setWidth(Integer.MAX_VALUE); + yamlOptions.setIndicatorIndent(2); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + + return yamlConfiguration.saveToString(); + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/config/CommentedFileConfigurationHelper.java b/src/main/java/dev/esophose/playerparticles/config/CommentedFileConfigurationHelper.java new file mode 100644 index 0000000..8035651 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/config/CommentedFileConfigurationHelper.java @@ -0,0 +1,223 @@ +package dev.esophose.playerparticles.config; + +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CommentedFileConfigurationHelper { + + private JavaPlugin plugin; + + /** + * Manage custom configurations and files + */ + public CommentedFileConfigurationHelper(JavaPlugin plugin) { + this.plugin = plugin; + } + + /** + * Get new configuration + * + * @param file - Path to file + * @return - New SimpleConfig + */ + public CommentedFileConfiguration getNewConfig(File file) { + if (!this.plugin.getDataFolder().exists()) + this.plugin.getDataFolder().mkdir(); + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return new CommentedFileConfiguration(this.getConfigContent(file), file, this.getCommentsNum(file), this.plugin); + } + + /** + * Read file and make comments SnakeYAML friendly + * + * @param file - Path to file + * @return - File as Input Stream + */ + public Reader getConfigContent(File file) { + if (!file.exists()) + return new InputStreamReader(new ByteArrayInputStream(new byte[0])); + + try { + int commentNum = 0; + + String pluginName = this.getPluginName(); + + StringBuilder whole = new StringBuilder(); + BufferedReader reader = new BufferedReader(new FileReader(file)); + + String currentLine; + while ((currentLine = reader.readLine()) != null) { + // Convert comments into keys + if (currentLine.trim().startsWith("#")) { + String addLine = currentLine.replaceAll(Pattern.quote("'"), Matcher.quoteReplacement("''")) + .replaceFirst("#", pluginName + "_COMMENT_" + commentNum++ + ": '") + "'"; + whole.append(addLine).append("\n"); + } else { + whole.append(currentLine).append("\n"); + } + } + + String config = whole.toString(); + Reader configStream = new InputStreamReader(new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8))); + + reader.close(); + return configStream; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Get comments from file + * + * @param file - File + * @return - Comments number + */ + private int getCommentsNum(File file) { + if (!file.exists()) + return 0; + + try { + int comments = 0; + String currentLine; + + BufferedReader reader = new BufferedReader(new FileReader(file)); + + while ((currentLine = reader.readLine()) != null) + if (currentLine.trim().startsWith("#")) + comments++; + + reader.close(); + return comments; + } catch (IOException e) { + e.printStackTrace(); + return 0; + } + } + + private String prepareConfigString(String configString) { + boolean lastLine = false; + + String[] lines = configString.split("\n"); + StringBuilder config = new StringBuilder(); + + for (String line : lines) { + if (line.trim().startsWith(this.getPluginName() + "_COMMENT")) { + int whitespaceIndex = line.indexOf(line.trim()); + String comment = line.substring(0, whitespaceIndex) + "#" + line.substring(line.indexOf(":") + 3, line.length() - 1); + + String normalComment; + if (comment.trim().startsWith("#'")) { + normalComment = comment.substring(0, comment.length() - 1).replaceFirst("#'", "# "); + } else { + normalComment = comment; + } + + normalComment = normalComment.replaceAll("''", "'"); + + if (!lastLine) { + config.append(normalComment).append("\n"); + } else { + config.append("\n").append(normalComment).append("\n"); + } + + lastLine = false; + } else { + config.append(line).append("\n"); + lastLine = true; + } + } + + return config.toString(); + } + + /** + * Saves configuration to file + * + * @param configString - Config string + * @param file - Config file + * @param compactLines - If lines should forcefully be separated by only one newline character + */ + public void saveConfig(String configString, File file, boolean compactLines) { + String configuration = this.prepareConfigString(configString).replaceAll("\n\n", "\n"); + + // Apply post-processing to config string to make it pretty + StringBuilder stringBuilder = new StringBuilder(); + try (Scanner scanner = new Scanner(configuration)) { + boolean lastLineHadContent = false; + int lastCommentSpacing = -1; + int lastLineSpacing = -1; + boolean forceCompact = false; + + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + + boolean lineHadContent = false; + boolean lineWasComment = false; + int commentSpacing = -1; + int lineSpacing = line.indexOf(line.trim()); + + if (line.trim().startsWith("#")) { + lineWasComment = true; + String trimmed = line.trim().replaceFirst("#", ""); + commentSpacing = trimmed.indexOf(trimmed.trim()); + } else if (!line.trim().isEmpty()) { + lineHadContent = true; + + if (line.trim().startsWith("-")) + forceCompact = true; + } + + if (!compactLines && !forceCompact && ((lastLineSpacing != -1 && lineSpacing != lastLineSpacing) + || (commentSpacing != -1 && commentSpacing < lastCommentSpacing) + || (lastLineHadContent && lineHadContent) + || (lineWasComment && lastLineHadContent))) { + stringBuilder.append('\n'); + } + + stringBuilder.append(line).append('\n'); + + lastLineHadContent = lineHadContent; + lastCommentSpacing = commentSpacing; + lastLineSpacing = lineSpacing; + forceCompact = false; + } + } + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(file)); + writer.write(stringBuilder.toString()); + writer.flush(); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public String getPluginName() { + return this.plugin.getDescription().getName(); + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/database/DataMigration.java b/src/main/java/dev/esophose/playerparticles/database/DataMigration.java new file mode 100644 index 0000000..24f95dd --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/database/DataMigration.java @@ -0,0 +1,31 @@ +package dev.esophose.playerparticles.database; + +import java.sql.Connection; +import java.sql.SQLException; + +public abstract class DataMigration { + + private final int revision; + + public DataMigration(int revision) { + this.revision = revision; + } + + /** + * Migrates the database to this migration stage + * + * @param connector The connector for the database + * @param connection The connection to the database + * @param tablePrefix The prefix of the database + * @throws SQLException Any error that occurs during the SQL execution + */ + public abstract void migrate(DatabaseConnector connector, Connection connection, String tablePrefix) throws SQLException; + + /** + * @return the revision number of this migration + */ + public int getRevision() { + return this.revision; + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/database/DatabaseConnector.java b/src/main/java/dev/esophose/playerparticles/database/DatabaseConnector.java index 5ab1013..b94fa03 100644 --- a/src/main/java/dev/esophose/playerparticles/database/DatabaseConnector.java +++ b/src/main/java/dev/esophose/playerparticles/database/DatabaseConnector.java @@ -7,8 +7,8 @@ public interface DatabaseConnector { /** * Checks if the connection to the database has been created - * - * @return If the connection is created or not + * + * @return true if the connection is created, otherwise false */ boolean isInitialized(); @@ -19,16 +19,16 @@ public interface DatabaseConnector { /** * Executes a callback with a Connection passed and automatically closes it when finished - * + * * @param callback The callback to execute once the connection is retrieved */ void connect(ConnectionCallback callback); /** - * Allows Lambda expressions to be used to reduce duplicated code for getting connections + * Wraps a connection in a callback which will automagically handle catching sql errors */ interface ConnectionCallback { - void execute(Connection connection) throws SQLException; + void accept(Connection connection) throws SQLException; } } diff --git a/src/main/java/dev/esophose/playerparticles/database/MySqlDatabaseConnector.java b/src/main/java/dev/esophose/playerparticles/database/MySQLConnector.java similarity index 52% rename from src/main/java/dev/esophose/playerparticles/database/MySqlDatabaseConnector.java rename to src/main/java/dev/esophose/playerparticles/database/MySQLConnector.java index b353656..6288ba0 100644 --- a/src/main/java/dev/esophose/playerparticles/database/MySqlDatabaseConnector.java +++ b/src/main/java/dev/esophose/playerparticles/database/MySQLConnector.java @@ -1,31 +1,26 @@ package dev.esophose.playerparticles.database; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.bukkit.plugin.Plugin; + import java.sql.Connection; import java.sql.SQLException; -import dev.esophose.playerparticles.PlayerParticles; -import dev.esophose.playerparticles.manager.SettingManager.PSetting; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; - -public class MySqlDatabaseConnector implements DatabaseConnector { +public class MySQLConnector implements DatabaseConnector { + private final Plugin plugin; private HikariDataSource hikari; private boolean initializedSuccessfully; - public MySqlDatabaseConnector() { - String hostname = PSetting.DATABASE_HOSTNAME.getString(); - String port = PSetting.DATABASE_PORT.getString(); - String database = PSetting.DATABASE_NAME.getString(); - String user = PSetting.DATABASE_USER_NAME.getString(); - String pass = PSetting.DATABASE_USER_PASSWORD.getString(); - boolean useSSL = PSetting.DATABASE_USE_SSL.getBoolean(); + public MySQLConnector(Plugin plugin, String hostname, int port, String database, String username, String password, boolean useSSL) { + this.plugin = plugin; HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://" + hostname + ":" + port + "/" + database + "?useSSL=" + useSSL); - config.setUsername(user); - config.setPassword(pass); - config.setMaximumPoolSize(3); + config.setUsername(username); + config.setPassword(password); + config.setMaximumPoolSize(5); try { this.hikari = new HikariDataSource(config); @@ -45,9 +40,10 @@ public class MySqlDatabaseConnector implements DatabaseConnector { public void connect(ConnectionCallback callback) { try (Connection connection = this.hikari.getConnection()) { - callback.execute(connection); + callback.accept(connection); } catch (SQLException ex) { - PlayerParticles.getPlugin().getLogger().severe("An error occurred retrieving a MySQL database connection: " + ex.getMessage()); + this.plugin.getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage()); + ex.printStackTrace(); } } diff --git a/src/main/java/dev/esophose/playerparticles/database/SQLiteConnector.java b/src/main/java/dev/esophose/playerparticles/database/SQLiteConnector.java new file mode 100644 index 0000000..c773d88 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/database/SQLiteConnector.java @@ -0,0 +1,59 @@ +package dev.esophose.playerparticles.database; + +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class SQLiteConnector implements DatabaseConnector { + + private final Plugin plugin; + private final String connectionString; + private Connection connection; + + public SQLiteConnector(Plugin plugin) { + this.plugin = plugin; + this.connectionString = "jdbc:sqlite:" + plugin.getDataFolder() + File.separator + plugin.getDescription().getName().toLowerCase() + ".db"; + + try { + Class.forName("org.sqlite.JDBC"); // This is required to put here for Spigot 1.10 and below for some reason + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public boolean isInitialized() { + return true; // Always available + } + + public void closeConnection() { + try { + if (this.connection != null) { + this.connection.close(); + } + } catch (SQLException ex) { + this.plugin.getLogger().severe("An error occurred closing the SQLite database connection: " + ex.getMessage()); + } + } + + public void connect(ConnectionCallback callback) { + if (this.connection == null) { + try { + this.connection = DriverManager.getConnection(this.connectionString); + } catch (SQLException ex) { + this.plugin.getLogger().severe("An error occurred retrieving the SQLite database connection: " + ex.getMessage()); + } + } + + try { + callback.accept(this.connection); + } catch (Exception ex) { + this.plugin.getLogger().severe("An error occurred executing an SQLite query: " + ex.getMessage()); + ex.printStackTrace(); + } + } + +} + diff --git a/src/main/java/dev/esophose/playerparticles/database/SqliteDatabaseConnector.java b/src/main/java/dev/esophose/playerparticles/database/SqliteDatabaseConnector.java deleted file mode 100644 index d233b62..0000000 --- a/src/main/java/dev/esophose/playerparticles/database/SqliteDatabaseConnector.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.esophose.playerparticles.database; - -import java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -import dev.esophose.playerparticles.PlayerParticles; - -public class SqliteDatabaseConnector implements DatabaseConnector { - - private final String connectionString; - private Connection connection; - - public SqliteDatabaseConnector(String directory) { - this.connectionString = "jdbc:sqlite:" + directory + File.separator + "playerparticles.db"; - } - - public boolean isInitialized() { - return true; // Always available - } - - public void closeConnection() { - try { - if (this.connection != null) { - this.connection.close(); - } - } catch (SQLException ex) { - PlayerParticles.getPlugin().getLogger().severe("An error occurred closing the SQLite database connection: " + ex.getMessage()); - } - } - - public void connect(ConnectionCallback callback) { - if (this.connection == null) { - try { - this.connection = DriverManager.getConnection(this.connectionString); - } catch (SQLException ex) { - PlayerParticles.getPlugin().getLogger().severe("An error occurred retrieving the SQLite database connection: " + ex.getMessage()); - } - } - - try { - callback.execute(this.connection); - } catch (SQLException ex) { - PlayerParticles.getPlugin().getLogger().severe("An error occurred retrieving the SQLite database connection: " + ex.getMessage()); - } - } - -} diff --git a/src/main/java/dev/esophose/playerparticles/database/migrations/_1_InitialMigration.java b/src/main/java/dev/esophose/playerparticles/database/migrations/_1_InitialMigration.java new file mode 100644 index 0000000..6a58a14 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/database/migrations/_1_InitialMigration.java @@ -0,0 +1,91 @@ +package dev.esophose.playerparticles.database.migrations; + +import dev.esophose.playerparticles.database.DataMigration; +import dev.esophose.playerparticles.database.DatabaseConnector; +import dev.esophose.playerparticles.database.SQLiteConnector; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public class _1_InitialMigration extends DataMigration { + + public _1_InitialMigration() { + super(1); + } + + @Override + public void migrate(DatabaseConnector connector, Connection connection, String tablePrefix) throws SQLException { + // Check if we are still running legacy data + boolean hasLegacy; + String legacyQuery; + if (connector instanceof SQLiteConnector) { + legacyQuery = "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?"; + } else { + legacyQuery = "SHOW TABLES LIKE ?"; + } + + try (PreparedStatement statement = connection.prepareStatement(legacyQuery)) { + statement.setString(1, "pp_data_note"); + hasLegacy = statement.executeQuery().next(); + } + + if (hasLegacy) { + // Drop legacy tables + try (Statement statement = connection.createStatement()) { + statement.addBatch("DROP TABLE IF EXISTS pp_users"); + statement.addBatch("DROP TABLE IF EXISTS pp_fixed"); + statement.addBatch("DROP TABLE IF EXISTS pp_data_item"); + statement.addBatch("DROP TABLE IF EXISTS pp_data_block"); + statement.addBatch("DROP TABLE IF EXISTS pp_data_color"); + statement.addBatch("DROP TABLE IF EXISTS pp_data_note"); + statement.executeBatch(); + } + } + + // Check if we are still running legacy data + boolean hasTables; + String tablesQuery; + if (connector instanceof SQLiteConnector) { + tablesQuery = "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?"; + } else { + tablesQuery = "SHOW TABLES LIKE ?"; + } + + try (PreparedStatement statement = connection.prepareStatement(tablesQuery)) { + statement.setString(1, "pp_settings"); + hasTables = statement.executeQuery().next(); + } + + // Rename the tables if they aleady exist + if (hasTables) { + try (Statement statement = connection.createStatement()) { + if (connector instanceof SQLiteConnector) { + statement.addBatch("ALTER TABLE pp_settings RENAME TO " + tablePrefix + "_settings"); + statement.addBatch("ALTER TABLE pp_particle RENAME TO " + tablePrefix + "_particle"); + statement.addBatch("ALTER TABLE pp_group RENAME TO " + tablePrefix + "_group"); + statement.addBatch("ALTER TABLE pp_fixed RENAME TO " + tablePrefix + "_fixed"); + } else { + statement.addBatch("RENAME TABLE pp_settings TO " + tablePrefix + "_settings"); + statement.addBatch("RENAME TABLE pp_particle TO " + tablePrefix + "_particle"); + statement.addBatch("RENAME TABLE pp_group TO " + tablePrefix + "_group"); + statement.addBatch("RENAME TABLE pp_fixed TO " + tablePrefix + "_fixed"); + } + + statement.executeBatch(); + } + } else { // Otherwise create them + try (Statement createStatement = connection.createStatement()) { + createStatement.addBatch("CREATE TABLE IF NOT EXISTS " + tablePrefix + "_settings (player_uuid VARCHAR(36), particles_hidden TINYINT)"); + createStatement.addBatch("CREATE TABLE IF NOT EXISTS " + tablePrefix + "_particle (uuid VARCHAR(36), group_uuid VARCHAR(36), id SMALLINT, effect VARCHAR(100), style VARCHAR(100), item_material VARCHAR(100), block_material VARCHAR(100), note SMALLINT, r SMALLINT, g SMALLINT, b SMALLINT, PRIMARY KEY(uuid))"); + createStatement.addBatch("CREATE TABLE IF NOT EXISTS " + tablePrefix + "_group (uuid VARCHAR(36), owner_uuid VARCHAR(36), name VARCHAR(100), PRIMARY KEY(uuid))"); + createStatement.addBatch("CREATE TABLE IF NOT EXISTS " + tablePrefix + "_fixed (owner_uuid VARCHAR(36), id SMALLINT, particle_uuid VARCHAR(36), world VARCHAR(100), xPos DOUBLE, yPos DOUBLE, zPos DOUBLE, PRIMARY KEY(owner_uuid, id), FOREIGN KEY(particle_uuid) REFERENCES pp_particle(uuid) ON DELETE CASCADE)"); + createStatement.executeBatch(); + } + } + + } + +} + diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java index d170575..affcb5c 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java @@ -13,7 +13,7 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import dev.esophose.playerparticles.manager.SettingManager.PSetting; +import dev.esophose.playerparticles.manager.SettingManager.Setting; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.util.ParticleUtils; @@ -154,7 +154,7 @@ public abstract class GuiInventory implements InventoryHolder { for (GuiActionButton button : this.actionButtons) { if (button.getSlot() == slot) { button.handleClick(isShiftClick); - if (PSetting.GUI_BUTTON_SOUND.getBoolean() && event.getWhoClicked() instanceof Player) { + if (Setting.GUI_BUTTON_SOUND.getBoolean() && event.getWhoClicked() instanceof Player) { Player player = (Player) event.getWhoClicked(); player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 1); } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryDefault.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryDefault.java index 653d212..7199865 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryDefault.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryDefault.java @@ -1,6 +1,8 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.ParticleGroupPresetManager; @@ -26,6 +28,8 @@ public class GuiInventoryDefault extends GuiInventory { public GuiInventoryDefault(PPlayer pplayer) { super(pplayer, Bukkit.createInventory(pplayer.getPlayer(), INVENTORY_SIZE, LangManager.getText(Lang.GUI_PLAYERPARTICLES))); + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + this.fillBorder(BorderColor.WHITE); // PPlayer information icon @@ -55,8 +59,8 @@ public class GuiInventoryDefault extends GuiInventory { this.inventory.setItem(13, headIcon); // Define what slots to put the icons at based on what other slots are visible - boolean manageGroupsVisible = PermissionManager.canPlayerSaveGroups(pplayer); - boolean loadPresetGroupVisible = !ParticleGroupPresetManager.getPresetGroupsForPlayer(pplayer.getPlayer()).isEmpty(); + boolean manageGroupsVisible = PlayerParticles.getInstance().getManager(PermissionManager.class).canPlayerSaveGroups(pplayer); + boolean loadPresetGroupVisible = !PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroupsForPlayer(pplayer.getPlayer()).isEmpty(); int manageParticlesSlot = -1, manageGroupsSlot = -1, loadPresetGroupSlot = -1; if (!manageGroupsVisible && !loadPresetGroupVisible) { @@ -79,7 +83,7 @@ public class GuiInventoryDefault extends GuiInventory { GuiIcon.PARTICLES.get(), LangManager.getText(Lang.GUI_COLOR_ICON_NAME) + LangManager.getText(Lang.GUI_MANAGE_YOUR_PARTICLES), new String[]{LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_MANAGE_YOUR_PARTICLES_DESCRIPTION)}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryManageParticles(pplayer))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryManageParticles(pplayer))); this.actionButtons.add(manageYourParticlesButton); // Manage Your Groups button @@ -89,7 +93,7 @@ public class GuiInventoryDefault extends GuiInventory { GuiIcon.GROUPS.get(), LangManager.getText(Lang.GUI_COLOR_ICON_NAME) + LangManager.getText(Lang.GUI_MANAGE_YOUR_GROUPS), new String[]{LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_MANAGE_YOUR_GROUPS_DESCRIPTION)}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryManageGroups(pplayer))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryManageGroups(pplayer))); this.actionButtons.add(manageYourGroupsButton); } @@ -100,7 +104,7 @@ public class GuiInventoryDefault extends GuiInventory { GuiIcon.PRESET_GROUPS.get(), LangManager.getText(Lang.GUI_COLOR_ICON_NAME) + LangManager.getText(Lang.GUI_LOAD_A_PRESET_GROUP), new String[]{LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_LOAD_A_PRESET_GROUP_DESCRIPTION)}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryLoadPresetGroups(pplayer, false))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryLoadPresetGroups(pplayer, false))); this.actionButtons.add(loadPresetGroups); } @@ -116,8 +120,8 @@ public class GuiInventoryDefault extends GuiInventory { new String[]{LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_EDIT_PRIMARY_EFFECT_DESCRIPTION)}, (button, isShiftClick) -> { List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryDefault(pplayer))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditEffect(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryDefault(pplayer))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditEffect(pplayer, editingParticle, 1, callbacks, 1))); callbacks.add(() -> { ParticleGroup group = pplayer.getActiveParticleGroup(); if (canEditPrimaryStyleAndData) { @@ -130,9 +134,9 @@ public class GuiInventoryDefault extends GuiInventory { } else { group.getParticles().add(editingParticle); } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); - GuiHandler.transition(new GuiInventoryDefault(pplayer)); + GuiManager.transition(new GuiInventoryDefault(pplayer)); }); callbacks.get(1).execute(); @@ -158,8 +162,8 @@ public class GuiInventoryDefault extends GuiInventory { if (!canEditPrimaryStyleAndData) return; List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryDefault(pplayer))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditStyle(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryDefault(pplayer))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditStyle(pplayer, editingParticle, 1, callbacks, 1))); callbacks.add(() -> { ParticleGroup group = pplayer.getActiveParticleGroup(); for (ParticlePair particle : group.getParticles()) { @@ -168,9 +172,9 @@ public class GuiInventoryDefault extends GuiInventory { break; } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); - GuiHandler.transition(new GuiInventoryDefault(pplayer)); + GuiManager.transition(new GuiInventoryDefault(pplayer)); }); callbacks.get(1).execute(); @@ -201,8 +205,8 @@ public class GuiInventoryDefault extends GuiInventory { if (!canEditPrimaryStyleAndData || !doesEffectUseData) return; List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryDefault(pplayer))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditData(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryDefault(pplayer))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditData(pplayer, editingParticle, 1, callbacks, 1))); callbacks.add(() -> { ParticleGroup group = pplayer.getActiveParticleGroup(); for (ParticlePair particle : group.getParticles()) { @@ -214,9 +218,9 @@ public class GuiInventoryDefault extends GuiInventory { break; } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); - GuiHandler.transition(new GuiInventoryDefault(pplayer)); + GuiManager.transition(new GuiInventoryDefault(pplayer)); }); callbacks.get(1).execute(); diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java index 823aa99..3fb7ff5 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java @@ -1,5 +1,6 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.SettingManager.GuiIcon; @@ -308,7 +309,7 @@ public class GuiInventoryEditData extends GuiInventory { GuiIcon.PREVIOUS_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_PREVIOUS_PAGE_BUTTON, pageNumber - 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); this.actionButtons.add(previousPageButton); } @@ -319,7 +320,7 @@ public class GuiInventoryEditData extends GuiInventory { GuiIcon.NEXT_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_NEXT_PAGE_BUTTON, pageNumber + 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); this.actionButtons.add(nextPageButton); } } @@ -369,7 +370,7 @@ public class GuiInventoryEditData extends GuiInventory { GuiIcon.PREVIOUS_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_PREVIOUS_PAGE_BUTTON, pageNumber - 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); this.actionButtons.add(previousPageButton); } @@ -380,7 +381,7 @@ public class GuiInventoryEditData extends GuiInventory { GuiIcon.NEXT_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_NEXT_PAGE_BUTTON, pageNumber + 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); this.actionButtons.add(nextPageButton); } } @@ -430,7 +431,7 @@ public class GuiInventoryEditData extends GuiInventory { GuiIcon.PREVIOUS_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_PREVIOUS_PAGE_BUTTON, pageNumber - 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); this.actionButtons.add(previousPageButton); } @@ -441,7 +442,7 @@ public class GuiInventoryEditData extends GuiInventory { GuiIcon.NEXT_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_NEXT_PAGE_BUTTON, pageNumber + 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditData(this.pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); this.actionButtons.add(nextPageButton); } } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditEffect.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditEffect.java index c4fea01..0f39584 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditEffect.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditEffect.java @@ -1,5 +1,7 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.PermissionManager; @@ -20,7 +22,7 @@ public class GuiInventoryEditEffect extends GuiInventory { this.fillBorder(BorderColor.LIGHT_BLUE); // Select Effect Buttons - List effectsUserHasPermissionFor = PermissionManager.getEffectsUserHasPermissionFor(pplayer.getPlayer()); + List effectsUserHasPermissionFor = PlayerParticles.getInstance().getManager(PermissionManager.class).getEffectsUserHasPermissionFor(pplayer.getPlayer()); int numberOfItems = effectsUserHasPermissionFor.size(); int itemsPerPage = 28; int maxPages = (int) Math.ceil((double) numberOfItems / itemsPerPage); @@ -65,7 +67,7 @@ public class GuiInventoryEditEffect extends GuiInventory { GuiIcon.PREVIOUS_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_PREVIOUS_PAGE_BUTTON, pageNumber - 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditEffect(pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditEffect(pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); this.actionButtons.add(previousPageButton); } @@ -76,7 +78,7 @@ public class GuiInventoryEditEffect extends GuiInventory { GuiIcon.NEXT_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_NEXT_PAGE_BUTTON, pageNumber + 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditEffect(pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditEffect(pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); this.actionButtons.add(nextPageButton); } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditParticle.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditParticle.java index e3163c3..3c44bdf 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditParticle.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditParticle.java @@ -1,6 +1,8 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.SettingManager.GuiIcon; @@ -18,6 +20,8 @@ public class GuiInventoryEditParticle extends GuiInventory { public GuiInventoryEditParticle(PPlayer pplayer, ParticlePair editingParticle) { super(pplayer, Bukkit.createInventory(pplayer.getPlayer(), INVENTORY_SIZE, LangManager.getText(Lang.GUI_EDITING_PARTICLE, editingParticle.getId()))); + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + this.fillBorder(BorderColor.RED); // Particle Info Icon @@ -38,8 +42,8 @@ public class GuiInventoryEditParticle extends GuiInventory { new String[]{LangManager.getText(Lang.GUI_COLOR_SUBTEXT) + LangManager.getText(Lang.GUI_EDIT_EFFECT_DESCRIPTION)}, (button, isShiftClick) -> { List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditParticle(pplayer, editingParticle))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditEffect(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditParticle(pplayer, editingParticle))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditEffect(pplayer, editingParticle, 1, callbacks, 1))); callbacks.add(() -> { ParticleGroup group = pplayer.getActiveParticleGroup(); for (ParticlePair particle : group.getParticles()) { @@ -48,9 +52,9 @@ public class GuiInventoryEditParticle extends GuiInventory { break; } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); - GuiHandler.transition(new GuiInventoryEditParticle(pplayer, editingParticle)); + GuiManager.transition(new GuiInventoryEditParticle(pplayer, editingParticle)); }); callbacks.get(1).execute(); @@ -64,8 +68,8 @@ public class GuiInventoryEditParticle extends GuiInventory { new String[]{LangManager.getText(Lang.GUI_COLOR_SUBTEXT) + LangManager.getText(Lang.GUI_EDIT_STYLE_DESCRIPTION)}, (button, isShiftClick) -> { List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditParticle(pplayer, editingParticle))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditStyle(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditParticle(pplayer, editingParticle))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditStyle(pplayer, editingParticle, 1, callbacks, 1))); callbacks.add(() -> { ParticleGroup group = pplayer.getActiveParticleGroup(); for (ParticlePair particle : group.getParticles()) { @@ -74,9 +78,9 @@ public class GuiInventoryEditParticle extends GuiInventory { break; } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); - GuiHandler.transition(new GuiInventoryEditParticle(pplayer, editingParticle)); + GuiManager.transition(new GuiInventoryEditParticle(pplayer, editingParticle)); }); callbacks.get(1).execute(); @@ -93,8 +97,8 @@ public class GuiInventoryEditParticle extends GuiInventory { (button, isShiftClick) -> { if (usesData) { List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditParticle(pplayer, editingParticle))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditData(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditParticle(pplayer, editingParticle))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditData(pplayer, editingParticle, 1, callbacks, 1))); callbacks.add(() -> { ParticleGroup group = pplayer.getActiveParticleGroup(); for (ParticlePair particle : group.getParticles()) { @@ -106,9 +110,9 @@ public class GuiInventoryEditParticle extends GuiInventory { break; } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); - GuiHandler.transition(new GuiInventoryEditParticle(pplayer, editingParticle)); + GuiManager.transition(new GuiInventoryEditParticle(pplayer, editingParticle)); }); callbacks.get(1).execute(); @@ -122,7 +126,7 @@ public class GuiInventoryEditParticle extends GuiInventory { GuiIcon.BACK.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_BACK_BUTTON), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryManageParticles(pplayer))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryManageParticles(pplayer))); this.actionButtons.add(backButton); this.populate(); diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditStyle.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditStyle.java index d59b434..cf8fcb0 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditStyle.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditStyle.java @@ -1,5 +1,7 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.PermissionManager; @@ -20,7 +22,7 @@ public class GuiInventoryEditStyle extends GuiInventory { this.fillBorder(BorderColor.BLUE); // Select Style Buttons - List stylesUserHasPermissionFor = PermissionManager.getStylesUserHasPermissionFor(pplayer.getPlayer()); + List stylesUserHasPermissionFor = PlayerParticles.getInstance().getManager(PermissionManager.class).getStylesUserHasPermissionFor(pplayer.getPlayer()); int numberOfItems = stylesUserHasPermissionFor.size(); int itemsPerPage = 28; int maxPages = (int) Math.ceil((double) numberOfItems / itemsPerPage); @@ -65,7 +67,7 @@ public class GuiInventoryEditStyle extends GuiInventory { GuiIcon.PREVIOUS_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_PREVIOUS_PAGE_BUTTON, pageNumber - 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditStyle(pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditStyle(pplayer, editingParticle, pageNumber - 1, callbackList, callbackListPosition))); this.actionButtons.add(previousPageButton); } @@ -76,7 +78,7 @@ public class GuiInventoryEditStyle extends GuiInventory { GuiIcon.NEXT_PAGE.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_NEXT_PAGE_BUTTON, pageNumber + 1, maxPages), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryEditStyle(pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryEditStyle(pplayer, editingParticle, pageNumber + 1, callbackList, callbackListPosition))); this.actionButtons.add(nextPageButton); } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryLoadPresetGroups.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryLoadPresetGroups.java index 470e379..8439505 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryLoadPresetGroups.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryLoadPresetGroups.java @@ -1,11 +1,13 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.ParticleGroupPresetManager; import dev.esophose.playerparticles.manager.SettingManager.GuiIcon; -import dev.esophose.playerparticles.manager.SettingManager.PSetting; +import dev.esophose.playerparticles.manager.SettingManager.Setting; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticleGroupPreset; @@ -22,6 +24,8 @@ public class GuiInventoryLoadPresetGroups extends GuiInventory { public GuiInventoryLoadPresetGroups(PPlayer pplayer, boolean isEndPoint) { super(pplayer, Bukkit.createInventory(pplayer.getPlayer(), INVENTORY_SIZE, LangManager.getText(Lang.GUI_LOAD_A_PRESET_GROUP))); + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + this.fillBorder(BorderColor.GREEN); Player player = pplayer.getPlayer(); @@ -29,7 +33,7 @@ public class GuiInventoryLoadPresetGroups extends GuiInventory { int index = 10; int nextWrap = 17; int maxIndex = 43; - List groups = ParticleGroupPresetManager.getPresetGroupsForPlayer(pplayer.getPlayer()); + List groups = PlayerParticles.getInstance().getManager(ParticleGroupPresetManager.class).getPresetGroupsForPlayer(pplayer.getPlayer()); for (ParticleGroupPreset group : groups) { if (!group.getGroup().canPlayerUse(player)) continue; @@ -51,9 +55,9 @@ public class GuiInventoryLoadPresetGroups extends GuiInventory { activeGroup.getParticles().clear(); for (ParticlePair particle : particles) activeGroup.getParticles().add(particle.clone()); - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + dataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); - if (PSetting.GUI_CLOSE_AFTER_GROUP_SELECTED.getBoolean()) { + if (Setting.GUI_CLOSE_AFTER_GROUP_SELECTED.getBoolean()) { pplayer.getPlayer().closeInventory(); } }); @@ -73,7 +77,7 @@ public class GuiInventoryLoadPresetGroups extends GuiInventory { INVENTORY_SIZE - 1, GuiIcon.BACK.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_BACK_BUTTON), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryDefault(pplayer))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryDefault(pplayer))); this.actionButtons.add(backButton); } else { // Reset Particles Button @@ -84,7 +88,7 @@ public class GuiInventoryLoadPresetGroups extends GuiInventory { new String[]{LangManager.getText(Lang.GUI_COLOR_UNAVAILABLE) + LangManager.getText(Lang.GUI_RESET_PARTICLES_DESCRIPTION)}, (button, isShiftClick) -> { // Reset particles - DataManager.saveParticleGroup(pplayer.getUniqueId(), ParticleGroup.getDefaultGroup()); + dataManager.saveParticleGroup(pplayer.getUniqueId(), ParticleGroup.getDefaultGroup()); }); this.actionButtons.add(resetParticles); } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageGroups.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageGroups.java index 6f84dc5..89a6120 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageGroups.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageGroups.java @@ -1,13 +1,15 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.gui.hook.PlayerChatHook; import dev.esophose.playerparticles.gui.hook.PlayerChatHookData; import dev.esophose.playerparticles.manager.DataManager; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.manager.SettingManager.GuiIcon; -import dev.esophose.playerparticles.manager.SettingManager.PSetting; +import dev.esophose.playerparticles.manager.SettingManager.Setting; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticlePair; @@ -23,6 +25,8 @@ public class GuiInventoryManageGroups extends GuiInventory { public GuiInventoryManageGroups(PPlayer pplayer) { super(pplayer, Bukkit.createInventory(pplayer.getPlayer(), INVENTORY_SIZE, LangManager.getText(Lang.GUI_MANAGE_YOUR_GROUPS))); + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + this.fillBorder(BorderColor.BROWN); int index = 10; @@ -54,7 +58,7 @@ public class GuiInventoryManageGroups extends GuiInventory { lore, (button, isShiftClick) -> { if (isShiftClick) { - DataManager.removeParticleGroup(pplayer.getUniqueId(), group); + dataManager.removeParticleGroup(pplayer.getUniqueId(), group); this.actionButtons.remove(button); this.inventory.setItem(button.getSlot(), null); @@ -63,9 +67,9 @@ public class GuiInventoryManageGroups extends GuiInventory { activeGroup.getParticles().clear(); for (ParticlePair particle : particles) activeGroup.getParticles().add(particle.clone()); - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + dataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); - if (PSetting.GUI_CLOSE_AFTER_GROUP_SELECTED.getBoolean()) { + if (Setting.GUI_CLOSE_AFTER_GROUP_SELECTED.getBoolean()) { pplayer.getPlayer().closeInventory(); } } @@ -80,7 +84,7 @@ public class GuiInventoryManageGroups extends GuiInventory { if (index > maxIndex) break; // Overflowed the available space } - boolean hasReachedMax = PermissionManager.hasPlayerReachedMaxGroups(pplayer); + boolean hasReachedMax = PlayerParticles.getInstance().getManager(PermissionManager.class).hasPlayerReachedMaxGroups(pplayer); boolean hasParticles = !pplayer.getActiveParticles().isEmpty(); String[] lore; if (hasReachedMax) { @@ -108,7 +112,7 @@ public class GuiInventoryManageGroups extends GuiInventory { PlayerChatHook.addHook(new PlayerChatHookData(pplayer.getUniqueId(), 15, (textEntered) -> { if (textEntered == null || textEntered.equalsIgnoreCase("cancel")) { - GuiHandler.transition(new GuiInventoryManageGroups(pplayer)); + GuiManager.transition(new GuiInventoryManageGroups(pplayer)); } else { String groupName = textEntered.split(" ")[0]; @@ -136,14 +140,14 @@ public class GuiInventoryManageGroups extends GuiInventory { } // Apply changes and notify player - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); if (groupUpdated) { LangManager.sendMessage(pplayer, Lang.GROUP_SAVE_SUCCESS_OVERWRITE, groupName); } else { LangManager.sendMessage(pplayer, Lang.GROUP_SAVE_SUCCESS, groupName); } - GuiHandler.transition(new GuiInventoryManageGroups(pplayer)); + GuiManager.transition(new GuiInventoryManageGroups(pplayer)); } })); pplayer.getPlayer().closeInventory(); @@ -156,7 +160,7 @@ public class GuiInventoryManageGroups extends GuiInventory { GuiIcon.BACK.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_BACK_BUTTON), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryDefault(pplayer))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryDefault(pplayer))); this.actionButtons.add(backButton); this.populate(); diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageParticles.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageParticles.java index 23f9f79..10f6926 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageParticles.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryManageParticles.java @@ -1,6 +1,8 @@ package dev.esophose.playerparticles.gui; +import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.DataManager; +import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LangManager; import dev.esophose.playerparticles.manager.LangManager.Lang; import dev.esophose.playerparticles.manager.PermissionManager; @@ -21,6 +23,8 @@ public class GuiInventoryManageParticles extends GuiInventory { public GuiInventoryManageParticles(PPlayer pplayer) { super(pplayer, Bukkit.createInventory(pplayer.getPlayer(), INVENTORY_SIZE, LangManager.getText(Lang.GUI_MANAGE_YOUR_PARTICLES))); + DataManager dataManager = PlayerParticles.getInstance().getManager(DataManager.class); + this.fillBorder(BorderColor.ORANGE); // Manage/Delete Particle Buttons @@ -42,7 +46,7 @@ public class GuiInventoryManageParticles extends GuiInventory { }, (button, isShiftClick) -> { if (!isShiftClick) { - GuiHandler.transition(new GuiInventoryEditParticle(pplayer, particle)); + GuiManager.transition(new GuiInventoryEditParticle(pplayer, particle)); } else { // Delete particle ParticleGroup activeGroup = pplayer.getActiveParticleGroup(); @@ -52,7 +56,7 @@ public class GuiInventoryManageParticles extends GuiInventory { break; } } - DataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); + dataManager.saveParticleGroup(pplayer.getUniqueId(), activeGroup); // Update inventory to reflect deletion this.actionButtons.remove(button); @@ -70,7 +74,7 @@ public class GuiInventoryManageParticles extends GuiInventory { } // Create New Particle Button - boolean canCreate = pplayer.getActiveParticles().size() < PermissionManager.getMaxParticlesAllowed(pplayer.getPlayer()); + boolean canCreate = pplayer.getActiveParticles().size() < PlayerParticles.getInstance().getManager(PermissionManager.class).getMaxParticlesAllowed(pplayer.getPlayer()); String lore = LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_CREATE_PARTICLE_DESCRIPTION); GuiActionButton createNewParticle = new GuiActionButton( 38, @@ -81,12 +85,12 @@ public class GuiInventoryManageParticles extends GuiInventory { if (!canCreate) return; ParticlePair editingParticle = ParticlePair.getNextDefault(pplayer); List callbacks = new ArrayList<>(); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryManageParticles(pplayer))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditEffect(pplayer, editingParticle, 1, callbacks, 1))); - callbacks.add(() -> GuiHandler.transition(new GuiInventoryEditStyle(pplayer, editingParticle, 1, callbacks, 2))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryManageParticles(pplayer))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditEffect(pplayer, editingParticle, 1, callbacks, 1))); + callbacks.add(() -> GuiManager.transition(new GuiInventoryEditStyle(pplayer, editingParticle, 1, callbacks, 2))); callbacks.add(() -> { if (editingParticle.getEffect().hasProperty(ParticleProperty.COLORABLE) || editingParticle.getEffect().hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) { - GuiHandler.transition(new GuiInventoryEditData(pplayer, editingParticle, 1, callbacks, 3)); + GuiManager.transition(new GuiInventoryEditData(pplayer, editingParticle, 1, callbacks, 3)); } else { callbacks.get(4).execute(); } @@ -95,10 +99,10 @@ public class GuiInventoryManageParticles extends GuiInventory { // Save new particle ParticleGroup group = pplayer.getActiveParticleGroup(); group.getParticles().add(editingParticle); - DataManager.saveParticleGroup(pplayer.getUniqueId(), group); + dataManager.saveParticleGroup(pplayer.getUniqueId(), group); // Reopen the manage particle inventory - GuiHandler.transition(new GuiInventoryManageParticles(pplayer)); + GuiManager.transition(new GuiInventoryManageParticles(pplayer)); }); callbacks.get(1).execute(); }); @@ -111,10 +115,10 @@ public class GuiInventoryManageParticles extends GuiInventory { new String[]{LangManager.getText(Lang.GUI_COLOR_UNAVAILABLE) + LangManager.getText(Lang.GUI_RESET_PARTICLES_DESCRIPTION)}, (button, isShiftClick) -> { // Reset particles - DataManager.saveParticleGroup(pplayer.getUniqueId(), ParticleGroup.getDefaultGroup()); + dataManager.saveParticleGroup(pplayer.getUniqueId(), ParticleGroup.getDefaultGroup()); // Reopen this same inventory to refresh it - GuiHandler.transition(new GuiInventoryManageParticles(pplayer)); + GuiManager.transition(new GuiInventoryManageParticles(pplayer)); }); this.actionButtons.add(resetParticles); @@ -124,7 +128,7 @@ public class GuiInventoryManageParticles extends GuiInventory { GuiIcon.BACK.get(), LangManager.getText(Lang.GUI_COLOR_INFO) + LangManager.getText(Lang.GUI_BACK_BUTTON), new String[]{}, - (button, isShiftClick) -> GuiHandler.transition(new GuiInventoryDefault(pplayer))); + (button, isShiftClick) -> GuiManager.transition(new GuiInventoryDefault(pplayer))); this.actionButtons.add(backButton); this.populate(); diff --git a/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHook.java b/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHook.java index 25a7e7d..c8b666a 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHook.java +++ b/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHook.java @@ -31,7 +31,7 @@ public class PlayerChatHook extends BukkitRunnable implements Listener { hooks = new HashSet<>(); if (hookTask != null) hookTask.cancel(); - hookTask = new PlayerChatHook().runTaskTimer(PlayerParticles.getPlugin(), 0, 20); + hookTask = new PlayerChatHook().runTaskTimer(PlayerParticles.getInstance(), 0, 20); } /** @@ -45,7 +45,7 @@ public class PlayerChatHook extends BukkitRunnable implements Listener { if (hook.getPlayerUUID().equals(event.getPlayer().getUniqueId())) { event.setCancelled(true); hooks.remove(hook); - Bukkit.getScheduler().scheduleSyncDelayedTask(PlayerParticles.getPlugin(), () -> hook.triggerCallback(event.getMessage())); + Bukkit.getScheduler().scheduleSyncDelayedTask(PlayerParticles.getInstance(), () -> hook.triggerCallback(event.getMessage())); return; } } diff --git a/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHookData.java b/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHookData.java index 27af102..b7d9430 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHookData.java +++ b/src/main/java/dev/esophose/playerparticles/gui/hook/PlayerChatHookData.java @@ -1,15 +1,16 @@ package dev.esophose.playerparticles.gui.hook; import java.util.UUID; +import java.util.function.Consumer; public class PlayerChatHookData { private UUID playerUUID; private int maxHookLength; private int hookLength; - private PlayerChatHookCallback hookCallback; + private Consumer hookCallback; - public PlayerChatHookData(UUID playerUUID, int hookLength, PlayerChatHookCallback hookCallback) { + public PlayerChatHookData(UUID playerUUID, int hookLength, Consumer hookCallback) { this.playerUUID = playerUUID; this.maxHookLength = hookLength; this.hookLength = hookLength; @@ -65,15 +66,7 @@ public class PlayerChatHookData { * @param textEntered The text that was entered by the player */ public void triggerCallback(String textEntered) { - this.hookCallback.onPlayerChat(textEntered); - } - - /** - * Allows simple hooking into the player chat for a specific time interval - */ - @FunctionalInterface - public interface PlayerChatHookCallback { - void onPlayerChat(String textEntered); + this.hookCallback.accept(textEntered); } } diff --git a/src/main/java/dev/esophose/playerparticles/locale/EnglishLocale.java b/src/main/java/dev/esophose/playerparticles/locale/EnglishLocale.java new file mode 100644 index 0000000..489cc6a --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/locale/EnglishLocale.java @@ -0,0 +1,303 @@ +package dev.esophose.playerparticles.locale; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class EnglishLocale implements Locale { + + @Override + public String getLocaleName() { + return "en_US"; + } + + @Override + public String getTranslatorName() { + return "Esophose"; + } + + @Override + public Map getDefaultLocaleStrings() { + return new LinkedHashMap() {{ + // Plugin Prefix + this.put("prefix", "&7[&3PlayerParticles&7] "); + + // Command Errors + this.put("command-error-no-effects", "&cYou must have access to effects to use this command!"); + this.put("command-error-unknown", "&cUnknown command, use &b/pp help &cfor a list of commands."); + + // Command Descriptions + this.put("command-descriptions", "&eThe following commands are available:"); + this.put("command-descriptions-usage", "&e/pp {0} {1}"); + this.put("command-descriptions-help-1", "&7> &b/pp {0} &e- {1}"); + this.put("command-descriptions-help-2", "&7> &b/pp {0} {1} &e- {2}"); + this.put("command-descriptions-help-other", "&7> &b/ppo &e- Run a /pp command as a player"); + this.put("command-description-add", "Add a new particle"); + this.put("command-description-data", "Check what type of data an effect uses"); + this.put("command-description-default", "The main command. By default, opens the GUI"); + this.put("command-description-edit", "Edit a particle"); + this.put("command-description-effects", "Display a list of effects you can use"); + this.put("command-description-fixed", "Manage your fixed effects"); + this.put("command-description-group", "Manage your groups"); + this.put("command-description-gui", "Display the GUI for easy editing of particles"); + this.put("command-description-help", "Displays the help menu... You have arrived"); + this.put("command-description-info", "Gets the description of one of your active particles"); + this.put("command-description-list", "Lists the IDs of your active particles"); + this.put("command-description-reload", "Reloads the config.yml and lang file"); + this.put("command-description-remove", "Removes some particles"); + this.put("command-description-reset", "Removes all your active particles"); + this.put("command-description-styles", "Display a list of styles you can use"); + this.put("command-description-toggle", "Toggles particle visibility on/off"); + this.put("command-description-version", "Display the plugin version and author"); + this.put("command-description-worlds", "Find out what worlds particles are disabled in"); + + // Sub-Command Usage + this.put("command-description-fixed-create", "&e/pp fixed create < |>