Implements persistent storage of records

This commit is contained in:
Kristian Knarvik 2023-03-26 17:42:55 +02:00
parent 49eb0ac82c
commit 592f53ec9e
9 changed files with 199 additions and 30 deletions

View File

@ -2,6 +2,7 @@ package net.knarcraft.dropper;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.command.CreateArenaCommand;
import net.knarcraft.dropper.command.EditArenaCommand;
import net.knarcraft.dropper.command.EditArenaTabCompleter;
@ -11,12 +12,15 @@ import net.knarcraft.dropper.command.LeaveArenaCommand;
import net.knarcraft.dropper.command.ListArenaCommand;
import net.knarcraft.dropper.command.RemoveArenaCommand;
import net.knarcraft.dropper.command.RemoveArenaTabCompleter;
import net.knarcraft.dropper.container.SerializableMaterial;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.listener.DamageListener;
import net.knarcraft.dropper.listener.MoveListener;
import net.knarcraft.dropper.listener.PlayerLeaveListener;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
@ -61,6 +65,15 @@ public final class Dropper extends JavaPlugin {
return this.playerRegistry;
}
@Override
public void onLoad() {
super.onLoad();
// Register serialization classes
ConfigurationSerialization.registerClass(SerializableMaterial.class);
ConfigurationSerialization.registerClass(DropperArenaRecordsRegistry.class);
ConfigurationSerialization.registerClass(SerializableUUID.class);
}
@Override
public void onEnable() {
// Plugin startup logic

View File

@ -1,20 +1,23 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.RecordResult;
import org.bukkit.entity.Player;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
/**
* A registry keeping track of all records
*/
public class DropperArenaRecordsRegistry {
public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
private final Map<Player, Integer> leastDeaths;
private final Map<Player, Long> shortestTimeMilliSeconds;
private final Map<SerializableUUID, Integer> leastDeaths;
private final Map<SerializableUUID, Long> shortestTimeMilliSeconds;
/**
* Instantiates a new empty records registry
@ -30,8 +33,8 @@ public class DropperArenaRecordsRegistry {
* @param leastDeaths <p>The existing least death records to use</p>
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
*/
public DropperArenaRecordsRegistry(@NotNull Map<Player, Integer> leastDeaths,
@NotNull Map<Player, Long> shortestTimeMilliSeconds) {
public DropperArenaRecordsRegistry(@NotNull Map<SerializableUUID, Integer> leastDeaths,
@NotNull Map<SerializableUUID, Long> shortestTimeMilliSeconds) {
this.leastDeaths = new HashMap<>(leastDeaths);
this.shortestTimeMilliSeconds = new HashMap<>(shortestTimeMilliSeconds);
}
@ -41,7 +44,7 @@ public class DropperArenaRecordsRegistry {
*
* @return <p>Existing death records</p>
*/
public Map<Player, Integer> getLeastDeathsRecords() {
public Map<SerializableUUID, Integer> getLeastDeathsRecords() {
return new HashMap<>(this.leastDeaths);
}
@ -50,29 +53,32 @@ public class DropperArenaRecordsRegistry {
*
* @return <p>Existing time records</p>
*/
public Map<Player, Long> getShortestTimeMilliSecondsRecords() {
public Map<SerializableUUID, Long> getShortestTimeMilliSecondsRecords() {
return new HashMap<>(this.shortestTimeMilliSeconds);
}
/**
* Registers a new deaths-record
*
* @param player <p>The player that performed the records</p>
* @param deaths <p>The number of deaths suffered before the player finished the arena</p>
* @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 Player player, int deaths) {
public @NotNull RecordResult registerDeathRecord(@NotNull UUID playerId, int deaths) {
RecordResult result;
Stream<Map.Entry<Player, Integer>> records = leastDeaths.entrySet().stream();
Stream<Map.Entry<SerializableUUID, Integer>> records = leastDeaths.entrySet().stream();
SerializableUUID serializableUUID = new SerializableUUID(playerId);
if (records.allMatch((entry) -> deaths < entry.getValue())) {
//If the given value is less than all other values, that's a world record!
result = RecordResult.WORLD_RECORD;
leastDeaths.put(player, deaths);
} else if (leastDeaths.containsKey(player) && deaths < leastDeaths.get(player)) {
leastDeaths.put(serializableUUID, deaths);
save();
} else if (leastDeaths.containsKey(serializableUUID) && deaths < leastDeaths.get(serializableUUID)) {
//If the given value is less than the player's previous value, that's a personal best!
result = RecordResult.PERSONAL_BEST;
leastDeaths.put(player, deaths);
leastDeaths.put(serializableUUID, deaths);
save();
} else {
result = RecordResult.NONE;
}
@ -83,22 +89,26 @@ public class DropperArenaRecordsRegistry {
/**
* Registers a new time-record
*
* @param player <p>The player that performed the records</p>
* @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 Player player, long milliseconds) {
public @NotNull RecordResult registerTimeRecord(@NotNull UUID playerId, long milliseconds) {
RecordResult result;
Stream<Map.Entry<Player, Long>> records = shortestTimeMilliSeconds.entrySet().stream();
Stream<Map.Entry<SerializableUUID, Long>> records = shortestTimeMilliSeconds.entrySet().stream();
SerializableUUID serializableUUID = new SerializableUUID(playerId);
if (records.allMatch((entry) -> milliseconds < entry.getValue())) {
//If the given value is less than all other values, that's a world record!
result = RecordResult.WORLD_RECORD;
shortestTimeMilliSeconds.put(player, milliseconds);
} else if (shortestTimeMilliSeconds.containsKey(player) && milliseconds < shortestTimeMilliSeconds.get(player)) {
shortestTimeMilliSeconds.put(serializableUUID, milliseconds);
save();
} else if (shortestTimeMilliSeconds.containsKey(serializableUUID) &&
milliseconds < shortestTimeMilliSeconds.get(serializableUUID)) {
//If the given value is less than the player's previous value, that's a personal best!
result = RecordResult.PERSONAL_BEST;
shortestTimeMilliSeconds.put(player, milliseconds);
shortestTimeMilliSeconds.put(serializableUUID, milliseconds);
save();
} else {
result = RecordResult.NONE;
}
@ -106,4 +116,36 @@ public class DropperArenaRecordsRegistry {
return result;
}
/**
* Saves changed records
*/
private void save() {
Dropper.getInstance().getArenaHandler().saveArenas();
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("leastDeaths", this.leastDeaths);
data.put("shortestTime", this.shortestTimeMilliSeconds);
return data;
}
/**
* 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) {
Map<SerializableUUID, Integer> leastDeathsData =
(Map<SerializableUUID, Integer>) data.getOrDefault("leastDeaths", new HashMap<>());
Map<SerializableUUID, Long> shortestTimeMillisecondsData =
(Map<SerializableUUID, Long>) data.getOrDefault("shortestTime", new HashMap<>());
return new DropperArenaRecordsRegistry(leastDeathsData, shortestTimeMillisecondsData);
}
}

View File

@ -105,9 +105,9 @@ public class DropperArenaSession {
private void registerRecord() {
DropperArenaRecordsRegistry recordsRegistry = this.arena.getRecordsRegistry();
RecordResult recordResult = switch (this.gameMode) {
case LEAST_TIME -> recordsRegistry.registerTimeRecord(this.player,
case LEAST_TIME -> recordsRegistry.registerTimeRecord(this.player.getUniqueId(),
System.currentTimeMillis() - this.startTime);
case LEAST_DEATHS -> recordsRegistry.registerDeathRecord(this.player, this.deaths);
case LEAST_DEATHS -> recordsRegistry.registerDeathRecord(this.player.getUniqueId(), this.deaths);
case DEFAULT -> RecordResult.NONE;
};
switch (recordResult) {

View File

@ -32,9 +32,10 @@ public class CreateArenaCommand implements CommandExecutor {
return false;
}
//TODO: Make sure the arena name doesn't contain any unwanted characters
// Remove known characters that are likely to cause trouble if used in an arena name
String arenaName = arguments[0].replaceAll("[§ :=&]", "");
DropperArena arena = new DropperArena(arguments[0], player.getLocation());
DropperArena arena = new DropperArena(arenaName, player.getLocation());
Dropper.getInstance().getArenaHandler().addArena(arena);
commandSender.sendMessage("The arena was successfully created!");
return true;

View File

@ -0,0 +1,37 @@
package net.knarcraft.dropper.container;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* A material container able to be serialized
*
* @param material <p>The material stored by this record</p>
*/
public record SerializableMaterial(Material material) implements ConfigurationSerializable {
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("name", material.name());
return data;
}
/**
* Deserializes a serialized material
*
* @param data <p>The serialized data</p>
* @return <p>The deserialized material</p>
*/
@SuppressWarnings("unused")
public static SerializableMaterial deserialize(Map<String, Object> data) {
Material material = Material.matchMaterial((String) data.getOrDefault("name", "AIR"));
return new SerializableMaterial(material);
}
}

View File

@ -0,0 +1,50 @@
package net.knarcraft.dropper.container;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* A UUID container able to be serialized
*
* @param uuid <p>The UUID stored by this record</p>
*/
public record SerializableUUID(UUID uuid) implements ConfigurationSerializable {
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("id", uuid.toString());
return data;
}
/**
* Deserializes a serialized UUID
*
* @param data <p>The serialized data</p>
* @return <p>The deserialized UUID</p>
*/
@SuppressWarnings("unused")
public static SerializableUUID deserialize(Map<String, Object> data) {
String id = (String) data.getOrDefault("id", null);
if (id != null) {
return new SerializableUUID(UUID.fromString(id));
} else {
return null;
}
}
@Override
public boolean equals(Object object) {
if (object instanceof SerializableUUID) {
return this.uuid.equals(((SerializableUUID) object).uuid);
} else {
return false;
}
}
}

View File

@ -41,6 +41,11 @@ public enum ArenaStorageKey {
* The key for the type of this arena's win block
*/
WIN_BLOCK_TYPE("winBlockType"),
/**
* The hey for this arena's records
*/
RECORDS("records"),
;
private final @NotNull String key;

View File

@ -3,6 +3,7 @@ package net.knarcraft.dropper.util;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.container.SerializableMaterial;
import net.knarcraft.dropper.property.ArenaStorageKey;
import org.bukkit.Location;
import org.bukkit.Material;
@ -49,7 +50,8 @@ public final class ArenaStorageHelper {
configSection.set(ArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey(), arena.getPlayerVerticalVelocity());
configSection.set(ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey(), arena.getPlayerHorizontalVelocity());
configSection.set(ArenaStorageKey.STAGE.getKey(), arena.getStage());
configSection.set(ArenaStorageKey.WIN_BLOCK_TYPE.getKey(), arena.getWinBlockType());
configSection.set(ArenaStorageKey.WIN_BLOCK_TYPE.getKey(), new SerializableMaterial(arena.getWinBlockType()));
configSection.set(ArenaStorageKey.RECORDS.getKey(), arena.getRecordsRegistry());
}
//TODO: Save records belonging to the arena
configuration.save(arenaFile);
@ -100,18 +102,25 @@ public final class ArenaStorageHelper {
double verticalVelocity = configurationSection.getDouble(ArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey());
double horizontalVelocity = configurationSection.getDouble(ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey());
Integer stage = (Integer) configurationSection.get(ArenaStorageKey.STAGE.getKey());
Material winBlockType = (Material) configurationSection.get(ArenaStorageKey.WIN_BLOCK_TYPE.getKey());
SerializableMaterial winBlockType = (SerializableMaterial) configurationSection.get(
ArenaStorageKey.WIN_BLOCK_TYPE.getKey());
DropperArenaRecordsRegistry recordsRegistry = (DropperArenaRecordsRegistry) configurationSection.get(
ArenaStorageKey.RECORDS.getKey());
if (arenaName == null || spawnLocation == null) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Could not load the arena at configuration " +
"section " + configurationSection.getName() + ". Please check the arenas storage file for issues.");
return null;
}
if (winBlockType == null) {
winBlockType = Material.WATER;
winBlockType = new SerializableMaterial(Material.WATER);
}
//TODO: Load records for this arena
if (recordsRegistry == null) {
recordsRegistry = new DropperArenaRecordsRegistry();
}
return new DropperArena(arenaName, spawnLocation, exitLocation, verticalVelocity, horizontalVelocity, stage,
winBlockType, new DropperArenaRecordsRegistry());
winBlockType.material(), recordsRegistry);
}
/**

View File

@ -6,10 +6,14 @@ description: A plugin for dropper mini-games
commands:
dropperlist:
aliases:
- dlist
permission: dropper.join
usage: /<command>
description: Used to list all current dropper arenas
dropperjoin:
aliases:
- djoin
permission: dropper.join
usage: |
/<command> <arena> [mode]
@ -18,18 +22,26 @@ commands:
time = A shortest-time competitive game-mode
description: Used to join a dropper arena
dropperleave:
aliases:
- dleave
permission: dropper.join
usage: /<command>
description: Used to leave the current dropper arena
droppercreate:
aliases:
- dcreate
permission: dropper.create
usage: /<command> (Details not finalized)
description: Used to create a new dropper arena
dropperedit:
aliases:
- dedit
permission: dropper.edit
usage: /<command> (Details not finalized)
description: Used to edit an existing dropper arena
dropperremove:
aliases:
- dremove
permission: dropper.remove
usage: /<command> <arena>
description: Used to remove an existing dropper arena