mirror of
https://github.com/SunNetservers/MiniGames.git
synced 2025-07-23 16:30:30 +02:00
Parkour implementation safety save 2
This is just a safety save in case the code gets too broken to fix.
This commit is contained in:
33
src/main/java/net/knarcraft/minigames/arena/Arena.java
Normal file
33
src/main/java/net/knarcraft/minigames/arena/Arena.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package net.knarcraft.minigames.arena;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An interface describing all arenas
|
||||
*/
|
||||
public interface Arena {
|
||||
|
||||
/**
|
||||
* Gets the id of this arena
|
||||
*
|
||||
* @return <p>This arena's identifier</p>
|
||||
*/
|
||||
@NotNull UUID getArenaId();
|
||||
|
||||
/**
|
||||
* Gets the name of this arena
|
||||
*
|
||||
* @return <p>The name of this arena</p>
|
||||
*/
|
||||
@NotNull String getArenaName();
|
||||
|
||||
/**
|
||||
* Gets this arena's sanitized name
|
||||
*
|
||||
* @return <p>This arena's sanitized name</p>
|
||||
*/
|
||||
@NotNull String getArenaNameSanitized();
|
||||
|
||||
}
|
108
src/main/java/net/knarcraft/minigames/arena/ArenaData.java
Normal file
108
src/main/java/net/knarcraft/minigames/arena/ArenaData.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package net.knarcraft.minigames.arena;
|
||||
|
||||
import net.knarcraft.minigames.container.SerializableContainer;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.knarcraft.minigames.util.SerializableConverter.makeSerializable;
|
||||
|
||||
/**
|
||||
* An interface describing generic arena data
|
||||
*/
|
||||
public abstract class ArenaData implements ConfigurationSerializable {
|
||||
|
||||
protected final @NotNull UUID arenaId;
|
||||
private final @NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries;
|
||||
private final @NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted;
|
||||
|
||||
/**
|
||||
* Instantiates arena data
|
||||
*
|
||||
* @param arenaId <p>The id of the arena this data belongs to</p>
|
||||
* @param recordRegistries <p>The registry storing records for this arena</p>
|
||||
* @param playersCompleted <p>The players that have completed this arena</p>
|
||||
*/
|
||||
public ArenaData(@NotNull UUID arenaId, @NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries,
|
||||
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
|
||||
this.arenaId = arenaId;
|
||||
this.recordRegistries = recordRegistries;
|
||||
this.playersCompleted = playersCompleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of this arena
|
||||
*
|
||||
* @return <p>The id of this arena</p>
|
||||
*/
|
||||
public @NotNull UUID getArenaId() {
|
||||
return this.arenaId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all record registries
|
||||
*
|
||||
* @return <p>All record registries</p>
|
||||
*/
|
||||
public @NotNull Map<ArenaGameMode, ArenaRecordsRegistry> getRecordRegistries() {
|
||||
return new HashMap<>(this.recordRegistries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the given player has cleared this arena
|
||||
*
|
||||
* @param arenaGameMode <p>The game-mode to check for</p>
|
||||
* @param player <p>The player to check</p>
|
||||
* @return <p>True if the player has cleared the arena this data belongs to</p>
|
||||
*/
|
||||
public boolean hasNotCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
|
||||
return !this.playersCompleted.getOrDefault(arenaGameMode, new HashSet<>()).contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given player as having completed this arena
|
||||
*
|
||||
* @param arenaGameMode <p>The game-mode the player completed</p>
|
||||
* @param player <p>The player that completed this data's arena</p>
|
||||
*/
|
||||
public boolean setCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
|
||||
// Make sure to add an empty set to prevent a NullPointerException
|
||||
if (!this.playersCompleted.containsKey(arenaGameMode)) {
|
||||
this.playersCompleted.put(arenaGameMode, new HashSet<>());
|
||||
}
|
||||
|
||||
boolean added = this.playersCompleted.get(arenaGameMode).add(player.getUniqueId());
|
||||
// Persistently save the completion
|
||||
if (added) {
|
||||
saveData();
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this data to disk
|
||||
*/
|
||||
public abstract void saveData();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("arenaId", new SerializableUUID(this.arenaId));
|
||||
data.put("recordsRegistry", this.recordRegistries);
|
||||
|
||||
// Convert normal UUIDs to serializable UUIDs
|
||||
Map<ArenaGameMode, Set<SerializableContainer<UUID>>> serializablePlayersCompleted = new HashMap<>();
|
||||
makeSerializable(this.playersCompleted, serializablePlayersCompleted, new SerializableUUID(null));
|
||||
data.put("playersCompleted", serializablePlayersCompleted);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
package net.knarcraft.minigames.arena;
|
||||
|
||||
public interface ArenaGameMode {
|
||||
}
|
165
src/main/java/net/knarcraft/minigames/arena/ArenaGroup.java
Normal file
165
src/main/java/net/knarcraft/minigames/arena/ArenaGroup.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package net.knarcraft.minigames.arena;
|
||||
|
||||
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import net.knarcraft.minigames.util.StringSanitizer;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
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;
|
||||
|
||||
public abstract class ArenaGroup 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
|
||||
*/
|
||||
protected final List<UUID> arenas;
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena group
|
||||
*
|
||||
* @param groupName <p>The name of this group</p>
|
||||
*/
|
||||
protected ArenaGroup(@NotNull String groupName) {
|
||||
this.groupId = UUID.randomUUID();
|
||||
this.groupName = groupName;
|
||||
this.arenas = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new arena group
|
||||
*
|
||||
* @param groupId <p>The unique id of this group</p>
|
||||
* @param groupName <p>The name of this group</p>
|
||||
* @param arenas <p>The arenas in this group</p>
|
||||
*/
|
||||
protected ArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas) {
|
||||
this.groupId = groupId;
|
||||
this.groupName = groupName;
|
||||
this.arenas = new ArrayList<>(arenas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of this arena group
|
||||
*
|
||||
* @return <p>The id of this group</p>
|
||||
*/
|
||||
public @NotNull UUID getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this arena group
|
||||
*
|
||||
* @return <p>The name of this group</p>
|
||||
*/
|
||||
public @NotNull String getGroupName() {
|
||||
return this.groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arenas contained in this group in the correct order
|
||||
*
|
||||
* @return <p>The ids of the arenas in this group</p>
|
||||
*/
|
||||
public @NotNull List<UUID> getArenas() {
|
||||
return new ArrayList<>(arenas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given arena from this group
|
||||
*
|
||||
* @param arenaId <p>The id of the arena to remove</p>
|
||||
*/
|
||||
public void removeArena(UUID arenaId) {
|
||||
this.arenas.remove(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an arena to the end of this group
|
||||
*
|
||||
* @param arenaId <p>The arena to add to this group</p>
|
||||
*/
|
||||
public void addArena(UUID arenaId) {
|
||||
addArena(arenaId, this.arenas.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an arena to the end of this group
|
||||
*
|
||||
* @param arenaId <p>The arena to add to this group</p>
|
||||
* @param index <p>The index to put the arena in</p>
|
||||
*/
|
||||
public void addArena(UUID arenaId, int index) {
|
||||
// Make sure we don't have duplicates
|
||||
if (!this.arenas.contains(arenaId)) {
|
||||
this.arenas.add(index, arenaId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this group's name, but sanitized
|
||||
*
|
||||
* @return <p>The sanitized group name</p>
|
||||
*/
|
||||
public @NotNull String getGroupNameSanitized() {
|
||||
return StringSanitizer.sanitizeArenaName(this.getGroupName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the arenas at the given indices
|
||||
*
|
||||
* @param index1 <p>The index of the first arena to swap</p>
|
||||
* @param index2 <p>The index of the second arena to swap</p>
|
||||
*/
|
||||
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<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("groupId", new SerializableUUID(this.groupId));
|
||||
data.put("groupName", this.groupName);
|
||||
|
||||
List<SerializableUUID> serializableArenas = new ArrayList<>();
|
||||
for (UUID arenaId : arenas) {
|
||||
serializableArenas.add(new SerializableUUID(arenaId));
|
||||
}
|
||||
data.put("arenas", serializableArenas);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof DropperArenaGroup otherGroup)) {
|
||||
return false;
|
||||
}
|
||||
return this.getGroupNameSanitized().equals(otherGroup.getGroupNameSanitized());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
package net.knarcraft.minigames.arena;
|
||||
|
||||
import net.knarcraft.minigames.arena.record.ArenaRecord;
|
||||
import net.knarcraft.minigames.arena.record.IntegerRecord;
|
||||
import net.knarcraft.minigames.arena.record.LongRecord;
|
||||
import net.knarcraft.minigames.arena.record.SummableArenaRecord;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import net.knarcraft.minigames.property.RecordResult;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A records registry storing records for an arena
|
||||
*/
|
||||
public abstract class ArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
|
||||
protected final UUID arenaId;
|
||||
private final @NotNull Set<IntegerRecord> leastDeaths;
|
||||
private final @NotNull Set<LongRecord> shortestTimeMilliSeconds;
|
||||
|
||||
/**
|
||||
* Instantiates a new empty records registry
|
||||
*/
|
||||
public ArenaRecordsRegistry(@NotNull UUID arenaId) {
|
||||
this.arenaId = arenaId;
|
||||
this.leastDeaths = new HashSet<>();
|
||||
this.shortestTimeMilliSeconds = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new records registry
|
||||
*
|
||||
* @param leastDeaths <p>The existing least death records to use</p>
|
||||
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
|
||||
*/
|
||||
protected ArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
|
||||
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
|
||||
this.arenaId = arenaId;
|
||||
this.leastDeaths = new HashSet<>(leastDeaths);
|
||||
this.shortestTimeMilliSeconds = new HashSet<>(shortestTimeMilliSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all existing death records
|
||||
*
|
||||
* @return <p>Existing death records</p>
|
||||
*/
|
||||
public Set<SummableArenaRecord<Integer>> getLeastDeathsRecords() {
|
||||
return new HashSet<>(this.leastDeaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all existing time records
|
||||
*
|
||||
* @return <p>Existing time records</p>
|
||||
*/
|
||||
public Set<SummableArenaRecord<Long>> getShortestTimeMilliSecondsRecords() {
|
||||
return new HashSet<>(this.shortestTimeMilliSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new deaths-record
|
||||
*
|
||||
* @param playerId <p>The id of the player that performed the records</p>
|
||||
* @param deaths <p>The number of deaths suffered before the player finished the arena</p>
|
||||
* @return <p>The result explaining what type of record was achieved</p>
|
||||
*/
|
||||
public @NotNull RecordResult registerDeathRecord(@NotNull UUID playerId, int deaths) {
|
||||
Consumer<Integer> consumer = (value) -> {
|
||||
leastDeaths.removeIf((item) -> item.getUserId().equals(playerId));
|
||||
leastDeaths.add(new IntegerRecord(playerId, value));
|
||||
};
|
||||
return registerRecord(new HashSet<>(leastDeaths), consumer, playerId, deaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new time-record
|
||||
*
|
||||
* @param playerId <p>The id of the player that performed the records</p>
|
||||
* @param milliseconds <p>The number of milliseconds it took the player to finish the dropper arena</p>
|
||||
* @return <p>The result explaining what type of record was achieved</p>
|
||||
*/
|
||||
public @NotNull RecordResult registerTimeRecord(@NotNull UUID playerId, long milliseconds) {
|
||||
Consumer<Long> consumer = (value) -> {
|
||||
shortestTimeMilliSeconds.removeIf((item) -> item.getUserId().equals(playerId));
|
||||
shortestTimeMilliSeconds.add(new LongRecord(playerId, value));
|
||||
};
|
||||
return registerRecord(new HashSet<>(shortestTimeMilliSeconds), consumer, playerId, milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves changed records
|
||||
*/
|
||||
protected abstract void save();
|
||||
|
||||
/**
|
||||
* Registers a new record if applicable
|
||||
*
|
||||
* @param existingRecords <p>The map of existing records to use</p>
|
||||
* @param recordSetter <p>The consumer used to set a new record</p>
|
||||
* @param playerId <p>The id of the player that potentially achieved a record</p>
|
||||
* @param amount <p>The amount of whatever the player achieved</p>
|
||||
* @return <p>The result of the player's record attempt</p>
|
||||
*/
|
||||
private <T extends Comparable<T>> @NotNull RecordResult registerRecord(@NotNull Set<ArenaRecord<T>> existingRecords,
|
||||
@NotNull Consumer<T> recordSetter,
|
||||
@NotNull UUID playerId, T amount) {
|
||||
RecordResult result;
|
||||
if (existingRecords.stream().allMatch((entry) -> amount.compareTo(entry.getRecord()) < 0)) {
|
||||
// If the given value is less than all other values, that's a world record!
|
||||
result = RecordResult.WORLD_RECORD;
|
||||
recordSetter.accept(amount);
|
||||
save();
|
||||
return result;
|
||||
}
|
||||
|
||||
ArenaRecord<T> playerRecord = getRecord(existingRecords, playerId);
|
||||
if (playerRecord != null && amount.compareTo(playerRecord.getRecord()) < 0) {
|
||||
// If the given value is less than the player's previous value, that's a personal best!
|
||||
result = RecordResult.PERSONAL_BEST;
|
||||
recordSetter.accept(amount);
|
||||
save();
|
||||
} else {
|
||||
// Make sure to save the record if this is the user's first attempt
|
||||
if (playerRecord == null) {
|
||||
recordSetter.accept(amount);
|
||||
save();
|
||||
}
|
||||
result = RecordResult.NONE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the record stored for the given player
|
||||
*
|
||||
* @param existingRecords <p>The existing records to look through</p>
|
||||
* @param playerId <p>The id of the player to look for</p>
|
||||
* @param <T> <p>The type of the stored record</p>
|
||||
* @return <p>The record, or null if not found</p>
|
||||
*/
|
||||
private <T extends Comparable<T>> @Nullable ArenaRecord<T> getRecord(@NotNull Set<ArenaRecord<T>> existingRecords,
|
||||
@NotNull UUID playerId) {
|
||||
AtomicReference<ArenaRecord<T>> record = new AtomicReference<>();
|
||||
existingRecords.forEach((item) -> {
|
||||
if (item.getUserId().equals(playerId)) {
|
||||
record.set(item);
|
||||
}
|
||||
});
|
||||
return record.get();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("arenaId", new SerializableUUID(this.arenaId));
|
||||
data.put("leastDeaths", this.leastDeaths);
|
||||
data.put("shortestTime", this.shortestTimeMilliSeconds);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,315 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.Arena;
|
||||
import net.knarcraft.minigames.arena.ArenaGameMode;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.config.DropperConfiguration;
|
||||
import net.knarcraft.minigames.util.StringSanitizer;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.knarcraft.minigames.util.InputValidationHelper.isInvalid;
|
||||
|
||||
/**
|
||||
* A representation of one dropper arena
|
||||
*/
|
||||
public class DropperArena implements Arena {
|
||||
|
||||
/**
|
||||
* An unique and persistent identifier for this arena
|
||||
*/
|
||||
private final UUID arenaId;
|
||||
|
||||
/**
|
||||
* A name used when listing and storing this arena.
|
||||
*/
|
||||
private @NotNull String arenaName;
|
||||
|
||||
/**
|
||||
* The location players are teleported to when joining this arena.
|
||||
*/
|
||||
private @NotNull Location spawnLocation;
|
||||
|
||||
/**
|
||||
* The location players will be sent to when they win or lose the arena. If not set, their entry location should be
|
||||
* used instead.
|
||||
*/
|
||||
private @Nullable Location exitLocation;
|
||||
|
||||
/**
|
||||
* The velocity in the y-direction to apply to all players in this arena.
|
||||
*/
|
||||
private double playerVerticalVelocity;
|
||||
|
||||
/**
|
||||
* The velocity in the x-direction to apply to all players in this arena
|
||||
*
|
||||
* <p>This is technically the fly speed</p>
|
||||
*/
|
||||
private float playerHorizontalVelocity;
|
||||
|
||||
/**
|
||||
* The material of the block players have to hit to win this dropper arena
|
||||
*/
|
||||
private @NotNull Material winBlockType;
|
||||
|
||||
/**
|
||||
* The arena data for this arena
|
||||
*/
|
||||
private final DropperArenaData dropperArenaData;
|
||||
|
||||
private final DropperArenaHandler dropperArenaHandler;
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena
|
||||
*
|
||||
* @param arenaId <p>The id of the arena</p>
|
||||
* @param arenaName <p>The name of the arena</p>
|
||||
* @param spawnLocation <p>The location players spawn in when entering the arena</p>
|
||||
* @param exitLocation <p>The location the players are teleported to when exiting the arena, or null</p>
|
||||
* @param playerVerticalVelocity <p>The velocity to use for players' vertical velocity</p>
|
||||
* @param playerHorizontalVelocity <p>The velocity to use for players' horizontal velocity (-1 to 1)</p>
|
||||
* @param winBlockType <p>The material of the block players have to hit to win this dropper arena</p>
|
||||
* @param dropperArenaData <p>The arena data keeping track of which players have done what in this arena</p>
|
||||
* @param arenaHandler <p>The arena handler used for saving any changes</p>
|
||||
*/
|
||||
public DropperArena(@NotNull UUID arenaId, @NotNull String arenaName, @NotNull Location spawnLocation,
|
||||
@Nullable Location exitLocation, double playerVerticalVelocity, float playerHorizontalVelocity,
|
||||
@NotNull Material winBlockType, @NotNull DropperArenaData dropperArenaData,
|
||||
@NotNull DropperArenaHandler arenaHandler) {
|
||||
this.arenaId = arenaId;
|
||||
this.arenaName = arenaName;
|
||||
this.spawnLocation = spawnLocation;
|
||||
this.exitLocation = exitLocation;
|
||||
this.playerVerticalVelocity = playerVerticalVelocity;
|
||||
this.playerHorizontalVelocity = playerHorizontalVelocity;
|
||||
this.winBlockType = winBlockType;
|
||||
this.dropperArenaData = dropperArenaData;
|
||||
this.dropperArenaHandler = arenaHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena
|
||||
*
|
||||
* <p>Note that this minimal constructor can be used to quickly create a new dropper arena at the player's given
|
||||
* location, simply by them giving an arena name.</p>
|
||||
*
|
||||
* @param arenaName <p>The name of the arena</p>
|
||||
* @param spawnLocation <p>The location players spawn in when entering the arena</p>
|
||||
* @param arenaHandler <p>The arena handler used for saving any changes</p>
|
||||
*/
|
||||
public DropperArena(@NotNull String arenaName, @NotNull Location spawnLocation,
|
||||
@NotNull DropperArenaHandler arenaHandler) {
|
||||
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
|
||||
this.arenaId = UUID.randomUUID();
|
||||
this.arenaName = arenaName;
|
||||
this.spawnLocation = spawnLocation;
|
||||
this.exitLocation = null;
|
||||
this.playerVerticalVelocity = configuration.getVerticalVelocity();
|
||||
this.playerHorizontalVelocity = configuration.getHorizontalVelocity();
|
||||
|
||||
Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries = new HashMap<>();
|
||||
for (ArenaGameMode arenaGameMode : DropperArenaGameMode.values()) {
|
||||
recordRegistries.put(arenaGameMode, new DropperArenaRecordsRegistry(this.arenaId));
|
||||
}
|
||||
|
||||
this.dropperArenaData = new DropperArenaData(this.arenaId, recordRegistries, new HashMap<>());
|
||||
this.winBlockType = Material.WATER;
|
||||
this.dropperArenaHandler = arenaHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's data
|
||||
*
|
||||
* @return <p>This arena's data</p>
|
||||
*/
|
||||
public @NotNull DropperArenaData getData() {
|
||||
return this.dropperArenaData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UUID getArenaId() {
|
||||
return this.arenaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getArenaName() {
|
||||
return this.arenaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's spawn location
|
||||
*
|
||||
* <p>The spawn location is the location every player starts from when entering the dropper.</p>
|
||||
*
|
||||
* @return <p>This arena's spawn location.</p>
|
||||
*/
|
||||
public @NotNull Location getSpawnLocation() {
|
||||
return this.spawnLocation.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's exit location
|
||||
*
|
||||
* @return <p>This arena's exit location, or null if no such location is set.</p>
|
||||
*/
|
||||
public @Nullable Location getExitLocation() {
|
||||
return this.exitLocation != null ? this.exitLocation.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vertical velocity for players in this arena
|
||||
*
|
||||
* <p>This velocity will be set on the negative y-axis, for all players in this arena.</p>
|
||||
*
|
||||
* @return <p>Players' velocity in this arena</p>
|
||||
*/
|
||||
public double getPlayerVerticalVelocity() {
|
||||
return this.playerVerticalVelocity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the horizontal for players in this arena
|
||||
*
|
||||
* <p>This will be used for players' fly-speed in this arena</p>
|
||||
*
|
||||
* @return <p>Players' velocity in this arena</p>
|
||||
*/
|
||||
public float getPlayerHorizontalVelocity() {
|
||||
return this.playerHorizontalVelocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of block a player has to hit to win this arena
|
||||
*
|
||||
* @return <p>The kind of block players must hit</p>
|
||||
*/
|
||||
public @NotNull Material getWinBlockType() {
|
||||
return this.winBlockType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getArenaNameSanitized() {
|
||||
return StringSanitizer.sanitizeArenaName(this.getArenaName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spawn location for this arena
|
||||
*
|
||||
* @param newLocation <p>The new spawn location</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setSpawnLocation(@NotNull Location newLocation) {
|
||||
if (isInvalid(newLocation)) {
|
||||
return false;
|
||||
} else {
|
||||
this.spawnLocation = newLocation;
|
||||
dropperArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the exit location for this arena
|
||||
*
|
||||
* @param newLocation <p>The new exit location</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setExitLocation(@NotNull Location newLocation) {
|
||||
if (isInvalid(newLocation)) {
|
||||
return false;
|
||||
} else {
|
||||
this.exitLocation = newLocation;
|
||||
dropperArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this arena
|
||||
*
|
||||
* @param arenaName <p>The new name</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setName(@NotNull String arenaName) {
|
||||
if (!arenaName.isBlank()) {
|
||||
String oldName = this.getArenaNameSanitized();
|
||||
this.arenaName = arenaName;
|
||||
// Update the arena lookup map to make sure the new name can be used immediately
|
||||
dropperArenaHandler.updateLookupName(oldName, this.getArenaNameSanitized());
|
||||
dropperArenaHandler.saveArenas();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the material of the win block type
|
||||
*
|
||||
* <p>The win block type is the type of block a player must hit to win in this arena</p>
|
||||
*
|
||||
* @param material <p>The material to set for the win block type</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setWinBlockType(@NotNull Material material) {
|
||||
if (material.isAir() || !material.isBlock()) {
|
||||
return false;
|
||||
} else {
|
||||
this.winBlockType = material;
|
||||
dropperArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the horizontal velocity of this arena's players
|
||||
*
|
||||
* <p>Note: It's assumed the given value is already bound-checked! (-1 to 1)</p>
|
||||
*
|
||||
* @param horizontalVelocity <p>The horizontal velocity to use</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setHorizontalVelocity(float horizontalVelocity) {
|
||||
if (horizontalVelocity < -1 || horizontalVelocity > 1) {
|
||||
return false;
|
||||
} else {
|
||||
this.playerHorizontalVelocity = horizontalVelocity;
|
||||
dropperArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertical velocity of this arena's players
|
||||
*
|
||||
* @param verticalVelocity <p>The vertical velocity to use</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setVerticalVelocity(double verticalVelocity) {
|
||||
if (verticalVelocity <= 0 || verticalVelocity > 100) {
|
||||
return false;
|
||||
} else {
|
||||
this.playerVerticalVelocity = verticalVelocity;
|
||||
dropperArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof DropperArena otherArena)) {
|
||||
return false;
|
||||
}
|
||||
return this.getArenaNameSanitized().equals(otherArena.getArenaNameSanitized());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaData;
|
||||
import net.knarcraft.minigames.arena.ArenaGameMode;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.container.SerializableContainer;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import net.knarcraft.minigames.util.SerializableConverter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Data stored for an arena
|
||||
*/
|
||||
public class DropperArenaData extends ArenaData {
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena data object
|
||||
*
|
||||
* @param arenaId <p>The id of the arena this data belongs to</p>
|
||||
* @param recordRegistries <p>The registries of this arena's records</p>
|
||||
* @param playersCompleted <p>The set of the players that have cleared this arena for each game-mode</p>
|
||||
*/
|
||||
public DropperArenaData(@NotNull UUID arenaId,
|
||||
@NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries,
|
||||
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
|
||||
super(arenaId, recordRegistries, playersCompleted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData() {
|
||||
MiniGames.getInstance().getDropperArenaHandler().saveData(this.arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a dropper arena data from the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized dropper arena data</p>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static @NotNull DropperArenaData deserialize(@NotNull Map<String, Object> data) {
|
||||
SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId");
|
||||
Map<ArenaGameMode, ArenaRecordsRegistry> recordsRegistry =
|
||||
(Map<ArenaGameMode, ArenaRecordsRegistry>) data.get("recordsRegistry");
|
||||
Map<ArenaGameMode, Set<SerializableContainer<UUID>>> playersCompletedData =
|
||||
(Map<ArenaGameMode, Set<SerializableContainer<UUID>>>) data.get("playersCompleted");
|
||||
|
||||
if (recordsRegistry == null) {
|
||||
recordsRegistry = new HashMap<>();
|
||||
} else if (playersCompletedData == null) {
|
||||
playersCompletedData = new HashMap<>();
|
||||
}
|
||||
|
||||
// Convert the serializable UUIDs to normal UUIDs
|
||||
Map<ArenaGameMode, Set<UUID>> allPlayersCompleted = new HashMap<>();
|
||||
SerializableConverter.getRawValue(playersCompletedData, allPlayersCompleted);
|
||||
|
||||
for (ArenaGameMode arenaGameMode : playersCompletedData.keySet()) {
|
||||
if (!recordsRegistry.containsKey(arenaGameMode) || recordsRegistry.get(arenaGameMode) == null) {
|
||||
recordsRegistry.put(arenaGameMode, new DropperArenaRecordsRegistry(serializableUUID.getRawValue()));
|
||||
}
|
||||
}
|
||||
return new DropperArenaData(serializableUUID.getRawValue(), recordsRegistry, allPlayersCompleted);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* All editable properties of a dropper arena
|
||||
*/
|
||||
public enum DropperArenaEditableProperty {
|
||||
|
||||
/**
|
||||
* 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<DropperArena, String> currentValueProvider;
|
||||
|
||||
/**
|
||||
* Instantiates a new arena editable property
|
||||
*
|
||||
* @param argumentString <p>The argument string used to specify this property</p>
|
||||
*/
|
||||
DropperArenaEditableProperty(@NotNull String argumentString, Function<DropperArena, String> currentValueProvider) {
|
||||
this.argumentString = argumentString;
|
||||
this.currentValueProvider = currentValueProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation of this property's current value
|
||||
*
|
||||
* @param arena <p>The arena to check the value for</p>
|
||||
* @return <p>The current value as a string</p>
|
||||
*/
|
||||
public String getCurrentValueAsString(DropperArena arena) {
|
||||
return this.currentValueProvider.apply(arena);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument string used to specify this property
|
||||
*
|
||||
* @return <p>The argument string</p>
|
||||
*/
|
||||
public @NotNull String getArgumentString() {
|
||||
return this.argumentString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the editable property corresponding to the given argument string
|
||||
*
|
||||
* @param argumentString <p>The argument string used to specify an editable property</p>
|
||||
* @return <p>The corresponding editable property, or null if not found</p>
|
||||
*/
|
||||
public static @Nullable DropperArenaEditableProperty getFromArgumentString(String argumentString) {
|
||||
for (DropperArenaEditableProperty property : DropperArenaEditableProperty.values()) {
|
||||
if (property.argumentString.equalsIgnoreCase(argumentString)) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.arena.ArenaGameMode;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A representation of possible arena game-modes
|
||||
*/
|
||||
public enum DropperArenaGameMode implements ConfigurationSerializable, ArenaGameMode {
|
||||
|
||||
/**
|
||||
* The default game-mode. Failing once throws the player out.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* A game-mode where the player's directional buttons are inverted
|
||||
*/
|
||||
INVERTED,
|
||||
|
||||
/**
|
||||
* A game-mode which swaps between normal and inverted controls on a set schedule of a few seconds
|
||||
*/
|
||||
RANDOM_INVERTED,
|
||||
;
|
||||
|
||||
/**
|
||||
* Tries to match the correct game-mode according to the given string
|
||||
*
|
||||
* @param gameMode <p>The game-mode string to match</p>
|
||||
* @return <p>The specified arena game-mode</p>
|
||||
*/
|
||||
public static @NotNull DropperArenaGameMode matchGamemode(@NotNull String gameMode) {
|
||||
String sanitized = gameMode.trim().toLowerCase();
|
||||
if (sanitized.matches("(invert(ed)?|inverse)")) {
|
||||
return DropperArenaGameMode.INVERTED;
|
||||
} else if (sanitized.matches("rand(om)?")) {
|
||||
return DropperArenaGameMode.RANDOM_INVERTED;
|
||||
} else {
|
||||
return DropperArenaGameMode.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("name", this.name());
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the arena game-mode specified by the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized arena game-mode</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static DropperArenaGameMode deserialize(Map<String, Object> data) {
|
||||
return DropperArenaGameMode.valueOf((String) data.get("name"));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaGroup;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import net.knarcraft.minigames.util.SerializableConverter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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 extends ArenaGroup {
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena group
|
||||
*
|
||||
* @param groupName <p>The name of this group</p>
|
||||
*/
|
||||
public DropperArenaGroup(@NotNull String groupName) {
|
||||
super(groupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena group
|
||||
*
|
||||
* @param groupId <p>The unique id of this group</p>
|
||||
* @param groupName <p>The name of this group</p>
|
||||
* @param arenas <p>The arenas in this group</p>
|
||||
*/
|
||||
private DropperArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas) {
|
||||
super(groupId, groupName, arenas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given player has beaten all arenas in this group on the given game-mode
|
||||
*
|
||||
* @param gameMode <p>The game-mode to check</p>
|
||||
* @param player <p>The player to check</p>
|
||||
* @return <p>True if the player has beaten all arenas, false otherwise</p>
|
||||
*/
|
||||
public boolean hasBeatenAll(DropperArenaGameMode gameMode, Player player) {
|
||||
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
|
||||
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
|
||||
MiniGames.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(gameMode, player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the given player can play the given arena part of this group, on the given game-mode
|
||||
*
|
||||
* @param gameMode <p>The game-mode the player is trying to play</p>
|
||||
* @param player <p>The player to check</p>
|
||||
* @param arenaId <p>The id of the arena in this group to check</p>
|
||||
* @return <p>True if the player is allowed to play the arena</p>
|
||||
* @throws IllegalArgumentException <p>If checking an arena not in this group</p>
|
||||
*/
|
||||
public boolean canPlay(DropperArenaGameMode gameMode, 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 = MiniGames.getInstance().getDropperArenaHandler();
|
||||
|
||||
for (UUID anArenaId : this.getArenas()) {
|
||||
// If the target arena is reached, allow, as all previous arenas must have been cleared
|
||||
if (arenaId.equals(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
|
||||
MiniGames.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(gameMode, player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized arena group</p>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static @NotNull DropperArenaGroup deserialize(@NotNull Map<String, Object> data) {
|
||||
UUID id = ((SerializableUUID) data.get("groupId")).getRawValue();
|
||||
String name = (String) data.get("groupName");
|
||||
List<SerializableUUID> serializableArenas = (List<SerializableUUID>) data.get("arenas");
|
||||
List<UUID> arenas = new ArrayList<>();
|
||||
|
||||
SerializableConverter.getRawValue(new ArrayList<>(serializableArenas), arenas);
|
||||
return new DropperArenaGroup(id, name, arenas);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,249 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.util.ArenaStorageHelper;
|
||||
import net.knarcraft.minigames.util.StringSanitizer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A handler that keeps track of all dropper arenas
|
||||
*/
|
||||
public class DropperArenaHandler {
|
||||
|
||||
private Map<UUID, DropperArena> arenas = new HashMap<>();
|
||||
private Map<UUID, DropperArenaGroup> arenaGroups = new HashMap<>();
|
||||
private Map<String, UUID> arenaNameLookup = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Gets all arenas that are within a group
|
||||
*
|
||||
* @return <p>All arenas in a group</p>
|
||||
*/
|
||||
public @NotNull Set<DropperArena> getArenasInAGroup() {
|
||||
Set<DropperArena> arenas = new HashSet<>();
|
||||
for (UUID arenaId : arenaGroups.keySet()) {
|
||||
arenas.add(this.arenas.get(arenaId));
|
||||
}
|
||||
return arenas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of all dropper groups
|
||||
*
|
||||
* @return <p>All dropper groups</p>
|
||||
*/
|
||||
public Set<DropperArenaGroup> getAllGroups() {
|
||||
return new HashSet<>(arenaGroups.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group the given arena belongs to
|
||||
*
|
||||
* @param arenaId <p>The id of the arena to get the group of</p>
|
||||
* @return <p>The group the arena belongs to, or null if not in a group</p>
|
||||
*/
|
||||
public @Nullable DropperArenaGroup getGroup(@NotNull UUID arenaId) {
|
||||
return this.arenaGroups.get(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the group for the given arena
|
||||
*
|
||||
* @param arenaId <p>The id of the arena to change</p>
|
||||
* @param arenaGroup <p>The group to add the arena to, or null to remove the current group</p>
|
||||
*/
|
||||
public void setGroup(@NotNull UUID arenaId, @Nullable DropperArenaGroup arenaGroup) {
|
||||
if (arenaGroup == null) {
|
||||
// No need to remove something non-existing
|
||||
if (!this.arenaGroups.containsKey(arenaId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the existing group
|
||||
DropperArenaGroup oldGroup = this.arenaGroups.remove(arenaId);
|
||||
oldGroup.removeArena(arenaId);
|
||||
} else {
|
||||
// Make sure to remove the arena from the old group's internal tracking
|
||||
if (this.arenaGroups.containsKey(arenaId)) {
|
||||
this.arenaGroups.remove(arenaId).removeArena(arenaId);
|
||||
}
|
||||
|
||||
this.arenaGroups.put(arenaId, arenaGroup);
|
||||
arenaGroup.addArena(arenaId);
|
||||
}
|
||||
saveGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dropper arena group with the given name
|
||||
*
|
||||
* @param groupName <p>The name of the group to get</p>
|
||||
* @return <p>The group, or null if not found</p>
|
||||
*/
|
||||
public @Nullable DropperArenaGroup getGroup(String groupName) {
|
||||
String sanitized = StringSanitizer.sanitizeArenaName(groupName);
|
||||
for (DropperArenaGroup arenaGroup : this.arenaGroups.values()) {
|
||||
if (arenaGroup.getGroupNameSanitized().equals(sanitized)) {
|
||||
return arenaGroup;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an arena's lookup name
|
||||
*
|
||||
* @param oldName <p>The arena's old sanitized lookup name</p>
|
||||
* @param newName <p>The arena's new sanitized lookup name</p>
|
||||
*/
|
||||
public void updateLookupName(@NotNull String oldName, @NotNull String newName) {
|
||||
UUID arenaId = this.arenaNameLookup.remove(oldName);
|
||||
if (arenaId != null) {
|
||||
this.arenaNameLookup.put(newName, arenaId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new arena
|
||||
*
|
||||
* @param arena <p>The arena to add</p>
|
||||
*/
|
||||
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 <p>The id of the arena to get</p>
|
||||
* @return <p>The arena, or null if no arena could be found</p>
|
||||
*/
|
||||
public @Nullable DropperArena getArena(@NotNull UUID arenaId) {
|
||||
return this.arenas.get(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arena with the given name
|
||||
*
|
||||
* @param arenaName <p>The arena to get</p>
|
||||
* @return <p>The arena with the given name, or null if not found</p>
|
||||
*/
|
||||
public @Nullable DropperArena getArena(@NotNull String arenaName) {
|
||||
return this.arenas.get(this.arenaNameLookup.get(StringSanitizer.sanitizeArenaName(arenaName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all known arenas
|
||||
*
|
||||
* @return <p>All known arenas</p>
|
||||
*/
|
||||
public @NotNull Map<UUID, DropperArena> getArenas() {
|
||||
return new HashMap<>(this.arenas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given arena
|
||||
*
|
||||
* @param arena <p>The arena to remove</p>
|
||||
*/
|
||||
public void removeArena(@NotNull DropperArena arena) {
|
||||
UUID arenaId = arena.getArenaId();
|
||||
MiniGames.getInstance().getDropperArenaPlayerRegistry().removeForArena(arena);
|
||||
this.arenas.remove(arenaId);
|
||||
this.arenaNameLookup.remove(arena.getArenaNameSanitized());
|
||||
this.arenaGroups.remove(arenaId);
|
||||
if (!ArenaStorageHelper.removeDropperArenaData(arenaId)) {
|
||||
MiniGames.log(Level.WARNING, "Unable to remove dropper arena data file " + arenaId + ".yml. " +
|
||||
"You must remove it manually!");
|
||||
}
|
||||
this.saveArenas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the data for the given arena
|
||||
*
|
||||
* @param arenaId <p>The id of the arena whose data should be saved</p>
|
||||
*/
|
||||
public void saveData(UUID arenaId) {
|
||||
try {
|
||||
ArenaStorageHelper.saveDropperArenaData(this.arenas.get(arenaId).getData());
|
||||
} catch (IOException e) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to save arena data! Data loss can occur!");
|
||||
MiniGames.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all current dropper groups to disk
|
||||
*/
|
||||
public void saveGroups() {
|
||||
try {
|
||||
ArenaStorageHelper.saveDropperArenaGroups(new HashSet<>(this.arenaGroups.values()));
|
||||
} catch (IOException e) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to save current arena groups! " +
|
||||
"Data loss can occur!");
|
||||
MiniGames.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all arenas and groups from disk
|
||||
*/
|
||||
public void load() {
|
||||
loadArenas();
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all dropper groups from disk
|
||||
*/
|
||||
private void loadGroups() {
|
||||
Set<DropperArenaGroup> arenaGroups = ArenaStorageHelper.loadDropperArenaGroups();
|
||||
Map<UUID, DropperArenaGroup> 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
|
||||
*/
|
||||
public void saveArenas() {
|
||||
try {
|
||||
ArenaStorageHelper.saveDropperArenas(this.arenas);
|
||||
} catch (IOException e) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to save current arenas! " +
|
||||
"Data loss can occur!");
|
||||
MiniGames.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all arenas from disk
|
||||
*/
|
||||
private void loadArenas() {
|
||||
this.arenas = ArenaStorageHelper.loadDropperArenas();
|
||||
|
||||
// Save a map from arena name to arena id for improved performance
|
||||
this.arenaNameLookup = new HashMap<>();
|
||||
for (Map.Entry<UUID, DropperArena> arena : this.arenas.entrySet()) {
|
||||
String sanitizedName = arena.getValue().getArenaNameSanitized();
|
||||
this.arenaNameLookup.put(sanitizedName, arena.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A registry to keep track of which players are playing in which arenas
|
||||
*/
|
||||
public class DropperArenaPlayerRegistry {
|
||||
|
||||
private final Map<UUID, DropperArenaSession> arenaPlayers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Registers that the given player has started playing the given dropper arena session
|
||||
*
|
||||
* @param playerId <p>The id of the player that started playing</p>
|
||||
* @param arena <p>The arena session to register</p>
|
||||
*/
|
||||
public void registerPlayer(@NotNull UUID playerId, @NotNull DropperArenaSession arena) {
|
||||
this.arenaPlayers.put(playerId, arena);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this player from players currently playing
|
||||
*
|
||||
* @param playerId <p>The id of the player to remove</p>
|
||||
*/
|
||||
public boolean removePlayer(@NotNull UUID playerId) {
|
||||
return this.arenaPlayers.remove(playerId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player's active dropper arena session
|
||||
*
|
||||
* @param playerId <p>The id of the player to get arena for</p>
|
||||
* @return <p>The player's active arena session, or null if not currently playing</p>
|
||||
*/
|
||||
public @Nullable DropperArenaSession getArenaSession(@NotNull UUID playerId) {
|
||||
return this.arenaPlayers.getOrDefault(playerId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all active sessions for the given arena
|
||||
*
|
||||
* @param arena <p>The arena to remove sessions for</p>
|
||||
*/
|
||||
public void removeForArena(DropperArena arena) {
|
||||
for (Map.Entry<UUID, DropperArenaSession> entry : this.arenaPlayers.entrySet()) {
|
||||
if (entry.getValue().getArena() == arena) {
|
||||
// Kick the player gracefully
|
||||
entry.getValue().triggerQuit(false);
|
||||
this.arenaPlayers.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.arena.record.IntegerRecord;
|
||||
import net.knarcraft.minigames.arena.record.LongRecord;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A registry keeping track of all records
|
||||
*/
|
||||
public class DropperArenaRecordsRegistry extends ArenaRecordsRegistry {
|
||||
|
||||
/**
|
||||
* Instantiates a new empty records registry
|
||||
*/
|
||||
public DropperArenaRecordsRegistry(@NotNull UUID arenaId) {
|
||||
super(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new records registry
|
||||
*
|
||||
* @param leastDeaths <p>The existing least death records to use</p>
|
||||
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
|
||||
*/
|
||||
private DropperArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
|
||||
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
|
||||
super(arenaId, leastDeaths, shortestTimeMilliSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves changed records
|
||||
*/
|
||||
protected void save() {
|
||||
MiniGames.getInstance().getDropperArenaHandler().saveData(this.arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized records registry</p>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static DropperArenaRecordsRegistry deserialize(Map<String, Object> data) {
|
||||
UUID arenaId = ((SerializableUUID) data.get("arenaId")).getRawValue();
|
||||
Set<IntegerRecord> leastDeaths =
|
||||
(Set<IntegerRecord>) data.getOrDefault("leastDeaths", new HashMap<>());
|
||||
Set<LongRecord> shortestTimeMilliseconds =
|
||||
(Set<LongRecord>) data.getOrDefault("shortestTime", new HashMap<>());
|
||||
|
||||
leastDeaths.removeIf(Objects::isNull);
|
||||
shortestTimeMilliseconds.removeIf(Objects::isNull);
|
||||
return new DropperArenaRecordsRegistry(arenaId, leastDeaths, shortestTimeMilliseconds);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,206 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.config.DropperConfiguration;
|
||||
import net.knarcraft.minigames.property.RecordResult;
|
||||
import net.knarcraft.minigames.util.PlayerTeleporter;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* A representation of a player's current session in a dropper arena
|
||||
*/
|
||||
public class DropperArenaSession {
|
||||
|
||||
private final @NotNull DropperArena arena;
|
||||
private final @NotNull Player player;
|
||||
private final @NotNull DropperArenaGameMode gameMode;
|
||||
private int deaths;
|
||||
private final long startTime;
|
||||
private final DropperPlayerEntryState entryState;
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena session
|
||||
*
|
||||
* @param dropperArena <p>The arena that's being played in</p>
|
||||
* @param player <p>The player playing the arena</p>
|
||||
* @param gameMode <p>The game-mode</p>
|
||||
*/
|
||||
public DropperArenaSession(@NotNull DropperArena dropperArena, @NotNull Player player,
|
||||
@NotNull DropperArenaGameMode gameMode) {
|
||||
this.arena = dropperArena;
|
||||
this.player = player;
|
||||
this.gameMode = gameMode;
|
||||
this.deaths = 0;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
|
||||
boolean makeInvisible = configuration.makePlayersInvisible();
|
||||
boolean disableCollision = configuration.disableHitCollision();
|
||||
this.entryState = new DropperPlayerEntryState(player, gameMode, makeInvisible, disableCollision);
|
||||
// Make the player fly to improve mobility in the air
|
||||
this.entryState.setArenaState(this.arena.getPlayerHorizontalVelocity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the game-mode the player is playing in this session
|
||||
*
|
||||
* @return <p>The game-mode for this session</p>
|
||||
*/
|
||||
public @NotNull DropperArenaGameMode getGameMode() {
|
||||
return this.gameMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state of the player when they joined the session
|
||||
*
|
||||
* @return <p>The player's entry state</p>
|
||||
*/
|
||||
public @NotNull DropperPlayerEntryState getEntryState() {
|
||||
return this.entryState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a win for the player playing in this session
|
||||
*/
|
||||
public void triggerWin() {
|
||||
// Stop this session
|
||||
stopSession();
|
||||
|
||||
// Check for, and display, records
|
||||
MiniGames miniGames = MiniGames.getInstance();
|
||||
boolean ignore = miniGames.getDropperConfiguration().ignoreRecordsUntilGroupBeatenOnce();
|
||||
DropperArenaGroup group = miniGames.getDropperArenaHandler().getGroup(this.arena.getArenaId());
|
||||
if (!ignore || group == null || group.hasBeatenAll(this.gameMode, this.player)) {
|
||||
registerRecord();
|
||||
}
|
||||
|
||||
// Mark the arena as cleared
|
||||
if (this.arena.getData().setCompleted(this.gameMode, this.player)) {
|
||||
this.player.sendMessage("You cleared the arena!");
|
||||
}
|
||||
this.player.sendMessage("You won!");
|
||||
|
||||
// Teleport the player out of the arena
|
||||
teleportToExit(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the playing player out of the arena
|
||||
*/
|
||||
private void teleportToExit(boolean immediately) {
|
||||
// Teleport the player out of the arena
|
||||
Location exitLocation;
|
||||
if (this.arena.getExitLocation() != null) {
|
||||
exitLocation = this.arena.getExitLocation();
|
||||
} else {
|
||||
exitLocation = this.entryState.getEntryLocation();
|
||||
}
|
||||
PlayerTeleporter.teleportPlayer(this.player, exitLocation, true, immediately);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this session from current sessions
|
||||
*/
|
||||
private void removeSession() {
|
||||
// Remove this session for game sessions to stop listeners from fiddling more with the player
|
||||
boolean removedSession = MiniGames.getInstance().getDropperArenaPlayerRegistry().removePlayer(player.getUniqueId());
|
||||
if (!removedSession) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to remove dropper arena session for " + player.getName() + ". " +
|
||||
"This will have unintended consequences.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the player's record if necessary, and prints record information to the player
|
||||
*/
|
||||
private void registerRecord() {
|
||||
ArenaRecordsRegistry recordsRegistry = this.arena.getData().getRecordRegistries().get(this.gameMode);
|
||||
long timeElapsed = System.currentTimeMillis() - this.startTime;
|
||||
announceRecord(recordsRegistry.registerTimeRecord(this.player.getUniqueId(), timeElapsed), "time");
|
||||
announceRecord(recordsRegistry.registerDeathRecord(this.player.getUniqueId(), this.deaths), "least deaths");
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces a record set by this player
|
||||
*
|
||||
* @param recordResult <p>The result of the record</p>
|
||||
* @param type <p>The type of record set (time or deaths)</p>
|
||||
*/
|
||||
private void announceRecord(@NotNull RecordResult recordResult, @NotNull String type) {
|
||||
if (recordResult == RecordResult.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets a string representation of the played game-mode
|
||||
String gameModeString = switch (this.gameMode) {
|
||||
case DEFAULT -> "default";
|
||||
case INVERTED -> "inverted";
|
||||
case RANDOM_INVERTED -> "random";
|
||||
};
|
||||
|
||||
String recordString = "You just set a %s on the %s game-mode!";
|
||||
recordString = switch (recordResult) {
|
||||
case WORLD_RECORD -> String.format(recordString, "new %s record", gameModeString);
|
||||
case PERSONAL_BEST -> String.format(recordString, "personal %s record", gameModeString);
|
||||
default -> throw new IllegalStateException("Unexpected value: " + recordResult);
|
||||
};
|
||||
player.sendMessage(String.format(recordString, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a loss for the player playing in this session
|
||||
*/
|
||||
public void triggerLoss() {
|
||||
this.deaths++;
|
||||
//Teleport the player back to the top
|
||||
PlayerTeleporter.teleportPlayer(this.player, this.arena.getSpawnLocation(), true, false);
|
||||
this.entryState.setArenaState(this.arena.getPlayerHorizontalVelocity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a quit for the player playing in this session
|
||||
*/
|
||||
public void triggerQuit(boolean immediately) {
|
||||
// Stop this session
|
||||
stopSession();
|
||||
// Teleport the player out of the arena
|
||||
teleportToExit(immediately);
|
||||
|
||||
player.sendMessage("You quit the arena!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this session, and disables flight mode
|
||||
*/
|
||||
private void stopSession() {
|
||||
// Remove this session from game sessions to stop listeners from fiddling more with the player
|
||||
removeSession();
|
||||
|
||||
// Remove flight mode
|
||||
entryState.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arena this session is being played in
|
||||
*
|
||||
* @return <p>The session's arena</p>
|
||||
*/
|
||||
public @NotNull DropperArena getArena() {
|
||||
return this.arena;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player playing in this session
|
||||
*
|
||||
* @return <p>This session's player</p>
|
||||
*/
|
||||
public @NotNull Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A representation of each key used for storing arena data
|
||||
*/
|
||||
public enum DropperArenaStorageKey {
|
||||
|
||||
/**
|
||||
* The key for an arena's id
|
||||
*/
|
||||
ID("arenaId"),
|
||||
|
||||
/**
|
||||
* The key for an arena's name
|
||||
*/
|
||||
NAME("arenaName"),
|
||||
|
||||
/**
|
||||
* The key for an arena's spawn location
|
||||
*/
|
||||
SPAWN_LOCATION("arenaSpawnLocation"),
|
||||
|
||||
/**
|
||||
* The key for an arena's exit location
|
||||
*/
|
||||
EXIT_LOCATION("arenaExitLocation"),
|
||||
|
||||
/**
|
||||
* The key for a player in this arena's vertical velocity
|
||||
*/
|
||||
PLAYER_VERTICAL_VELOCITY("arenaPlayerVerticalVelocity"),
|
||||
|
||||
/**
|
||||
* The key for a player in this arena's horizontal velocity
|
||||
*/
|
||||
PLAYER_HORIZONTAL_VELOCITY("arenaPlayerHorizontalVelocity"),
|
||||
|
||||
/**
|
||||
* The key for the type of this arena's win block
|
||||
*/
|
||||
WIN_BLOCK_TYPE("winBlockType"),
|
||||
|
||||
/**
|
||||
* The hey for this arena's data
|
||||
*/
|
||||
DATA("arenaData"),
|
||||
;
|
||||
|
||||
private final @NotNull String key;
|
||||
|
||||
/**
|
||||
* Instantiates a new arena storage key
|
||||
*
|
||||
* @param key <p>The string path of the configuration key this value represents.</p>
|
||||
*/
|
||||
DropperArenaStorageKey(@NotNull String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration key this enum represents
|
||||
*
|
||||
* @return <p>The string key representation.</p>
|
||||
*/
|
||||
public @NotNull String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
package net.knarcraft.minigames.arena.dropper;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The state of a player before entering a dropper arena
|
||||
*/
|
||||
public class DropperPlayerEntryState {
|
||||
|
||||
private final Player player;
|
||||
private final Location entryLocation;
|
||||
private final boolean originalIsFlying;
|
||||
private final float originalFlySpeed;
|
||||
private final GameMode originalGameMode;
|
||||
private final boolean originalAllowFlight;
|
||||
private final boolean originalInvulnerable;
|
||||
private final boolean originalIsSwimming;
|
||||
private final boolean originalCollideAble;
|
||||
private final boolean makePlayerInvisible;
|
||||
private final boolean disableHitCollision;
|
||||
private final DropperArenaGameMode arenaGameMode;
|
||||
|
||||
/**
|
||||
* Instantiates a new player state
|
||||
*
|
||||
* @param player <p>The player whose state should be stored</p>
|
||||
*/
|
||||
public DropperPlayerEntryState(@NotNull Player player, @NotNull DropperArenaGameMode arenaGameMode, boolean makePlayerInvisible,
|
||||
boolean disableHitCollision) {
|
||||
this.player = player;
|
||||
this.entryLocation = player.getLocation().clone();
|
||||
this.originalFlySpeed = player.getFlySpeed();
|
||||
this.originalIsFlying = player.isFlying();
|
||||
this.originalGameMode = player.getGameMode();
|
||||
this.originalAllowFlight = player.getAllowFlight();
|
||||
this.originalInvulnerable = player.isInvulnerable();
|
||||
this.originalIsSwimming = player.isSwimming();
|
||||
this.arenaGameMode = arenaGameMode;
|
||||
this.originalCollideAble = player.isCollidable();
|
||||
this.makePlayerInvisible = makePlayerInvisible;
|
||||
this.disableHitCollision = disableHitCollision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the stored player to the state used by arenas
|
||||
*
|
||||
* @param horizontalVelocity <p>The horizontal velocity to apply to the player</p>
|
||||
*/
|
||||
public void setArenaState(float horizontalVelocity) {
|
||||
this.player.setAllowFlight(true);
|
||||
this.player.setFlying(true);
|
||||
this.player.setGameMode(GameMode.ADVENTURE);
|
||||
this.player.setSwimming(false);
|
||||
if (this.disableHitCollision) {
|
||||
this.player.setCollidable(false);
|
||||
}
|
||||
if (this.makePlayerInvisible) {
|
||||
this.player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,
|
||||
PotionEffect.INFINITE_DURATION, 3));
|
||||
}
|
||||
|
||||
// If playing on the inverted game-mode, negate the horizontal velocity to swap the controls
|
||||
if (arenaGameMode == DropperArenaGameMode.INVERTED) {
|
||||
this.player.setFlySpeed(-horizontalVelocity);
|
||||
} else {
|
||||
this.player.setFlySpeed(horizontalVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the stored state for the stored player
|
||||
*/
|
||||
public void restore() {
|
||||
this.player.setFlying(this.originalIsFlying);
|
||||
this.player.setGameMode(this.originalGameMode);
|
||||
this.player.setAllowFlight(this.originalAllowFlight);
|
||||
this.player.setFlySpeed(this.originalFlySpeed);
|
||||
this.player.setInvulnerable(this.originalInvulnerable);
|
||||
this.player.setSwimming(this.originalIsSwimming);
|
||||
if (this.disableHitCollision) {
|
||||
this.player.setCollidable(this.originalCollideAble);
|
||||
}
|
||||
if (this.makePlayerInvisible) {
|
||||
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location the player entered from
|
||||
*
|
||||
* @return <p>The location the player entered from</p>
|
||||
*/
|
||||
public Location getEntryLocation() {
|
||||
return this.entryLocation;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,363 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaGameMode;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.util.StringSanitizer;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.knarcraft.minigames.util.InputValidationHelper.isInvalid;
|
||||
|
||||
/**
|
||||
* A representation of one parkour arena
|
||||
*/
|
||||
public class ParkourArena {
|
||||
|
||||
/**
|
||||
* An unique and persistent identifier for this arena
|
||||
*/
|
||||
private final UUID arenaId;
|
||||
|
||||
/**
|
||||
* A name used when listing and storing this arena.
|
||||
*/
|
||||
private @NotNull String arenaName;
|
||||
|
||||
/**
|
||||
* The location players are teleported to when joining this arena.
|
||||
*/
|
||||
private @NotNull Location spawnLocation;
|
||||
|
||||
/**
|
||||
* The location players will be sent to when they win or lose the arena. If not set, their entry location should be
|
||||
* used instead.
|
||||
*/
|
||||
private @Nullable Location exitLocation;
|
||||
|
||||
/**
|
||||
* The material of the block players have to hit to win this parkour arena
|
||||
*/
|
||||
private @NotNull Material winBlockType;
|
||||
|
||||
/**
|
||||
* The location the player has to reach to win. If not set, winBlockType is used instead
|
||||
*/
|
||||
private @Nullable Location winLocation;
|
||||
|
||||
/**
|
||||
* The block types constituting this arena's kill plane
|
||||
*/
|
||||
private @Nullable Set<Material> killPlaneBlocks;
|
||||
|
||||
/**
|
||||
* The checkpoints for this arena. Entering a checkpoint overrides the player's spawn location.
|
||||
*/
|
||||
private @NotNull List<Location> checkpoints;
|
||||
|
||||
/**
|
||||
* The arena data for this arena
|
||||
*/
|
||||
private final ParkourArenaData parkourArenaData;
|
||||
|
||||
private final ParkourArenaHandler parkourArenaHandler;
|
||||
|
||||
/**
|
||||
* Instantiates a new parkour arena
|
||||
*
|
||||
* @param arenaId <p>The id of the arena</p>
|
||||
* @param arenaName <p>The name of the arena</p>
|
||||
* @param spawnLocation <p>The location players spawn in when entering the arena</p>
|
||||
* @param exitLocation <p>The location the players are teleported to when exiting the arena, or null</p>
|
||||
* @param winBlockType <p>The material of the block players have to hit to win this parkour arena</p>
|
||||
* @param parkourArenaData <p>The arena data keeping track of which players have done what in this arena</p>
|
||||
* @param arenaHandler <p>The arena handler used for saving any changes</p>
|
||||
*/
|
||||
public ParkourArena(@NotNull UUID arenaId, @NotNull String arenaName, @NotNull Location spawnLocation,
|
||||
@Nullable Location exitLocation, @NotNull Material winBlockType,
|
||||
@Nullable Set<Material> killPlaneBlocks, @NotNull List<Location> checkpoints,
|
||||
@NotNull ParkourArenaData parkourArenaData, @NotNull ParkourArenaHandler arenaHandler) {
|
||||
this.arenaId = arenaId;
|
||||
this.arenaName = arenaName;
|
||||
this.spawnLocation = spawnLocation;
|
||||
this.exitLocation = exitLocation;
|
||||
this.winBlockType = winBlockType;
|
||||
this.killPlaneBlocks = killPlaneBlocks;
|
||||
this.checkpoints = checkpoints;
|
||||
this.parkourArenaData = parkourArenaData;
|
||||
this.parkourArenaHandler = arenaHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new parkour arena
|
||||
*
|
||||
* <p>Note that this minimal constructor can be used to quickly create a new parkour arena at the player's given
|
||||
* location, simply by them giving an arena name.</p>
|
||||
*
|
||||
* @param arenaName <p>The name of the arena</p>
|
||||
* @param spawnLocation <p>The location players spawn in when entering the arena</p>
|
||||
* @param arenaHandler <p>The arena handler used for saving any changes</p>
|
||||
*/
|
||||
public ParkourArena(@NotNull String arenaName, @NotNull Location spawnLocation,
|
||||
@NotNull ParkourArenaHandler arenaHandler) {
|
||||
this.arenaId = UUID.randomUUID();
|
||||
this.arenaName = arenaName;
|
||||
this.spawnLocation = spawnLocation;
|
||||
this.exitLocation = null;
|
||||
this.winLocation = null;
|
||||
|
||||
Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries = new HashMap<>();
|
||||
for (ParkourArenaGameMode arenaGameMode : ParkourArenaGameMode.values()) {
|
||||
recordRegistries.put(arenaGameMode, new ParkourArenaRecordsRegistry(this.arenaId));
|
||||
}
|
||||
|
||||
this.parkourArenaData = new ParkourArenaData(this.arenaId, recordRegistries, new HashMap<>());
|
||||
this.winBlockType = Material.EMERALD_BLOCK;
|
||||
this.killPlaneBlocks = null;
|
||||
this.checkpoints = new ArrayList<>();
|
||||
this.parkourArenaHandler = arenaHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's data
|
||||
*
|
||||
* @return <p>This arena's data</p>
|
||||
*/
|
||||
public @NotNull ParkourArenaData getData() {
|
||||
return this.parkourArenaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of this arena
|
||||
*
|
||||
* @return <p>This arena's identifier</p>
|
||||
*/
|
||||
public @NotNull UUID getArenaId() {
|
||||
return this.arenaId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this arena
|
||||
*
|
||||
* @return <p>The name of this arena</p>
|
||||
*/
|
||||
public @NotNull String getArenaName() {
|
||||
return this.arenaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's spawn location
|
||||
*
|
||||
* <p>The spawn location is the location every player starts from when entering the parkour arena.</p>
|
||||
*
|
||||
* @return <p>This arena's spawn location.</p>
|
||||
*/
|
||||
public @NotNull Location getSpawnLocation() {
|
||||
return this.spawnLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's exit location
|
||||
*
|
||||
* @return <p>This arena's exit location, or null if no such location is set.</p>
|
||||
*/
|
||||
public @Nullable Location getExitLocation() {
|
||||
return this.exitLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of block a player has to hit to win this arena
|
||||
*
|
||||
* @return <p>The kind of block players must hit</p>
|
||||
*/
|
||||
public @NotNull Material getWinBlockType() {
|
||||
return this.winBlockType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The location a player has to reach to win this arena
|
||||
*
|
||||
* <p></p>
|
||||
*
|
||||
* @return <p>The win trigger's location</p>
|
||||
*/
|
||||
public @Nullable Location getWinLocation() {
|
||||
return this.winLocation != null ? this.winLocation.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block types used for this parkour arena's kill plane
|
||||
*
|
||||
* @return <p>The types of blocks that cause a loss</p>
|
||||
*/
|
||||
public Set<Material> getKillPlaneBlocks() {
|
||||
if (this.killPlaneBlocks != null) {
|
||||
return new HashSet<>(this.killPlaneBlocks);
|
||||
} else {
|
||||
return MiniGames.getInstance().getParkourConfiguration().getKillPlaneBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all checkpoint locations for this arena
|
||||
*
|
||||
* @return <p>All checkpoint locations for this arena</p>
|
||||
*/
|
||||
public List<Location> getCheckpoints() {
|
||||
List<Location> copy = new ArrayList<>(this.checkpoints.size());
|
||||
for (Location location : this.checkpoints) {
|
||||
copy.add(location.clone());
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this arena's sanitized name
|
||||
*
|
||||
* @return <p>This arena's sanitized name</p>
|
||||
*/
|
||||
public @NotNull String getArenaNameSanitized() {
|
||||
return StringSanitizer.sanitizeArenaName(this.getArenaName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spawn location for this arena
|
||||
*
|
||||
* @param newLocation <p>The new spawn location</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setSpawnLocation(@NotNull Location newLocation) {
|
||||
if (isInvalid(newLocation)) {
|
||||
return false;
|
||||
} else {
|
||||
this.spawnLocation = newLocation;
|
||||
parkourArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the exit location for this arena
|
||||
*
|
||||
* @param newLocation <p>The new exit location</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setExitLocation(@NotNull Location newLocation) {
|
||||
if (isInvalid(newLocation)) {
|
||||
return false;
|
||||
} else {
|
||||
this.exitLocation = newLocation;
|
||||
parkourArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this arena
|
||||
*
|
||||
* @param arenaName <p>The new name</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setName(@NotNull String arenaName) {
|
||||
if (!arenaName.isBlank()) {
|
||||
String oldName = this.getArenaNameSanitized();
|
||||
this.arenaName = arenaName;
|
||||
// Update the arena lookup map to make sure the new name can be used immediately
|
||||
parkourArenaHandler.updateLookupName(oldName, this.getArenaNameSanitized());
|
||||
parkourArenaHandler.saveArenas();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the material of the win block type
|
||||
*
|
||||
* <p>The win block type is the type of block a player must hit to win in this arena</p>
|
||||
*
|
||||
* @param material <p>The material to set for the win block type</p>
|
||||
* @return <p>True if successfully updated</p>
|
||||
*/
|
||||
public boolean setWinBlockType(@NotNull Material material) {
|
||||
if (material.isAir() || !material.isBlock()) {
|
||||
return false;
|
||||
} else {
|
||||
this.winBlockType = material;
|
||||
parkourArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location players need to reach to win this arena
|
||||
*
|
||||
* @param newLocation <p>The location players have to reach</p>
|
||||
* @return <p>True if successfully changed</p>
|
||||
*/
|
||||
public boolean setWinLocation(@NotNull Location newLocation) {
|
||||
if (isInvalid(newLocation)) {
|
||||
return false;
|
||||
} else {
|
||||
this.exitLocation = newLocation.clone();
|
||||
parkourArenaHandler.saveArenas();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of blocks constituting this arena's kill plane
|
||||
*
|
||||
* @param killPlaneBlocks <p>The blocks that will cause players to lose</p>
|
||||
* @return <p>True if successfully changed</p>
|
||||
*/
|
||||
public boolean setKillPlaneBlocks(@NotNull Set<Material> killPlaneBlocks) {
|
||||
for (Material material : killPlaneBlocks) {
|
||||
// Make sure no nulls have entered the set
|
||||
if (material == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.killPlaneBlocks = new HashSet<>(killPlaneBlocks);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checkpoints of this arena
|
||||
*
|
||||
* @param checkpoints <p>The checkpoints to use</p>
|
||||
* @return <p>True if successfully changed</p>
|
||||
*/
|
||||
public boolean setCheckpoints(@NotNull List<Location> checkpoints) {
|
||||
List<Location> copy = new ArrayList<>(checkpoints.size());
|
||||
for (Location location : checkpoints) {
|
||||
if (isInvalid(location)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
copy.add(location.clone());
|
||||
}
|
||||
this.checkpoints = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof ParkourArena otherArena)) {
|
||||
return false;
|
||||
}
|
||||
return this.getArenaNameSanitized().equals(otherArena.getArenaNameSanitized());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaData;
|
||||
import net.knarcraft.minigames.arena.ArenaGameMode;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.container.SerializableContainer;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import net.knarcraft.minigames.util.SerializableConverter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Data stored for an arena
|
||||
*/
|
||||
public class ParkourArenaData extends ArenaData {
|
||||
|
||||
/**
|
||||
* Instantiates a new parkour arena data object
|
||||
*
|
||||
* @param arenaId <p>The id of the arena this data belongs to</p>
|
||||
* @param recordRegistries <p>The registry of this arena's records</p>
|
||||
* @param playersCompleted <p>The set of the players that have cleared this arena</p>
|
||||
*/
|
||||
public ParkourArenaData(@NotNull UUID arenaId,
|
||||
@NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries,
|
||||
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
|
||||
super(arenaId, recordRegistries, playersCompleted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData() {
|
||||
MiniGames.getInstance().getParkourArenaHandler().saveData(this.arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a dropper arena data from the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized dropper arena data</p>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static @NotNull ParkourArenaData deserialize(@NotNull Map<String, Object> data) {
|
||||
SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId");
|
||||
Map<ArenaGameMode, ArenaRecordsRegistry> recordsRegistry =
|
||||
(Map<ArenaGameMode, ArenaRecordsRegistry>) data.get("recordsRegistry");
|
||||
Map<ArenaGameMode, Set<SerializableContainer<UUID>>> playersCompletedData =
|
||||
(Map<ArenaGameMode, Set<SerializableContainer<UUID>>>) data.get("playersCompleted");
|
||||
|
||||
if (recordsRegistry == null) {
|
||||
recordsRegistry = new HashMap<>();
|
||||
} else if (playersCompletedData == null) {
|
||||
playersCompletedData = new HashMap<>();
|
||||
}
|
||||
|
||||
// Convert the serializable UUIDs to normal UUIDs
|
||||
Map<ArenaGameMode, Set<UUID>> allPlayersCompleted = new HashMap<>();
|
||||
SerializableConverter.getRawValue(playersCompletedData, allPlayersCompleted);
|
||||
|
||||
for (ArenaGameMode arenaGameMode : playersCompletedData.keySet()) {
|
||||
if (!recordsRegistry.containsKey(arenaGameMode) || recordsRegistry.get(arenaGameMode) == null) {
|
||||
recordsRegistry.put(arenaGameMode, new DropperArenaRecordsRegistry(serializableUUID.getRawValue()));
|
||||
}
|
||||
}
|
||||
return new ParkourArenaData(serializableUUID.getRawValue(), recordsRegistry, allPlayersCompleted);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.arena.ArenaGameMode;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A representation of possible arena game-modes
|
||||
*/
|
||||
public enum ParkourArenaGameMode implements ConfigurationSerializable, ArenaGameMode {
|
||||
|
||||
/**
|
||||
* The default game-mode. Failing once throws the player out.
|
||||
*/
|
||||
DEFAULT,
|
||||
;
|
||||
|
||||
/**
|
||||
* Tries to match the correct game-mode according to the given string
|
||||
*
|
||||
* @param gameMode <p>The game-mode string to match</p>
|
||||
* @return <p>The specified arena game-mode</p>
|
||||
*/
|
||||
public static @NotNull ParkourArenaGameMode matchGamemode(@NotNull String gameMode) {
|
||||
return ParkourArenaGameMode.DEFAULT;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("name", this.name());
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the arena game-mode specified by the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized arena game-mode</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static ParkourArenaGameMode deserialize(Map<String, Object> data) {
|
||||
return ParkourArenaGameMode.valueOf((String) data.get("name"));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaGroup;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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 ParkourArenaGroup extends ArenaGroup {
|
||||
|
||||
/**
|
||||
* Instantiates a new parkour arena group
|
||||
*
|
||||
* @param groupName <p>The name of this group</p>
|
||||
*/
|
||||
public ParkourArenaGroup(@NotNull String groupName) {
|
||||
super(groupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new parkour arena group
|
||||
*
|
||||
* @param groupId <p>The unique id of this group</p>
|
||||
* @param groupName <p>The name of this group</p>
|
||||
* @param arenas <p>The arenas in this group</p>
|
||||
*/
|
||||
private ParkourArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas) {
|
||||
super(groupId, groupName, arenas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given player has beaten all arenas in this group on the given game-mode
|
||||
*
|
||||
* @param gameMode <p>The game-mode to check</p>
|
||||
* @param player <p>The player to check</p>
|
||||
* @return <p>True if the player has beaten all arenas, false otherwise</p>
|
||||
*/
|
||||
public boolean hasBeatenAll(ParkourArenaGameMode gameMode, Player player) {
|
||||
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
|
||||
for (UUID anArenaId : this.getArenas()) {
|
||||
ParkourArena parkourArena = arenaHandler.getArena(anArenaId);
|
||||
if (parkourArena == null) {
|
||||
// The arena would only be null if the arena has been deleted, but not removed from this group
|
||||
MiniGames.log(Level.WARNING, "The parkour group " + this.getGroupName() +
|
||||
" contains the arena id " + anArenaId + " which is not a valid arena id!");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parkourArena.getData().hasNotCompleted(gameMode, player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the given player can play the given arena part of this group, on the given game-mode
|
||||
*
|
||||
* @param gameMode <p>The game-mode the player is trying to play</p>
|
||||
* @param player <p>The player to check</p>
|
||||
* @param arenaId <p>The id of the arena in this group to check</p>
|
||||
* @return <p>True if the player is allowed to play the arena</p>
|
||||
* @throws IllegalArgumentException <p>If checking an arena not in this group</p>
|
||||
*/
|
||||
public boolean canPlay(ParkourArenaGameMode gameMode, Player player, UUID arenaId) throws IllegalArgumentException {
|
||||
if (!this.arenas.contains(arenaId)) {
|
||||
throw new IllegalArgumentException("Cannot check for playability for arena not in this group!");
|
||||
}
|
||||
|
||||
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
|
||||
|
||||
for (UUID anArenaId : this.getArenas()) {
|
||||
// If the target arena is reached, allow, as all previous arenas must have been cleared
|
||||
if (arenaId.equals(anArenaId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ParkourArena parkourArena = arenaHandler.getArena(anArenaId);
|
||||
if (parkourArena == null) {
|
||||
// The arena would only be null if the arena has been deleted, but not removed from this group
|
||||
MiniGames.log(Level.WARNING, String.format("The parkour 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 (parkourArena.getData().hasNotCompleted(gameMode, player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized arena group</p>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static @NotNull ParkourArenaGroup deserialize(@NotNull Map<String, Object> data) {
|
||||
UUID id = ((SerializableUUID) data.get("groupId")).getRawValue();
|
||||
String name = (String) data.get("groupName");
|
||||
List<SerializableUUID> serializableArenas = (List<SerializableUUID>) data.get("arenas");
|
||||
List<UUID> arenas = new ArrayList<>();
|
||||
for (SerializableUUID arenaId : serializableArenas) {
|
||||
arenas.add(arenaId.getRawValue());
|
||||
}
|
||||
return new ParkourArenaGroup(id, name, arenas);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,249 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.util.ArenaStorageHelper;
|
||||
import net.knarcraft.minigames.util.StringSanitizer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A handler that keeps track of all parkour arenas
|
||||
*/
|
||||
public class ParkourArenaHandler {
|
||||
|
||||
private Map<UUID, ParkourArena> arenas = new HashMap<>();
|
||||
private Map<UUID, ParkourArenaGroup> arenaGroups = new HashMap<>();
|
||||
private Map<String, UUID> arenaNameLookup = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Gets all arenas that are within a group
|
||||
*
|
||||
* @return <p>All arenas in a group</p>
|
||||
*/
|
||||
public @NotNull Set<ParkourArena> getArenasInAGroup() {
|
||||
Set<ParkourArena> arenas = new HashSet<>();
|
||||
for (UUID arenaId : arenaGroups.keySet()) {
|
||||
arenas.add(this.arenas.get(arenaId));
|
||||
}
|
||||
return arenas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of all parkour groups
|
||||
*
|
||||
* @return <p>All parkour groups</p>
|
||||
*/
|
||||
public Set<ParkourArenaGroup> getAllGroups() {
|
||||
return new HashSet<>(arenaGroups.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group the given arena belongs to
|
||||
*
|
||||
* @param arenaId <p>The id of the arena to get the group of</p>
|
||||
* @return <p>The group the arena belongs to, or null if not in a group</p>
|
||||
*/
|
||||
public @Nullable ParkourArenaGroup getGroup(@NotNull UUID arenaId) {
|
||||
return this.arenaGroups.get(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the group for the given arena
|
||||
*
|
||||
* @param arenaId <p>The id of the arena to change</p>
|
||||
* @param arenaGroup <p>The group to add the arena to, or null to remove the current group</p>
|
||||
*/
|
||||
public void setGroup(@NotNull UUID arenaId, @Nullable ParkourArenaGroup arenaGroup) {
|
||||
if (arenaGroup == null) {
|
||||
// No need to remove something non-existing
|
||||
if (!this.arenaGroups.containsKey(arenaId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the existing group
|
||||
ParkourArenaGroup oldGroup = this.arenaGroups.remove(arenaId);
|
||||
oldGroup.removeArena(arenaId);
|
||||
} else {
|
||||
// Make sure to remove the arena from the old group's internal tracking
|
||||
if (this.arenaGroups.containsKey(arenaId)) {
|
||||
this.arenaGroups.remove(arenaId).removeArena(arenaId);
|
||||
}
|
||||
|
||||
this.arenaGroups.put(arenaId, arenaGroup);
|
||||
arenaGroup.addArena(arenaId);
|
||||
}
|
||||
saveGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parkour arena group with the given name
|
||||
*
|
||||
* @param groupName <p>The name of the group to get</p>
|
||||
* @return <p>The group, or null if not found</p>
|
||||
*/
|
||||
public @Nullable ParkourArenaGroup getGroup(String groupName) {
|
||||
String sanitized = StringSanitizer.sanitizeArenaName(groupName);
|
||||
for (ParkourArenaGroup arenaGroup : this.arenaGroups.values()) {
|
||||
if (arenaGroup.getGroupNameSanitized().equals(sanitized)) {
|
||||
return arenaGroup;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an arena's lookup name
|
||||
*
|
||||
* @param oldName <p>The arena's old sanitized lookup name</p>
|
||||
* @param newName <p>The arena's new sanitized lookup name</p>
|
||||
*/
|
||||
public void updateLookupName(@NotNull String oldName, @NotNull String newName) {
|
||||
UUID arenaId = this.arenaNameLookup.remove(oldName);
|
||||
if (arenaId != null) {
|
||||
this.arenaNameLookup.put(newName, arenaId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new arena
|
||||
*
|
||||
* @param arena <p>The arena to add</p>
|
||||
*/
|
||||
public void addArena(@NotNull ParkourArena 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 <p>The id of the arena to get</p>
|
||||
* @return <p>The arena, or null if no arena could be found</p>
|
||||
*/
|
||||
public @Nullable ParkourArena getArena(@NotNull UUID arenaId) {
|
||||
return this.arenas.get(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arena with the given name
|
||||
*
|
||||
* @param arenaName <p>The arena to get</p>
|
||||
* @return <p>The arena with the given name, or null if not found</p>
|
||||
*/
|
||||
public @Nullable ParkourArena getArena(@NotNull String arenaName) {
|
||||
return this.arenas.get(this.arenaNameLookup.get(StringSanitizer.sanitizeArenaName(arenaName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all known arenas
|
||||
*
|
||||
* @return <p>All known arenas</p>
|
||||
*/
|
||||
public @NotNull Map<UUID, ParkourArena> getArenas() {
|
||||
return new HashMap<>(this.arenas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given arena
|
||||
*
|
||||
* @param arena <p>The arena to remove</p>
|
||||
*/
|
||||
public void removeArena(@NotNull ParkourArena arena) {
|
||||
UUID arenaId = arena.getArenaId();
|
||||
MiniGames.getInstance().getParkourArenaPlayerRegistry().removeForArena(arena);
|
||||
this.arenas.remove(arenaId);
|
||||
this.arenaNameLookup.remove(arena.getArenaNameSanitized());
|
||||
this.arenaGroups.remove(arenaId);
|
||||
if (!ArenaStorageHelper.removeParkourArenaData(arenaId)) {
|
||||
MiniGames.log(Level.WARNING, "Unable to remove parkour arena data file " + arenaId + ".yml. " +
|
||||
"You must remove it manually!");
|
||||
}
|
||||
this.saveArenas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the data for the given arena
|
||||
*
|
||||
* @param arenaId <p>The id of the arena whose data should be saved</p>
|
||||
*/
|
||||
public void saveData(UUID arenaId) {
|
||||
try {
|
||||
ArenaStorageHelper.saveParkourArenaData(this.arenas.get(arenaId).getData());
|
||||
} catch (IOException e) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to save arena data! Data loss can occur!");
|
||||
MiniGames.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all current parkour groups to disk
|
||||
*/
|
||||
public void saveGroups() {
|
||||
try {
|
||||
ArenaStorageHelper.saveParkourArenaGroups(new HashSet<>(this.arenaGroups.values()));
|
||||
} catch (IOException e) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to save current arena groups! " +
|
||||
"Data loss can occur!");
|
||||
MiniGames.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all arenas and groups from disk
|
||||
*/
|
||||
public void load() {
|
||||
loadArenas();
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all parkour groups from disk
|
||||
*/
|
||||
private void loadGroups() {
|
||||
Set<ParkourArenaGroup> arenaGroups = ArenaStorageHelper.loadParkourArenaGroups();
|
||||
Map<UUID, ParkourArenaGroup> arenaGroupMap = new HashMap<>();
|
||||
for (ParkourArenaGroup arenaGroup : arenaGroups) {
|
||||
for (UUID arenaId : arenaGroup.getArenas()) {
|
||||
arenaGroupMap.put(arenaId, arenaGroup);
|
||||
}
|
||||
}
|
||||
this.arenaGroups = arenaGroupMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all current arenas to disk
|
||||
*/
|
||||
public void saveArenas() {
|
||||
try {
|
||||
ArenaStorageHelper.saveParkourArenas(this.arenas);
|
||||
} catch (IOException e) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to save current arenas! " +
|
||||
"Data loss can occur!");
|
||||
MiniGames.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all arenas from disk
|
||||
*/
|
||||
private void loadArenas() {
|
||||
this.arenas = ArenaStorageHelper.loadParkourArenas();
|
||||
|
||||
// Save a map from arena name to arena id for improved performance
|
||||
this.arenaNameLookup = new HashMap<>();
|
||||
for (Map.Entry<UUID, ParkourArena> arena : this.arenas.entrySet()) {
|
||||
String sanitizedName = arena.getValue().getArenaNameSanitized();
|
||||
this.arenaNameLookup.put(sanitizedName, arena.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A registry to keep track of which players are playing in which arenas
|
||||
*/
|
||||
public class ParkourArenaPlayerRegistry {
|
||||
|
||||
private final Map<UUID, ParkourArenaSession> arenaPlayers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Registers that the given player has started playing the given parkour arena session
|
||||
*
|
||||
* @param playerId <p>The id of the player that started playing</p>
|
||||
* @param arena <p>The arena session to register</p>
|
||||
*/
|
||||
public void registerPlayer(@NotNull UUID playerId, @NotNull ParkourArenaSession arena) {
|
||||
this.arenaPlayers.put(playerId, arena);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this player from players currently playing
|
||||
*
|
||||
* @param playerId <p>The id of the player to remove</p>
|
||||
*/
|
||||
public boolean removePlayer(@NotNull UUID playerId) {
|
||||
return this.arenaPlayers.remove(playerId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player's active parkour arena session
|
||||
*
|
||||
* @param playerId <p>The id of the player to get arena for</p>
|
||||
* @return <p>The player's active arena session, or null if not currently playing</p>
|
||||
*/
|
||||
public @Nullable ParkourArenaSession getArenaSession(@NotNull UUID playerId) {
|
||||
return this.arenaPlayers.getOrDefault(playerId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all active sessions for the given arena
|
||||
*
|
||||
* @param arena <p>The arena to remove sessions for</p>
|
||||
*/
|
||||
public void removeForArena(ParkourArena arena) {
|
||||
for (Map.Entry<UUID, ParkourArenaSession> entry : this.arenaPlayers.entrySet()) {
|
||||
if (entry.getValue().getArena() == arena) {
|
||||
// Kick the player gracefully
|
||||
entry.getValue().triggerQuit(false);
|
||||
this.arenaPlayers.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.arena.record.IntegerRecord;
|
||||
import net.knarcraft.minigames.arena.record.LongRecord;
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A registry keeping track of all records
|
||||
*/
|
||||
public class ParkourArenaRecordsRegistry extends ArenaRecordsRegistry {
|
||||
|
||||
/**
|
||||
* Instantiates a new empty records registry
|
||||
*/
|
||||
public ParkourArenaRecordsRegistry(@NotNull UUID arenaId) {
|
||||
super(arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new records registry
|
||||
*
|
||||
* @param leastDeaths <p>The existing least death records to use</p>
|
||||
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
|
||||
*/
|
||||
private ParkourArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
|
||||
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
|
||||
super(arenaId, leastDeaths, shortestTimeMilliSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves changed records
|
||||
*/
|
||||
@Override
|
||||
protected void save() {
|
||||
MiniGames.getInstance().getParkourArenaHandler().saveData(this.arenaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the given data
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized records registry</p>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static ParkourArenaRecordsRegistry deserialize(Map<String, Object> data) {
|
||||
UUID arenaId = ((SerializableUUID) data.get("arenaId")).getRawValue();
|
||||
Set<IntegerRecord> leastDeaths =
|
||||
(Set<IntegerRecord>) data.getOrDefault("leastDeaths", new HashMap<>());
|
||||
Set<LongRecord> shortestTimeMilliseconds =
|
||||
(Set<LongRecord>) data.getOrDefault("shortestTime", new HashMap<>());
|
||||
|
||||
leastDeaths.removeIf(Objects::isNull);
|
||||
shortestTimeMilliseconds.removeIf(Objects::isNull);
|
||||
return new ParkourArenaRecordsRegistry(arenaId, leastDeaths, shortestTimeMilliseconds);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,194 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import net.knarcraft.minigames.MiniGames;
|
||||
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
|
||||
import net.knarcraft.minigames.config.ParkourConfiguration;
|
||||
import net.knarcraft.minigames.property.RecordResult;
|
||||
import net.knarcraft.minigames.util.PlayerTeleporter;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* A representation of a player's current session in a parkour arena
|
||||
*/
|
||||
public class ParkourArenaSession {
|
||||
|
||||
private final @NotNull ParkourArena arena;
|
||||
private final @NotNull Player player;
|
||||
private final @NotNull ParkourArenaGameMode gameMode;
|
||||
private int deaths;
|
||||
private final long startTime;
|
||||
private final ParkourPlayerEntryState entryState;
|
||||
|
||||
/**
|
||||
* Instantiates a new parkour arena session
|
||||
*
|
||||
* @param parkourArena <p>The arena that's being played in</p>
|
||||
* @param player <p>The player playing the arena</p>
|
||||
* @param gameMode <p>The game-mode</p>
|
||||
*/
|
||||
public ParkourArenaSession(@NotNull ParkourArena parkourArena, @NotNull Player player,
|
||||
@NotNull ParkourArenaGameMode gameMode) {
|
||||
this.arena = parkourArena;
|
||||
this.player = player;
|
||||
this.gameMode = gameMode;
|
||||
this.deaths = 0;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
ParkourConfiguration configuration = MiniGames.getInstance().getParkourConfiguration();
|
||||
boolean makeInvisible = configuration.makePlayersInvisible();
|
||||
this.entryState = new ParkourPlayerEntryState(player, makeInvisible);
|
||||
// Make the player fly to improve mobility in the air
|
||||
this.entryState.setArenaState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state of the player when they joined the session
|
||||
*
|
||||
* @return <p>The player's entry state</p>
|
||||
*/
|
||||
public @NotNull ParkourPlayerEntryState getEntryState() {
|
||||
return this.entryState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a win for the player playing in this session
|
||||
*/
|
||||
public void triggerWin() {
|
||||
// Stop this session
|
||||
stopSession();
|
||||
|
||||
// Check for, and display, records
|
||||
MiniGames miniGames = MiniGames.getInstance();
|
||||
boolean ignore = miniGames.getDropperConfiguration().ignoreRecordsUntilGroupBeatenOnce();
|
||||
ParkourArenaGroup group = miniGames.getParkourArenaHandler().getGroup(this.arena.getArenaId());
|
||||
if (!ignore || group == null || group.hasBeatenAll(this.gameMode, this.player)) {
|
||||
registerRecord();
|
||||
}
|
||||
|
||||
// Mark the arena as cleared
|
||||
if (this.arena.getData().setCompleted(this.gameMode, this.player)) {
|
||||
this.player.sendMessage("You cleared the arena!");
|
||||
}
|
||||
this.player.sendMessage("You won!");
|
||||
|
||||
// Teleport the player out of the arena
|
||||
teleportToExit(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the playing player out of the arena
|
||||
*/
|
||||
private void teleportToExit(boolean immediately) {
|
||||
// Teleport the player out of the arena
|
||||
Location exitLocation;
|
||||
if (this.arena.getExitLocation() != null) {
|
||||
exitLocation = this.arena.getExitLocation();
|
||||
} else {
|
||||
exitLocation = this.entryState.getEntryLocation();
|
||||
}
|
||||
PlayerTeleporter.teleportPlayer(this.player, exitLocation, true, immediately);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this session from current sessions
|
||||
*/
|
||||
private void removeSession() {
|
||||
// Remove this session for game sessions to stop listeners from fiddling more with the player
|
||||
boolean removedSession = MiniGames.getInstance().getDropperArenaPlayerRegistry().removePlayer(player.getUniqueId());
|
||||
if (!removedSession) {
|
||||
MiniGames.log(Level.SEVERE, "Unable to remove dropper arena session for " + player.getName() + ". " +
|
||||
"This will have unintended consequences.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the player's record if necessary, and prints record information to the player
|
||||
*/
|
||||
private void registerRecord() {
|
||||
ArenaRecordsRegistry recordsRegistry = this.arena.getData().getRecordRegistries().get(this.gameMode);
|
||||
long timeElapsed = System.currentTimeMillis() - this.startTime;
|
||||
announceRecord(recordsRegistry.registerTimeRecord(this.player.getUniqueId(), timeElapsed), "time");
|
||||
announceRecord(recordsRegistry.registerDeathRecord(this.player.getUniqueId(), this.deaths), "least deaths");
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces a record set by this player
|
||||
*
|
||||
* @param recordResult <p>The result of the record</p>
|
||||
* @param type <p>The type of record set (time or deaths)</p>
|
||||
*/
|
||||
private void announceRecord(@NotNull RecordResult recordResult, @NotNull String type) {
|
||||
if (recordResult == RecordResult.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets a string representation of the played game-mode
|
||||
String gameModeString = switch (this.gameMode) {
|
||||
case DEFAULT -> "default";
|
||||
};
|
||||
|
||||
String recordString = "You just set a %s on the %s game-mode!";
|
||||
recordString = switch (recordResult) {
|
||||
case WORLD_RECORD -> String.format(recordString, "new %s record", gameModeString);
|
||||
case PERSONAL_BEST -> String.format(recordString, "personal %s record", gameModeString);
|
||||
default -> throw new IllegalStateException("Unexpected value: " + recordResult);
|
||||
};
|
||||
player.sendMessage(String.format(recordString, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a loss for the player playing in this session
|
||||
*/
|
||||
public void triggerLoss() {
|
||||
this.deaths++;
|
||||
//Teleport the player back to the top
|
||||
PlayerTeleporter.teleportPlayer(this.player, this.arena.getSpawnLocation(), true, false);
|
||||
this.entryState.setArenaState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a quit for the player playing in this session
|
||||
*/
|
||||
public void triggerQuit(boolean immediately) {
|
||||
// Stop this session
|
||||
stopSession();
|
||||
// Teleport the player out of the arena
|
||||
teleportToExit(immediately);
|
||||
|
||||
player.sendMessage("You quit the arena!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this session, and disables flight mode
|
||||
*/
|
||||
private void stopSession() {
|
||||
// Remove this session from game sessions to stop listeners from fiddling more with the player
|
||||
removeSession();
|
||||
|
||||
// Remove flight mode
|
||||
entryState.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arena this session is being played in
|
||||
*
|
||||
* @return <p>The session's arena</p>
|
||||
*/
|
||||
public @NotNull ParkourArena getArena() {
|
||||
return this.arena;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player playing in this session
|
||||
*
|
||||
* @return <p>This session's player</p>
|
||||
*/
|
||||
public @NotNull Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A representation of each key used for storing arena data
|
||||
*/
|
||||
public enum ParkourArenaStorageKey {
|
||||
|
||||
/**
|
||||
* The key for an arena's id
|
||||
*/
|
||||
ID("arenaId"),
|
||||
|
||||
/**
|
||||
* The key for an arena's name
|
||||
*/
|
||||
NAME("arenaName"),
|
||||
|
||||
/**
|
||||
* The key for an arena's spawn location
|
||||
*/
|
||||
SPAWN_LOCATION("arenaSpawnLocation"),
|
||||
|
||||
/**
|
||||
* The key for an arena's exit location
|
||||
*/
|
||||
EXIT_LOCATION("arenaExitLocation"),
|
||||
|
||||
/**
|
||||
* The key for the type of this arena's win block
|
||||
*/
|
||||
WIN_BLOCK_TYPE("winBlockType"),
|
||||
|
||||
/**
|
||||
* The key for this arena's win location (overrides win block type)
|
||||
*/
|
||||
WIN_LOCATION("winLocation"),
|
||||
|
||||
/**
|
||||
* The key for this arena's kill plane blocks (overrides the config)
|
||||
*/
|
||||
KILL_PLANE_BLOCKS("killPlaneBlocks"),
|
||||
|
||||
/**
|
||||
* The key for this arena's checkpoint locations
|
||||
*/
|
||||
CHECKPOINTS("checkpoints"),
|
||||
|
||||
/**
|
||||
* The hey for this arena's data
|
||||
*/
|
||||
DATA("arenaData"),
|
||||
;
|
||||
|
||||
private final @NotNull String key;
|
||||
|
||||
/**
|
||||
* Instantiates a new arena storage key
|
||||
*
|
||||
* @param key <p>The string path of the configuration key this value represents.</p>
|
||||
*/
|
||||
ParkourArenaStorageKey(@NotNull String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration key this enum represents
|
||||
*
|
||||
* @return <p>The string key representation.</p>
|
||||
*/
|
||||
public @NotNull String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package net.knarcraft.minigames.arena.parkour;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The state of a player before entering a dropper arena
|
||||
*/
|
||||
public class ParkourPlayerEntryState {
|
||||
|
||||
private final Player player;
|
||||
private final Location entryLocation;
|
||||
private final boolean originalIsFlying;
|
||||
private final GameMode originalGameMode;
|
||||
private final boolean originalAllowFlight;
|
||||
private final boolean originalInvulnerable;
|
||||
private final boolean originalIsSwimming;
|
||||
private final boolean originalCollideAble;
|
||||
private final boolean makePlayerInvisible;
|
||||
|
||||
/**
|
||||
* Instantiates a new player state
|
||||
*
|
||||
* @param player <p>The player whose state should be stored</p>
|
||||
*/
|
||||
public ParkourPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) {
|
||||
this.player = player;
|
||||
this.entryLocation = player.getLocation().clone();
|
||||
this.originalIsFlying = player.isFlying();
|
||||
this.originalGameMode = player.getGameMode();
|
||||
this.originalAllowFlight = player.getAllowFlight();
|
||||
this.originalInvulnerable = player.isInvulnerable();
|
||||
this.originalIsSwimming = player.isSwimming();
|
||||
this.originalCollideAble = player.isCollidable();
|
||||
this.makePlayerInvisible = makePlayerInvisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the stored player to the state used by arenas
|
||||
*/
|
||||
public void setArenaState() {
|
||||
this.player.setAllowFlight(false);
|
||||
this.player.setFlying(false);
|
||||
this.player.setGameMode(GameMode.ADVENTURE);
|
||||
this.player.setSwimming(false);
|
||||
this.player.setCollidable(false);
|
||||
if (this.makePlayerInvisible) {
|
||||
this.player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,
|
||||
PotionEffect.INFINITE_DURATION, 3));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the stored state for the stored player
|
||||
*/
|
||||
public void restore() {
|
||||
this.player.setFlying(this.originalIsFlying);
|
||||
this.player.setGameMode(this.originalGameMode);
|
||||
this.player.setAllowFlight(this.originalAllowFlight);
|
||||
this.player.setInvulnerable(this.originalInvulnerable);
|
||||
this.player.setSwimming(this.originalIsSwimming);
|
||||
this.player.setCollidable(this.originalCollideAble);
|
||||
if (this.makePlayerInvisible) {
|
||||
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location the player entered from
|
||||
*
|
||||
* @return <p>The location the player entered from</p>
|
||||
*/
|
||||
public Location getEntryLocation() {
|
||||
return this.entryLocation;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package net.knarcraft.minigames.arena.record;
|
||||
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A record stored for an arena
|
||||
*/
|
||||
public abstract class ArenaRecord<K extends Comparable<K>> implements Comparable<ArenaRecord<K>>, ConfigurationSerializable {
|
||||
|
||||
private final UUID userId;
|
||||
private final K record;
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public ArenaRecord(UUID userId, K record) {
|
||||
this.userId = userId;
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the user this record belongs to
|
||||
*
|
||||
* @return <p>The record's achiever</p>
|
||||
*/
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the stored record
|
||||
*
|
||||
* @return <p>The record value</p>
|
||||
*/
|
||||
public K getRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof ArenaRecord<?> && userId.equals(((ArenaRecord<?>) other).userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ArenaRecord<K> other) {
|
||||
return record.compareTo(other.record);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("userId", new SerializableUUID(getUserId()));
|
||||
data.put("record", record);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(userId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return userId + ": " + record;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package net.knarcraft.minigames.arena.record;
|
||||
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A record storing an integer
|
||||
*/
|
||||
public class IntegerRecord extends SummableArenaRecord<Integer> {
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public IntegerRecord(UUID userId, Integer record) {
|
||||
super(userId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SummableArenaRecord<Integer> sum(Integer value) {
|
||||
return new IntegerRecord(this.getUserId(), this.getRecord() + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof IntegerRecord && this.getUserId().equals(((IntegerRecord) other).getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the saved arena record
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized data</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static IntegerRecord deserialize(@NotNull Map<String, Object> data) {
|
||||
return new IntegerRecord(((SerializableUUID) data.get("userId")).getRawValue(), (Integer) data.get("record"));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package net.knarcraft.minigames.arena.record;
|
||||
|
||||
import net.knarcraft.minigames.container.SerializableUUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A record storing a Long
|
||||
*/
|
||||
public class LongRecord extends SummableArenaRecord<Long> {
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public LongRecord(UUID userId, Long record) {
|
||||
super(userId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof LongRecord && this.getUserId().equals(((LongRecord) other).getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SummableArenaRecord<Long> sum(Long value) {
|
||||
return new LongRecord(this.getUserId(), this.getRecord() + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the saved arena record
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized data</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static LongRecord deserialize(@NotNull Map<String, Object> data) {
|
||||
return new LongRecord(((SerializableUUID) data.get("userId")).getRawValue(), ((Number) data.get("record")).longValue());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package net.knarcraft.minigames.arena.record;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A type of arena record which can be summed together
|
||||
*
|
||||
* @param <K> <p>The type of the stored value</p>
|
||||
*/
|
||||
public abstract class SummableArenaRecord<K extends Comparable<K>> extends ArenaRecord<K> {
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public SummableArenaRecord(UUID userId, K record) {
|
||||
super(userId, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summable record with the resulting sum
|
||||
*
|
||||
* @param value <p>The value to add to the existing value</p>
|
||||
* @return <p>A record with the sum of this record and the given value</p>
|
||||
*/
|
||||
public abstract SummableArenaRecord<K> sum(K value);
|
||||
|
||||
}
|
Reference in New Issue
Block a user