Adds a lot of small improvements

Makes the type of block players have to hit to win configurable
Separates the velocity option into vertical and horizontal velocities
Reduces some redundancy when getting an arena from an arena name
Partially implements the list command
Tries to improve the handling of players exiting in the middle of a session
Adds missing comments to ArenaStorageKey
This commit is contained in:
Kristian Knarvik 2023-03-25 23:18:03 +01:00
parent 6385b4c5e8
commit 0c58860026
13 changed files with 199 additions and 81 deletions

View File

@ -1,6 +1,7 @@
package net.knarcraft.dropper.arena;
import org.bukkit.Location;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -28,7 +29,14 @@ public class DropperArena {
/**
* The velocity in the y-direction to apply to all players in this arena.
*/
private final double playerVelocity;
private final 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 final double playerHorizontalVelocity;
/**
* The stage number of this arena. If not null, the previous stage number must be cleared before access.
@ -40,26 +48,36 @@ public class DropperArena {
*/
private final @NotNull DropperArenaRecordsRegistry recordsRegistry;
/**
* The material of the block players have to hit to win this dropper arena
*/
private final @NotNull Material winBlockType;
//TODO: Store records for this arena (maps with player->deaths/time). It should be possible to get those in sorted
// order (smallest to largest)
/**
* Instantiates a new dropper arena
*
* @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 playerVelocity <p>The velocity multiplier to use for players' velocity</p>
* @param stage <p>The stage number of this stage, or null if not limited to stages</p>
* @param recordsRegistry <p>The registry keeping track of all of this arena's records</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</p>
* @param stage <p>The stage number of this stage, or null if not limited to stages</p>
* @param winBlockType <p>The material of the block players have to hit to win this dropper arena</p>
* @param recordsRegistry <p>The registry keeping track of all of this arena's records</p>
*/
public DropperArena(@NotNull String arenaName, @NotNull Location spawnLocation, @Nullable Location exitLocation,
double playerVelocity, @Nullable Integer stage, @NotNull DropperArenaRecordsRegistry recordsRegistry) {
double playerVerticalVelocity, double playerHorizontalVelocity, @Nullable Integer stage, @NotNull Material winBlockType,
@NotNull DropperArenaRecordsRegistry recordsRegistry) {
this.arenaName = arenaName;
this.spawnLocation = spawnLocation;
this.exitLocation = exitLocation;
this.playerVelocity = playerVelocity;
this.playerVerticalVelocity = playerVerticalVelocity;
this.playerHorizontalVelocity = playerHorizontalVelocity;
this.stage = stage;
this.winBlockType = winBlockType;
this.recordsRegistry = recordsRegistry;
}
@ -76,9 +94,11 @@ public class DropperArena {
this.arenaName = arenaName;
this.spawnLocation = spawnLocation;
this.exitLocation = null;
this.playerVelocity = 1;
this.playerVerticalVelocity = 1;
this.playerHorizontalVelocity = 1;
this.stage = null;
this.recordsRegistry = new DropperArenaRecordsRegistry();
this.winBlockType = Material.WATER;
}
/**
@ -120,15 +140,26 @@ public class DropperArena {
}
/**
* Gets the velocity for players in this arena
* Gets the vertical velocity for players in this arena
*
* <p>The velocity is the multiplier used to define players' dropping speed in this dropper arena. 1.0 is the normal
* falling speed. 0.5 is half speed. 2 is double speed etc.</p>
* <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 getPlayerVelocity() {
return this.playerVelocity;
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 double getPlayerHorizontalVelocity() {
return this.playerHorizontalVelocity;
}
/**
@ -143,6 +174,13 @@ public class DropperArena {
return this.stage;
}
//TODO: Add the appropriate getters/setters and other methods
/**
* 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;
}
}

View File

@ -4,6 +4,7 @@ import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
@ -50,6 +51,22 @@ public class DropperArenaHandler {
this.saveArenas();
}
/**
* 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) {
arenaName = ArenaStorageHelper.sanitizeArenaName(arenaName);
for (DropperArena arena : arenas) {
if (ArenaStorageHelper.sanitizeArenaName(arena.getArenaName()).equals(arenaName)) {
return arena;
}
}
return null;
}
/**
* Gets all known arenas
*

View File

@ -43,7 +43,7 @@ public class DropperArenaSession {
player.setAllowFlight(true);
player.setFlying(true);
this.playersOriginalFlySpeed = player.getFlySpeed();
player.setFlySpeed((float) this.arena.getPlayerVelocity());
player.setFlySpeed((float) this.arena.getPlayerHorizontalVelocity());
}
/**

View File

@ -2,7 +2,6 @@ package net.knarcraft.dropper.command;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -27,19 +26,15 @@ public class CreateArenaCommand implements CommandExecutor {
return false;
}
String arenaName = arguments[0];
String sanitized = ArenaStorageHelper.sanitizeArenaName(arenaName);
for (DropperArena arena : Dropper.getInstance().getArenaHandler().getArenas()) {
if (sanitized.equals(ArenaStorageHelper.sanitizeArenaName(arena.getArenaName()))) {
commandSender.sendMessage("There already exists a dropper arena with that name!");
return false;
}
DropperArena existingArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
if (existingArena != null) {
commandSender.sendMessage("There already exists a dropper arena with that name!");
return false;
}
//TODO: Make sure the arena name doesn't contain any unwanted characters
DropperArena arena = new DropperArena(arenaName, player.getLocation());
DropperArena arena = new DropperArena(arguments[0], player.getLocation());
Dropper.getInstance().getArenaHandler().addArena(arena);
commandSender.sendMessage("The arena was successfully created!");
return true;

View File

@ -1,8 +1,11 @@
package net.knarcraft.dropper.command;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
@ -12,8 +15,18 @@ public class EditArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] strings) {
//TODO: Make sure the console cannot run this
@NotNull String[] arguments) {
if (!(commandSender instanceof Player)) {
commandSender.sendMessage("This command must be used by a player");
return false;
}
DropperArena specifiedArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena.");
return false;
}
//TODO: If an arena name and a property is given, display the current value
//TODO: If an arena name, a property and a value is given, check if it's valid, and update the property
return false;

View File

@ -5,7 +5,6 @@ import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import net.knarcraft.dropper.util.PlayerTeleporter;
import org.bukkit.GameMode;
import org.bukkit.command.Command;
@ -45,14 +44,7 @@ public class JoinArenaCommand implements CommandExecutor {
}
// Make sure the arena exists
String arenaName = ArenaStorageHelper.sanitizeArenaName(arguments[0]);
DropperArena specifiedArena = null;
for (DropperArena arena : Dropper.getInstance().getArenaHandler().getArenas()) {
if (ArenaStorageHelper.sanitizeArenaName(arena.getArenaName()).equals(arenaName)) {
specifiedArena = arena;
break;
}
}
DropperArena specifiedArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena.");
return false;

View File

@ -21,7 +21,8 @@ public class LeaveArenaCommand implements CommandExecutor {
return false;
}
DropperArenaSession existingSession = Dropper.getInstance().getPlayerRegistry().getArenaSession(player.getUniqueId());
DropperArenaSession existingSession = Dropper.getInstance().getPlayerRegistry().getArenaSession(
player.getUniqueId());
if (existingSession == null) {
commandSender.sendMessage("You are not in a dropper arena!");
return false;

View File

@ -1,20 +1,37 @@
package net.knarcraft.dropper.command;
import net.knarcraft.dropper.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* A command for listing existing dropper arenas
*/
public class ListArenaCommand implements CommandExecutor {
public class ListArenaCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
//TODO: List all existing arenas, and possibly information about a specified arena
return false;
@NotNull String[] arguments) {
sender.sendMessage("Dropper arenas:");
for (String arenaName : TabCompleteHelper.getArenas()) {
sender.sendMessage(arenaName);
}
//TODO: Allow displaying information about each arena (possibly admin-only)
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
return new ArrayList<>();
}
}

