Implements #35

This commit is contained in:
Kristian Knarvik 2024-04-28 16:21:54 +02:00
parent 3a68d30044
commit 87788e60a3
13 changed files with 566 additions and 151 deletions

View File

@ -282,7 +282,9 @@ These are all the options that can be changed for an arena.
- LIGHTNING_ROD - LIGHTNING_ROD
- CHAIN - CHAIN
## Record placeholders ## Placeholders
### Record Placeholders
Player records can be displayed on a leaderboard by using PlaceholderAPI. If you want to display a sign-based Player records can be displayed on a leaderboard by using PlaceholderAPI. If you want to display a sign-based
leaderboard, you can use the [Placeholder Signs](https://git.knarcraft.net/EpicKnarvik97/PlaceholderSigns) plugin. The leaderboard, you can use the [Placeholder Signs](https://git.knarcraft.net/EpicKnarvik97/PlaceholderSigns) plugin. The
@ -291,16 +293,34 @@ format for the built-in placeholders is as follows:
`%gameMode_record_recordType_gameModeType_identifierType_identifier_recordPosition_infoType%` `%gameMode_record_recordType_gameModeType_identifierType_identifier_recordPosition_infoType%`
| Variable | Values | Description | | Variable | Values | Description |
|----------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------| |----------------|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| gameMode | dropper / parkour | A selection of which game-mode you are getting a record for | | gameMode | dropper / parkour | A selection of which game-mode you are getting a record for |
| record | | This must be as-is. It's a selector in case placeholders are added for more than records. | | record | | This must be as-is. It's a selector for the type of placeholder to get. |
| recordType | deaths / time | Selects the type of record to get (deaths or time). | | recordType | deaths / time | Selects the type of record to get (deaths or time). |
| gameModeType | default / inverted / random | Selects the game-mode to get the record for. | | gameModeType | default / inverted / random / hardcore | Selects the game-mode to get the record for. |
| identifierType | arena / group | The type of thing the following identifier points to (an arena or an arena group). | | identifierType | arena / group | The type of thing the following identifier points to (an arena or an arena group). |
| identifier | ? | An identifier (the name or UUID) for an arena or a group (whichever was chosen as identifierType). | | identifier | ? | An identifier (the name or UUID) for an arena or a group (whichever was chosen as identifierType). |
| recordPosition | 1 / 2 / 3 / ... | The position of the record to get (1 = first place, 2 = second place, etc.). | | recordPosition | 1 / 2 / 3 / ... | The position of the record to get (1 = first place, 2 = second place, etc.). |
| infoType | player / value / combined | The type of info to get. Player gets the player name, Value gets the value of the achieved record. Combined gets "Player: Record". | | infoType | player / value / combined | The type of info to get. Player gets the player name, Value gets the value of the achieved record. Combined gets "Player: Record". |
### Player Placeholders
The number of currently playing players can be displayed using PlaceholderAPI. If you want to display a sign-based
leaderboard, you can use the [Placeholder Signs](https://git.knarcraft.net/EpicKnarvik97/PlaceholderSigns) plugin. The
format for the built-in placeholders is as follows:
`%gameMode_players_playing_gameModeType_identifierType_identifier_infoType_additionalSpecifier%`
| Variable | Values | Description |
|-----------------|----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| gameMode | dropper / parkour | A selection of which game-mode you are getting a record for |
| players_playing | | This must be as-is. It's a selector in case placeholders are added for more than playing players. |
| gameModeType | default / inverted / random / hardcore / all | Selects the game-mode to get the players for. Note that "all" combines players for all game-modes. |
| identifierType | arena / group | The type of thing the following identifier points to (an arena or an arena group). |
| identifier | ? | An identifier (the name or UUID) for an arena or a group (whichever was chosen as identifierType). |
| playerInfoType | player / count | The type of info to get. Player gets the player name, and count gets the total number of players. |
| playerNumber | 1 / 2 / 3 / ... | The player number to get the name of. Players are numbered after their sorted names. This argument is not used when getting the player count. |
## Notes about material tags ## Notes about material tags
Where a list of material is allowed, this plugin supports using material tags that specify a set of blocks. This makes Where a list of material is allowed, this plugin supports using material tags that specify a set of blocks. This makes

View File

@ -70,8 +70,8 @@ import net.knarcraft.minigames.listener.MoveListener;
import net.knarcraft.minigames.listener.PlayerStateChangeListener; import net.knarcraft.minigames.listener.PlayerStateChangeListener;
import net.knarcraft.minigames.manager.EconomyManager; import net.knarcraft.minigames.manager.EconomyManager;
import net.knarcraft.minigames.manager.PermissionManager; import net.knarcraft.minigames.manager.PermissionManager;
import net.knarcraft.minigames.placeholder.DropperRecordExpansion; import net.knarcraft.minigames.placeholder.DropperExpansion;
import net.knarcraft.minigames.placeholder.ParkourRecordExpansion; import net.knarcraft.minigames.placeholder.ParkourExpansion;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission; import net.milkbowl.vault.permission.Permission;
@ -102,8 +102,8 @@ public final class MiniGames extends JavaPlugin {
private ParkourConfiguration parkourConfiguration; private ParkourConfiguration parkourConfiguration;
private DropperArenaHandler dropperArenaHandler; private DropperArenaHandler dropperArenaHandler;
private ArenaPlayerRegistry<DropperArena> dropperArenaPlayerRegistry; private ArenaPlayerRegistry<DropperArena> dropperArenaPlayerRegistry;
private DropperRecordExpansion dropperRecordExpansion; private DropperExpansion dropperExpansion;
private ParkourRecordExpansion parkourRecordExpansion; private ParkourExpansion parkourExpansion;
private ParkourArenaHandler parkourArenaHandler; private ParkourArenaHandler parkourArenaHandler;
private ArenaPlayerRegistry<ParkourArena> parkourArenaPlayerRegistry; private ArenaPlayerRegistry<ParkourArena> parkourArenaPlayerRegistry;
private PlayerVisibilityManager playerVisibilityManager; private PlayerVisibilityManager playerVisibilityManager;
@ -253,8 +253,8 @@ public final class MiniGames extends JavaPlugin {
this.parkourConfiguration.load(this.getConfig()); this.parkourConfiguration.load(this.getConfig());
// Clear record caches // Clear record caches
this.dropperRecordExpansion.clearCaches(); this.dropperExpansion.clearCaches();
this.parkourRecordExpansion.clearCaches(); this.parkourExpansion.clearCaches();
} }
@Override @Override
@ -316,12 +316,12 @@ public final class MiniGames extends JavaPlugin {
*/ */
private void doPluginIntegration() { private void doPluginIntegration() {
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
this.dropperRecordExpansion = new DropperRecordExpansion(this); this.dropperExpansion = new DropperExpansion(this);
if (!this.dropperRecordExpansion.register()) { if (!this.dropperExpansion.register()) {
log(Level.WARNING, "Unable to register PlaceholderAPI dropper expansion!"); log(Level.WARNING, "Unable to register PlaceholderAPI dropper expansion!");
} }
this.parkourRecordExpansion = new ParkourRecordExpansion(this); this.parkourExpansion = new ParkourExpansion(this);
if (!this.parkourRecordExpansion.register()) { if (!this.parkourExpansion.register()) {
log(Level.WARNING, "Unable to register PlaceholderAPI parkour expansion!"); log(Level.WARNING, "Unable to register PlaceholderAPI parkour expansion!");
} }
} }

View File

@ -52,4 +52,11 @@ public interface ArenaSession {
*/ */
void reset(); void reset();
/**
* Gets the game-mode the player is playing
*
* @return <p>The game-mode the player is playing</p>
*/
@NotNull ArenaGameMode getGameMode();
} }

View File

@ -123,6 +123,10 @@ public class ParkourArenaSession extends AbstractArenaSession {
// Teleport the player out of the arena // Teleport the player out of the arena
teleportToExit(false); teleportToExit(false);
if (MiniGames.getInstance().getParkourArenaPlayerRegistry().getPlayingPlayers(this.arena).isEmpty()) {
resetLevers();
}
} }
@Override @Override

View File

@ -0,0 +1,73 @@
package net.knarcraft.minigames.placeholder;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.placeholder.parsing.PlayerPlaceholderParser;
import net.knarcraft.minigames.placeholder.parsing.RecordPlaceholderParser;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
/**
* A placeholderAPI expansion for Dropper-related placeholders
*/
public class DropperExpansion extends PlaceholderExpansion {
private final @NotNull RecordPlaceholderParser recordPlaceholderParser;
private final @NotNull PlayerPlaceholderParser<DropperArena> playerPlaceholderParser;
/**
* Instantiates a new dropper expansion
*
* @param plugin <p>A reference to the mini-games plugin</p>
*/
public DropperExpansion(@NotNull MiniGames plugin) {
DropperArenaHandler arenaHandler = plugin.getDropperArenaHandler();
this.recordPlaceholderParser = new RecordPlaceholderParser(arenaHandler, DropperArenaGameMode::matchGameMode);
this.playerPlaceholderParser = new PlayerPlaceholderParser<>(arenaHandler, DropperArenaGameMode::matchGameMode,
plugin.getDropperArenaPlayerRegistry());
}
/**
* Clears record caches
*/
public void clearCaches() {
this.recordPlaceholderParser.clearCaches();
}
@Override
public String getIdentifier() {
return "dropper";
}
@Override
public String getAuthor() {
return "EpicKnarvik97";
}
@Override
public String getVersion() {
return "1.0.0";
}
@Override
public boolean persist() {
return true;
}
@Override
public String onRequest(OfflinePlayer player, String parameters) {
String[] parts = parameters.split("_");
// Record is used as the prefix for all record placeholders in case more placeholder types are added
if (parts[0].equalsIgnoreCase("record") && parts.length >= 7) {
return recordPlaceholderParser.onRequest(parameters, parts);
} else if (parts[0].equalsIgnoreCase("players")) {
return this.playerPlaceholderParser.onRequest(parameters, parts);
} else {
return parameters;
}
}
}

View File

@ -1,32 +0,0 @@
package net.knarcraft.minigames.placeholder;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import org.jetbrains.annotations.NotNull;
/**
* A placeholder expansion for dropper record placeholders
*/
public class DropperRecordExpansion extends RecordExpansion {
/**
* Initializes a new record expansion
*
* @param plugin <p>A reference to the dropper plugin</p>
*/
public DropperRecordExpansion(MiniGames plugin) {
super(plugin.getDropperArenaHandler());
}
@Override
public String getIdentifier() {
return "dropper";
}
@Override
protected @NotNull ArenaGameMode parseGameMode(@NotNull String gameMode) {
return DropperArenaGameMode.matchGameMode(gameMode);
}
}

View File

@ -0,0 +1,73 @@
package net.knarcraft.minigames.placeholder;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.placeholder.parsing.PlayerPlaceholderParser;
import net.knarcraft.minigames.placeholder.parsing.RecordPlaceholderParser;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
/**
* A placeholderAPI expansion for Parkour-related placeholders
*/
public class ParkourExpansion extends PlaceholderExpansion {
private final @NotNull RecordPlaceholderParser recordPlaceholderParser;
private final @NotNull PlayerPlaceholderParser<ParkourArena> playerPlaceholderParser;
/**
* Instantiates a new dropper expansion
*
* @param plugin <p>A reference to the mini-games plugin</p>
*/
public ParkourExpansion(@NotNull MiniGames plugin) {
ParkourArenaHandler arenaHandler = plugin.getParkourArenaHandler();
this.recordPlaceholderParser = new RecordPlaceholderParser(arenaHandler, ParkourArenaGameMode::matchGamemode);
this.playerPlaceholderParser = new PlayerPlaceholderParser<>(arenaHandler, ParkourArenaGameMode::matchGamemode,
plugin.getParkourArenaPlayerRegistry());
}
/**
* Clears record caches
*/
public void clearCaches() {
this.recordPlaceholderParser.clearCaches();
}
@Override
public String getIdentifier() {
return "parkour";
}
@Override
public String getAuthor() {
return "EpicKnarvik97";
}
@Override
public String getVersion() {
return "1.0.0";
}
@Override
public boolean persist() {
return true;
}
@Override
public String onRequest(OfflinePlayer player, String parameters) {
String[] parts = parameters.split("_");
// Record is used as the prefix for all record placeholders in case more placeholder types are added
if (parts[0].equalsIgnoreCase("record") && parts.length >= 7) {
return recordPlaceholderParser.onRequest(parameters, parts);
} else if (parts[0].equalsIgnoreCase("players")) {
return this.playerPlaceholderParser.onRequest(parameters, parts);
} else {
return parameters;
}
}
}

View File

@ -1,32 +0,0 @@
package net.knarcraft.minigames.placeholder;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import org.jetbrains.annotations.NotNull;
/**
* A placeholder expansion for parkour record placeholders
*/
public class ParkourRecordExpansion extends RecordExpansion {
/**
* Initializes a new record expansion
*
* @param plugin <p>A reference to the dropper plugin</p>
*/
public ParkourRecordExpansion(MiniGames plugin) {
super(plugin.getParkourArenaHandler());
}
@Override
public String getIdentifier() {
return "parkour";
}
@Override
protected @NotNull ArenaGameMode parseGameMode(@NotNull String gameMode) {
return ParkourArenaGameMode.matchGamemode(gameMode);
}
}

View File

@ -0,0 +1,37 @@
package net.knarcraft.minigames.placeholder.parsing;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The types of player information that can be retrieved in placeholders
*/
public enum PlayerInfoType {
/**
* The number of current players
*/
COUNT,
/**
* Information about a single player
*/
PLAYER,
;
/**
* Gets the info type specified in the given string
*
* @param type <p>The string specifying the info type</p>
* @return <p>The info type, or null if not found</p>
*/
public static @Nullable PlayerInfoType getFromString(@NotNull String type) {
for (PlayerInfoType infoType : PlayerInfoType.values()) {
if (infoType.name().equalsIgnoreCase(type)) {
return infoType;
}
}
return null;
}
}

View File

@ -0,0 +1,271 @@
package net.knarcraft.minigames.placeholder.parsing;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.Arena;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaGroup;
import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import net.knarcraft.minigames.arena.ArenaSession;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.logging.Level;
/**
* A parser for player-related placeholders
*/
public class PlayerPlaceholderParser<K extends Arena> {
private final @NotNull ArenaHandler<K, ?> arenaHandler;
private final @NotNull Function<String, ArenaGameMode> gameModeParser;
private final ArenaPlayerRegistry<K> playerRegistry;
/**
* Initializes a new player placeholder parser
*
* @param arenaHandler <p>The arena handler to get arenas from</p>
* @param gameModeParser <p>The function to use for parsing the specified game-mode</p>
* @param playerRegistry <p>The player registry to get player info from</p>
*/
public PlayerPlaceholderParser(@NotNull ArenaHandler<K, ?> arenaHandler,
@NotNull Function<String, ArenaGameMode> gameModeParser,
ArenaPlayerRegistry<K> playerRegistry) {
this.arenaHandler = arenaHandler;
this.gameModeParser = gameModeParser;
this.playerRegistry = playerRegistry;
}
/**
* The method to run when parsing a record placeholder request
*
* @param parameters <p>The parameters specified</p>
* @return <p>The resulting string</p>
*/
@NotNull
public String onRequest(@NotNull String parameters, @NotNull String[] parts) {
if (parts.length < 6) {
return parameters;
}
// String selector = parts[1]; // The selector for which aspect of players to get. Playing is the only one available yet.
String gameModeName = parts[2];
ArenaGameMode gameMode = gameModeParser.apply(gameModeName);
if (gameModeName.equalsIgnoreCase("combined") || gameModeName.equalsIgnoreCase("all")) {
gameMode = null;
}
SelectionType selectionType = SelectionType.getFromString(parts[3]);
String identifier = parts[4];
// The type of info to get. Either count (number of players) or player_position (a named player).
PlayerInfoType infoType = PlayerInfoType.getFromString(parts[5]);
if (infoType == null) {
return parameters;
}
String info = null;
if (selectionType == SelectionType.GROUP) {
ArenaGroup<?, ?> group = arenaHandler.getGroup(identifier);
if (group != null) {
info = getGroupInfo(group, gameMode, infoType, parts);
}
} else if (selectionType == SelectionType.ARENA) {
info = getArenaInfo(identifier, gameMode, infoType, parts);
}
return Objects.requireNonNullElse(info, parameters);
}
/**
* Gets information about an arena group's players
*
* @param group <p>The group to get info about</p>
* @param gameMode <p>The game-mode to get information for</p>
* @param infoType <p>The type of information to get</p>
* @param parts <p>The placeholder arguments specified by a user</p>
* @return <p>The specified group info, or null if the placeholder is invalid</p>
*/
@Nullable
private String getGroupInfo(@NotNull ArenaGroup<?, ?> group, @Nullable ArenaGameMode gameMode,
@NotNull PlayerInfoType infoType, @NotNull String[] parts) {
List<UUID> arenaIds = group.getArenas();
List<K> arenas = new ArrayList<>();
for (UUID arenaId : arenaIds) {
K arena = arenaHandler.getArena(arenaId);
if (arena != null) {
arenas.add(arena);
}
}
if (infoType == PlayerInfoType.COUNT) {
int playerCount = 0;
for (K arena : arenas) {
playerCount += getArenaPlayers(arena, gameMode).size();
}
return String.valueOf(playerCount);
} else if (infoType == PlayerInfoType.PLAYER) {
if (parts.length < 7) {
return null;
}
Integer playerNumber = getPositionNumber(parts[6]);
List<String> arenaPlayerNames = new ArrayList<>();
for (K arena : arenas) {
arenaPlayerNames.addAll(getArenaPlayersSorted(arena, gameMode));
}
arenaPlayerNames.sort(Comparator.naturalOrder());
if (playerNumber != null) {
if (playerNumber >= arenaPlayerNames.size()) {
return "";
} else {
return arenaPlayerNames.get(playerNumber);
}
}
}
return null;
}
/**
* Gets information about an arena's players
*
* @param arenaName <p>The name of the arena</p>
* @param gameMode <p>The game-mode to get information for</p>
* @param infoType <p>The type of information to get</p>
* @param parts <p>The placeholder arguments specified by a user</p>
* @return <p>The specified arena info, or null if the placeholder is invalid</p>
*/
@Nullable
private String getArenaInfo(@NotNull String arenaName, @Nullable ArenaGameMode gameMode,
@NotNull PlayerInfoType infoType, @NotNull String[] parts) {
if (infoType == PlayerInfoType.COUNT) {
Set<UUID> arenaPlayers = getArenaPlayers(arenaName, gameMode);
if (arenaPlayers != null) {
return String.valueOf(arenaPlayers.size());
}
} else if (infoType == PlayerInfoType.PLAYER) {
if (parts.length < 7) {
return null;
}
Integer playerNumber = getPositionNumber(parts[6]);
List<String> players = getArenaPlayersSorted(arenaName, gameMode);
if (playerNumber != null && players != null) {
if (playerNumber >= players.size()) {
return "";
} else {
return players.get(playerNumber);
}
}
}
return null;
}
/**
* Gets the position number from the given string
*
* @param positionNumber <p>The position number to parse</p>
* @return <p>The position number, or null if not valid</p>
*/
@Nullable
private Integer getPositionNumber(@NotNull String positionNumber) {
try {
return Integer.parseInt(positionNumber) - 1;
} catch (NumberFormatException exception) {
MiniGames.log(Level.WARNING, "Invalid placeholder given. " + positionNumber +
" supplied instead of player number.");
return null;
}
}
/**
* Gets names of all players in an arena in sorted order
*
* @param arenaName <p>The name of the arena to get players from</p>
* @param arenaGameMode <p>The game-mode to get players for</p>
* @return <p>Player names in sorted order, or null if the arena name is invalid</p>
*/
@Nullable
private List<String> getArenaPlayersSorted(@NotNull String arenaName, @Nullable ArenaGameMode arenaGameMode) {
K arena = arenaHandler.getArena(arenaName);
if (arena == null) {
return null;
}
return getArenaPlayersSorted(arena, arenaGameMode);
}
/**
* Gets names of all players in an arena in sorted order
*
* @param arena <p>The arena to get players from</p>
* @param arenaGameMode <p>The game-mode to get players for</p>
* @return <p>Player names in sorted order, or null if the arena name is invalid</p>
*/
@NotNull
private List<String> getArenaPlayersSorted(@NotNull K arena, @Nullable ArenaGameMode arenaGameMode) {
Set<UUID> players = getArenaPlayers(arena, arenaGameMode);
List<String> playerNames = new ArrayList<>(players.size());
for (UUID playerId : players) {
Player player = Bukkit.getPlayer(playerId);
if (player != null) {
playerNames.add(player.getName());
}
}
playerNames.sort(Comparator.naturalOrder());
return playerNames;
}
/**
* Gets all players from the given arena
*
* @param arenaName <p>The name of the arena to get players from</p>
* @param arenaGameMode <p>The game-mode to get players for</p>
* @return <p>The players in the given arena playing the given game-mode</p>
*/
@Nullable
private Set<UUID> getArenaPlayers(@NotNull String arenaName, @Nullable ArenaGameMode arenaGameMode) {
K arena = arenaHandler.getArena(arenaName);
if (arena == null) {
return null;
}
return getArenaPlayers(arena, arenaGameMode);
}
/**
* Gets all players from the given arena
*
* @param arena <p>The arena to get players from</p>
* @param arenaGameMode <p>The game-mode to get players for</p>
* @return <p>The players in the given arena playing the given game-mode</p>
*/
@NotNull
private Set<UUID> getArenaPlayers(@NotNull K arena, @Nullable ArenaGameMode arenaGameMode) {
// If getting count for any game-mode, skip filtering
Set<UUID> players = playerRegistry.getPlayingPlayers(arena);
if (arenaGameMode == null) {
return players;
}
Set<UUID> output = new HashSet<>();
for (UUID playerId : players) {
ArenaSession arenaSession = playerRegistry.getArenaSession(playerId);
if (arenaSession == null || arenaSession.getGameMode() != arenaGameMode) {
continue;
}
output.add(playerId);
}
return output;
}
}

View File

@ -1,6 +1,5 @@
package net.knarcraft.minigames.placeholder; package net.knarcraft.minigames.placeholder.parsing;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
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.ArenaGameMode; import net.knarcraft.minigames.arena.ArenaGameMode;
@ -8,12 +7,10 @@ import net.knarcraft.minigames.arena.ArenaGroup;
import net.knarcraft.minigames.arena.ArenaHandler; import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry; import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.record.ArenaRecord; import net.knarcraft.minigames.arena.record.ArenaRecord;
import net.knarcraft.minigames.placeholder.parsing.InfoType; import net.knarcraft.minigames.placeholder.GroupRecordCache;
import net.knarcraft.minigames.placeholder.parsing.SelectionType;
import net.knarcraft.minigames.property.RecordType; import net.knarcraft.minigames.property.RecordType;
import net.knarcraft.minigames.util.GroupRecordHelper; import net.knarcraft.minigames.util.GroupRecordHelper;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -26,51 +23,45 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
* A placeholder expansion for parkour record placeholders * A parser for record-related placeholders
*/ */
public abstract class RecordExpansion extends PlaceholderExpansion { public class RecordPlaceholderParser {
private final ArenaHandler<?, ?> arenaHandler; private final @NotNull ArenaHandler<?, ?> arenaHandler;
private final Map<UUID, Set<GroupRecordCache<Integer>>> groupRecordDeathsCache; private final @NotNull Map<UUID, Set<GroupRecordCache<Integer>>> groupRecordDeathsCache;
private final Map<UUID, Set<GroupRecordCache<Long>>> groupRecordTimeCache; private final @NotNull Map<UUID, Set<GroupRecordCache<Long>>> groupRecordTimeCache;
private final @NotNull Function<String, ArenaGameMode> gameModeParser;
/** /**
* Initializes a new record expansion * Initializes a new record placeholder parser
*/ */
public RecordExpansion(ArenaHandler<?, ?> arenaHandler) { public RecordPlaceholderParser(@NotNull ArenaHandler<?, ?> arenaHandler,
@NotNull Function<String, ArenaGameMode> gameModeParser) {
this.groupRecordDeathsCache = new HashMap<>(); this.groupRecordDeathsCache = new HashMap<>();
this.groupRecordTimeCache = new HashMap<>(); this.groupRecordTimeCache = new HashMap<>();
this.arenaHandler = arenaHandler; this.arenaHandler = arenaHandler;
this.gameModeParser = gameModeParser;
} }
@Override /**
public String getAuthor() { * The method to run when parsing a record placeholder request
return "EpicKnarvik97"; *
} * @param parameters <p>The parameters specified</p>
* @return <p>The resulting string</p>
@Override */
public String getVersion() { @NotNull
return "1.0.0"; public String onRequest(@NotNull String parameters, @NotNull String[] parts) {
} if (parts.length < 7) {
@Override
public boolean persist() {
return true;
}
@Override
public String onRequest(OfflinePlayer player, String parameters) {
String[] parts = parameters.split("_");
// Record is used as the prefix for all record placeholders in case more placeholder types are added
if (parts.length < 7 || !parts[0].equals("record")) {
return parameters; return parameters;
} }
RecordType recordType = RecordType.getFromString(parts[1]); RecordType recordType = RecordType.getFromString(parts[1]);
ArenaGameMode gameMode = parseGameMode(parts[2]); ArenaGameMode gameMode = gameModeParser.apply(parts[2]);
SelectionType selectionType = SelectionType.getFromString(parts[3]); SelectionType selectionType = SelectionType.getFromString(parts[3]);
String identifier = parts[4]; String identifier = parts[4];
int recordNumber; int recordNumber;
@ -78,7 +69,7 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
recordNumber = Integer.parseInt(parts[5]) - 1; recordNumber = Integer.parseInt(parts[5]) - 1;
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
MiniGames.log(Level.WARNING, "Invalid placeholder given. " + parts[5] + MiniGames.log(Level.WARNING, "Invalid placeholder given. " + parts[5] +
" supplied instead of record placement."); " supplied instead of record position.");
return parameters; return parameters;
} }
InfoType infoType = InfoType.getFromString(parts[6]); InfoType infoType = InfoType.getFromString(parts[6]);
@ -97,14 +88,6 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
return Objects.requireNonNullElse(info, parameters); return Objects.requireNonNullElse(info, parameters);
} }
/**
* Parses the game-mode specified in the given string
*
* @param gameMode <p>The game-mode to parse</p>
* @return <p>The parsed game-mode</p>
*/
protected abstract @NotNull ArenaGameMode parseGameMode(@NotNull String gameMode);
/** /**
* Clears all record caches * Clears all record caches
*/ */
@ -124,7 +107,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param infoType <p>The type of info (player, value, combined) to get</p> * @param infoType <p>The type of info (player, value, combined) to get</p>
* @return <p>The selected information about the record, or null if not found</p> * @return <p>The selected information about the record, or null if not found</p>
*/ */
private @Nullable String getGroupRecord(@NotNull ArenaHandler<?, ?> arenaHandler, @NotNull String identifier, @Nullable
private String getGroupRecord(@NotNull ArenaHandler<?, ?> arenaHandler, @NotNull String identifier,
@NotNull ArenaGameMode gameMode, @NotNull RecordType recordType, @NotNull ArenaGameMode gameMode, @NotNull RecordType recordType,
int recordNumber, @NotNull InfoType infoType) { int recordNumber, @NotNull InfoType infoType) {
// Allow specifying the group UUID or the arena name // Allow specifying the group UUID or the arena name
@ -162,7 +146,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param arenaHandler <p>The handler to get arenas from</p> * @param arenaHandler <p>The handler to get arenas from</p>
* @return <p>The record, or null if not found</p> * @return <p>The record, or null if not found</p>
*/ */
private @Nullable ArenaRecord<?> getGroupTimeRecord(@NotNull ArenaGroup<?, ?> group, @Nullable
private ArenaRecord<?> getGroupTimeRecord(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode, int recordNumber, @NotNull ArenaGameMode gameMode, int recordNumber,
@NotNull ArenaHandler<?, ?> arenaHandler) { @NotNull ArenaHandler<?, ?> arenaHandler) {
return getCachedGroupRecord(group, gameMode, RecordType.TIME, recordNumber, groupRecordTimeCache, return getCachedGroupRecord(group, gameMode, RecordType.TIME, recordNumber, groupRecordTimeCache,
@ -178,7 +163,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param arenaHandler <p>The handler to get arenas from</p> * @param arenaHandler <p>The handler to get arenas from</p>
* @return <p>The record, or null if not found</p> * @return <p>The record, or null if not found</p>
*/ */
private @Nullable ArenaRecord<?> getGroupDeathRecord(@NotNull ArenaGroup<?, ?> group, @Nullable
private ArenaRecord<?> getGroupDeathRecord(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode, int recordNumber, @NotNull ArenaGameMode gameMode, int recordNumber,
@NotNull ArenaHandler<?, ?> arenaHandler) { @NotNull ArenaHandler<?, ?> arenaHandler) {
return getCachedGroupRecord(group, gameMode, RecordType.DEATHS, recordNumber, groupRecordDeathsCache, return getCachedGroupRecord(group, gameMode, RecordType.DEATHS, recordNumber, groupRecordDeathsCache,
@ -197,7 +183,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param <K> <p>The type of the provided records</p> * @param <K> <p>The type of the provided records</p>
* @return <p>The specified record, or null if not found</p> * @return <p>The specified record, or null if not found</p>
*/ */
private <K extends Comparable<K>> @Nullable ArenaRecord<?> getCachedGroupRecord(@NotNull ArenaGroup<?, ?> group, @Nullable
private <K extends Comparable<K>> ArenaRecord<?> getCachedGroupRecord(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode, @NotNull ArenaGameMode gameMode,
@NotNull RecordType recordType, @NotNull RecordType recordType,
int recordNumber, int recordNumber,
@ -244,7 +231,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param infoType <p>The type of info (player, value, combined) to get</p> * @param infoType <p>The type of info (player, value, combined) to get</p>
* @return <p>The selected information about the record, or null if not found</p> * @return <p>The selected information about the record, or null if not found</p>
*/ */
private @Nullable String getArenaRecord(@NotNull ArenaHandler<?, ?> arenaHandler, @NotNull String identifier, @Nullable
private String getArenaRecord(@NotNull ArenaHandler<?, ?> arenaHandler, @NotNull String identifier,
@NotNull ArenaGameMode gameMode, @NotNull RecordType recordType, @NotNull ArenaGameMode gameMode, @NotNull RecordType recordType,
int recordNumber, @NotNull InfoType infoType) { int recordNumber, @NotNull InfoType infoType) {
// Allow specifying the arena UUID or the arena name // Allow specifying the arena UUID or the arena name
@ -278,7 +266,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param recordNumber <p>The position of the record to get (1st place, 2nd place, etc.)</p> * @param recordNumber <p>The position of the record to get (1st place, 2nd place, etc.)</p>
* @return <p>The record, or null if not found</p> * @return <p>The record, or null if not found</p>
*/ */
private @Nullable ArenaRecord<?> getRecord(@NotNull ArenaRecordsRegistry recordsRegistry, @Nullable
private ArenaRecord<?> getRecord(@NotNull ArenaRecordsRegistry recordsRegistry,
@NotNull RecordType recordType, int recordNumber) { @NotNull RecordType recordType, int recordNumber) {
return switch (recordType) { return switch (recordType) {
case TIME -> getRecord(new HashSet<>(recordsRegistry.getShortestTimeMilliSecondsRecords()), recordNumber); case TIME -> getRecord(new HashSet<>(recordsRegistry.getShortestTimeMilliSecondsRecords()), recordNumber);
@ -294,7 +283,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param <K> <p>The type of record in the record list</p> * @param <K> <p>The type of record in the record list</p>
* @return <p>The record, or null if index is out of bounds</p> * @return <p>The record, or null if index is out of bounds</p>
*/ */
private <K extends Comparable<K>> @Nullable ArenaRecord<K> getRecord(Set<ArenaRecord<K>> records, int index) { @Nullable
private <K extends Comparable<K>> ArenaRecord<K> getRecord(@NotNull Set<ArenaRecord<K>> records, int index) {
List<ArenaRecord<K>> sorted = getSortedRecords(records); List<ArenaRecord<K>> sorted = getSortedRecords(records);
if (index < sorted.size() && index >= 0) { if (index < sorted.size() && index >= 0) {
return sorted.get(index); return sorted.get(index);
@ -310,6 +300,7 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param arenaRecord <p>The record to get the data from</p> * @param arenaRecord <p>The record to get the data from</p>
* @return <p>The requested data as a string, or null</p> * @return <p>The requested data as a string, or null</p>
*/ */
@Nullable
private String getRecordData(@NotNull InfoType infoType, @NotNull ArenaRecord<?> arenaRecord) { private String getRecordData(@NotNull InfoType infoType, @NotNull ArenaRecord<?> arenaRecord) {
return switch (infoType) { return switch (infoType) {
case PLAYER -> getPlayerName(arenaRecord.getUserId()); case PLAYER -> getPlayerName(arenaRecord.getUserId());
@ -325,8 +316,8 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param <K> <p>The type of the records</p> * @param <K> <p>The type of the records</p>
* @return <p>The sorted records</p> * @return <p>The sorted records</p>
*/ */
private <K extends Comparable<K>> @NotNull List<ArenaRecord<K>> getSortedRecords( @NotNull
@NotNull Set<ArenaRecord<K>> recordSet) { private <K extends Comparable<K>> List<ArenaRecord<K>> getSortedRecords(@NotNull Set<ArenaRecord<K>> recordSet) {
List<ArenaRecord<K>> records = new ArrayList<>(recordSet); List<ArenaRecord<K>> records = new ArrayList<>(recordSet);
Collections.sort(records); Collections.sort(records);
return records; return records;
@ -338,6 +329,7 @@ public abstract class RecordExpansion extends PlaceholderExpansion {
* @param playerId <p>The id of the player to get the name for</p> * @param playerId <p>The id of the player to get the name for</p>
* @return <p>The name of the player, or a string representation of the UUID if not found</p> * @return <p>The name of the player, or a string representation of the UUID if not found</p>
*/ */
@Nullable
private String getPlayerName(@NotNull UUID playerId) { private String getPlayerName(@NotNull UUID playerId) {
return Bukkit.getOfflinePlayer(playerId).getName(); return Bukkit.getOfflinePlayer(playerId).getName();
} }

View File

@ -7,6 +7,8 @@ description: A plugin that adds various mini-games
softdepend: softdepend:
- PlaceholderAPI - PlaceholderAPI
- Vault - Vault
- Geyser-Spigot
- floodgate
# Note to self: Aliases must be lowercase! # Note to self: Aliases must be lowercase!
commands: commands: