package net.william278.husksync; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.william278.annotaml.Annotaml; import net.william278.desertwell.Version; import net.william278.husksync.command.BukkitCommand; import net.william278.husksync.command.BukkitCommandType; import net.william278.husksync.command.Permission; import net.william278.husksync.config.Locales; import net.william278.husksync.config.Settings; import net.william278.husksync.data.CompressedDataAdapter; import net.william278.husksync.data.DataAdapter; import net.william278.husksync.data.JsonDataAdapter; import net.william278.husksync.database.Database; import net.william278.husksync.database.MySqlDatabase; import net.william278.husksync.event.BukkitEventCannon; import net.william278.husksync.event.EventCannon; import net.william278.husksync.hook.PlanHook; import net.william278.husksync.listener.BukkitEventListener; import net.william278.husksync.listener.EventListener; import net.william278.husksync.migrator.LegacyMigrator; import net.william278.husksync.migrator.Migrator; import net.william278.husksync.migrator.MpdbMigrator; import net.william278.husksync.player.BukkitPlayer; import net.william278.husksync.player.OnlineUser; import net.william278.husksync.redis.RedisManager; import net.william278.husksync.util.BukkitLogger; import net.william278.husksync.util.BukkitResourceReader; import net.william278.husksync.util.Logger; import net.william278.husksync.util.ResourceReader; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; import org.bukkit.entity.Player; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.stream.Collectors; public class BukkitHuskSync extends JavaPlugin implements HuskSync { /** * Metrics ID for HuskSync on Bukkit. */ private static final int METRICS_ID = 13140; private Database database; private RedisManager redisManager; private Logger logger; private ResourceReader resourceReader; private EventListener eventListener; private DataAdapter dataAdapter; private EventCannon eventCannon; private Settings settings; private Locales locales; private List availableMigrators; private BukkitAudiences audiences; private static BukkitHuskSync instance; /** * (Internal use only) Returns the instance of the implementing Bukkit plugin * * @return the instance of the Bukkit plugin */ public static BukkitHuskSync getInstance() { return instance; } @Override public void onLoad() { instance = this; } @Override public void onEnable() { // Initialize HuskSync final AtomicBoolean initialized = new AtomicBoolean(true); try { // Set the logging adapter and resource reader this.logger = new BukkitLogger(this.getLogger()); this.resourceReader = new BukkitResourceReader(this); // Create adventure audience this.audiences = BukkitAudiences.create(this); // Load settings and locales getLoggingAdapter().log(Level.INFO, "Loading plugin configuration settings & locales..."); initialized.set(reload().join()); if (initialized.get()) { logger.showDebugLogs(settings.debugLogging); getLoggingAdapter().log(Level.INFO, "Successfully loaded plugin configuration settings & locales"); } else { throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales"); } // Prepare data adapter if (settings.compressData) { dataAdapter = new CompressedDataAdapter(); } else { dataAdapter = new JsonDataAdapter(); } // Prepare event cannon eventCannon = new BukkitEventCannon(); // Prepare migrators availableMigrators = new ArrayList<>(); availableMigrators.add(new LegacyMigrator(this)); final Plugin mySqlPlayerDataBridge = Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge"); if (mySqlPlayerDataBridge != null) { availableMigrators.add(new MpdbMigrator(this, mySqlPlayerDataBridge)); } // Prepare database connection this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter, eventCannon); getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the database..."); initialized.set(this.database.initialize()); if (initialized.get()) { getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the database"); } else { throw new HuskSyncInitializationException("Failed to establish a connection to the database. " + "Please check the supplied database credentials in the config file"); } // Prepare redis connection this.redisManager = new RedisManager(this); getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server..."); initialized.set(this.redisManager.initialize().join()); if (initialized.get()) { getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the Redis server"); } else { throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " + "Please check the supplied Redis credentials in the config file"); } // Register events getLoggingAdapter().log(Level.INFO, "Registering events..."); this.eventListener = new BukkitEventListener(this); getLoggingAdapter().log(Level.INFO, "Successfully registered events listener"); // Register permissions getLoggingAdapter().log(Level.INFO, "Registering permissions & commands..."); Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager() .addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) { case EVERYONE -> PermissionDefault.TRUE; case NOBODY -> PermissionDefault.FALSE; case OPERATORS -> PermissionDefault.OP; }))); // Register commands for (final BukkitCommandType bukkitCommandType : BukkitCommandType.values()) { final PluginCommand pluginCommand = getCommand(bukkitCommandType.commandBase.command); if (pluginCommand != null) { new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand); } } getLoggingAdapter().log(Level.INFO, "Successfully registered permissions & commands"); // Hook into plan if (Bukkit.getPluginManager().getPlugin("Plan") != null) { getLoggingAdapter().log(Level.INFO, "Enabling Plan integration..."); new PlanHook(database, logger).hookIntoPlan(); getLoggingAdapter().log(Level.INFO, "Plan integration enabled!"); } // Hook into bStats metrics try { new Metrics(this, METRICS_ID); } catch (final Exception e) { getLoggingAdapter().log(Level.WARNING, "Skipped bStats metrics initialization due to an exception."); } // Check for updates if (settings.checkForUpdates) { getLoggingAdapter().log(Level.INFO, "Checking for updates..."); getLatestVersionIfOutdated().thenAccept(newestVersion -> newestVersion.ifPresent(newVersion -> getLoggingAdapter().log(Level.WARNING, "An update is available for HuskSync, v" + newVersion + " (Currently running v" + getPluginVersion() + ")"))); } } catch (HuskSyncInitializationException exception) { getLoggingAdapter().log(Level.SEVERE, """ *************************************************** Failed to initialize HuskSync! *************************************************** The plugin was disabled due to an error. Please check the logs below for details. No user data will be synchronised. *************************************************** Caused by: %error_message% """ .replaceAll("%error_message%", exception.getMessage())); initialized.set(false); } catch (Exception exception) { getLoggingAdapter().log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception); initialized.set(false); } finally { // Validate initialization if (initialized.get()) { getLoggingAdapter().log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion()); } else { getLoggingAdapter().log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled"); getServer().getPluginManager().disablePlugin(this); } } } @Override public void onDisable() { if (this.eventListener != null) { this.eventListener.handlePluginDisable(); } getLoggingAdapter().log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion()); } @Override public @NotNull Set getOnlineUsers() { return Bukkit.getOnlinePlayers().stream().map(BukkitPlayer::adapt).collect(Collectors.toSet()); } @Override public @NotNull Optional getOnlineUser(@NotNull UUID uuid) { final Player player = Bukkit.getPlayer(uuid); if (player == null) { return Optional.empty(); } return Optional.of(BukkitPlayer.adapt(player)); } @Override public @NotNull Database getDatabase() { return database; } @Override public @NotNull RedisManager getRedisManager() { return redisManager; } @Override public @NotNull DataAdapter getDataAdapter() { return dataAdapter; } @Override public @NotNull EventCannon getEventCannon() { return eventCannon; } @NotNull @Override public List getAvailableMigrators() { return availableMigrators; } @Override public @NotNull Settings getSettings() { return settings; } @Override public @NotNull Locales getLocales() { return locales; } @Override public @NotNull Logger getLoggingAdapter() { return logger; } @NotNull @Override public ResourceReader getResourceReader() { return resourceReader; } @NotNull @Override public Version getPluginVersion() { return Version.fromString(getDescription().getVersion(), "-"); } @NotNull @Override public Version getMinecraftVersion() { return Version.fromMinecraftVersionString(Bukkit.getBukkitVersion()); } /** * Returns the adventure Bukkit audiences * * @return The adventure Bukkit audiences */ @NotNull public BukkitAudiences getAudiences() { return audiences; } @Override public CompletableFuture reload() { return CompletableFuture.supplyAsync(() -> { try { // Load plugin settings this.settings = Annotaml.create(new File(getDataFolder(), "config.yml"), new Settings()).get(); // Load locales from language preset default final Locales languagePresets = Annotaml.create(Locales.class, Objects.requireNonNull(getResource("locales/" + settings.language + ".yml"))).get(); this.locales = Annotaml.create(new File(getDataFolder(), "messages_" + settings.language + ".yml"), languagePresets).get(); return true; } catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException | InstantiationException e) { getLoggingAdapter().log(Level.SEVERE, "Failed to load data from the config", e); return false; } }); } }