View File

@ -2,7 +2,6 @@ package net.knarcraft.dropper.command;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -17,15 +16,7 @@ public class RemoveArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
// Get the specified arena
String arenaName = arguments[0];
String sanitized = ArenaStorageHelper.sanitizeArenaName(arenaName);
DropperArena targetArena = null;
for (DropperArena arena : Dropper.getInstance().getArenaHandler().getArenas()) {
if (sanitized.equals(ArenaStorageHelper.sanitizeArenaName(arena.getArenaName()))) {
targetArena = arena;
}
}
DropperArena targetArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
if (targetArena == null) {
commandSender.sendMessage("Unable to find the specified arena");
return false;

View File

@ -43,8 +43,11 @@ public class MoveListener implements Listener {
// 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
for (Block block : getBlocksBeneathLocation(event.getTo(), 0)) {
if (block.getType() == Material.WATER) {
Material winBlockType = arenaSession.getArena().getWinBlockType();
// For water, only trigger when the player enters the water, but trigger earlier for everything else
int depth = winBlockType == Material.WATER ? 0 : 1;
for (Block block : getBlocksBeneathLocation(event.getTo(), depth)) {
if (block.getType() == winBlockType) {
arenaSession.triggerWin();
return;
}
@ -88,7 +91,7 @@ public class MoveListener implements Listener {
private void updatePlayerVelocity(DropperArenaSession session) {
Player player = session.getPlayer();
Vector playerVelocity = player.getVelocity();
double arenaVelocity = session.getArena().getPlayerVelocity();
double arenaVelocity = session.getArena().getPlayerVerticalVelocity();
Vector newVelocity = new Vector(playerVelocity.getX(), -arenaVelocity, playerVelocity.getZ());
player.setVelocity(newVelocity);
}

View File

@ -2,27 +2,58 @@ package net.knarcraft.dropper.listener;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaSession;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
/**
* A listener for players leaving the server or the arena
*/
public class PlayerLeaveListener implements Listener {
private final Map<UUID, DropperArenaSession> leftSessions = new HashMap<>();
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
triggerQuit(event.getPlayer());
Player player = event.getPlayer();
DropperArenaSession arenaSession = getSession(player);
if (arenaSession == null) {
return;
}
Dropper.getInstance().getLogger().log(Level.WARNING, "Found player " + player.getUniqueId() +
" leaving in the middle of a session!");
leftSessions.put(player.getUniqueId(), arenaSession);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
UUID playerId = event.getPlayer().getUniqueId();
// Force the player to quit from the session once they re-join
if (leftSessions.containsKey(playerId)) {
Dropper.getInstance().getLogger().log(Level.WARNING, "Found un-exited dropper session!");
Bukkit.getScheduler().runTaskLater(Dropper.getInstance(), () -> {
leftSessions.get(playerId).triggerQuit();
Dropper.getInstance().getLogger().log(Level.WARNING, "Triggered a quit!");
leftSessions.remove(playerId);
}, 80);
}
}
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
if (event.getTo() == null) {
if (event.getTo() == null || event.isCancelled()) {
return;
}
@ -35,24 +66,7 @@ public class PlayerLeaveListener implements Listener {
return;
}
triggerQuit(event.getPlayer());
}
/**
* Forces the given player to quit their current arena
*
* @param player <p>The player to trigger a quit for</p>
*/
private void triggerQuit(Player player) {
DropperArenaSession arenaSession = getSession(player);
if (arenaSession == null) {
return;
}
arenaSession.triggerQuit();
//TODO: It might not be possible to alter a leaving player's location here. It might be necessary to move them once
// they join again
}
/**

View File

@ -7,11 +7,40 @@ import org.jetbrains.annotations.NotNull;
*/
public enum ArenaStorageKey {
/**
* 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"),
PLAYER_VELOCITY("arenaPlayerVelocity"),
/**
* 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 this arena's stage
*/
STAGE("arenaStage"),
/**
* The key for the type of this arena's win block
*/
WIN_BLOCK_TYPE("winBlockType"),
;
private final @NotNull String key;

View File

@ -5,6 +5,7 @@ import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.property.ArenaStorageKey;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
@ -45,8 +46,10 @@ public final class ArenaStorageHelper {
configSection.set(ArenaStorageKey.NAME.getKey(), arena.getArenaName());
configSection.set(ArenaStorageKey.SPAWN_LOCATION.getKey(), arena.getSpawnLocation());
configSection.set(ArenaStorageKey.EXIT_LOCATION.getKey(), arena.getExitLocation());
configSection.set(ArenaStorageKey.PLAYER_VELOCITY.getKey(), arena.getPlayerVelocity());
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());
}
//TODO: Save records belonging to the arena
configuration.save(arenaFile);
@ -94,16 +97,21 @@ public final class ArenaStorageHelper {
String arenaName = configurationSection.getString(ArenaStorageKey.NAME.getKey());
Location spawnLocation = (Location) configurationSection.get(ArenaStorageKey.SPAWN_LOCATION.getKey());
Location exitLocation = (Location) configurationSection.get(ArenaStorageKey.EXIT_LOCATION.getKey());
double playerVelocity = configurationSection.getDouble(ArenaStorageKey.PLAYER_VELOCITY.getKey());
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());
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;
}
//TODO: Load records for this arena
return new DropperArena(arenaName, spawnLocation, exitLocation, playerVelocity, stage,
new DropperArenaRecordsRegistry());
return new DropperArena(arenaName, spawnLocation, exitLocation, verticalVelocity, horizontalVelocity, stage,
winBlockType, new DropperArenaRecordsRegistry());
}
/**