14 Commits

60 changed files with 1633 additions and 696 deletions

View File

@ -1,12 +1,18 @@
# MiniGames # MiniGames
This plugin adds several mini-games. This plugin adds several mini-games.
To create a dropper arena, simply use `/droppercreate <name>`, where \<name> is simply the name used to differentiate To create a dropper arena, simply use `/droppercreate <name>`, where \<name> is simply the name used to differentiate
and and recognize the arena. Your location will be used as the spawn location for anyone joining the dropper arena. To start
recognize the arena. Your location will be used as the spawn location for anyone joining the dropper arena. To start
playing, simply use `/dropperjoin <name>`, where \<name> is the same as you specified upon creation. playing, simply use `/dropperjoin <name>`, where \<name> is the same as you specified upon creation.
To modify the arena, use `/dropperedit <name> <property> <value>`. To modify the arena, use `/dropperedit <name> <property> <value>`.
To create a parkour arena, simply use `/parkourcreate <name>`, where \<name> is simply the name used to differentiate
and recognize the arena. Your location will be used as the spawn location for anyone joining the dropper arena. To start
playing, simply use `/parkourjoin <name>`, where \<name> is the same as you specified upon creation.
To modify the arena, use `/parkouredit <name> <property> <value>`. Use `/parkouredit checkpointAdd here` to add a
checkpoint at your current location.
## Permissions ## Permissions
The only permission normal players will need is `minigames.join` which is set to true by default. The only permission normal players will need is `minigames.join` which is set to true by default.
@ -243,3 +249,8 @@ Example tags:
- +DRAGON_IMMUNE - +DRAGON_IMMUNE
- +FENCE_GATES - +FENCE_GATES
- +FENCES - +FENCES
## License
MiniGames is licensed under the GNU Public License Version 3.0. This includes every source and resource file. See the
HEADER file for a more detailed license description.

View File

@ -1,19 +1,23 @@
package net.knarcraft.minigames; package net.knarcraft.minigames;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.arena.ArenaSession; import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaData; import net.knarcraft.minigames.arena.dropper.DropperArenaData;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode; import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup; import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler; import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.arena.dropper.DropperArenaPlayerRegistry; import net.knarcraft.minigames.arena.dropper.DropperArenaPlayerRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry; import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaSession; import net.knarcraft.minigames.arena.dropper.DropperPlayerEntryState;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaData; import net.knarcraft.minigames.arena.parkour.ParkourArenaData;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode; import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup; import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler; import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.arena.parkour.ParkourArenaPlayerRegistry; import net.knarcraft.minigames.arena.parkour.ParkourArenaPlayerRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArenaRecordsRegistry; import net.knarcraft.minigames.arena.parkour.ParkourArenaRecordsRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourPlayerEntryState;
import net.knarcraft.minigames.arena.record.IntegerRecord; import net.knarcraft.minigames.arena.record.IntegerRecord;
import net.knarcraft.minigames.arena.record.LongRecord; import net.knarcraft.minigames.arena.record.LongRecord;
import net.knarcraft.minigames.command.LeaveArenaCommand; import net.knarcraft.minigames.command.LeaveArenaCommand;
@ -48,7 +52,7 @@ import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.listener.CommandListener; import net.knarcraft.minigames.listener.CommandListener;
import net.knarcraft.minigames.listener.DamageListener; import net.knarcraft.minigames.listener.DamageListener;
import net.knarcraft.minigames.listener.MoveListener; import net.knarcraft.minigames.listener.MoveListener;
import net.knarcraft.minigames.listener.PlayerLeaveListener; import net.knarcraft.minigames.listener.PlayerStateChangeListener;
import net.knarcraft.minigames.placeholder.DropperRecordExpansion; import net.knarcraft.minigames.placeholder.DropperRecordExpansion;
import net.knarcraft.minigames.placeholder.ParkourRecordExpansion; import net.knarcraft.minigames.placeholder.ParkourRecordExpansion;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -56,7 +60,6 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -76,11 +79,11 @@ public final class MiniGames extends JavaPlugin {
private DropperConfiguration dropperConfiguration; private DropperConfiguration dropperConfiguration;
private ParkourConfiguration parkourConfiguration; private ParkourConfiguration parkourConfiguration;
private DropperArenaHandler dropperArenaHandler; private DropperArenaHandler dropperArenaHandler;
private DropperArenaPlayerRegistry dropperArenaPlayerRegistry; private ArenaPlayerRegistry<DropperArena> dropperArenaPlayerRegistry;
private DropperRecordExpansion dropperRecordExpansion; private DropperRecordExpansion dropperRecordExpansion;
private ParkourRecordExpansion parkourRecordExpansion; private ParkourRecordExpansion parkourRecordExpansion;
private ParkourArenaHandler parkourArenaHandler; private ParkourArenaHandler parkourArenaHandler;
private ParkourArenaPlayerRegistry parkourArenaPlayerRegistry; private ArenaPlayerRegistry<ParkourArena> parkourArenaPlayerRegistry;
/** /**
* Gets an instance of this plugin * Gets an instance of this plugin
@ -114,7 +117,7 @@ public final class MiniGames extends JavaPlugin {
* *
* @return <p>A dropper arena player registry</p> * @return <p>A dropper arena player registry</p>
*/ */
public DropperArenaPlayerRegistry getDropperArenaPlayerRegistry() { public ArenaPlayerRegistry<DropperArena> getDropperArenaPlayerRegistry() {
return this.dropperArenaPlayerRegistry; return this.dropperArenaPlayerRegistry;
} }
@ -123,7 +126,7 @@ public final class MiniGames extends JavaPlugin {
* *
* @return <p>A parkour arena player registry</p> * @return <p>A parkour arena player registry</p>
*/ */
public ParkourArenaPlayerRegistry getParkourArenaPlayerRegistry() { public ArenaPlayerRegistry<ParkourArena> getParkourArenaPlayerRegistry() {
return this.parkourArenaPlayerRegistry; return this.parkourArenaPlayerRegistry;
} }
@ -163,7 +166,7 @@ public final class MiniGames extends JavaPlugin {
* @return <p>The player's current session, or null if not found</p> * @return <p>The player's current session, or null if not found</p>
*/ */
public @Nullable ArenaSession getSession(@NotNull UUID playerId) { public @Nullable ArenaSession getSession(@NotNull UUID playerId) {
DropperArenaSession dropperArenaSession = dropperArenaPlayerRegistry.getArenaSession(playerId); ArenaSession dropperArenaSession = dropperArenaPlayerRegistry.getArenaSession(playerId);
if (dropperArenaSession != null) { if (dropperArenaSession != null) {
return dropperArenaSession; return dropperArenaSession;
} }
@ -216,6 +219,8 @@ public final class MiniGames extends JavaPlugin {
ConfigurationSerialization.registerClass(ParkourArenaData.class); ConfigurationSerialization.registerClass(ParkourArenaData.class);
ConfigurationSerialization.registerClass(ParkourArenaGroup.class); ConfigurationSerialization.registerClass(ParkourArenaGroup.class);
ConfigurationSerialization.registerClass(ParkourArenaGameMode.class); ConfigurationSerialization.registerClass(ParkourArenaGameMode.class);
ConfigurationSerialization.registerClass(DropperPlayerEntryState.class);
ConfigurationSerialization.registerClass(ParkourPlayerEntryState.class);
} }
@Override @Override
@ -239,7 +244,7 @@ public final class MiniGames extends JavaPlugin {
PluginManager pluginManager = getServer().getPluginManager(); PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new DamageListener(), this); pluginManager.registerEvents(new DamageListener(), this);
pluginManager.registerEvents(new MoveListener(this.dropperConfiguration, this.parkourConfiguration), this); pluginManager.registerEvents(new MoveListener(this.dropperConfiguration, this.parkourConfiguration), this);
pluginManager.registerEvents(new PlayerLeaveListener(), this); pluginManager.registerEvents(new PlayerStateChangeListener(), this);
pluginManager.registerEvents(new CommandListener(), this); pluginManager.registerEvents(new CommandListener(), this);
registerCommand("miniGamesReload", new ReloadCommand(), null); registerCommand("miniGamesReload", new ReloadCommand(), null);
@ -277,12 +282,12 @@ public final class MiniGames extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
// Throw out currently playing players before exiting // Kill all sessions before exiting
for (Player player : getServer().getOnlinePlayers()) { for (DropperArena arena : dropperArenaHandler.getArenas().values()) {
ArenaSession session = getSession(player.getUniqueId()); dropperArenaPlayerRegistry.removeForArena(arena, true);
if (session != null) { }
session.triggerQuit(true); for (ParkourArena arena : parkourArenaHandler.getArenas().values()) {
} parkourArenaPlayerRegistry.removeForArena(arena, true);
} }
} }

View File

@ -0,0 +1,107 @@
package net.knarcraft.minigames.arena;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.util.ArenaStorageHelper;
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.logging.Level;
/**
* A player registry to keep track of currently playing players
*
* @param <K> <p>The type of arena stored</p>
*/
public abstract class AbstractArenaPlayerRegistry<K extends Arena> implements ArenaPlayerRegistry<K> {
private final Map<UUID, ArenaSession> arenaPlayers = new HashMap<>();
private final Map<UUID, PlayerEntryState> entryStates = new HashMap<>();
/**
* Instantiates a new arena player registry
*/
public AbstractArenaPlayerRegistry() {
loadEntryStates();
}
@Override
public @Nullable PlayerEntryState getEntryState(@NotNull UUID playerId) {
return this.entryStates.get(playerId);
}
@Override
public void registerPlayer(@NotNull UUID playerId, @NotNull ArenaSession arenaSession) {
this.arenaPlayers.put(playerId, arenaSession);
this.entryStates.put(playerId, arenaSession.getEntryState());
this.saveEntryStates();
}
@Override
public boolean removePlayer(@NotNull UUID playerId, boolean restoreState) {
// Try and restore the state. If it cannot be restored, retain the entry state
PlayerEntryState entryState = this.entryStates.remove(playerId);
if (restoreState) {
if (entryState.restore()) {
this.saveEntryStates();
} else {
this.entryStates.put(playerId, entryState);
}
} else {
this.saveEntryStates();
}
return this.arenaPlayers.remove(playerId) != null;
}
@Override
public @Nullable ArenaSession getArenaSession(@NotNull UUID playerId) {
return this.arenaPlayers.getOrDefault(playerId, null);
}
@Override
public void removeForArena(K arena, boolean immediately) {
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());
}
}
}
/**
* Gets a string key unique to this type of player registry
*
* @return <p>A unique key used for entry state storage</p>
*/
protected abstract String getEntryStateStorageKey();
/**
* Saves all entry states to disk
*/
private void saveEntryStates() {
ArenaStorageHelper.storeArenaPlayerEntryStates(getEntryStateStorageKey(), new HashSet<>(entryStates.values()));
}
/**
* Loads all entry states from disk
*/
private void loadEntryStates() {
this.entryStates.clear();
Set<PlayerEntryState> entryStates = ArenaStorageHelper.getArenaPlayerEntryStates(getEntryStateStorageKey());
for (PlayerEntryState entryState : entryStates) {
this.entryStates.put(entryState.getPlayerId(), entryState);
}
if (this.entryStates.size() > 0) {
MiniGames.log(Level.WARNING, entryStates.size() + " un-exited sessions found. This happens if " +
"players leave in the middle of a game, or if the server crashes. MiniGames will do its best " +
"to fix the players' states.");
}
}
}

View File

@ -0,0 +1,107 @@
package net.knarcraft.minigames.arena;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.container.PlaceholderContainer;
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;
public abstract class AbstractArenaSession implements ArenaSession {
private final @NotNull Arena arena;
private final @NotNull ArenaGameMode gameMode;
private final @NotNull Player player;
protected int deaths;
protected final long startTime;
protected PlayerEntryState entryState;
/**
* Instantiates a new abstract arena session
*
* @param arena <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 AbstractArenaSession(@NotNull Arena arena, @NotNull Player player, @NotNull ArenaGameMode gameMode) {
this.arena = arena;
this.player = player;
this.gameMode = gameMode;
this.deaths = 0;
this.startTime = System.currentTimeMillis();
}
@Override
public void triggerQuit(boolean immediately) {
// Stop this session
removeSession();
// Teleport the player out of the arena
teleportToExit(immediately);
player.sendMessage(Message.SUCCESS_ARENA_QUIT.getMessage());
}
/**
* 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>
*/
protected void announceRecord(@NotNull RecordResult recordResult, @NotNull String type) {
if (recordResult == RecordResult.NONE) {
return;
}
// Gets a string representation of the played game-mode
String gameModeString = getGameModeString();
Message recordInfoMessage = switch (recordResult) {
case WORLD_RECORD -> Message.RECORD_ACHIEVED_GLOBAL;
case PERSONAL_BEST -> Message.RECORD_ACHIEVED_PERSONAL;
default -> throw new IllegalStateException("Unexpected value: " + recordResult);
};
String recordInfo = recordInfoMessage.getPartialMessage("{recordType}", type);
PlaceholderContainer placeholderContainer = new PlaceholderContainer().add("{gameMode}", gameModeString);
placeholderContainer.add("{recordInfo}", recordInfo);
player.sendMessage(Message.SUCCESS_RECORD_ACHIEVED.getMessage(placeholderContainer));
}
/**
* Registers the player's record if necessary, and prints record information to the player
*/
protected 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");
}
/**
* Teleports the playing player out of the arena
*/
protected 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);
}
/**
* Gets the string representation of the session's game-mode
*
* @return <p>The string representation</p>
*/
protected abstract String getGameModeString();
/**
* Removes this session from current sessions
*/
protected abstract void removeSession();
}

View File

