diff --git a/api/src/main/java/fr/euphyllia/skyllia/api/addons/AddonLoadPhase.java b/api/src/main/java/fr/euphyllia/skyllia/api/addons/AddonLoadPhase.java new file mode 100644 index 0000000..8b4b3bb --- /dev/null +++ b/api/src/main/java/fr/euphyllia/skyllia/api/addons/AddonLoadPhase.java @@ -0,0 +1,6 @@ +package fr.euphyllia.skyllia.api.addons; + +public enum AddonLoadPhase { + BEFORE, + AFTER +} diff --git a/api/src/main/java/fr/euphyllia/skyllia/api/addons/SkylliaAddon.java b/api/src/main/java/fr/euphyllia/skyllia/api/addons/SkylliaAddon.java new file mode 100644 index 0000000..ac5a5e7 --- /dev/null +++ b/api/src/main/java/fr/euphyllia/skyllia/api/addons/SkylliaAddon.java @@ -0,0 +1,28 @@ +package fr.euphyllia.skyllia.api.addons; + +import org.bukkit.plugin.Plugin; + +public interface SkylliaAddon { + + /** + * Indicates the loading phase in which the addon wishes to be initialized. + */ + AddonLoadPhase getLoadPhase(); + + /** + * Called when the addon is detected, according to its loading phase (BEFORE/AFTER). + */ + void onLoad(Plugin plugin); + + /** + * Called immediately after onLoad (within the same phase). + * Useful if you want to separate configuration/initialization (onLoad) + * from the actual activation (onEnable). + */ + void onEnable(); + + /** + * Called when the addon is deactivated. + */ + void onDisabled(); +} diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/Main.java b/plugin/src/main/java/fr/euphyllia/skyllia/Main.java index ebc0f9c..2941a57 100644 --- a/plugin/src/main/java/fr/euphyllia/skyllia/Main.java +++ b/plugin/src/main/java/fr/euphyllia/skyllia/Main.java @@ -1,194 +1,136 @@ package fr.euphyllia.skyllia; +import fr.euphyllia.skyllia.addons.AddonLoader; import fr.euphyllia.skyllia.api.InterneAPI; +import fr.euphyllia.skyllia.api.addons.AddonLoadPhase; import fr.euphyllia.skyllia.api.commands.SubCommandRegistry; import fr.euphyllia.skyllia.api.exceptions.UnsupportedMinecraftVersionException; import fr.euphyllia.skyllia.api.utils.Metrics; -import fr.euphyllia.skyllia.api.utils.VersionUtils; -import fr.euphyllia.skyllia.commands.admin.SkylliaAdminCommand; -import fr.euphyllia.skyllia.commands.admin.SubAdminCommandImpl; -import fr.euphyllia.skyllia.commands.common.SkylliaCommand; -import fr.euphyllia.skyllia.commands.common.SubCommandImpl; +import fr.euphyllia.skyllia.cache.CacheScheduler; +import fr.euphyllia.skyllia.commands.CommandRegistrar; +import fr.euphyllia.skyllia.configuration.ConfigManager; import fr.euphyllia.skyllia.configuration.ConfigToml; -import fr.euphyllia.skyllia.configuration.LanguageToml; -import fr.euphyllia.skyllia.configuration.PermissionsToml; -import fr.euphyllia.skyllia.listeners.bukkitevents.blocks.BlockEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.blocks.PistonEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.entity.DamageEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.folia.PortalAlternativeFoliaEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.BlockGameRuleEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.ExplosionEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.GriefingEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.MobSpawnEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.PickupEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.paper.PortalAlternativePaperEvent; -import fr.euphyllia.skyllia.listeners.bukkitevents.player.*; -import fr.euphyllia.skyllia.listeners.skyblockevents.SkyblockEvent; -import fr.euphyllia.skyllia.managers.Managers; -import fr.euphyllia.skyllia.sgbd.exceptions.DatabaseException; -import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; -import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import fr.euphyllia.skyllia.listeners.ListenersRegistrar; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; +/** + * Main class of the plugin, coordinating initialization, enabling, and disabling. + */ public class Main extends JavaPlugin { - private final Logger logger = LogManager.getLogger(this); + private static final Logger LOGGER = LogManager.getLogger(Main.class); + private InterneAPI interneAPI; + private SubCommandRegistry commandRegistry; private SubCommandRegistry adminCommandRegistry; + @Override + public void onLoad() { + // Load addons which must be loaded before the plugin is fully enabled + new AddonLoader(this, LOGGER).loadAddons(AddonLoadPhase.BEFORE); + } + @Override public void onEnable() { + // Initialize the internal API (checks server version compatibility) if (!initializeInterneAPI()) { - return; + return; // Stop if version is not supported } - if (!loadConfigurations()) { + // Load and check all configurations (config.toml, language.toml, permissions.toml) + ConfigManager configManager = new ConfigManager(this, LOGGER); + if (!configManager.loadConfigurations(interneAPI)) { + // If config load fails, the plugin is disabled (inside loadConfigurations) return; } - initializeCommandsDispatcher(); + // Register commands via CommandRegistrar + CommandRegistrar commandRegistrar = new CommandRegistrar(this); + commandRegistrar.registerCommands(); + // Récupérer les SubCommandRegistry si besoin + this.commandRegistry = commandRegistrar.getCommandRegistry(); + this.adminCommandRegistry = commandRegistrar.getAdminCommandRegistry(); - initializeManagers(); - registerListeners(); - scheduleCacheUpdate(); + // Initialize managers + this.interneAPI.setManagers(new fr.euphyllia.skyllia.managers.Managers(interneAPI)); + this.interneAPI.getManagers().init(); + + // Register listeners + new ListenersRegistrar(this, interneAPI, LOGGER).registerListeners(); + + // Schedule cache updates + new CacheScheduler(this, interneAPI, LOGGER).scheduleCacheUpdate(); + + // Check server configs for Nether/End warnings checkDisabledConfig(); + // Load addons which must be loaded after the plugin is fully enabled + new AddonLoader(this, LOGGER).loadAddons(AddonLoadPhase.AFTER); + + // bStats metrics new Metrics(this, 20874); } @Override public void onDisable() { + // Disable all loaded addons + AddonLoader.disableAllAddons(); + + // Cancel scheduled tasks Bukkit.getAsyncScheduler().cancelTasks(this); Bukkit.getGlobalRegionScheduler().cancelTasks(this); + + // Close DB if needed if (this.interneAPI != null && this.interneAPI.getDatabaseLoader() != null) { this.interneAPI.getDatabaseLoader().closeDatabase(); } } - public InterneAPI getInterneAPI() { - return this.interneAPI; - } - - public @NotNull SubCommandRegistry getCommandRegistry() { - return commandRegistry; - } - - public @NotNull SubCommandRegistry getAdminCommandRegistry() { - return adminCommandRegistry; - } - + /** + * Initializes the internal API, handling version compatibility. + * + * @return true if initialization succeeded, false otherwise + */ private boolean initializeInterneAPI() { try { this.interneAPI = new InterneAPI(this); - this.interneAPI.loadAPI(); return true; } catch (UnsupportedMinecraftVersionException e) { - logger.log(Level.FATAL, e.getMessage(), e); + LOGGER.log(Level.FATAL, e.getMessage(), e); Bukkit.getPluginManager().disablePlugin(this); return false; } } - private boolean loadConfigurations() { - try { - this.interneAPI.setupFirstSchematic(getDataFolder(), getResource("schematics/default.schem")); - if (!this.interneAPI.setupConfigs(getDataFolder(), "config.toml", ConfigToml::init) || - !this.interneAPI.setupConfigs(getDataFolder(), "language.toml", LanguageToml::init) || - !this.interneAPI.setupConfigs(getDataFolder(), "permissions.toml", PermissionsToml::init) || - !this.interneAPI.setupSGBD()) { - Bukkit.getPluginManager().disablePlugin(this); - return false; + /** + * Checks if Nether/End are disabled, and logs a warning if they are not (based on config). + */ + private void checkDisabledConfig() { + if (fr.euphyllia.skyllia.api.utils.VersionUtils.IS_FOLIA && !ConfigToml.suppressWarningNetherEndEnabled) { + if (Bukkit.getAllowNether()) { + LOGGER.log(Level.WARN, "Disable nether in server.properties to disable nether portals!"); + } + if (Bukkit.getAllowEnd()) { + LOGGER.log(Level.WARN, "Disable end in bukkit.yml to disable end portals!"); } - return true; - } catch (DatabaseException | IOException exception) { - logger.log(Level.FATAL, exception, exception); - Bukkit.getPluginManager().disablePlugin(this); - return false; - } - } - - private void initializeManagers() { - this.interneAPI.setManagers(new Managers(interneAPI)); - this.interneAPI.getManagers().init(); - this.commandRegistry = new SubCommandImpl(); - this.adminCommandRegistry = new SubAdminCommandImpl(); - } - - private void registerListeners() { - PluginManager pluginManager = getServer().getPluginManager(); - - // Bukkit Events - registerEvent(pluginManager, new JoinEvent(this.interneAPI)); - registerEvent(pluginManager, new BlockEvent(this.interneAPI)); - registerEvent(pluginManager, new InventoryEvent(this.interneAPI)); - registerEvent(pluginManager, new PlayerEvent(this.interneAPI)); - registerEvent(pluginManager, new DamageEvent(this.interneAPI)); - registerEvent(pluginManager, new InteractEvent(this.interneAPI)); - registerEvent(pluginManager, new TeleportEvent(this.interneAPI)); // TODO: Doesn't work with Folia 1.19.4-1.21.4 - registerEvent(pluginManager, new PistonEvent(this.interneAPI)); - - if (VersionUtils.IS_FOLIA) { - registerEvent(pluginManager, new PortalAlternativeFoliaEvent(this.interneAPI)); - } - if (VersionUtils.IS_PAPER) { - registerEvent(pluginManager, new PortalAlternativePaperEvent()); } - - // GameRule Events - registerEvent(pluginManager, new BlockGameRuleEvent(this.interneAPI)); - registerEvent(pluginManager, new ExplosionEvent(this.interneAPI)); - registerEvent(pluginManager, new GriefingEvent(this.interneAPI)); - registerEvent(pluginManager, new MobSpawnEvent(this.interneAPI)); - registerEvent(pluginManager, new PickupEvent(this.interneAPI)); - - // Skyblock Event - registerEvent(pluginManager, new SkyblockEvent(this.interneAPI)); - } - - private void registerEvent(PluginManager pluginManager, Object listener) { - pluginManager.registerEvents((org.bukkit.event.Listener) listener, this); } - private void scheduleCacheUpdate() { - Runnable cacheUpdateTask = () -> Bukkit.getOnlinePlayers().forEach(player -> this.interneAPI.updateCache(player)); - Bukkit.getAsyncScheduler().runAtFixedRate(this, task -> cacheUpdateTask.run(), 1, ConfigToml.updateCacheTimer, TimeUnit.SECONDS); + public InterneAPI getInterneAPI() { + return this.interneAPI; } - private void checkDisabledConfig() { - /* Since 1.20.3, there is a gamerule that allows you to increase the number of ticks between entering a portal and teleporting. - This makes the configuration possibly useless. - BUT just in case, I leave the message enabled by default. - */ - if (VersionUtils.IS_FOLIA && !ConfigToml.suppressWarningNetherEndEnabled) { - if (Bukkit.getAllowNether()) { - logger.log(Level.WARN, "Disable nether in server.properties to disable nether portals!"); - } - if (Bukkit.getAllowEnd()) { - logger.log(Level.WARN, "Disable end in bukkit.yml to disable end portals!"); - } - } + public SubCommandRegistry getCommandRegistry() { + return commandRegistry; } - private void initializeCommandsDispatcher() { - LifecycleEventManager manager = this.getLifecycleManager(); - manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { - final Commands commands = event.registrar(); - commands.register("skyllia", "Islands commands", List.of("is"), new SkylliaCommand(this)); - commands.register("skylliaadmin", "Administrator commands", List.of("isadmin"), new SkylliaAdminCommand(this)); - }); + public SubCommandRegistry getAdminCommandRegistry() { + return adminCommandRegistry; } } diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/addons/AddonLoader.java b/plugin/src/main/java/fr/euphyllia/skyllia/addons/AddonLoader.java new file mode 100644 index 0000000..269e804 --- /dev/null +++ b/plugin/src/main/java/fr/euphyllia/skyllia/addons/AddonLoader.java @@ -0,0 +1,87 @@ +package fr.euphyllia.skyllia.addons; + +import fr.euphyllia.skyllia.Main; +import fr.euphyllia.skyllia.api.addons.AddonLoadPhase; +import fr.euphyllia.skyllia.api.addons.SkylliaAddon; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +/** + * This class scans and loads addons from the /addons folder. + */ +public class AddonLoader { + + private static final List LOADED_ADDONS = new ArrayList<>(); + + private final Main plugin; + private final Logger logger; + + /** + * Constructs an AddonLoader. + * + * @param plugin the main plugin instance + * @param logger a shared logger + */ + public AddonLoader(Main plugin, Logger logger) { + this.plugin = plugin; + this.logger = logger; + } + + /** + * Searches for .jar files in the addons directory and loads them based on the specified phase. + * + * @param phase the AddonLoadPhase (BEFORE or AFTER plugin enable) + */ + public void loadAddons(AddonLoadPhase phase) { + File extensionsDir = new File(plugin.getDataFolder(), "addons"); + if (!extensionsDir.exists()) { + extensionsDir.mkdirs(); + logger.info("Addon directory created at {}", extensionsDir.getAbsolutePath()); + return; + } + + File[] files = extensionsDir.listFiles((dir, name) -> name.endsWith(".jar")); + if (files == null || files.length == 0) { + logger.warn("No addon files found in {}", extensionsDir.getAbsolutePath()); + return; + } + + for (File file : files) { + try { + URL jarUrl = file.toURI().toURL(); + URLClassLoader classLoader = new URLClassLoader(new URL[]{jarUrl}, plugin.getClass().getClassLoader()); + + ServiceLoader serviceLoader = ServiceLoader.load(SkylliaAddon.class, classLoader); + for (SkylliaAddon addon : serviceLoader) { + if (addon.getLoadPhase() == phase) { + addon.onLoad(plugin); + addon.onEnable(); + LOADED_ADDONS.add(addon); + logger.info("Loaded {} addon: {}", phase, addon.getClass().getName()); + } + } + } catch (Exception e) { + logger.error("Failed to load addon: {}", file.getName(), e); + } + } + } + + /** + * Disables all loaded addons, clearing the internal list. + */ + public static void disableAllAddons() { + for (SkylliaAddon addon : LOADED_ADDONS) { + try { + addon.onDisabled(); + } catch (Exception ignored) { + } + } + LOADED_ADDONS.clear(); + } +} diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/api/InterneAPI.java b/plugin/src/main/java/fr/euphyllia/skyllia/api/InterneAPI.java index e9e011a..a050adb 100644 --- a/plugin/src/main/java/fr/euphyllia/skyllia/api/InterneAPI.java +++ b/plugin/src/main/java/fr/euphyllia/skyllia/api/InterneAPI.java @@ -16,7 +16,6 @@ import fr.euphyllia.skyllia.sgbd.MariaDB; import fr.euphyllia.skyllia.sgbd.exceptions.DatabaseException; import net.kyori.adventure.text.minimessage.MiniMessage; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; @@ -27,74 +26,121 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +/** + * Internal API class, managing NMS, database connections, and caching. + */ public class InterneAPI { - private final Logger logger = LogManager.getLogger(this); + private static final Logger LOGGER = LogManager.getLogger(InterneAPI.class); + private final Main plugin; private final SkyblockManager skyblockManager; private final CacheManager cacheManager; - private @Nullable DatabaseLoader database; - private DatabaseLoader databaseLoader; - private Managers managers; + private WorldNMS worldNMS; private PlayerNMS playerNMS; private BiomesImpl biomesImpl; + private DatabaseLoader database; + private Managers managers; + + /** + * Constructor that checks the Minecraft version and initializes NMS classes. + * + * @param plugin The main plugin instance + * @throws UnsupportedMinecraftVersionException if server version is not supported + */ public InterneAPI(Main plugin) throws UnsupportedMinecraftVersionException { this.plugin = plugin; - this.setVersionNMS(); - this.skyblockManager = new SkyblockManager(this.plugin); + setVersionNMS(); + this.skyblockManager = new SkyblockManager(plugin); this.cacheManager = new CacheManager(this.skyblockManager, this); + loadAPI(); } - public @Nullable DatabaseLoader getDatabaseLoader() { - return this.database; - } - - public Managers getManagers() { - return managers; - } - - public void setManagers(Managers managers) { - this.managers = managers; - } - - private @Nullable File checkFileExist(File dataFolder, String fileName) throws IOException { - if (!dataFolder.exists() && (!dataFolder.mkdir())) { - logger.log(Level.FATAL, "Unable to create the configuration folder."); - return null; - - } - FileSystem fs = FileSystems.getDefault(); - Path configFolder = fs.getPath(dataFolder.getAbsolutePath()); - - File configFile = new File(configFolder + File.separator + fileName); - if (!configFile.exists()) { - configFile.createNewFile(); + /** + * Determines the version of NMS to use based on the Bukkit server version. + * + * @throws UnsupportedMinecraftVersionException if the version is not supported + */ + private void setVersionNMS() throws UnsupportedMinecraftVersionException { + final String[] bukkitVersion = Bukkit.getServer().getBukkitVersion().split("-"); + switch (bukkitVersion[0]) { + case "1.20", "1.20.1" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R1.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R1.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R1.BiomeNMS(); + } + case "1.20.2" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R2.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R2.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R2.BiomeNMS(); + } + case "1.20.3", "1.20.4" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R3.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R3.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R3.BiomeNMS(); + } + case "1.20.5", "1.20.6" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R4.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R4.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R4.BiomeNMS(); + } + case "1.21", "1.21.1" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R1.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R1.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_21_R1.BiomeNMS(); + } + case "1.21.2", "1.21.3" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R2.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R2.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_21_R2.BiomeNMS(); + } + case "1.21.4" -> { + this.worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R3.WorldNMS(); + this.playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R3.PlayerNMS(); + this.biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_21_R3.BiomeNMS(); + } + default -> { + throw new UnsupportedMinecraftVersionException("Version " + bukkitVersion[0] + " not supported!"); + } } - return configFile; } + + /** + * Loads a config file if it doesn't exist, then initializes it via a ConfigInitializer. + * + * @param dataFolder Path to the plugin's data folder + * @param fileName The config file name + * @param initializer The ConfigInitializer functional interface + * @return true if successful, false otherwise + * @throws IOException if file creation fails + */ public boolean setupConfigs(File dataFolder, String fileName, ConfigInitializer initializer) throws IOException { - File configFile = this.checkFileExist(dataFolder, fileName); + File configFile = checkFileExist(dataFolder, fileName); if (configFile == null) { return false; } - try { initializer.initConfig(configFile); } catch (Exception ex) { - logger.log(Level.FATAL, ex.getMessage(), ex); + LOGGER.error("Error while initializing config: ", ex); return false; } return true; } + /** + * Copies a default schematic if it does not exist already. + * + * @param dataFolder The plugin's data folder + * @param resource The InputStream for the default schematic + */ public void setupFirstSchematic(@NotNull File dataFolder, @Nullable InputStream resource) { File schematicsDir = new File(dataFolder, "schematics"); File defaultSchem = new File(schematicsDir, "default.schem"); @@ -103,19 +149,21 @@ public void setupFirstSchematic(@NotNull File dataFolder, @Nullable InputStream } else { return; } - if (!defaultSchem.exists()) { - try { - try (InputStream in = resource) { - if (in != null) { - Files.copy(in, defaultSchem.toPath()); - } - } + if (!defaultSchem.exists() && resource != null) { + try (InputStream in = resource) { + Files.copy(in, defaultSchem.toPath()); } catch (IOException e) { e.printStackTrace(); } } } + /** + * Initializes the SGBD (e.g., MariaDB) from the config, creating the database schema if necessary. + * + * @return true if successful, false otherwise + * @throws DatabaseException if database initialization fails + */ public boolean setupSGBD() throws DatabaseException { if (ConfigToml.mariaDBConfig != null) { MariaDB mariaDB = new MariaDB(ConfigToml.mariaDBConfig); @@ -129,14 +177,70 @@ public boolean setupSGBD() throws DatabaseException { } } + /** + * Returns an IslandQuery instance to access or modify island data. + * + * @return A new IslandQuery instance + */ public IslandQuery getIslandQuery() { return new IslandQuery(this, ConfigToml.mariaDBConfig.database()); } + /** + * Periodically updates the cache for a given player. + * + * @param player The player whose cache is updated + */ + public void updateCache(Player player) { + this.cacheManager.updateCache(skyblockManager, player); + } + + /** + * Loads the public API implementation. + */ + private void loadAPI() { + fr.euphyllia.skyllia.api.SkylliaAPI.setImplementation(this.plugin, new APISkyllia(this)); + } + + /** + * Ensures a config file exists in the dataFolder, creating it if necessary. + * + * @param dataFolder The plugin data folder + * @param fileName The file name + * @return The File object or null if creation fails + * @throws IOException if file creation fails + */ + private @Nullable File checkFileExist(File dataFolder, String fileName) throws IOException { + if (!dataFolder.exists() && !dataFolder.mkdir()) { + LOGGER.error("Could not create directory: {}", dataFolder.getAbsolutePath()); + return null; + } + Path configFolder = FileSystems.getDefault().getPath(dataFolder.getAbsolutePath()); + File configFile = new File(configFolder + File.separator + fileName); + if (!configFile.exists()) { + configFile.createNewFile(); + } + return configFile; + } + + /* Getters and setters */ + public Main getPlugin() { return this.plugin; } + public Managers getManagers() { + return managers; + } + + public void setManagers(Managers managers) { + this.managers = managers; + } + + public DatabaseLoader getDatabaseLoader() { + return this.database; + } + public SkyblockManager getSkyblockManager() { return this.skyblockManager; } @@ -145,57 +249,10 @@ public SkyblockManager getSkyblockManager() { return MiniMessage.miniMessage(); } - public void updateCache(Player player) { - this.cacheManager.updateCache(skyblockManager, player); - } - public CacheManager getCacheManager() { return this.cacheManager; } - private void setVersionNMS() throws UnsupportedMinecraftVersionException { - final String[] bukkitVersion = Bukkit.getServer().getBukkitVersion().split("-"); - switch (bukkitVersion[0]) { - case "1.20", "1.20.1" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R1.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R1.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R1.BiomeNMS(); - } - case "1.20.2" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R2.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R2.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R2.BiomeNMS(); - } - case "1.20.3", "1.20.4" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R3.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R3.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R3.BiomeNMS(); - } - case "1.20.5", "1.20.6" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R4.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_20_R4.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_20_R4.BiomeNMS(); - } - case "1.21", "1.21.1" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R1.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R1.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_21_R1.BiomeNMS(); - } - case "1.21.2", "1.21.3" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R2.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R2.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_21_R2.BiomeNMS(); - } - case "1.21.4" -> { - worldNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R3.WorldNMS(); - playerNMS = new fr.euphyllia.skyllia.utils.nms.v1_21_R3.PlayerNMS(); - biomesImpl = new fr.euphyllia.skyllia.utils.nms.v1_21_R3.BiomeNMS(); - } - default -> - throw new UnsupportedMinecraftVersionException("Version %s not supported !".formatted(bukkitVersion[0])); - } - } - public WorldNMS getWorldNMS() { return this.worldNMS; } @@ -204,10 +261,6 @@ public PlayerNMS getPlayerNMS() { return this.playerNMS; } - public void loadAPI() { - SkylliaAPI.setImplementation(this.plugin, new APISkyllia(this)); - } - public BiomesImpl getBiomesImpl() { return this.biomesImpl; } diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/cache/CacheScheduler.java b/plugin/src/main/java/fr/euphyllia/skyllia/cache/CacheScheduler.java new file mode 100644 index 0000000..274c99f --- /dev/null +++ b/plugin/src/main/java/fr/euphyllia/skyllia/cache/CacheScheduler.java @@ -0,0 +1,46 @@ +package fr.euphyllia.skyllia.cache; + +import fr.euphyllia.skyllia.Main; +import fr.euphyllia.skyllia.api.InterneAPI; +import fr.euphyllia.skyllia.configuration.ConfigToml; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; + +import java.util.concurrent.TimeUnit; + +/** + * Schedules periodic cache updates. + */ +public class CacheScheduler { + + private final Main plugin; + private final InterneAPI interneAPI; + private final Logger logger; + + /** + * Constructs a CacheScheduler. + * + * @param plugin the main plugin instance + * @param interneAPI the internal API + * @param logger a shared logger + */ + public CacheScheduler(Main plugin, InterneAPI interneAPI, Logger logger) { + this.plugin = plugin; + this.interneAPI = interneAPI; + this.logger = logger; + } + + /** + * Schedules the cache update task at a fixed rate defined in the config. + */ + public void scheduleCacheUpdate() { + Bukkit.getAsyncScheduler().runAtFixedRate( + plugin, + task -> Bukkit.getOnlinePlayers().forEach(interneAPI::updateCache), + 1, + ConfigToml.updateCacheTimer, + TimeUnit.SECONDS + ); + logger.info("CacheScheduler started with an interval of {} seconds.", ConfigToml.updateCacheTimer); + } +} diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/commands/CommandRegistrar.java b/plugin/src/main/java/fr/euphyllia/skyllia/commands/CommandRegistrar.java new file mode 100644 index 0000000..fc20225 --- /dev/null +++ b/plugin/src/main/java/fr/euphyllia/skyllia/commands/CommandRegistrar.java @@ -0,0 +1,68 @@ +package fr.euphyllia.skyllia.commands; + +import fr.euphyllia.skyllia.Main; +import fr.euphyllia.skyllia.api.commands.SubCommandRegistry; +import fr.euphyllia.skyllia.commands.admin.SkylliaAdminCommand; +import fr.euphyllia.skyllia.commands.admin.SubAdminCommandImpl; +import fr.euphyllia.skyllia.commands.common.SkylliaCommand; +import fr.euphyllia.skyllia.commands.common.SubCommandImpl; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import org.bukkit.plugin.Plugin; + +/** + * This class handles the registration of all plugin commands using Paper's Brigadier. + */ +public class CommandRegistrar { + + private final Main plugin; + private final SubCommandRegistry commandRegistry; + private final SubCommandRegistry adminCommandRegistry; + + /** + * Constructs a CommandRegistrar that will register Skyllia commands. + * + * @param plugin The main plugin instance + */ + public CommandRegistrar(Main plugin) { + this.plugin = plugin; + + // Création des SubCommandRegistry + this.commandRegistry = new SubCommandImpl(); + this.adminCommandRegistry = new SubAdminCommandImpl(); + } + + /** + * Registers all plugin commands via the Paper lifecycle command event. + */ + public void registerCommands() { + LifecycleEventManager manager = plugin.getLifecycleManager(); + + manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { + final Commands commands = event.registrar(); + + commands.register( + "skyllia", + "Islands commands", + java.util.List.of("is"), + new SkylliaCommand(plugin) + ); + + commands.register( + "skylliaadmin", + "Administrator commands", + java.util.List.of("isadmin"), + new SkylliaAdminCommand(plugin) + ); + }); + } + + public SubCommandRegistry getCommandRegistry() { + return commandRegistry; + } + + public SubCommandRegistry getAdminCommandRegistry() { + return adminCommandRegistry; + } +} diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/configuration/ConfigManager.java b/plugin/src/main/java/fr/euphyllia/skyllia/configuration/ConfigManager.java new file mode 100644 index 0000000..676838d --- /dev/null +++ b/plugin/src/main/java/fr/euphyllia/skyllia/configuration/ConfigManager.java @@ -0,0 +1,72 @@ +package fr.euphyllia.skyllia.configuration; + +import fr.euphyllia.skyllia.Main; +import fr.euphyllia.skyllia.api.InterneAPI; +import fr.euphyllia.skyllia.sgbd.exceptions.DatabaseException; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; + +import java.io.IOException; + +/** + * Manages loading of various plugin configurations (config.toml, language.toml, permissions.toml). + */ +public class ConfigManager { + + private final Main plugin; + private final Logger logger; + + /** + * Constructs a ConfigManager. + * + * @param plugin the main plugin instance + * @param logger a shared logger + */ + public ConfigManager(Main plugin, Logger logger) { + this.plugin = plugin; + this.logger = logger; + } + + /** + * Loads all necessary configurations using the provided InterneAPI. + * + * @param interneAPI the internal API + * @return true if successful, false otherwise + */ + public boolean loadConfigurations(InterneAPI interneAPI) { + try { + // Copie la schematic par défaut + interneAPI.setupFirstSchematic(plugin.getDataFolder(), plugin.getResource("schematics/default.schem")); + + // Load config.toml + if (!interneAPI.setupConfigs(plugin.getDataFolder(), "config.toml", ConfigToml::init)) { + Bukkit.getPluginManager().disablePlugin(plugin); + return false; + } + + // Load language.toml + if (!interneAPI.setupConfigs(plugin.getDataFolder(), "language.toml", LanguageToml::init)) { + Bukkit.getPluginManager().disablePlugin(plugin); + return false; + } + + // Load permissions.toml + if (!interneAPI.setupConfigs(plugin.getDataFolder(), "permissions.toml", PermissionsToml::init)) { + Bukkit.getPluginManager().disablePlugin(plugin); + return false; + } + + // Initialize the DB + if (!interneAPI.setupSGBD()) { + Bukkit.getPluginManager().disablePlugin(plugin); + return false; + } + } catch (DatabaseException | IOException e) { + logger.log(Level.FATAL, "Error loading configurations or initializing DB:", e); + Bukkit.getPluginManager().disablePlugin(plugin); + return false; + } + return true; + } +} diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/listeners/ListenersRegistrar.java b/plugin/src/main/java/fr/euphyllia/skyllia/listeners/ListenersRegistrar.java new file mode 100644 index 0000000..916eda3 --- /dev/null +++ b/plugin/src/main/java/fr/euphyllia/skyllia/listeners/ListenersRegistrar.java @@ -0,0 +1,92 @@ +package fr.euphyllia.skyllia.listeners; + +import fr.euphyllia.skyllia.Main; +import fr.euphyllia.skyllia.api.InterneAPI; +import fr.euphyllia.skyllia.api.utils.VersionUtils; +import fr.euphyllia.skyllia.listeners.bukkitevents.blocks.BlockEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.blocks.PistonEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.entity.DamageEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.folia.PortalAlternativeFoliaEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.BlockGameRuleEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.ExplosionEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.GriefingEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.MobSpawnEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.gamerule.entity.PickupEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.paper.PortalAlternativePaperEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.player.InteractEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.player.InventoryEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.player.JoinEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.player.PlayerEvent; +import fr.euphyllia.skyllia.listeners.bukkitevents.player.TeleportEvent; +import fr.euphyllia.skyllia.listeners.skyblockevents.SkyblockEvent; +import org.apache.logging.log4j.Logger; +import org.bukkit.event.Listener; +import org.bukkit.plugin.PluginManager; + +/** + * Registers all listeners in one place. + */ +public class ListenersRegistrar { + + private final Main plugin; + private final InterneAPI interneAPI; + private final Logger logger; + + /** + * Constructs a ListenersRegistrar. + * + * @param plugin the main plugin instance + * @param interneAPI the internal API + * @param logger a shared logger + */ + public ListenersRegistrar(Main plugin, InterneAPI interneAPI, Logger logger) { + this.plugin = plugin; + this.interneAPI = interneAPI; + this.logger = logger; + } + + /** + * Registers all required event listeners. + */ + public void registerListeners() { + PluginManager pluginManager = plugin.getServer().getPluginManager(); + + // Bukkit Events + registerEvent(pluginManager, new JoinEvent(interneAPI)); + registerEvent(pluginManager, new BlockEvent(interneAPI)); + registerEvent(pluginManager, new InventoryEvent(interneAPI)); + registerEvent(pluginManager, new PlayerEvent(interneAPI)); + registerEvent(pluginManager, new DamageEvent(interneAPI)); + registerEvent(pluginManager, new InteractEvent(interneAPI)); + registerEvent(pluginManager, new TeleportEvent(interneAPI)); + registerEvent(pluginManager, new PistonEvent(interneAPI)); + + // Folia/Paper specifics + if (VersionUtils.IS_FOLIA) { + registerEvent(pluginManager, new PortalAlternativeFoliaEvent(interneAPI)); + } + if (VersionUtils.IS_PAPER) { + registerEvent(pluginManager, new PortalAlternativePaperEvent()); + } + + // GameRule Events + registerEvent(pluginManager, new BlockGameRuleEvent(interneAPI)); + registerEvent(pluginManager, new ExplosionEvent(interneAPI)); + registerEvent(pluginManager, new GriefingEvent(interneAPI)); + registerEvent(pluginManager, new MobSpawnEvent(interneAPI)); + registerEvent(pluginManager, new PickupEvent(interneAPI)); + + // Skyblock Events + registerEvent(pluginManager, new SkyblockEvent(interneAPI)); + } + + /** + * Helper method to register a listener with the plugin's PluginManager. + * + * @param pluginManager the PluginManager + * @param listener the listener to register + */ + private void registerEvent(PluginManager pluginManager, Listener listener) { + pluginManager.registerEvents(listener, plugin); + } +} diff --git a/plugin/src/main/java/fr/euphyllia/skyllia/listeners/bukkitevents/player/JoinEvent.java b/plugin/src/main/java/fr/euphyllia/skyllia/listeners/bukkitevents/player/JoinEvent.java index aab72fd..f15fb2f 100644 --- a/plugin/src/main/java/fr/euphyllia/skyllia/listeners/bukkitevents/player/JoinEvent.java +++ b/plugin/src/main/java/fr/euphyllia/skyllia/listeners/bukkitevents/player/JoinEvent.java @@ -26,6 +26,7 @@ public class JoinEvent implements Listener { public JoinEvent(InterneAPI interneAPI) { this.api = interneAPI; + } @EventHandler