diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bc8ada1..7af44ee 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: JavaDocs on: push: - branches: [ main] + branches: [ main ] workflow_dispatch: permissions: diff --git a/pom.xml b/pom.xml index c4f017d..646045c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 @@ -23,8 +23,8 @@ maven-compiler-plugin 3.8.1 - ${java.version} - ${java.version} + 17 + 17 diff --git a/src/main/java/pro/cloudnode/smp/nations/Nations.java b/src/main/java/pro/cloudnode/smp/nations/Nations.java index 65e1c4a..9966d79 100644 --- a/src/main/java/pro/cloudnode/smp/nations/Nations.java +++ b/src/main/java/pro/cloudnode/smp/nations/Nations.java @@ -1,20 +1,74 @@ package pro.cloudnode.smp.nations; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import pro.cloudnode.smp.nations.commands.NationsCommand; +import pro.cloudnode.smp.nations.listeners.ChatMessageListener; +import pro.cloudnode.smp.nations.listeners.PlayerConnectionListener; +import pro.cloudnode.smp.nations.locale.Messages; +import pro.cloudnode.smp.nations.util.NationManager; import java.util.Objects; public final class Nations extends JavaPlugin { + public static NationManager nationManager; + + public static @NotNull NationManager getNationManager() { + return nationManager; + } + + /** + * Translate a message to a component and replace placeholders + * + * @param message the message to translate + * @param args the arguments to replace placeholders with + * @return the translated component + */ + public static @NotNull Component t(@NotNull Messages message, @NotNull Object... args) { + return MiniMessage.miniMessage().deserialize(message.replacePlaceholders(args)); + } + @Override public void onEnable() { - Objects.requireNonNull(this.getCommand("nations")).setExecutor(new NationsCommand(this)); - Objects.requireNonNull(this.getCommand("nations")).setTabCompleter(new NationsCommand(this)); + // save messages.yml if it doesn't exist + Messages.save(); + Messages.addMissingDefaults(); + Messages.load(); + + // load nations + nationManager = new NationManager(this); + nationManager.load(); + + // register commands + NationsCommand nationsCommand = new NationsCommand(); + Objects.requireNonNull(this.getCommand("nations")).setExecutor(nationsCommand); + Objects.requireNonNull(this.getCommand("nations")).setTabCompleter(nationsCommand); + + // register chat listener + getServer().getPluginManager().registerEvents(new ChatMessageListener(), this); + getServer().getPluginManager().registerEvents(new PlayerConnectionListener(), this); } @Override public void onDisable() { + nationManager.save(); + } + + public static Nations getInstance() { + return getPlugin(Nations.class); + } + + @ApiStatus.Experimental + public static String getVersion() { + return getInstance().getPluginMeta().getVersion(); + } + @ApiStatus.Experimental + public static String getMinecraftVersion() { + return getInstance().getPluginMeta().getAPIVersion(); } } diff --git a/src/main/java/pro/cloudnode/smp/nations/commands/NationsCommand.java b/src/main/java/pro/cloudnode/smp/nations/commands/NationsCommand.java index af566cc..e68b321 100644 --- a/src/main/java/pro/cloudnode/smp/nations/commands/NationsCommand.java +++ b/src/main/java/pro/cloudnode/smp/nations/commands/NationsCommand.java @@ -1,22 +1,371 @@ package pro.cloudnode.smp.nations.commands; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import pro.cloudnode.smp.nations.Nations; +import pro.cloudnode.smp.nations.locale.Messages; import pro.cloudnode.smp.nations.util.BaseCommand; +import pro.cloudnode.smp.nations.util.Nation; +import pro.cloudnode.smp.nations.util.NationManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static pro.cloudnode.smp.nations.Nations.t; public class NationsCommand extends BaseCommand { - public NationsCommand(@NotNull Nations plugin) { - super(plugin); + private final @NotNull List COLORS = List.of("white", "red", "blue", "green", "yellow", "light_purple", "aqua", "pink", "gray", "dark_gray", "dark_red", "dark_blue", "dark_green", "dark_aqua", "dark_purple"); + private final @NotNull List EMPTY = new ArrayList<>(); + + public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (!isPlayer()) { + return sendMessage(t(Messages.ONLY_PLAYERS)); + } + + if (args.length == 0) { + help(sender, label, args); + return false; + } + + return switch (args[0]) { + case "create" -> newNation(sender, label, args); + case "invite" -> invite(sender, label, args); + case "kick" -> kick(sender, label, args); + case "list" -> list(sender, label, args); + case "quit", "leave" -> quit(sender, label, args); + case "info" -> info(sender, label, args); + case "option" -> option(sender, label, args); + case "join" -> join(sender, label, args); + case "reload" -> reload(sender, label, args); + case "force-delete" -> forceDelete(sender, label, args); + case "cancel-invite" -> cancelInvite(sender, label, args); + default -> help(sender, label, args); + }; + } + + private boolean cancelInvite(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "cancel-invite", "")); + } + + Nation nation = NationManager.getPlayerNation(getPlayer().getUniqueId()); + if (nation == null) { + return sendMessage(t(Messages.NOT_IN_NATION)); + } + + if (!nation.leader.equals(getPlayer().getUniqueId())) { + return sendMessage(t(Messages.NOT_LEADER)); + } + + Player player = Bukkit.getPlayer(args[1]); + if (player == null) { + return sendMessage(t(Messages.PLAYER_NOT_FOUND, args[1])); + } + + if (!nation.invited.contains(player.getUniqueId())) { + return sendMessage(t(Messages.PLAYER_NOT_INVITED, player)); + } + + nation.removeInvited(player.getUniqueId()); + return sendMessage(t(Messages.CANCELED_INVITE, player)); } - public void execute(CommandSender sender, String label, String[] args) { + // help for each command + private boolean help(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + // send plugin info if (args.length == 0) { - //@todo: send help message - sendMessage("Usage: /" + label + " [create|invite|kick|list]"); - return; + sendMessage(t(Messages.PLUGIN_INFO, Nations.getVersion(), Nations.getMinecraftVersion())); + return true; + } + + switch (args[1]) { + case "create" -> sendMessage(t(Messages.USAGE, label, "create", "")); + case "invite" -> sendMessage(t(Messages.USAGE, label, "invite", "")); + case "kick" -> sendMessage(t(Messages.USAGE, label, "kick", "")); + case "list" -> sendMessage(t(Messages.USAGE, label, "list", "")); + case "quit", "leave" -> sendMessage(t(Messages.USAGE, label, "quit", "")); + case "info" -> sendMessage(t(Messages.USAGE, label, "info", "")); + case "option" -> sendMessage(t(Messages.USAGE, label, "option", " ")); + case "join" -> sendMessage(t(Messages.USAGE, label, "join", "")); + case "cancel-invite" -> sendMessage(t(Messages.USAGE, label, "cancel-invite", "")); + default -> help(sender, label, new String[0]); + } + + return true; + } + + private boolean join(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "join", "")); + } + + if (NationManager.isInNation((Player) sender)) { + return sendMessage(t(Messages.ALREADY_IN_NATION)); + } + + Nation nation = Nations.getNationManager().get(args[1]); + if (nation == null) { + return sendMessage(t(Messages.NATION_NOT_FOUND, args[1])); + } + + if (!nation.invited.contains(getPlayer().getUniqueId())) { + return sendMessage(t(Messages.NOT_INVITED_TO_NATION, nation)); + } + + nation.addMember(getPlayer().getUniqueId()); + nation.removeInvited(getPlayer().getUniqueId()); + return sendMessage(t(Messages.JOINED_NATION, nation)); + } + + private boolean option(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "option", " ")); + } + + Nation nation = NationManager.getPlayerNation(getPlayer().getUniqueId()); + if (nation == null) { + return sendMessage(t(Messages.NOT_IN_NATION)); + } + + if (!nation.leader.equals(getPlayer().getUniqueId())) { + return sendMessage(t(Messages.NOT_LEADER)); + } + + String key = args[1]; + + if (key.equals("color")) { + if (args.length == 2) { + return sendMessage(t(Messages.USAGE, label, "option", "color ")); + } + String color = args[2].toLowerCase(); + boolean isHex = color.matches("^#[0-9a-f]{6}$"); + if (!COLORS.contains(color) && !isHex) { + return sendMessage(t(Messages.INVALID_COLOR)); + } + + nation.color = color; + sendMessage(t(Messages.COLOR_SET, color)); + nation.update(); + } else { + sendMessage(t(Messages.USAGE, label, "option", " ")); + } + + return true; + } + + private boolean info(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length > 1) { + return sendMessage(t(Messages.USAGE, label, "info", "")); + } + + Nation nation = NationManager.getPlayerNation(getPlayer().getUniqueId()); + if (nation == null) { + return sendMessage(t(Messages.NOT_IN_NATION)); + } + + sendMessage(t(Messages.INFO_HEADER, nation)); + sendMessage(t(Messages.INFO_LEADER, Objects.requireNonNull(Bukkit.getPlayer(nation.leader)).getName())); + return sendMessage(t(Messages.INFO_MEMBERS, nation.members.stream().map(Bukkit::getPlayer).filter(Objects::nonNull).map(Player::getName).toList().toString())); + } + + private boolean quit(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length > 1) { + return sendMessage(t(Messages.USAGE, label, "quit", "")); + } + + Nation nation = NationManager.getPlayerNation(getPlayer().getUniqueId()); + if (nation == null) { + return sendMessage(t(Messages.NOT_IN_NATION)); + } + + if (nation.members.isEmpty() && nation.leader.equals(getPlayer().getUniqueId())) { + sendMessage(t(Messages.NATION_DISBANDED, nation)); + Nations.getNationManager().remove(nation); + return true; + } + + if (nation.leader.equals(getPlayer().getUniqueId())) { + return sendMessage(t(Messages.CANT_QUIT_AS_LEADER)); + } + + sendMessage(t(Messages.QUIT_NATION, nation)); + nation.removeMember(getPlayer().getUniqueId()); + + return true; + } + + private boolean list(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + sendMessage(t(Messages.LIST_HEADER)); + if (NationManager.nations.isEmpty()) { + return sendMessage(t(Messages.NO_NATIONS)); + } + for (Nation nation : NationManager.nations.values()) { + sendMessage(t(Messages.LIST_ITEM, nation)); + } + return true; + } + + private boolean forceDelete(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("nations.admin") && !sender.hasPermission("nations.forceDelete")) { + return sendMessage(t(Messages.NO_PERMISSION)); + } + + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "force-delete", "")); + } + + Nation nation = Nations.getNationManager().get(args[1]); + if (nation == null) { + return sendMessage(t(Messages.NATION_NOT_FOUND, args[1])); + } + + Nations.getNationManager().remove(nation); + return sendMessage(t(Messages.NATION_DELETED, nation)); + } + + private boolean reload(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("nations.admin") && !sender.hasPermission("nations.reload")) { + return sendMessage(t(Messages.NO_PERMISSION)); + } + if (args.length > 1) { + return sendMessage(t(Messages.USAGE, label, "reload", "")); + } + + Nations.getPlugin(Nations.class).reloadConfig(); + Messages.load(); + return sendMessage(t(Messages.RELOADED)); + } + + private boolean kick(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "kick", "")); } + Nation nation = NationManager.getPlayerNation(getPlayer().getUniqueId()); + if (nation == null) { + return sendMessage(t(Messages.NOT_IN_NATION)); + } + + if (!nation.leader.equals(getPlayer().getUniqueId())) { + return sendMessage(t(Messages.NOT_LEADER)); + } + + Player player = Bukkit.getPlayer(args[1]); + if (player == null) { + return sendMessage(t(Messages.PLAYER_NOT_FOUND, args[1])); + } + + if (!nation.members.contains(player.getUniqueId())) { + return sendMessage(t(Messages.PLAYER_NOT_IN_NATION, player)); + } + + nation.removeMember(player.getUniqueId()); + sendMessage(t(Messages.YOU_HAVE_KICKED, player)); + return sendMessage(player, t(Messages.YOU_HAVE_BEEN_KICKED, nation)); + } + + private boolean invite(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "invite", "")); + } + + Nation nation = NationManager.getPlayerNation(getPlayer().getUniqueId()); + if (nation == null) { + return sendMessage(t(Messages.NOT_IN_NATION)); + } + + if (!nation.leader.equals(getPlayer().getUniqueId())) { + return sendMessage(t(Messages.NOT_LEADER)); + } + + Player player = Bukkit.getPlayer(args[1]); + if (player == null) { + return sendMessage(t(Messages.PLAYER_NOT_FOUND, args[1])); + } + Nation inviteeNation = NationManager.getPlayerNation(player.getUniqueId()); + + if (inviteeNation != null) { + return sendMessage(t(Messages.PLAYER_ALREADY_IN_NATION, player)); + } + + if (nation.invited.contains(player.getUniqueId())) { + return sendMessage(t(Messages.PLAYER_ALREADY_INVITED, player)); + } + + nation.addInvited(player.getUniqueId()); + sendMessage(t(Messages.INVITED_PLAYER, player)); + return sendMessage(player, t(Messages.YOU_HAVE_BEEN_INVITED, nation)); + } + + public boolean newNation(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length <= 1) { + return sendMessage(t(Messages.USAGE, label, "create", "")); + } + + if (NationManager.isInNation((Player) sender)) { + return sendMessage(t(Messages.ALREADY_IN_NATION)); + } + + String name = args[1]; + + // check if name is valid (ascii) + if (!name.matches("^[a-zA-Z0-9]*$") || name.length() > 16) { + return sendMessage(t(Messages.INVALID_NAME)); + } + + // create nation + Nation nation = new Nation(name, getPlayer().getUniqueId()); + + // save nation + Nations.getNationManager().add(nation); + + // send message + return sendMessage(t(Messages.NEW_NATION, nation)); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + Player player = (Player) sender; + Stream commands = Stream.of("list", "help"); + Nation nation = null; + if (NationManager.isNationLeader(player.getUniqueId())) + commands = Stream.concat(commands, Stream.of("kick", "invite", "cancel-invite", "option")); + if (NationManager.isInNation(player.getUniqueId())) { + commands = Stream.concat(commands, Stream.of("quit", "info")); + nation = NationManager.getPlayerNation(player.getUniqueId()); + } else commands = Stream.concat(commands, Stream.of("join", "create")); + if (sender.hasPermission("nations.admin")) + commands = Stream.concat(commands, Stream.of("force-delete", "reload")); + + // sort alphabetically + commands = commands.sorted(); + return switch (args.length) { + case 1 -> commands.filter(s -> s.startsWith(args[0])).toList(); + case 2 -> switch (args[0]) { + case "create" -> List.of(""); + case "invite", "kick" -> null; + case "option" -> List.of("color"); + case "join" -> Nations.getNationManager().getNationsInviting(player.getUniqueId()); + case "force-delete" -> Nations.getNationManager().getNations(); + case "cancel-invite" -> { + if (nation == null) yield EMPTY; + yield NationManager.getNationInvites(nation).stream().map(Player::getName).toList(); + } + case "help" -> commands.filter(s -> s.startsWith(args[1])).toList(); + default -> EMPTY; + }; + case 3 -> switch (args[1]) { + case "color" -> COLORS.stream().filter(s -> s.startsWith(args[2])).toList(); + default -> EMPTY; + }; + default -> EMPTY; + }; } } diff --git a/src/main/java/pro/cloudnode/smp/nations/listeners/ChatMessageListener.java b/src/main/java/pro/cloudnode/smp/nations/listeners/ChatMessageListener.java new file mode 100644 index 0000000..8065de3 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/nations/listeners/ChatMessageListener.java @@ -0,0 +1,36 @@ +package pro.cloudnode.smp.nations.listeners; + +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import pro.cloudnode.smp.nations.locale.Messages; +import pro.cloudnode.smp.nations.util.Nation; +import pro.cloudnode.smp.nations.util.NationManager; + +import static pro.cloudnode.smp.nations.Nations.t; + +public class ChatMessageListener implements Listener { + + @EventHandler + public void onChatMessage(AsyncChatEvent event) { + Player player = event.getPlayer(); + Nation nation = NationManager.getPlayerNation(player.getUniqueId()); + Component message; + + if (nation == null) { + message = t(Messages.CHAT_FORMAT, player, MiniMessage.miniMessage().serialize(event.message())); + } else { + message = t(Messages.CHAT_FORMAT_NATION, nation, player, MiniMessage.miniMessage().serialize(event.message())); + } + event.setCancelled(true); + + + for (Audience recipient : event.viewers()) { + recipient.sendMessage(message); + } + } +} diff --git a/src/main/java/pro/cloudnode/smp/nations/listeners/PlayerConnectionListener.java b/src/main/java/pro/cloudnode/smp/nations/listeners/PlayerConnectionListener.java new file mode 100644 index 0000000..e2528a3 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/nations/listeners/PlayerConnectionListener.java @@ -0,0 +1,38 @@ +package pro.cloudnode.smp.nations.listeners; + + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import pro.cloudnode.smp.nations.locale.Messages; +import pro.cloudnode.smp.nations.util.Nation; +import pro.cloudnode.smp.nations.util.NationManager; + +import static pro.cloudnode.smp.nations.Nations.t; + +public class PlayerConnectionListener implements Listener { + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + NationManager.updatePlayersName(player.getUniqueId()); + + Nation n = NationManager.getPlayerNation(player.getUniqueId()); + // set join message + if (n != null) { + event.joinMessage(t(Messages.PLAYER_JOIN_SERVER, player, n)); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + Nation n = NationManager.getPlayerNation(player.getUniqueId()); + // set join message + if (n != null) { + event.quitMessage(t(Messages.PLAYER_LEAVE_SERVER, player, n)); + } + } +} diff --git a/src/main/java/pro/cloudnode/smp/nations/locale/Messages.java b/src/main/java/pro/cloudnode/smp/nations/locale/Messages.java new file mode 100644 index 0000000..a930234 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/nations/locale/Messages.java @@ -0,0 +1,177 @@ +package pro.cloudnode.smp.nations.locale; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import pro.cloudnode.smp.nations.Nations; +import pro.cloudnode.smp.nations.util.Nation; + +import java.io.File; + +public enum Messages { + USAGE("usage", "Usage: /$0 $1 $2"), + NO_PERMISSION("no-permission", "(!) You do not have permission to use this command."), + + NATION_NOT_FOUND("nation-not-found", "(!) Nation $0 not found."), + + NOT_INVITED_TO_NATION("not-invited-to-nation", "(!) You are not invited to "), + JOINED_NATION("joined-nation", "You have joined the nation "), + NOT_IN_NATION("not-in-nation", "(!) You are not in a nation."), + NOT_LEADER("not-leader", "(!) You are not the leader of your nation."), + COLOR_SET("color-set", "You have set your nation color to <$0>$0"), + INVALID_COLOR("invalid-color", "(!) Invalid color, must be a hex code (#RRGGBB) or a color name."), + + INFO_HEADER("info-header", "Info for "), + INFO_LEADER("info-leader", "Leader: $0"), + INFO_MEMBERS("info-members", "Members: $0"), + INFO_COLOR("info-color", "Color: "), + + LIST_HEADER("list-header", "Nations:"), + + LIST_ITEM("list-item", "Leader: Members: '>"), + + NATION_DISBANDED("nation-disbanded", "(!) Your nation () has been disbanded."), + + CANT_QUIT_AS_LEADER("cant-quit-as-leader", "(!) You cannot quit the nation because you are the leader. Use /nations kick to kick all members."), + + QUIT_NATION("quit-nation", "You have quit the nation "), + + NO_NATIONS("no-nations", "There are no nations."), + YOU_HAVE_KICKED("you-have-kicked", "(!) You have kicked from your nation."), + YOU_HAVE_BEEN_KICKED("you-have-been-kicked", "(!) You have been kicked from "), + PLAYER_NOT_FOUND("player-not-found", "(!) Player $0 Not found."), + PLAYER_NOT_IN_NATION("player-not-in-nation", "(!) Player is not in your nation."), + INVALID_NAME("invalid-name", "(!) Invalid name, only alphanumeric characters are allowed and may not be longer than 16 characters."), + NEW_NATION("new-nation", "You have created the nation "), + ONLY_PLAYERS("only-players", "(!) Only players can use this command."), + + COMMANDS_HEADER("commands-header", "Commands:"), + COMMANDS_ITEM("commands-item", "- /nations $0 $1 ($2)"), + PLAYER_ALREADY_IN_NATION("player-already-in-nation", "(!) Player is already in a nation."), + PLAYER_ALREADY_INVITED("player-already-invited", "(!) Player is already invited to your nation."), + INVITED_PLAYER("invited-player", "You have invited to your nation."), + YOU_HAVE_BEEN_INVITED("you-have-been-invited", "You have been invited to . Use /nations join to join."), + CHAT_FORMAT_NATION("chat-format-nation", "() » $2"), + CHAT_FORMAT("chat-format", " » $1"), + NATION_DELETED("nation-deleted", "You have deleted the nation "), + RELOADED("reloaded", "Nations has been reloaded."), + ALREADY_IN_NATION("already-in-nation", "(!) You are already in a nation."), + NATION_DISPLAYNAME("nation-displayname", "() "), + PLAYER_DISPLAYNAME("player-displayname", ""), + PLAYER_JOIN_SERVER("player-join-server", "[+] () "), + PLAYER_LEAVE_SERVER("player-leave-server", "[-] () "), + PLAYER_NOT_INVITED("player-not-invited", "(!) Player is not invited to your nation."), + CANCELED_INVITE("canceled-invite", "You have canceled the invite for "), + PLUGIN_INFO("plugin-info", "Running SMPNations, v$0 for $1"); + + public final String key; + + public String default_value; + + Messages(String key, String default_value) { + this.key = key; + this.default_value = default_value; + } + + /** + * Save messages to the config + *

+ * This will *not* overwrite the config + * This saves into `plugins/Nations/messages.yml` + */ + public static void save() { + // check if messages.yml exists + File messagesFile = new File("plugins/Nations/messages.yml"); + if (messagesFile.exists()) return; + + YamlConfiguration config = new YamlConfiguration(); + + for (Messages message : Messages.values()) { + config.set("messages." + message.getKey(), message.getDefaultValue()); + } + + try { + config.save(new File("plugins/Nations/messages.yml")); + Nations.getPlugin(Nations.class).getLogger().info("Saved default messages.yml"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Load messages from the config + *
+ * This loads from `plugins/Nations/messages.yml` + */ + public static void load() { + // add missing first + addMissingDefaults(); + YamlConfiguration config = YamlConfiguration.loadConfiguration(new File("plugins/Nations/messages.yml")); + + for (Messages message : Messages.values()) { + message.default_value = config.getString("messages." + message.getKey()); + } + } + + /** + * Adds the default messages to the config if they don't exist + *
+ * This saves into `plugins/Nations/messages.yml` + */ + public static void addMissingDefaults() { + YamlConfiguration config = YamlConfiguration.loadConfiguration(new File("plugins/Nations/messages.yml")); + + for (Messages message : Messages.values()) { + if (!config.contains("messages." + message.getKey())) { + config.set("messages." + message.getKey(), message.getDefaultValue()); + } + } + + try { + config.save(new File("plugins/Nations/messages.yml")); + Nations.getPlugin(Nations.class).getLogger().info("Added missing messages to messages.yml"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public @NotNull String getKey() { + return key; + } + + public @NotNull String getDefaultValue() { + return default_value; + } + + /** + * Translate a message to a component and replace placeholders + * + * @param args the arguments to replace placeholders with + * @return the translated component + */ + public @NotNull String replacePlaceholders(@NotNull Object... args) { + String message = this.getDefaultValue(); + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + if (arg instanceof Player player) { + message = message.replace("", player.getName()) + .replace("", player.getUniqueId().toString()) + .replace("", player.displayName().toString()) + .replace("", player.getName()); + } else if (arg instanceof Nation nation) { + String leaderName = Bukkit.getOfflinePlayer(nation.leader).getName(); + leaderName = leaderName == null ? "Unknown" : leaderName; + message = message.replace("", nation.name) + .replace("", nation.leader.toString()) + .replace("", "<" + nation.color + ">") + .replace("", nation.uuid.toString()) + .replace("", leaderName) + .replace("", nation.members.size() + 1 + ""); + } else if (arg instanceof String str) { + message = message.replace("$" + i, str); + } + } + return message; + } +} diff --git a/src/main/java/pro/cloudnode/smp/nations/util/BaseCommand.java b/src/main/java/pro/cloudnode/smp/nations/util/BaseCommand.java index fb424ad..edcd3e3 100644 --- a/src/main/java/pro/cloudnode/smp/nations/util/BaseCommand.java +++ b/src/main/java/pro/cloudnode/smp/nations/util/BaseCommand.java @@ -1,6 +1,6 @@ package pro.cloudnode.smp.nations.util; -import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.Component; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -8,47 +8,62 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import pro.cloudnode.smp.nations.Nations; import java.util.List; public class BaseCommand implements CommandExecutor, TabCompleter { - private final @NotNull Nations plugin; - private CommandSender sender; + private @Nullable CommandSender sender; - public BaseCommand(@NotNull Nations plugin) { - this.plugin = plugin; - } - - public @NotNull Nations getPlugin() { - return plugin; - } - - public void execute(CommandSender sender, String label, String[] args) { + public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { throw new UnsupportedOperationException("Not implemented yet"); } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { this.sender = sender; - execute(sender, label, args); - return true; + return execute(sender, label, args); } + /** + * Check if the sender is a player + * + * @return Whether the sender is a player + */ public boolean isPlayer() { return !(this.sender == null) && this.sender instanceof Player; } + /** + * Get the sender a player + * + * @return The player + * You should check with {@link #isPlayer()} before using this method + */ public Player getPlayer() { return (Player) sender; } - public CommandSender getSender() { - return sender; + /** + * Send a message to the sender + * + * @param message The message to send + */ + public boolean sendMessage(@NotNull Component message) { + if (this.sender != null) { + this.sender.sendMessage(message); + } + return true; } - public void sendMessage(String message) { - this.sender.sendMessage(MiniMessage.miniMessage().deserialize(message)); + /** + * Send a message to a player + * + * @param player The player to send the message to + * @param message The message to send + */ + public boolean sendMessage(@NotNull Player player, @NotNull Component message) { + player.sendMessage(message); + return true; } @Override diff --git a/src/main/java/pro/cloudnode/smp/nations/util/Nation.java b/src/main/java/pro/cloudnode/smp/nations/util/Nation.java new file mode 100644 index 0000000..366483e --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/nations/util/Nation.java @@ -0,0 +1,121 @@ +package pro.cloudnode.smp.nations.util; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; + +public class Nation { + public final @NotNull UUID uuid; + public @NotNull String name; + public @NotNull UUID leader; + public @NotNull ArrayList members; + public @NotNull ArrayList invited; + public @NotNull String color; + + public Nation(@NotNull String name, @NotNull UUID leader) { + this.uuid = UUID.randomUUID(); + this.name = name; + this.leader = leader; + this.members = new ArrayList<>(); + this.invited = new ArrayList<>(); + this.color = "white"; + } + + public Nation(@NotNull String key, @NotNull FileConfiguration config) { + this.uuid = UUID.fromString(key); + this.load((YamlConfiguration) config); + } + + /** + * Save the nation to a config + * + * @param config The config to save to + */ + public void save(@NotNull YamlConfiguration config) { + config.set("nations." + uuid + ".name", name); + config.set("nations." + uuid + ".leader", leader.toString()); + config.set("nations." + uuid + ".members", Arrays.stream(members.toArray()).map(Object::toString).toArray()); + config.set("nations." + uuid + ".invited", Arrays.stream(invited.toArray()).map(Object::toString).toArray()); + config.set("nations." + uuid + ".color", color); + } + + /** + * Load a nation from the config + * + * @param config The config to load from + */ + public void load(@NotNull YamlConfiguration config) { + this.name = Objects.requireNonNull(config.getString("nations." + uuid + ".name")); + this.leader = UUID.fromString(Objects.requireNonNull(config.getString("nations." + uuid + ".leader"))); + this.members = new ArrayList<>(); + this.invited = new ArrayList<>(); + + String[] members = config.getStringList("nations." + uuid + ".members").toArray(new String[0]); + String[] invited = config.getStringList("nations." + uuid + ".invited").toArray(new String[0]); + + for (String member : members) { + this.members.add(UUID.fromString(member)); + } + + for (String invite : invited) { + this.invited.add(UUID.fromString(invite)); + } + + this.color = Objects.requireNonNull(config.getString("nations." + uuid + ".color")); + } + + /** + * Add a member to the nation + * + * @param uuid The UUID of the player to add + */ + public void addMember(@NotNull UUID uuid) { + members.add(uuid); + update(); + } + + /** + * Remove a member from the nation + * + * @param uuid The UUID of the player to remove + */ + public void removeMember(@NotNull UUID uuid) { + members.remove(uuid); + update(); + } + + /** + * Add an invited player + * + * @param uuid The UUID of the player to add + * This adds a player to a 'invited list', and does not add them to a nation + */ + public void addInvited(@NotNull UUID uuid) { + invited.add(uuid); + } + + /** + * Remove an invited player + * + * @param uuid The UUID of the player to remove + */ + public void removeInvited(@NotNull UUID uuid) { + invited.remove(uuid); + } + + + public void update() { + // update leader + NationManager.updatePlayersName(leader); + + // update members + for (UUID member : members) { + NationManager.updatePlayersName(member); + } + } +} diff --git a/src/main/java/pro/cloudnode/smp/nations/util/NationManager.java b/src/main/java/pro/cloudnode/smp/nations/util/NationManager.java new file mode 100644 index 0000000..c3d4770 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/nations/util/NationManager.java @@ -0,0 +1,165 @@ +package pro.cloudnode.smp.nations.util; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import pro.cloudnode.smp.nations.Nations; +import pro.cloudnode.smp.nations.locale.Messages; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +public class NationManager { + public static @NotNull HashMap nations = new HashMap<>(); + private final @NotNull Nations plugin; + + public NationManager(@NotNull Nations plugin) { + this.plugin = plugin; + } + + /** + * Get the nation of a player + * + * @param uuid The UUID of the player + * @return The nation, or null if not found + */ + public static @Nullable Nation getPlayerNation(@NotNull UUID uuid) { + for (Nation nation : nations.values()) { + if (nation.leader.equals(uuid)) { + return nation; + } + if (nation.members.contains(uuid)) { + return nation; + } + } + return null; + } + + /** + * Check if a player is in a nation + * + * @param uuid The UUID of the player + * @return Whether the player is in a nation + */ + public static boolean isInNation(@NotNull UUID uuid) { + return getPlayerNation(uuid) != null; + } + + /** + * Check if a player is in a nation + * + * @param player The player + * @return Whether the player is in a nation + */ + public static boolean isInNation(@NotNull Player player) { + return isInNation(player.getUniqueId()); + } + + public static void updatePlayersName(@NotNull UUID uuid) { + Player player = Nations.getPlugin(Nations.class).getServer().getPlayer(uuid); + if (player == null) return; + Nation nation = getPlayerNation(player.getUniqueId()); + if (nation == null) { + player.displayName(Nations.t(Messages.PLAYER_DISPLAYNAME, player)); + } else { + player.displayName(Nations.t(Messages.NATION_DISPLAYNAME, nation, player)); + } + player.playerListName(player.displayName()); + } + + public static boolean isNationLeader(@NotNull UUID uuid) { + Nation nation = getPlayerNation(uuid); + if (nation == null) return false; + return nation.leader.equals(uuid); + } + + public static List getNationInvites(Nation nation) { + return nation.invited.stream().map(uuid -> Nations.getPlugin(Nations.class).getServer().getPlayer(uuid)).toList(); + } + + /** + * Add a nation + * + * @param nation The nation to add + */ + public void add(Nation nation) { + nations.put(nation.uuid, nation); + updatePlayersName(nation.leader); + } + + /** + * Remove a nation + * + * @param nation The nation to remove + */ + public void remove(Nation nation) { + UUID leader = nation.leader; + nations.remove(nation.uuid); + updatePlayersName(leader); + } + + /** + * Get a nation by name + * + * @param name The name of the nation + * @return The nation, or null if not found + */ + public Nation get(String name) { + for (Nation nation : nations.values()) { + if (nation.name.equals(name)) { + return nation; + } + } + return null; + } + + /** + * Save nations to the config + *

+ * This will overwrite the config + * This saves into `plugins/Nations/nations.yml` + */ + public void save() { + // save nations + YamlConfiguration config = new YamlConfiguration(); + if (!nations.isEmpty()) { + nations.forEach((uuid, nation) -> nation.save(config)); + } + + // write to plugins/Nations/nations.yml + try { + File file = new File(this.plugin.getDataFolder(), "nations.yml"); + config.save(file); + } catch (Exception e) { + this.plugin.getLogger().warning("Failed to save nations.yml"); + this.plugin.getLogger().warning(e.getMessage()); + } + } + + public List getNations() { + return nations.values().stream().map(nation -> nation.name).toList(); + } + + public List getNationsInviting(UUID uuid) { + return nations.values().stream().filter(nation -> nation.invited.contains(uuid)).map(nation -> nation.name).toList(); + } + + /** + * Load nations from the config + */ + public void load() { + File file = new File(this.plugin.getDataFolder(), "nations.yml"); + if (file.exists()) { + FileConfiguration config = YamlConfiguration.loadConfiguration(file); + if (config.getConfigurationSection("nations") != null) { + Objects.requireNonNull(config.getConfigurationSection("nations")).getKeys(false).forEach(key -> add(new Nation(key, config))); + } + } + } + +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 230e246..8882996 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,5 +5,5 @@ api-version: '1.20' commands: nations: description: Main command for Nations - aliases: [n, nation] - usage: / \ No newline at end of file + aliases: [ n, nation ] + usage: /