Implements #13 and #14

Removes deaths and time as separate game-modes, and instead always tracks records.
Adds two proper game-modes. One inverts the player's controls, and the other randomly inverts the player's controls every 7 seconds.
Saves cleared status and records for each game-mode separately
Only allows 0-1 for the horizontal velocity
Requires arenas in arena groups to be cleared in sequence on all game-modes, not just the default one.
This commit is contained in:
2023-03-30 14:59:44 +02:00
parent 572bb980c1
commit f9008ca050
14 changed files with 258 additions and 108 deletions

View File

@ -22,6 +22,7 @@ import net.knarcraft.dropper.listener.CommandListener;
import net.knarcraft.dropper.listener.DamageListener;
import net.knarcraft.dropper.listener.MoveListener;
import net.knarcraft.dropper.listener.PlayerLeaveListener;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
@ -89,6 +90,7 @@ public final class Dropper extends JavaPlugin {
ConfigurationSerialization.registerClass(SerializableUUID.class);
ConfigurationSerialization.registerClass(DropperArenaData.class);
ConfigurationSerialization.registerClass(DropperArenaGroup.class);
ConfigurationSerialization.registerClass(ArenaGameMode.class);
}
@Override

View File

@ -1,6 +1,7 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import org.bukkit.Location;
import org.bukkit.Material;
@ -8,7 +9,8 @@ import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
@ -106,8 +108,13 @@ public class DropperArena {
this.exitLocation = null;
this.playerVerticalVelocity = 3.92;
this.playerHorizontalVelocity = 1;
this.dropperArenaData = new DropperArenaData(this.arenaId, new DropperArenaRecordsRegistry(this.arenaId),
new HashSet<>());
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries = new HashMap<>();
for (ArenaGameMode arenaGameMode : ArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new DropperArenaRecordsRegistry(this.arenaId));
}
this.dropperArenaData = new DropperArenaData(this.arenaId, recordRegistries, new HashMap<>());
this.winBlockType = Material.WATER;
}

View File