@ -1,5 +1,8 @@
package net.knarcraft.minigames.arena; package net.knarcraft.minigames.arena;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -7,12 +10,17 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
/** /**
* An abstract representation of a player's entry state * An abstract representation of a player's entry state
*/ */
public abstract class AbstractPlayerEntryState implements PlayerEntryState { public abstract class AbstractPlayerEntryState implements PlayerEntryState {
protected final Player player; protected final UUID playerId;
private final boolean makePlayerInvisible; private final boolean makePlayerInvisible;
private final Location entryLocation; private final Location entryLocation;
private final boolean originalIsFlying; private final boolean originalIsFlying;
@ -29,7 +37,7 @@ public abstract class AbstractPlayerEntryState implements PlayerEntryState {
* @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p> * @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p>
*/ */
public AbstractPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) { public AbstractPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) {
this.player = player; this.playerId = player.getUniqueId();
this.makePlayerInvisible = makePlayerInvisible; this.makePlayerInvisible = makePlayerInvisible;
this.entryLocation = player.getLocation().clone(); this.entryLocation = player.getLocation().clone();
this.originalIsFlying = player.isFlying(); this.originalIsFlying = player.isFlying();
@ -40,24 +48,71 @@ public abstract class AbstractPlayerEntryState implements PlayerEntryState {
this.originalCollideAble = player.isCollidable(); 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,
boolean originalIsFlying, GameMode originalGameMode, boolean originalAllowFlight,
boolean originalInvulnerable, boolean originalIsSwimming,
boolean originalCollideAble) {
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
public @NotNull UUID getPlayerId() {
return this.playerId;
}
@Override @Override
public void setArenaState() { public void setArenaState() {
Player player = getPlayer();
if (player == null) {
return;
}
if (this.makePlayerInvisible) { if (this.makePlayerInvisible) {
this.player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,
PotionEffect.INFINITE_DURATION, 3)); PotionEffect.INFINITE_DURATION, 3));
} }
} }
@Override @Override
public void restore() { public boolean restore() {
this.player.setFlying(this.originalIsFlying); Player player = getPlayer();
this.player.setGameMode(this.originalGameMode); if (player == null) {
this.player.setAllowFlight(this.originalAllowFlight); return false;
this.player.setInvulnerable(this.originalInvulnerable); }
this.player.setSwimming(this.originalIsSwimming); restore(player);
this.player.setCollidable(this.originalCollideAble); return true;
}
@Override
public void restore(@NotNull Player player) {
player.setFlying(this.originalIsFlying);
player.setGameMode(this.originalGameMode);
player.setAllowFlight(this.originalAllowFlight);
player.setInvulnerable(this.originalInvulnerable);
player.setSwimming(this.originalIsSwimming);
player.setCollidable(this.originalCollideAble);
if (this.makePlayerInvisible) { if (this.makePlayerInvisible) {
this.player.removePotionEffect(PotionEffectType.INVISIBILITY); player.removePotionEffect(PotionEffectType.INVISIBILITY);
} }
} }
@ -66,4 +121,34 @@ public abstract class AbstractPlayerEntryState implements PlayerEntryState {
return this.entryLocation; return this.entryLocation;
} }
/**
* Gets the player this entry state belongs to
*
* @return <p>The player, or null if not currently online</p>
*/
protected Player getPlayer() {
Player player = Bukkit.getOfflinePlayer(this.playerId).getPlayer();
if (player == null) {
MiniGames.log(Level.WARNING, "Unable to change state for player with id " + this.playerId +
" because the player was not found on the server.");
}
return player;
}
@NotNull
@Override
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.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
@ -83,4 +84,11 @@ public interface Arena {
*/ */
@NotNull Location getSpawnLocation(); @NotNull Location getSpawnLocation();
/**
* Gets this arena's exit location
*
* @return <p>This arena's exit location, or null if no such location is set.</p>
*/
@Nullable Location getExitLocation();
} }

View File

@ -170,7 +170,7 @@ public abstract class ArenaHandler<K extends Arena, S extends ArenaGroup<K, S>>
*/ */
public void removeArena(@NotNull K arena) { public void removeArena(@NotNull K arena) {
UUID arenaId = arena.getArenaId(); UUID arenaId = arena.getArenaId();
this.playerRegistry.removeForArena(arena); this.playerRegistry.removeForArena(arena, false);
this.arenas.remove(arenaId); this.arenas.remove(arenaId);
this.arenaNameLookup.remove(arena.getArenaNameSanitized()); this.arenaNameLookup.remove(arena.getArenaNameSanitized());
this.arenaGroups.remove(arenaId); this.arenaGroups.remove(arenaId);

View File

@ -1,5 +1,10 @@
package net.knarcraft.minigames.arena; package net.knarcraft.minigames.arena;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/** /**
* A registry keeping track of all player sessions for some arenas * A registry keeping track of all player sessions for some arenas
* *
@ -7,11 +12,44 @@ package net.knarcraft.minigames.arena;
*/ */
public interface ArenaPlayerRegistry<K extends Arena> { public interface ArenaPlayerRegistry<K extends Arena> {
/**
* Gets the current entry state for the given player
*
* @param playerId <p>The id of the player to get an entry state for</p>
* @return <p>The entry state of the player, or null if not found</p>
*/
@Nullable PlayerEntryState getEntryState(@NotNull UUID playerId);
/**
* 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 arenaSession <p>The arena session to register</p>
*/
void registerPlayer(@NotNull UUID playerId, @NotNull ArenaSession arenaSession);
/**
* Removes this player from players currently playing
*
* @param playerId <p>The id of the player to remove</p>
* @param restoreState <p>Whether to restore the state of the player as part of the removal</p>
*/
boolean removePlayer(@NotNull UUID playerId, boolean restoreState);
/**
* 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>
*/
@Nullable ArenaSession getArenaSession(@NotNull UUID playerId);
/** /**
* Removes all active sessions for the given arena * Removes all active sessions for the given arena
* *
* @param arena <p>The arena to remove sessions for</p> * @param arena <p>The arena to remove sessions for</p>
* @param immediately <p>Whether to immediately teleport the player</p>
*/ */
void removeForArena(K arena); void removeForArena(K arena, boolean immediately);
} }

View File

@ -1,6 +1,5 @@
package net.knarcraft.minigames.arena; package net.knarcraft.minigames.arena;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@ -8,13 +7,6 @@ import org.jetbrains.annotations.NotNull;
*/ */
public interface ArenaSession { public interface ArenaSession {
/**
* Gets the game-mode the player is playing in this session
*
* @return <p>The game-mode for this session</p>
*/
@NotNull ArenaGameMode getGameMode();
/** /**
* Gets the state of the player when they joined the session * Gets the state of the player when they joined the session
* *
@ -46,11 +38,4 @@ public interface ArenaSession {
*/ */
@NotNull Arena getArena(); @NotNull Arena getArena();
/**
* Gets the player playing in this session
*
* @return <p>This session's player</p>
*/
@NotNull Player getPlayer();
} }

View File

@ -0,0 +1,43 @@
package net.knarcraft.minigames.arena;
/**
* The type of one editable property
*/
public enum EditablePropertyType {
/**
* The property is a location
*/
LOCATION,
/**
* The property is an arena name
*/
ARENA_NAME,
/**
* The property is a horizontal velocity
*/
HORIZONTAL_VELOCITY,
/**
* The property is a vertical velocity (fly speed)
*/
VERTICAL_VELOCITY,
/**
* The property is a material that specifies a block
*/
BLOCK_TYPE,
/**
* The property clears a checkpoint
*/
CHECKPOINT_CLEAR,
/**
* The property is a comma-separated list of materials
*/
MATERIAL_LIST
}

View File

@ -1,11 +1,15 @@
package net.knarcraft.minigames.arena; package net.knarcraft.minigames.arena;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import java.util.UUID;
/** /**
* The stored state of a player * The stored state of a player
*/ */
public interface PlayerEntryState { public interface PlayerEntryState extends ConfigurationSerializable {
/** /**
* Sets the state of the stored player to the state used by the arena * Sets the state of the stored player to the state used by the arena
@ -15,7 +19,21 @@ public interface PlayerEntryState {
/** /**
* Restores the stored state for the stored player * Restores the stored state for the stored player
*/ */
void restore(); boolean restore();
/**
* Restores the stored state for the given player
*
* @param player <p>A player object that's refers to the same player as the stored player</p>
*/
void restore(Player player);
/**
* Gets the id of the player this state belongs to
*
* @return <p>The player the state belongs to</p>
*/
UUID getPlayerId();
/** /**
* Gets the location the player entered from * Gets the location the player entered from

View File

@ -150,11 +150,7 @@ public class DropperArena implements Arena {
return this.spawnLocation.clone(); return this.spawnLocation.clone();
} }
/** @Override
* 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() { public @Nullable Location getExitLocation() {
return this.exitLocation != null ? this.exitLocation.clone() : null; return this.exitLocation != null ? this.exitLocation.clone() : null;
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.minigames.arena.dropper; package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.arena.EditablePropertyType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -13,45 +14,62 @@ public enum DropperArenaEditableProperty {
/** /**
* The name of the arena * The name of the arena
*/ */
NAME("name", DropperArena::getArenaName), NAME("name", DropperArena::getArenaName, EditablePropertyType.ARENA_NAME),
/** /**
* The arena's spawn location * The arena's spawn location
*/ */
SPAWN_LOCATION("spawnLocation", (arena) -> String.valueOf(arena.getSpawnLocation())), SPAWN_LOCATION("spawnLocation", (arena) -> String.valueOf(arena.getSpawnLocation()),
EditablePropertyType.LOCATION),
/** /**
* The arena's exit location * The arena's exit location
*/ */
EXIT_LOCATION("exitLocation", (arena) -> String.valueOf(arena.getExitLocation())), EXIT_LOCATION("exitLocation", (arena) -> String.valueOf(arena.getExitLocation()),
EditablePropertyType.LOCATION),
/** /**
* The arena's vertical velocity * The arena's vertical velocity
*/ */
VERTICAL_VELOCITY("verticalVelocity", (arena) -> String.valueOf(arena.getPlayerVerticalVelocity())), VERTICAL_VELOCITY("verticalVelocity", (arena) -> String.valueOf(arena.getPlayerVerticalVelocity()),
EditablePropertyType.VERTICAL_VELOCITY),
/** /**
* The arena's horizontal velocity * The arena's horizontal velocity
*/ */
HORIZONTAL_VELOCITY("horizontalVelocity", (arena) -> String.valueOf(arena.getPlayerHorizontalVelocity())), HORIZONTAL_VELOCITY("horizontalVelocity", (arena) -> String.valueOf(arena.getPlayerHorizontalVelocity()),
EditablePropertyType.HORIZONTAL_VELOCITY),
/** /**
* The arena's win block type * The arena's win block type
*/ */
WIN_BLOCK_TYPE("winBlockType", (arena) -> arena.getWinBlockType().toString()), WIN_BLOCK_TYPE("winBlockType", (arena) -> arena.getWinBlockType().toString(),
EditablePropertyType.BLOCK_TYPE),
; ;
private final @NotNull String argumentString; private final @NotNull String argumentString;
private final Function<DropperArena, String> currentValueProvider; private final Function<DropperArena, String> currentValueProvider;
private final EditablePropertyType propertyType;
/** /**
* Instantiates a new arena editable property * Instantiates a new arena editable property
* *
* @param argumentString <p>The argument string used to specify this property</p> * @param argumentString <p>The argument string used to specify this property</p>
*/ */
DropperArenaEditableProperty(@NotNull String argumentString, Function<DropperArena, String> currentValueProvider) { DropperArenaEditableProperty(@NotNull String argumentString, Function<DropperArena, String> currentValueProvider,
EditablePropertyType propertyType) {
this.argumentString = argumentString; this.argumentString = argumentString;
this.currentValueProvider = currentValueProvider; this.currentValueProvider = currentValueProvider;
this.propertyType = propertyType;
}
/**
* Gets the type of property this editable property represents
*
* @return <p>The type of this property</p>
*/
public EditablePropertyType getPropertyType() {
return this.propertyType;
} }
/** /**

View File

@ -2,6 +2,8 @@ package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaHandler; import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.DropperArenaStorageHelper; import net.knarcraft.minigames.util.DropperArenaStorageHelper;
import java.io.IOException; import java.io.IOException;
@ -22,7 +24,7 @@ public class DropperArenaHandler extends ArenaHandler<DropperArena, DropperArena
* *
* @param playerRegistry <p>The registry keeping track of player sessions</p> * @param playerRegistry <p>The registry keeping track of player sessions</p>
*/ */
public DropperArenaHandler(DropperArenaPlayerRegistry playerRegistry) { public DropperArenaHandler(ArenaPlayerRegistry<DropperArena> playerRegistry) {
super(playerRegistry); super(playerRegistry);
} }
@ -31,8 +33,7 @@ public class DropperArenaHandler extends ArenaHandler<DropperArena, DropperArena
try { try {
DropperArenaStorageHelper.saveDropperArenaGroups(new HashSet<>(this.arenaGroups.values())); DropperArenaStorageHelper.saveDropperArenaGroups(new HashSet<>(this.arenaGroups.values()));
} catch (IOException e) { } catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save current arena groups! " + MiniGames.log(Level.SEVERE, Message.ERROR_CANNOT_SAVE_ARENA_GROUPS.getMessage());
"Data loss can occur!");
MiniGames.log(Level.SEVERE, e.getMessage()); MiniGames.log(Level.SEVERE, e.getMessage());
} }
} }

View File

