From 21425f73a16ccf3611a569822a6a870e9b36b12f Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Wed, 29 Mar 2023 00:55:19 +0200 Subject: [PATCH 01/11] Performs a lot of changes. Mostly implements #3 Properly and persistently stores which players have beaten which arenas Adds a hopefully fully functional DropperArenaGroup class Stores arena groups in the arena handler Adds a lookup map to improve performance of getting arena by name Adds saving and loading of groups Adds all necessary checks for whether players have beaten the required dropper arenas before joining Removes stage from arenas Adds a test to make sure changing the order of arenas in a group works as intended --- pom.xml | 6 + .../java/net/knarcraft/dropper/Dropper.java | 4 + .../knarcraft/dropper/arena/DropperArena.java | 41 ++- .../dropper/arena/DropperArenaData.java | 14 +- .../dropper/arena/DropperArenaGroup.java | 250 ++++++++++++++++++ .../dropper/arena/DropperArenaHandler.java | 116 ++++++-- .../dropper/arena/DropperArenaSession.java | 11 +- .../dropper/command/CreateArenaCommand.java | 2 +- .../dropper/command/JoinArenaCommand.java | 39 ++- .../dropper/property/ArenaStorageKey.java | 5 - .../dropper/util/ArenaStorageHelper.java | 54 +++- .../dropper/arena/DropperArenaGroupTest.java | 50 ++++ 12 files changed, 524 insertions(+), 68 deletions(-) create mode 100644 src/main/java/net/knarcraft/dropper/arena/DropperArenaGroup.java create mode 100644 src/test/java/net/knarcraft/dropper/arena/DropperArenaGroupTest.java diff --git a/pom.xml b/pom.xml index c594159..c1b5315 100644 --- a/pom.xml +++ b/pom.xml @@ -73,5 +73,11 @@ 24.0.1 provided + + org.junit.jupiter + junit-jupiter + 5.9.2 + test + diff --git a/src/main/java/net/knarcraft/dropper/Dropper.java b/src/main/java/net/knarcraft/dropper/Dropper.java index 79ba724..df2408d 100644 --- a/src/main/java/net/knarcraft/dropper/Dropper.java +++ b/src/main/java/net/knarcraft/dropper/Dropper.java @@ -1,6 +1,7 @@ package net.knarcraft.dropper; import net.knarcraft.dropper.arena.DropperArenaData; +import net.knarcraft.dropper.arena.DropperArenaGroup; import net.knarcraft.dropper.arena.DropperArenaHandler; import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry; import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry; @@ -76,6 +77,7 @@ public final class Dropper extends JavaPlugin { public void reload() { // Load all arenas again this.arenaHandler.loadArenas(); + this.arenaHandler.loadGroups(); } @Override @@ -86,6 +88,7 @@ public final class Dropper extends JavaPlugin { ConfigurationSerialization.registerClass(DropperArenaRecordsRegistry.class); ConfigurationSerialization.registerClass(SerializableUUID.class); ConfigurationSerialization.registerClass(DropperArenaData.class); + ConfigurationSerialization.registerClass(DropperArenaGroup.class); } @Override @@ -95,6 +98,7 @@ public final class Dropper extends JavaPlugin { this.playerRegistry = new DropperArenaPlayerRegistry(); this.arenaHandler = new DropperArenaHandler(); this.arenaHandler.loadArenas(); + this.arenaHandler.loadGroups(); //TODO: Store various information about players' performance, and hook into PlaceholderAPI diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArena.java b/src/main/java/net/knarcraft/dropper/arena/DropperArena.java index ac2b978..3cb3e60 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArena.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArena.java @@ -1,5 +1,6 @@ package net.knarcraft.dropper.arena; +import net.knarcraft.dropper.util.ArenaStorageHelper; import org.bukkit.Location; import org.bukkit.Material; import org.jetbrains.annotations.NotNull; @@ -46,11 +47,6 @@ public class DropperArena { */ private final float playerHorizontalVelocity; - /** - * The stage number of this arena. If not null, the previous stage number must be cleared before access. - */ - private final @Nullable Integer stage; - /** * The material of the block players have to hit to win this dropper arena */ @@ -72,21 +68,18 @@ public class DropperArena { * @param exitLocation

The location the players are teleported to when exiting the arena, or null

* @param playerVerticalVelocity

The velocity to use for players' vertical velocity

* @param playerHorizontalVelocity

The velocity to use for players' horizontal velocity (-1 to 1)

- * @param stage

The stage number of this stage, or null if not limited to stages

* @param winBlockType

The material of the block players have to hit to win this dropper arena

* @param dropperArenaData

The arena data keeping track of which players have done what in this arena

*/ public DropperArena(@NotNull UUID arenaId, @NotNull String arenaName, @NotNull Location spawnLocation, @Nullable Location exitLocation, double playerVerticalVelocity, float playerHorizontalVelocity, - @Nullable Integer stage, @NotNull Material winBlockType, - @NotNull DropperArenaData dropperArenaData) { + @NotNull Material winBlockType, @NotNull DropperArenaData dropperArenaData) { this.arenaId = arenaId; this.arenaName = arenaName; this.spawnLocation = spawnLocation; this.exitLocation = exitLocation; this.playerVerticalVelocity = playerVerticalVelocity; this.playerHorizontalVelocity = playerHorizontalVelocity; - this.stage = stage; this.winBlockType = winBlockType; this.dropperArenaData = dropperArenaData; } @@ -107,7 +100,6 @@ public class DropperArena { this.exitLocation = null; this.playerVerticalVelocity = 1; this.playerHorizontalVelocity = 1; - this.stage = null; this.dropperArenaData = new DropperArenaData(this.arenaId, new DropperArenaRecordsRegistry(this.arenaId), new HashSet<>()); this.winBlockType = Material.WATER; @@ -183,18 +175,6 @@ public class DropperArena { return this.playerHorizontalVelocity; } - /** - * Gets the stage this arena belongs to - * - *

It's assumed that arena stages go from 1,2,3,4,... and upwards. If the stage number is set, this arena can - * only be played if all previous stages have been beaten. If not set, however, this arena can be used freely.

- * - * @return

This arena's stage number

- */ - public @Nullable Integer getStage() { - return this.stage; - } - /** * Gets the type of block a player has to hit to win this arena * @@ -204,4 +184,21 @@ public class DropperArena { return this.winBlockType; } + /** + * Gets this arena's sanitized name + * + * @return

This arena's sanitized name

+ */ + public @NotNull String getArenaNameSanitized() { + return ArenaStorageHelper.sanitizeArenaName(this.getArenaName()); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DropperArena otherArena)) { + return false; + } + return this.getArenaNameSanitized().equals(otherArena.getArenaNameSanitized()); + } + } diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java index 457fb0d..38d5158 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java @@ -1,5 +1,6 @@ package net.knarcraft.dropper.arena; +import net.knarcraft.dropper.Dropper; import net.knarcraft.dropper.container.SerializableUUID; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.Player; @@ -41,8 +42,8 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor * @param player

The player to check

* @return

True if the player has cleared the arena this data belongs to

