diff --git a/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestoration.java b/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestoration.java index 2ef076e6..19024956 100644 --- a/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestoration.java +++ b/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestoration.java @@ -26,7 +26,7 @@ public class ArenaRestoration implements ArenaModuleInitializer { public static final EventActionType RESTORE_ARENA_ACTION = EventActionType.create("restore-arena", RestoreArenaAction.class, RestoreArenaAction::new); public static final Message NO_BOUNDS = Messages.error("arena-restoration-no-bounds", "You must first set the map bounds before executing this command!"); - public static final Message SCHEMATIC_CREATED = Messages.success("arena-restoration-schematic-created", "Schematic created for map {}."); + public static final Message SCHEMATIC_CREATED = Messages.success("arena-restoration-schematic-created", "Schematic created for map {}."); public static final Message FAILED_TO_CREATE_SCHEMATIC = Messages.error("arena-restoration-failed-to-create-schematic", "Failed to create schematic! Check the console for more information."); @EventHandler diff --git a/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestorationExecutor.java b/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestorationExecutor.java index 07b40b12..a3a4013a 100644 --- a/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestorationExecutor.java +++ b/module/arena-restoration/src/main/java/org/battleplugins/arena/module/restoration/ArenaRestorationExecutor.java @@ -30,7 +30,7 @@ public ArenaRestorationExecutor(ArenaRestoration module, Arena arena) { this.arena = arena; } - @ArenaCommand(commands = "schematic", description = "Creates a schematic for the specified arena from your clipboard.", permissionNode = "region") + @ArenaCommand(commands = "schematic", description = "Creates a schematic for the specified arena from the map bounds.", permissionNode = "region") public void region(Player player, Competition competition) { if (!(competition instanceof LiveCompetition liveCompetition)) { return; // Cannot restore a non-live competition diff --git a/module/classes/src/main/java/org/battleplugins/arena/module/classes/ClassesExecutor.java b/module/classes/src/main/java/org/battleplugins/arena/module/classes/ClassesExecutor.java index 1db43908..d8e40e1b 100644 --- a/module/classes/src/main/java/org/battleplugins/arena/module/classes/ClassesExecutor.java +++ b/module/classes/src/main/java/org/battleplugins/arena/module/classes/ClassesExecutor.java @@ -3,6 +3,7 @@ import org.battleplugins.arena.Arena; import org.battleplugins.arena.ArenaPlayer; import org.battleplugins.arena.command.ArenaCommand; +import org.battleplugins.arena.command.Argument; import org.battleplugins.arena.command.SubCommandExecutor; import org.battleplugins.arena.messages.Messages; import org.battleplugins.arena.options.types.BooleanArenaOption; @@ -21,7 +22,7 @@ public ClassesExecutor(Classes module, Arena arena) { } @ArenaCommand(commands = "equip", description = "Equip a class.", permissionNode = "equip") - public void equip(Player player, ArenaClass arenaClass) { + public void equip(Player player, @Argument(name = "class") ArenaClass arenaClass) { // Should not get here, but just as an additional safeguard // *just in case* if (!this.arena.isModuleEnabled(Classes.ID)) { diff --git a/module/join-messages/src/main/java/org/battleplugins/arena/module/joinmessages/JoinMessages.java b/module/join-messages/src/main/java/org/battleplugins/arena/module/joinmessages/JoinMessages.java index a7efcc03..487ab635 100644 --- a/module/join-messages/src/main/java/org/battleplugins/arena/module/joinmessages/JoinMessages.java +++ b/module/join-messages/src/main/java/org/battleplugins/arena/module/joinmessages/JoinMessages.java @@ -44,7 +44,7 @@ public void onJoin(ArenaJoinEvent event) { player.getPlayer(), event.getPlayer().getName(), Integer.toString(competition.getPlayers().size()), - Integer.toString(competition.getArena().getTeams().getTeamAmount().getMax()) + Integer.toString(competition.getMaxPlayers()) ); } else { PLAYER_JOINED_NO_LIMIT.send(player.getPlayer(), event.getPlayer().getName()); @@ -66,7 +66,7 @@ public void onLeave(ArenaLeaveEvent event) { player.getPlayer(), event.getPlayer().getName(), Integer.toString(competition.getPlayers().size()), - Integer.toString(competition.getArena().getTeams().getTeamAmount().getMax()) + Integer.toString(competition.getMaxPlayers()) ); } else { PLAYER_LEFT_NO_LIMIT.send(player.getPlayer(), event.getPlayer().getName()); diff --git a/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java b/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java index 9934aa69..a7829448 100644 --- a/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java +++ b/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java @@ -4,6 +4,8 @@ import org.battleplugins.arena.competition.PlayerRole; import org.battleplugins.arena.competition.PlayerStorage; import org.battleplugins.arena.event.player.ArenaStatChangeEvent; +import org.battleplugins.arena.event.player.ArenaTeamJoinEvent; +import org.battleplugins.arena.event.player.ArenaTeamLeaveEvent; import org.battleplugins.arena.stat.ArenaStat; import org.battleplugins.arena.stat.StatHolder; import org.battleplugins.arena.team.ArenaTeam; @@ -130,6 +132,14 @@ public ArenaTeam getTeam() { * @param team the team that this player is on */ public void setTeam(@Nullable ArenaTeam team) { + if (this.team != null) { + new ArenaTeamLeaveEvent(this, this.team).callEvent(); + } + + if (team != null) { + new ArenaTeamJoinEvent(this, team).callEvent(); + } + this.team = team; } diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java index fbaad434..d815243d 100644 --- a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java @@ -311,6 +311,15 @@ public Arena getArena(String name) { return this.arenas.get(name); } + /** + * Returns all the {@link Arena}s for the plugin. + * + * @return all the arenas for the plugin + */ + public List getArenas() { + return List.copyOf(this.arenas.values()); + } + /** * Registers the given {@link Arena}. * diff --git a/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java b/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java index 727694e2..b28ca440 100644 --- a/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java +++ b/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java @@ -1,5 +1,7 @@ package org.battleplugins.arena.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.battleplugins.arena.Arena; import org.battleplugins.arena.ArenaPlayer; import org.battleplugins.arena.competition.Competition; @@ -17,6 +19,10 @@ import org.battleplugins.arena.editor.type.MapOption; import org.battleplugins.arena.event.player.ArenaLeaveEvent; import org.battleplugins.arena.messages.Messages; +import org.battleplugins.arena.options.ArenaOptionType; +import org.battleplugins.arena.options.TeamSelection; +import org.battleplugins.arena.options.types.BooleanArenaOption; +import org.battleplugins.arena.team.ArenaTeam; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -182,12 +188,12 @@ public void leave(Player player) { Messages.ARENA_LEFT.send(player, arenaPlayer.getCompetition().getMap().getName()); } - @ArenaCommand(commands = "create", description = "Create a new arena.", permissionNode = "create") + @ArenaCommand(commands = "create", description = "Create a new map.", permissionNode = "create") public void create(Player player) { ArenaEditorWizards.MAP_CREATION.openWizard(player, this.arena); } - @ArenaCommand(commands = { "remove", "delete" }, description = "Removes an arena.", permissionNode = "remove") + @ArenaCommand(commands = { "remove", "delete" }, description = "Removes a map.", permissionNode = "remove") public void remove(Player player, CompetitionMap map) { if (!(map instanceof LiveCompetitionMap liveMap)) { Messages.NO_ARENA_WITH_NAME.send(player); @@ -209,7 +215,7 @@ public void remove(Player player, CompetitionMap map) { } @ArenaCommand(commands = "edit", description = "Edit an arena map.", permissionNode = "edit") - public void map(Player player, CompetitionMap map, MapOption option) { + public void map(Player player, CompetitionMap map, @Argument(name = "map option") MapOption option) { if (!(map instanceof LiveCompetitionMap liveMap)) { Messages.NO_ARENA_WITH_NAME.send(player); return; @@ -239,6 +245,106 @@ public void advance(Player player) { } } + @ArenaCommand(commands = "team", subCommands = { "join", "j" }, description = "Joins a team.", permissionNode = "team.join") + public void joinTeam(Player player, ArenaTeam team) { + ArenaPlayer arenaPlayer = ArenaPlayer.getArenaPlayer(player); + if (arenaPlayer == null) { + Messages.NOT_IN_ARENA.send(player); + return; + } + + if (this.arena.getTeams().isNonTeamGame()) { + Messages.CANNOT_JOIN_TEAM_SOLO.send(player); + return; + } + + if (this.arena.getTeams().getTeamSelection() != TeamSelection.PICK) { + Messages.TEAM_SELECTION_NOT_AVAILABLE.send(player); + return; + } + + if (!arenaPlayer.getCompetition().option(ArenaOptionType.TEAM_SELECTION) + .map(BooleanArenaOption::isEnabled) + .orElse(true)) { + Messages.TEAM_SELECTION_NOT_AVAILABLE.send(player); + return; + } + + if (team.equals(arenaPlayer.getTeam())) { + Messages.ALREADY_ON_THIS_TEAM.send(player, team.getFormattedName()); + return; + } + + LiveCompetition competition = arenaPlayer.getCompetition(); + if (!competition.getTeamManager().canJoinTeam(team)) { + Messages.TEAM_FULL.send(player, team.getFormattedName()); + return; + } + + competition.getTeamManager().joinTeam(arenaPlayer, team); + Messages.TEAM_JOINED.send(player, team.getFormattedName()); + } + + @ArenaCommand(commands = "team", subCommands = { "leave", "l" }, description = "Leaves your current team.", permissionNode = "team.leave") + public void leaveTeam(Player player) { + ArenaPlayer arenaPlayer = ArenaPlayer.getArenaPlayer(player); + if (arenaPlayer == null) { + Messages.NOT_IN_ARENA.send(player); + return; + } + + if (this.arena.getTeams().getTeamSelection() != TeamSelection.PICK) { + Messages.TEAM_SELECTION_NOT_AVAILABLE.send(player); + return; + } + + if (!arenaPlayer.getCompetition().option(ArenaOptionType.TEAM_SELECTION) + .map(BooleanArenaOption::isEnabled) + .orElse(true)) { + Messages.TEAM_SELECTION_NOT_AVAILABLE.send(player); + return; + } + + ArenaTeam team = arenaPlayer.getTeam(); + if (team == null) { + Messages.NOT_ON_TEAM.send(player); + return; + } + + LiveCompetition competition = arenaPlayer.getCompetition(); + competition.getTeamManager().leaveTeam(arenaPlayer); + + Messages.TEAM_LEFT.send(player, team.getFormattedName()); + } + + @ArenaCommand(commands = "team", subCommands = "list", description = "List all available teams.", permissionNode = "team.list") + public void listTeams(Player player) { + ArenaPlayer arenaPlayer = ArenaPlayer.getArenaPlayer(player); + if (arenaPlayer == null) { + Messages.NOT_IN_ARENA.send(player); + return; + } + + LiveCompetition competition = arenaPlayer.getCompetition(); + Set availableTeams = competition.getTeamManager().getTeams(); + if (availableTeams.isEmpty() || this.arena.getTeams().isNonTeamGame()) { + Messages.NO_TEAMS.send(player); + return; + } + + Messages.HEADER.sendCentered(player, "Teams"); + for (ArenaTeam team : availableTeams) { + int players = competition.getTeamManager().getNumberOfPlayersOnTeam(team); + int max = competition.getTeamManager().getMaximumTeamSize(team); + if (max == 0) { + continue; // Don't display empty teams + } + + String playersText = max == Integer.MAX_VALUE ? " (" + players + ")" : " (" + players + "/" + max + ")"; + player.sendMessage(Component.text("- ", NamedTextColor.GRAY).append(team.getFormattedName()).append(Component.text(playersText, Messages.PRIMARY_COLOR))); + } + } + @Override protected Object onVerifyArgument(CommandSender sender, String arg, Class parameter) { switch (parameter.getSimpleName().toLowerCase()) { @@ -253,6 +359,12 @@ protected Object onVerifyArgument(CommandSender sender, String arg, Class par case "competitionmap" -> { return this.arena.getPlugin().getMap(this.arena, arg); } + case "arenateam" -> { + return this.arena.getTeams().getAvailableTeams().stream() + .filter(team -> team.getName().equalsIgnoreCase(arg)) + .findFirst() + .orElse(null); + } } return super.onVerifyArgument(sender, arg, parameter); @@ -265,6 +377,10 @@ protected boolean onInvalidArgument(CommandSender sender, Class parameter, St Messages.NO_ARENA_WITH_NAME.send(sender); return true; } + case "arenateam" -> { + Messages.NO_TEAM_WITH_NAME.send(sender, input); + return true; + } } return super.onInvalidArgument(sender, parameter, input); @@ -283,6 +399,10 @@ protected List onVerifyTabComplete(String arg, Class parameter) { .map(CompetitionMap::getName) .distinct() .toList(); + } else if (parameter.getSimpleName().equalsIgnoreCase("arenateam")) { + return this.arena.getTeams().getAvailableTeams().stream() + .map(ArenaTeam::getName) + .toList(); } return super.onVerifyTabComplete(arg, parameter); @@ -291,9 +411,9 @@ protected List onVerifyTabComplete(String arg, Class parameter) { @Override protected String onGetUsageString(Class parameter) { return switch (parameter.getSimpleName().toLowerCase()) { - case "arena" -> " "; case "competition" -> " "; // Best name for player-facing values case "competitionmap" -> " "; + case "arenateam" -> " "; default -> super.onGetUsageString(parameter); }; } diff --git a/plugin/src/main/java/org/battleplugins/arena/command/BACommandExecutor.java b/plugin/src/main/java/org/battleplugins/arena/command/BACommandExecutor.java index 6d22fe7c..15b2f2ab 100644 --- a/plugin/src/main/java/org/battleplugins/arena/command/BACommandExecutor.java +++ b/plugin/src/main/java/org/battleplugins/arena/command/BACommandExecutor.java @@ -3,7 +3,6 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.JoinConfiguration; import net.kyori.adventure.text.event.ClickEvent; -import org.apache.commons.lang3.StringUtils; import org.battleplugins.arena.Arena; import org.battleplugins.arena.BattleArena; import org.battleplugins.arena.competition.CompetitionType; @@ -49,7 +48,7 @@ public void backups(Player player, @Argument(name = "player") String playerName) Messages.HEADER.sendCentered(player, Messages.INVENTORY_BACKUPS); List options = backups.stream().map(backup -> new OptionSelector.Option( - Messages.BACKUP_NUMBER.withContext(Integer.toString(backups.indexOf(backup) + 1)), + Messages.BACKUP_INFO.withContext(backup.getFormattedDate()), "/ba restore " + target.getName() + " " + (backups.indexOf(backup) + 1) )).toList(); OptionSelector.sendOptions(player, options, ClickEvent.Action.SUGGEST_COMMAND); @@ -73,7 +72,6 @@ public void restore(Player player, Player target, int backupIndex) { return; } - backup.restore(target); Messages.BACKUP_RESTORED.send(player, target.getName()); } @@ -147,7 +145,7 @@ public void stopAll(Player player) { } } - @ArenaCommand(commands = "schedule", description = "Schedules an event to start automatically.", permissionNode = "schedule") + @ArenaCommand(commands = "schedule", description = "Schedules an event to start at the specified time.", permissionNode = "schedule") public void schedule(Player player, Arena arena, Duration interval) { if (arena.getType() != CompetitionType.EVENT) { Messages.NOT_EVENT.send(player); diff --git a/plugin/src/main/java/org/battleplugins/arena/command/BaseCommandExecutor.java b/plugin/src/main/java/org/battleplugins/arena/command/BaseCommandExecutor.java index 077ca2ac..bb288eb8 100644 --- a/plugin/src/main/java/org/battleplugins/arena/command/BaseCommandExecutor.java +++ b/plugin/src/main/java/org/battleplugins/arena/command/BaseCommandExecutor.java @@ -8,7 +8,9 @@ import net.kyori.adventure.text.format.TextDecoration; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; +import org.battleplugins.arena.Arena; import org.battleplugins.arena.BattleArena; +import org.battleplugins.arena.config.DurationParser; import org.battleplugins.arena.config.ItemStackParser; import org.battleplugins.arena.config.ParseException; import org.battleplugins.arena.messages.Messages; @@ -20,6 +22,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -433,6 +436,14 @@ private Object verifyArgument(CommandSender sender, String arg, Class paramet default -> null; }; } + case "duration" -> { + try { + return DurationParser.parseDuration(arg); + } catch (ParseException e) { + ParseException.handle(e); + return null; + } + } case "material" -> { try { return ItemStackParser.deserializeSingular(arg); @@ -521,6 +532,7 @@ private List verifyTabComplete(String arg, Class parameter) { // lol no way we're listing all offline players Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()); case "world" -> Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList()); + case "arena" -> BattleArena.getInstance().getArenas().stream().map(Arena::getName).collect(Collectors.toList()); default -> new ArrayList<>(); }; @@ -594,10 +606,12 @@ private String getUsageString(Class parameter, @Nullable Argument argument) { return switch (parameter.getSimpleName().toLowerCase()) { case "string[]" -> "[string...] "; case "int", "double", "float" -> " "; + case "duration" -> " "; case "boolean" -> " "; case "material" -> " "; case "player", "offlineplayer" -> " "; case "world" -> " "; + case "arena" -> " "; default -> { for (SubCommandExecutor subCommandExecutor : this.subCommandExecutors) { String usage = subCommandExecutor.getUsageString(parameter); @@ -632,6 +646,7 @@ public final List onTabComplete(CommandSender sender, Command command, S try { if (args.length == 1) { + List commands = new ArrayList<>(); for (String cmd : this.commandMethods.keySet()) { CommandWrapper wrapper = this.commandMethods.get(cmd).iterator().next(); ArenaCommand arenaCommand = wrapper.getCommand(); @@ -644,22 +659,10 @@ public final List onTabComplete(CommandSender sender, Command command, S continue; } - completions.add(cmd); - } - - for (int i = 0; i < completions.size(); i++) { - String completion = completions.get(i); - if (!completion.toLowerCase().startsWith(args[0].toLowerCase())) { - completions.remove(completion); - i--; - } + commands.add(cmd); } - } - if (args.length == 2) { - if (!this.commandMethods.containsKey(args[0])) { - return List.of(); - } + StringUtil.copyPartialMatches(args[0], commands, completions); } if (args.length > 1) { @@ -670,25 +673,27 @@ public final List onTabComplete(CommandSender sender, Command command, S continue; } + boolean hasSubCommand = arenaCommand.subCommands().length > 0; + if (hasSubCommand && args.length == 2) { + StringUtil.copyPartialMatches(args[1], Arrays.asList(arenaCommand.subCommands()), completions); + continue; + } + Class[] requestedParams = wrapper.method.getParameterTypes(); - if (requestedParams.length < args.length) { + if (requestedParams.length < (hasSubCommand ? args.length - 1 : args.length)) { continue; } - Class requestedParam = requestedParams[args.length - 1]; - if (arenaCommand.subCommands().length > 0 && Arrays.asList(arenaCommand.subCommands()).contains(args[1])) { + Class requestedParam; + String token = args[args.length - 1]; + if (hasSubCommand && Arrays.asList(arenaCommand.subCommands()).contains(args[1])) { requestedParam = requestedParams[args.length - 2]; + } else { + requestedParam = requestedParams[args.length - 1]; } - completions.addAll(this.verifyTabComplete(args[args.length - 1], requestedParam)); - } - - for (int i = 0; i < completions.size(); i++) { - String completion = completions.get(i); - if (!completion.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) { - completions.remove(completion); - i--; - } + List subCompletions = new ArrayList<>(this.verifyTabComplete(token, requestedParam)); + StringUtil.copyPartialMatches(token, subCompletions, completions); } } } catch (Exception e) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java index 6022b7f4..9ab18a6e 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java @@ -4,6 +4,7 @@ import org.battleplugins.arena.ArenaLike; import org.battleplugins.arena.ArenaPlayer; import org.battleplugins.arena.competition.map.LiveCompetitionMap; +import org.battleplugins.arena.competition.map.options.Spawns; import org.battleplugins.arena.competition.phase.CompetitionPhase; import org.battleplugins.arena.competition.phase.CompetitionPhaseType; import org.battleplugins.arena.competition.phase.LiveCompetitionPhase; @@ -51,6 +52,8 @@ public class LiveCompetition> implements ArenaLike, Com private final CompetitionListener competitionListener; private final OptionsListener optionsListener; private final StatListener statListener; + + private final int maxPlayers; public LiveCompetition(Arena arena, CompetitionType type, LiveCompetitionMap map) { this.arena = arena; @@ -68,6 +71,9 @@ public LiveCompetition(Arena arena, CompetitionType type, LiveCompetitionMap map // Set the initial phase CompetitionPhaseType initialPhase = arena.getInitialPhase(); this.phaseManager.setPhase(initialPhase); + + // Calculate max players + this.maxPlayers = this.calculateMaxPlayers(); } // API methods @@ -78,32 +84,33 @@ public CompletableFuture canJoin(Player player, PlayerRole role) { // Check if the player can join the competition in its current state if (role == PlayerRole.PLAYING) { - if (!currentPhase.canJoin()){ + if (!currentPhase.canJoin()) { return CompletableFuture.completedFuture(JoinResult.NOT_JOINABLE); } // See if the player will fit within the player limits Teams teams = this.arena.getTeams(); - List availableTeams = teams.getAvailableTeams(); - IntRange teamSize = teams.getTeamSize(); - - for (ArenaTeam availableTeam : availableTeams) { - int playersOnTeam = this.teamManager.getNumberOfPlayersOnTeam(availableTeam); - if (playersOnTeam >= teamSize.getMax()) { - if (teams.isNonTeamGame()) { - // If the team selection is none and the default team is available, then - // we can just put the player on the default team. But first, we need to - // check to see if the default team is full - int teamSizeMax = teamSize.getMax(); - int teamAmount = teams.getTeamAmount().getMax(); - if (teamAmount == Integer.MAX_VALUE || teamSizeMax == Integer.MAX_VALUE || playersOnTeam < teamAmount * teamSizeMax) { - // Not full - allow them through - return CompletableFuture.completedFuture(JoinResult.SUCCESS); - } else { - // No available teams - return false - return CompletableFuture.completedFuture(JoinResult.ARENA_FULL); - } + + // If team selection involves the player picking their own + // team, or the game is not a team game, then we just need to check + // the overall maximum number of players this competition can have + if (teams.getTeamSelection() == TeamSelection.PICK || teams.isNonTeamGame()) { + // Player cannot join - arena is full + if ((this.getPlayers().size() + 1) > this.maxPlayers) { + return CompletableFuture.completedFuture(JoinResult.ARENA_FULL); + } + } else { + List availableTeams = teams.getAvailableTeams(); + // Otherwise, we need to go through all teams and see if there is room for the player + for (ArenaTeam availableTeam : availableTeams) { + // If we have less than the minimum amount of players on the team, then we can + // assume that this team has room and break + if (this.teamManager.canJoinTeam(availableTeam)) { + break; } + + // No available teams - return that the arena is full + return CompletableFuture.completedFuture(JoinResult.ARENA_FULL); } } } @@ -131,6 +138,7 @@ public void findAndJoinTeamIfApplicable(ArenaPlayer player) { // the player on the default team if (teams.isNonTeamGame()) { this.teamManager.joinTeam(player, ArenaTeams.DEFAULT); + return; } if (teams.getTeamSelection() == TeamSelection.RANDOM) { @@ -250,6 +258,15 @@ public final Set getPlayers() { public final Set getSpectators() { return Collections.unmodifiableSet(this.playersByRole.getOrDefault(PlayerRole.SPECTATING, Set.of())); } + + /** + * Gets the maximum amount of players that can join this competition. + * + * @return the maximum amount of players that can join this competition + */ + public final int getMaxPlayers() { + return this.maxPlayers; + } /** * Gets the {@link PhaseManager} responsible for managing the phases of the competition. @@ -319,4 +336,32 @@ protected void onDestroy() { private ArenaPlayer createPlayer(Player player) { return new ArenaPlayer(player, this.arena, this); } + + private int calculateMaxPlayers() { + Teams teams = this.arena.getTeams(); + int teamAmount = teams.isNonTeamGame() ? teams.getTeamAmount().getMax() : teams.getAvailableTeams().size(); + int teamSizeMax = teams.getTeamSize().getMax(); + int maxPlayers; + if (teamSizeMax == Integer.MAX_VALUE) { + maxPlayers = Integer.MAX_VALUE; + } else { + maxPlayers = teamAmount * teamSizeMax; + } + + Spawns spawns = this.map.getSpawns(); + + // If spawn points are not shared, that means we only have a limited + // amount of spawn points for each team. We need to check if the team + // is full based on the amount of spawn points available. + if (!teams.isSharedSpawnPoints() && spawns != null) { + maxPlayers = Math.min(maxPlayers, spawns.getSpawnPointCount()); + } + + // If we have zero spawns in any situation, this competition cannot hold any players + if (spawns == null || spawns.getSpawnPointCount() == 0) { + maxPlayers = 0; + } + + return maxPlayers; + } } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Spawns.java b/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Spawns.java index 4589a424..ea25f60e 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Spawns.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Spawns.java @@ -42,4 +42,33 @@ public final PositionWithRotation getSpectatorSpawn() { public final Map getTeamSpawns() { return this.teamSpawns; } + + public final int getSpawnPointCount() { + if (this.teamSpawns == null) { + return 0; + } + + int count = 0; + for (TeamSpawns spawns : this.teamSpawns.values()) { + if (spawns.getSpawns() != null) { + count += spawns.getSpawns().size(); + } + } + + return count; + } + + public final int getSpawnPointCount(String teamName) { + if (this.teamSpawns == null) { + return 0; + } + + int count = 0; + TeamSpawns spawns = this.teamSpawns.get(teamName); + if (spawns != null && spawns.getSpawns() != null) { + count = spawns.getSpawns().size(); + } + + return count; + } } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java index e892e3b5..cdecb7ab 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java @@ -2,6 +2,8 @@ import org.battleplugins.arena.ArenaPlayer; import org.battleplugins.arena.competition.LiveCompetition; +import org.battleplugins.arena.competition.map.LiveCompetitionMap; +import org.battleplugins.arena.competition.map.options.Spawns; import org.battleplugins.arena.options.Teams; import org.battleplugins.arena.stat.StatHolder; import org.battleplugins.arena.team.ArenaTeam; @@ -25,6 +27,10 @@ public class TeamManager { public TeamManager(LiveCompetition competition) { this.competition = competition; + + for (ArenaTeam availableTeam : competition.getArena().getTeams().getAvailableTeams()) { + this.teams.put(availableTeam, new HashSet<>()); + } } /** @@ -33,7 +39,11 @@ public TeamManager(LiveCompetition competition) { * @param player the player to join */ public void joinTeam(ArenaPlayer player, ArenaTeam team) { - this.teams.computeIfAbsent(team, e -> new HashSet<>()).add(player); + if (player.getTeam() != null) { + this.teams.get(player.getTeam()).remove(player); + } + + this.teams.get(team).add(player); player.setTeam(team); } @@ -135,10 +145,41 @@ public StatHolder getStats(ArenaTeam team) { return this.stats.computeIfAbsent(team, e -> new TeamStatHolder(this, team)); } - private boolean canJoinTeam(ArenaTeam team) { - Teams teams = this.competition.getArena().getTeams(); + /** + * Returns whether the player can join the given {@link ArenaTeam}. + * + * @param team the team to check if the player can join + * @return whether the player can join the team + */ + public boolean canJoinTeam(ArenaTeam team) { int playersOnTeam = this.getNumberOfPlayersOnTeam(team); - return playersOnTeam + 1 <= teams.getTeamSize().getMax(); + return playersOnTeam < this.getMaximumTeamSize(team); + } + + /** + * Returns the maximum team size for the given {@link ArenaTeam}. + * + * @param team the team to get the maximum team size for + * @return the maximum team size for the team + */ + public int getMaximumTeamSize(ArenaTeam team) { + Teams teams = this.competition.getArena().getTeams(); + int teamSizeMax = teams.getTeamSize().getMax(); + + // If spawn points are not shared, that means we only have a limited + // amount of spawn points for each team. We need to check if the team + // is full based on the amount of spawn points available. + Spawns spawns = this.competition.getMap().getSpawns(); + if (!teams.isSharedSpawnPoints() && spawns != null) { + teamSizeMax = Math.min(teamSizeMax, spawns.getSpawnPointCount(team.getName())); + } + + // If we have zero spawns in any situation, this team cannot hold any players + if (spawns == null || spawns.getSpawnPointCount(team.getName()) == 0) { + teamSizeMax = 0; + } + + return teamSizeMax; } /** diff --git a/plugin/src/main/java/org/battleplugins/arena/config/DurationParser.java b/plugin/src/main/java/org/battleplugins/arena/config/DurationParser.java index b7d59bed..da4eebdd 100644 --- a/plugin/src/main/java/org/battleplugins/arena/config/DurationParser.java +++ b/plugin/src/main/java/org/battleplugins/arena/config/DurationParser.java @@ -11,6 +11,10 @@ public final class DurationParser implements ArenaConfigParser.Parser @Override public Duration parse(Object object) throws ParseException { + return parseDuration(object); + } + + public static Duration parseDuration(Object object) throws ParseException { if (object instanceof Number number) { return Duration.ofSeconds(number.longValue()); } @@ -19,7 +23,7 @@ public Duration parse(Object object) throws ParseException { if (value.isBlank()) { throw new ParseException("Duration value was not provided!") .cause(ParseException.Cause.MISSING_VALUE) - .type(this.getClass()) + .type(DurationParser.class) .userError(); } @@ -56,7 +60,7 @@ public Duration parse(Object object) throws ParseException { default: throw new ParseException("Invalid unit: " + unit) .cause(ParseException.Cause.INVALID_TYPE) - .type(this.getClass()) + .type(DurationParser.class) .userError(); } } @@ -64,7 +68,7 @@ public Duration parse(Object object) throws ParseException { if (totalSeconds == 0) { throw new ParseException("Failed to parse Duration from value " + object) .cause(ParseException.Cause.INVALID_VALUE) - .type(this.getClass()) + .type(DurationParser.class) .userError(); } diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/stage/TeamSpawnInputStage.java b/plugin/src/main/java/org/battleplugins/arena/editor/stage/TeamSpawnInputStage.java index 761bbb3e..7e68365a 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/stage/TeamSpawnInputStage.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/stage/TeamSpawnInputStage.java @@ -1,5 +1,9 @@ package org.battleplugins.arena.editor.stage; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.TextColor; import org.battleplugins.arena.editor.EditorContext; import org.battleplugins.arena.editor.WizardStage; import org.battleplugins.arena.messages.Message; @@ -75,14 +79,15 @@ public void onChatInput(String input) { } // Send list of valid teams - List teamNames = context.getArena().getTeams().getAvailableTeams() + List teamNames = context.getArena().getTeams().getAvailableTeams() .stream() - .map(ArenaTeam::getName) + .map(team -> Component.text(team.getName(), TextColor.color(team.getTextColor()))) .toList(); - Messages.VALID_TEAMS.send(player, String.join(", ", teamNames)); + Component teamsList = Component.join(JoinConfiguration.commas(true), teamNames); + Messages.VALID_TEAMS.send(player, teamsList); - new InteractionInputs.ChatInput(player, Messages.INVALID_TEAM_VALID_TEAMS.withContext(String.join(", ", teamNames))) { + new InteractionInputs.ChatInput(player, Messages.INVALID_TEAM_VALID_TEAMS.withContext(teamsList)) { @Override public void onChatInput(String input) { diff --git a/plugin/src/main/java/org/battleplugins/arena/event/action/EventActionType.java b/plugin/src/main/java/org/battleplugins/arena/event/action/EventActionType.java index f5608cb6..62ad2156 100644 --- a/plugin/src/main/java/org/battleplugins/arena/event/action/EventActionType.java +++ b/plugin/src/main/java/org/battleplugins/arena/event/action/EventActionType.java @@ -10,6 +10,7 @@ import org.battleplugins.arena.event.action.types.FlightAction; import org.battleplugins.arena.event.action.types.GiveEffectsAction; import org.battleplugins.arena.event.action.types.HealthAction; +import org.battleplugins.arena.event.action.types.JoinRandomTeamAction; import org.battleplugins.arena.event.action.types.KillEntitiesAction; import org.battleplugins.arena.event.action.types.LeaveAction; import org.battleplugins.arena.event.action.types.PlaySoundAction; @@ -47,6 +48,7 @@ public final class EventActionType { public static final EventActionType FLIGHT = new EventActionType<>("flight", FlightAction.class, FlightAction::new); public static final EventActionType GIVE_EFFECTS = new EventActionType<>("give-effects", GiveEffectsAction.class, GiveEffectsAction::new); public static final EventActionType HEALTH = new EventActionType<>("health", HealthAction.class, HealthAction::new); + public static final EventActionType JOIN_RANDOM_TEAM = new EventActionType<>("join-random-team", JoinRandomTeamAction.class, JoinRandomTeamAction::new); public static final EventActionType KILL_ENTITIES = new EventActionType<>("kill-entities", KillEntitiesAction.class, KillEntitiesAction::new); public static final EventActionType LEAVE = new EventActionType<>("leave", LeaveAction.class, LeaveAction::new); public static final EventActionType PLAY_SOUND = new EventActionType<>("play-sound", PlaySoundAction.class, PlaySoundAction::new); diff --git a/plugin/src/main/java/org/battleplugins/arena/event/action/types/JoinRandomTeamAction.java b/plugin/src/main/java/org/battleplugins/arena/event/action/types/JoinRandomTeamAction.java new file mode 100644 index 00000000..b822e13b --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/event/action/types/JoinRandomTeamAction.java @@ -0,0 +1,21 @@ +package org.battleplugins.arena.event.action.types; + +import org.battleplugins.arena.ArenaPlayer; +import org.battleplugins.arena.competition.team.TeamManager; +import org.battleplugins.arena.event.action.EventAction; + +import java.util.Map; + +public class JoinRandomTeamAction extends EventAction { + public JoinRandomTeamAction(Map params) { + super(params); + } + + @Override + public void call(ArenaPlayer arenaPlayer) { + if (arenaPlayer.getTeam() == null) { + TeamManager teamManager = arenaPlayer.getCompetition().getTeamManager(); + teamManager.joinTeam(arenaPlayer, teamManager.findSuitableTeam()); + } + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/event/action/types/TeleportAction.java b/plugin/src/main/java/org/battleplugins/arena/event/action/types/TeleportAction.java index 4ed0e0ba..f987a11b 100644 --- a/plugin/src/main/java/org/battleplugins/arena/event/action/types/TeleportAction.java +++ b/plugin/src/main/java/org/battleplugins/arena/event/action/types/TeleportAction.java @@ -49,7 +49,7 @@ public void call(ArenaPlayer arenaPlayer) { } if (arenaPlayer.getTeam() == null) { - throw new IllegalArgumentException("Team not defined for player"); + throw new IllegalArgumentException("Team not defined for player. Ensure that the 'join-random-team' action is specified so players that are not on a team get placed on one."); } String teamName = arenaPlayer.getTeam().getName(); diff --git a/plugin/src/main/java/org/battleplugins/arena/event/player/ArenaTeamJoinEvent.java b/plugin/src/main/java/org/battleplugins/arena/event/player/ArenaTeamJoinEvent.java new file mode 100644 index 00000000..b13a7232 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/event/player/ArenaTeamJoinEvent.java @@ -0,0 +1,40 @@ +package org.battleplugins.arena.event.player; + +import org.battleplugins.arena.ArenaPlayer; +import org.battleplugins.arena.team.ArenaTeam; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player joins an {@link ArenaTeam}. + */ +public class ArenaTeamJoinEvent extends BukkitArenaPlayerEvent { + + private final static HandlerList HANDLERS = new HandlerList(); + + private final ArenaTeam team; + + public ArenaTeamJoinEvent(ArenaPlayer player, ArenaTeam team) { + super(player.getArena(), player); + this.team = team; + } + + /** + * Returns the {@link ArenaTeam} the player joined. + * + * @return the team the player joined + */ + public ArenaTeam getTeam() { + return this.team; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/event/player/ArenaTeamLeaveEvent.java b/plugin/src/main/java/org/battleplugins/arena/event/player/ArenaTeamLeaveEvent.java new file mode 100644 index 00000000..b9c46f6f --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/event/player/ArenaTeamLeaveEvent.java @@ -0,0 +1,40 @@ +package org.battleplugins.arena.event.player; + +import org.battleplugins.arena.ArenaPlayer; +import org.battleplugins.arena.team.ArenaTeam; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player leaves an {@link ArenaTeam}. + */ +public class ArenaTeamLeaveEvent extends BukkitArenaPlayerEvent { + + private final static HandlerList HANDLERS = new HandlerList(); + + private final ArenaTeam team; + + public ArenaTeamLeaveEvent(ArenaPlayer player, ArenaTeam team) { + super(player.getArena(), player); + this.team = team; + } + + /** + * Returns the {@link ArenaTeam} the player left. + * + * @return the team the player left + */ + public ArenaTeam getTeam() { + return this.team; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/messages/Message.java b/plugin/src/main/java/org/battleplugins/arena/messages/Message.java index 7c45d834..c71ffd63 100644 --- a/plugin/src/main/java/org/battleplugins/arena/messages/Message.java +++ b/plugin/src/main/java/org/battleplugins/arena/messages/Message.java @@ -45,23 +45,17 @@ public void sendCentered(CommandSender sender, Message... replacements) { } public void send(CommandSender sender, Component... replacements) { - String[] strReplacements = new String[replacements.length]; - for (int i = 0; i < strReplacements.length; i++) { - Component replacement = replacements[i]; - strReplacements[i] = PlainTextComponentSerializer.plainText().serialize(replacement); - } - - sender.sendMessage(this.toComponent(strReplacements)); + sender.sendMessage(this.toComponent(replacements.clone())); } public void send(CommandSender sender, Message... replacements) { - String[] strReplacements = new String[replacements.length]; - for (int i = 0; i < strReplacements.length; i++) { + Component[] compReplacements = new Component[replacements.length]; + for (int i = 0; i < compReplacements.length; i++) { Message replacement = replacements[i]; - strReplacements[i] = replacement.asPlainText(); + compReplacements[i] = replacement.toComponent(); } - sender.sendMessage(this.toComponent(strReplacements)); + sender.sendMessage(this.toComponent(compReplacements)); } public Message withContext(Message... replacements) { @@ -92,17 +86,23 @@ public String asPlainText() { return PlainTextComponentSerializer.plainText().serialize(this.toComponent()); } - public String asPlainText(String... replacements) { - return PlainTextComponentSerializer.plainText().serialize(this.toComponent(replacements)); - } - public Component toComponent() { return this.text; } public Component toComponent(String... replacements) { + Component[] compReplacements = new Component[replacements.length]; + for (int i = 0; i < compReplacements.length; i++) { + String replacement = replacements[i]; + compReplacements[i] = Component.text(replacement); + } + + return this.toComponent(compReplacements); + } + + public Component toComponent(Component... replacements) { Component text = this.text; - for (String replacement : replacements) { + for (Component replacement : replacements) { text = text.replaceText(builder -> builder.matchLiteral("{}").once().replacement(replacement)); } @@ -110,13 +110,13 @@ public Component toComponent(String... replacements) { } public Component toComponent(Message... replacements) { - String[] strReplacements = new String[replacements.length]; - for (int i = 0; i < strReplacements.length; i++) { + Component[] compReplacements = new Component[replacements.length]; + for (int i = 0; i < compReplacements.length; i++) { Message replacement = replacements[i]; - strReplacements[i] = replacement.asPlainText(); + compReplacements[i] = replacement.toComponent(); } - return this.toComponent(strReplacements); + return this.toComponent(compReplacements); } private Message attachContext() { diff --git a/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java b/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java index e3343766..00c0397f 100644 --- a/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java +++ b/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java @@ -69,6 +69,15 @@ public final class Messages { public static final Message MANUAL_EVENT_MESSAGE = info("arena-manual-event-message", "%prefix% A {} event is starting! Run /{} join to join!"); public static final Message ADVANCED_PHASE = info("arena-advanced-phase", "Advanced to the next phase: {}!"); public static final Message NO_PHASES = error("arena-no-phases", "There are no phases to advance to!"); + public static final Message NO_TEAM_WITH_NAME = error("arena-no-team-with-name", "There is no team with the name {}!"); + public static final Message TEAM_FULL = error("arena-team-full", "The team {} is full!"); + public static final Message TEAM_JOINED = info("arena-team-joined", "You have joined the {} team!"); + public static final Message TEAM_LEFT = info("arena-team-left", "You have left the {} team!"); + public static final Message ALREADY_ON_THIS_TEAM = error("arena-already-on-this-team", "You are already on the {} team!"); + public static final Message NOT_ON_TEAM = error("arena-not-on-team", "You are not on a team!"); + public static final Message NO_TEAMS = error("arena-no-teams", "There are no teams in this arena!"); + public static final Message CANNOT_JOIN_TEAM_SOLO = error("arena-cannot-join-team-solo", "You cannot join a team in a solo arena!"); + public static final Message TEAM_SELECTION_NOT_AVAILABLE = error("arena-team-selection-not-available", "Team selection is not available at this time!"); public static final Message ARENA_STARTS_IN = info("arena-starts-in", "{} will start in {}!"); public static final Message ARENA_START_CANCELLED = error("arena-starts-cancelled", "Countdown cancelled as there is not enough players to start!"); @@ -129,7 +138,7 @@ public final class Messages { public static final Message BACKUP_NOT_FOUND = error("util-backup-not-found", "A backup at this index could not be found!"); public static final Message BACKUP_RESTORED = success("util-backup-restored", "Successfully restored backup for player {}!"); public static final Message BACKUP_CREATED = success("util-backup-created", "Successfully created backup for player {}!"); - public static final Message BACKUP_NUMBER = message("util-backup-number", "Backup #{}"); + public static final Message BACKUP_INFO = message("util-backup-info", "Backup {}"); public static final Message MODULES = message("util-modules", "Modules"); public static final Message MODULE = message("util-module", "- {}: {}"); public static final Message STARTING_RELOAD = info("util-starting-reload", "Reloading BattleArena..."); diff --git a/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java b/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java index aa2ac374..c4d56edb 100644 --- a/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java +++ b/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java @@ -26,6 +26,7 @@ public final class ArenaOptionType { public static final ArenaOptionType ITEM_DROPS = new ArenaOptionType<>("item-drops", BooleanArenaOption::new); public static final ArenaOptionType KEEP_INVENTORY = new ArenaOptionType<>("keep-inventory", BooleanArenaOption::new); public static final ArenaOptionType KEEP_EXPERIENCE = new ArenaOptionType<>("keep-experience", BooleanArenaOption::new); + public static final ArenaOptionType TEAM_SELECTION = new ArenaOptionType<>("team-selection", BooleanArenaOption::new); public static final ArenaOptionType> DAMAGE_PLAYERS = new ArenaOptionType<>("damage-players", params -> new EnumArenaOption<>(params, DamageOption.class, "option")); public static final ArenaOptionType> DAMAGE_ENTITIES = new ArenaOptionType<>("damage-entities", params -> new EnumArenaOption<>(params, DamageOption.class, "option")); diff --git a/plugin/src/main/java/org/battleplugins/arena/options/Lives.java b/plugin/src/main/java/org/battleplugins/arena/options/Lives.java index 001a01f5..46a77f59 100644 --- a/plugin/src/main/java/org/battleplugins/arena/options/Lives.java +++ b/plugin/src/main/java/org/battleplugins/arena/options/Lives.java @@ -3,7 +3,7 @@ import org.battleplugins.arena.config.ArenaOption; import org.battleplugins.arena.config.DocumentationSource; -@DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/lives") +@DocumentationSource("https://docs.battleplugins.org/books/user-guide/chapter/configuration") public class Lives { @ArenaOption(name = "enabled", description = "Whether or not lives are enabled.") diff --git a/plugin/src/main/java/org/battleplugins/arena/options/Teams.java b/plugin/src/main/java/org/battleplugins/arena/options/Teams.java index d7d273ef..6b91008b 100644 --- a/plugin/src/main/java/org/battleplugins/arena/options/Teams.java +++ b/plugin/src/main/java/org/battleplugins/arena/options/Teams.java @@ -31,6 +31,9 @@ public class Teams implements PostProcessable { @ArenaOption(name = "team-selection", description = "The team selection type.") private TeamSelection teamSelection = TeamSelection.RANDOM; + @ArenaOption(name = "shared-spawn-points", description = "Whether spawn points are shared between team members.") + private boolean sharedSpawnPoints = false; + private final List availableTeams = new ArrayList<>(); @Override @@ -93,6 +96,10 @@ public TeamSelection getTeamSelection() { return this.teamSelection; } + public boolean isSharedSpawnPoints() { + return this.sharedSpawnPoints; + } + public List getAvailableTeams() { return this.availableTeams; } diff --git a/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeam.java b/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeam.java index 8e691775..8f96f830 100644 --- a/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeam.java +++ b/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeam.java @@ -1,11 +1,16 @@ package org.battleplugins.arena.team; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; import org.battleplugins.arena.config.ArenaOption; import org.battleplugins.arena.config.DocumentationSource; import org.bukkit.inventory.ItemStack; import java.awt.Color; +/** + * Represents a team in an arena. + */ @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/teams") public class ArenaTeam { @@ -27,18 +32,57 @@ public ArenaTeam(String name, Color color, ItemStack item) { this.item = item; } + /** + * Returns the name of the team. + * + * @return the name of the team + */ public String getName() { return this.name; } + /** + * Returns the color of the team. + * + * @return the color of the team + */ public Color getColor() { return this.color; } + /** + * Returns the {@link TextColor} of the team. + * + * @return the text color of the team + */ + public TextColor getTextColor() { + return TextColor.color(this.color.getRGB()); + } + + /** + * Returns the formatted name of the team. + * + * @return the formatted name of the team + */ + public Component getFormattedName() { + return Component.text(this.name).color(this.getTextColor()); + } + + /** + * Returns the {@link ItemStack} representing the team. + * + * @return the item representing the team + */ public ItemStack getItem() { return this.item; } + /** + * Returns whether this team is hostile to the given team. + * + * @param team the team to check hostility against + * @return whether this team is hostile to the given team + */ public boolean isHostileTo(ArenaTeam team) { if (this == ArenaTeams.DEFAULT && team == ArenaTeams.DEFAULT) { return true; diff --git a/plugin/src/main/java/org/battleplugins/arena/util/InventoryBackup.java b/plugin/src/main/java/org/battleplugins/arena/util/InventoryBackup.java index 3e36bef4..2b1428e0 100644 --- a/plugin/src/main/java/org/battleplugins/arena/util/InventoryBackup.java +++ b/plugin/src/main/java/org/battleplugins/arena/util/InventoryBackup.java @@ -1,7 +1,6 @@ package org.battleplugins.arena.util; import org.battleplugins.arena.BattleArena; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; @@ -14,6 +13,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.List; import java.util.UUID; import java.util.function.BiFunction; @@ -51,6 +51,14 @@ public ItemStack[] getItems() { return this.items; } + public Instant getTimestamp() { + return Instant.ofEpochMilli(this.timestamp); + } + + public String getFormattedDate() { + return DATE_FORMAT.format(this.timestamp); + } + private void save() { // Save the inventory backup to the specified path Path path = BattleArena.getInstance().getBackupPath(INVENTORY_TYPE)