@ -1,62 +1,15 @@
package net.knarcraft.minigames.arena.dropper; package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry; import net.knarcraft.minigames.arena.AbstractArenaPlayerRegistry;
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 * A registry to keep track of which players are playing in which arenas
*/ */
public class DropperArenaPlayerRegistry implements ArenaPlayerRegistry<DropperArena> { public class DropperArenaPlayerRegistry extends AbstractArenaPlayerRegistry<DropperArena> {
private final Map<UUID, DropperArenaSession> arenaPlayers = new HashMap<>(); @Override
protected String getEntryStateStorageKey() {
/** return "dropper";
* 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());
}
}
} }
} }

View File

@ -1,13 +1,11 @@
package net.knarcraft.minigames.arena.dropper; package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry; import net.knarcraft.minigames.arena.AbstractArenaSession;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState; import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.config.DropperConfiguration; import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.property.RecordResult; import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.PlayerTeleporter; import net.knarcraft.minigames.util.PlayerTeleporter;
import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -16,14 +14,11 @@ import java.util.logging.Level;
/** /**
* A representation of a player's current session in a dropper arena * A representation of a player's current session in a dropper arena
*/ */
public class DropperArenaSession implements ArenaSession { public class DropperArenaSession extends AbstractArenaSession {
private final @NotNull DropperArena arena; private final @NotNull DropperArena arena;
private final @NotNull Player player; private final @NotNull Player player;
private final @NotNull DropperArenaGameMode gameMode; private final @NotNull DropperArenaGameMode gameMode;
private int deaths;
private final long startTime;
private final PlayerEntryState entryState;
/** /**
* Instantiates a new dropper arena session * Instantiates a new dropper arena session
@ -34,173 +29,19 @@ public class DropperArenaSession implements ArenaSession {
*/ */
public DropperArenaSession(@NotNull DropperArena dropperArena, @NotNull Player player, public DropperArenaSession(@NotNull DropperArena dropperArena, @NotNull Player player,
@NotNull DropperArenaGameMode gameMode) { @NotNull DropperArenaGameMode gameMode) {
super(dropperArena, player, gameMode);
this.arena = dropperArena; this.arena = dropperArena;
this.player = player; this.player = player;
this.gameMode = gameMode; this.gameMode = gameMode;
this.deaths = 0;
this.startTime = System.currentTimeMillis();
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration(); DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
boolean makeInvisible = configuration.makePlayersInvisible(); boolean makeInvisible = configuration.makePlayersInvisible();
boolean disableCollision = configuration.disableHitCollision(); boolean disableCollision = configuration.disableHitCollision();
this.entryState = new DropperPlayerEntryState(player, gameMode, makeInvisible, disableCollision, this.entryState = new DropperPlayerEntryState(player, gameMode, makeInvisible, disableCollision,
dropperArena.getPlayerHorizontalVelocity()); dropperArena.getPlayerHorizontalVelocity());
// Make the player fly to improve mobility in the air
this.entryState.setArenaState(); this.entryState.setArenaState();
} }
/**
* 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 PlayerEntryState 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
*
* @param immediately <p>Whether to to the teleportation immediately, not using any timers</p>
*/
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();
}
/**
* Triggers a quit for the player playing in this session
*
* @param immediately <p>Whether to to the teleportation immediately, not using any timers</p>
*/
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 * Gets the player playing in this session
* *
@ -210,4 +51,74 @@ public class DropperArenaSession implements ArenaSession {
return this.player; return this.player;
} }
/**
* 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;
}
@Override
public @NotNull PlayerEntryState getEntryState() {
return this.entryState;
}
@Override
public void triggerWin() {
// Stop this session
removeSession();
// 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(Message.SUCCESS_ARENA_FIRST_CLEAR.getMessage());
}
this.player.sendMessage(Message.SUCCESS_ARENA_WIN.getMessage());
// Teleport the player out of the arena
teleportToExit(false);
}
@Override
public void triggerLoss() {
this.deaths++;
//Teleport the player back to the top
PlayerTeleporter.teleportPlayer(this.player, this.arena.getSpawnLocation(), true, false);
this.entryState.setArenaState();
}
@Override
public @NotNull DropperArena getArena() {
return this.arena;
}
@Override
protected 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(), true);
if (!removedSession) {
MiniGames.log(Level.SEVERE, "Unable to remove dropper arena session for " + player.getName() + ". " +
"This will have unintended consequences.");
}
}
@Override
protected String getGameModeString() {
return switch (this.gameMode) {
case DEFAULT -> "default";
case INVERTED -> "inverted";
case RANDOM_INVERTED -> "random";
};
}
} }

View File

@ -1,10 +1,15 @@
package net.knarcraft.minigames.arena.dropper; package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.arena.AbstractPlayerEntryState; import net.knarcraft.minigames.arena.AbstractPlayerEntryState;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.UUID;
/** /**
* The state of a player before entering a dropper arena * The state of a player before entering a dropper arena
*/ */
@ -29,29 +34,106 @@ public class DropperPlayerEntryState extends AbstractPlayerEntryState {
this.horizontalVelocity = horizontalVelocity; this.horizontalVelocity = horizontalVelocity;
} }
/**
* 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 DropperPlayerEntryState(@NotNull UUID playerId, boolean makePlayerInvisible, 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);
this.originalFlySpeed = originalFlySpeed;
this.disableHitCollision = disableHitCollision;
this.horizontalVelocity = horizontalVelocity;
this.arenaGameMode = arenaGameMode;
}
@Override @Override
public void setArenaState() { public void setArenaState() {
super.setArenaState(); super.setArenaState();
this.player.setAllowFlight(true); Player player = getPlayer();
this.player.setFlying(true); if (player == null) {
this.player.setGameMode(GameMode.ADVENTURE); return;
this.player.setSwimming(false); }
player.setAllowFlight(true);
player.setFlying(true);
player.setGameMode(GameMode.ADVENTURE);
player.setSwimming(false);
if (this.disableHitCollision) { if (this.disableHitCollision) {
this.player.setCollidable(false); player.setCollidable(false);
} }
// If playing on the inverted game-mode, negate the horizontal velocity to swap the controls // If playing on the inverted game-mode, negate the horizontal velocity to swap the controls
if (this.arenaGameMode == DropperArenaGameMode.INVERTED) { if (this.arenaGameMode == DropperArenaGameMode.INVERTED) {
this.player.setFlySpeed(-this.horizontalVelocity); player.setFlySpeed(-this.horizontalVelocity);
} else { } else {
this.player.setFlySpeed(this.horizontalVelocity); player.setFlySpeed(this.horizontalVelocity);
} }
} }
@Override @Override
public void restore() { public boolean restore() {
super.restore(); Player player = getPlayer();
this.player.setFlySpeed(this.originalFlySpeed); if (player == null) {
return false;
}
this.restore(player);
return true;
}
@Override
public void restore(@NotNull Player player) {
super.restore(player);
player.setFlySpeed(this.originalFlySpeed);
}
@NotNull
@Override
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;
}
/**
* Deserializes a ParkourPlayerEntryState from the given data
*
* @return <p>The data to deserialize</p>
*/
@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);
} }
} }

View File

@ -162,11 +162,7 @@ public class ParkourArena implements Arena {
return this.spawnLocation; return this.spawnLocation;
} }
/** @Override
* 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() { public @Nullable Location getExitLocation() {
return this.exitLocation; return this.exitLocation;
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.minigames.arena.parkour; package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.arena.EditablePropertyType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -13,22 +14,25 @@ public enum ParkourArenaEditableProperty {
/** /**
* The name of the arena * The name of the arena
*/ */
NAME("name", ParkourArena::getArenaName), NAME("name", ParkourArena::getArenaName, EditablePropertyType.ARENA_NAME),
/** /**
* The arena's spawn location * The arena's spawn location
*/ */
SPAWN_LOCATION("spawnLocation", (arena) -> String.valueOf(arena.getSpawnLocation())), SPAWN_LOCATION("spawnLocation", (arena) -> String.valueOf(arena.getSpawnLocation()),
EditablePropertyType.LOCATION),
/** /**
* The arena's exit location * The arena's exit location
*/ */
EXIT_LOCATION("exitLocation", (arena) -> String.valueOf(arena.getExitLocation())), EXIT_LOCATION("exitLocation", (arena) -> String.valueOf(arena.getExitLocation()),
EditablePropertyType.LOCATION),
/** /**
* The arena's win block type * The arena's win block type
*/ */
WIN_BLOCK_TYPE("winBlockType", (arena) -> arena.getWinBlockType().toString()), WIN_BLOCK_TYPE("winBlockType", (arena) -> arena.getWinBlockType().toString(),
EditablePropertyType.BLOCK_TYPE),
/** /**
* The arena's win location (overrides the win block type) * The arena's win location (overrides the win block type)
@ -39,35 +43,50 @@ public enum ParkourArenaEditableProperty {
} else { } else {
return "null"; return "null";
} }
}), }, EditablePropertyType.LOCATION),
/** /**
* The arena's check points. Specifically used for adding. * The arena's check points. Specifically used for adding.
*/ */
CHECKPOINT_ADD("checkpointAdd", (arena) -> String.valueOf(arena.getCheckpoints())), CHECKPOINT_ADD("checkpointAdd", (arena) -> String.valueOf(arena.getCheckpoints()),
EditablePropertyType.LOCATION),
/** /**
* The arena's check points. Specifically used for clearing. * The arena's check points. Specifically used for clearing.
*/ */
CHECKPOINT_CLEAR("checkpointClear", (arena) -> String.valueOf(arena.getCheckpoints())), CHECKPOINT_CLEAR("checkpointClear", (arena) -> String.valueOf(arena.getCheckpoints()),
EditablePropertyType.CHECKPOINT_CLEAR),
/** /**
* The blocks constituting the arena's lethal blocks * The blocks constituting the arena's lethal blocks
*/ */
KILL_PLANE_BLOCKS("killPlaneBlocks", (arena) -> String.valueOf(arena.getKillPlaneBlockNames())), KILL_PLANE_BLOCKS("killPlaneBlocks", (arena) -> String.valueOf(arena.getKillPlaneBlockNames()),
EditablePropertyType.MATERIAL_LIST),
; ;
private final @NotNull String argumentString; private final @NotNull String argumentString;
private final Function<ParkourArena, String> currentValueProvider; private final Function<ParkourArena, String> currentValueProvider;
private final EditablePropertyType propertyType;
/** /**
* Instantiates a new arena editable property * Instantiates a new arena editable property
* *
* @param argumentString <p>The argument string used to specify this property</p> * @param argumentString <p>The argument string used to specify this property</p>
*/ */
ParkourArenaEditableProperty(@NotNull String argumentString, Function<ParkourArena, String> currentValueProvider) { ParkourArenaEditableProperty(@NotNull String argumentString, Function<ParkourArena, String> currentValueProvider,
EditablePropertyType propertyType) {
this.argumentString = argumentString; this.argumentString = argumentString;
this.currentValueProvider = currentValueProvider; this.currentValueProvider = currentValueProvider;
this.propertyType = propertyType;
}
/**
* Gets the type of property this editable property represents
*
* @return <p>The type of this property</p>
*/
public EditablePropertyType getPropertyType() {
return this.propertyType;
} }
/** /**

View File

@ -2,6 +2,8 @@ package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaHandler; import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.ParkourArenaStorageHelper; import net.knarcraft.minigames.util.ParkourArenaStorageHelper;
import java.io.IOException; import java.io.IOException;
@ -22,7 +24,7 @@ public class ParkourArenaHandler extends ArenaHandler<ParkourArena, ParkourArena
* *
* @param playerRegistry <p>The registry keeping track of player sessions</p> * @param playerRegistry <p>The registry keeping track of player sessions</p>
*/ */
public ParkourArenaHandler(ParkourArenaPlayerRegistry playerRegistry) { public ParkourArenaHandler(ArenaPlayerRegistry<ParkourArena> playerRegistry) {
super(playerRegistry); super(playerRegistry);
} }
@ -31,8 +33,7 @@ public class ParkourArenaHandler extends ArenaHandler<ParkourArena, ParkourArena
try { try {
ParkourArenaStorageHelper.saveParkourArenaGroups(new HashSet<>(this.arenaGroups.values())); ParkourArenaStorageHelper.saveParkourArenaGroups(new HashSet<>(this.arenaGroups.values()));
} catch (IOException e) { } catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save current arena groups! " + MiniGames.log(Level.SEVERE, Message.ERROR_CANNOT_SAVE_ARENA_GROUPS.getMessage());
"Data loss can occur!");
MiniGames.log(Level.SEVERE, e.getMessage()); MiniGames.log(Level.SEVERE, e.getMessage());
} }
} }

View File

@ -1,62 +1,15 @@
package net.knarcraft.minigames.arena.parkour; package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry; import net.knarcraft.minigames.arena.AbstractArenaPlayerRegistry;
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 * A registry to keep track of which players are playing in which arenas
*/ */
public class ParkourArenaPlayerRegistry implements ArenaPlayerRegistry<ParkourArena> { public class ParkourArenaPlayerRegistry extends AbstractArenaPlayerRegistry<ParkourArena> {
private final Map<UUID, ParkourArenaSession> arenaPlayers = new HashMap<>(); @Override
protected String getEntryStateStorageKey() {
/** return "parkour";
* 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());
}
}
} }
} }

View File

@ -1,12 +1,10 @@
package net.knarcraft.minigames.arena.parkour; package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGameMode; import net.knarcraft.minigames.arena.AbstractArenaSession;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState; import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.config.ParkourConfiguration; import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.property.RecordResult;
import net.knarcraft.minigames.util.PlayerTeleporter; import net.knarcraft.minigames.util.PlayerTeleporter;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -18,14 +16,11 @@ import java.util.logging.Level;
/** /**
* A representation of a player's current session in a parkour arena * A representation of a player's current session in a parkour arena
*/ */
public class ParkourArenaSession implements ArenaSession { public class ParkourArenaSession extends AbstractArenaSession {
private final @NotNull ParkourArena arena; private final @NotNull ParkourArena arena;
private final @NotNull Player player; private final @NotNull Player player;
private final @NotNull ParkourArenaGameMode gameMode; private final @NotNull ParkourArenaGameMode gameMode;
private int deaths;
private final long startTime;
private final PlayerEntryState entryState;
private Location reachedCheckpoint = null; private Location reachedCheckpoint = null;
/** /**
@ -37,33 +32,17 @@ public class ParkourArenaSession implements ArenaSession {
*/ */
public ParkourArenaSession(@NotNull ParkourArena parkourArena, @NotNull Player player, public ParkourArenaSession(@NotNull ParkourArena parkourArena, @NotNull Player player,
@NotNull ParkourArenaGameMode gameMode) { @NotNull ParkourArenaGameMode gameMode) {
super(parkourArena, player, gameMode);
this.arena = parkourArena; this.arena = parkourArena;
this.player = player; this.player = player;
this.gameMode = gameMode; this.gameMode = gameMode;
this.deaths = 0;
this.startTime = System.currentTimeMillis();
ParkourConfiguration configuration = MiniGames.getInstance().getParkourConfiguration(); ParkourConfiguration configuration = MiniGames.getInstance().getParkourConfiguration();
boolean makeInvisible = configuration.makePlayersInvisible(); boolean makeInvisible = configuration.makePlayersInvisible();
this.entryState = new ParkourPlayerEntryState(player, makeInvisible); this.entryState = new ParkourPlayerEntryState(player, makeInvisible);
// Make the player fly to improve mobility in the air
this.entryState.setArenaState(); this.entryState.setArenaState();
} }
@Override
public @NotNull ArenaGameMode 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 PlayerEntryState getEntryState() {
return this.entryState;
}
/** /**
* Registers the checkpoint this session's player has reached * Registers the checkpoint this session's player has reached
* *
@ -82,12 +61,15 @@ public class ParkourArenaSession implements ArenaSession {
return this.reachedCheckpoint; return this.reachedCheckpoint;
} }
/** @Override
* Triggers a win for the player playing in this session public @NotNull PlayerEntryState getEntryState() {
*/ return this.entryState;
}
@Override
public void triggerWin() { public void triggerWin() {
// Stop this session // Stop this session
stopSession(); removeSession();
// Check for, and display, records // Check for, and display, records
MiniGames miniGames = MiniGames.getInstance(); MiniGames miniGames = MiniGames.getInstance();
@ -99,78 +81,15 @@ public class ParkourArenaSession implements ArenaSession {
// Mark the arena as cleared // Mark the arena as cleared
if (this.arena.getData().setCompleted(this.gameMode, this.player)) { if (this.arena.getData().setCompleted(this.gameMode, this.player)) {
this.player.sendMessage("You cleared the arena!"); this.player.sendMessage(Message.SUCCESS_ARENA_FIRST_CLEAR.getMessage());
} }
this.player.sendMessage("You won!"); this.player.sendMessage(Message.SUCCESS_ARENA_WIN.getMessage());
// Teleport the player out of the arena // Teleport the player out of the arena
teleportToExit(false); teleportToExit(false);
} }
/** @Override
* 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().getParkourArenaPlayerRegistry().removePlayer(player.getUniqueId());
if (!removedSession) {
MiniGames.log(Level.SEVERE, "Unable to remove parkour 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() { public void triggerLoss() {
this.deaths++; this.deaths++;
//Teleport the player back to the top //Teleport the player back to the top
@ -179,45 +98,27 @@ public class ParkourArenaSession implements ArenaSession {
this.entryState.setArenaState(); this.entryState.setArenaState();
} }
/** @Override
* 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() { public @NotNull ParkourArena getArena() {
return this.arena; return this.arena;
} }
/** @Override
* Gets the player playing in this session protected void removeSession() {
* // Remove this session for game sessions to stop listeners from fiddling more with the player
* @return <p>This session's player</p> boolean removedSession = MiniGames.getInstance().getParkourArenaPlayerRegistry().removePlayer(
*/ player.getUniqueId(), true);
public @NotNull Player getPlayer() { if (!removedSession) {
return this.player; MiniGames.log(Level.SEVERE, "Unable to remove parkour arena session for " + player.getName() + ". " +
"This will have unintended consequences.");
}
}
@Override
protected String getGameModeString() {
return switch (this.gameMode) {
case DEFAULT -> "default";
};
} }
} }

View File

@ -1,10 +1,15 @@
package net.knarcraft.minigames.arena.parkour; package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.arena.AbstractPlayerEntryState; import net.knarcraft.minigames.arena.AbstractPlayerEntryState;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.UUID;
/** /**
* The state of a player before entering a parkour arena * The state of a player before entering a parkour arena
*/ */
@ -19,14 +24,60 @@ public class ParkourPlayerEntryState extends AbstractPlayerEntryState {
super(player, makePlayerInvisible); super(player, makePlayerInvisible);
} }
/**
* 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,
boolean originalIsFlying, GameMode originalGameMode, boolean originalAllowFlight,
boolean originalInvulnerable, boolean originalIsSwimming,
boolean originalCollideAble) {
super(playerId, makePlayerInvisible, entryLocation, originalIsFlying, originalGameMode, originalAllowFlight,
originalInvulnerable, originalIsSwimming, originalCollideAble);
}
@Override @Override
public void setArenaState() { public void setArenaState() {
super.setArenaState(); super.setArenaState();
this.player.setAllowFlight(false); Player player = getPlayer();
this.player.setFlying(false); if (player == null) {
this.player.setGameMode(GameMode.ADVENTURE); return;
this.player.setSwimming(false); }
this.player.setCollidable(false); player.setAllowFlight(false);
player.setFlying(false);
player.setGameMode(GameMode.ADVENTURE);
player.setSwimming(false);
player.setCollidable(false);
}
/**
* Deserializes a ParkourPlayerEntryState from the given data
*
* @return <p>The data to deserialize</p>
*/
@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);
} }
} }

View File

@ -11,6 +11,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* An abstract class for an arena joining tab-completer * An abstract class for an arena joining tab-completer
*/ */
@ -34,13 +36,13 @@ public abstract class JoinArenaTabCompleter implements TabCompleter {
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] arguments) { @NotNull String label, @NotNull String[] arguments) {
if (arguments.length == 1) { if (arguments.length == 1) {
return arenaNameSupplier.get(); return filterMatchingContains(arenaNameSupplier.get(), arguments[0]);
} else if (arguments.length == 2) { } else if (arguments.length == 2) {
List<String> gameModes = new ArrayList<>(); List<String> gameModes = new ArrayList<>();
for (ArenaGameMode gameMode : gameMode.getValues()) { for (ArenaGameMode gameMode : gameMode.getValues()) {
gameModes.add(gameMode.name().toLowerCase()); gameModes.add(gameMode.name().toLowerCase());
} }
return gameModes; return filterMatchingContains(gameModes, arguments[1]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -2,6 +2,7 @@ package net.knarcraft.minigames.command;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaSession; import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -21,13 +22,13 @@ public class LeaveArenaCommand implements TabExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] strings) { @NotNull String[] strings) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
ArenaSession existingSession = MiniGames.getInstance().getSession(player.getUniqueId()); ArenaSession existingSession = MiniGames.getInstance().getSession(player.getUniqueId());
if (existingSession == null) { if (existingSession == null) {
commandSender.sendMessage("You are not in a mini-games arena!"); commandSender.sendMessage(Message.ERROR_NOT_IN_ARENA.getMessage());
return false; return false;
} }

View File

@ -1,6 +1,7 @@
package net.knarcraft.minigames.command; package net.knarcraft.minigames.command;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -19,7 +20,7 @@ public class ReloadCommand implements TabExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
MiniGames.getInstance().reload(); MiniGames.getInstance().reload();
commandSender.sendMessage("Plugin reloaded!"); commandSender.sendMessage(Message.SUCCESS_PLUGIN_RELOADED.getMessage());
return true; return true;
} }

View File

@ -3,6 +3,7 @@ package net.knarcraft.minigames.command.dropper;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler; import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.StringSanitizer; import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
@ -19,7 +20,7 @@ public class CreateDropperArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
@ -40,13 +41,13 @@ public class CreateDropperArenaCommand implements CommandExecutor {
DropperArena existingArena = arenaHandler.getArena(arenaName); DropperArena existingArena = arenaHandler.getArena(arenaName);
if (existingArena != null) { if (existingArena != null) {
commandSender.sendMessage("There already exists a dropper arena with that name!"); commandSender.sendMessage(Message.ERROR_ARENA_NAME_COLLISION.getMessage());
return false; return false;
} }
DropperArena arena = new DropperArena(arenaName, player.getLocation(), arenaHandler); DropperArena arena = new DropperArena(arenaName, player.getLocation(), arenaHandler);
arenaHandler.addArena(arena); arenaHandler.addArena(arena);
commandSender.sendMessage("The arena was successfully created!"); commandSender.sendMessage(Message.SUCCESS_ARENA_CREATED.getMessage());
return true; return true;
} }

View File

@ -4,6 +4,7 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup; import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler; import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -14,6 +15,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The command for listing groups and the stages within * The command for listing groups and the stages within
*/ */
@ -58,12 +61,12 @@ public class DropperGroupListCommand implements TabExecutor {
@NotNull String groupName) { @NotNull String groupName) {
DropperArenaGroup arenaGroup = arenaHandler.getGroup(groupName); DropperArenaGroup arenaGroup = arenaHandler.getGroup(groupName);
if (arenaGroup == null) { if (arenaGroup == null) {
sender.sendMessage("Unable to find the specified group!"); sender.sendMessage(Message.ERROR_GROUP_NOT_FOUND.getMessage());
return false; return false;
} }
// Send a list of all stages (arenas in the group) // Send a list of all stages (arenas in the group)
StringBuilder builder = new StringBuilder(groupName).append("'s stages:").append("\n"); StringBuilder builder = new StringBuilder(Message.SUCCESS_GROUP_STAGES.getMessage("{group}", groupName));
int counter = 1; int counter = 1;
for (UUID arenaId : arenaGroup.getArenas()) { for (UUID arenaId : arenaGroup.getArenas()) {
DropperArena arena = arenaHandler.getArena(arenaId); DropperArena arena = arenaHandler.getArena(arenaId);
@ -84,7 +87,7 @@ public class DropperGroupListCommand implements TabExecutor {
for (DropperArenaGroup group : MiniGames.getInstance().getDropperArenaHandler().getAllGroups()) { for (DropperArenaGroup group : MiniGames.getInstance().getDropperArenaHandler().getAllGroups()) {
groupNames.add(group.getGroupName()); groupNames.add(group.getGroupName());
} }
return groupNames; return filterMatchingContains(groupNames, arguments[0]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -4,6 +4,7 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup; import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler; import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.StringSanitizer; import net.knarcraft.minigames.util.StringSanitizer;
import net.knarcraft.minigames.util.TabCompleteHelper; import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -15,6 +16,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The command for setting the group of an arena * The command for setting the group of an arena
*/ */
@ -31,7 +34,7 @@ public class DropperGroupSetCommand implements TabExecutor {
DropperArena specifiedArena = arenaHandler.getArena(arguments[0]); DropperArena specifiedArena = arenaHandler.getArena(arguments[0]);
if (specifiedArena == null) { if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena."); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
@ -53,7 +56,7 @@ public class DropperGroupSetCommand implements TabExecutor {
arenaHandler.setGroup(specifiedArena.getArenaId(), arenaGroup); arenaHandler.setGroup(specifiedArena.getArenaId(), arenaGroup);
commandSender.sendMessage("The arena's group has been updated"); commandSender.sendMessage(Message.SUCCESS_ARENA_GROUP_UPDATED.getMessage());
return true; return true;
} }
@ -62,7 +65,7 @@ public class DropperGroupSetCommand implements TabExecutor {
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (arguments.length == 1) { if (arguments.length == 1) {
return TabCompleteHelper.getDropperArenas(); return filterMatchingContains(TabCompleteHelper.getDropperArenas(), arguments[0]);
} else if (arguments.length == 2) { } else if (arguments.length == 2) {
List<String> possibleValues = new ArrayList<>(); List<String> possibleValues = new ArrayList<>();
possibleValues.add("none"); possibleValues.add("none");
@ -70,7 +73,7 @@ public class DropperGroupSetCommand implements TabExecutor {
for (DropperArenaGroup group : MiniGames.getInstance().getDropperArenaHandler().getAllGroups()) { for (DropperArenaGroup group : MiniGames.getInstance().getDropperArenaHandler().getAllGroups()) {
possibleValues.add(group.getGroupName()); possibleValues.add(group.getGroupName());
} }
return possibleValues; return filterMatchingContains(possibleValues, arguments[1]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -4,6 +4,7 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup; import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler; import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -14,6 +15,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The command for swapping the order of two arenas in a group * The command for swapping the order of two arenas in a group
*/ */
@ -30,26 +33,26 @@ public class DropperGroupSwapCommand implements TabExecutor {
DropperArena arena1 = arenaHandler.getArena(arguments[0]); DropperArena arena1 = arenaHandler.getArena(arguments[0]);
if (arena1 == null) { if (arena1 == null) {
commandSender.sendMessage("Unable to find the first specified dropper arena."); commandSender.sendMessage(Message.ERROR_ARENA_1_NOT_FOUND.getMessage());
return false; return false;
} }
DropperArena arena2 = arenaHandler.getArena(arguments[1]); DropperArena arena2 = arenaHandler.getArena(arguments[1]);
if (arena2 == null) { if (arena2 == null) {
commandSender.sendMessage("Unable to find the second specified dropper arena."); commandSender.sendMessage(Message.ERROR_ARENA_2_NOT_FOUND.getMessage());
return false; return false;
} }
DropperArenaGroup arena1Group = arenaHandler.getGroup(arena1.getArenaId()); DropperArenaGroup arena1Group = arenaHandler.getGroup(arena1.getArenaId());
DropperArenaGroup arena2Group = arenaHandler.getGroup(arena2.getArenaId()); DropperArenaGroup arena2Group = arenaHandler.getGroup(arena2.getArenaId());
if (arena1Group == null || !arena1Group.equals(arena2Group)) { if (arena1Group == null || !arena1Group.equals(arena2Group)) {
commandSender.sendMessage("You cannot swap arenas in different groups!"); commandSender.sendMessage(Message.ERROR_SWAP_DIFFERENT_GROUPS.getMessage());
return false; return false;
} }
arena1Group.swapArenas(arena1Group.getArenas().indexOf(arena1.getArenaId()), arena1Group.swapArenas(arena1Group.getArenas().indexOf(arena1.getArenaId()),
arena1Group.getArenas().indexOf(arena2.getArenaId())); arena1Group.getArenas().indexOf(arena2.getArenaId()));
commandSender.sendMessage("The arenas have been swapped!"); commandSender.sendMessage(Message.SUCCESS_ARENAS_SWAPPED.getMessage());
return true; return true;
} }
@ -63,9 +66,9 @@ public class DropperGroupSwapCommand implements TabExecutor {
for (DropperArena dropperArena : arenaHandler.getArenasInAGroup()) { for (DropperArena dropperArena : arenaHandler.getArenasInAGroup()) {
arenaNames.add(dropperArena.getArenaName()); arenaNames.add(dropperArena.getArenaName());
} }
return arenaNames; return filterMatchingContains(arenaNames, arguments[0]);
} else if (arguments.length == 2) { } else if (arguments.length == 2) {
return getArenaNamesInSameGroup(arguments[0]); return filterMatchingContains(getArenaNamesInSameGroup(arguments[0]), arguments[1]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -4,6 +4,8 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaEditableProperty; import net.knarcraft.minigames.arena.dropper.DropperArenaEditableProperty;
import net.knarcraft.minigames.config.DropperConfiguration; import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.container.PlaceholderContainer;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -32,7 +34,7 @@ public class EditDropperArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
@ -42,29 +44,29 @@ public class EditDropperArenaCommand implements CommandExecutor {
DropperArena specifiedArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]); DropperArena specifiedArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) { if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena."); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
DropperArenaEditableProperty editableProperty = DropperArenaEditableProperty.getFromArgumentString(arguments[1]); DropperArenaEditableProperty editableProperty = DropperArenaEditableProperty.getFromArgumentString(arguments[1]);
if (editableProperty == null) { if (editableProperty == null) {
commandSender.sendMessage("Unknown property specified."); commandSender.sendMessage(Message.ERROR_UNKNOWN_PROPERTY.getMessage());
return false; return false;
} }
String currentValueFormat = "Current value of %s is: %s";
if (arguments.length < 3) { if (arguments.length < 3) {
// Print the current value of the property // Print the current value of the property
String value = editableProperty.getCurrentValueAsString(specifiedArena); String value = editableProperty.getCurrentValueAsString(specifiedArena);
commandSender.sendMessage(String.format(currentValueFormat, editableProperty.getArgumentString(), value)); commandSender.sendMessage(Message.SUCCESS_CURRENT_VALUE.getMessage(new PlaceholderContainer().add(
"{property}", editableProperty.getArgumentString()).add("{value}", value)));
return true; return true;
} else { } else {
boolean successful = changeValue(specifiedArena, editableProperty, arguments[2], player); boolean successful = changeValue(specifiedArena, editableProperty, arguments[2], player);
if (successful) { if (successful) {
player.sendMessage(String.format("Property %s changed to: %s", editableProperty, arguments[2])); player.sendMessage(Message.SUCCESS_PROPERTY_CHANGED.getMessage("{property}",
editableProperty.getArgumentString()));
} else { } else {
player.sendMessage("Unable to change the property. Make sure your input is valid!"); player.sendMessage(Message.ERROR_PROPERTY_INPUT_INVALID.getMessage());
} }
return successful; return successful;
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.minigames.command.dropper; package net.knarcraft.minigames.command.dropper;
import net.knarcraft.minigames.arena.dropper.DropperArenaEditableProperty;
import net.knarcraft.minigames.util.TabCompleteHelper; import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -10,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The tab-completer for the edit arena command * The tab-completer for the edit arena command
*/ */
@ -17,14 +20,18 @@ public class EditDropperArenaTabCompleter implements TabCompleter {
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) { @NotNull String label, @NotNull String[] arguments) {
if (args.length == 1) { if (arguments.length == 1) {
return TabCompleteHelper.getDropperArenas(); return filterMatchingContains(TabCompleteHelper.getDropperArenas(), arguments[0]);
} else if (args.length == 2) { } else if (arguments.length == 2) {
return TabCompleteHelper.getDropperArenaProperties(); return filterMatchingContains(TabCompleteHelper.getDropperArenaProperties(), arguments[1]);
} else if (args.length == 3) { } else if (arguments.length == 3) {
//TODO: Tab-complete possible values for the given property DropperArenaEditableProperty property = DropperArenaEditableProperty.getFromArgumentString(arguments[1]);
return null; if (property == null) {
return new ArrayList<>();
}
return filterMatchingContains(TabCompleteHelper.getTabCompleteSuggestions(property.getPropertyType()),
arguments[2]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -1,12 +1,13 @@
package net.knarcraft.minigames.command.dropper; package net.knarcraft.minigames.command.dropper;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode; import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup; import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaPlayerRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaSession; import net.knarcraft.minigames.arena.dropper.DropperArenaSession;
import net.knarcraft.minigames.config.DropperConfiguration; import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.PlayerTeleporter; import net.knarcraft.minigames.util.PlayerTeleporter;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
@ -23,7 +24,7 @@ public class JoinDropperArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
@ -33,20 +34,20 @@ public class JoinDropperArenaCommand implements CommandExecutor {
// Disallow joining if the player is already in a mini-game arena // Disallow joining if the player is already in a mini-game arena
if (MiniGames.getInstance().getSession(player.getUniqueId()) != null) { if (MiniGames.getInstance().getSession(player.getUniqueId()) != null) {
commandSender.sendMessage("You are already playing a mini-game!"); commandSender.sendMessage(Message.ERROR_ALREADY_PLAYING.getMessage());
return false; return false;
} }
// Make sure the arena exists // Make sure the arena exists
DropperArena specifiedArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]); DropperArena specifiedArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) { if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena."); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
// Deny vehicles as allowing this is tricky, and will cause problems in some cases // Deny vehicles as allowing this is tricky, and will cause problems in some cases
if (player.isInsideVehicle() || !player.getPassengers().isEmpty()) { if (player.isInsideVehicle() || !player.getPassengers().isEmpty()) {
commandSender.sendMessage("You cannot join an arena while inside a vehicle or carrying a passenger."); commandSender.sendMessage(Message.ERROR_JOIN_IN_VEHICLE_OR_PASSENGER.getMessage());
return false; return false;
} }
@ -80,25 +81,25 @@ public class JoinDropperArenaCommand implements CommandExecutor {
if (MiniGames.getInstance().getDropperConfiguration().mustDoNormalModeFirst() && if (MiniGames.getInstance().getDropperConfiguration().mustDoNormalModeFirst() &&
gameMode != DropperArenaGameMode.DEFAULT && gameMode != DropperArenaGameMode.DEFAULT &&
specifiedArena.getData().hasNotCompleted(DropperArenaGameMode.DEFAULT, player)) { specifiedArena.getData().hasNotCompleted(DropperArenaGameMode.DEFAULT, player)) {
player.sendMessage("You must complete this arena in normal mode first!"); player.sendMessage(Message.ERROR_NORMAL_MODE_REQUIRED.getMessage());
return false; return false;
} }
// Register the player's session // Register the player's session
DropperArenaSession newSession = new DropperArenaSession(specifiedArena, player, gameMode); DropperArenaSession newSession = new DropperArenaSession(specifiedArena, player, gameMode);
DropperArenaPlayerRegistry playerRegistry = MiniGames.getInstance().getDropperArenaPlayerRegistry(); ArenaPlayerRegistry<DropperArena> playerRegistry = MiniGames.getInstance().getDropperArenaPlayerRegistry();
playerRegistry.registerPlayer(player.getUniqueId(), newSession); playerRegistry.registerPlayer(player.getUniqueId(), newSession);
// Try to teleport the player to the arena // Try to teleport the player to the arena
boolean teleported = PlayerTeleporter.teleportPlayer(player, specifiedArena.getSpawnLocation(), false, false); boolean teleported = PlayerTeleporter.teleportPlayer(player, specifiedArena.getSpawnLocation(), false, false);
if (!teleported) { if (!teleported) {
player.sendMessage("Unable to teleport you to the dropper arena. Make sure you're not in a vehicle," + player.sendMessage(Message.ERROR_ARENA_TELEPORT_FAILED.getMessage());
"and not carrying a passenger!");
newSession.triggerQuit(false); newSession.triggerQuit(false);
return false; return false;
} else { } else {
// Make sure to update the state again in the air to remove a potential swimming state // Make sure to update the state again in the air to remove a potential swimming state
newSession.getEntryState().setArenaState(); newSession.getEntryState().setArenaState();
player.sendMessage(Message.SUCCESS_ARENA_JOINED.getMessage());
return true; return true;
} }
} }
@ -118,14 +119,14 @@ public class JoinDropperArenaCommand implements CommandExecutor {
// Require that players beat all arenas in the group in the normal game-mode before trying challenge modes // Require that players beat all arenas in the group in the normal game-mode before trying challenge modes
if (configuration.mustDoNormalModeFirst() && arenaGameMode != DropperArenaGameMode.DEFAULT && if (configuration.mustDoNormalModeFirst() && arenaGameMode != DropperArenaGameMode.DEFAULT &&
!arenaGroup.hasBeatenAll(DropperArenaGameMode.DEFAULT, player)) { !arenaGroup.hasBeatenAll(DropperArenaGameMode.DEFAULT, player)) {
player.sendMessage("You have not yet beaten all arenas in this group!"); player.sendMessage(Message.ERROR_GROUP_NORMAL_MODE_REQUIRED.getMessage());
return false; return false;
} }
// Require that the player has beaten the previous arena on the same game-mode before trying this one // Require that the player has beaten the previous arena on the same game-mode before trying this one
if (configuration.mustDoGroupedInSequence() && if (configuration.mustDoGroupedInSequence() &&
arenaGroup.cannotPlay(arenaGameMode, player, dropperArena.getArenaId())) { arenaGroup.cannotPlay(arenaGameMode, player, dropperArena.getArenaId())) {
player.sendMessage("You have not yet beaten the previous arena!"); player.sendMessage(Message.ERROR_PREVIOUS_ARENA_REQUIRED.getMessage());
return false; return false;
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.minigames.command.dropper; package net.knarcraft.minigames.command.dropper;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.TabCompleteHelper; import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -18,10 +19,11 @@ public class ListDropperArenaCommand implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
sender.sendMessage("Dropper arenas:"); StringBuilder builder = new StringBuilder(Message.SUCCESS_DROPPER_ARENAS_LIST.getMessage());
for (String arenaName : TabCompleteHelper.getDropperArenas()) { for (String arenaName : TabCompleteHelper.getDropperArenas()) {
sender.sendMessage(arenaName); builder.append("\n").append(arenaName);
} }
sender.sendMessage(builder.toString());
return true; return true;
} }

View File

@ -2,6 +2,7 @@ package net.knarcraft.minigames.command.dropper;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena; import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -23,13 +24,13 @@ public class RemoveDropperArenaCommand implements CommandExecutor {
// Get the specified arena // Get the specified arena
DropperArena targetArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]); DropperArena targetArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]);
if (targetArena == null) { if (targetArena == null) {
commandSender.sendMessage("Unable to find the specified arena"); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
// Remove the arena // Remove the arena
MiniGames.getInstance().getDropperArenaHandler().removeArena(targetArena); MiniGames.getInstance().getDropperArenaHandler().removeArena(targetArena);
commandSender.sendMessage("The specified arena has been successfully removed"); commandSender.sendMessage(Message.SUCCESS_ARENA_REMOVED.getMessage());
return true; return true;
} }

View File

@ -10,6 +10,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The tab-completer for the remove arena command * The tab-completer for the remove arena command
*/ */
@ -20,7 +22,7 @@ public class RemoveDropperArenaTabCompleter implements TabCompleter {
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (arguments.length == 1) { if (arguments.length == 1) {
return TabCompleteHelper.getDropperArenas(); return filterMatchingContains(TabCompleteHelper.getDropperArenas(), arguments[0]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -3,6 +3,7 @@ package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler; import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.StringSanitizer; import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
@ -19,7 +20,7 @@ public class CreateParkourArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
@ -40,13 +41,13 @@ public class CreateParkourArenaCommand implements CommandExecutor {
ParkourArena existingArena = arenaHandler.getArena(arenaName); ParkourArena existingArena = arenaHandler.getArena(arenaName);
if (existingArena != null) { if (existingArena != null) {
commandSender.sendMessage("There already exists a parkour arena with that name!"); commandSender.sendMessage(Message.ERROR_ARENA_NAME_COLLISION.getMessage());
return false; return false;
} }
ParkourArena arena = new ParkourArena(arenaName, player.getLocation(), arenaHandler); ParkourArena arena = new ParkourArena(arenaName, player.getLocation(), arenaHandler);
arenaHandler.addArena(arena); arenaHandler.addArena(arena);
commandSender.sendMessage("The arena was successfully created!"); commandSender.sendMessage(Message.SUCCESS_ARENA_CREATED.getMessage());
return true; return true;
} }

View File

@ -3,6 +3,8 @@ package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaEditableProperty; import net.knarcraft.minigames.arena.parkour.ParkourArenaEditableProperty;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.container.PlaceholderContainer;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -29,7 +31,7 @@ public class EditParkourArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
@ -39,29 +41,29 @@ public class EditParkourArenaCommand implements CommandExecutor {
ParkourArena specifiedArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]); ParkourArena specifiedArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) { if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena."); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
ParkourArenaEditableProperty editableProperty = ParkourArenaEditableProperty.getFromArgumentString(arguments[1]); ParkourArenaEditableProperty editableProperty = ParkourArenaEditableProperty.getFromArgumentString(arguments[1]);
if (editableProperty == null) { if (editableProperty == null) {
commandSender.sendMessage("Unknown property specified."); commandSender.sendMessage(Message.ERROR_UNKNOWN_PROPERTY.getMessage());
return false; return false;
} }
String currentValueFormat = "Current value of %s is: %s";
if (arguments.length < 3) { if (arguments.length < 3) {
// Print the current value of the property // Print the current value of the property
String value = editableProperty.getCurrentValueAsString(specifiedArena); String value = editableProperty.getCurrentValueAsString(specifiedArena);
commandSender.sendMessage(String.format(currentValueFormat, editableProperty.getArgumentString(), value)); commandSender.sendMessage(Message.SUCCESS_CURRENT_VALUE.getMessage(new PlaceholderContainer().add(
"{property}", editableProperty.getArgumentString()).add("{value}", value)));
return true; return true;
} else { } else {
boolean successful = changeValue(specifiedArena, editableProperty, arguments[2], player); boolean successful = changeValue(specifiedArena, editableProperty, arguments[2], player);
if (successful) { if (successful) {
player.sendMessage(String.format("Property %s changed to: %s", editableProperty, arguments[2])); player.sendMessage(Message.SUCCESS_PROPERTY_CHANGED.getMessage("{property}",
editableProperty.getArgumentString()));
} else { } else {
player.sendMessage("Unable to change the property. Make sure your input is valid!"); player.sendMessage(Message.ERROR_PROPERTY_INPUT_INVALID.getMessage());
} }
return successful; return successful;
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.minigames.command.parkour; package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.arena.parkour.ParkourArenaEditableProperty;
import net.knarcraft.minigames.util.TabCompleteHelper; import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -10,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The tab-completer for the edit arena command * The tab-completer for the edit arena command
*/ */
@ -17,14 +20,18 @@ public class EditParkourArenaTabCompleter implements TabCompleter {
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) { @NotNull String label, @NotNull String[] arguments) {
if (args.length == 1) { if (arguments.length == 1) {
return TabCompleteHelper.getParkourArenas(); return filterMatchingContains(TabCompleteHelper.getParkourArenas(), arguments[0]);
} else if (args.length == 2) { } else if (arguments.length == 2) {
return TabCompleteHelper.getParkourArenaProperties(); return filterMatchingContains(TabCompleteHelper.getParkourArenaProperties(), arguments[1]);
} else if (args.length == 3) { } else if (arguments.length == 3) {
//TODO: Tab-complete possible values for the given property ParkourArenaEditableProperty property = ParkourArenaEditableProperty.getFromArgumentString(arguments[1]);
return null; if (property == null) {
return new ArrayList<>();
}
return filterMatchingContains(TabCompleteHelper.getTabCompleteSuggestions(property.getPropertyType()),
arguments[2]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -1,11 +1,12 @@
package net.knarcraft.minigames.command.parkour; package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode; import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup; import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaPlayerRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArenaSession; import net.knarcraft.minigames.arena.parkour.ParkourArenaSession;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.config.ParkourConfiguration; import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.util.PlayerTeleporter; import net.knarcraft.minigames.util.PlayerTeleporter;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -23,7 +24,7 @@ public class JoinParkourArenaCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) { if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player"); commandSender.sendMessage(Message.ERROR_PLAYER_ONLY.getMessage());
return false; return false;
} }
@ -33,20 +34,20 @@ public class JoinParkourArenaCommand implements CommandExecutor {
// Disallow joining if the player is already in a mini-game arena // Disallow joining if the player is already in a mini-game arena
if (MiniGames.getInstance().getSession(player.getUniqueId()) != null) { if (MiniGames.getInstance().getSession(player.getUniqueId()) != null) {
commandSender.sendMessage("You are already playing a mini-game!"); commandSender.sendMessage(Message.ERROR_ALREADY_PLAYING.getMessage());
return false; return false;
} }
// Make sure the arena exists // Make sure the arena exists
ParkourArena specifiedArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]); ParkourArena specifiedArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) { if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified parkour arena."); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
// Deny vehicles as allowing this is tricky, and will cause problems in some cases // Deny vehicles as allowing this is tricky, and will cause problems in some cases
if (player.isInsideVehicle() || !player.getPassengers().isEmpty()) { if (player.isInsideVehicle() || !player.getPassengers().isEmpty()) {
commandSender.sendMessage("You cannot join an arena while inside a vehicle or carrying a passenger."); commandSender.sendMessage(Message.ERROR_JOIN_IN_VEHICLE_OR_PASSENGER.getMessage());
return false; return false;
} }
@ -78,19 +79,19 @@ public class JoinParkourArenaCommand implements CommandExecutor {
// Register the player's session // Register the player's session
ParkourArenaSession newSession = new ParkourArenaSession(specifiedArena, player, gameMode); ParkourArenaSession newSession = new ParkourArenaSession(specifiedArena, player, gameMode);
ParkourArenaPlayerRegistry playerRegistry = MiniGames.getInstance().getParkourArenaPlayerRegistry(); ArenaPlayerRegistry<ParkourArena> playerRegistry = MiniGames.getInstance().getParkourArenaPlayerRegistry();
playerRegistry.registerPlayer(player.getUniqueId(), newSession); playerRegistry.registerPlayer(player.getUniqueId(), newSession);
// Try to teleport the player to the arena // Try to teleport the player to the arena
boolean teleported = PlayerTeleporter.teleportPlayer(player, specifiedArena.getSpawnLocation(), false, false); boolean teleported = PlayerTeleporter.teleportPlayer(player, specifiedArena.getSpawnLocation(), false, false);
if (!teleported) { if (!teleported) {
player.sendMessage("Unable to teleport you to the parkour arena. Make sure you're not in a vehicle," + player.sendMessage(Message.ERROR_ARENA_TELEPORT_FAILED.getMessage());
"and not carrying a passenger!");
newSession.triggerQuit(false); newSession.triggerQuit(false);
return false; return false;
} else { } else {
// Make sure to update the state again in the air to remove a potential swimming state // Make sure to update the state again in the air to remove a potential swimming state
newSession.getEntryState().setArenaState(); newSession.getEntryState().setArenaState();
player.sendMessage(Message.SUCCESS_ARENA_JOINED.getMessage());
return true; return true;
} }
} }
@ -111,7 +112,7 @@ public class JoinParkourArenaCommand implements CommandExecutor {
// Require that the player has beaten the previous arena on the same game-mode before trying this one // Require that the player has beaten the previous arena on the same game-mode before trying this one
if (configuration.mustDoGroupedInSequence() && if (configuration.mustDoGroupedInSequence() &&
arenaGroup.cannotPlay(arenaGameMode, player, parkourArena.getArenaId())) { arenaGroup.cannotPlay(arenaGameMode, player, parkourArena.getArenaId())) {
player.sendMessage("You have not yet beaten the previous arena!"); player.sendMessage(Message.ERROR_PREVIOUS_ARENA_REQUIRED.getMessage());
return false; return false;
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.minigames.command.parkour; package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.TabCompleteHelper; import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -18,10 +19,11 @@ public class ListParkourArenaCommand implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
sender.sendMessage("Parkour arenas:"); StringBuilder builder = new StringBuilder(Message.SUCCESS_PARKOUR_ARENAS_LIST.getMessage());
for (String arenaName : TabCompleteHelper.getParkourArenas()) { for (String arenaName : TabCompleteHelper.getParkourArenas()) {
sender.sendMessage(arenaName); builder.append("\n").append(arenaName);
} }
sender.sendMessage(builder.toString());
return true; return true;
} }

View File

@ -4,6 +4,7 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup; import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler; import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -15,6 +16,8 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The command for listing groups and the stages within * The command for listing groups and the stages within
*/ */
@ -59,17 +62,17 @@ public class ParkourGroupListCommand implements TabExecutor {
@NotNull String groupName) { @NotNull String groupName) {
ParkourArenaGroup arenaGroup = arenaHandler.getGroup(groupName); ParkourArenaGroup arenaGroup = arenaHandler.getGroup(groupName);
if (arenaGroup == null) { if (arenaGroup == null) {
sender.sendMessage("Unable to find the specified group!"); sender.sendMessage(Message.ERROR_GROUP_NOT_FOUND.getMessage());
return false; return false;
} }
// Send a list of all stages (arenas in the group) // Send a list of all stages (arenas in the group)
StringBuilder builder = new StringBuilder(groupName).append("'s stages:").append("\n"); StringBuilder builder = new StringBuilder(Message.SUCCESS_GROUP_STAGES.getMessage("{group}", groupName));
int counter = 1; int counter = 1;
for (UUID arenaId : arenaGroup.getArenas()) { for (UUID arenaId : arenaGroup.getArenas()) {
ParkourArena arena = arenaHandler.getArena(arenaId); ParkourArena arena = arenaHandler.getArena(arenaId);
if (arena != null) { if (arena != null) {
builder.append(counter++).append(". ").append(arena.getArenaName()).append("\n"); builder.append("\n").append(counter++).append(". ").append(arena.getArenaName());
} }
} }
sender.sendMessage(builder.toString()); sender.sendMessage(builder.toString());
@ -86,7 +89,7 @@ public class ParkourGroupListCommand implements TabExecutor {
for (ParkourArenaGroup group : arenaGroups) { for (ParkourArenaGroup group : arenaGroups) {
groupNames.add(group.getGroupName()); groupNames.add(group.getGroupName());
} }
return groupNames; return filterMatchingContains(groupNames, arguments[0]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -4,6 +4,7 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup; import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler; import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.util.StringSanitizer; import net.knarcraft.minigames.util.StringSanitizer;
import net.knarcraft.minigames.util.TabCompleteHelper; import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -15,6 +16,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The command for setting the group of an arena * The command for setting the group of an arena
*/ */
@ -31,7 +34,7 @@ public class ParkourGroupSetCommand implements TabExecutor {
ParkourArena specifiedArena = arenaHandler.getArena(arguments[0]); ParkourArena specifiedArena = arenaHandler.getArena(arguments[0]);
if (specifiedArena == null) { if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified parkour arena."); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
@ -53,7 +56,7 @@ public class ParkourGroupSetCommand implements TabExecutor {
arenaHandler.setGroup(specifiedArena.getArenaId(), arenaGroup); arenaHandler.setGroup(specifiedArena.getArenaId(), arenaGroup);
commandSender.sendMessage("The arena's group has been updated"); commandSender.sendMessage(Message.SUCCESS_ARENA_GROUP_UPDATED.getMessage());
return true; return true;
} }
@ -62,7 +65,7 @@ public class ParkourGroupSetCommand implements TabExecutor {
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (arguments.length == 1) { if (arguments.length == 1) {
return TabCompleteHelper.getParkourArenas(); return filterMatchingContains(TabCompleteHelper.getParkourArenas(), arguments[0]);
} else if (arguments.length == 2) { } else if (arguments.length == 2) {
List<String> possibleValues = new ArrayList<>(); List<String> possibleValues = new ArrayList<>();
possibleValues.add("none"); possibleValues.add("none");
@ -70,7 +73,7 @@ public class ParkourGroupSetCommand implements TabExecutor {
for (ParkourArenaGroup group : MiniGames.getInstance().getParkourArenaHandler().getAllGroups()) { for (ParkourArenaGroup group : MiniGames.getInstance().getParkourArenaHandler().getAllGroups()) {
possibleValues.add(group.getGroupName()); possibleValues.add(group.getGroupName());
} }
return possibleValues; return filterMatchingContains(possibleValues, arguments[1]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -4,6 +4,7 @@ import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup; import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler; import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -14,6 +15,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The command for swapping the order of two arenas in a group * The command for swapping the order of two arenas in a group
*/ */
@ -30,26 +33,26 @@ public class ParkourGroupSwapCommand implements TabExecutor {
ParkourArena arena1 = arenaHandler.getArena(arguments[0]); ParkourArena arena1 = arenaHandler.getArena(arguments[0]);
if (arena1 == null) { if (arena1 == null) {
commandSender.sendMessage("Unable to find the first specified parkour arena."); commandSender.sendMessage(Message.ERROR_ARENA_1_NOT_FOUND.getMessage());
return false; return false;
} }
ParkourArena arena2 = arenaHandler.getArena(arguments[1]); ParkourArena arena2 = arenaHandler.getArena(arguments[1]);
if (arena2 == null) { if (arena2 == null) {
commandSender.sendMessage("Unable to find the second specified parkour arena."); commandSender.sendMessage(Message.ERROR_ARENA_2_NOT_FOUND.getMessage());
return false; return false;
} }
ParkourArenaGroup arena1Group = arenaHandler.getGroup(arena1.getArenaId()); ParkourArenaGroup arena1Group = arenaHandler.getGroup(arena1.getArenaId());
ParkourArenaGroup arena2Group = arenaHandler.getGroup(arena2.getArenaId()); ParkourArenaGroup arena2Group = arenaHandler.getGroup(arena2.getArenaId());
if (arena1Group == null || !arena1Group.equals(arena2Group)) { if (arena1Group == null || !arena1Group.equals(arena2Group)) {
commandSender.sendMessage("You cannot swap arenas in different groups!"); commandSender.sendMessage(Message.ERROR_SWAP_DIFFERENT_GROUPS.getMessage());
return false; return false;
} }
arena1Group.swapArenas(arena1Group.getArenas().indexOf(arena1.getArenaId()), arena1Group.swapArenas(arena1Group.getArenas().indexOf(arena1.getArenaId()),
arena1Group.getArenas().indexOf(arena2.getArenaId())); arena1Group.getArenas().indexOf(arena2.getArenaId()));
commandSender.sendMessage("The arenas have been swapped!"); commandSender.sendMessage(Message.SUCCESS_ARENAS_SWAPPED.getMessage());
return true; return true;
} }
@ -63,9 +66,9 @@ public class ParkourGroupSwapCommand implements TabExecutor {
for (ParkourArena parkourArena : arenaHandler.getArenasInAGroup()) { for (ParkourArena parkourArena : arenaHandler.getArenasInAGroup()) {
arenaNames.add(parkourArena.getArenaName()); arenaNames.add(parkourArena.getArenaName());
} }
return arenaNames; return filterMatchingContains(arenaNames, arguments[0]);
} else if (arguments.length == 2) { } else if (arguments.length == 2) {
return getArenaNamesInSameGroup(arguments[0]); return filterMatchingContains(getArenaNamesInSameGroup(arguments[0]), arguments[1]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -2,6 +2,7 @@ package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.config.Message;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -23,13 +24,13 @@ public class RemoveParkourArenaCommand implements CommandExecutor {
// Get the specified arena // Get the specified arena
ParkourArena targetArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]); ParkourArena targetArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]);
if (targetArena == null) { if (targetArena == null) {
commandSender.sendMessage("Unable to find the specified arena"); commandSender.sendMessage(Message.ERROR_ARENA_NOT_FOUND.getMessage());
return false; return false;
} }
// Remove the arena // Remove the arena
MiniGames.getInstance().getParkourArenaHandler().removeArena(targetArena); MiniGames.getInstance().getParkourArenaHandler().removeArena(targetArena);
commandSender.sendMessage("The specified arena has been successfully removed"); commandSender.sendMessage(Message.SUCCESS_ARENA_REMOVED.getMessage());
return true; return true;
} }

View File

@ -10,6 +10,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.minigames.util.TabCompleteHelper.filterMatchingContains;
/** /**
* The tab-completer for the remove arena command * The tab-completer for the remove arena command
*/ */
@ -20,7 +22,7 @@ public class RemoveParkourArenaTabCompleter implements TabCompleter {
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (arguments.length == 1) { if (arguments.length == 1) {
return TabCompleteHelper.getParkourArenas(); return filterMatchingContains(TabCompleteHelper.getParkourArenas(), arguments[0]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -0,0 +1,300 @@
package net.knarcraft.minigames.config;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.container.PlaceholderContainer;
import net.knarcraft.minigames.util.ColorHelper;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* A message which ca be displayed to the user
*/
public enum Message {
/* ************** *
* Error messages *
* ************** */
/**
* The message displayed if saving arena groups fails
*/
ERROR_CANNOT_SAVE_ARENA_GROUPS("&cUnable to save current arena groups! Data loss can occur!"),
/**
* The message displayed if an un-parse-able message is given by a user
*/
ERROR_MATERIAL_NOT_PARSE_ABLE("&cUnable to parse material: {material}"),
/**
* The message displayed if a player tries to be teleported to/from an arena with a passenger
*/
ERROR_TELEPORT_WITH_PASSENGER("&cYou cannot be teleported with a passenger!"),
/**
* The message displayed if a player tries to be teleported to/from an arena with a vehicle
*/
ERROR_TELEPORT_IN_VEHICLE("&cYou cannot be teleported while in a vehicle"),
/**
* The message displayed if an arena cannot be loaded
*/
ERROR_ARENA_NOT_LOADED("&cCould not load the arena at configuration section {section}. Please check " +
"the {file} storage file for issues."),
/**
* The message displayed if an arena's data cannot be loaded
*/
ERROR_ARENA_DATA_NOT_LOADED("&cUnable to load arena data for dropper arena: {arena}"),
/**
* The message displayed if the user specifies an unrecognized arena
*/
ERROR_ARENA_NOT_FOUND("&cUnable to find the specified arena."),
/**
* The message displayed if the user specifies an unrecognized group
*/
ERROR_GROUP_NOT_FOUND("&cUnable to find the specified group!"),
/**
* The message displayed if the console tries to execute a player-only command
*/
ERROR_PLAYER_ONLY("&cThis command must be used by a player"),
/**
* The message displayed if the name of an arena is duplicated
*/
ERROR_ARENA_NAME_COLLISION("&cThere already exists an arena with that name!"),
/**
* The message displayed if the player is required to win on the default difficulty first
*/
ERROR_NORMAL_MODE_REQUIRED("&cYou must complete this arena in normal mode first!"),
/**
* The message displayed if the player is required to win on the default difficulty for all arenas in the group first
*/
ERROR_GROUP_NORMAL_MODE_REQUIRED("&cYou have not yet beaten the default game-mode for all arenas in this group!"),
/**
* The message displayed if the player is required to beat the previous arena in the group
*/
ERROR_PREVIOUS_ARENA_REQUIRED("&cYou have not yet beaten the previous arena!"),
/**
* The message displayed if player teleportation failed for some reason
*/
ERROR_ARENA_TELEPORT_FAILED("&cUnable to teleport you to the arena."),
/**
* The message displayed if the player tries to quit the arena while not in an arena
*/
ERROR_NOT_IN_ARENA("&cYou are not in a mini-games arena!"),
/**
* The message displayed if the player tries to join an arena while already playing
*
* <p>This should in theory be impossible, as players cannot use any commands except /miniGamesLeave while playing
* in an arena.</p>
*/
ERROR_ALREADY_PLAYING("&cYou are already playing a mini-game!"),
/**
* The message displayed if a player tries to join an arena with a passenger or riding a vehicle
*/
ERROR_JOIN_IN_VEHICLE_OR_PASSENGER("&cYou cannot join an arena while inside a vehicle or carrying a passenger."),
/**
* The message displayed if the player tries to change an unrecognized arena property
*/
ERROR_UNKNOWN_PROPERTY("&cUnknown property specified."),
/**
* The message displayed if the given input to /dEdit or /pEdit's value is invalid
*/
ERROR_PROPERTY_INPUT_INVALID("&cUnable to change the property. Make sure your input is valid!"),
/**
* The message displayed if the first arena specified in /dgSwap or /pgSwap is invalid
*/
ERROR_ARENA_1_NOT_FOUND("&cUnable to find the first specified arena."),
/**
* The message displayed if the second arena specified in /dgSwap or /pgSwap is invalid
*/
ERROR_ARENA_2_NOT_FOUND("&cUnable to find the second specified dropper arena."),
/**
* The message displayed if the two groups specified for /dgSwap or /pgSwap are in different arenas
*/
ERROR_SWAP_DIFFERENT_GROUPS("&cYou cannot swap arenas in different groups!"),
/**
* The message displayed if a player tries to use any command other than /mLeave while in an arena
*/
ERROR_ILLEGAL_COMMAND("&cYou cannot use that command while in an arena!"),
/* **************** *
* Success messages *
* **************** */
/**
* The message displayed if an arena's group has been changed
*/
SUCCESS_ARENA_GROUP_UPDATED("&aThe arena's group has been updated"),
/**
* The message displayed if the MiniGames plugin is reloaded
*/
SUCCESS_PLUGIN_RELOADED("&aPlugin reloaded!"),
/**
* The message displayed if a new arena has been created
*/
SUCCESS_ARENA_CREATED("&aThe arena was successfully created!"),
/**
* The message displayed if a player clears/wins an arena for the first time
*/
SUCCESS_ARENA_FIRST_CLEAR("&aYou cleared the arena!"),
/**
* The message displayed when a player wins an arena
*/
SUCCESS_ARENA_WIN("&aYou won!"),
/**
* The message displayed when a player quits an arena
*/
SUCCESS_ARENA_QUIT("&aYou quit the arena!"),
/**
* The message used to display the current value of an arena property
*/
SUCCESS_CURRENT_VALUE("&aCurrent value of {property} is: {value}"),
/**
* The message used to announce that an arena property has been changed
*/
SUCCESS_PROPERTY_CHANGED("&aProperty {property} successfully changed"),
/**
* The message displayed when two arenas' order in a group have been swapped
*/
SUCCESS_ARENAS_SWAPPED("&aThe arenas have been swapped!"),
/**
* The message displayed when an arena has been removed
*/
SUCCESS_ARENA_REMOVED("&aThe specified arena has been successfully removed"),
/**
* The header displayed before listing all dropper arenas
*/
SUCCESS_DROPPER_ARENAS_LIST("&aDropper arenas:&r"),
/**
* The header displayed before listing all parkour arenas
*/
SUCCESS_PARKOUR_ARENAS_LIST("&aParkour arenas:&r"),
/**
* The message displayed when a player reaches a new checkpoint in a parkour arena
*/
SUCCESS_CHECKPOINT_REACHED("&aCheckpoint reached!"),
/**
* The header displayed before listing all arenas (stages) in a group
*/
SUCCESS_GROUP_STAGES("&a{group}'s stages:&r"),
/**
* The message displayed when a new record has been achieved
*/
SUCCESS_RECORD_ACHIEVED("&aYou just set a {recordInfo} on the {gameMode} game-mode!"),
/**
* The partial message used to describe that the player achieved a world record
*/
RECORD_ACHIEVED_GLOBAL("new {recordType} record"),
/**
* The partial message used to describe that the player achieved a personal best record
*/
RECORD_ACHIEVED_PERSONAL("personal {recordType} record"),
/**
* The message displayed when a player joins an arena
*/
SUCCESS_ARENA_JOINED("&aYou joined the arena."),
;
private final @NotNull String defaultMessage;
/**
* Instantiates a new message
*
* @param defaultMessage <p>The default value of the message</p>
*/
Message(@NotNull String defaultMessage) {
this.defaultMessage = defaultMessage;
}
/**
* Gets the message this enum represents
*
* @return <p>The formatted message</p>
*/
public @NotNull String getMessage() {
return formatMessage(this.defaultMessage);
}
/**
* Gets the message this enum represents
*
* @param placeholder <p>The placeholder to replace</p>
* @param replacement <p>The replacement to use</p>
* @return <p>The formatted message</p>
*/
public @NotNull String getMessage(@NotNull String placeholder, @NotNull String replacement) {
return formatMessage(this.defaultMessage.replace(placeholder, replacement));
}
/**
* Gets the message this enum represents, intended for display within another message
*
* @param placeholder <p>The placeholder to replace</p>
* @param replacement <p>The replacement to use</p>
* @return <p>The formatted message</p>
*/
public @NotNull String getPartialMessage(@NotNull String placeholder, @NotNull String replacement) {
return ColorHelper.translateAllColorCodes(this.defaultMessage.replace(placeholder, replacement));
}
/**
* Gets the message this enum represents
*
* @param placeholders <p>The placeholder -> replacement map specifying necessary replacements</p>
* @return <p>The formatted message</p>
*/
public @NotNull String getMessage(@NotNull PlaceholderContainer placeholders) {
String replaced = this.defaultMessage;
for (Map.Entry<String, String> entry : placeholders.getPlaceholders().entrySet()) {
replaced = replaced.replace(entry.getKey(), entry.getValue());
}
return formatMessage(replaced);
}
/**
* Gets the formatted version of the given message
*
* @param message <p>The message to format</p>
* @return <p>The formatted message</p>
*/
private @NotNull String formatMessage(@NotNull String message) {
String prefix = MiniGames.getInstance().getDescription().getPrefix();
return ColorHelper.translateAllColorCodes("#546EED[&r&l" + prefix + "#546EED]&r " + message);
}
}

View File

@ -0,0 +1,41 @@
package net.knarcraft.minigames.container;
import java.util.HashMap;
import java.util.Map;
/**
* A container for keeping track of several placeholder to value mappings
*/
public class PlaceholderContainer {
private final Map<String, String> placeholders;
/**
* Instantiates a new placeholder container
*/
public PlaceholderContainer() {
this.placeholders = new HashMap<>();
}
/**
* Gets all placeholders
*
* @return <p>All placeholders</p>
*/
public Map<String, String> getPlaceholders() {
return new HashMap<>(this.placeholders);
}
/**
* Adds a new placeholder
*
* @param placeholder <p>The placeholder to register</p>
* @param value <p>The value of the placeholder</p>
* @return <p>This object</p>
*/
public PlaceholderContainer add(String placeholder, String value) {
this.placeholders.put(placeholder, value);
return this;
}
}

View File

@ -2,6 +2,7 @@ package net.knarcraft.minigames.listener;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaSession; import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.config.Message;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@ -40,7 +41,7 @@ public class CommandListener implements Listener {
} }
} }
player.sendMessage("You cannot use that command while in an arena!"); player.sendMessage(Message.ERROR_ILLEGAL_COMMAND.getMessage());
event.setCancelled(true); event.setCancelled(true);
} }

View File

@ -8,6 +8,7 @@ import net.knarcraft.minigames.arena.dropper.DropperArenaSession;
import net.knarcraft.minigames.arena.parkour.ParkourArena; import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaSession; import net.knarcraft.minigames.arena.parkour.ParkourArenaSession;
import net.knarcraft.minigames.config.DropperConfiguration; import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.config.ParkourConfiguration; import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.config.SharedConfiguration; import net.knarcraft.minigames.config.SharedConfiguration;
import org.bukkit.Location; import org.bukkit.Location;
@ -80,19 +81,30 @@ public class MoveListener implements Listener {
List<Location> checkpoints = arena.getCheckpoints(); List<Location> checkpoints = arena.getCheckpoints();
for (Location checkpoint : checkpoints) { for (Location checkpoint : checkpoints) {
Location previousCheckpoint = arenaSession.getRegisteredCheckpoint(); Location previousCheckpoint = arenaSession.getRegisteredCheckpoint();
if (checkpoint.getBlock().equals(event.getTo().getBlock()) && !checkpoint.equals(previousCheckpoint)) {
if (parkourConfiguration.enforceCheckpointOrder()) {
int checkpointIndex = checkpoints.indexOf(checkpoint);
int previousIndex = previousCheckpoint == null ? -1 : checkpoints.indexOf(previousCheckpoint);
if (checkpointIndex - previousIndex != 1) {
continue;
}
}
arenaSession.registerCheckpoint(checkpoint.clone()); // Skip if checkpoint has not been reached
event.getPlayer().sendMessage("Checkpoint reached!"); if (!checkpoint.getBlock().equals(event.getTo().getBlock())) {
continue;
}
// If the checkpoint is the same as the previously reached one, abort
if (previousCheckpoint != null && checkpoint.getBlock().equals(previousCheckpoint.getBlock())) {
return; return;
} }
// If not the correct checkpoint according to the enforced order, abort
if (parkourConfiguration.enforceCheckpointOrder()) {
int checkpointIndex = checkpoints.indexOf(checkpoint);
int previousIndex = previousCheckpoint == null ? -1 : checkpoints.indexOf(previousCheckpoint);
if (checkpointIndex - previousIndex != 1) {
return;
}
}
// Register the checkpoint
arenaSession.registerCheckpoint(checkpoint.clone());
event.getPlayer().sendMessage(Message.SUCCESS_CHECKPOINT_REACHED.getMessage());
return;
} }
} }

View File

@ -1,79 +0,0 @@
package net.knarcraft.minigames.listener;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.parkour.ParkourArenaSession;
import org.bukkit.Bukkit;
import org.bukkit.Location;
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 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, ArenaSession> leftSessions = new HashMap<>();
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
Player player = event.getPlayer();
ArenaSession arenaSession = MiniGames.getInstance().getSession(event.getPlayer().getUniqueId());
if (arenaSession == null) {
return;
}
MiniGames.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)) {
MiniGames.log(Level.WARNING, "Found un-exited dropper session!");
Bukkit.getScheduler().runTaskLater(MiniGames.getInstance(), () -> {
leftSessions.get(playerId).triggerQuit(false);
MiniGames.log(Level.WARNING, "Triggered a quit!");
leftSessions.remove(playerId);
}, 80);
}
}
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
Location targetLocation = event.getTo();
if (targetLocation == null || event.isCancelled()) {
return;
}
ArenaSession arenaSession = MiniGames.getInstance().getSession(event.getPlayer().getUniqueId());
if (arenaSession == null) {
return;
}
if (targetLocation.equals(arenaSession.getArena().getSpawnLocation())) {
return;
}
if (arenaSession instanceof ParkourArenaSession parkourArenaSession &&
targetLocation.equals(parkourArenaSession.getRegisteredCheckpoint())) {
return;
}
arenaSession.triggerQuit(false);
}
}

View File

@ -0,0 +1,92 @@
package net.knarcraft.minigames.listener;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.arena.parkour.ParkourArenaSession;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.spigotmc.event.player.PlayerSpawnLocationEvent;
import java.util.logging.Level;
/**
* A listener for players leaving/joining the server, or leaving the server unexpectedly
*/
public class PlayerStateChangeListener implements Listener {
@EventHandler
public void onPlayerSpawn(PlayerSpawnLocationEvent event) {
Player player = event.getPlayer();
// Restore any lingering arena states
Location restoreLocation;
restoreLocation = restoreStateIfNecessary(player, MiniGames.getInstance().getDropperArenaPlayerRegistry());
if (restoreLocation != null) {
event.setSpawnLocation(restoreLocation);
}
restoreLocation = restoreStateIfNecessary(player, MiniGames.getInstance().getParkourArenaPlayerRegistry());
if (restoreLocation != null) {
event.setSpawnLocation(restoreLocation);
}
}
/**
* Prevent the player from teleporting away from an arena for any reason
*
* @param event <p>The triggered teleport event</p>
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerTeleport(PlayerTeleportEvent event) {
Location targetLocation = event.getTo();
if (targetLocation == null) {
return;
}
// Ignore if not in an arena session
ArenaSession arenaSession = MiniGames.getInstance().getSession(event.getPlayer().getUniqueId());
if (arenaSession == null) {
return;
}
// If teleported to the arena's spawn, it's fine
if (targetLocation.equals(arenaSession.getArena().getSpawnLocation())) {
return;
}
// If teleported to the arena's checkpoint, it's fine
if (arenaSession instanceof ParkourArenaSession parkourArenaSession &&
targetLocation.equals(parkourArenaSession.getRegisteredCheckpoint())) {
return;
}
event.setCancelled(true);
}
/**
* Restores the state of the given player if a lingering session is found in the given player registry
*
* @param player <p>The player whose state should be checked</p>
* @param playerRegistry <p>The registry to check for a lingering state</p>
* @return <p>The location the player should spawn in, or null if not restored</p>
*/
private Location restoreStateIfNecessary(Player player, ArenaPlayerRegistry<?> playerRegistry) {
PlayerEntryState entryState = playerRegistry.getEntryState(player.getUniqueId());
if (entryState != null) {
MiniGames.log(Level.INFO, "Found existing state for joining player " + player +
". Attempting to restore the player's state.");
playerRegistry.removePlayer(player.getUniqueId(), false);
Bukkit.getScheduler().runTaskLater(MiniGames.getInstance(), () -> entryState.restore(player), 1);
return entryState.getEntryLocation();
} else {
return null;
}
}
}

View File

@ -1,18 +1,68 @@
package net.knarcraft.minigames.util; package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.PlayerEntryState;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
/**
* A helper class for dealing with arena storage
*/
public final class ArenaStorageHelper { public final class ArenaStorageHelper {
private ArenaStorageHelper() { private ArenaStorageHelper() {
} }
/**
* Stores the given entry states to disk
*
* @param key <p>The key specifying the correct entry state file</p>
* @param entryStates <p>The entry states to save</p>
*/
public static void storeArenaPlayerEntryStates(String key, Set<PlayerEntryState> entryStates) {
YamlConfiguration configuration = new YamlConfiguration();
configuration.set(key, new ArrayList<>(entryStates));
try {
configuration.save(new File(MiniGames.getInstance().getDataFolder(), key + "EntryStates.yml"));
} catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save entry states to disk");
}
}
/**
* Gets saved entry states from disk
*
* @param key <p>The key specifying the correct entry state file</p>
* @return <p>The previously saved entry states</p>
*/
public static Set<PlayerEntryState> getArenaPlayerEntryStates(String key) {
File entryStateFile = new File(MiniGames.getInstance().getDataFolder(), key + "EntryStates.yml");
if (!entryStateFile.exists()) {
return new HashSet<>();
}
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(entryStateFile);
Set<PlayerEntryState> output = new HashSet<>();
List<?> entries = configuration.getList(key, new ArrayList<>());
for (Object entry : entries) {
if (entry instanceof PlayerEntryState entryState) {
output.add(entryState);
}
}
return output;
}
/** /**
* Gets the file used to store the given arena id's data * Gets the file used to store the given arena id's data
* *
@ -20,7 +70,7 @@ public final class ArenaStorageHelper {
* @param arenaId <p>The id of the arena to get a data file for</p> * @param arenaId <p>The id of the arena to get a data file for</p>
* @return <p>The file the arena's data is/should be stored in</p> * @return <p>The file the arena's data is/should be stored in</p>
*/ */
static @NotNull File getArenaDataFile(File root, @NotNull UUID arenaId) { public static @NotNull File getArenaDataFile(File root, @NotNull UUID arenaId) {
File arenaDataFile = new File(root, arenaId + ".yml"); File arenaDataFile = new File(root, arenaId + ".yml");
if (!root.exists() && !root.mkdirs()) { if (!root.exists() && !root.mkdirs()) {
MiniGames.log(Level.SEVERE, "Unable to create the arena data directories"); MiniGames.log(Level.SEVERE, "Unable to create the arena data directories");

View File

@ -0,0 +1,29 @@
package net.knarcraft.minigames.util;
import net.md_5.bungee.api.ChatColor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class for converting colors
*/
public class ColorHelper {
/**
* Translates all found color codes to formatting in a string
*
* @param message <p>The string to search for color codes</p>
* @return <p>The message with color codes translated</p>
*/
public static String translateAllColorCodes(String message) {
message = ChatColor.translateAlternateColorCodes('&', message);
Pattern pattern = Pattern.compile("&?(#[a-fA-F0-9]{6})");
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group(1)));
}
return message;
}
}

View File

@ -9,6 +9,8 @@ import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup; import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry; import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaStorageKey; import net.knarcraft.minigames.arena.dropper.DropperArenaStorageKey;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.container.PlaceholderContainer;
import net.knarcraft.minigames.container.SerializableMaterial; import net.knarcraft.minigames.container.SerializableMaterial;
import net.knarcraft.minigames.container.SerializableUUID; import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.Location; import org.bukkit.Location;
@ -159,8 +161,8 @@ public final class DropperArenaStorageHelper {
DropperArenaStorageKey.WIN_BLOCK_TYPE.getKey()); DropperArenaStorageKey.WIN_BLOCK_TYPE.getKey());
if (arenaName == null || spawnLocation == null) { if (arenaName == null || spawnLocation == null) {
MiniGames.log(Level.SEVERE, "Could not load the arena at configuration " + MiniGames.log(Level.SEVERE, Message.ERROR_ARENA_NOT_LOADED.getMessage(new PlaceholderContainer().add(
"section " + configurationSection.getName() + ". Please check the dropper_arenas storage file for issues."); "{section}", configurationSection.getName()).add("{file}", "dropper_arenas")));
return null; return null;
} }
if (winBlockType == null) { if (winBlockType == null) {
@ -170,7 +172,8 @@ public final class DropperArenaStorageHelper {
// Generate new, empty arena data if not available // Generate new, empty arena data if not available
DropperArenaData arenaData = loadDropperArenaData(arenaId); DropperArenaData arenaData = loadDropperArenaData(arenaId);
if (arenaData == null) { if (arenaData == null) {
MiniGames.log(Level.SEVERE, "Unable to load arena data for dropper arena" + arenaId); MiniGames.log(Level.SEVERE, Message.ERROR_ARENA_DATA_NOT_LOADED.getMessage("{arena}",
arenaId.toString()));
arenaData = getEmptyDropperData(arenaId); arenaData = getEmptyDropperData(arenaId);
} }

View File

@ -1,6 +1,7 @@
package net.knarcraft.minigames.util; package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.config.Message;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
@ -43,7 +44,7 @@ public final class MaterialHelper {
if (matched != null) { if (matched != null) {
parsedMaterials.add(matched); parsedMaterials.add(matched);
} else { } else {
MiniGames.log(Level.WARNING, "Unable to parse: " + string); MiniGames.log(Level.WARNING, Message.ERROR_MATERIAL_NOT_PARSE_ABLE.getMessage("{material}", string));
} }
} }
return parsedMaterials; return parsedMaterials;

View File

@ -9,6 +9,8 @@ import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup; import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaRecordsRegistry; import net.knarcraft.minigames.arena.parkour.ParkourArenaRecordsRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArenaStorageKey; import net.knarcraft.minigames.arena.parkour.ParkourArenaStorageKey;
import net.knarcraft.minigames.config.Message;
import net.knarcraft.minigames.container.PlaceholderContainer;
import net.knarcraft.minigames.container.SerializableMaterial; import net.knarcraft.minigames.container.SerializableMaterial;
import net.knarcraft.minigames.container.SerializableUUID; import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.Location; import org.bukkit.Location;
@ -164,8 +166,8 @@ public final class ParkourArenaStorageHelper {
// The arena name and spawn location must be present // The arena name and spawn location must be present
if (arenaName == null || spawnLocation == null) { if (arenaName == null || spawnLocation == null) {
MiniGames.log(Level.SEVERE, "Could not load the arena at configuration " + MiniGames.log(Level.SEVERE, Message.ERROR_ARENA_NOT_LOADED.getMessage(new PlaceholderContainer().add(
"section " + configurationSection.getName() + ". Please check the parkour_arenas storage file for issues."); "{section}", configurationSection.getName()).add("{file}", "parkour_arena")));
return null; return null;
} }
@ -177,7 +179,8 @@ public final class ParkourArenaStorageHelper {
// Generate new, empty arena data if not available // Generate new, empty arena data if not available
ParkourArenaData arenaData = loadParkourArenaData(arenaId); ParkourArenaData arenaData = loadParkourArenaData(arenaId);
if (arenaData == null) { if (arenaData == null) {
MiniGames.log(Level.SEVERE, "Unable to load arena data for parkour arena" + arenaId); MiniGames.log(Level.SEVERE, Message.ERROR_ARENA_DATA_NOT_LOADED.getMessage("{arena}",
arenaId.toString()));
arenaData = getEmptyParkourData(arenaId); arenaData = getEmptyParkourData(arenaId);
} }

View File

@ -1,6 +1,7 @@
package net.knarcraft.minigames.util; package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.config.Message;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@ -35,7 +36,7 @@ public final class PlayerTeleporter {
passenger.teleport(location); passenger.teleport(location);
} }
} else { } else {
player.sendMessage("You cannot be teleported with a passenger!"); player.sendMessage(Message.ERROR_TELEPORT_WITH_PASSENGER.getMessage());
return false; return false;
} }
} }
@ -45,16 +46,16 @@ public final class PlayerTeleporter {
player.eject(); player.eject();
vehicle.teleport(location); vehicle.teleport(location);
} else { } else {
player.sendMessage("You cannot be teleported while in a vehicle"); player.sendMessage(Message.ERROR_TELEPORT_IN_VEHICLE.getMessage());
return false; return false;
} }
} }
//Stop the existing player velocity to prevent unevenness between players // Stop the existing player velocity to prevent unevenness between players
player.setVelocity(new Vector(0, 0, 0)); player.setVelocity(new Vector(0, 0, 0));
player.setInvulnerable(true); player.setInvulnerable(true);
player.teleport(location); player.teleport(location);
player.setVelocity(new Vector(0, 0, 0)); player.setVelocity(new Vector(0, 0, 0));
//When teleporting a player out of the arena, sometimes the move listener is slow to react, giving the player // When teleporting a player out of the arena, sometimes the move listener is slow to react, giving the player
// lethal velocity, and causing damage. That's why the player is given 5 ticks of invulnerability // lethal velocity, and causing damage. That's why the player is given 5 ticks of invulnerability
if (!immediately) { if (!immediately) {
Bukkit.getScheduler().runTaskLater(MiniGames.getInstance(), () -> player.setInvulnerable(false), 5); Bukkit.getScheduler().runTaskLater(MiniGames.getInstance(), () -> player.setInvulnerable(false), 5);

View File

@ -3,18 +3,24 @@ package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames; import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.Arena; import net.knarcraft.minigames.arena.Arena;
import net.knarcraft.minigames.arena.ArenaHandler; import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.arena.EditablePropertyType;
import net.knarcraft.minigames.arena.dropper.DropperArenaEditableProperty; import net.knarcraft.minigames.arena.dropper.DropperArenaEditableProperty;
import net.knarcraft.minigames.arena.parkour.ParkourArenaEditableProperty; import net.knarcraft.minigames.arena.parkour.ParkourArenaEditableProperty;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* A helper-class for common tab-completions * A helper-class for common tab-completions
*/ */
public final class TabCompleteHelper { public final class TabCompleteHelper {
private static Map<EditablePropertyType, List<String>> tabCompleteSuggestions;
private TabCompleteHelper() { private TabCompleteHelper() {
} }
@ -76,4 +82,137 @@ public final class TabCompleteHelper {
return arenaProperties; return arenaProperties;
} }
/**
* Finds tab complete values that contain the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(@NotNull List<String> values, @NotNull String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Gets tab-complete suggestions for the given property type
*
* @param propertyType <p>The property type to get suggestions for</p>
* @return <p>The suggestions produced</p>
*/
public static List<String> getTabCompleteSuggestions(EditablePropertyType propertyType) {
if (tabCompleteSuggestions == null) {
tabCompleteSuggestions = new HashMap<>();
tabCompleteSuggestions.put(EditablePropertyType.LOCATION, getLocationSuggestions());
tabCompleteSuggestions.put(EditablePropertyType.ARENA_NAME, getNameSuggestions());
tabCompleteSuggestions.put(EditablePropertyType.HORIZONTAL_VELOCITY, getHorizontalVelocitySuggestions());
tabCompleteSuggestions.put(EditablePropertyType.VERTICAL_VELOCITY, getVerticalVelocitySuggestions());
tabCompleteSuggestions.put(EditablePropertyType.BLOCK_TYPE, getBlockTypeSuggestions());
tabCompleteSuggestions.put(EditablePropertyType.CHECKPOINT_CLEAR, getCheckpointClearSuggestions());
tabCompleteSuggestions.put(EditablePropertyType.MATERIAL_LIST, getMaterialListSuggestions());
}
return tabCompleteSuggestions.get(propertyType);
}
/**
* Gets suggestions for a list of materials
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getMaterialListSuggestions() {
List<String> suggestions = new ArrayList<>();
suggestions.add("LAVA,MAGMA_BLOCK");
suggestions.add("WATER,MAGMA_BLOCK,LAVA,+BUTTONS,+CORALS");
return suggestions;
}
/**
* Gets suggestions for checkpointClear
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getCheckpointClearSuggestions() {
List<String> suggestions = new ArrayList<>();
suggestions.add("true");
return suggestions;
}
/**
* Gets suggestions for a block material
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getBlockTypeSuggestions() {
List<String> materials = new ArrayList<>();
for (Material material : Material.values()) {
if (material.isBlock()) {
materials.add(material.name());
}
}
return materials;
}
/**
* Gets suggestions for a vertical velocity
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getVerticalVelocitySuggestions() {
List<String> velocities = new ArrayList<>();
velocities.add("0.01");
velocities.add("0.5");
velocities.add("1");
velocities.add("3");
velocities.add("10");
velocities.add("30");
velocities.add("75");
return velocities;
}
/**
* Gets suggestions for a horizontal velocity
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getHorizontalVelocitySuggestions() {
List<String> velocities = new ArrayList<>();
velocities.add("0.01");
velocities.add("0.1");
velocities.add("0.5");
velocities.add("1");
return velocities;
}
/**
* Gets suggestions for an arena name
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getNameSuggestions() {
List<String> locations = new ArrayList<>();
locations.add("DropperArena1");
locations.add("DropperArena2");
locations.add("ParkourArena1");
locations.add("ParkourArena2");
return locations;
}
/**
* Gets suggestions for a location
*
* @return <p>A list of suggestions</p>
*/
private static List<String> getLocationSuggestions() {
List<String> locations = new ArrayList<>();
locations.add("here");
locations.add("x,y,z");
return locations;
}
} }

View File

@ -1,4 +1,5 @@
name: MiniGames name: MiniGames
prefix: MiniGames
version: '${project.version}' version: '${project.version}'
main: net.knarcraft.minigames.MiniGames main: net.knarcraft.minigames.MiniGames
api-version: 1.19 api-version: 1.19