package cc.fascinated.account; import cc.fascinated.Aetheria; import cc.fascinated.account.command.SaveAccountsCommand; import cc.fascinated.bot.DiscordBot; import cc.fascinated.bot.DiscordChannel; import cc.fascinated.command.CommandManager; import cc.fascinated.config.Config; import cc.fascinated.config.Lang; import cc.fascinated.playercolor.PlayerColorProfile; import cc.fascinated.utils.Manager; import cc.fascinated.utils.Priority; import cc.fascinated.utils.Style; import com.viaversion.viaversion.api.Via; import lombok.extern.log4j.Log4j2; import net.dv8tion.jda.api.EmbedBuilder; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import java.awt.*; import java.util.List; import java.util.*; import java.util.concurrent.TimeUnit; @Log4j2 public class AccountManager extends Manager implements Listener { private static final Map ACCOUNTS = new HashMap<>(); private final long SAVE_INTERVAL = 5; // in minutes public AccountManager() { CommandManager.registerCommand(new SaveAccountsCommand()); Aetheria.INSTANCE.getServer().getPluginManager().registerEvents(this, Aetheria.INSTANCE); for (Player player : Bukkit.getOnlinePlayers()) { if (isAccountLoaded(player.getUniqueId())) { // Don't load the account if it's already loaded continue; } loadAccount(player.getUniqueId(), player.getName()); } Bukkit.getAsyncScheduler().runAtFixedRate(Aetheria.INSTANCE, (task) -> { saveAccounts(); }, SAVE_INTERVAL, SAVE_INTERVAL, TimeUnit.MINUTES); } /** * Gets a list of all online accounts. * * @return the online accounts */ public static List getOnlineAccounts() { List accounts = new ArrayList<>(); for (Account account : ACCOUNTS.values()) { if (account.isOnline()) { accounts.add(account); } } return accounts; } /** * Gets the account for the specified player. * * @param uuid the player's UUID * @return the account */ public static Account getAccount(UUID uuid) { return ACCOUNTS.get(uuid); } /** * Registers an account for the specified player. * * @param uuid the player's UUID * @param name the player's name */ private static void loadAccount(UUID uuid, String name) { Account account = new Account(uuid, name); ACCOUNTS.put(uuid, account); } /** * Save dirty accounts to disk. */ public static void saveAccounts() { long before = System.currentTimeMillis(), saved = 0; // The amount of accounts that were saved log.info("Saving accounts..."); List toRemove = new ArrayList<>(); for (Account account : ACCOUNTS.values()) { boolean didSave = account.save(); // Save the account if (didSave) { saved++; } if (!account.isOnline()) { toRemove.add(account); } } toRemove.forEach(account -> ACCOUNTS.remove(account.getUuid())); // Unload offline accounts log.info("Saved {}/{} accounts. ({}ms)", saved, ACCOUNTS.size(), System.currentTimeMillis() - before); } /** * Deletes an account from the server. * This will remove the account from memory and delete the account file. * * @param account the account to delete */ public void deleteAccount(Account account) { account.getFile().delete(); ACCOUNTS.remove(account.getUuid()); account.getPlayer().kick(Component.text("Your account has been deleted. Please reconnect.")); log.info("Deleted account for {}", account.getUuid()); } @Override public Priority getPriority() { return Priority.LOWEST; } /** * Checks if an account is already registered for the specified player. * * @param uuid the player's UUID * @return true if the account is already registered, false otherwise */ private boolean isAccountLoaded(UUID uuid) { return ACCOUNTS.containsKey(uuid); } @EventHandler public void onAsyncPlayerPreLoginEvent(AsyncPlayerPreLoginEvent event) { UUID uuid = event.getUniqueId(); String name = event.getName(); if (isAccountLoaded(uuid)) { // Account already loaded return; } loadAccount(uuid, name); // Load the account into memory } @Override public void onPlayerJoin(Account account, PlayerJoinEvent event) { String joinMessage = Lang.JOIN_MESSAGE.getAsString(); PlayerColorProfile playerColorProfile = account.getPlayerColorProfile(); if (!account.getPlayer().hasPlayedBefore()) { joinMessage = Lang.FIRST_JOIN_MESSAGE.getAsString(); DiscordBot.sendEmbed(DiscordChannel.PLAYER_LOGS, new EmbedBuilder() .setThumbnail("https://crafatar.com/avatars/" + account.getUuid()) .setColor(Color.GREEN) .setTitle("New Player Joined") .addField("Player", account.getName(), true) .addField("UUID", account.getUuid().toString(), true) ); } event.joinMessage(Style.getMiniMessage().deserialize(joinMessage .replace("%player%", account.getName()) .replace("player-color", playerColorProfile.getColor().toString()) )); // Check if the player is using an outdated version and send a warning message int version = Via.getAPI().getPlayerVersion(account.getUuid()); if (version < Config.VERSION_WARNING_VERSION.getAsInt()) { account.sendMessage(Style.getMiniMessage().deserialize(Config.VERSION_WARNING_MESSAGE.getAsString())); } } @Override public void onPlayerQuit(Account account, PlayerQuitEvent event) { event.quitMessage(Style.getMiniMessage().deserialize(Lang.QUIT_MESSAGE.getAsString() .replace("%player%", event.getPlayer().getName()) .replace("player-color", account.getPlayerColorProfile().getColor().toString()) )); } @Override public void onAetheriaDisable() { saveAccounts(); // Save the accounts to disk } }