Implements #27 among other things

It was found that the Spigot API's methods for cancelling player collisions won't work without changing the scoreboard. Because of that, the normal option has been disabled. The invisibility option has also been removed, as that's a bad idea if players can still push each-other.
The toggle player option which is implemented in this commit does disable player collision, so that's the only working way right now.

A potential ConcurrentModificationException has been fixed.
The parkourCheckpoint command has been removed, as the functionality is now available through the API.
This commit is contained in:
2023-05-10 15:14:28 +02:00
parent 00ac0582f4
commit 7848a0a028
23 changed files with 193 additions and 194 deletions

View File

@@ -29,6 +29,11 @@ public abstract class AbstractArenaPlayerRegistry<K extends Arena> implements Ar
loadEntryStates();
}
@Override
public @NotNull Set<UUID> getPlayingPlayers() {
return arenaPlayers.keySet();
}
@Override
public @Nullable PlayerEntryState getEntryState(@NotNull UUID playerId) {
return this.entryStates.get(playerId);
@@ -65,13 +70,15 @@ public abstract class AbstractArenaPlayerRegistry<K extends Arena> implements Ar
@Override
public void removeForArena(K arena, boolean immediately) {
Set<UUID> removed = new HashSet<>();
for (Map.Entry<UUID, ArenaSession> entry : this.arenaPlayers.entrySet()) {
if (entry.getValue().getArena() == arena) {
// Kick the player gracefully
entry.getValue().triggerQuit(immediately);
this.arenaPlayers.remove(entry.getKey());
entry.getValue().triggerQuit(immediately, false);
removed.add(entry.getKey());
}
}
removed.forEach(this.arenaPlayers::remove);
}
/**

View File

@@ -1,5 +1,6 @@
package net.knarcraft.minigames.arena;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.container.PlaceholderContainer;
import net.knarcraft.minigames.property.RecordResult;
@@ -33,11 +34,15 @@ public abstract class AbstractArenaSession implements ArenaSession {
}
@Override
public void triggerQuit(boolean immediately) {
public void triggerQuit(boolean immediately, boolean removeSession) {
// Stop this session
removeSession();
if (removeSession) {
removeSession();
}
// Teleport the player out of the arena
teleportToExit(immediately);
// Make the player visible to everyone
MiniGames.getInstance().getPlayerVisibilityManager().showPlayersFor(player);
player.sendMessage(Message.SUCCESS_ARENA_QUIT.getMessage());
}

View File

@@ -6,8 +6,6 @@ import org.bukkit.Bukkit;
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;
import java.util.HashMap;
@@ -21,59 +19,49 @@ import java.util.logging.Level;
public abstract class AbstractPlayerEntryState implements PlayerEntryState {
protected final UUID playerId;
private final boolean makePlayerInvisible;
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;
/**
* Instantiates a new abstract player entry state
*
* @param player <p>The player whose state this should keep track of</p>
* @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p>
* @param player <p>The player whose state this should keep track of</p>
*/
public AbstractPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) {
public AbstractPlayerEntryState(@NotNull Player player) {
this.playerId = player.getUniqueId();
this.makePlayerInvisible = makePlayerInvisible;
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();
}
/**
* Instantiates a new abstract player entry state
*
* @param playerId <p>The id of the player whose state this should keep track of</p>
* @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p>
* @param entryLocation <p>The location the player entered from</p>
* @param originalIsFlying <p>Whether the player was flying before entering the arena</p>
* @param originalGameMode <p>The game-mode of the player before entering the arena</p>
* @param originalAllowFlight <p>Whether the player was allowed flight before entering the arena</p>
* @param originalInvulnerable <p>Whether the player was invulnerable before entering the arena</p>
* @param originalIsSwimming <p>Whether the player was swimming before entering the arena</p>
* @param originalCollideAble <p>Whether the player was collide-able before entering the arena</p>
*/
public AbstractPlayerEntryState(@NotNull UUID playerId, boolean makePlayerInvisible, Location entryLocation,
public AbstractPlayerEntryState(@NotNull UUID playerId, Location entryLocation,
boolean originalIsFlying, GameMode originalGameMode, boolean originalAllowFlight,
boolean originalInvulnerable, boolean originalIsSwimming,
boolean originalCollideAble) {
boolean originalInvulnerable, boolean originalIsSwimming) {
this.playerId = playerId;
this.makePlayerInvisible = makePlayerInvisible;
this.entryLocation = entryLocation;
this.originalIsFlying = originalIsFlying;
this.originalGameMode = originalGameMode;
this.originalAllowFlight = originalAllowFlight;
this.originalInvulnerable = originalInvulnerable;
this.originalIsSwimming = originalIsSwimming;
this.originalCollideAble = originalCollideAble;
}
@Override
@@ -81,18 +69,6 @@ public abstract class AbstractPlayerEntryState implements PlayerEntryState {
return this.playerId;
}
@Override
public void setArenaState() {
Player player = getPlayer();
if (player == null) {
return;
}
if (this.makePlayerInvisible) {
player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,
PotionEffect.INFINITE_DURATION, 3));
}
}
@Override
public boolean restore() {
Player player = getPlayer();
@@ -110,10 +86,6 @@ public abstract class AbstractPlayerEntryState implements PlayerEntryState {
player.setAllowFlight(this.originalAllowFlight);
player.setInvulnerable(this.originalInvulnerable);
player.setSwimming(this.originalIsSwimming);
player.setCollidable(this.originalCollideAble);
if (this.makePlayerInvisible) {
player.removePotionEffect(PotionEffectType.INVISIBILITY);
}
}
@Override
@@ -140,14 +112,12 @@ public abstract class AbstractPlayerEntryState implements PlayerEntryState {
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("playerId", new SerializableUUID(this.playerId));
data.put("makePlayerInvisible", this.makePlayerInvisible);
data.put("entryLocation", this.entryLocation);
data.put("originalIsFlying", this.originalIsFlying);
data.put("originalGameMode", this.originalGameMode.name());
data.put("originalAllowFlight", this.originalAllowFlight);
data.put("originalInvulnerable", this.originalInvulnerable);
data.put("originalIsSwimming", this.originalIsSwimming);
data.put("originalCollideAble", this.originalCollideAble);
return data;
}

View File

@@ -3,6 +3,7 @@ package net.knarcraft.minigames.arena;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.UUID;
/**
@@ -12,6 +13,13 @@ import java.util.UUID;
*/
public interface ArenaPlayerRegistry<K extends Arena> {
/**
* Gets the ids of the players currently playing
*
* @return <p>The ids of the playing players</p>
*/
@NotNull Set<UUID> getPlayingPlayers();
/**
* Gets the current entry state for the given player
*

View File

@@ -28,9 +28,10 @@ public interface ArenaSession {
/**
* Triggers a quit for the player playing in this session
*
* @param immediately <p>Whether to to the teleportation immediately, not using any timers</p>
* @param immediately <p>Whether to to the teleportation immediately, not using any timers</p>
* @param removeSession <p>Whether to also remove the session. Should usually be true.</p>
*/
void triggerQuit(boolean immediately);
void triggerQuit(boolean immediately, boolean removeSession);
/**
* Gets the arena this session is being played in

View File

@@ -0,0 +1,95 @@
package net.knarcraft.minigames.arena;
import net.knarcraft.minigames.MiniGames;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* A manager for keeping track of which players have set other players as hidden
*/
public class PlayerVisibilityManager {
private final Set<UUID> hidingEnabledFor = new HashSet<>();
/**
* Toggles whether players should be hidden for the player with the given id
*
* @param player <p>The the player to update</p>
*/
public void toggleHidePlayers(@NotNull ArenaPlayerRegistry<?> playerRegistry, @NotNull Player player) {
if (hidingEnabledFor.contains(player.getUniqueId())) {
hidingEnabledFor.remove(player.getUniqueId());
// Make all other players visible again
changeVisibilityFor(playerRegistry, player, false);
} else {
hidingEnabledFor.add(player.getUniqueId());
// Make all other players hidden
changeVisibilityFor(playerRegistry, player, true);
}
}
/**
* Updates which players are seen as hidden
*
* @param playerRegistry <p>The registry containing all playing players</p>
* @param player <p>The player that joined the arena</p>
*/
public void updateHiddenPlayers(@NotNull ArenaPlayerRegistry<?> playerRegistry, @NotNull Player player) {
boolean hideForPlayer = hidingEnabledFor.contains(player.getUniqueId());
for (UUID playerId : playerRegistry.getPlayingPlayers()) {
Player otherPlayer = Bukkit.getPlayer(playerId);
if (otherPlayer == null) {
continue;
}
// Hide the arena player from the newly joined player
if (hideForPlayer) {
player.hidePlayer(MiniGames.getInstance(), otherPlayer);
}
// Hide the newly joined player from this player
if (hidingEnabledFor.contains(playerId)) {
otherPlayer.hidePlayer(MiniGames.getInstance(), player);
}
}
}
/**
* Makes all players visible to the given player
*
* @param player <p>The player to update visibility for</p>
*/
public void showPlayersFor(@NotNull Player player) {
for (Player otherPlayer : Bukkit.getOnlinePlayers()) {
player.showPlayer(MiniGames.getInstance(), otherPlayer);
otherPlayer.showPlayer(MiniGames.getInstance(), player);
}
}
/**
* Changes whether the given player can see the other players in the arena
*
* @param playerRegistry <p>The player registry containing other players</p>
* @param player <p>The player to change the visibility for</p>
* @param hide <p>Whether to hide the players or show the players</p>
*/
private void changeVisibilityFor(@NotNull ArenaPlayerRegistry<?> playerRegistry, @NotNull Player player, boolean hide) {
for (UUID playerId : playerRegistry.getPlayingPlayers()) {
Player otherPlayer = Bukkit.getPlayer(playerId);
if (otherPlayer == null) {
continue;
}
if (hide) {
player.hidePlayer(MiniGames.getInstance(), otherPlayer);
} else {
player.showPlayer(MiniGames.getInstance(), otherPlayer);
}
}
}
}

View File

@@ -3,7 +3,6 @@ package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.AbstractArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.gui.ArenaGUI;
import net.knarcraft.minigames.gui.DropperGUI;
@@ -38,11 +37,7 @@ public class DropperArenaSession extends AbstractArenaSession {
this.player = player;
this.gameMode = gameMode;
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
boolean makeInvisible = configuration.makePlayersInvisible();
boolean disableCollision = configuration.disableHitCollision();
this.entryState = new DropperPlayerEntryState(player, gameMode, makeInvisible, disableCollision,
dropperArena.getPlayerHorizontalVelocity());
this.entryState = new DropperPlayerEntryState(player, gameMode, dropperArena.getPlayerHorizontalVelocity());
this.entryState.setArenaState();
}

View File

@@ -16,7 +16,6 @@ import java.util.UUID;
public class DropperPlayerEntryState extends AbstractPlayerEntryState {
private final float originalFlySpeed;
private final boolean disableHitCollision;
private final float horizontalVelocity;
private final DropperArenaGameMode arenaGameMode;
@@ -26,11 +25,10 @@ public class DropperPlayerEntryState extends AbstractPlayerEntryState {
* @param player <p>The player whose state should be stored</p>
*/
public DropperPlayerEntryState(@NotNull Player player, @NotNull DropperArenaGameMode arenaGameMode,
boolean makePlayerInvisible, boolean disableHitCollision, float horizontalVelocity) {
super(player, makePlayerInvisible);
float horizontalVelocity) {
super(player);
this.originalFlySpeed = player.getFlySpeed();
this.arenaGameMode = arenaGameMode;
this.disableHitCollision = disableHitCollision;
this.horizontalVelocity = horizontalVelocity;
}
@@ -38,31 +36,29 @@ public class DropperPlayerEntryState extends AbstractPlayerEntryState {
* Instantiates a new parkour player entry state
*
* @param playerId <p>The id of the player whose state this should keep track of</p>
* @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p>
* @param entryLocation <p>The location the player entered from</p>
* @param originalIsFlying <p>Whether the player was flying before entering the arena</p>
* @param originalGameMode <p>The game-mode of the player before entering the arena</p>
* @param originalAllowFlight <p>Whether the player was allowed flight before entering the arena</p>
* @param originalInvulnerable <p>Whether the player was invulnerable before entering the arena</p>
* @param originalIsSwimming <p>Whether the player was swimming before entering the arena</p>
* @param originalCollideAble <p>Whether the player was collide-able before entering the arena</p>
* @param originalFlySpeed <p>The fly-speed of the player before entering the arena</p>
* @param horizontalVelocity <p>The horizontal velocity of the player before entering the arena</p>
*/
public DropperPlayerEntryState(@NotNull UUID playerId, boolean makePlayerInvisible, Location entryLocation,
public DropperPlayerEntryState(@NotNull UUID playerId, Location entryLocation,
boolean originalIsFlying, GameMode originalGameMode, boolean originalAllowFlight,
boolean originalInvulnerable, boolean originalIsSwimming,
boolean originalCollideAble, float originalFlySpeed, boolean disableHitCollision,
float horizontalVelocity, DropperArenaGameMode arenaGameMode) {
super(playerId, makePlayerInvisible, entryLocation, originalIsFlying, originalGameMode, originalAllowFlight,
originalInvulnerable, originalIsSwimming, originalCollideAble);
float originalFlySpeed, float horizontalVelocity,
DropperArenaGameMode arenaGameMode) {
super(playerId, entryLocation, originalIsFlying, originalGameMode, originalAllowFlight,
originalInvulnerable, originalIsSwimming);
this.originalFlySpeed = originalFlySpeed;
this.disableHitCollision = disableHitCollision;
this.horizontalVelocity = horizontalVelocity;
this.arenaGameMode = arenaGameMode;
}
@Override
public void setArenaState() {
super.setArenaState();
Player player = getPlayer();
if (player == null) {
return;
@@ -71,9 +67,6 @@ public class DropperPlayerEntryState extends AbstractPlayerEntryState {
player.setFlying(true);
player.setGameMode(GameMode.ADVENTURE);
player.setSwimming(false);
if (this.disableHitCollision) {
player.setCollidable(false);
}
// If playing on the inverted game-mode, negate the horizontal velocity to swap the controls
if (this.arenaGameMode == DropperArenaGameMode.INVERTED) {
@@ -104,7 +97,6 @@ public class DropperPlayerEntryState extends AbstractPlayerEntryState {
public Map<String, Object> serialize() {
Map<String, Object> data = super.serialize();
data.put("originalFlySpeed", this.originalFlySpeed);
data.put("disableHitCollision", this.disableHitCollision);
data.put("horizontalVelocity", this.horizontalVelocity);
data.put("arenaGameMode", this.arenaGameMode);
return data;
@@ -118,22 +110,19 @@ public class DropperPlayerEntryState extends AbstractPlayerEntryState {
@SuppressWarnings("unused")
public static DropperPlayerEntryState deserialize(Map<String, Object> data) {
UUID playerId = ((SerializableUUID) data.get("playerId")).getRawValue();
boolean makePlayerInvisible = (boolean) data.get("makePlayerInvisible");
Location entryLocation = (Location) data.get("entryLocation");
boolean originalIsFlying = (boolean) data.get("originalIsFlying");
GameMode originalGameMode = GameMode.valueOf((String) data.get("originalGameMode"));
boolean originalAllowFlight = (boolean) data.get("originalAllowFlight");
boolean originalInvulnerable = (boolean) data.get("originalInvulnerable");
boolean originalIsSwimming = (boolean) data.get("originalIsSwimming");
boolean originalCollideAble = (boolean) data.get("originalCollideAble");
float originalFlySpeed = ((Number) data.get("originalFlySpeed")).floatValue();
boolean disableHitCollision = (boolean) data.get("disableHitCollision");
float horizontalVelocity = ((Number) data.get("horizontalVelocity")).floatValue();
DropperArenaGameMode arenaGameMode = (DropperArenaGameMode) data.get("arenaGameMode");
return new DropperPlayerEntryState(playerId, makePlayerInvisible, entryLocation, originalIsFlying,
originalGameMode, originalAllowFlight, originalInvulnerable, originalIsSwimming, originalCollideAble,
originalFlySpeed, disableHitCollision, horizontalVelocity, arenaGameMode);
return new DropperPlayerEntryState(playerId, entryLocation, originalIsFlying,
originalGameMode, originalAllowFlight, originalInvulnerable, originalIsSwimming,
originalFlySpeed, horizontalVelocity, arenaGameMode);
}
}

View File

@@ -4,7 +4,6 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.AbstractArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.gui.ArenaGUI;
import net.knarcraft.minigames.gui.ParkourGUI;
import net.knarcraft.minigames.util.PlayerTeleporter;
@@ -41,9 +40,7 @@ public class ParkourArenaSession extends AbstractArenaSession {
this.player = player;
this.gameMode = gameMode;
ParkourConfiguration configuration = MiniGames.getInstance().getParkourConfiguration();
boolean makeInvisible = configuration.makePlayersInvisible();
this.entryState = new ParkourPlayerEntryState(player, makeInvisible);
this.entryState = new ParkourPlayerEntryState(player);
this.entryState.setArenaState();
}

View File

@@ -20,34 +20,30 @@ public class ParkourPlayerEntryState extends AbstractPlayerEntryState {
*
* @param player <p>The player whose state should be stored</p>
*/
public ParkourPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) {
super(player, makePlayerInvisible);
public ParkourPlayerEntryState(@NotNull Player player) {
super(player);
}
/**
* Instantiates a new parkour player entry state
*
* @param playerId <p>The id of the player whose state this should keep track of</p>
* @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p>
* @param entryLocation <p>The location the player entered from</p>
* @param originalIsFlying <p>Whether the player was flying before entering the arena</p>
* @param originalGameMode <p>The game-mode of the player before entering the arena</p>
* @param originalAllowFlight <p>Whether the player was allowed flight before entering the arena</p>
* @param originalInvulnerable <p>Whether the player was invulnerable before entering the arena</p>
* @param originalIsSwimming <p>Whether the player was swimming before entering the arena</p>
* @param originalCollideAble <p>Whether the player was collide-able before entering the arena</p>
*/
public ParkourPlayerEntryState(@NotNull UUID playerId, boolean makePlayerInvisible, Location entryLocation,
public ParkourPlayerEntryState(@NotNull UUID playerId, Location entryLocation,
boolean originalIsFlying, GameMode originalGameMode, boolean originalAllowFlight,
boolean originalInvulnerable, boolean originalIsSwimming,
boolean originalCollideAble) {
super(playerId, makePlayerInvisible, entryLocation, originalIsFlying, originalGameMode, originalAllowFlight,
originalInvulnerable, originalIsSwimming, originalCollideAble);
boolean originalInvulnerable, boolean originalIsSwimming) {
super(playerId, entryLocation, originalIsFlying, originalGameMode, originalAllowFlight,
originalInvulnerable, originalIsSwimming);
}
@Override
public void setArenaState() {
super.setArenaState();
Player player = getPlayer();
if (player == null) {
return;
@@ -56,7 +52,6 @@ public class ParkourPlayerEntryState extends AbstractPlayerEntryState {
player.setFlying(false);
player.setGameMode(GameMode.ADVENTURE);
player.setSwimming(false);
player.setCollidable(false);
}
/**
@@ -67,17 +62,15 @@ public class ParkourPlayerEntryState extends AbstractPlayerEntryState {
@SuppressWarnings("unused")
public static ParkourPlayerEntryState deserialize(Map<String, Object> data) {
UUID playerId = ((SerializableUUID) data.get("playerId")).getRawValue();
boolean makePlayerInvisible = (boolean) data.get("makePlayerInvisible");
Location entryLocation = (Location) data.get("entryLocation");
boolean originalIsFlying = (boolean) data.get("originalIsFlying");
GameMode originalGameMode = GameMode.valueOf((String) data.get("originalGameMode"));
boolean originalAllowFlight = (boolean) data.get("originalAllowFlight");
boolean originalInvulnerable = (boolean) data.get("originalInvulnerable");
boolean originalIsSwimming = (boolean) data.get("originalIsSwimming");
boolean originalCollideAble = (boolean) data.get("originalCollideAble");
return new ParkourPlayerEntryState(playerId, makePlayerInvisible, entryLocation, originalIsFlying,
originalGameMode, originalAllowFlight, originalInvulnerable, originalIsSwimming, originalCollideAble);
return new ParkourPlayerEntryState(playerId, entryLocation, originalIsFlying,
originalGameMode, originalAllowFlight, originalInvulnerable, originalIsSwimming);
}
}