@ -2,6 +2,7 @@ package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@ -16,34 +17,37 @@ import java.util.UUID;
* Data stored for an arena
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordsRegistry <p>The records belonging to the arena</p>
* @param recordRegistries <p>The records belonging to the arena</p>
* @param playersCompleted <p>A list of all player that have completed this arena</p>
*/
public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecordsRegistry recordsRegistry,
@NotNull Set<UUID> playersCompleted) implements ConfigurationSerializable {
public record DropperArenaData(@NotNull UUID arenaId,
@NotNull Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) implements ConfigurationSerializable {
/**
* Instantiates a new dropper arena data object
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordsRegistry <p>The registry of this arena's records</p>
* @param playersCompleted <p>The set of ids for players that have cleared this data's arena</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 DropperArenaRecordsRegistry recordsRegistry,
@NotNull Set<UUID> playersCompleted) {
public DropperArenaData(@NotNull UUID arenaId,
@NotNull Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
this.arenaId = arenaId;
this.recordsRegistry = recordsRegistry;
this.playersCompleted = new HashSet<>(playersCompleted);
this.recordRegistries = recordRegistries;
this.playersCompleted = new HashMap<>(playersCompleted);
}
/**
* Gets whether the given player has cleared this arena
*
* @param player <p>The player to check</p>
* @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 Player player) {
return !this.playersCompleted.contains(player.getUniqueId());
public boolean hasNotCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
return !this.playersCompleted.getOrDefault(arenaGameMode, new HashSet<>()).contains(player.getUniqueId());
}
/**
@ -51,8 +55,13 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor
*
* @param player <p>The player that completed this data's arena</p>
*/
public boolean addCompleted(@NotNull Player player) {
boolean added = this.playersCompleted.add(player.getUniqueId());
public boolean addCompleted(@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) {
Dropper.getInstance().getArenaHandler().saveData(this.arenaId);
@ -65,13 +74,18 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("arenaId", new SerializableUUID(this.arenaId));
data.put("recordsRegistry", this.recordsRegistry);
data.put("recordsRegistry", this.recordRegistries);
Set<SerializableUUID> playersCompleted = new HashSet<>();
for (UUID playerCompleted : this.playersCompleted) {
playersCompleted.add(new SerializableUUID(playerCompleted));
// Convert normal UUIDs to serializable UUIDs
Map<ArenaGameMode, Set<SerializableUUID>> serializablePlayersCompleted = new HashMap<>();
for (ArenaGameMode arenaGameMode : this.playersCompleted.keySet()) {
Set<SerializableUUID> playersCompleted = new HashSet<>();
for (UUID playerCompleted : this.playersCompleted.get(arenaGameMode)) {
playersCompleted.add(new SerializableUUID(playerCompleted));
}
serializablePlayersCompleted.put(arenaGameMode, playersCompleted);
}
data.put("playersCompleted", playersCompleted);
data.put("playersCompleted", serializablePlayersCompleted);
return data;
}
@ -84,13 +98,21 @@ public record DropperArenaData(@NotNull UUID arenaId, @NotNull DropperArenaRecor
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull DropperArenaData deserialize(@NotNull Map<String, Object> data) {
SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId");
DropperArenaRecordsRegistry recordsRegistry = (DropperArenaRecordsRegistry) data.get("recordsRegistry");
Set<SerializableUUID> playersCompletedData = (Set<SerializableUUID>) data.get("playersCompleted");
Set<UUID> playersCompleted = new HashSet<>();
for (SerializableUUID completedId : playersCompletedData) {
playersCompleted.add(completedId.uuid());
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordsRegistry =
(Map<ArenaGameMode, DropperArenaRecordsRegistry>) data.get("recordsRegistry");
Map<ArenaGameMode, Set<SerializableUUID>> playersCompletedData =
(Map<ArenaGameMode, Set<SerializableUUID>>) data.get("playersCompleted");
// Convert the serializable UUIDs to normal UUIDs
Map<ArenaGameMode, Set<UUID>> allPlayersCompleted = new HashMap<>();
for (ArenaGameMode arenaGameMode : playersCompletedData.keySet()) {
Set<UUID> playersCompleted = new HashSet<>();
for (SerializableUUID completedId : playersCompletedData.get(arenaGameMode)) {
playersCompleted.add(completedId.uuid());
}
allPlayersCompleted.put(arenaGameMode, playersCompleted);
}
return new DropperArenaData(serializableUUID.uuid(), recordsRegistry, playersCompleted);
return new DropperArenaData(serializableUUID.uuid(), recordsRegistry, allPlayersCompleted);
}
}

View File

@ -2,6 +2,7 @@ package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
@ -117,12 +118,13 @@ public class DropperArenaGroup implements ConfigurationSerializable {
}
/**
* Checks whether the given player has beaten all arenas in this group
* Checks whether the given player has beaten all arenas in this group on the given game-mode
*
* @param player <p>The player to check</p>
* @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(Player player) {
public boolean hasBeatenAll(ArenaGameMode gameMode, Player player) {
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
for (UUID anArenaId : this.getArenas()) {
DropperArena dropperArena = arenaHandler.getArena(anArenaId);
@ -133,7 +135,7 @@ public class DropperArenaGroup implements ConfigurationSerializable {
continue;
}
if (dropperArena.getData().hasNotCompleted(player)) {
if (dropperArena.getData().hasNotCompleted(gameMode, player)) {
return false;
}
}
@ -141,14 +143,15 @@ public class DropperArenaGroup implements ConfigurationSerializable {
}
/**
* Gets whether the given player can play the given arena part of this group
* Gets whether the given player can play the given arena part of this group, on the given game-mode
*
* @param player <p>The player to check</p>
* @param arenaId <p>The id of the arena in this group to check</p>
* @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(Player player, UUID arenaId) throws IllegalArgumentException {
public boolean canPlay(ArenaGameMode 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!");
}
@ -170,7 +173,7 @@ public class DropperArenaGroup implements ConfigurationSerializable {
}
// This is a lower-numbered arena the player has yet to complete
if (dropperArena.getData().hasNotCompleted(player)) {
if (dropperArena.getData().hasNotCompleted(gameMode, player)) {
return false;
}
}

View File

@ -37,11 +37,20 @@ public class DropperArenaSession {
this.deaths = 0;
this.startTime = System.currentTimeMillis();
this.entryState = new PlayerEntryState(player);
this.entryState = new PlayerEntryState(player, gameMode);
// 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 ArenaGameMode getGameMode() {
return this.gameMode;
}
/**
* Gets the state of the player when they joined the session
*
@ -62,7 +71,7 @@ public class DropperArenaSession {
registerRecord();
// Mark the arena as cleared
if (this.arena.getData().addCompleted(this.player)) {
if (this.arena.getData().addCompleted(this.gameMode, this.player)) {
this.player.sendMessage("You cleared the arena!");
}
this.player.sendMessage("You won!");
@ -101,27 +110,44 @@ public class DropperArenaSession {
* Registers the player's record if necessary, and prints record information to the player
*/
private void registerRecord() {
DropperArenaRecordsRegistry recordsRegistry = this.arena.getData().recordsRegistry();
RecordResult recordResult = switch (this.gameMode) {
case LEAST_TIME -> recordsRegistry.registerTimeRecord(this.player.getUniqueId(),
System.currentTimeMillis() - this.startTime);
case LEAST_DEATHS -> recordsRegistry.registerDeathRecord(this.player.getUniqueId(), this.deaths);
case DEFAULT -> RecordResult.NONE;
};
switch (recordResult) {
case WORLD_RECORD -> this.player.sendMessage("You just set a new record for this arena!");
case PERSONAL_BEST -> this.player.sendMessage("You just got a new personal record!");
DropperArenaRecordsRegistry recordsRegistry = this.arena.getData().recordRegistries().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() {
// Add to the death count if playing the least-deaths game-mode
if (this.gameMode == ArenaGameMode.LEAST_DEATHS) {
this.deaths++;
}
this.deaths++;
//Teleport the player back to the top
PlayerTeleporter.teleportPlayer(this.player, this.arena.getSpawnLocation(), true, false);
this.entryState.setArenaState(this.arena.getPlayerHorizontalVelocity());

View File

@ -1,8 +1,10 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* The state of a player before entering a dropper arena
@ -17,13 +19,14 @@ public class PlayerEntryState {
private final boolean originalAllowFlight;
private final boolean originalInvulnerable;
private final boolean originalIsSwimming;
private final ArenaGameMode arenaGameMode;
/**
* Instantiates a new player state
*
* @param player <p>The player whose state should be stored</p>
*/
public PlayerEntryState(Player player) {
public PlayerEntryState(@NotNull Player player, @NotNull ArenaGameMode arenaGameMode) {
this.player = player;
this.entryLocation = player.getLocation().clone();
this.originalFlySpeed = player.getFlySpeed();
@ -32,6 +35,7 @@ public class PlayerEntryState {
this.originalAllowFlight = player.getAllowFlight();
this.originalInvulnerable = player.isInvulnerable();
this.originalIsSwimming = player.isSwimming();
this.arenaGameMode = arenaGameMode;
}
/**
@ -42,9 +46,15 @@ public class PlayerEntryState {
public void setArenaState(float horizontalVelocity) {
this.player.setAllowFlight(true);
this.player.setFlying(true);
this.player.setFlySpeed(horizontalVelocity);
this.player.setGameMode(GameMode.ADVENTURE);
this.player.setSwimming(false);
// If playing on the inverted game-mode, negate the horizontal velocity to swap the controls
if (arenaGameMode == ArenaGameMode.INVERTED) {
this.player.setFlySpeed(-horizontalVelocity);
} else {
this.player.setFlySpeed(horizontalVelocity);
}
}
/**

View File

@ -120,7 +120,7 @@ public class EditArenaCommand implements CommandExecutor {
}
// If outside bonds, choose the most extreme value
return Math.min(Math.max(-1, velocity), 1);
return Math.min(Math.max(0.1f, velocity), 1);
}
/**

View File

@ -76,9 +76,10 @@ public class JoinArenaCommand implements CommandExecutor {
return false;
}
// Make sure the player has beaten the arena once before playing a challenge mode
if (gameMode != ArenaGameMode.DEFAULT && specifiedArena.getData().hasNotCompleted(player)) {
player.sendMessage("You must complete this arena in normal mode before starting a challenge!");
// Make sure the player has beaten the arena once in normal mode before playing another mode
if (gameMode != ArenaGameMode.DEFAULT &&
specifiedArena.getData().hasNotCompleted(ArenaGameMode.DEFAULT, player)) {
player.sendMessage("You must complete this arena in normal mode first!");
return false;
}
@ -112,18 +113,20 @@ public class JoinArenaCommand implements CommandExecutor {
*/
private boolean doGroupChecks(@NotNull DropperArena dropperArena, @NotNull DropperArenaGroup arenaGroup,
@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
if (arenaGameMode == ArenaGameMode.DEFAULT) {
if (!arenaGroup.canPlay(player, dropperArena.getArenaId())) {
player.sendMessage("You have not yet beaten the previous arena!");
return false;
}
} else {
if (arenaGroup.hasBeatenAll(player)) {
// Require that players beat all arenas in the group in the normal game-mode before trying challenge modes
if (arenaGameMode != ArenaGameMode.DEFAULT) {
if (!arenaGroup.hasBeatenAll(ArenaGameMode.DEFAULT, player)) {
player.sendMessage("You have not yet beaten all arenas in this group!");
return false;
}
}
// Require that the player has beaten the previous arena on the same game-mode before trying this one
if (!arenaGroup.canPlay(arenaGameMode, player, dropperArena.getArenaId())) {
player.sendMessage("You have not yet beaten the previous arena!");
return false;
}
return true;
}

View File

@ -23,8 +23,8 @@ public class JoinArenaTabCompleter implements TabCompleter {
} else if (arguments.length == 2) {
List<String> gameModes = new ArrayList<>();
gameModes.add("default");
gameModes.add("deaths");
gameModes.add("time");
gameModes.add("inverted");
gameModes.add("random");
return gameModes;
} else {
return new ArrayList<>();

View File

@ -3,6 +3,7 @@ package net.knarcraft.dropper.listener;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
@ -11,7 +12,9 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
@ -40,6 +43,27 @@ public class MoveListener implements Listener {
return;
}
// Only do block type checking if the block beneath the player changes
if (event.getFrom().getBlock() != event.getTo().getBlock() &&
checkForSpecialBlock(arenaSession, event.getTo())) {
return;
}
//Updates the player's velocity to the one set by the arena
updatePlayerVelocity(arenaSession);
}
/**
* Check if the player in the session is triggering a block with a special significance
*
* <p>This basically looks for the win block, or whether the player is hitting a solid block.</p>
*
* @param arenaSession <p>The arena session to check for</p>
* @param toLocation <p>The location the player's session is about to hit</p>
* @return <p>True if a special block has been hit</p>
*/
private boolean checkForSpecialBlock(DropperArenaSession arenaSession, Location toLocation) {
/* This decides how far inside a non-solid block the player must go before detection triggers. The closer to -1
it is, the more accurate it will seem to the player, but the likelihood of not detecting the hit decreases */
double liquidDepth = -0.8;
@ -47,31 +71,27 @@ public class MoveListener implements Listener {
likelihood of detecting the hit decreases, but the immersion increases. */
double solidDepth = 0.2;
// Only do block type checking if the block beneath the player changes
if (event.getFrom().getBlock() != event.getTo().getBlock()) {
// Check if the player enters water
Material winBlockType = arenaSession.getArena().getWinBlockType();
// For water, only trigger when the player enters the water, but trigger earlier for everything else
double depth = !winBlockType.isSolid() ? liquidDepth : solidDepth;
for (Block block : getBlocksBeneathLocation(event.getTo(), depth)) {
if (block.getType() == winBlockType) {
arenaSession.triggerWin();
return;
}
}
// Check if the player is about to hit a non-air and non-liquid block
for (Block block : getBlocksBeneathLocation(event.getTo(), solidDepth)) {
if (!block.getType().isAir() && block.getType() != Material.STRUCTURE_VOID &&
block.getType() != Material.WATER && block.getType() != Material.LAVA) {
arenaSession.triggerLoss();
return;
}
// Check if the player enters water
Material winBlockType = arenaSession.getArena().getWinBlockType();
// For water, only trigger when the player enters the water, but trigger earlier for everything else
double depth = !winBlockType.isSolid() ? liquidDepth : solidDepth;
for (Block block : getBlocksBeneathLocation(toLocation, depth)) {
if (block.getType() == winBlockType) {
arenaSession.triggerWin();
return true;
}
}
//Updates the player's velocity to the one set by the arena
updatePlayerVelocity(arenaSession);
// Check if the player is about to hit a non-air and non-liquid block
for (Block block : getBlocksBeneathLocation(toLocation, solidDepth)) {
if (!block.getType().isAir() && block.getType() != Material.STRUCTURE_VOID &&
block.getType() != Material.WATER && block.getType() != Material.LAVA) {
arenaSession.triggerLoss();
return true;
}
}
return false;
}
/**
@ -95,12 +115,40 @@ public class MoveListener implements Listener {
*
* @param session <p>The session to update the velocity for</p>
*/
private void updatePlayerVelocity(DropperArenaSession session) {
private void updatePlayerVelocity(@NotNull DropperArenaSession session) {
Player player = session.getPlayer();
Vector playerVelocity = player.getVelocity();
double arenaVelocity = session.getArena().getPlayerVerticalVelocity();
Vector newVelocity = new Vector(playerVelocity.getX(), -arenaVelocity, playerVelocity.getZ());
player.setVelocity(newVelocity);
// Toggle the direction of the player's flying, as necessary
toggleFlyInversion(session);
}
/**
* Toggles the player's flying direction inversion if playing on the random game-mode
*
* @param session <p>The session to possibly invert flying for</p>
*/
private void toggleFlyInversion(@NotNull DropperArenaSession session) {
if (session.getGameMode() != ArenaGameMode.RANDOM_INVERTED) {
return;
}
Player player = session.getPlayer();
float horizontalVelocity = session.getArena().getPlayerHorizontalVelocity();
float secondsBetweenToggle = 7;
int seconds = Calendar.getInstance().get(Calendar.SECOND);
/*
* A trick to make the inversion change after a customizable amount of seconds
* If the quotient of dividing the current number of seconds with the set amount of seconds is even, invert.
* So, if the number of seconds between toggles is 5, that would mean for the first 5 seconds, the flying would
* be inverted. Once 5 seconds have passed, the quotient becomes 1, which is odd, so the flying is no longer
* inverted. After 10 seconds, the quotient is 2, which is even, and inverts the flying.
*/
boolean invert = Math.floor(seconds / secondsBetweenToggle) % 2 == 0;
player.setFlySpeed(invert ? -horizontalVelocity : horizontalVelocity);
}
}

View File

@ -1,11 +1,15 @@
package net.knarcraft.dropper.property;
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 ArenaGameMode {
public enum ArenaGameMode implements ConfigurationSerializable {
/**
* The default game-mode. Failing once throws the player out.
@ -13,14 +17,14 @@ public enum ArenaGameMode {
DEFAULT,
/**
* The least-deaths game-mode. Player plays until they manage to win. The number of deaths is recorded.
* A game-mode where the player's directional buttons are inverted
*/
LEAST_DEATHS,
INVERTED,
/**
* The least-time game-mode. Player plays until they manage to win. The total time of the session is recorded.
* A game-mode which swaps between normal and inverted controls on a set schedule of a few seconds
*/
LEAST_TIME,
RANDOM_INVERTED,
;
/**
@ -31,13 +35,32 @@ public enum ArenaGameMode {
*/
public static @NotNull ArenaGameMode matchGamemode(@NotNull String gameMode) {
String sanitized = gameMode.trim().toLowerCase();
if (sanitized.matches("(least)?deaths?")) {
return ArenaGameMode.LEAST_DEATHS;
} else if (sanitized.matches("(least)?time")) {
return ArenaGameMode.LEAST_TIME;
if (sanitized.matches("(invert(ed)?|inverse)")) {
return ArenaGameMode.INVERTED;
} else if (sanitized.matches("rand(om)?")) {
return ArenaGameMode.RANDOM_INVERTED;
} else {
return ArenaGameMode.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 ArenaGameMode deserialize(Map<String, Object> data) {
return ArenaGameMode.valueOf((String) data.get("name"));
}
}

View File

@ -7,6 +7,7 @@ import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.container.SerializableMaterial;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.property.ArenaStorageKey;
import org.bukkit.Location;
import org.bukkit.Material;
@ -164,7 +165,12 @@ public final class ArenaStorageHelper {
DropperArenaData arenaData = loadArenaData(arenaId);
if (arenaData == null) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to load arena data for " + arenaId);
arenaData = new DropperArenaData(arenaId, new DropperArenaRecordsRegistry(arenaId), new HashSet<>());
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries = new HashMap<>();
for (ArenaGameMode arenaGameMode : ArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new DropperArenaRecordsRegistry(arenaId));
}
arenaData = new DropperArenaData(arenaId, recordRegistries, new HashMap<>());
}
return new DropperArena(arenaId, arenaName, spawnLocation, exitLocation, verticalVelocity, horizontalVelocity,

View File

@ -24,8 +24,8 @@ commands:
usage: |
/<command> <arena> [mode]
Mode can be used to select challenge modes which can be played after beating the arena.
deaths = A least-deaths competitive game-mode
time = A shortest-time competitive game-mode
inverted = A game-mode where the WASD buttons are inverted
random = A game-mode where the WASD buttons toggle between being inverted and not
description: Used to join a dropper arena
dropperleave:
aliases: