From 3a631cbd2c9f31e5c4d69e0d386cb912820f4700 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 30 Jun 2024 12:52:15 -0500 Subject: [PATCH] Document core parts of the plugin --- .github/workflows/build.yml | 2 +- build.gradle.kts | 2 - plugin/build.gradle.kts | 5 + .../org/battleplugins/arena/ArenaPlayer.java | 142 +++++++- .../org/battleplugins/arena/BattleArena.java | 310 ++++++++++++++---- .../arena/BattleArenaConfig.java | 3 + .../arena/command/ArenaCommandExecutor.java | 12 +- .../arena/competition/Competition.java | 10 +- .../competition/CompetitionListener.java | 2 +- .../arena/competition/CompetitionResult.java | 6 + .../arena/competition/CompetitionType.java | 21 +- .../arena/competition/JoinResult.java | 25 +- .../arena/competition/LiveCompetition.java | 168 +++++++--- .../arena/competition/OptionsListener.java | 2 +- .../arena/competition/PlayerRole.java | 9 + .../arena/competition/PlayerStorage.java | 94 ++++-- .../arena/competition/StatListener.java | 2 +- .../arena/competition/event/Event.java | 9 + .../arena/competition/event/EventOptions.java | 3 + .../competition/event/EventScheduler.java | 37 +++ .../arena/competition/event/LiveEvent.java | 3 + .../competition/map/LiveCompetitionMap.java | 84 +++++ .../arena/competition/map/options/Bounds.java | 3 + .../arena/competition/map/options/Spawns.java | 3 + .../competition/map/options/TeamSpawns.java | 3 + .../arena/competition/match/LiveMatch.java | 3 + .../arena/competition/match/Match.java | 11 + .../competition/phase/CompetitionPhase.java | 64 +++- .../phase/CompetitionPhaseType.java | 6 + .../phase/LiveCompetitionPhase.java | 28 ++ .../arena/competition/phase/PhaseManager.java | 26 ++ .../arena/competition/team/TeamManager.java | 53 +++ .../victory/VictoryConditionType.java | 6 + .../competition/victory/VictoryManager.java | 25 ++ .../arena/event/ArenaEventManager.java | 37 +++ .../arena/event/ArenaEventType.java | 19 ++ .../arena/event/action/EventAction.java | 41 +++ .../arena/event/action/EventActionType.java | 19 ++ .../arena/options/ArenaOptionType.java | 5 + .../arena/util/PositionWithRotation.java | 3 + 40 files changed, 1117 insertions(+), 189 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cddd1ac..b10fc519 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 21 + - name: Set up JDK 17 uses: actions/setup-java@v2 with: java-version: '17' diff --git a/build.gradle.kts b/build.gradle.kts index 18ee5166..dd0bf27f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,8 +16,6 @@ allprojects { } java { - withJavadocJar() - withSourcesJar() toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index d982326f..67a9cc22 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -17,6 +17,11 @@ dependencies { compileOnly(libs.worldedit) } +java { + withJavadocJar() + withSourcesJar() +} + tasks { runServer { minecraftVersion("1.20.6") diff --git a/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java b/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java index 21da217c..9934aa69 100644 --- a/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java +++ b/plugin/src/main/java/org/battleplugins/arena/ArenaPlayer.java @@ -16,6 +16,9 @@ import java.util.Optional; import java.util.function.Function; +/** + * Represents a player in an active competition. + */ public class ArenaPlayer implements StatHolder { private static final String ARENA_PLAYER_META_KEY = "arena-player"; @@ -42,43 +45,97 @@ public ArenaPlayer(Player player, Arena arena, LiveCompetition competition) { this.setMetadata(); } + /** + * Returns the {@link Player} associated with this + * arena player. + * + * @return the player associated with this arena player + */ public Player getPlayer() { return this.player; } + /** + * Returns the {@link Arena} that this arena player is + * currently in. + * + * @return the arena that this arena player is in + */ public Arena getArena() { return this.arena; } + /** + * Returns the {@link LiveCompetition} that this arena player + * is currently in. + * + * @return the live competition that this arena player is in + */ public LiveCompetition getCompetition() { return this.competition; } + /** + * Returns the {@link PlayerStorage} which stores information + * about this player which may need to be restored after the + * competition. + * + * @return the player storage for this player + */ public PlayerStorage getStorage() { return this.storage; } + /** + * Returns the role of this player in the competition. + * + * @return the role of this player in the competition + */ public PlayerRole getRole() { return this.role; } + /** + * Sets the role of this player in the competition. + * + * @param role the role of this player in the competition + */ public void setRole(PlayerRole role) { this.role = role; } + /** + * Returns the team that this player is on. + * + * @return the team that this player is on + */ public Optional team() { return Optional.ofNullable(this.getTeam()); } + /** + * Returns the team that this player is on. + * + * @return the team that this player is on, + * or null if the player is not on a team + */ @Nullable public ArenaTeam getTeam() { return this.team; } + /** + * Sets the team that this player is on. + * + * @param team the team that this player is on + */ public void setTeam(@Nullable ArenaTeam team) { this.team = team; } + /** + * Removes the metadata associated with this player. + */ public void remove() { this.removeMetadata(); } @@ -91,22 +148,51 @@ void removeMetadata() { this.player.removeMetadata(ARENA_PLAYER_META_KEY, this.arena.getPlugin()); } + /** + * Returns the stat value of the given {@link ArenaStat}. + * + * @param stat the stat to get + * @param the type of the stat + * @return the stat of the given arena stat + */ @Override public Optional stat(ArenaStat stat) { return Optional.ofNullable(this.getStat(stat)); } + /** + * Returns the stat value of the given {@link ArenaStat}. + * + * @param stat the stat to get + * @param the type of the stat + * @return the stat of the given arena stat, or null if the stat + * does not exist or is not set for this player + */ @Override @Nullable public T getStat(ArenaStat stat) { return (T) this.stats.get(stat); } + /** + * Sets the stat value of the given {@link ArenaStat}. + * + * @param stat the stat to set + * @param value the value to set the stat to + * @param the type of the stat + */ @Override public void setStat(ArenaStat stat, T value) { this.computeStat(stat, oldValue -> value); } + /** + * Computes the stat value of the given {@link ArenaStat} using the given function. + * + * @param stat the stat to compute + * @param computeFunction the function to compute the stat + * @param the type of the stat + */ @Override @SuppressWarnings("unchecked") public void computeStat(ArenaStat stat, Function computeFunction) { @@ -122,24 +208,55 @@ private T statChange(ArenaStat stat, T oldValue, T newValue) { return event.getNewValue(); } + /** + * Gets a stored metadata value for this player. + * + * @param metadataClass the class of the metadata + * @param the type of the metadata + * @return the metadata value for this player + */ + public Optional metadata(Class metadataClass) { + return Optional.ofNullable(this.getMetadata(metadataClass)); + } + + /** + * Gets a stored metadata value for this player. + * + * @param metadataClass the class of the metadata + * @param the type of the metadata + * @return the metadata value for this player, or + * null if the metadata does not exist + */ @SuppressWarnings("unchecked") @Nullable public T getMetadata(Class metadataClass) { return (T) this.metadata.get(metadataClass); } - public Optional metadata(Class metadataClass) { - return Optional.ofNullable(this.getMetadata(metadataClass)); - } - + /** + * Sets a metadata value for this player. + * + * @param metadataClass the class of the metadata + * @param value the value to set the metadata to + * @param the type of the metadata + */ public void setMetadata(Class metadataClass, T value) { this.metadata.put(metadataClass, value); } + /** + * Removes a metadata value for this player. + * + * @param metadataClass the class of the metadata to remove + * @param the type of the metadata + */ public void removeMetadata(Class metadataClass) { this.metadata.remove(metadataClass); } + /** + * Resets the state of this player. + */ public void resetState() { // TODO: Save stats in a remote location (BattleTracker) this.stats.clear(); @@ -155,6 +272,23 @@ public String toString() { '}'; } + /** + * Gets an {@link Optional} of the {@link ArenaPlayer} associated with the given player. + * + * @param player the player to get the arena player of + * @return an optional of the arena player associated with the given player + */ + public static Optional arenaPlayer(Player player) { + return Optional.ofNullable(getArenaPlayer(player)); + } + + /** + * Gets the {@link ArenaPlayer} associated with the given player. + * + * @param player the player to get the arena player of + * @return the arena player associated with the given player, or + * null if the player is not in a competition + */ @Nullable public static ArenaPlayer getArenaPlayer(Player player) { if (!player.hasMetadata(ARENA_PLAYER_META_KEY)) { diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java index 3610de2c..4af116c0 100644 --- a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java @@ -289,6 +289,9 @@ private void postInitialize() { } } + /** + * Reloads the plugin. + */ public void reload() { new BattleArenaReloadEvent(this).callEvent(); @@ -299,35 +302,72 @@ public void reload() { new BattleArenaReloadedEvent(this).callEvent(); } + /** + * Returns whether the given {@link Player} is in an {@link Arena}. + * + * @param player the player to check + * @return whether the player is in an arena + */ public boolean isInArena(Player player) { return ArenaPlayer.getArenaPlayer(player) != null; } + /** + * Returns the {@link Arena} from the given name. + * + * @param name the name of the arena + * @return the arena from the given name + */ public Optional arena(String name) { return Optional.ofNullable(this.arenas.get(name)); } + /** + * Returns the {@link Arena} from the given name. + * + * @param name the name of the arena + * @return the arena from the given name, or null if not found + */ @Nullable public Arena getArena(String name) { return this.arenas.get(name); } - public void registerArena(String name, Class arena) { - this.registerArena(name, arena, () -> { + /** + * Registers the given {@link Arena}. + * + * @param name the name of the arena + * @param arenaClass the arena type to register + */ + public void registerArena(String name, Class arenaClass) { + this.registerArena(name, arenaClass, () -> { try { - return arena.getConstructor().newInstance(); + return arenaClass.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new RuntimeException("Failed to instantiate arena " + arena.getName(), e); + throw new RuntimeException("Failed to instantiate arena " + arenaClass.getName(), e); } }); } + /** + * Registers the given {@link Arena}. + * + * @param name the name of the arena + * @param arenaClass the arena type to register + * @param arenaFactory the factory to create the arena + */ public void registerArena(String name, Class arenaClass, Supplier arenaFactory) { ArenaConfigParser.registerFactory(arenaClass, arenaFactory); this.arenaTypes.put(name, arenaClass); } + /** + * Returns all the available maps for the given {@link Arena}. + * + * @param arena the arena to get the maps for + * @return all the available maps for the given arena + */ public List> getMaps(Arena arena) { List> maps = this.arenaMaps.get(arena); if (maps == null) { @@ -337,10 +377,24 @@ public List> getMaps(Arena arena) { return List.copyOf(maps); } + /** + * Returns the map from the given {@link Arena} and map name. + * + * @param arena the arena to get the map from + * @param name the name of the map + * @return the map from the given arena and name + */ public Optional> map(Arena arena, String name) { return Optional.ofNullable(this.getMap(arena, name)); } + /** + * Returns the map from the given {@link Arena} and map name. + * + * @param arena the arena to get the map from + * @param name the name of the map + * @return the map from the given arena and name, or null if not found + */ @Nullable public LiveCompetitionMap getMap(Arena arena, String name) { List> maps = this.arenaMaps.get(arena); @@ -354,10 +408,22 @@ public LiveCompetitionMap getMap(Arena arena, String name) { .orElse(null); } + /** + * Adds a new {@link LiveCompetitionMap} to the given {@link Arena}. + * + * @param arena the arena to add the map to + * @param map the map to add + */ public void addArenaMap(Arena arena, LiveCompetitionMap map) { this.arenaMaps.computeIfAbsent(arena, k -> new ArrayList<>()).add(map); } + /** + * Removes the given {@link LiveCompetitionMap} from the given {@link Arena}. + * + * @param arena the arena to remove the map from + * @param map the map to remove + */ public void removeArenaMap(Arena arena, LiveCompetitionMap map) { this.arenaMaps.computeIfAbsent(arena, k -> new ArrayList<>()).remove(map); @@ -377,34 +443,75 @@ public void removeArenaMap(Arena arena, LiveCompetitionMap map) { } } + /** + * Returns all the {@link Competition}s for the given {@link Arena}. + * + * @param arena the arena to get the competitions for + * @return all the competitions for the given arena + */ public List> getCompetitions(Arena arena) { return this.competitionManager.getCompetitions(arena); } + /** + * Returns all the {@link Competition}s for the given {@link Arena} and + * specified map name. + * + * @param arena the arena to get the competitions for + * @param name the name of the competition + * @return all the competitions for the given arena and name + */ public List> getCompetitions(Arena arena, String name) { return this.competitionManager.getCompetitions(arena, name); } + /** + * Returns a currently active {@link Competition} for the given {@link Arena}, + * {@link Player}, {@link PlayerRole} and map name. If no competition is found, + * a new one is created if applicable. + * + * @param arena the arena to get the competition for + * @param player the player to get the competition for + * @param role the role of the player + * @param name the name of the competition + * @return the competition result + */ public CompletableFuture getOrCreateCompetition(Arena arena, Player player, PlayerRole role, @Nullable String name) { return this.competitionManager.getOrCreateCompetition(arena, player, role, name); } + /** + * Finds a joinable {@link Competition} for the given {@link Player} and {@link PlayerRole}. + * + * @param competitions the competitions to find from + * @param player the player to find the competition for + * @param role the role of the player + * @return the competition result + */ public CompletableFuture findJoinableCompetition(List> competitions, Player player, PlayerRole role) { return this.competitionManager.findJoinableCompetition(competitions, player, role); } + /** + * Adds a new {@link Competition} to the given {@link Arena}. + * + * @param arena the arena to add the competition to + * @param competition the competition to add + */ public void addCompetition(Arena arena, Competition competition) { this.competitionManager.addCompetition(arena, competition); } + /** + * Removes the given {@link Competition} from the specified {@link Arena}. + * + * @param arena the arena to remove the competition from + * @param competition the competition to remove + */ public void removeCompetition(Arena arena, Competition competition) { this.competitionManager.removeCompetition(arena, competition); } - public Path getMapsPath() { - return this.getDataFolder().toPath().resolve("maps"); - } - private void loadArenas() { // Register our arenas once ALL the plugins have loaded. This ensures that // all custom plugins adding their own arena types have been loaded. @@ -417,6 +524,135 @@ private void loadArenas() { } } + /** + * Returns the {@link EventScheduler}, which is responsible for scheduling events. + * + * @return the event scheduler + */ + public EventScheduler getEventScheduler() { + return this.eventScheduler; + } + + /** + * Returns an in-memory representation of the configuration. + * + * @return the BattleArena configuration + */ + public BattleArenaConfig getMainConfig() { + return this.config; + } + + /** + * Returns the teams for the plugin. + * + * @return the teams for the plugin + */ + public ArenaTeams getTeams() { + return this.teams; + } + + /** + * Returns the {@link ArenaModuleContainer} for the given module id. + * + * @param id the id of the module + * @return the module container for the given id + * @param the type of the module + */ + public Optional> module(String id) { + return Optional.ofNullable(this.getModule(id)); + } + + /** + * Returns the {@link ArenaModuleContainer} for the given module id. + * + * @param id the id of the module + * @return the module container for the given id, or null if not found + * @param the type of the module + */ + @Nullable + public ArenaModuleContainer getModule(String id) { + return this.moduleLoader.getModule(id); + } + + /** + * Returns all the modules for the plugin. + * + * @return all the modules for the plugin + */ + public List> getModules() { + return this.moduleLoader.getModules(); + } + + /** + * Returns all the failed modules for the plugin. + * + * @return all the failed modules for the plugin + */ + public Set getFailedModules() { + return this.moduleLoader.getFailedModules(); + } + + /** + * Registers a new command executor for the given command. + * + * @param commandName the name of the command + * @param executor the executor to register + * @param aliases the aliases for the command + */ + public void registerExecutor(String commandName, BaseCommandExecutor executor, String... aliases) { + PluginCommand command = CommandInjector.inject(commandName, commandName.toLowerCase(Locale.ROOT), aliases); + command.setExecutor(executor); + } + + /** + * Returns the path to the maps directory. + * + * @return the path to the maps directory + */ + public Path getMapsPath() { + return this.getDataFolder().toPath().resolve("maps"); + } + + /** + * Returns the path to the backup directory for the given type. + * + * @param type the type of backup + * @return the path to the backup directory + */ + public Path getBackupPath(String type) { + return this.getDataFolder().toPath().resolve("backups").resolve(type); + } + + /** + * Returns whether the plugin is in debug mode. + * + * @return whether the plugin is in debug mode + */ + @Override + public boolean isDebugMode() { + return this.debugMode; + } + + /** + * Sets whether the plugin is in debug mode. + * + * @param debugMode whether the plugin is in debug mode + */ + @Override + public void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + + /** + * Gets the SLF4J logger for the plugin. + * + * @return the SLF4J logger for the plugin + */ + @Override + public @NotNull Logger getSLF4JLogger() { + return super.getSLF4JLogger(); + } + private void loadArenaMaps() { // All the arenas have been loaded, now we can load the maps Path mapsPath = this.getMapsPath(); @@ -464,44 +700,6 @@ private void loadArenaMaps() { } } - public EventScheduler getEventScheduler() { - return this.eventScheduler; - } - - public BattleArenaConfig getMainConfig() { - return this.config; - } - - public ArenaTeams getTeams() { - return this.teams; - } - - public Optional> module(String id) { - return Optional.ofNullable(this.getModule(id)); - } - - @Nullable - public ArenaModuleContainer getModule(String id) { - return this.moduleLoader.getModule(id); - } - - public List> getModules() { - return this.moduleLoader.getModules(); - } - - public Set getFailedModules() { - return this.moduleLoader.getFailedModules(); - } - - public void registerExecutor(String name, BaseCommandExecutor executor, String... aliases) { - PluginCommand command = CommandInjector.inject(name, name.toLowerCase(Locale.ROOT), aliases); - command.setExecutor(executor); - } - - public Path getBackupPath(String type) { - return this.getDataFolder().toPath().resolve("backups").resolve(type); - } - private void clearDynamicMaps() { for (File file : Bukkit.getWorldContainer().listFiles()) { if (file.isDirectory() && file.getName().startsWith("ba-dynamic")) { @@ -518,21 +716,11 @@ private void clearDynamicMaps() { } } - @Override - public boolean isDebugMode() { - return this.debugMode; - } - - @Override - public void setDebugMode(boolean debugMode) { - this.debugMode = debugMode; - } - - @Override - public @NotNull Logger getSLF4JLogger() { - return super.getSLF4JLogger(); - } - + /** + * Returns the instance of the plugin. + * + * @return the instance of the plugin + */ public static BattleArena getInstance() { return instance; } diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArenaConfig.java b/plugin/src/main/java/org/battleplugins/arena/BattleArenaConfig.java index 08fd0d26..1e3457f3 100644 --- a/plugin/src/main/java/org/battleplugins/arena/BattleArenaConfig.java +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArenaConfig.java @@ -6,6 +6,9 @@ import java.util.List; import java.util.Map; +/** + * Represents the BattleArena configuration. + */ public class BattleArenaConfig { @ArenaOption(name = "config-version", description = "The version of the config.", required = true) 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 bdc450da..0df65d6c 100644 --- a/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java +++ b/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java @@ -89,10 +89,10 @@ public void join(Player player, @Argument(name = "map") CompetitionMap map) { // No competition - something happened that stopped the // dynamic arena from being created. Not much we can do here, // but info will be in console in the event of an error - if (newResult.result() != JoinResult.NOT_JOINABLE && newResult.result().getMessage() != null) { - newResult.result().getMessage().send(player); - } else if (result.result() != JoinResult.NOT_JOINABLE && result.result().getMessage() != null) { - result.result().getMessage().send(player); + if (newResult.result() != JoinResult.NOT_JOINABLE && newResult.result().message() != null) { + newResult.result().message().send(player); + } else if (result.result() != JoinResult.NOT_JOINABLE && result.result().message() != null) { + result.result().message().send(player); } else { Messages.ARENA_NOT_JOINABLE.send(player); } @@ -161,8 +161,8 @@ public void spectate(Player player, Competition competition) { Messages.ARENA_SPECTATE.send(player, competition.getMap().getName()); } else { - if (result.getMessage() != null) { - result.getMessage().send(player); + if (result.message() != null) { + result.message().send(player); } else { Messages.ARENA_NOT_SPECTATABLE.send(player); } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java b/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java index f3f68017..e497c2d0 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java @@ -9,10 +9,12 @@ import java.util.concurrent.CompletableFuture; /** - * Represents a competition. Competitions are a representation of an + * Represents a competition. + *

+ * Competitions are a representation of an * active {@link Arena}. Where an Arena will contain all the actual * game logic, a Competition will contain all the logic for the live - * competition itself (such as the game timer, score, etc). + * competition itself (such as the game timer, score, etc.). *

* Competitions are also responsible for handling the lifecycle of * an Arena. This includes starting, stopping, and resetting the @@ -52,9 +54,9 @@ public interface Competition> extends CompetitionLike> implements ArenaListener, CompetitionLike { +class CompetitionListener> implements ArenaListener, CompetitionLike { private final LiveCompetition competition; diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionResult.java b/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionResult.java index 971be074..633a676c 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionResult.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionResult.java @@ -2,5 +2,11 @@ import org.jetbrains.annotations.Nullable; +/** + * Represents a competition result. + * + * @param competition the competition + * @param result the {@link JoinResult} of the competition + */ public record CompetitionResult(@Nullable Competition competition, JoinResult result) { } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionType.java b/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionType.java index 212ee5a4..9c7a77a4 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionType.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionType.java @@ -10,7 +10,13 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; +/** + * Represents a competition type. + * + * @param the type of competition + */ public final class CompetitionType> { private static final Map> COMPETITION_TYPES = new HashMap<>(); @@ -44,7 +50,20 @@ public static > CompetitionType create(String name, return new CompetitionType<>(name, clazz, factory); } - interface CompetitionFactory> { + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + CompetitionType that = (CompetitionType) object; + return Objects.equals(this.clazz, that.clazz); + } + + @Override + public int hashCode() { + return Objects.hash(this.clazz); + } + + public interface CompetitionFactory> { T create(Arena arena, LiveCompetitionMap map); } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/JoinResult.java b/plugin/src/main/java/org/battleplugins/arena/competition/JoinResult.java index 42b78856..65245017 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/JoinResult.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/JoinResult.java @@ -4,26 +4,15 @@ import org.battleplugins.arena.messages.Messages; import org.jetbrains.annotations.Nullable; -public class JoinResult { +/** + * Represents the result of a player joining an arena. + * + * @param canJoin whether the player can join the arena + * @param message the message to send to the player if they cannot join + */ +public record JoinResult(boolean canJoin, @Nullable Message message) { public static final JoinResult SUCCESS = new JoinResult(true, null); public static final JoinResult ARENA_FULL = new JoinResult(false, Messages.ARENA_FULL); public static final JoinResult NOT_JOINABLE = new JoinResult(false, Messages.ARENA_NOT_JOINABLE); public static final JoinResult NOT_SPECTATABLE = new JoinResult(false, Messages.ARENA_NOT_SPECTATABLE); - - private final boolean canJoin; - private final Message message; - - public JoinResult(boolean canJoin, Message message) { - this.canJoin = canJoin; - this.message = message; - } - - public boolean canJoin() { - return this.canJoin; - } - - @Nullable - public Message getMessage() { - return this.message; - } } 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 8731b0ea..d67cab0c 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java @@ -68,15 +68,7 @@ public LiveCompetition(Arena arena, LiveCompetitionMap map) { this.phaseManager.setPhase(initialPhase); } - protected void onDestroy() { - this.arena.getEventManager().unregisterEvents(this.competitionListener); - this.arena.getEventManager().unregisterEvents(this.optionsListener); - this.arena.getEventManager().unregisterEvents(this.statListener); - } - - private ArenaPlayer createPlayer(Player player) { - return new ArenaPlayer(player, this.arena, this); - } + // API methods @Override public CompletableFuture canJoin(Player player, PlayerRole role) { @@ -124,20 +116,65 @@ public CompletableFuture canJoin(Player player, PlayerRole role) { return CompletableFuture.completedFuture(event.getResult()); } + /** + * Finds a suitable team for the player to join and + * joins them to that team, if applicable. + * + * @param player the player to find a team for + */ + public void findAndJoinTeamIfApplicable(ArenaPlayer player) { + Teams teams = this.arena.getTeams(); + + // If the team selection is none, then we can just put + // the player on the default team + if (teams.isNonTeamGame()) { + this.teamManager.joinTeam(player, ArenaTeams.DEFAULT); + } + + if (teams.getTeamSelection() == TeamSelection.RANDOM) { + this.teamManager.joinTeam(player, this.teamManager.findSuitableTeam()); + } + } + + // Internal methods (cannot be overridden by extending plugins) + + @Override + public final Arena getArena() { + return this.arena; + } + + @Override + public final LiveCompetitionMap getMap() { + return this.map; + } + + @Override + public final CompetitionPhaseType getPhase() { + return this.phaseManager.getCurrentPhase().getType(); + } + @Override - public void join(Player player, PlayerRole type) { - this.join(player, type, null); + public final void join(Player player, PlayerRole role) { + this.join(player, role, null); } - public void join(Player player, PlayerRole type, @Nullable ArenaTeam team) { + /** + * Makes the player join the competition with the specified {@link PlayerRole} + * and {@link ArenaTeam}. + * + * @param player the player to join + * @param role the role of the player + * @param team the team to join + */ + public final void join(Player player, PlayerRole role, @Nullable ArenaTeam team) { if (this.arena.getPlugin().isInArena(player)) { throw new IllegalStateException("Player is already in an arena!"); } ArenaPlayer arenaPlayer = this.createPlayer(player); - arenaPlayer.setRole(type); + arenaPlayer.setRole(role); - this.join(arenaPlayer, null); + this.join(arenaPlayer, team); } private void join(ArenaPlayer player, @Nullable ArenaTeam team) { @@ -162,7 +199,7 @@ private void join(ArenaPlayer player, @Nullable ArenaTeam team) { } @Override - public void leave(Player player, ArenaLeaveEvent.Cause cause) { + public final void leave(Player player, ArenaLeaveEvent.Cause cause) { ArenaPlayer arenaPlayer = this.players.get(player); if (arenaPlayer == null) { return; @@ -171,7 +208,13 @@ public void leave(Player player, ArenaLeaveEvent.Cause cause) { this.leave(arenaPlayer, cause); } - public void leave(ArenaPlayer player, ArenaLeaveEvent.Cause cause) { + /** + * Makes the {@link ArenaPlayer} leave the competition with the specified {@link ArenaLeaveEvent.Cause}. + * + * @param player the player to leave + * @param cause the cause of the player leaving + */ + public final void leave(ArenaPlayer player, ArenaLeaveEvent.Cause cause) { this.players.remove(player.getPlayer()); this.playersByRole.get(player.getRole()).remove(player); @@ -183,65 +226,86 @@ public void leave(ArenaPlayer player, ArenaLeaveEvent.Cause cause) { player.remove(); } - public void findAndJoinTeamIfApplicable(ArenaPlayer player) { - Teams teams = this.arena.getTeams(); - - // If the team selection is none, then we can just put - // the player on the default team - if (teams.isNonTeamGame()) { - this.teamManager.joinTeam(player, ArenaTeams.DEFAULT); - } - - if (teams.getTeamSelection() == TeamSelection.RANDOM) { - this.teamManager.joinTeam(player, this.teamManager.findSuitableTeam()); - } - } - - @Override - public Arena getArena() { - return this.arena; - } - - @Override - public LiveCompetitionMap getMap() { - return this.map; - } - - @Override - public CompetitionPhaseType getPhase() { - return this.phaseManager.getCurrentPhase().getType(); - } - - public Set getPlayers() { + /** + * Gets all the {@link ArenaPlayer players} in the competition. + * + * @return all players in the competition + */ + public final Set getPlayers() { return Collections.unmodifiableSet(this.playersByRole.getOrDefault(PlayerRole.PLAYING, Set.of())); } - public Set getSpectators() { + /** + * Gets all the {@link ArenaPlayer spectators} in the competition. + * + * @return all spectators in the competition + */ + public final Set getSpectators() { return Collections.unmodifiableSet(this.playersByRole.getOrDefault(PlayerRole.SPECTATING, Set.of())); } - public PhaseManager getPhaseManager() { + /** + * Gets the {@link PhaseManager} responsible for managing the phases of the competition. + * + * @return the phase manager + */ + public final PhaseManager getPhaseManager() { return this.phaseManager; } - public TeamManager getTeamManager() { + /** + * Gets the {@link TeamManager} responsible for managing the teams of the competition. + * + * @return the team manager + */ + public final TeamManager getTeamManager() { return this.teamManager; } - public VictoryManager getVictoryManager() { + /** + * Gets the {@link VictoryManager} responsible for managing the victory conditions + * of the competition. + * + * @return the victory manager + */ + public final VictoryManager getVictoryManager() { return this.victoryManager; } - public Optional option(ArenaOptionType type) { + /** + * Gets the {@link org.battleplugins.arena.options.ArenaOption} of the specified type. + * + * @param type the type of option + * @param the type of option + * @return the option of the specified type + */ + public final Optional option(ArenaOptionType type) { return Optional.ofNullable(this.getOption(type)); } + /** + * Gets the {@link org.battleplugins.arena.options.ArenaOption} of the specified type. + * + * @param type the type of option + * @param the type of option + * @return the option of the specified type, or null if it does not exist + */ @Nullable - public E getOption(ArenaOptionType type) { + public final E getOption(ArenaOptionType type) { if (this.getPhaseManager().getCurrentPhase() instanceof LiveCompetitionPhase livePhase) { return livePhase.getOption(type); } return this.arena.getOption(type); } + + protected final void onDestroy() { + this.arena.getEventManager().unregisterEvents(this.competitionListener); + this.arena.getEventManager().unregisterEvents(this.optionsListener); + this.arena.getEventManager().unregisterEvents(this.statListener); + } + + private ArenaPlayer createPlayer(Player player) { + return new ArenaPlayer(player, this.arena, this); + } } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java b/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java index 05b12d97..afe1c122 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java @@ -16,7 +16,7 @@ import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerInteractEvent; -public class OptionsListener> implements ArenaListener, CompetitionLike { +class OptionsListener> implements ArenaListener, CompetitionLike { private final LiveCompetition competition; public OptionsListener(LiveCompetition competition) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/PlayerRole.java b/plugin/src/main/java/org/battleplugins/arena/competition/PlayerRole.java index 41ef789a..5a8d6755 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/PlayerRole.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/PlayerRole.java @@ -1,6 +1,15 @@ package org.battleplugins.arena.competition; +/** + * Represents the role of a player in a competition. + */ public enum PlayerRole { + /** + * Represents a player who is playing in the competition. + */ PLAYING, + /** + * Represents a player who is spectating the competition. + */ SPECTATING } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/PlayerStorage.java b/plugin/src/main/java/org/battleplugins/arena/competition/PlayerStorage.java index a9b28c0a..20a403ca 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/PlayerStorage.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/PlayerStorage.java @@ -18,6 +18,10 @@ import java.util.Set; import java.util.function.Consumer; +/** + * Represents the storage of a player for data which may need to + * be restored after the end of a competition. + */ public class PlayerStorage { private final ArenaPlayer player; @@ -46,7 +50,12 @@ public class PlayerStorage { public PlayerStorage(ArenaPlayer player) { this.player = player; } - + + /** + * Stores the player's data from on the given {@link Type types}. + * + * @param toStore the types to store + */ public void store(Set toStore) { if (this.stored) { return; @@ -59,8 +68,8 @@ public void store(Set toStore) { this.stored = true; this.clearState(toStore); } - - public void storeAll() { + + private void storeAll() { this.storeInventory(); this.storeGameMode(); this.storeHealth(); @@ -70,24 +79,24 @@ public void storeAll() { this.storeEffects(); this.storeLocation(); } - - public void storeInventory() { + + private void storeInventory() { this.inventory = this.player.getPlayer().getInventory().getContents(); if (BattleArena.getInstance().getMainConfig().isBackupInventories()) { InventoryBackup.save(new InventoryBackup(this.player.getPlayer().getUniqueId(), this.inventory.clone())); } } - public void storeGameMode() { + private void storeGameMode() { this.gameMode = this.player.getPlayer().getGameMode(); } - - public void storeHealth() { + + private void storeHealth() { this.health = this.player.getPlayer().getHealth(); this.hunger = this.player.getPlayer().getFoodLevel(); } - - public void storeAttributes() { + + private void storeAttributes() { for (Attribute attribute : Attribute.values()) { AttributeInstance instance = this.player.getPlayer().getAttribute(attribute); if (instance == null) { @@ -100,31 +109,36 @@ public void storeAttributes() { this.walkSpeed = this.player.getPlayer().getWalkSpeed(); this.flySpeed = this.player.getPlayer().getFlySpeed(); } - - public void storeExperience() { + + private void storeExperience() { this.exp = this.player.getPlayer().getTotalExperience(); this.expLevels = this.player.getPlayer().getLevel(); } - public void storeFlight() { + private void storeFlight() { this.flight = this.player.getPlayer().isFlying(); this.allowFlight = this.player.getPlayer().getAllowFlight(); } - - public void storeEffects() { + + private void storeEffects() { this.effects.addAll(this.player.getPlayer().getActivePotionEffects()); } - - public void storeLocation() { + + private void storeLocation() { this.lastLocation = this.player.getPlayer().getLocation().clone(); } - - public void restore(Set toStore) { + + /** + * Restores the player's data from on the given {@link Type types}. + * + * @param toRestore the types to restore + */ + public void restore(Set toRestore) { if (!this.stored) { return; } - for (Type type : toStore) { + for (Type type : toRestore) { type.restore(this); } @@ -140,8 +154,8 @@ public void restore(Set toStore) { this.stored = false; } - - public void restoreAll() { + + private void restoreAll() { this.restoreInventory(); this.restoreGameMode(); this.restoreHealth(); @@ -151,21 +165,21 @@ public void restoreAll() { this.restoreEffects(); this.restoreLocation(); } - - public void restoreInventory() { + + private void restoreInventory() { this.player.getPlayer().getInventory().setContents(this.inventory); } - public void restoreGameMode() { + private void restoreGameMode() { this.player.getPlayer().setGameMode(this.gameMode); } - - public void restoreHealth() { + + private void restoreHealth() { this.player.getPlayer().setHealth(this.health); this.player.getPlayer().setFoodLevel(this.hunger); } - - public void restoreAttributes() { + + private void restoreAttributes() { for (Map.Entry entry : this.attributes.entrySet()) { AttributeInstance instance = this.player.getPlayer().getAttribute(entry.getKey()); if (instance == null) { @@ -179,32 +193,37 @@ public void restoreAttributes() { this.player.getPlayer().setFlySpeed(this.flySpeed); } - public void restoreFlight() { + private void restoreFlight() { this.player.getPlayer().setAllowFlight(this.allowFlight); this.player.getPlayer().setFlying(this.flight); } - public void restoreExperience() { + private void restoreExperience() { this.player.getPlayer().setTotalExperience(this.exp); this.player.getPlayer().setLevel(this.expLevels); } - - public void restoreEffects() { + + private void restoreEffects() { for (PotionEffect effect : this.effects) { this.player.getPlayer().addPotionEffect(effect); } } - public void restoreLocation() { + private void restoreLocation() { this.player.getPlayer().teleport(this.lastLocation); } + /** + * Returns the last stored location of the player. + * + * @return the last stored location of the player + */ @Nullable public Location getLastLocation() { return this.lastLocation; } - public void clearState(Set toStore) { + private void clearState(Set toStore) { boolean all = toStore.contains(Type.ALL); if (all || toStore.contains(Type.INVENTORY)) { this.player.getPlayer().getInventory().clear(); @@ -245,7 +264,10 @@ public void clearState(Set toStore) { } } } - + + /** + * The different types of data that can be stored/restored. + */ public enum Type { ALL(PlayerStorage::storeAll, PlayerStorage::restoreAll), INVENTORY(PlayerStorage::storeInventory, PlayerStorage::restoreInventory), diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/StatListener.java b/plugin/src/main/java/org/battleplugins/arena/competition/StatListener.java index 0567b2ed..f018c0b5 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/StatListener.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/StatListener.java @@ -11,7 +11,7 @@ import org.battleplugins.arena.stat.ArenaStats; import org.bukkit.event.EventPriority; -public class StatListener> implements ArenaListener, CompetitionLike { +class StatListener> implements ArenaListener, CompetitionLike { private final LiveCompetition competition; public StatListener(LiveCompetition competition) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/event/Event.java b/plugin/src/main/java/org/battleplugins/arena/competition/event/Event.java index 59f46b3d..84e2d067 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/event/Event.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/event/Event.java @@ -3,6 +3,15 @@ import org.battleplugins.arena.competition.Competition; import org.battleplugins.arena.competition.CompetitionType; +/** + * Represents an event competition. + *

+ * An event is a mode that is started based on a certain interval, + * or when triggered by a server action. These games cannot be joined + * normally unless the event is active. + *

+ * The bulk of event management logic is handled in the {@link EventScheduler}. + */ public interface Event extends Competition { @Override diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/event/EventOptions.java b/plugin/src/main/java/org/battleplugins/arena/competition/event/EventOptions.java index baaa000a..513728c0 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/event/EventOptions.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/event/EventOptions.java @@ -5,6 +5,9 @@ import java.time.Duration; +/** + * Represents the options for an event. + */ public class EventOptions { @ArenaOption(name = "type", required = true, description = "The type of event.") private EventType type; diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/event/EventScheduler.java b/plugin/src/main/java/org/battleplugins/arena/competition/event/EventScheduler.java index d7475e39..b0f69028 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/event/EventScheduler.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/event/EventScheduler.java @@ -3,6 +3,7 @@ import net.kyori.adventure.text.Component; import org.battleplugins.arena.Arena; import org.battleplugins.arena.competition.Competition; +import org.battleplugins.arena.competition.CompetitionType; import org.battleplugins.arena.competition.map.LiveCompetitionMap; import org.battleplugins.arena.competition.map.MapType; import org.bukkit.Bukkit; @@ -14,10 +15,21 @@ import java.util.Set; import java.util.concurrent.ThreadLocalRandom; +/** + * Represents an event scheduler for events, responsible + * for scheduling and starting events for arenas that are + * of type {@link CompetitionType#EVENT}. + */ public class EventScheduler { private final Map scheduledEvents = new HashMap<>(); private final Map> activeEvents = new HashMap<>(); + /** + * Schedules an event in the given {@link Arena}. + * + * @param arena the arena to schedule the event in + * @param options the options for the event + */ public void scheduleEvent(Arena arena, EventOptions options) { ScheduledEvent scheduledEvent = this.scheduledEvents.get(arena); if (scheduledEvent != null) { @@ -33,6 +45,12 @@ public void scheduleEvent(Arena arena, EventOptions options) { this.scheduledEvents.put(arena, new ScheduledEvent(options, bukkitTask)); } + /** + * Starts an event in the given {@link Arena}. + * + * @param arena the arena to start the event in + * @param options the options for the event + */ public void startEvent(Arena arena, EventOptions options) { if (this.activeEvents.containsKey(arena)) { arena.getPlugin().warn("An event is already running in arena {}, failed to start!", arena.getName()); @@ -61,6 +79,11 @@ public void startEvent(Arena arena, EventOptions options) { } } + /** + * Stops an event in the given {@link Arena}. + * + * @param arena the arena to stop the event in + */ public void stopEvent(Arena arena) { ScheduledEvent scheduledEvent = this.scheduledEvents.get(arena); if (scheduledEvent != null) { @@ -76,6 +99,12 @@ public void stopEvent(Arena arena) { arena.getPlugin().removeCompetition(arena, competition); } + /** + * Called when an event has ended in the given {@link Arena}. + * + * @param arena the arena the event ended in + * @param competition the competition that ended + */ public void eventEnded(Arena arena, Competition competition) { Competition activeCompetition = this.activeEvents.get(arena); if (activeCompetition == null || !activeCompetition.equals(competition)) { @@ -101,6 +130,9 @@ public void eventEnded(Arena arena, Competition competition) { arena.getPlugin().info("Event in arena {} has ended. Rescheduling event at interval.", arena.getName()); } + /** + * Stops all events in the scheduler. + */ public void stopAllEvents() { for (ScheduledEvent task : this.scheduledEvents.values()) { task.task().cancel(); @@ -110,6 +142,11 @@ public void stopAllEvents() { this.activeEvents.clear(); } + /** + * Gets all scheduled events in the scheduler. + * + * @return all scheduled events in the scheduler + */ public Set getScheduledEvents() { return Set.copyOf(this.scheduledEvents.keySet()); } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/event/LiveEvent.java b/plugin/src/main/java/org/battleplugins/arena/competition/event/LiveEvent.java index 4e536d5b..297bf786 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/event/LiveEvent.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/event/LiveEvent.java @@ -4,6 +4,9 @@ import org.battleplugins.arena.competition.LiveCompetition; import org.battleplugins.arena.competition.map.LiveCompetitionMap; +/** + * Represents an event which is live on this server. + */ public class LiveEvent extends LiveCompetition implements Event { public LiveEvent(Arena arena, LiveCompetitionMap map) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/map/LiveCompetitionMap.java b/plugin/src/main/java/org/battleplugins/arena/competition/map/LiveCompetitionMap.java index 6a6fff31..1fba211b 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/map/LiveCompetitionMap.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/map/LiveCompetitionMap.java @@ -17,6 +17,7 @@ import org.bukkit.WorldType; import org.jetbrains.annotations.Nullable; +import java.util.Optional; import java.util.UUID; /** @@ -70,10 +71,20 @@ public final String getName() { return this.name; } + /** + * Sets the name of the map. + * + * @param name the name of the map + */ public final void setName(String name) { this.name = name; } + /** + * Returns the {@link Arena} this map belongs to. + * + * @return the arena this map belongs to + */ @Override public final Arena getArena() { return this.arena; @@ -84,43 +95,116 @@ public final CompetitionType getCompetitionType() { return (CompetitionType) this.arena.getType(); } + /** + * Gets the {@link MapType} of this map. + * + * @return the map type of this map + */ @Override public final MapType getType() { return this.type; } + /** + * Sets the type of the map. + * + * @param type the type of the map + */ public final void setType(MapType type) { this.type = type; } + /** + * Gets the {@link World} this map is located in. + * + * @return the world this map is located in + */ public final World getWorld() { return this.mapWorld; } + /** + * Gets the {@link Bounds} of the map. + * + * @return the bounds of the map + */ + public final Optional bounds() { + return Optional.ofNullable(this.bounds); + } + + /** + * Gets the bounds of the map. + * + * @return the bounds of the map, or null if there are no bounds + */ @Nullable public final Bounds getBounds() { return this.bounds; } + /** + * Sets the bounds of the map. + * + * @param bounds the bounds of the map + */ public final void setBounds(Bounds bounds) { this.bounds = bounds; } + /** + * Gets the {@link Spawns} of the map. + * + * @return the spawn locations of the map + */ + public final Optional spawns() { + return Optional.ofNullable(this.spawns); + } + + /** + * Gets the spawn locations of the map. + * + * @return the spawn locations of the map, or null if there are no spawn locations + */ @Nullable public final Spawns getSpawns() { return this.spawns; } + /** + * Sets the spawn locations of the map. + * + * @param spawns the spawn locations of the map + */ public final void setSpawns(Spawns spawns) { this.spawns = spawns; } + + /** + * Creates a new competition for this map. + * + * @param arena the arena to create the competition for + * @return the created competition + */ public T createCompetition(Arena arena) { return this.getCompetitionType().create(arena, this); } + /** + * Creates a new dynamic competition for this map. + *

+ * This is only supported for maps with a {@link MapType} + * of type {@link MapType#DYNAMIC}. + * + * @param arena the arena to create the competition for + * @return the created dynamic competition + */ @Nullable public final T createDynamicCompetition(Arena arena) { + if (this.type != MapType.DYNAMIC) { + throw new IllegalStateException("Cannot create dynamic competition for non-dynamic map!"); + } + String worldName = "ba-dynamic-" + UUID.randomUUID(); World world = Bukkit.createWorld(WorldCreator.name(worldName) .generator(VoidChunkGenerator.INSTANCE) diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Bounds.java b/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Bounds.java index 42dfb591..2a12b547 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Bounds.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/map/options/Bounds.java @@ -5,6 +5,9 @@ import org.bukkit.Location; import org.bukkit.util.BoundingBox; +/** + * Represents the bounds of a map. + */ public class Bounds { @ArenaOption(name = "min-x", description = "The minimum X coordinate of the map.", required = true) private int minX; 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 7c933b8e..4589a424 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 @@ -6,6 +6,9 @@ import java.util.Map; +/** + * Represents the spawn options for a map. + */ public class Spawns { @ArenaOption(name = "waitroom", description = "The waitroom spawn.") private PositionWithRotation waitroomSpawn; diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/map/options/TeamSpawns.java b/plugin/src/main/java/org/battleplugins/arena/competition/map/options/TeamSpawns.java index 132e9ddc..7f8d938f 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/map/options/TeamSpawns.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/map/options/TeamSpawns.java @@ -6,6 +6,9 @@ import java.util.List; +/** + * Represents the spawn options for a team. + */ public class TeamSpawns { @ArenaOption(name = "spawns", description = "The spawns for this team.") diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/match/LiveMatch.java b/plugin/src/main/java/org/battleplugins/arena/competition/match/LiveMatch.java index 963af709..c3e0c1cd 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/match/LiveMatch.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/match/LiveMatch.java @@ -4,6 +4,9 @@ import org.battleplugins.arena.competition.LiveCompetition; import org.battleplugins.arena.competition.map.LiveCompetitionMap; +/** + * Represents a match which is live on this server. + */ public class LiveMatch extends LiveCompetition implements Match { public LiveMatch(Arena arena, LiveCompetitionMap map) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/match/Match.java b/plugin/src/main/java/org/battleplugins/arena/competition/match/Match.java index 835674a4..2bbb6628 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/match/Match.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/match/Match.java @@ -2,7 +2,18 @@ import org.battleplugins.arena.competition.Competition; import org.battleplugins.arena.competition.CompetitionType; +import org.battleplugins.arena.competition.event.EventScheduler; +/** + * Represents a match competition. + *

+ * A match is a mode that is started when a certain condition is met (i.e. number of players), + * or is always active. These games can be joined at any time, as long as there are available + * maps. + *

+ * The bulk of match management logic is handled by the plugin and manual intervention in terms + * of starting the match is not required. + */ public interface Match extends Competition { @Override diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhase.java b/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhase.java index 65b53295..20ea2683 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhase.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhase.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; /** * Represents the phase of a competition. @@ -51,8 +52,14 @@ public abstract class CompetitionPhase> implements Comp // API methods + /** + * Called when the phase starts. + */ public abstract void onStart(); + /** + * Called when the phase completes. + */ public abstract void onComplete(); // Internal methods (cannot be overridden by extending plugins) @@ -71,32 +78,87 @@ public final T getCompetition() { return this.competition; } + /** + * Gets whether players can join during this phase. + * + * @return whether players can join during this phase + */ public final boolean canJoin() { return this.allowJoin; } + /** + * Gets whether players can spectate during this phase. + * + * @return whether players can spectate during this phase + */ public final boolean canSpectate() { return this.allowSpectate; } + /** + * Gets the {@link EventAction actions} for this phase. + * + * @return the event actions for this phase + */ public final Map, List> getEventActions() { - return this.eventActions; + return Map.copyOf(this.eventActions); } + /** + * Gets the {@link CompetitionPhaseType} of this phase. + * + * @return the competition phase type of this phase + */ public final CompetitionPhaseType> getType() { return this.type; } + /** + * Gets the next phase that succeeds this phase. + * + * @return the next phase that succeeds this phase + */ + public final Optional>> nextPhase() { + return Optional.ofNullable(this.nextPhase); + } + + /** + * Gets the next phase that succeeds this phase. + * + * @return the next phase that succeeds this phase, or + * null if there is no next phase + */ @Nullable public final CompetitionPhaseType> getNextPhase() { return this.nextPhase; } + /** + * Gets the previous phase that precedes this phase. + * + * @return the previous phase that precedes this phase + */ + public final Optional> previousPhase() { + return Optional.ofNullable(this.previousPhase); + } + + /** + * Gets the previous phase that precedes this phase. + * + * @return the previous phase that precedes this phase, or + * null if there is no previous phase + */ @Nullable public final CompetitionPhase getPreviousPhase() { return this.previousPhase; } + /** + * Sets the previous phase that precedes this phase. + * + * @param previousPhase the previous phase that precedes this phase + */ protected final void setPreviousPhase(CompetitionPhase previousPhase) { this.previousPhase = previousPhase; } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhaseType.java b/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhaseType.java index 0a7f136f..ebb98f9c 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhaseType.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/phase/CompetitionPhaseType.java @@ -12,6 +12,12 @@ import java.util.Objects; import java.util.Set; +/** + * Represents a type of competition phase. + * + * @param the type of competition + * @param the type of competition phase + */ public final class CompetitionPhaseType, T extends CompetitionPhase> { private static final Map> PHASE_TYPES = new HashMap<>(); diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/phase/LiveCompetitionPhase.java b/plugin/src/main/java/org/battleplugins/arena/competition/phase/LiveCompetitionPhase.java index 5bf9ebe7..6b5f9ae5 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/phase/LiveCompetitionPhase.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/phase/LiveCompetitionPhase.java @@ -6,6 +6,13 @@ import org.battleplugins.arena.options.ArenaOptionType; import org.jetbrains.annotations.Nullable; +import java.util.Optional; + +/** + * Represents a live competition phase. + * + * @param the type of competition + */ public abstract class LiveCompetitionPhase> extends CompetitionPhase { public void setPhase(CompetitionPhaseType> phase) { @@ -40,6 +47,9 @@ final void complete() { super.complete(); } + /** + * Advances to the next phase. + */ protected void advanceToNextPhase() { if (this.nextPhase == null) { this.competition.getArena().getPlugin().warn("No next phase found for {}! Not advancing to next phase.", this.getClass().getSimpleName()); @@ -55,6 +65,24 @@ protected void advanceToNextPhase() { this.setPhase(this.nextPhase); } + /** + * Gets the {@link org.battleplugins.arena.options.ArenaOption} of the specified type. + * + * @param type the type of option + * @param the type of option + * @return the option of the specified type + */ + public final Optional option(ArenaOptionType type) { + return Optional.ofNullable(this.getOption(type)); + } + + /** + * Gets the {@link org.battleplugins.arena.options.ArenaOption} of the specified type. + * + * @param type the type of option + * @param the type of option + * @return the option of the specified type, or null if it does not exist + */ @Nullable public E getOption(ArenaOptionType type) { if (this.options == null) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/phase/PhaseManager.java b/plugin/src/main/java/org/battleplugins/arena/competition/phase/PhaseManager.java index 91c61b68..4956dd51 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/phase/PhaseManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/phase/PhaseManager.java @@ -4,6 +4,11 @@ import org.battleplugins.arena.competition.Competition; import org.battleplugins.arena.competition.victory.VictoryCondition; +/** + * Manages the phases of a competition. + * + * @param the type of competition + */ public class PhaseManager> { private final Arena arena; private final T competition; @@ -15,10 +20,21 @@ public PhaseManager(Arena arena, T competition) { this.competition = competition; } + /** + * Sets the current phase of the competition. + * + * @param phaseType the phase type to set + */ public void setPhase(CompetitionPhaseType phaseType) { this.setPhase(phaseType, true); } + /** + * Sets the current phase of the competition. + * + * @param phaseType the phase type to set + * @param complete whether the phase is complete + */ public void setPhase(CompetitionPhaseType phaseType, boolean complete) { this.end(complete); @@ -27,6 +43,11 @@ public void setPhase(CompetitionPhaseType phaseType, boolean complete) { this.currentPhase.start(); } + /** + * Ends the current phase of the competition. + * + * @param complete whether the phase is complete + */ public void end(boolean complete) { if (this.currentPhase != null) { if (complete) { @@ -36,6 +57,11 @@ public void end(boolean complete) { } } + /** + * Returns the current {@link CompetitionPhase} of the competition. + * + * @return the current phase of the competition + */ public CompetitionPhase getCurrentPhase() { return this.currentPhase; } 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 011d0f7e..e892e3b5 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 @@ -14,6 +14,9 @@ import java.util.Map; import java.util.Set; +/** + * Represents the team manager, which manages all the teams in the competition. + */ public class TeamManager { private final LiveCompetition competition; @@ -24,15 +27,31 @@ public TeamManager(LiveCompetition competition) { this.competition = competition; } + /** + * Joins the given {@link ArenaPlayer} to the specified {@link ArenaTeam}. + * + * @param player the player to join + */ public void joinTeam(ArenaPlayer player, ArenaTeam team) { this.teams.computeIfAbsent(team, e -> new HashSet<>()).add(player); player.setTeam(team); } + /** + * Removes the given {@link ArenaPlayer} from their current team. + * + * @param player the player to remove + */ public void leaveTeam(ArenaPlayer player) { this.leaveTeam(player, player.getTeam()); } + /** + * Removes the given {@link ArenaPlayer} from the specified {@link ArenaTeam}. + * + * @param player the player to remove + * @param team the team to remove the player from + */ public void leaveTeam(ArenaPlayer player, ArenaTeam team) { Set players = this.teams.get(team); if (players == null) { @@ -43,15 +62,33 @@ public void leaveTeam(ArenaPlayer player, ArenaTeam team) { player.setTeam(null); } + /** + * Returns the number of players on the given {@link ArenaTeam}. + * + * @param team the team to get the number of players from + * @return the number of players on the team + */ public int getNumberOfPlayersOnTeam(ArenaTeam team) { Set players = this.teams.get(team); return players == null ? 0 : players.size(); } + /** + * Returns all the players on the given {@link ArenaTeam}. + * + * @param team the team to get the players from + * @return all the players on the team + */ public Set getPlayersOnTeam(ArenaTeam team) { return this.teams.getOrDefault(team, Set.of()); } + /** + * Finds a suitable team for the player to join. + * + * @return a suitable team for the player to join, or + * null if no suitable team is found + */ @Nullable public ArenaTeam findSuitableTeam() { Teams teams = this.competition.getArena().getTeams(); @@ -79,10 +116,21 @@ public ArenaTeam findSuitableTeam() { return null; } + /** + * Returns all the {@link ArenaTeam teams} in the competition. + * + * @return all the teams in the competition + */ public Set getTeams() { return Set.copyOf(this.teams.keySet()); } + /** + * Returns the {@link StatHolder} for the given {@link ArenaTeam}. + * + * @param team the team to get the stats for + * @return the stats for the team + */ public StatHolder getStats(ArenaTeam team) { return this.stats.computeIfAbsent(team, e -> new TeamStatHolder(this, team)); } @@ -93,6 +141,11 @@ private boolean canJoinTeam(ArenaTeam team) { return playersOnTeam + 1 <= teams.getTeamSize().getMax(); } + /** + * Gets the {@link LiveCompetition} this team manager is managing. + * + * @return the competition this team manager is managing + */ public LiveCompetition getCompetition() { return this.competition; } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryConditionType.java b/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryConditionType.java index 16c1bdbe..9384b8dd 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryConditionType.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryConditionType.java @@ -11,6 +11,12 @@ import java.util.Map; import java.util.Set; +/** + * Represents a victory condition type. + * + * @param the type of competition + * @param the type of victory condition + */ @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/victory-conditions-reference") public final class VictoryConditionType, T extends VictoryCondition> { private static final Map> VICTORY_TYPES = new HashMap<>(); diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryManager.java b/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryManager.java index 440cfcf1..04ff035a 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/victory/VictoryManager.java @@ -15,6 +15,11 @@ import java.util.Map; import java.util.Set; +/** + * Manages the victory conditions of a competition. + * + * @param the type of competition + */ public class VictoryManager> implements ArenaListener, CompetitionLike { private final Map, VictoryCondition> victoryConditions = new HashMap<>(); @@ -41,6 +46,11 @@ public VictoryManager(Arena arena, T competition) { arena.getEventManager().registerEvents(this); } + /** + * Identifies the potential victors of the competition. + * + * @return the potential victors of the competition + */ public Set identifyPotentialVictors() { Set victors = new HashSet<>(); int conditionsWithVictors = 0; @@ -67,6 +77,11 @@ public Set identifyPotentialVictors() { return victors; } + /** + * Ends the victory manager. + * + * @param closed whether the victory manager is closed + */ public void end(boolean closed) { this.closed = closed; @@ -92,11 +107,21 @@ public void onPhaseStart(ArenaPhaseStartEvent event) { } } + /** + * Returns the {@link LiveCompetition} this team manager is managing. + * + * @return the competition this team manager is managing + */ @Override public T getCompetition() { return this.competition; } + /** + * Returns whether the victory manager is closed. + * + * @return whether the victory manager is closed + */ public final boolean isClosed() { return this.closed; } diff --git a/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java b/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java index 4b9828a0..feaa3a7c 100644 --- a/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java @@ -37,6 +37,9 @@ import java.util.Set; import java.util.function.Function; +/** + * Manages events for an {@link Arena}. + */ public class ArenaEventManager { private static final Map, Function> PLAYER_EVENT_RESOLVERS = new PolymorphicHashMap<>() { { @@ -76,14 +79,35 @@ public ArenaEventManager(Arena arena) { this.arena = arena; } + /** + * Registers a custom resolver for a specific event class. + *

+ * Custom resolvers allow you to resolve the {@link LiveCompetition} for an event + * that may not be directly associated with an {@link Arena}. + * + * @param eventClass the event class + * @param resolver the resolver for the event class + */ public void registerArenaResolver(Class eventClass, Function> resolver) { this.arenaEventResolvers.put(eventClass, resolver); } + /** + * Gets the {@link Arena} this event manager is managing events for. + * + * @return the arena this event manager is managing events for + */ public Arena getArena() { return this.arena; } + /** + * Calls an event and processes any actions associated with the event. + * + * @param event the event to call + * @param the type of event + * @return the event after processing + */ public T callEvent(T event) { Bukkit.getPluginManager().callEvent(event); if (event.getEventTrigger() != null) { @@ -161,6 +185,11 @@ private void pollActions(Competition competition, Iterator itera } } + /** + * Registers an {@link ArenaListener} to listen for events. + * + * @param listener the listener to register + */ public void registerEvents(ArenaListener listener) { this.trackedListeners.add(listener); @@ -263,11 +292,19 @@ public void registerEvents(ArenaListener listener) { } } + /** + * Unregisters an {@link ArenaListener} from listening for events. + * + * @param listener the listener to unregister + */ public void unregisterEvents(ArenaListener listener) { HandlerList.unregisterAll(listener); this.trackedListeners.remove(listener); } + /** + * Unregisters all listeners from listening for events. + */ public void unregisterAll() { for (ArenaListener listener : this.trackedListeners) { HandlerList.unregisterAll(listener); diff --git a/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventType.java b/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventType.java index fecf9836..5c68aea3 100644 --- a/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventType.java +++ b/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventType.java @@ -19,8 +19,14 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; +/** + * Represents an event type in an arena. + * + * @param the type of event + */ @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/event-reference") public final class ArenaEventType { private static final Map> EVENT_TYPES = new HashMap<>(); @@ -67,6 +73,19 @@ public static ArenaEventType create(String name, Class return new ArenaEventType<>(name, clazz); } + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + ArenaEventType that = (ArenaEventType) object; + return Objects.equals(this.clazz, that.clazz); + } + + @Override + public int hashCode() { + return Objects.hash(this.clazz); + } + public static Set> values() { return Set.copyOf(EVENT_TYPES.values()); } diff --git a/plugin/src/main/java/org/battleplugins/arena/event/action/EventAction.java b/plugin/src/main/java/org/battleplugins/arena/event/action/EventAction.java index d7ba4890..4cc05b9d 100644 --- a/plugin/src/main/java/org/battleplugins/arena/event/action/EventAction.java +++ b/plugin/src/main/java/org/battleplugins/arena/event/action/EventAction.java @@ -3,9 +3,13 @@ import org.battleplugins.arena.Arena; import org.battleplugins.arena.ArenaPlayer; import org.battleplugins.arena.competition.Competition; +import org.jetbrains.annotations.Nullable; import java.util.Map; +/** + * Represents an action that occurs in an {@link Arena}. + */ public abstract class EventAction { private final Map params; @@ -18,19 +22,56 @@ public EventAction(Map params, String... requiredKeys) { } } + /** + * Gets the parameter with the given key. + * + * @param key the key to get the parameter from + * @return the parameter with the given key + */ + @Nullable public String get(String key) { return this.params.get(key); } + /** + * Gets the parameter with the given key or the default value if the key does not exist. + * + * @param key the key to get the parameter from + * @param defaultValue the default value to return if the key does not exist + * @return the parameter with the given key or the default value if the key does not exist + */ public String getOrDefault(String key, String defaultValue) { return this.params.getOrDefault(key, defaultValue); } + /** + * Called before the action is processed. + *

+ * This is run globally any time an action occurs, meaning this + * method is not bound to any specific player. + * + * @param arena the arena the action is occurring in + * @param competition the competition the action is occurring in + */ public void preProcess(Arena arena, Competition competition) { } + /** + * Called after the action is processed. + *

+ * This is run globally any time an action occurs, meaning this + * method is not bound to any specific player. + * + * @param arena the arena the action is occurring in + * @param competition the competition the action is occurring in + */ public void postProcess(Arena arena, Competition competition) { } + /** + * Calls the action for the given {@link ArenaPlayer}. + * + * @param arenaPlayer the player to call the action for + */ public abstract void call(ArenaPlayer arenaPlayer); } 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 34b381cb..f5608cb6 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 @@ -25,9 +25,15 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; +/** + * Represents an event action type in an arena. + * + * @param the type of event action + */ @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/action-reference") public final class EventActionType { private static final Map> ACTION_TYPES = new HashMap<>(); @@ -86,6 +92,19 @@ public static EventActionType create(String name, Cla return new EventActionType<>(name, clazz, factory); } + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + EventActionType that = (EventActionType) object; + return Objects.equals(this.clazz, that.clazz); + } + + @Override + public int hashCode() { + return Objects.hash(this.clazz); + } + public static Set> values() { return Set.copyOf(ACTION_TYPES.values()); } 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 f7cece21..92ea7769 100644 --- a/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java +++ b/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java @@ -10,6 +10,11 @@ import java.util.Set; import java.util.function.Function; +/** + * Represents an option in an arena. + * + * @param the type of option + */ @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/option-reference") public final class ArenaOptionType { private static final Map> OPTION_TYPES = new HashMap<>(); diff --git a/plugin/src/main/java/org/battleplugins/arena/util/PositionWithRotation.java b/plugin/src/main/java/org/battleplugins/arena/util/PositionWithRotation.java index a509ac43..21588799 100644 --- a/plugin/src/main/java/org/battleplugins/arena/util/PositionWithRotation.java +++ b/plugin/src/main/java/org/battleplugins/arena/util/PositionWithRotation.java @@ -4,6 +4,9 @@ import org.bukkit.Location; import org.bukkit.World; +/** + * Represents a position with a rotation. + */ public class PositionWithRotation { @ArenaOption(name = "x", description = "The X position of the spawn.", required = true) private double x;