*/ - public boolean hasCompleted(@NotNull Player player) { - return this.playersCompleted.contains(new SerializableUUID(player.getUniqueId())); + public boolean hasNotCompleted(@NotNull Player player) { + return !this.playersCompleted.contains(new SerializableUUID(player.getUniqueId())); } /** @@ -50,8 +51,13 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor * * @param player

The player that completed this data's arena

*/ - public void addCompleted(@NotNull Player player) { - this.playersCompleted.add(new SerializableUUID(player.getUniqueId())); + public boolean addCompleted(@NotNull Player player) { + boolean added = this.playersCompleted.add(new SerializableUUID(player.getUniqueId())); + // Persistently save the completion + if (added) { + Dropper.getInstance().getArenaHandler().saveData(this.arenaId); + } + return added; } @NotNull diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaGroup.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaGroup.java new file mode 100644 index 0000000..241e774 --- /dev/null +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaGroup.java @@ -0,0 +1,250 @@ +package net.knarcraft.dropper.arena; + +import net.knarcraft.dropper.Dropper; +import net.knarcraft.dropper.container.SerializableUUID; +import net.knarcraft.dropper.util.ArenaStorageHelper; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +/** + * A sorted group of arenas that must be completed in sequence + */ +public class DropperArenaGroup implements ConfigurationSerializable { + + /** + * The unique id for this group of arenas + */ + private final UUID groupId; + + /** + * The unique name for this group of arenas + */ + private final String groupName; + + /** + * The arenas in this group, ordered from stage 1 to stage n + */ + private final List arenas; + + /** + * Instantiates a new dropper arena group + * + * @param groupName

The name of this group

+ */ + public DropperArenaGroup(@NotNull String groupName) { + this.groupId = UUID.randomUUID(); + this.groupName = groupName; + this.arenas = new ArrayList<>(); + } + + /** + * Instantiates a new dropper arena group + * + * @param groupId

The unique id of this group

+ * @param groupName

The name of this group

+ * @param arenas

The arenas in this group

+ */ + private DropperArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List arenas) { + this.groupId = groupId; + this.groupName = groupName; + this.arenas = new ArrayList<>(arenas); + } + + /** + * Gets the id of this dropper arena group + * + * @return

The id of this group

+ */ + public @NotNull UUID getGroupId() { + return this.groupId; + } + + /** + * Gets the name of this dropper arena group + * + * @return

The name of this group

+ */ + public @NotNull String getGroupName() { + return this.groupName; + } + + /** + * Gets the arenas contained in this group in the correct order + * + * @return

The ids of the arenas in this group

+ */ + public @NotNull List getArenas() { + return new ArrayList<>(arenas); + } + + /** + * Removes the given dropper arena from this group + * + * @param arenaId

The id of the dropper arena to remove

+ */ + public void removeArena(UUID arenaId) { + this.arenas.remove(arenaId); + } + + /** + * Adds an arena to the end of this group + * + * @param arenaId

The arena to add to this group

+ */ + public void addArena(UUID arenaId) { + addArena(arenaId, this.arenas.size()); + } + + /** + * Adds an arena to the end of this group + * + * @param arenaId

The arena to add to this group

+ * @param index

The index to put the arena in

+ */ + public void addArena(UUID arenaId, int index) { + // Make sure we don't have duplicates + if (!this.arenas.contains(arenaId)) { + this.arenas.add(index, arenaId); + } + } + + /** + * Checks whether the given player has beaten all arenas in this group + * + * @param player

The player to check

+ * @return

True if the player has beaten all arenas, false otherwise

+ */ + public boolean hasBeatenAll(Player player) { + DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler(); + for (UUID anArenaId : this.getArenas()) { + DropperArena dropperArena = arenaHandler.getArena(anArenaId); + if (dropperArena == null) { + // The arena would only be null if the arena has been deleted, but not removed from this group + Dropper.getInstance().getLogger().log(Level.WARNING, "The dropper group " + this.getGroupName() + + " contains the arena id " + anArenaId + " which is not a valid arena id!"); + continue; + } + + if (dropperArena.getData().hasNotCompleted(player)) { + return false; + } + } + return true; + } + + /** + * Gets whether the given player can play the given arena part of this group + * + * @param player

The player to check

+ * @param arenaId

The id of the arena in this group to check

+ * @return

True if the player is allowed to play the arena

+ * @throws IllegalArgumentException

If checking an arena not in this group

+ */ + public boolean canPlay(Player player, UUID arenaId) throws IllegalArgumentException { + if (!this.arenas.contains(arenaId)) { + throw new IllegalArgumentException("Cannot check for playability for arena not in this group!"); + } + + DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler(); + + for (UUID anArenaId : this.getArenas()) { + // If the target arena is reached, allow, as all previous arenas must have been cleared + if (arenaId == anArenaId) { + return true; + } + + DropperArena dropperArena = arenaHandler.getArena(anArenaId); + if (dropperArena == null) { + // The arena would only be null if the arena has been deleted, but not removed from this group + Dropper.getInstance().getLogger().log(Level.WARNING, String.format("The dropper group %s contains the" + + " arena id %s which is not a valid arena id!", this.getGroupName(), anArenaId)); + continue; + } + + // This is a lower-numbered arena the player has yet to complete + if (dropperArena.getData().hasNotCompleted(player)) { + return false; + } + } + + return false; + } + + /** + * Gets this group's name, but sanitized + * + * @return

The sanitized group name

+ */ + public @NotNull String getGroupNameSanitized() { + return ArenaStorageHelper.sanitizeArenaName(this.getGroupName()); + } + + /** + * Swaps the arenas at the given indices + * + * @param index1

The index of the first arena to swap

+ * @param index2

The index of the second arena to swap

+ */ + public void swapArenas(int index1, int index2) { + // Change nothing if not a valid request + if (index1 == index2 || index1 < 0 || index2 < 0 || index1 >= this.arenas.size() || + index2 >= this.arenas.size()) { + return; + } + + // Swap the two arena ids + UUID temporaryValue = this.arenas.get(index2); + this.arenas.set(index2, this.arenas.get(index1)); + this.arenas.set(index1, temporaryValue); + } + + @NotNull + @Override + public Map serialize() { + Map data = new HashMap<>(); + data.put("groupId", new SerializableUUID(this.groupId)); + data.put("groupName", this.groupName); + + List serializableArenas = new ArrayList<>(); + for (UUID arenaId : arenas) { + serializableArenas.add(new SerializableUUID(arenaId)); + } + data.put("arenas", serializableArenas); + return data; + } + + /** + * Deserializes the given data + * + * @param data

The data to deserialize

+ * @return

The deserialized arena group

+ */ + @SuppressWarnings({"unused", "unchecked"}) + public static @NotNull DropperArenaGroup deserialize(@NotNull Map data) { + UUID id = ((SerializableUUID) data.get("groupId")).uuid(); + String name = (String) data.get("groupName"); + List serializableArenas = (List) data.get("arenas"); + List arenas = new ArrayList<>(); + for (SerializableUUID arenaId : serializableArenas) { + arenas.add(arenaId.uuid()); + } + return new DropperArenaGroup(id, name, arenas); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DropperArenaGroup otherGroup)) { + return false; + } + return this.getGroupNameSanitized().equals(otherGroup.getGroupNameSanitized()); + } + +} diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaHandler.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaHandler.java index 7041e4a..cbdfa1f 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaHandler.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaHandler.java @@ -2,13 +2,13 @@ package net.knarcraft.dropper.arena; import net.knarcraft.dropper.Dropper; import net.knarcraft.dropper.util.ArenaStorageHelper; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -18,22 +18,57 @@ import java.util.logging.Level; public class DropperArenaHandler { private Map arenas = new HashMap<>(); - private final Map stagesCleared = new HashMap<>(); + private Map arenaGroups = new HashMap<>(); + private Map arenaNameLookup = new HashMap<>(); /** - * Tries to register the given stage as cleared + * Gets the group the given arena belongs to * - * @param player

The player that cleared a stage

- * @param stage

The stage the player cleared

- * @return

True if the player cleared a new stage

+ * @param arenaId

The id of the arena to get the group of

+ * @return

The group the arena belongs to, or null if not in a group

*/ - public boolean registerStageCleared(@NotNull Player player, int stage) { - if ((!stagesCleared.containsKey(player) && stage == 1) || (stagesCleared.containsKey(player) && - stage == stagesCleared.get(player) + 1)) { - stagesCleared.put(player, stage); - return true; - } else { - return false; + public @Nullable DropperArenaGroup getGroup(@NotNull UUID arenaId) { + return this.arenaGroups.get(arenaId); + } + + /** + * Sets the group for the given arena + * + * @param arenaId

The id of the arena to change

+ * @param arenaGroup

The group to add the arena to

+ */ + public void setGroup(@NotNull UUID arenaId, @NotNull DropperArenaGroup arenaGroup) { + this.arenaGroups.put(arenaId, arenaGroup); + arenaGroup.addArena(arenaId); + saveGroups(); + } + + /** + * Gets the dropper arena group with the given name + * + * @param groupName

The name of the group to get

+ * @return

The group, or null if not found

+ */ + public @Nullable DropperArenaGroup getGroup(String groupName) { + String sanitized = ArenaStorageHelper.sanitizeArenaName(groupName); + for (DropperArenaGroup arenaGroup : this.arenaGroups.values()) { + if (arenaGroup.getGroupNameSanitized().equals(sanitized)) { + return arenaGroup; + } + } + return null; + } + + /** + * Removes the given arena from its group + * + * @param arenaId

The id of the arena to ungroup

+ */ + public void removeFromGroup(@NotNull UUID arenaId) { + if (this.arenaGroups.containsKey(arenaId)) { + this.arenaGroups.get(arenaId).removeArena(arenaId); + this.arenaGroups.remove(arenaId); + saveGroups(); } } @@ -44,9 +79,20 @@ public class DropperArenaHandler { */ public void addArena(@NotNull DropperArena arena) { this.arenas.put(arena.getArenaId(), arena); + this.arenaNameLookup.put(arena.getArenaNameSanitized(), arena.getArenaId()); this.saveArenas(); } + /** + * Gets the arena with the given id + * + * @param arenaId

The id of the arena to get

+ * @return

The arena, or null if no arena could be found

+ */ + public @Nullable DropperArena getArena(@NotNull UUID arenaId) { + return this.arenas.get(arenaId); + } + /** * Gets the arena with the given name * @@ -54,13 +100,7 @@ public class DropperArenaHandler { * @return

The arena with the given name, or null if not found

*/ public @Nullable DropperArena getArena(@NotNull String arenaName) { - arenaName = ArenaStorageHelper.sanitizeArenaName(arenaName); - for (DropperArena arena : arenas.values()) { - if (ArenaStorageHelper.sanitizeArenaName(arena.getArenaName()).equals(arenaName)) { - return arena; - } - } - return null; + return this.arenas.get(this.arenaNameLookup.get(ArenaStorageHelper.sanitizeArenaName(arenaName))); } /** @@ -80,6 +120,8 @@ public class DropperArenaHandler { public void removeArena(@NotNull DropperArena arena) { Dropper.getInstance().getPlayerRegistry().removeForArena(arena); this.arenas.remove(arena.getArenaId()); + this.arenaNameLookup.remove(arena.getArenaNameSanitized()); + this.arenaGroups.remove(arena.getArenaId()); this.saveArenas(); } @@ -97,6 +139,33 @@ public class DropperArenaHandler { } } + /** + * Saves all current dropper groups to disk + */ + public void saveGroups() { + try { + ArenaStorageHelper.saveDropperArenaGroups((Set) this.arenaGroups.values()); + } catch (IOException e) { + Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to save current arena groups! " + + "Data loss can occur!"); + Dropper.getInstance().getLogger().log(Level.SEVERE, e.getMessage()); + } + } + + /** + * Loads all dropper groups from disk + */ + public void loadGroups() { + Set arenaGroups = ArenaStorageHelper.loadDropperArenaGroups(); + Map arenaGroupMap = new HashMap<>(); + for (DropperArenaGroup arenaGroup : arenaGroups) { + for (UUID arenaId : arenaGroup.getArenas()) { + arenaGroupMap.put(arenaId, arenaGroup); + } + } + this.arenaGroups = arenaGroupMap; + } + /** * Saves all current arenas to disk */ @@ -115,6 +184,13 @@ public class DropperArenaHandler { */ public void loadArenas() { this.arenas = ArenaStorageHelper.loadArenas(); + + // Save a map from arena name to arena id for improved performance + this.arenaNameLookup = new HashMap<>(); + for (Map.Entry arena : this.arenas.entrySet()) { + String sanitizedName = arena.getValue().getArenaNameSanitized(); + this.arenaNameLookup.put(sanitizedName, arena.getKey()); + } } } diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java index 735da6b..2f32395 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java @@ -63,15 +63,10 @@ public class DropperArenaSession { //TODO: Give reward? - // Register and announce any cleared stages - Integer arenaStage = this.arena.getStage(); - if (arenaStage != null) { - boolean clearedNewStage = Dropper.getInstance().getArenaHandler().registerStageCleared(this.player, arenaStage); - if (clearedNewStage) { - this.player.sendMessage("You cleared stage " + arenaStage + "!"); - } + // Mark the arena as cleared + if (this.arena.getData().addCompleted(this.player)) { + this.player.sendMessage("You cleared the arena!"); } - this.player.sendMessage("You won!"); // Teleport the player out of the arena diff --git a/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java b/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java index f4a14ec..70f9757 100644 --- a/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java +++ b/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java @@ -25,7 +25,7 @@ public class CreateArenaCommand implements CommandExecutor { if (arguments.length < 1) { return false; } - + DropperArena existingArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]); if (existingArena != null) { commandSender.sendMessage("There already exists a dropper arena with that name!"); diff --git a/src/main/java/net/knarcraft/dropper/command/JoinArenaCommand.java b/src/main/java/net/knarcraft/dropper/command/JoinArenaCommand.java index ec6232c..306935f 100644 --- a/src/main/java/net/knarcraft/dropper/command/JoinArenaCommand.java +++ b/src/main/java/net/knarcraft/dropper/command/JoinArenaCommand.java @@ -2,6 +2,7 @@ package net.knarcraft.dropper.command; import net.knarcraft.dropper.Dropper; import net.knarcraft.dropper.arena.DropperArena; +import net.knarcraft.dropper.arena.DropperArenaGroup; import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry; import net.knarcraft.dropper.arena.DropperArenaSession; import net.knarcraft.dropper.property.ArenaGameMode; @@ -69,7 +70,17 @@ public class JoinArenaCommand implements CommandExecutor { gameMode = ArenaGameMode.DEFAULT; } - //TODO: Check if the arena has been beaten if the non-default game-mode has been chosen + // Make sure the player has beaten the necessary levels + DropperArenaGroup arenaGroup = Dropper.getInstance().getArenaHandler().getGroup(specifiedArena.getArenaId()); + if (arenaGroup != null && !doGroupChecks(specifiedArena, arenaGroup, gameMode, player)) { + return false; + } + + // Make sure the player has beaten the arena once before playing a challenge mode + if (gameMode != ArenaGameMode.DEFAULT && specifiedArena.getData().hasNotCompleted(player)) { + player.sendMessage("You must complete this arena in normal mode before starting a challenge!"); + return false; + } // Register the player's session DropperArenaSession newSession = new DropperArenaSession(specifiedArena, player, gameMode); @@ -90,4 +101,30 @@ public class JoinArenaCommand implements CommandExecutor { } } + /** + * Performs necessary check for the given arena's group + * + * @param dropperArena

The arena the player is trying to join

+ * @param arenaGroup

The arena group the arena belongs to

+ * @param arenaGameMode

The game-mode the player selected

+ * @param player

The the player trying to join the arena

+ * @return

False if any checks failed

+ */ + private boolean doGroupChecks(@NotNull DropperArena dropperArena, @NotNull DropperArenaGroup arenaGroup, + @NotNull ArenaGameMode arenaGameMode, @NotNull Player player) { + if (arenaGameMode == ArenaGameMode.DEFAULT) { + if (!arenaGroup.canPlay(player, dropperArena.getArenaId())) { + player.sendMessage("You have not yet beaten the previous arena!"); + return false; + } + } else { + if (arenaGroup.hasBeatenAll(player)) { + player.sendMessage("You have not yet beaten all arenas in this group!"); + return false; + } + } + + return true; + } + } diff --git a/src/main/java/net/knarcraft/dropper/property/ArenaStorageKey.java b/src/main/java/net/knarcraft/dropper/property/ArenaStorageKey.java index 1c5d522..a97de1e 100644 --- a/src/main/java/net/knarcraft/dropper/property/ArenaStorageKey.java +++ b/src/main/java/net/knarcraft/dropper/property/ArenaStorageKey.java @@ -37,11 +37,6 @@ public enum ArenaStorageKey { */ PLAYER_HORIZONTAL_VELOCITY("arenaPlayerHorizontalVelocity"), - /** - * The key for this arena's stage - */ - STAGE("arenaStage"), - /** * The key for the type of this arena's win block */ diff --git a/src/main/java/net/knarcraft/dropper/util/ArenaStorageHelper.java b/src/main/java/net/knarcraft/dropper/util/ArenaStorageHelper.java index bf4cce8..a57bda4 100644 --- a/src/main/java/net/knarcraft/dropper/util/ArenaStorageHelper.java +++ b/src/main/java/net/knarcraft/dropper/util/ArenaStorageHelper.java @@ -3,6 +3,7 @@ package net.knarcraft.dropper.util; import net.knarcraft.dropper.Dropper; import net.knarcraft.dropper.arena.DropperArena; import net.knarcraft.dropper.arena.DropperArenaData; +import net.knarcraft.dropper.arena.DropperArenaGroup; import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry; import net.knarcraft.dropper.container.SerializableMaterial; import net.knarcraft.dropper.container.SerializableUUID; @@ -19,6 +20,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -28,7 +30,9 @@ import java.util.logging.Level; public final class ArenaStorageHelper { private final static String arenasConfigurationSection = "arenas"; + private final static String groupsConfigurationSection = "groups"; private static final File arenaFile = new File(Dropper.getInstance().getDataFolder(), "arenas.yml"); + private static final File groupFile = new File(Dropper.getInstance().getDataFolder(), "groups.yml"); private static final File arenaDataFolder = new File(Dropper.getInstance().getDataFolder(), "arena_data"); private ArenaStorageHelper() { @@ -36,7 +40,46 @@ public final class ArenaStorageHelper { } /** - * Saves the given arenas to the given file + * Saves the given dropper arena groups + * + * @param arenaGroups

The arena groups to save

+ * @throws IOException

If unable to write to the file

+ */ + public static void saveDropperArenaGroups(@NotNull Set arenaGroups) throws IOException { + YamlConfiguration configuration = new YamlConfiguration(); + ConfigurationSection groupSection = configuration.createSection(groupsConfigurationSection); + + for (DropperArenaGroup arenaGroup : arenaGroups) { + groupSection.set(arenaGroup.getGroupId().toString(), arenaGroup); + } + + configuration.save(groupFile); + } + + /** + * Loads all existing dropper arena groups + * + * @return

The loaded arena groups

+ */ + public static @NotNull Set loadDropperArenaGroups() { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(groupFile); + ConfigurationSection groupSection = configuration.getConfigurationSection(groupsConfigurationSection); + //If no such section exists, it must be the case that there is no data to load + if (groupSection == null) { + return new HashSet<>(); + } + + Set arenaGroups = new HashSet<>(); + + for (String sectionName : groupSection.getKeys(false)) { + arenaGroups.add((DropperArenaGroup) groupSection.get(sectionName)); + } + + return arenaGroups; + } + + /** + * Saves the given arenas * * @param arenas

The arenas to save

* @throws IOException

If unable to write to the file

@@ -47,15 +90,13 @@ public final class ArenaStorageHelper { for (DropperArena arena : arenas.values()) { //Note: While the arena name is used as the key, as the key has to be sanitized, the un-sanitized arena name // must be stored as well - @NotNull ConfigurationSection configSection = arenaSection.createSection(sanitizeArenaName( - arena.getArenaName())); + @NotNull ConfigurationSection configSection = arenaSection.createSection(arena.getArenaId().toString()); configSection.set(ArenaStorageKey.ID.getKey(), new SerializableUUID(arena.getArenaId())); configSection.set(ArenaStorageKey.NAME.getKey(), arena.getArenaName()); configSection.set(ArenaStorageKey.SPAWN_LOCATION.getKey(), arena.getSpawnLocation()); configSection.set(ArenaStorageKey.EXIT_LOCATION.getKey(), arena.getExitLocation()); configSection.set(ArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey(), arena.getPlayerVerticalVelocity()); configSection.set(ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey(), arena.getPlayerHorizontalVelocity()); - configSection.set(ArenaStorageKey.STAGE.getKey(), arena.getStage()); configSection.set(ArenaStorageKey.WIN_BLOCK_TYPE.getKey(), new SerializableMaterial(arena.getWinBlockType())); saveArenaData(arena.getData()); } @@ -63,7 +104,7 @@ public final class ArenaStorageHelper { } /** - * Loads all arenas from the given file + * Loads all arenas * * @return

The loaded arenas, or null if the arenas configuration section is missing.

*/ @@ -108,7 +149,6 @@ public final class ArenaStorageHelper { double verticalVelocity = configurationSection.getDouble(ArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey()); float horizontalVelocity = sanitizeHorizontalVelocity((float) configurationSection.getDouble( ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey())); - Integer stage = (Integer) configurationSection.get(ArenaStorageKey.STAGE.getKey()); SerializableMaterial winBlockType = (SerializableMaterial) configurationSection.get( ArenaStorageKey.WIN_BLOCK_TYPE.getKey()); @@ -128,7 +168,7 @@ public final class ArenaStorageHelper { } return new DropperArena(arenaId, arenaName, spawnLocation, exitLocation, verticalVelocity, horizontalVelocity, - stage, winBlockType.material(), arenaData); + winBlockType.material(), arenaData); } /** diff --git a/src/test/java/net/knarcraft/dropper/arena/DropperArenaGroupTest.java b/src/test/java/net/knarcraft/dropper/arena/DropperArenaGroupTest.java new file mode 100644 index 0000000..39504d3 --- /dev/null +++ b/src/test/java/net/knarcraft/dropper/arena/DropperArenaGroupTest.java @@ -0,0 +1,50 @@ +package net.knarcraft.dropper.arena; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Tests for arena dropper groups + */ +public class DropperArenaGroupTest { + + @Test + public void swapTest() { + /* + This test makes sure the order of arenas is as expected when the arenas are added to a group. It also makes + sure that swapping two items works as expected. + */ + + DropperArenaGroup arenaGroup = new DropperArenaGroup("test"); + UUID arena1Id = UUID.randomUUID(); + UUID arena2Id = UUID.randomUUID(); + UUID arena3Id = UUID.randomUUID(); + UUID arena4Id = UUID.randomUUID(); + + arenaGroup.addArena(arena1Id); + arenaGroup.addArena(arena2Id); + arenaGroup.addArena(arena3Id); + arenaGroup.addArena(arena4Id); + + List initialOrder = new ArrayList<>(); + initialOrder.add(arena1Id); + initialOrder.add(arena2Id); + initialOrder.add(arena3Id); + initialOrder.add(arena4Id); + Assertions.assertEquals(initialOrder, arenaGroup.getArenas()); + + arenaGroup.swapArenas(1, 3); + + List swapped = new ArrayList<>(); + swapped.add(arena1Id); + swapped.add(arena4Id); + swapped.add(arena3Id); + swapped.add(arena2Id); + Assertions.assertEquals(swapped, arenaGroup.getArenas()); + } + +} From 45bdb3f2a6980fc6874438aaf6f8a8f0e705d4af Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Wed, 29 Mar 2023 02:07:00 +0200 Subject: [PATCH 02/11] Removes usage of SerializableUUID outside of serialization --- .../dropper/arena/DropperArenaData.java | 21 ++++-- .../arena/DropperArenaRecordsRegistry.java | 65 ++++++++++++------- .../dropper/command/CreateArenaCommand.java | 2 +- 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java index 38d5158..6c70f83 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaData.java @@ -20,7 +20,7 @@ import java.util.UUID; * @param playersCompleted

A list of all player that have completed this arena

*/ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecordsRegistry recordsRegistry, - @NotNull Set playersCompleted) implements ConfigurationSerializable { + @NotNull Set playersCompleted) implements ConfigurationSerializable { /** * Instantiates a new dropper arena data object @@ -30,7 +30,7 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor * @param playersCompleted

The set of ids for players that have cleared this data's arena

*/ public DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecordsRegistry recordsRegistry, - @NotNull Set playersCompleted) { + @NotNull Set playersCompleted) { this.arenaId = arenaId; this.recordsRegistry = recordsRegistry; this.playersCompleted = new HashSet<>(playersCompleted); @@ -43,7 +43,7 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor * @return

True if the player has cleared the arena this data belongs to

*/ public boolean hasNotCompleted(@NotNull Player player) { - return !this.playersCompleted.contains(new SerializableUUID(player.getUniqueId())); + return !this.playersCompleted.contains(player.getUniqueId()); } /** @@ -52,7 +52,7 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor * @param player

The player that completed this data's arena

*/ public boolean addCompleted(@NotNull Player player) { - boolean added = this.playersCompleted.add(new SerializableUUID(player.getUniqueId())); + boolean added = this.playersCompleted.add(player.getUniqueId()); // Persistently save the completion if (added) { Dropper.getInstance().getArenaHandler().saveData(this.arenaId); @@ -66,7 +66,12 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor Map data = new HashMap<>(); data.put("arenaId", new SerializableUUID(this.arenaId)); data.put("recordsRegistry", this.recordsRegistry); - data.put("playersCompleted", this.playersCompleted); + + Set playersCompleted = new HashSet<>(); + for (UUID playerCompleted : this.playersCompleted) { + playersCompleted.add(new SerializableUUID(playerCompleted)); + } + data.put("playersCompleted", playersCompleted); return data; } @@ -80,7 +85,11 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor public static @NotNull DropperArenaData deserialize(@NotNull Map data) { SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId"); DropperArenaRecordsRegistry recordsRegistry = (DropperArenaRecordsRegistry) data.get("recordsRegistry"); - Set playersCompleted = (Set) data.get("playersCompleted"); + Set playersCompletedData = (Set) data.get("playersCompleted"); + Set playersCompleted = new HashSet<>(); + for (SerializableUUID completedId : playersCompletedData) { + playersCompleted.add(completedId.uuid()); + } return new DropperArenaData(serializableUUID.uuid(), recordsRegistry, playersCompleted); } diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java index a60e018..529a4ee 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java @@ -17,8 +17,8 @@ import java.util.stream.Stream; public class DropperArenaRecordsRegistry implements ConfigurationSerializable { private final UUID arenaId; - private final Map leastDeaths; - private final Map shortestTimeMilliSeconds; + private final Map leastDeaths; + private final Map shortestTimeMilliSeconds; /** * Instantiates a new empty records registry @@ -35,8 +35,8 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * @param leastDeaths

The existing least death records to use

* @param shortestTimeMilliSeconds

The existing leash time records to use

*/ - public DropperArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Map leastDeaths, - @NotNull Map shortestTimeMilliSeconds) { + private DropperArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Map leastDeaths, + @NotNull Map shortestTimeMilliSeconds) { this.arenaId = arenaId; this.leastDeaths = new HashMap<>(leastDeaths); this.shortestTimeMilliSeconds = new HashMap<>(shortestTimeMilliSeconds); @@ -47,7 +47,7 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * * @return

Existing death records

*/ - public Map getLeastDeathsRecords() { + public Map getLeastDeathsRecords() { return new HashMap<>(this.leastDeaths); } @@ -56,7 +56,7 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * * @return

Existing time records

*/ - public Map getShortestTimeMilliSecondsRecords() { + public Map getShortestTimeMilliSecondsRecords() { return new HashMap<>(this.shortestTimeMilliSeconds); } @@ -69,22 +69,21 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { */ public @NotNull RecordResult registerDeathRecord(@NotNull UUID playerId, int deaths) { RecordResult result; - Stream> records = leastDeaths.entrySet().stream(); - SerializableUUID serializableUUID = new SerializableUUID(playerId); + Stream> records = leastDeaths.entrySet().stream(); if (records.allMatch((entry) -> deaths < entry.getValue())) { // If the given value is less than all other values, that's a world record! result = RecordResult.WORLD_RECORD; - leastDeaths.put(serializableUUID, deaths); + leastDeaths.put(playerId, deaths); save(); - } else if (leastDeaths.containsKey(serializableUUID) && deaths < leastDeaths.get(serializableUUID)) { + } else if (leastDeaths.containsKey(playerId) && deaths < leastDeaths.get(playerId)) { // If the given value is less than the player's previous value, that's a personal best! result = RecordResult.PERSONAL_BEST; - leastDeaths.put(serializableUUID, deaths); + leastDeaths.put(playerId, deaths); save(); } else { // Make sure to save the record if this is the user's first attempt - if (!leastDeaths.containsKey(serializableUUID)) { + if (!leastDeaths.containsKey(playerId)) { save(); } result = RecordResult.NONE; @@ -102,23 +101,22 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { */ public @NotNull RecordResult registerTimeRecord(@NotNull UUID playerId, long milliseconds) { RecordResult result; - Stream> records = shortestTimeMilliSeconds.entrySet().stream(); - SerializableUUID serializableUUID = new SerializableUUID(playerId); + Stream> records = shortestTimeMilliSeconds.entrySet().stream(); if (records.allMatch((entry) -> milliseconds < entry.getValue())) { //If the given value is less than all other values, that's a world record! result = RecordResult.WORLD_RECORD; - shortestTimeMilliSeconds.put(serializableUUID, milliseconds); + shortestTimeMilliSeconds.put(playerId, milliseconds); save(); - } else if (shortestTimeMilliSeconds.containsKey(serializableUUID) && - milliseconds < shortestTimeMilliSeconds.get(serializableUUID)) { + } else if (shortestTimeMilliSeconds.containsKey(playerId) && + milliseconds < shortestTimeMilliSeconds.get(playerId)) { //If the given value is less than the player's previous value, that's a personal best! result = RecordResult.PERSONAL_BEST; - shortestTimeMilliSeconds.put(serializableUUID, milliseconds); + shortestTimeMilliSeconds.put(playerId, milliseconds); save(); } else { // Make sure to save the record if this is the user's first attempt - if (!shortestTimeMilliSeconds.containsKey(serializableUUID)) { + if (!shortestTimeMilliSeconds.containsKey(playerId)) { save(); } result = RecordResult.NONE; @@ -139,8 +137,18 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { public Map serialize() { Map data = new HashMap<>(); data.put("arenaId", new SerializableUUID(this.arenaId)); - data.put("leastDeaths", this.leastDeaths); - data.put("shortestTime", this.shortestTimeMilliSeconds); + + Map leastDeaths = new HashMap<>(); + for (Map.Entry entry : this.leastDeaths.entrySet()) { + leastDeaths.put(new SerializableUUID(entry.getKey()), entry.getValue()); + } + data.put("leastDeaths", leastDeaths); + + Map shortestTimeMilliSeconds = new HashMap<>(); + for (Map.Entry entry : this.shortestTimeMilliSeconds.entrySet()) { + shortestTimeMilliSeconds.put(new SerializableUUID(entry.getKey()), entry.getValue()); + } + data.put("shortestTime", shortestTimeMilliSeconds); return data; } @@ -155,10 +163,19 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { UUID arenaId = ((SerializableUUID) data.get("arenaId")).uuid(); Map leastDeathsData = (Map) data.getOrDefault("leastDeaths", new HashMap<>()); - Map shortestTimeMillisecondsData = - (Map) data.getOrDefault("shortestTime", new HashMap<>()); + Map leastDeaths = new HashMap<>(); + for (Map.Entry entry : leastDeathsData.entrySet()) { + leastDeaths.put(entry.getKey().uuid(), entry.getValue()); + } - return new DropperArenaRecordsRegistry(arenaId, leastDeathsData, shortestTimeMillisecondsData); + Map shortestTimeMillisecondsData = + (Map) data.getOrDefault("shortestTime", new HashMap<>()); + Map shortestTimeMilliseconds = new HashMap<>(); + for (Map.Entry entry : shortestTimeMillisecondsData.entrySet()) { + shortestTimeMilliseconds.put(entry.getKey().uuid(), entry.getValue().longValue()); + } + + return new DropperArenaRecordsRegistry(arenaId, leastDeaths, shortestTimeMilliseconds); } } diff --git a/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java b/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java index 70f9757..f4a14ec 100644 --- a/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java +++ b/src/main/java/net/knarcraft/dropper/command/CreateArenaCommand.java @@ -25,7 +25,7 @@ public class CreateArenaCommand implements CommandExecutor { if (arguments.length < 1) { return false; } - + DropperArena existingArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]); if (existingArena != null) { commandSender.sendMessage("There already exists a dropper arena with that name!"); From 34989e6673828d6954612d3ba064563ab0e1f1d4 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Wed, 29 Mar 2023 02:19:40 +0200 Subject: [PATCH 03/11] Removes some TODOs --- src/main/java/net/knarcraft/dropper/Dropper.java | 8 -------- .../net/knarcraft/dropper/arena/DropperArenaSession.java | 2 -- .../net/knarcraft/dropper/command/ListArenaCommand.java | 2 -- 3 files changed, 12 deletions(-) diff --git a/src/main/java/net/knarcraft/dropper/Dropper.java b/src/main/java/net/knarcraft/dropper/Dropper.java index df2408d..6e10a28 100644 --- a/src/main/java/net/knarcraft/dropper/Dropper.java +++ b/src/main/java/net/knarcraft/dropper/Dropper.java @@ -100,14 +100,6 @@ public final class Dropper extends JavaPlugin { this.arenaHandler.loadArenas(); this.arenaHandler.loadGroups(); - //TODO: Store various information about players' performance, and hook into PlaceholderAPI - - //TODO: Possibly implement an optional queue mode, which only allows one player inside one dropper arena at any - // time (to prevent players from pushing each-other)? - - //TODO: Store which players have cleared which arenas to keep track of whether the trial game-modes should be - // available - PluginManager pluginManager = getServer().getPluginManager(); pluginManager.registerEvents(new DamageListener(), this); pluginManager.registerEvents(new MoveListener(), this); diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java index 2f32395..21fe101 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaSession.java @@ -61,8 +61,6 @@ public class DropperArenaSession { // Check for, and display, records registerRecord(); - //TODO: Give reward? - // Mark the arena as cleared if (this.arena.getData().addCompleted(this.player)) { this.player.sendMessage("You cleared the arena!"); diff --git a/src/main/java/net/knarcraft/dropper/command/ListArenaCommand.java b/src/main/java/net/knarcraft/dropper/command/ListArenaCommand.java index 88416d6..48c8a38 100644 --- a/src/main/java/net/knarcraft/dropper/command/ListArenaCommand.java +++ b/src/main/java/net/knarcraft/dropper/command/ListArenaCommand.java @@ -22,8 +22,6 @@ public class ListArenaCommand implements TabExecutor { for (String arenaName : TabCompleteHelper.getArenas()) { sender.sendMessage(arenaName); } - - //TODO: Allow displaying information about each arena (possibly admin-only) return true; } From ca5b0fcbca70782d0b0e53e0918c07bc7104853f Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Wed, 29 Mar 2023 11:37:26 +0200 Subject: [PATCH 04/11] Makes it possible to get the current value from /dedit Finally gets rid of the redundancy when checking if a record has been beaten Adds tab-completions for arenas and properties to the edit command Adds an ArenaEditableProperty enum to keep track of editable arena properties --- .../arena/DropperArenaRecordsRegistry.java | 106 +++++++++--------- .../dropper/command/EditArenaCommand.java | 20 +++- .../command/EditArenaTabCompleter.java | 15 ++- .../property/ArenaEditableProperty.java | 92 +++++++++++++++ .../dropper/util/TabCompleteHelper.java | 14 +++ 5 files changed, 188 insertions(+), 59 deletions(-) create mode 100644 src/main/java/net/knarcraft/dropper/property/ArenaEditableProperty.java diff --git a/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java b/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java index 529a4ee..f57d48f 100644 --- a/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java +++ b/src/main/java/net/knarcraft/dropper/arena/DropperArenaRecordsRegistry.java @@ -17,8 +17,8 @@ import java.util.stream.Stream; public class DropperArenaRecordsRegistry implements ConfigurationSerializable { private final UUID arenaId; - private final Map leastDeaths; - private final Map shortestTimeMilliSeconds; + private final @NotNull Map leastDeaths; + private final @NotNull Map shortestTimeMilliSeconds; /** * Instantiates a new empty records registry @@ -48,7 +48,11 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * @return

Existing death records

*/ public Map getLeastDeathsRecords() { - return new HashMap<>(this.leastDeaths); + Map leastDeathRecords = new HashMap<>(); + for (Map.Entry entry : this.leastDeaths.entrySet()) { + leastDeathRecords.put(entry.getKey(), entry.getValue().intValue()); + } + return leastDeathRecords; } /** @@ -57,7 +61,11 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * @return

Existing time records

*/ public Map getShortestTimeMilliSecondsRecords() { - return new HashMap<>(this.shortestTimeMilliSeconds); + Map leastTimeRecords = new HashMap<>(); + for (Map.Entry entry : this.shortestTimeMilliSeconds.entrySet()) { + leastTimeRecords.put(entry.getKey(), entry.getValue().longValue()); + } + return leastTimeRecords; } /** @@ -68,28 +76,7 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * @return

The result explaining what type of record was achieved

*/ public @NotNull RecordResult registerDeathRecord(@NotNull UUID playerId, int deaths) { - RecordResult result; - Stream> records = leastDeaths.entrySet().stream(); - - if (records.allMatch((entry) -> deaths < entry.getValue())) { - // If the given value is less than all other values, that's a world record! - result = RecordResult.WORLD_RECORD; - leastDeaths.put(playerId, deaths); - save(); - } else if (leastDeaths.containsKey(playerId) && deaths < leastDeaths.get(playerId)) { - // If the given value is less than the player's previous value, that's a personal best! - result = RecordResult.PERSONAL_BEST; - leastDeaths.put(playerId, deaths); - save(); - } else { - // Make sure to save the record if this is the user's first attempt - if (!leastDeaths.containsKey(playerId)) { - save(); - } - result = RecordResult.NONE; - } - - return result; + return registerRecord(leastDeaths, playerId, deaths); } /** @@ -100,29 +87,7 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { * @return

The result explaining what type of record was achieved

*/ public @NotNull RecordResult registerTimeRecord(@NotNull UUID playerId, long milliseconds) { - RecordResult result; - Stream> records = shortestTimeMilliSeconds.entrySet().stream(); - - if (records.allMatch((entry) -> milliseconds < entry.getValue())) { - //If the given value is less than all other values, that's a world record! - result = RecordResult.WORLD_RECORD; - shortestTimeMilliSeconds.put(playerId, milliseconds); - save(); - } else if (shortestTimeMilliSeconds.containsKey(playerId) && - milliseconds < shortestTimeMilliSeconds.get(playerId)) { - //If the given value is less than the player's previous value, that's a personal best! - result = RecordResult.PERSONAL_BEST; - shortestTimeMilliSeconds.put(playerId, milliseconds); - save(); - } else { - // Make sure to save the record if this is the user's first attempt - if (!shortestTimeMilliSeconds.containsKey(playerId)) { - save(); - } - result = RecordResult.NONE; - } - - return result; + return registerRecord(shortestTimeMilliSeconds, playerId, milliseconds); } /** @@ -132,20 +97,55 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable { Dropper.getInstance().getArenaHandler().saveData(this.arenaId); } + /** + * Registers a new record if applicable + * + * @param existingRecords

The map of existing records to use

+ * @param playerId

The id of the player that potentially achieved a record

+ * @param amount

The amount of whatever the player achieved

+ * @return

The result of the player's record attempt

+ */ + private @NotNull RecordResult registerRecord(@NotNull Map existingRecords, @NotNull UUID playerId, + Number amount) { + RecordResult result; + Stream> records = existingRecords.entrySet().stream(); + long amountLong = amount.longValue(); + + if (records.allMatch((entry) -> amountLong < entry.getValue().longValue())) { + // If the given value is less than all other values, that's a world record! + result = RecordResult.WORLD_RECORD; + existingRecords.put(playerId, amount); + save(); + } else if (existingRecords.containsKey(playerId) && amountLong < existingRecords.get(playerId).longValue()) { + // If the given value is less than the player's previous value, that's a personal best! + result = RecordResult.PERSONAL_BEST; + existingRecords.put(playerId, amount); + save(); + } else { + // Make sure to save the record if this is the user's first attempt + if (!existingRecords.containsKey(playerId)) { + save(); + } + result = RecordResult.NONE; + } + + return result; + } + @NotNull @Override public Map serialize() { Map data = new HashMap<>(); data.put("arenaId", new SerializableUUID(this.arenaId)); - Map leastDeaths = new HashMap<>(); - for (Map.Entry entry : this.leastDeaths.entrySet()) { + Map leastDeaths = new HashMap<>(); + for (Map.Entry entry : this.leastDeaths.entrySet()) { leastDeaths.put(new SerializableUUID(entry.getKey()), entry.getValue()); } data.put("leastDeaths", leastDeaths); - Map shortestTimeMilliSeconds = new HashMap<>(); - for (Map.Entry entry : this.shortestTimeMilliSeconds.entrySet()) { + Map shortestTimeMilliSeconds = new HashMap<>(); + for (Map.Entry entry : this.shortestTimeMilliSeconds.entrySet()) { shortestTimeMilliSeconds.put(new SerializableUUID(entry.getKey()), entry.getValue()); } data.put("shortestTime", shortestTimeMilliSeconds); diff --git a/src/main/java/net/knarcraft/dropper/command/EditArenaCommand.java b/src/main/java/net/knarcraft/dropper/command/EditArenaCommand.java index 7d5d397..a97e360 100644 --- a/src/main/java/net/knarcraft/dropper/command/EditArenaCommand.java +++ b/src/main/java/net/knarcraft/dropper/command/EditArenaCommand.java @@ -2,6 +2,7 @@ package net.knarcraft.dropper.command; import net.knarcraft.dropper.Dropper; import net.knarcraft.dropper.arena.DropperArena; +import net.knarcraft.dropper.property.ArenaEditableProperty; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -31,9 +32,22 @@ public class EditArenaCommand implements CommandExecutor { return false; } - //TODO: If an arena name and a property is given, display the current value - //TODO: If an arena name, a property and a value is given, check if it's valid, and update the property - return false; + ArenaEditableProperty editableProperty = ArenaEditableProperty.getFromArgumentString(arguments[1]); + if (editableProperty == null) { + commandSender.sendMessage("Unknown property specified."); + return false; + } + + String currentValueFormat = "Current value of %s is: %s"; + + if (arguments.length < 3) { + // Print the current value of the property + String value = editableProperty.getCurrentValueAsString(specifiedArena); + commandSender.sendMessage(String.format(currentValueFormat, editableProperty.getArgumentString(), value)); + } else { + // TODO: Expect a new value for the option, which needs to be validated, and possibly set + } + return true; } } diff --git a/src/main/java/net/knarcraft/dropper/command/EditArenaTabCompleter.java b/src/main/java/net/knarcraft/dropper/command/EditArenaTabCompleter.java index 7205e4b..43213af 100644 --- a/src/main/java/net/knarcraft/dropper/command/EditArenaTabCompleter.java +++ b/src/main/java/net/knarcraft/dropper/command/EditArenaTabCompleter.java @@ -1,11 +1,13 @@ package net.knarcraft.dropper.command; +import net.knarcraft.dropper.util.TabCompleteHelper; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; /** @@ -16,9 +18,16 @@ public class EditArenaTabCompleter implements TabCompleter { @Override public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - //TODO: Tab-complete existing arena names - //TODO: If an arena name is given, tab-complete change-able properties - return null; + if (args.length == 1) { + return TabCompleteHelper.getArenas(); + } else if (args.length == 2) { + return TabCompleteHelper.getArenaProperties(); + } else if (args.length == 3) { + //TODO: Tab-complete possible values for the given property + return null; + } else { + return new ArrayList<>(); + } } } diff --git a/src/main/java/net/knarcraft/dropper/property/ArenaEditableProperty.java b/src/main/java/net/knarcraft/dropper/property/ArenaEditableProperty.java new file mode 100644 index 0000000..7803021 --- /dev/null +++ b/src/main/java/net/knarcraft/dropper/property/ArenaEditableProperty.java @@ -0,0 +1,92 @@ +package net.knarcraft.dropper.property; + +import net.knarcraft.dropper.arena.DropperArena; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; + +/** + * All editable properties of a dropper arena + */ +public enum ArenaEditableProperty { + + /** + * The name of the arena + */ + NAME("name", DropperArena::getArenaName), + + /** + * The arena's spawn location + */ + SPAWN_LOCATION("spawnLocation", (arena) -> String.valueOf(arena.getSpawnLocation())), + + /** + * The arena's exit location + */ + EXIT_LOCATION("exitLocation", (arena) -> String.valueOf(arena.getExitLocation())), + + /** + * The arena's vertical velocity + */ + VERTICAL_VELOCITY("verticalVelocity", (arena) -> String.valueOf(arena.getPlayerVerticalVelocity())), + + /** + * The arena's horizontal velocity + */ + HORIZONTAL_VELOCITY("horizontalVelocity", (arena) -> String.valueOf(arena.getPlayerHorizontalVelocity())), + + /** + * The arena's win block type + */ + WIN_BLOCK_TYPE("winBlockType", (arena) -> arena.getWinBlockType().toString()), + ; + + private final @NotNull String argumentString; + private final Function currentValueProvider; + + /** + * Instantiates a new arena editable property + * + * @param argumentString

The argument string used to specify this property

+ */ + ArenaEditableProperty(@NotNull String argumentString, Function currentValueProvider) { + this.argumentString = argumentString; + this.currentValueProvider = currentValueProvider; + } + + /** + * Gets the string representation of this property's current value + * + * @param arena

The arena to check the value for

+ * @return

The current value as a string

+ */ + public String getCurrentValueAsString(DropperArena arena) { + return this.currentValueProvider.apply(arena); + } + + /** + * Gets the argument string used to specify this property + * + * @return

The argument string

+ */ + public @NotNull String getArgumentString() { + return this.argumentString; + } + + /** + * Gets the editable property corresponding to the given argument string + * + * @param argumentString

The argument string used to specify an editable property

+ * @return

The corresponding editable property, or null if not found

+ */ + public static @Nullable ArenaEditableProperty getFromArgumentString(String argumentString) { + for (ArenaEditableProperty property : ArenaEditableProperty.values()) { + if (property.argumentString.equalsIgnoreCase(argumentString)) { + return property; + } + } + return null; + } + +} diff --git a/src/main/java/net/knarcraft/dropper/util/TabCompleteHelper.java b/src/main/java/net/knarcraft/dropper/util/TabCompleteHelper.java index 51733b1..ead4679 100644 --- a/src/main/java/net/knarcraft/dropper/util/TabCompleteHelper.java +++ b/src/main/java/net/knarcraft/dropper/util/TabCompleteHelper.java @@ -2,6 +2,7 @@ package net.knarcraft.dropper.util; import net.knarcraft.dropper.Dropper; import net.knarcraft.dropper.arena.DropperArena; +import net.knarcraft.dropper.property.ArenaEditableProperty; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -29,4 +30,17 @@ public final class TabCompleteHelper { return arenaNames; } + /** + * Gets the argument strings of all arena properties + * + * @return

All arena properties

+ */ + public static @NotNull List getArenaProperties() { + List arenaProperties = new ArrayList<>(); + for (ArenaEditableProperty property : ArenaEditableProperty.values()) { + arenaProperties.add(property.getArgumentString()); + } + return arenaProperties; + } + } From c01e1c3509158d54c5c06f6f978aac76c6897023 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Wed, 29 Mar 2023 18:15:31 +0200 Subject: [PATCH 05/11] Finishes the edit command #9 --- README.md | 23 ++- .../knarcraft/dropper/arena/DropperArena.java | 134 +++++++++++++++++- .../dropper/arena/DropperArenaHandler.java | 13 ++ .../dropper/arena/PlayerEntryState.java | 2 +- .../dropper/command/EditArenaCommand.java | 113 ++++++++++++++- src/main/resources/plugin.yml | 4 +- 6 files changed, 271 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8d8b72e..2cc42dd 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ This command is used for joining a dropper arena. | Argument | Usage | |----------|------------------------------------------------------------------------------------------------------------------| -| arena | The name of the arena to join | +| arena | The name of the arena to join. | | mode | Additional challenge modes can be played after an arena has been cleared once. Available modes: deaths and time. | ### /dropperedit @@ -47,8 +47,19 @@ This command allows editing the specified property for the specified dropper are `/dropperedit