Merge pull request #20 from SunNetservers/dev

Config options
This commit is contained in:
Kristian Knarvik 2023-04-17 11:30:24 +00:00 committed by GitHub
commit bcddd214f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 5343 additions and 1224 deletions

15
HEADER Normal file
View File

@ -0,0 +1,15 @@
MiniGames - A mini-games plugin for spigot
Copyright (C) 2023 Kristian Knarvik (EpicKnarvik97)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

165
README.md
View File

@ -1,37 +1,58 @@
# Dropper
# MiniGames
This is a plugin for a dropper mini-game (try to reach the bottom without hitting any obstacles).
To create an arena, simply use `/droppercreate <name>`, where \<name> is simply the name used to differentiate and
This plugin adds several mini-games.
To create a dropper arena, simply use `/droppercreate <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 `/dropperjoin <name>`, where \<name> is the same as you specified upon creation.
To modify
To modify the arena, use `/dropperedit <name> <property> <value>`.
## Permissions
The only permission normal players will need is `minigames.join` which is set to true by default.
| Node | Description |
|----------------|----------------------------------------------------|
| dropper.admin | Gives all permissions. |
| dropper.join | Allows a player to participate in dropper arenas. |
| dropper.create | Allows a player to create a new dropper arena. |
| dropper.edit | Allows a player to edit an existing dropper arena. |
| dropper.remove | Allows a player to remove a dropper arena. |
|--------------------------|------------------------------------------------------|
| minigames.admin | Gives all permissions. |
| minigames.dropper | Gives all dropper-related permissions. |
| minigames.parkour | Gives all parkour-related permissions. |
| minigames.join | Allows a player to participate in mini-game arenas. |
| minigames.join.dropper | Allows a player to participate in dropper arenas. |
| minigames.join.parkour | Allows a player to participate in parkour arenas. |
| minigames.create | Allows a player to create a new mini-game arena. |
| minigames.create.dropper | Allows a player to create a new dropper arena. |
| minigames.create.parkour | Allows a player to create a new parkour arena. |
| minigames.edit | Allows a player to edit an existing mini-game arena. |
| minigames.edit.dropper | Allows a player to edit an existing dropper arena. |
| minigames.edit.parkour | Allows a player to edit an existing parkour arena. |
| minigames.remove | Allows a player to remove a mini-game arena. |
| minigames.remove.dropper | Allows a player to remove a dropper arena. |
| minigames.remove.parkour | Allows a player to remove a parkour arena. |
## Commands
| Command | Alias | Arguments | Description |
|----------------------------------------|----------|-----------------------------|-------------------------------------------------------------------------------------|
| /miniGamesReload | /mreload | | Reloads all data from disk. |
| /miniGamesLeave | /mleave | | Leaves the current mini-game. |
| /dropperList | /dlist | | Lists available dropper arenas. |
| [/dropperJoin](#dropperJoin) | /djoin | \<arena> \[mode] | Joins the selected arena. |
| /dropperLeave | /dleave | | Leaves the current dropper arena. |
| [/dropperJoin](#dropperjoin) | /djoin | \<arena> \[mode] | Joins the selected arena. |
| /dropperCreate | /dcreate | \<name> | Creates a new dropper arena with the given name. The spawn is set to your location. |
| /dropperRemove | /dremove | \<arena> | Removes the specified dropper arena. |
| [/dropperEdit](#dropperEdit) | /dedit | \<arena> \<option> \[value] | Gets or sets a dropper arena option. |
| /dropperReload | /dreload | | Reloads all data from disk. |
| [/dropperGroupSet](#dropperGroupSet) | /dgset | \<arena> \<group> | Puts the given arena in the given group. Use "none" to remove an existing group. |
| [/dropperEdit](#dropperedit) | /dedit | \<arena> \<option> \[value] | Gets or sets a dropper arena option. |
| [/dropperGroupSet](#droppergroupset) | /dgset | \<arena> \<group> | Puts the given arena in the given group. Use "none" to remove an existing group. |
| /dropperGroupList | /dglist | \[group] | Lists groups, or the stages of a group if a group is specified. |
| [/dropperGroupSwap](#dropperGroupSwap) | /dgswap | \<arena1> \<arena2> | Swaps the two arenas in the group's ordered list. |
| [/dropperGroupSwap](#droppergroupswap) | /dgswap | \<arena1> \<arena2> | Swaps the two arenas in the group's ordered list. |
| /parkourList | /plist | | Lists available parkour arenas. |
| /parkourJoin | /pjoin | \<arena> | Joins the selected arena. |
| /parkourCreate | /pcreate | \<name> | Creates a new parkour arena with the given name. The spawn is set to your location. |
| /parkourRemove | /premove | \<arena> | Removes the specified parkour arena. |
| [/parkourEdit](#parkouredit) | /pedit | \<arena> \<option> \[value] | Gets or sets a parkour arena option. |
| /parkourGroupSet | /pgset | \<arena> \<group> | Puts the given arena in the given group. Use "none" to remove an existing group. |
| /parkourGroupList | /pglist | \[group] | Lists groups, or the stages of a group if a group is specified. |
| [/parkourGroupSwap](#droppergroupswap) | /pgswap | \<arena1> \<arena2> | Swaps the two arenas in the group's ordered list. |
### Command explanation
### Command explanation dropper
#### /dropperJoin
@ -99,20 +120,126 @@ You could use `/droppergroupswap Sea Savanna` to change the order to:
3. Nether
4. Sea
### Command explanation parkour
#### /parkourEdit
This command allows editing the specified property for the specified parkour arena.
`/parkouredit <arena> <option> [value]`
| Argument | Usage |
|----------|---------------------------------------|
| arena | The name of the arena to edit. |
| option | The option to display or change. |
| value | The new value of the selected option. |
These are all the options that can be changed for an arena.
| Option | Details |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| name | The name of the arena. Used mainly to select the arena in commands. |
| spawnLocation | The spawn location of any player joining the arena. Use `56.546,64.0,44.45` to specify coordinates, or `here`, `this` or any other string to select your current location. |
| exitLocation | The location players will be sent to when exiting the arena. If not set, the player will be sent to where they joined from. Valid values are the same as for spawnLocation. |
| winBlockType | The type of block players must hit to win the arena. It can be any material as long as it's a block, and not a type of air. |
| winLocation | The location players must reach to win the arena (see spawnLocation for valid values). If set, this overrides, and is used instead of, the win block type. |
| checkpointAdd | Adds a new checkpoint to the arena's checkpoints (see spawnLocation for valid values). |
| checkpointClear | Clears all current checkpoints. Give any value to execute. If not given a value, current checkpoints are shown. |
| killPlaneBlocks | A comma-separated list of materials which will force a loss on hit. +WOOL and other [material tags](#notes-about-material-tags) are supported as well. |
## Configuration options
### Shared
| Name | Type | Default | Description |
|-----------------------------------|---------------------|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| liquidHitBoxDepth | -1 < decimal < 0 | -0.8 | This decides how far inside a non-solid block the player must go before detection triggers (-1, 0). The closer to -1 it is, the more accurate it will seem to the player, but the likelihood of not detecting the hit increases. |
| solidHitBoxDistance | 0 < decimal < 1 | 0.2 | This decides the distance the player must be from a block below them before a hit triggers (0, 1). If too low, the likelihood of detecting the hit decreases, but it won't look like the player hit the block without being near. |
### Dropper
| Name | Type | Default | Description |
|-----------------------------------|---------------------|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| blockSneaking | true/false | true | Whether to block using the shift key to drop faster than the intended drop speed |
| blockSprinting | true/false | true | Whether to block using the sprint key for slightly improved air speed |
| verticalVelocity | 0 < decimal <= 75 | 1.0 | The vertical velocity used as default for all arenas. Must be greater than 0. 3.92 is the max speed of a falling player. |
| horizontalVelocity | 0 < decimal <= 1 | 1.0 | The horizontal velocity used as default for all arenas (technically fly-speed). Must be between 0 (exclusive) and 1 (inclusive). |
| randomlyInvertedTimer | 0 < integer <= 3600 | 7 | The number of seconds before the randomly inverted game-mode switches between normal and inverted movement (0, 3600] |
| mustDoGroupedInSequence | true/false | true | Whether grouped dropper arenas must be played in the correct sequence |
| ignoreRecordsUntilGroupBeatenOnce | true/false | false | Whether records won't be registered unless the player has already beaten all arenas in a group. That means players are required to do a second play-through to register a record for a grouped arena. |
| mustDoNormalModeFirst | true/false | true | Whether a player must do the normal/default game-mode before playing any other game-modes |
| makePlayersInvisible | true/false | false | Whether players should be made invisible while playing in a dropper arena |
| disableHitCollision | true/false | true | Whether players should have their entity hit collision disabled while in an arena. This prevents players from pushing each-other if in the same arena. |
| liquidHitBoxDepth | -1 < decimal < 0 | -0.8 | This decides how far inside a non-solid block the player must go before detection triggers (-1, 0). The closer to -1 it is, the more accurate it will seem to the player, but the likelihood of not detecting the hit increases. |
| solidHitBoxDistance | 0 < decimal < 1 | 0.2 | This decides the distance the player must be from a block below them before a hit triggers (0, 1). If too low, the likelihood of detecting the hit decreases, but it won't look like the player hit the block without being near. |
| blockWhitelist | list | [see this](#blockwhitelist-default) | A whitelist for which blocks won't trigger a loss when hit/passed through. The win block check happens before the loss check, so even blocks on the whitelist can be used as the win-block. "+" denotes a [material tag](#notes-about-material-tags). |
### Parkour
| Name | Type | Default | Description |
|-----------------------------------|------------|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enforceCheckpointOrder | true/false | false | Whether to enforce the order in which a player must reach checkpoints. Enabling this ensures that a player cannot trigger a previous checkpoint by accident. It also ensures players cannot skip a checkpoint, even if the arena layout makes it possible. |
| mustDoGroupedInSequence | true/false | true | Whether grouped dropper arenas must be played in the correct sequence |
| ignoreRecordsUntilGroupBeatenOnce | true/false | false | Whether records won't be registered unless the player has already beaten all arenas in a group. That means players are required to do a second play-through to register a record for a grouped arena. |
| makePlayersInvisible | true/false | false | Whether players should be made invisible while playing in a dropper arena |
| killPlaneBlocks | list | [see this](#killplaneblocks-default) | The types of blocks compromising parkour arenas' kill planes. Add any materials you want to use for the "bottom" of your parkour arenas. +WOOL and other [material tags](#notes-about-material-tags) are supported. |
#### blockWhitelist default:
- WATER
- LAVA
- +WALL_SIGNS
- +STANDING_SIGNS
- STRUCTURE_VOID
- WALL_TORCH
- SOUL_WALL_TORCH
- REDSTONE_WALL_TORCH
- +BANNERS
- +BUTTONS
- +CORALS
- +WALL_CORALS
#### killPlaneBlocks default:
- LAVA
- MAGMA_BLOCK
## Record placeholders
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
format for the built-in placeholders is as follows:
`%dropper_record_recordType_gameModeType_identifierType_identifier_recordPlacing_infoType%`
`%gameMode_record_recordType_gameModeType_identifierType_identifier_recordPlacing_infoType%`
| Variable | Values | Description |
|----------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| dropper_record | | Denotes that it's a placeholder for a dropper record. Must be present as-is. |
| 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. |
| 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. |
| 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). |
| recordPlacing | 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". |
## 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
it much easier to add a lot of blocks without ending up with hundreds of individual materials. To specify
such a tag, use a `+` character, and then the tag name.
See <a href="https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Tag.html">the documentation</a> for a complete overview
of all tags. Only those with type `Tag<Material>` can be used.
Example tags:
- +WOOL
- +WALL_SIGNS
- +ACACIA_LOGS
- +ALL_SIGNS
- +DIAMOND_ORES
- +DIRT
- +DOORS
- +DRAGON_IMMUNE
- +FENCE_GATES
- +FENCES

View File

@ -5,13 +5,13 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.knarcraft</groupId>
<artifactId>Dropper</artifactId>
<artifactId>MiniGames</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Dropper</name>
<name>MiniGames</name>
<description>A plugin for dropper mini-games</description>
<description>A plugin which adds various mini-games</description>
<properties>
<java.version>16</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@ -1,169 +0,0 @@
package net.knarcraft.dropper;
import net.knarcraft.dropper.arena.DropperArenaData;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.dropper.arena.record.IntegerRecord;
import net.knarcraft.dropper.arena.record.LongRecord;
import net.knarcraft.dropper.command.CreateArenaCommand;
import net.knarcraft.dropper.command.EditArenaCommand;
import net.knarcraft.dropper.command.EditArenaTabCompleter;
import net.knarcraft.dropper.command.GroupListCommand;
import net.knarcraft.dropper.command.GroupSetCommand;
import net.knarcraft.dropper.command.GroupSwapCommand;
import net.knarcraft.dropper.command.JoinArenaCommand;
import net.knarcraft.dropper.command.JoinArenaTabCompleter;
import net.knarcraft.dropper.command.LeaveArenaCommand;
import net.knarcraft.dropper.command.ListArenaCommand;
import net.knarcraft.dropper.command.ReloadCommand;
import net.knarcraft.dropper.command.RemoveArenaCommand;
import net.knarcraft.dropper.command.RemoveArenaTabCompleter;
import net.knarcraft.dropper.container.SerializableMaterial;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.listener.CommandListener;
import net.knarcraft.dropper.listener.DamageListener;
import net.knarcraft.dropper.listener.MoveListener;
import net.knarcraft.dropper.listener.PlayerLeaveListener;
import net.knarcraft.dropper.placeholder.DropperRecordExpansion;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.logging.Level;
/**
* The dropper plugin's main class
*/
@SuppressWarnings("unused")
public final class Dropper extends JavaPlugin {
private static Dropper instance;
private DropperArenaHandler arenaHandler;
private DropperArenaPlayerRegistry playerRegistry;
/**
* Gets an instance of this plugin
*
* @return <p>An instance of this plugin, or null if not initialized yet.</p>
*/
public static Dropper getInstance() {
return instance;
}
/**
* Gets the arena handler for this instance
*
* @return <p>A dropper arena handler</p>
*/
public DropperArenaHandler getArenaHandler() {
return this.arenaHandler;
}
/**
* Gets the arena player registry for this instance
*
* @return <p>A dropper arena player registry</p>
*/
public DropperArenaPlayerRegistry getPlayerRegistry() {
return this.playerRegistry;
}
/**
* Reloads all configurations and data from disk
*/
public void reload() {
// Load all arenas again
this.arenaHandler.loadArenas();
this.arenaHandler.loadGroups();
}
@Override
public void onLoad() {
super.onLoad();
// Register serialization classes
ConfigurationSerialization.registerClass(SerializableMaterial.class);
ConfigurationSerialization.registerClass(DropperArenaRecordsRegistry.class);
ConfigurationSerialization.registerClass(SerializableUUID.class);
ConfigurationSerialization.registerClass(DropperArenaData.class);
ConfigurationSerialization.registerClass(DropperArenaGroup.class);
ConfigurationSerialization.registerClass(ArenaGameMode.class);
ConfigurationSerialization.registerClass(LongRecord.class);
ConfigurationSerialization.registerClass(IntegerRecord.class);
}
@Override
public void onEnable() {
// Plugin startup logic
instance = this;
this.playerRegistry = new DropperArenaPlayerRegistry();
this.arenaHandler = new DropperArenaHandler();
this.arenaHandler.loadArenas();
this.arenaHandler.loadGroups();
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new DamageListener(), this);
pluginManager.registerEvents(new MoveListener(), this);
pluginManager.registerEvents(new PlayerLeaveListener(), this);
pluginManager.registerEvents(new CommandListener(), this);
registerCommand("dropperReload", new ReloadCommand(), null);
registerCommand("dropperCreate", new CreateArenaCommand(), null);
registerCommand("dropperList", new ListArenaCommand(), null);
registerCommand("dropperJoin", new JoinArenaCommand(), new JoinArenaTabCompleter());
registerCommand("dropperLeave", new LeaveArenaCommand(), null);
registerCommand("dropperEdit", new EditArenaCommand(), new EditArenaTabCompleter());
registerCommand("dropperRemove", new RemoveArenaCommand(), new RemoveArenaTabCompleter());
registerCommand("dropperGroupSet", new GroupSetCommand(), null);
registerCommand("dropperGroupSwap", new GroupSwapCommand(), null);
registerCommand("dropperGroupList", new GroupListCommand(), null);
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
if (!new DropperRecordExpansion(this).register()) {
getLogger().log(Level.WARNING, "Unable to register PlaceholderAPI expansion!");
}
}
}
@Override
public void onDisable() {
// Throw out currently playing players before exiting
for (Player player : getServer().getOnlinePlayers()) {
DropperArenaSession session = playerRegistry.getArenaSession(player.getUniqueId());
if (session != null) {
session.triggerQuit(true);
}
}
}
/**
* Registers a command
*
* @param commandName <p>The name of the command to register (defined in plugin.yml)</p>
* @param commandExecutor <p>The executor for the command</p>
* @param tabCompleter <p>The tab-completer to use, or null</p>
*/
private void registerCommand(@NotNull String commandName, @NotNull CommandExecutor commandExecutor,
@Nullable TabCompleter tabCompleter) {
PluginCommand command = this.getCommand(commandName);
if (command != null) {
command.setExecutor(commandExecutor);
if (tabCompleter != null) {
command.setTabCompleter(tabCompleter);
}
} else {
getLogger().log(Level.SEVERE, "Unable to register the command " + commandName);
}
}
}

View File

@ -1,128 +0,0 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Data stored for an arena
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordRegistries <p>The records belonging to the arena</p>
* @param playersCompleted <p>A list of all player that have completed this arena</p>
*/
public record DropperArenaData(@NotNull UUID arenaId,
@NotNull Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) implements ConfigurationSerializable {
/**
* Instantiates a new dropper arena data object
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordRegistries <p>The registries of this arena's records</p>
* @param playersCompleted <p>The set of the players that have cleared this arena for each game-mode</p>
*/
public DropperArenaData(@NotNull UUID arenaId,
@NotNull Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
this.arenaId = arenaId;
this.recordRegistries = recordRegistries;
this.playersCompleted = new HashMap<>(playersCompleted);
}
/**
* Gets whether the given player has cleared this arena
*
* @param arenaGameMode <p>The game-mode to check for</p>
* @param player <p>The player to check</p>
* @return <p>True if the player has cleared the arena this data belongs to</p>
*/
public boolean hasNotCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
return !this.playersCompleted.getOrDefault(arenaGameMode, new HashSet<>()).contains(player.getUniqueId());
}
/**
* Registers the given player as having completed this arena
*
* @param player <p>The player that completed this data's arena</p>
*/
public boolean addCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
// Make sure to add an empty set to prevent a NullPointerException
if (!this.playersCompleted.containsKey(arenaGameMode)) {
this.playersCompleted.put(arenaGameMode, new HashSet<>());
}
boolean added = this.playersCompleted.get(arenaGameMode).add(player.getUniqueId());
// Persistently save the completion
if (added) {
Dropper.getInstance().getArenaHandler().saveData(this.arenaId);
}
return added;
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("arenaId", new SerializableUUID(this.arenaId));
data.put("recordsRegistry", this.recordRegistries);
// Convert normal UUIDs to serializable UUIDs
Map<ArenaGameMode, Set<SerializableUUID>> serializablePlayersCompleted = new HashMap<>();
for (ArenaGameMode arenaGameMode : this.playersCompleted.keySet()) {
Set<SerializableUUID> playersCompleted = new HashSet<>();
for (UUID playerCompleted : this.playersCompleted.get(arenaGameMode)) {
playersCompleted.add(new SerializableUUID(playerCompleted));
}
serializablePlayersCompleted.put(arenaGameMode, playersCompleted);
}
data.put("playersCompleted", serializablePlayersCompleted);
return data;
}
/**
* Deserializes a dropper arena data from the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized dropper arena data</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull DropperArenaData deserialize(@NotNull Map<String, Object> data) {
SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId");
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordsRegistry =
(Map<ArenaGameMode, DropperArenaRecordsRegistry>) data.get("recordsRegistry");
Map<ArenaGameMode, Set<SerializableUUID>> playersCompletedData =
(Map<ArenaGameMode, Set<SerializableUUID>>) data.get("playersCompleted");
if (recordsRegistry == null) {
recordsRegistry = new HashMap<>();
} else if (playersCompletedData == null) {
playersCompletedData = new HashMap<>();
}
// Convert the serializable UUIDs to normal UUIDs
Map<ArenaGameMode, Set<UUID>> allPlayersCompleted = new HashMap<>();
for (ArenaGameMode arenaGameMode : playersCompletedData.keySet()) {
Set<UUID> playersCompleted = new HashSet<>();
for (SerializableUUID completedId : playersCompletedData.get(arenaGameMode)) {
playersCompleted.add(completedId.uuid());
}
allPlayersCompleted.put(arenaGameMode, playersCompleted);
if (!recordsRegistry.containsKey(arenaGameMode) || recordsRegistry.get(arenaGameMode) == null) {
recordsRegistry.put(arenaGameMode, new DropperArenaRecordsRegistry(serializableUUID.uuid()));
}
}
return new DropperArenaData(serializableUUID.uuid(), recordsRegistry, allPlayersCompleted);
}
}

View File

@ -1,89 +0,0 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
/**
* The state of a player before entering a dropper arena
*/
public class PlayerEntryState {
private final Player player;
private final Location entryLocation;
private final boolean originalIsFlying;
private final float originalFlySpeed;
private final GameMode originalGameMode;
private final boolean originalAllowFlight;
private final boolean originalInvulnerable;
private final boolean originalIsSwimming;
private final boolean originalCollideAble;
private final ArenaGameMode arenaGameMode;
/**
* Instantiates a new player state
*
* @param player <p>The player whose state should be stored</p>
*/
public PlayerEntryState(@NotNull Player player, @NotNull ArenaGameMode arenaGameMode) {
this.player = player;
this.entryLocation = player.getLocation().clone();
this.originalFlySpeed = player.getFlySpeed();
this.originalIsFlying = player.isFlying();
this.originalGameMode = player.getGameMode();
this.originalAllowFlight = player.getAllowFlight();
this.originalInvulnerable = player.isInvulnerable();
this.originalIsSwimming = player.isSwimming();
this.arenaGameMode = arenaGameMode;
this.originalCollideAble = player.isCollidable();
}
/**
* Sets the state of the stored player to the state used by arenas
*
* @param horizontalVelocity <p>The horizontal velocity to apply to the player</p>
*/
public void setArenaState(float horizontalVelocity) {
this.player.setAllowFlight(true);
this.player.setFlying(true);
this.player.setGameMode(GameMode.ADVENTURE);
this.player.setSwimming(false);
this.player.setCollidable(false);
this.player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, PotionEffect.INFINITE_DURATION, 3));
// If playing on the inverted game-mode, negate the horizontal velocity to swap the controls
if (arenaGameMode == ArenaGameMode.INVERTED) {
this.player.setFlySpeed(-horizontalVelocity);
} else {
this.player.setFlySpeed(horizontalVelocity);
}
}
/**
* Restores the stored state for the stored player
*/
public void restore() {
this.player.setFlying(this.originalIsFlying);
this.player.setGameMode(this.originalGameMode);
this.player.setAllowFlight(this.originalAllowFlight);
this.player.setFlySpeed(this.originalFlySpeed);
this.player.setInvulnerable(this.originalInvulnerable);
this.player.setSwimming(this.originalIsSwimming);
this.player.setCollidable(this.originalCollideAble);
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
}
/**
* Gets the location the player entered from
*
* @return <p>The location the player entered from</p>
*/
public Location getEntryLocation() {
return this.entryLocation;
}
}

View File

@ -1,34 +0,0 @@
package net.knarcraft.dropper.command;
import net.knarcraft.dropper.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* The tab-completer for the join command
*/
public class JoinArenaTabCompleter implements TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] arguments) {
if (arguments.length == 1) {
return TabCompleteHelper.getArenas();
} else if (arguments.length == 2) {
List<String> gameModes = new ArrayList<>();
gameModes.add("default");
gameModes.add("inverted");
gameModes.add("random");
return gameModes;
} else {
return new ArrayList<>();
}
}
}

View File

@ -1,37 +0,0 @@
package net.knarcraft.dropper.container;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* A material container able to be serialized
*
* @param material <p>The material stored by this record</p>
*/
public record SerializableMaterial(Material material) implements ConfigurationSerializable {
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("name", material.name());
return data;
}
/**
* Deserializes a serialized material
*
* @param data <p>The serialized data</p>
* @return <p>The deserialized material</p>
*/
@SuppressWarnings("unused")
public static SerializableMaterial deserialize(Map<String, Object> data) {
Material material = Material.matchMaterial((String) data.getOrDefault("name", "AIR"));
return new SerializableMaterial(material);
}
}

View File

@ -1,39 +0,0 @@
package net.knarcraft.dropper.listener;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaSession;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
/**
* A listener for checking if a player takes damage within a dropper arena
*/
public class DamageListener implements Listener {
@EventHandler
public void onPlayerDamage(EntityDamageEvent event) {
// Only player damage matters
if (event.getEntityType() != EntityType.PLAYER) {
return;
}
Player player = (Player) event.getEntity();
// We don't care about damage outside arenas
DropperArenaSession arenaSession = Dropper.getInstance().getPlayerRegistry().getArenaSession(player.getUniqueId());
if (arenaSession == null) {
return;
}
event.setCancelled(true);
// Only trigger a loss when a player suffers fall damage
if (event.getCause() == EntityDamageEvent.DamageCause.FALL) {
arenaSession.triggerLoss();
}
}
}

View File

@ -1,46 +0,0 @@
package net.knarcraft.dropper.util;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.property.ArenaEditableProperty;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* A helper-class for common tab-completions
*/
public final class TabCompleteHelper {
private TabCompleteHelper() {
}
/**
* Gets the names of all current arenas
*
* @return <p>All arena names</p>
*/
public static @NotNull List<String> getArenas() {
List<String> arenaNames = new ArrayList<>();
for (DropperArena dropperArena : Dropper.getInstance().getArenaHandler().getArenas().values()) {
arenaNames.add(dropperArena.getArenaName());
}
return arenaNames;
}
/**
* Gets the argument strings of all arena properties
*
* @return <p>All arena properties</p>
*/
public static @NotNull List<String> getArenaProperties() {
List<String> arenaProperties = new ArrayList<>();
for (ArenaEditableProperty property : ArenaEditableProperty.values()) {
arenaProperties.add(property.getArgumentString());
}
return arenaProperties;
}
}

View File

@ -0,0 +1,309 @@
package net.knarcraft.minigames;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.dropper.DropperArenaData;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.arena.dropper.DropperArenaPlayerRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaSession;
import net.knarcraft.minigames.arena.parkour.ParkourArenaData;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.arena.parkour.ParkourArenaPlayerRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArenaRecordsRegistry;
import net.knarcraft.minigames.arena.record.IntegerRecord;
import net.knarcraft.minigames.arena.record.LongRecord;
import net.knarcraft.minigames.command.LeaveArenaCommand;
import net.knarcraft.minigames.command.ReloadCommand;
import net.knarcraft.minigames.command.dropper.CreateDropperArenaCommand;
import net.knarcraft.minigames.command.dropper.DropperGroupListCommand;
import net.knarcraft.minigames.command.dropper.DropperGroupSetCommand;
import net.knarcraft.minigames.command.dropper.DropperGroupSwapCommand;
import net.knarcraft.minigames.command.dropper.EditDropperArenaCommand;
import net.knarcraft.minigames.command.dropper.EditDropperArenaTabCompleter;
import net.knarcraft.minigames.command.dropper.JoinDropperArenaCommand;
import net.knarcraft.minigames.command.dropper.JoinDropperArenaTabCompleter;
import net.knarcraft.minigames.command.dropper.ListDropperArenaCommand;
import net.knarcraft.minigames.command.dropper.RemoveDropperArenaCommand;
import net.knarcraft.minigames.command.dropper.RemoveDropperArenaTabCompleter;
import net.knarcraft.minigames.command.parkour.CreateParkourArenaCommand;
import net.knarcraft.minigames.command.parkour.EditParkourArenaCommand;
import net.knarcraft.minigames.command.parkour.EditParkourArenaTabCompleter;
import net.knarcraft.minigames.command.parkour.JoinParkourArenaCommand;
import net.knarcraft.minigames.command.parkour.JoinParkourArenaTabCompleter;
import net.knarcraft.minigames.command.parkour.ListParkourArenaCommand;
import net.knarcraft.minigames.command.parkour.ParkourGroupListCommand;
import net.knarcraft.minigames.command.parkour.ParkourGroupSetCommand;
import net.knarcraft.minigames.command.parkour.ParkourGroupSwapCommand;
import net.knarcraft.minigames.command.parkour.RemoveParkourArenaCommand;
import net.knarcraft.minigames.command.parkour.RemoveParkourArenaTabCompleter;
import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.config.SharedConfiguration;
import net.knarcraft.minigames.container.SerializableMaterial;
import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.listener.CommandListener;
import net.knarcraft.minigames.listener.DamageListener;
import net.knarcraft.minigames.listener.MoveListener;
import net.knarcraft.minigames.listener.PlayerLeaveListener;
import net.knarcraft.minigames.placeholder.DropperRecordExpansion;
import net.knarcraft.minigames.placeholder.ParkourRecordExpansion;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.logging.Level;
/**
* The dropper plugin's main class
*/
@SuppressWarnings("unused")
public final class MiniGames extends JavaPlugin {
private static MiniGames instance;
private SharedConfiguration sharedConfiguration;
private DropperConfiguration dropperConfiguration;
private ParkourConfiguration parkourConfiguration;
private DropperArenaHandler dropperArenaHandler;
private DropperArenaPlayerRegistry dropperArenaPlayerRegistry;
private DropperRecordExpansion dropperRecordExpansion;
private ParkourRecordExpansion parkourRecordExpansion;
private ParkourArenaHandler parkourArenaHandler;
private ParkourArenaPlayerRegistry parkourArenaPlayerRegistry;
/**
* Gets an instance of this plugin
*
* @return <p>An instance of this plugin, or null if not initialized yet.</p>
*/
public static MiniGames getInstance() {
return instance;
}
/**
* Gets the dropper arena handler for this instance
*
* @return <p>A dropper arena handler</p>
*/
public DropperArenaHandler getDropperArenaHandler() {
return this.dropperArenaHandler;
}
/**
* Gets the parkour arena handler for this instance
*
* @return <p>A parkour arena handler</p>
*/
public ParkourArenaHandler getParkourArenaHandler() {
return this.parkourArenaHandler;
}
/**
* Gets the dropper arena player registry for this instance
*
* @return <p>A dropper arena player registry</p>
*/
public DropperArenaPlayerRegistry getDropperArenaPlayerRegistry() {
return this.dropperArenaPlayerRegistry;
}
/**
* Gets the parkour arena player registry for this instance
*
* @return <p>A parkour arena player registry</p>
*/
public ParkourArenaPlayerRegistry getParkourArenaPlayerRegistry() {
return this.parkourArenaPlayerRegistry;
}
/**
* Gets the shared configuration
*
* <p>The configuration for options which don't affect specific types of mini-games.</p>
*
* @return <p>The shared configuration</p>
*/
public SharedConfiguration getSharedConfiguration() {
return this.sharedConfiguration;
}
/**
* Gets the dropper configuration
*
* @return <p>The dropper configuration</p>
*/
public DropperConfiguration getDropperConfiguration() {
return this.dropperConfiguration;
}
/**
* Gets the parkour configuration
*
* @return <p>The parkour configuration</p>
*/
public ParkourConfiguration getParkourConfiguration() {
return this.parkourConfiguration;
}
/**
* Gets the current session of the given player
*
* @param playerId <p>The id of the player to get a session for</p>
* @return <p>The player's current session, or null if not found</p>
*/
public @Nullable ArenaSession getSession(@NotNull UUID playerId) {
DropperArenaSession dropperArenaSession = dropperArenaPlayerRegistry.getArenaSession(playerId);
if (dropperArenaSession != null) {
return dropperArenaSession;
}
return parkourArenaPlayerRegistry.getArenaSession(playerId);
}
/**
* Logs a message
*
* @param level <p>The message level to log at</p>
* @param message <p>The message to log</p>
*/
public static void log(Level level, String message) {
MiniGames.getInstance().getLogger().log(level, message);
}
/**
* Reloads all configurations and data from disk
*/
public void reload() {
// Load all arenas again
this.dropperArenaHandler.load();
this.parkourArenaHandler.load();
// Reload configuration
this.reloadConfig();
this.sharedConfiguration.load(this.getConfig());
this.dropperConfiguration.load(this.getConfig());
this.parkourConfiguration.load(this.getConfig());
// Clear record caches
this.dropperRecordExpansion.clearCaches();
this.parkourRecordExpansion.clearCaches();
}
@Override
public void onLoad() {
super.onLoad();
// Register serialization classes
ConfigurationSerialization.registerClass(SerializableMaterial.class);
ConfigurationSerialization.registerClass(DropperArenaRecordsRegistry.class);
ConfigurationSerialization.registerClass(SerializableUUID.class);
ConfigurationSerialization.registerClass(DropperArenaData.class);
ConfigurationSerialization.registerClass(DropperArenaGroup.class);
ConfigurationSerialization.registerClass(DropperArenaGameMode.class);
ConfigurationSerialization.registerClass(LongRecord.class);
ConfigurationSerialization.registerClass(IntegerRecord.class);
ConfigurationSerialization.registerClass(ParkourArenaRecordsRegistry.class);
ConfigurationSerialization.registerClass(ParkourArenaData.class);
ConfigurationSerialization.registerClass(ParkourArenaGroup.class);
ConfigurationSerialization.registerClass(ParkourArenaGameMode.class);
}
@Override
public void onEnable() {
// Plugin startup logic
instance = this;
this.saveDefaultConfig();
getConfig().options().copyDefaults(true);
saveConfig();
reloadConfig();
this.sharedConfiguration = new SharedConfiguration(this.getConfig());
this.dropperConfiguration = new DropperConfiguration(this.getConfig());
this.parkourConfiguration = new ParkourConfiguration(this.getConfig());
this.dropperArenaPlayerRegistry = new DropperArenaPlayerRegistry();
this.dropperArenaHandler = new DropperArenaHandler(this.dropperArenaPlayerRegistry);
this.dropperArenaHandler.load();
this.parkourArenaPlayerRegistry = new ParkourArenaPlayerRegistry();
this.parkourArenaHandler = new ParkourArenaHandler(this.parkourArenaPlayerRegistry);
this.parkourArenaHandler.load();
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new DamageListener(), this);
pluginManager.registerEvents(new MoveListener(this.dropperConfiguration, this.parkourConfiguration), this);
pluginManager.registerEvents(new PlayerLeaveListener(), this);
pluginManager.registerEvents(new CommandListener(), this);
registerCommand("miniGamesReload", new ReloadCommand(), null);
registerCommand("miniGamesLeave", new LeaveArenaCommand(), null);
registerCommand("dropperCreate", new CreateDropperArenaCommand(), null);
registerCommand("dropperList", new ListDropperArenaCommand(), null);
registerCommand("dropperJoin", new JoinDropperArenaCommand(), new JoinDropperArenaTabCompleter());
registerCommand("dropperEdit", new EditDropperArenaCommand(this.dropperConfiguration), new EditDropperArenaTabCompleter());
registerCommand("dropperRemove", new RemoveDropperArenaCommand(), new RemoveDropperArenaTabCompleter());
registerCommand("dropperGroupSet", new DropperGroupSetCommand(), null);
registerCommand("dropperGroupSwap", new DropperGroupSwapCommand(), null);
registerCommand("dropperGroupList", new DropperGroupListCommand(), null);
registerCommand("parkourCreate", new CreateParkourArenaCommand(), null);
registerCommand("parkourList", new ListParkourArenaCommand(), null);
registerCommand("parkourJoin", new JoinParkourArenaCommand(), new JoinParkourArenaTabCompleter());
registerCommand("parkourEdit", new EditParkourArenaCommand(), new EditParkourArenaTabCompleter());
registerCommand("parkourRemove", new RemoveParkourArenaCommand(), new RemoveParkourArenaTabCompleter());
registerCommand("parkourGroupSet", new ParkourGroupSetCommand(), null);
registerCommand("parkourGroupSwap", new ParkourGroupSwapCommand(), null);
registerCommand("parkourGroupList", new ParkourGroupListCommand(), null);
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
this.dropperRecordExpansion = new DropperRecordExpansion(this);
if (!this.dropperRecordExpansion.register()) {
log(Level.WARNING, "Unable to register PlaceholderAPI dropper expansion!");
}
this.parkourRecordExpansion = new ParkourRecordExpansion(this);
if (!this.parkourRecordExpansion.register()) {
log(Level.WARNING, "Unable to register PlaceholderAPI parkour expansion!");
}
}
}
@Override
public void onDisable() {
// Throw out currently playing players before exiting
for (Player player : getServer().getOnlinePlayers()) {
ArenaSession session = getSession(player.getUniqueId());
if (session != null) {
session.triggerQuit(true);
}
}
}
/**
* Registers a command
*
* @param commandName <p>The name of the command to register (defined in plugin.yml)</p>
* @param commandExecutor <p>The executor for the command</p>
* @param tabCompleter <p>The tab-completer to use, or null</p>
*/
private void registerCommand(@NotNull String commandName, @NotNull CommandExecutor commandExecutor,
@Nullable TabCompleter tabCompleter) {
PluginCommand command = this.getCommand(commandName);
if (command != null) {
command.setExecutor(commandExecutor);
if (tabCompleter != null) {
command.setTabCompleter(tabCompleter);
}
} else {
log(Level.SEVERE, "Unable to register the command " + commandName);
}
}
}

View File

@ -0,0 +1,69 @@
package net.knarcraft.minigames.arena;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
/**
* An abstract representation of a player's entry state
*/
public abstract class AbstractPlayerEntryState implements PlayerEntryState {
protected final Player player;
private final boolean makePlayerInvisible;
private final Location entryLocation;
private final boolean originalIsFlying;
private final GameMode originalGameMode;
private final boolean originalAllowFlight;
private final boolean originalInvulnerable;
private final boolean originalIsSwimming;
private final boolean originalCollideAble;
/**
* Instantiates a new abstract player entry state
*
* @param player <p>The player whose state this should keep track of</p>
* @param makePlayerInvisible <p>Whether players should be made invisible while in the arena</p>
*/
public AbstractPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) {
this.player = player;
this.makePlayerInvisible = makePlayerInvisible;
this.entryLocation = player.getLocation().clone();
this.originalIsFlying = player.isFlying();
this.originalGameMode = player.getGameMode();
this.originalAllowFlight = player.getAllowFlight();
this.originalInvulnerable = player.isInvulnerable();
this.originalIsSwimming = player.isSwimming();
this.originalCollideAble = player.isCollidable();
}
@Override
public void setArenaState() {
if (this.makePlayerInvisible) {
this.player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,
PotionEffect.INFINITE_DURATION, 3));
}
}
@Override
public void restore() {
this.player.setFlying(this.originalIsFlying);
this.player.setGameMode(this.originalGameMode);
this.player.setAllowFlight(this.originalAllowFlight);
this.player.setInvulnerable(this.originalInvulnerable);
this.player.setSwimming(this.originalIsSwimming);
this.player.setCollidable(this.originalCollideAble);
if (this.makePlayerInvisible) {
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
}
}
@Override
public Location getEntryLocation() {
return this.entryLocation;
}
}

View File

@ -0,0 +1,86 @@
package net.knarcraft.minigames.arena;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* An interface describing an arena
*/
public interface Arena {
/**
* Gets the name of this arena
*
* @return <p>The name of this arena</p>
*/
@NotNull String getArenaName();
/**
* Gets the data stored for this arena
*
* @return <p>The stored data</p>
*/
@NotNull ArenaData getData();
/**
* Gets the id of this arena
*
* @return <p>This arena's identifier</p>
*/
@NotNull UUID getArenaId();
/**
* Gets this arena's sanitized name
*
* @return <p>This arena's sanitized name</p>
*/
@NotNull String getArenaNameSanitized();
/**
* Removes the data file belonging to this arena
*
* @return <p>True if successfully removed</p>
*/
boolean removeData();
/**
* Saves this arena's data
*
* @return <p>True if successfully saved</p>
*/
boolean saveData();
/**
* Gets whether standing on the given block should cause a win
*
* @param block <p>The block to check</p>
* @return <p>True if standing on the block will cause a win</p>
*/
boolean willCauseWin(Block block);
/**
* Gets whether standing on the given block should cause a loss
*
* @param block <p>The block to check</p>
* @return <p>True if standing on the block will cause a loss</p>
*/
boolean willCauseLoss(Block block);
/**
* Gets whether the win location is a solid block
*
* @return <p>True if the location is a solid block</p>
*/
boolean winLocationIsSolid();
/**
* Gets the location of this arena's spawn
*
* @return <p>This arena's spawn location</p>
*/
@NotNull Location getSpawnLocation();
}

View File

@ -0,0 +1,108 @@
package net.knarcraft.minigames.arena;
import net.knarcraft.minigames.container.SerializableContainer;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static net.knarcraft.minigames.util.SerializableConverter.makeSerializable;
/**
* An interface describing generic arena data
*/
public abstract class ArenaData implements ConfigurationSerializable {
protected final @NotNull UUID arenaId;
private final @NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries;
private final @NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted;
/**
* Instantiates arena data
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordRegistries <p>The registry storing records for this arena</p>
* @param playersCompleted <p>The players that have completed this arena</p>
*/
public ArenaData(@NotNull UUID arenaId, @NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
this.arenaId = arenaId;
this.recordRegistries = recordRegistries;
this.playersCompleted = playersCompleted;
}
/**
* Gets the id of this arena
*
* @return <p>The id of this arena</p>
*/
public @NotNull UUID getArenaId() {
return this.arenaId;
}
/**
* Gets all record registries
*
* @return <p>All record registries</p>
*/
public @NotNull Map<ArenaGameMode, ArenaRecordsRegistry> getRecordRegistries() {
return new HashMap<>(this.recordRegistries);
}
/**
* Gets whether the given player has cleared this arena
*
* @param arenaGameMode <p>The game-mode to check for</p>
* @param player <p>The player to check</p>
* @return <p>True if the player has cleared the arena this data belongs to</p>
*/
public boolean hasNotCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
return !this.playersCompleted.getOrDefault(arenaGameMode, new HashSet<>()).contains(player.getUniqueId());
}
/**
* Registers the given player as having completed this arena
*
* @param arenaGameMode <p>The game-mode the player completed</p>
* @param player <p>The player that completed this data's arena</p>
*/
public boolean setCompleted(@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
// Make sure to add an empty set to prevent a NullPointerException
if (!this.playersCompleted.containsKey(arenaGameMode)) {
this.playersCompleted.put(arenaGameMode, new HashSet<>());
}
boolean added = this.playersCompleted.get(arenaGameMode).add(player.getUniqueId());
// Persistently save the completion
if (added) {
saveData();
}
return added;
}
/**
* Saves this data to disk
*/
public abstract void saveData();
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("arenaId", new SerializableUUID(this.arenaId));
data.put("recordsRegistry", this.recordRegistries);
// Convert normal UUIDs to serializable UUIDs
Map<ArenaGameMode, Set<SerializableContainer<UUID>>> serializablePlayersCompleted = new HashMap<>();
makeSerializable(this.playersCompleted, serializablePlayersCompleted, new SerializableUUID(null));
data.put("playersCompleted", serializablePlayersCompleted);
return data;
}
}

View File

@ -0,0 +1,24 @@
package net.knarcraft.minigames.arena;
import org.jetbrains.annotations.NotNull;
/**
* An interface describing any arena game-mode
*/
public interface ArenaGameMode {
/**
* Gets the name of this game-mode
*
* @return <p>The name of this game-mode</p>
*/
@NotNull String name();
/**
* Gets a set of all available arena game-modes in the type definition of this game-mode
*
* @return <p>All game-modes in this game-mode's class</p>
*/
@NotNull ArenaGameMode[] getValues();
}

View File

@ -1,9 +1,8 @@
package net.knarcraft.dropper.arena;
package net.knarcraft.minigames.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.StringSanitizer;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@ -16,9 +15,12 @@ import java.util.UUID;
import java.util.logging.Level;
/**
* A sorted group of arenas that must be completed in sequence
* A group containing a list of arenas
*
* @param <K> <p>The type of arena stored</p>
* @param <S> <p>The type of arena group stored in the given arena handler</p>
*/
public class DropperArenaGroup implements ConfigurationSerializable {
public abstract class ArenaGroup<K extends Arena, S extends ArenaGroup<K, S>> implements ConfigurationSerializable {
/**
* The unique id for this group of arenas
@ -30,6 +32,11 @@ public class DropperArenaGroup implements ConfigurationSerializable {
*/
private final String groupName;
/**
* The arena handler used to convert uuids to arenas
*/
private final ArenaHandler<K, S> arenaHandler;
/**
* The arenas in this group, ordered from stage 1 to stage n
*/
@ -39,28 +46,33 @@ public class DropperArenaGroup implements ConfigurationSerializable {
* Instantiates a new dropper arena group
*
* @param groupName <p>The name of this group</p>
* @param arenaHandler <p>The arena handler used to convert uuids to arenas</p>
*/
public DropperArenaGroup(@NotNull String groupName) {
protected ArenaGroup(@NotNull String groupName, @NotNull ArenaHandler<K, S> arenaHandler) {
this.groupId = UUID.randomUUID();
this.groupName = groupName;
this.arenas = new ArrayList<>();
this.arenaHandler = arenaHandler;
}
/**
* Instantiates a new dropper arena group
* Instantiates a new arena group
*
* @param groupId <p>The unique id of this group</p>
* @param groupName <p>The name of this group</p>
* @param arenas <p>The arenas in this group</p>
* @param arenaHandler <p>The arena handler used to convert uuids to arenas</p>
*/
private DropperArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas) {
protected ArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas,
@NotNull ArenaHandler<K, S> arenaHandler) {
this.groupId = groupId;
this.groupName = groupName;
this.arenas = new ArrayList<>(arenas);
this.arenaHandler = arenaHandler;
}
/**
* Gets the id of this dropper arena group
* Gets the id of this arena group
*
* @return <p>The id of this group</p>
*/
@ -69,7 +81,7 @@ public class DropperArenaGroup implements ConfigurationSerializable {
}
/**
* Gets the name of this dropper arena group
* Gets the name of this arena group
*
* @return <p>The name of this group</p>
*/
@ -87,9 +99,9 @@ public class DropperArenaGroup implements ConfigurationSerializable {
}
/**
* Removes the given dropper arena from this group
* Removes the given arena from this group
*
* @param arenaId <p>The id of the dropper arena to remove</p>
* @param arenaId <p>The id of the arena to remove</p>
*/
public void removeArena(UUID arenaId) {
this.arenas.remove(arenaId);
@ -117,6 +129,15 @@ public class DropperArenaGroup implements ConfigurationSerializable {
}
}
/**
* Gets this group's name, but sanitized
*
* @return <p>The sanitized group name</p>
*/
public @NotNull String getGroupNameSanitized() {
return StringSanitizer.sanitizeArenaName(this.getGroupName());
}
/**
* Checks whether the given player has beaten all arenas in this group on the given game-mode
*
@ -125,17 +146,16 @@ public class DropperArenaGroup implements ConfigurationSerializable {
* @return <p>True if the player has beaten all arenas, false otherwise</p>
*/
public boolean hasBeatenAll(ArenaGameMode gameMode, Player player) {
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
for (UUID anArenaId : this.getArenas()) {
DropperArena dropperArena = arenaHandler.getArena(anArenaId);
if (dropperArena == null) {
K arena = this.arenaHandler.getArena(anArenaId);
if (arena == null) {
// The arena would only be null if the arena has been deleted, but not removed from this group
Dropper.getInstance().getLogger().log(Level.WARNING, "The dropper group " + this.getGroupName() +
MiniGames.log(Level.WARNING, "The dropper group " + this.getGroupName() +
" contains the arena id " + anArenaId + " which is not a valid arena id!");
continue;
}
if (dropperArena.getData().hasNotCompleted(gameMode, player)) {
if (arena.getData().hasNotCompleted(gameMode, player)) {
return false;
}
}
@ -151,43 +171,32 @@ public class DropperArenaGroup implements ConfigurationSerializable {
* @return <p>True if the player is allowed to play the arena</p>
* @throws IllegalArgumentException <p>If checking an arena not in this group</p>
*/
public boolean canPlay(ArenaGameMode gameMode, Player player, UUID arenaId) throws IllegalArgumentException {
public boolean cannotPlay(ArenaGameMode gameMode, Player player, UUID arenaId) throws IllegalArgumentException {
if (!this.arenas.contains(arenaId)) {
throw new IllegalArgumentException("Cannot check for playability for arena not in this group!");
}
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
for (UUID anArenaId : this.getArenas()) {
// If the target arena is reached, allow, as all previous arenas must have been cleared
if (arenaId.equals(anArenaId)) {
return true;
return false;
}
DropperArena dropperArena = arenaHandler.getArena(anArenaId);
if (dropperArena == null) {
K arena = this.arenaHandler.getArena(anArenaId);
if (arena == null) {
// The arena would only be null if the arena has been deleted, but not removed from this group
Dropper.getInstance().getLogger().log(Level.WARNING, String.format("The dropper group %s contains the" +
MiniGames.log(Level.WARNING, String.format("The dropper group %s contains the" +
" arena id %s which is not a valid arena id!", this.getGroupName(), anArenaId));
continue;
}
// This is a lower-numbered arena the player has yet to complete
if (dropperArena.getData().hasNotCompleted(gameMode, player)) {
return false;
if (arena.getData().hasNotCompleted(gameMode, player)) {
return true;
}
}
return false;
}
/**
* Gets this group's name, but sanitized
*
* @return <p>The sanitized group name</p>
*/
public @NotNull String getGroupNameSanitized() {
return StringSanitizer.sanitizeArenaName(this.getGroupName());
return true;
}
/**
@ -224,27 +233,9 @@ public class DropperArenaGroup implements ConfigurationSerializable {
return data;
}
/**
* Deserializes the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized arena group</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull DropperArenaGroup deserialize(@NotNull Map<String, Object> data) {
UUID id = ((SerializableUUID) data.get("groupId")).uuid();
String name = (String) data.get("groupName");
List<SerializableUUID> serializableArenas = (List<SerializableUUID>) data.get("arenas");
List<UUID> arenas = new ArrayList<>();
for (SerializableUUID arenaId : serializableArenas) {
arenas.add(arenaId.uuid());
}
return new DropperArenaGroup(id, name, arenas);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof DropperArenaGroup otherGroup)) {
if (!(other instanceof ArenaGroup<?, ?> otherGroup)) {
return false;
}
return this.getGroupNameSanitized().equals(otherGroup.getGroupNameSanitized());

View File

@ -1,12 +1,10 @@
package net.knarcraft.dropper.arena;
package net.knarcraft.minigames.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.util.ArenaStorageHelper;
import net.knarcraft.dropper.util.StringSanitizer;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.util.StringSanitizer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -15,21 +13,34 @@ import java.util.UUID;
import java.util.logging.Level;
/**
* A handler that keeps track of all dropper arenas
* An interface describing a generic arena handler
*
* @param <K> <p>The type of arena stored</p>
* @param <S> <p>The type of arena group stored</p>
*/
public class DropperArenaHandler {
public abstract class ArenaHandler<K extends Arena, S extends ArenaGroup<K, S>> {
private Map<UUID, DropperArena> arenas = new HashMap<>();
private Map<UUID, DropperArenaGroup> arenaGroups = new HashMap<>();
private Map<String, UUID> arenaNameLookup = new HashMap<>();
protected Map<UUID, K> arenas = new HashMap<>();
protected Map<UUID, S> arenaGroups = new HashMap<>();
protected Map<String, UUID> arenaNameLookup = new HashMap<>();
private final ArenaPlayerRegistry<K> playerRegistry;
/**
* Instantiates a new arena handler
*
* @param playerRegistry <p>The registry keeping track of player sessions</p>
*/
public ArenaHandler(ArenaPlayerRegistry<K> playerRegistry) {
this.playerRegistry = playerRegistry;
}
/**
* Gets all arenas that are within a group
*
* @return <p>All arenas in a group</p>
*/
public @NotNull Set<DropperArena> getArenasInAGroup() {
Set<DropperArena> arenas = new HashSet<>();
public @NotNull Set<K> getArenasInAGroup() {
Set<K> arenas = new HashSet<>();
for (UUID arenaId : arenaGroups.keySet()) {
arenas.add(this.arenas.get(arenaId));
}
@ -37,11 +48,11 @@ public class DropperArenaHandler {
}
/**
* Gets a copy of all dropper groups
* Gets a copy of all arena groups
*
* @return <p>All dropper groups</p>
* @return <p>All arena groups</p>
*/
public Set<DropperArenaGroup> getAllGroups() {
public Set<S> getAllGroups() {
return new HashSet<>(arenaGroups.values());
}
@ -51,7 +62,7 @@ public class DropperArenaHandler {
* @param arenaId <p>The id of the arena to get the group of</p>
* @return <p>The group the arena belongs to, or null if not in a group</p>
*/
public @Nullable DropperArenaGroup getGroup(@NotNull UUID arenaId) {
public @Nullable S getGroup(@NotNull UUID arenaId) {
return this.arenaGroups.get(arenaId);
}
@ -61,7 +72,7 @@ public class DropperArenaHandler {
* @param arenaId <p>The id of the arena to change</p>
* @param arenaGroup <p>The group to add the arena to, or null to remove the current group</p>
*/
public void setGroup(@NotNull UUID arenaId, @Nullable DropperArenaGroup arenaGroup) {
public void setGroup(@NotNull UUID arenaId, @Nullable S arenaGroup) {
if (arenaGroup == null) {
// No need to remove something non-existing
if (!this.arenaGroups.containsKey(arenaId)) {
@ -69,7 +80,7 @@ public class DropperArenaHandler {
}
// Remove the existing group
DropperArenaGroup oldGroup = this.arenaGroups.remove(arenaId);
S oldGroup = this.arenaGroups.remove(arenaId);
oldGroup.removeArena(arenaId);
} else {
// Make sure to remove the arena from the old group's internal tracking
@ -84,14 +95,14 @@ public class DropperArenaHandler {
}
/**
* Gets the dropper arena group with the given name
* Gets the arena group with the given name
*
* @param groupName <p>The name of the group to get</p>
* @return <p>The group, or null if not found</p>
*/
public @Nullable DropperArenaGroup getGroup(String groupName) {
public @Nullable S getGroup(String groupName) {
String sanitized = StringSanitizer.sanitizeArenaName(groupName);
for (DropperArenaGroup arenaGroup : this.arenaGroups.values()) {
for (S arenaGroup : this.arenaGroups.values()) {
if (arenaGroup.getGroupNameSanitized().equals(sanitized)) {
return arenaGroup;
}
@ -117,7 +128,7 @@ public class DropperArenaHandler {
*
* @param arena <p>The arena to add</p>
*/
public void addArena(@NotNull DropperArena arena) {
public void addArena(@NotNull K arena) {
this.arenas.put(arena.getArenaId(), arena);
this.arenaNameLookup.put(arena.getArenaNameSanitized(), arena.getArenaId());
this.saveArenas();
@ -129,7 +140,7 @@ public class DropperArenaHandler {
* @param arenaId <p>The id of the arena to get</p>
* @return <p>The arena, or null if no arena could be found</p>
*/
public @Nullable DropperArena getArena(@NotNull UUID arenaId) {
public @Nullable K getArena(@NotNull UUID arenaId) {
return this.arenas.get(arenaId);
}
@ -139,7 +150,7 @@ public class DropperArenaHandler {
* @param arenaName <p>The arena to get</p>
* @return <p>The arena with the given name, or null if not found</p>
*/
public @Nullable DropperArena getArena(@NotNull String arenaName) {
public @Nullable K getArena(@NotNull String arenaName) {
return this.arenas.get(this.arenaNameLookup.get(StringSanitizer.sanitizeArenaName(arenaName)));
}
@ -148,7 +159,7 @@ public class DropperArenaHandler {
*
* @return <p>All known arenas</p>
*/
public @NotNull Map<UUID, DropperArena> getArenas() {
public @NotNull Map<UUID, K> getArenas() {
return new HashMap<>(this.arenas);
}
@ -157,15 +168,15 @@ public class DropperArenaHandler {
*
* @param arena <p>The arena to remove</p>
*/
public void removeArena(@NotNull DropperArena arena) {
public void removeArena(@NotNull K arena) {
UUID arenaId = arena.getArenaId();
Dropper.getInstance().getPlayerRegistry().removeForArena(arena);
this.playerRegistry.removeForArena(arena);
this.arenas.remove(arenaId);
this.arenaNameLookup.remove(arena.getArenaNameSanitized());
this.arenaGroups.remove(arenaId);
if (!ArenaStorageHelper.removeArenaData(arenaId)) {
Dropper.getInstance().getLogger().log(Level.WARNING, "Unable to remove dropper arena data file " +
arenaId + ".yml. You must remove it manually!");
if (!arena.removeData()) {
MiniGames.log(Level.WARNING, "Unable to remove arena data file " + arenaId + ".yml. " +
"You must remove it manually!");
}
this.saveArenas();
}
@ -176,66 +187,44 @@ public class DropperArenaHandler {
* @param arenaId <p>The id of the arena whose data should be saved</p>
*/
public void saveData(UUID arenaId) {
try {
ArenaStorageHelper.saveArenaData(this.arenas.get(arenaId).getData());
} catch (IOException e) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to save arena data! Data loss can occur!");
Dropper.getInstance().getLogger().log(Level.SEVERE, e.getMessage());
K arena = getArena(arenaId);
if (arena != null) {
if (!arena.saveData()) {
MiniGames.log(Level.WARNING, "Unable to save data for arena with id " + arenaId +
" because of a write exception!");
}
} else {
MiniGames.log(Level.WARNING, "Unable to save data for arena with id " + arenaId +
" because the arena could not be found!");
}
}
/**
* Saves all current dropper groups to disk
* Saves all current groups to disk
*/
public void saveGroups() {
try {
ArenaStorageHelper.saveDropperArenaGroups(new HashSet<>(this.arenaGroups.values()));
} catch (IOException e) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to save current arena groups! " +
"Data loss can occur!");
Dropper.getInstance().getLogger().log(Level.SEVERE, e.getMessage());
}
}
public abstract void saveGroups();
/**
* Loads all dropper groups from disk
* Loads all groups from disk
*/
public void loadGroups() {
Set<DropperArenaGroup> arenaGroups = ArenaStorageHelper.loadDropperArenaGroups();
Map<UUID, DropperArenaGroup> arenaGroupMap = new HashMap<>();
for (DropperArenaGroup arenaGroup : arenaGroups) {
for (UUID arenaId : arenaGroup.getArenas()) {
arenaGroupMap.put(arenaId, arenaGroup);
}
}
this.arenaGroups = arenaGroupMap;
protected abstract void loadGroups();
/**
* Loads all arenas and groups from disk
*/
public void load() {
loadArenas();
loadGroups();
}
/**
* Saves all current arenas to disk
*/
public void saveArenas() {
try {
ArenaStorageHelper.saveArenas(this.arenas);
} catch (IOException e) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to save current arenas! " +
"Data loss can occur!");
Dropper.getInstance().getLogger().log(Level.SEVERE, e.getMessage());
}
}
public abstract void saveArenas();
/**
* Loads all arenas from disk
*/
public void loadArenas() {
this.arenas = ArenaStorageHelper.loadArenas();
// Save a map from arena name to arena id for improved performance
this.arenaNameLookup = new HashMap<>();
for (Map.Entry<UUID, DropperArena> arena : this.arenas.entrySet()) {
String sanitizedName = arena.getValue().getArenaNameSanitized();
this.arenaNameLookup.put(sanitizedName, arena.getKey());
}
}
protected abstract void loadArenas();
}

View File

@ -0,0 +1,17 @@
package net.knarcraft.minigames.arena;
/**
* A registry keeping track of all player sessions for some arenas
*
* @param <K> <p>The type of arena this registry stores</p>
*/
public interface ArenaPlayerRegistry<K extends Arena> {
/**
* Removes all active sessions for the given arena
*
* @param arena <p>The arena to remove sessions for</p>
*/
void removeForArena(K arena);
}

View File

@ -1,12 +1,11 @@
package net.knarcraft.dropper.arena;
package net.knarcraft.minigames.arena;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.record.ArenaRecord;
import net.knarcraft.dropper.arena.record.IntegerRecord;
import net.knarcraft.dropper.arena.record.LongRecord;
import net.knarcraft.dropper.arena.record.SummableArenaRecord;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.RecordResult;
import net.knarcraft.minigames.arena.record.ArenaRecord;
import net.knarcraft.minigames.arena.record.IntegerRecord;
import net.knarcraft.minigames.arena.record.LongRecord;
import net.knarcraft.minigames.arena.record.SummableArenaRecord;
import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.property.RecordResult;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -14,25 +13,24 @@ import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* A registry keeping track of all records
* A records registry storing records for an arena
*/
public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
public abstract class ArenaRecordsRegistry implements ConfigurationSerializable {
private final UUID arenaId;
protected final UUID arenaId;
private final @NotNull Set<IntegerRecord> leastDeaths;
private final @NotNull Set<LongRecord> shortestTimeMilliSeconds;
/**
* Instantiates a new empty records registry
*/
public DropperArenaRecordsRegistry(@NotNull UUID arenaId) {
public ArenaRecordsRegistry(@NotNull UUID arenaId) {
this.arenaId = arenaId;
this.leastDeaths = new HashSet<>();
this.shortestTimeMilliSeconds = new HashSet<>();
@ -44,7 +42,7 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
* @param leastDeaths <p>The existing least death records to use</p>
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
*/
private DropperArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
protected ArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
this.arenaId = arenaId;
this.leastDeaths = new HashSet<>(leastDeaths);
@ -81,8 +79,7 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
leastDeaths.removeIf((item) -> item.getUserId().equals(playerId));
leastDeaths.add(new IntegerRecord(playerId, value));
};
Set<ArenaRecord<Integer>> asInt = new HashSet<>(leastDeaths);
return registerRecord(asInt, consumer, playerId, deaths);
return registerRecord(new HashSet<>(leastDeaths), consumer, playerId, deaths);
}
/**
@ -97,16 +94,13 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
shortestTimeMilliSeconds.removeIf((item) -> item.getUserId().equals(playerId));
shortestTimeMilliSeconds.add(new LongRecord(playerId, value));
};
Set<ArenaRecord<Long>> asLong = new HashSet<>(shortestTimeMilliSeconds);
return registerRecord(asLong, consumer, playerId, milliseconds);
return registerRecord(new HashSet<>(shortestTimeMilliSeconds), consumer, playerId, milliseconds);
}
/**
* Saves changed records
*/
private void save() {
Dropper.getInstance().getArenaHandler().saveData(this.arenaId);
}
protected abstract void save();
/**
* Registers a new record if applicable
@ -176,23 +170,4 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
return data;
}
/**
* Deserializes the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized records registry</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static DropperArenaRecordsRegistry deserialize(Map<String, Object> data) {
UUID arenaId = ((SerializableUUID) data.get("arenaId")).uuid();
Set<IntegerRecord> leastDeaths =
(Set<IntegerRecord>) data.getOrDefault("leastDeaths", new HashMap<>());
Set<LongRecord> shortestTimeMilliseconds =
(Set<LongRecord>) data.getOrDefault("shortestTime", new HashMap<>());
leastDeaths.removeIf(Objects::isNull);
shortestTimeMilliseconds.removeIf(Objects::isNull);
return new DropperArenaRecordsRegistry(arenaId, leastDeaths, shortestTimeMilliseconds);
}
}

View File

@ -0,0 +1,56 @@
package net.knarcraft.minigames.arena;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* A player's session while in an arena
*/
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
*
* @return <p>The player's entry state</p>
*/
@NotNull PlayerEntryState getEntryState();
/**
* Triggers a win for the player playing in this session
*/
void triggerWin();
/**
* Triggers a loss for the player playing in this session
*/
void triggerLoss();
/**
* Triggers a quit for the player playing in this session
*
* @param immediately <p>Whether to to the teleportation immediately, not using any timers</p>
*/
void triggerQuit(boolean immediately);
/**
* Gets the arena this session is being played in
*
* @return <p>The session's arena</p>
*/
@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,27 @@
package net.knarcraft.minigames.arena;
import org.bukkit.Location;
/**
* The stored state of a player
*/
public interface PlayerEntryState {
/**
* Sets the state of the stored player to the state used by the arena
*/
void setArenaState();
/**
* Restores the stored state for the stored player
*/
void restore();
/**
* Gets the location the player entered from
*
* @return <p>The location the player entered from</p>
*/
Location getEntryLocation();
}

View File

@ -1,21 +1,29 @@
package net.knarcraft.dropper.arena;
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.StringSanitizer;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.Arena;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.util.DropperArenaStorageHelper;
import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static net.knarcraft.minigames.util.InputValidationHelper.isInvalid;
/**
* A representation of one dropper arena
*/
public class DropperArena {
public class DropperArena implements Arena {
/**
* An unique and persistent identifier for this arena
@ -62,6 +70,8 @@ public class DropperArena {
private final DropperArenaHandler dropperArenaHandler;
private static final DropperConfiguration dropperConfiguration = MiniGames.getInstance().getDropperConfiguration();
/**
* Instantiates a new dropper arena
*
@ -102,15 +112,16 @@ public class DropperArena {
*/
public DropperArena(@NotNull String arenaName, @NotNull Location spawnLocation,
@NotNull DropperArenaHandler arenaHandler) {
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
this.arenaId = UUID.randomUUID();
this.arenaName = arenaName;
this.spawnLocation = spawnLocation;
this.exitLocation = null;
this.playerVerticalVelocity = 3.92;
this.playerHorizontalVelocity = 1;
this.playerVerticalVelocity = configuration.getVerticalVelocity();
this.playerHorizontalVelocity = configuration.getHorizontalVelocity();
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries = new HashMap<>();
for (ArenaGameMode arenaGameMode : ArenaGameMode.values()) {
Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries = new HashMap<>();
for (ArenaGameMode arenaGameMode : DropperArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new DropperArenaRecordsRegistry(this.arenaId));
}
@ -119,42 +130,24 @@ public class DropperArena {
this.dropperArenaHandler = arenaHandler;
}
/**
* Gets this arena's data
*
* @return <p>This arena's data</p>
*/
@Override
public @NotNull DropperArenaData getData() {
return this.dropperArenaData;
}
/**
* Gets the id of this arena
*
* @return <p>This arena's identifier</p>
*/
@Override
public @NotNull UUID getArenaId() {
return this.arenaId;
}
/**
* Gets the name of this arena
*
* @return <p>The name of this arena</p>
*/
@Override
public @NotNull String getArenaName() {
return this.arenaName;
}
/**
* Gets this arena's spawn location
*
* <p>The spawn location is the location every player starts from when entering the dropper.</p>
*
* @return <p>This arena's spawn location.</p>
*/
@Override
public @NotNull Location getSpawnLocation() {
return this.spawnLocation;
return this.spawnLocation.clone();
}
/**
@ -163,7 +156,7 @@ public class DropperArena {
* @return <p>This arena's exit location, or null if no such location is set.</p>
*/
public @Nullable Location getExitLocation() {
return this.exitLocation;
return this.exitLocation != null ? this.exitLocation.clone() : null;
}
/**
@ -207,6 +200,36 @@ public class DropperArena {
return StringSanitizer.sanitizeArenaName(this.getArenaName());
}
@Override
public boolean removeData() {
return DropperArenaStorageHelper.removeDropperArenaData(getArenaId());
}
@Override
public boolean saveData() {
try {
DropperArenaStorageHelper.saveDropperArenaData(getData());
return true;
} catch (IOException e) {
return false;
}
}
@Override
public boolean willCauseWin(Block block) {
return block.getType() == winBlockType;
}
@Override
public boolean willCauseLoss(Block block) {
return !dropperConfiguration.getBlockWhitelist().contains(block.getType());
}
@Override
public boolean winLocationIsSolid() {
return winBlockType.isSolid();
}
/**
* Sets the spawn location for this arena
*
@ -318,15 +341,4 @@ public class DropperArena {
return this.getArenaNameSanitized().equals(otherArena.getArenaNameSanitized());
}
/**
* Checks whether the given location is valid
*
* @param location <p>The location to validate</p>
* @return <p>False if the location is valid</p>
*/
private boolean isInvalid(Location location) {
World world = location.getWorld();
return world == null || !world.getWorldBorder().isInside(location);
}
}

View File

@ -0,0 +1,72 @@
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaData;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.container.SerializableContainer;
import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.util.SerializableConverter;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Data stored for an arena
*/
public class DropperArenaData extends ArenaData {
/**
* Instantiates a new dropper arena data object
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordRegistries <p>The registries of this arena's records</p>
* @param playersCompleted <p>The set of the players that have cleared this arena for each game-mode</p>
*/
public DropperArenaData(@NotNull UUID arenaId,
@NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
super(arenaId, recordRegistries, playersCompleted);
}
@Override
public void saveData() {
MiniGames.getInstance().getDropperArenaHandler().saveData(this.arenaId);
}
/**
* Deserializes a dropper arena data from the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized dropper arena data</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull DropperArenaData deserialize(@NotNull Map<String, Object> data) {
SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId");
Map<ArenaGameMode, ArenaRecordsRegistry> recordsRegistry =
(Map<ArenaGameMode, ArenaRecordsRegistry>) data.get("recordsRegistry");
Map<ArenaGameMode, Set<SerializableContainer<UUID>>> playersCompletedData =
(Map<ArenaGameMode, Set<SerializableContainer<UUID>>>) data.get("playersCompleted");
if (recordsRegistry == null) {
recordsRegistry = new HashMap<>();
} else if (playersCompletedData == null) {
playersCompletedData = new HashMap<>();
}
// Convert the serializable UUIDs to normal UUIDs
Map<ArenaGameMode, Set<UUID>> allPlayersCompleted = new HashMap<>();
SerializableConverter.getRawValue(playersCompletedData, allPlayersCompleted);
for (ArenaGameMode arenaGameMode : playersCompletedData.keySet()) {
if (!recordsRegistry.containsKey(arenaGameMode) || recordsRegistry.get(arenaGameMode) == null) {
recordsRegistry.put(arenaGameMode, new DropperArenaRecordsRegistry(serializableUUID.getRawValue()));
}
}
return new DropperArenaData(serializableUUID.getRawValue(), recordsRegistry, allPlayersCompleted);
}
}

View File

@ -1,6 +1,5 @@
package net.knarcraft.dropper.property;
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.dropper.arena.DropperArena;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -9,7 +8,7 @@ import java.util.function.Function;
/**
* All editable properties of a dropper arena
*/
public enum ArenaEditableProperty {
public enum DropperArenaEditableProperty {
/**
* The name of the arena
@ -50,7 +49,7 @@ public enum ArenaEditableProperty {
*
* @param argumentString <p>The argument string used to specify this property</p>
*/
ArenaEditableProperty(@NotNull String argumentString, Function<DropperArena, String> currentValueProvider) {
DropperArenaEditableProperty(@NotNull String argumentString, Function<DropperArena, String> currentValueProvider) {
this.argumentString = argumentString;
this.currentValueProvider = currentValueProvider;
}
@ -80,8 +79,8 @@ public enum ArenaEditableProperty {
* @param argumentString <p>The argument string used to specify an editable property</p>
* @return <p>The corresponding editable property, or null if not found</p>
*/
public static @Nullable ArenaEditableProperty getFromArgumentString(String argumentString) {
for (ArenaEditableProperty property : ArenaEditableProperty.values()) {
public static @Nullable DropperArenaEditableProperty getFromArgumentString(String argumentString) {
for (DropperArenaEditableProperty property : DropperArenaEditableProperty.values()) {
if (property.argumentString.equalsIgnoreCase(argumentString)) {
return property;
}

View File

@ -1,5 +1,6 @@
package net.knarcraft.dropper.property;
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.arena.ArenaGameMode;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
@ -9,7 +10,7 @@ import java.util.Map;
/**
* A representation of possible arena game-modes
*/
public enum ArenaGameMode implements ConfigurationSerializable {
public enum DropperArenaGameMode implements ConfigurationSerializable, ArenaGameMode {
/**
* The default game-mode. Failing once throws the player out.
@ -33,14 +34,14 @@ public enum ArenaGameMode implements ConfigurationSerializable {
* @param gameMode <p>The game-mode string to match</p>
* @return <p>The specified arena game-mode</p>
*/
public static @NotNull ArenaGameMode matchGamemode(@NotNull String gameMode) {
public static @NotNull DropperArenaGameMode matchGamemode(@NotNull String gameMode) {
String sanitized = gameMode.trim().toLowerCase();
if (sanitized.matches("(invert(ed)?|inverse)")) {
return ArenaGameMode.INVERTED;
return DropperArenaGameMode.INVERTED;
} else if (sanitized.matches("rand(om)?")) {
return ArenaGameMode.RANDOM_INVERTED;
return DropperArenaGameMode.RANDOM_INVERTED;
} else {
return ArenaGameMode.DEFAULT;
return DropperArenaGameMode.DEFAULT;
}
}
@ -59,8 +60,13 @@ public enum ArenaGameMode implements ConfigurationSerializable {
* @return <p>The deserialized arena game-mode</p>
*/
@SuppressWarnings("unused")
public static ArenaGameMode deserialize(Map<String, Object> data) {
return ArenaGameMode.valueOf((String) data.get("name"));
public static DropperArenaGameMode deserialize(Map<String, Object> data) {
return DropperArenaGameMode.valueOf((String) data.get("name"));
}
@Override
public @NotNull DropperArenaGameMode[] getValues() {
return DropperArenaGameMode.values();
}
}

View File

@ -0,0 +1,56 @@
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGroup;
import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.util.SerializableConverter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* A sorted group of arenas that must be completed in sequence
*/
public class DropperArenaGroup extends ArenaGroup<DropperArena, DropperArenaGroup> {
/**
* Instantiates a new dropper arena group
*
* @param groupName <p>The name of this group</p>
*/
public DropperArenaGroup(@NotNull String groupName) {
super(groupName, MiniGames.getInstance().getDropperArenaHandler());
}
/**
* Instantiates a new dropper arena group
*
* @param groupId <p>The unique id of this group</p>
* @param groupName <p>The name of this group</p>
* @param arenas <p>The arenas in this group</p>
*/
private DropperArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas) {
super(groupId, groupName, arenas, MiniGames.getInstance().getDropperArenaHandler());
}
/**
* Deserializes the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized arena group</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull DropperArenaGroup deserialize(@NotNull Map<String, Object> data) {
UUID id = ((SerializableUUID) data.get("groupId")).getRawValue();
String name = (String) data.get("groupName");
List<SerializableUUID> serializableArenas = (List<SerializableUUID>) data.get("arenas");
List<UUID> arenas = new ArrayList<>();
SerializableConverter.getRawValue(new ArrayList<>(serializableArenas), arenas);
return new DropperArenaGroup(id, name, arenas);
}
}

View File

@ -0,0 +1,75 @@
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.util.DropperArenaStorageHelper;
import java.io.IOException;
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 handler that keeps track of all dropper arenas
*/
public class DropperArenaHandler extends ArenaHandler<DropperArena, DropperArenaGroup> {
/**
* Instantiates a new arena handler
*
* @param playerRegistry <p>The registry keeping track of player sessions</p>
*/
public DropperArenaHandler(DropperArenaPlayerRegistry playerRegistry) {
super(playerRegistry);
}
@Override
public void saveGroups() {
try {
DropperArenaStorageHelper.saveDropperArenaGroups(new HashSet<>(this.arenaGroups.values()));
} catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save current arena groups! " +
"Data loss can occur!");
MiniGames.log(Level.SEVERE, e.getMessage());
}
}
@Override
protected void loadGroups() {
Set<DropperArenaGroup> arenaGroups = DropperArenaStorageHelper.loadDropperArenaGroups();
Map<UUID, DropperArenaGroup> arenaGroupMap = new HashMap<>();
for (DropperArenaGroup arenaGroup : arenaGroups) {
for (UUID arenaId : arenaGroup.getArenas()) {
arenaGroupMap.put(arenaId, arenaGroup);
}
}
this.arenaGroups = arenaGroupMap;
}
@Override
public void saveArenas() {
try {
DropperArenaStorageHelper.saveDropperArenas(this.arenas);
} catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save current arenas! " +
"Data loss can occur!");
MiniGames.log(Level.SEVERE, e.getMessage());
}
}
@Override
protected void loadArenas() {
this.arenas = DropperArenaStorageHelper.loadDropperArenas();
// Save a map from arena name to arena id for improved performance
this.arenaNameLookup = new HashMap<>();
for (Map.Entry<UUID, DropperArena> arena : this.arenas.entrySet()) {
String sanitizedName = arena.getValue().getArenaNameSanitized();
this.arenaNameLookup.put(sanitizedName, arena.getKey());
}
}
}

View File

@ -1,5 +1,6 @@
package net.knarcraft.dropper.arena;
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -10,7 +11,7 @@ import java.util.UUID;
/**
* A registry to keep track of which players are playing in which arenas
*/
public class DropperArenaPlayerRegistry {
public class DropperArenaPlayerRegistry implements ArenaPlayerRegistry<DropperArena> {
private final Map<UUID, DropperArenaSession> arenaPlayers = new HashMap<>();

View File

@ -0,0 +1,65 @@
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.record.IntegerRecord;
import net.knarcraft.minigames.arena.record.LongRecord;
import net.knarcraft.minigames.container.SerializableUUID;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* A registry keeping track of all records
*/
public class DropperArenaRecordsRegistry extends ArenaRecordsRegistry {
/**
* Instantiates a new empty records registry
*/
public DropperArenaRecordsRegistry(@NotNull UUID arenaId) {
super(arenaId);
}
/**
* Instantiates a new records registry
*
* @param leastDeaths <p>The existing least death records to use</p>
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
*/
private DropperArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
super(arenaId, leastDeaths, shortestTimeMilliSeconds);
}
/**
* Saves changed records
*/
protected void save() {
MiniGames.getInstance().getDropperArenaHandler().saveData(this.arenaId);
}
/**
* Deserializes the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized records registry</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static DropperArenaRecordsRegistry deserialize(Map<String, Object> data) {
UUID arenaId = ((SerializableUUID) data.get("arenaId")).getRawValue();
Set<IntegerRecord> leastDeaths =
(Set<IntegerRecord>) data.getOrDefault("leastDeaths", new HashMap<>());
Set<LongRecord> shortestTimeMilliseconds =
(Set<LongRecord>) data.getOrDefault("shortestTime", new HashMap<>());
leastDeaths.removeIf(Objects::isNull);
shortestTimeMilliseconds.removeIf(Objects::isNull);
return new DropperArenaRecordsRegistry(arenaId, leastDeaths, shortestTimeMilliseconds);
}
}

View File

@ -1,9 +1,12 @@
package net.knarcraft.dropper.arena;
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.property.RecordResult;
import net.knarcraft.dropper.util.PlayerTeleporter;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.config.DropperConfiguration;
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;
@ -13,11 +16,11 @@ import java.util.logging.Level;
/**
* A representation of a player's current session in a dropper arena
*/
public class DropperArenaSession {
public class DropperArenaSession implements ArenaSession {
private final @NotNull DropperArena arena;
private final @NotNull Player player;
private final @NotNull ArenaGameMode gameMode;
private final @NotNull DropperArenaGameMode gameMode;
private int deaths;
private final long startTime;
private final PlayerEntryState entryState;
@ -30,16 +33,20 @@ public class DropperArenaSession {
* @param gameMode <p>The game-mode</p>
*/
public DropperArenaSession(@NotNull DropperArena dropperArena, @NotNull Player player,
@NotNull ArenaGameMode gameMode) {
@NotNull DropperArenaGameMode gameMode) {
this.arena = dropperArena;
this.player = player;
this.gameMode = gameMode;
this.deaths = 0;
this.startTime = System.currentTimeMillis();
this.entryState = new PlayerEntryState(player, gameMode);
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
boolean makeInvisible = configuration.makePlayersInvisible();
boolean disableCollision = configuration.disableHitCollision();
this.entryState = new DropperPlayerEntryState(player, gameMode, makeInvisible, disableCollision,
dropperArena.getPlayerHorizontalVelocity());
// Make the player fly to improve mobility in the air
this.entryState.setArenaState(this.arena.getPlayerHorizontalVelocity());
this.entryState.setArenaState();
}
/**
@ -47,7 +54,7 @@ public class DropperArenaSession {
*
* @return <p>The game-mode for this session</p>
*/
public @NotNull ArenaGameMode getGameMode() {
public @NotNull DropperArenaGameMode getGameMode() {
return this.gameMode;
}
@ -68,10 +75,15 @@ public class DropperArenaSession {
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().addCompleted(this.gameMode, this.player)) {
if (this.arena.getData().setCompleted(this.gameMode, this.player)) {
this.player.sendMessage("You cleared the arena!");
}
this.player.sendMessage("You won!");
@ -82,6 +94,8 @@ public class DropperArenaSession {
/**
* 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
@ -99,10 +113,10 @@ public class DropperArenaSession {
*/
private void removeSession() {
// Remove this session for game sessions to stop listeners from fiddling more with the player
boolean removedSession = Dropper.getInstance().getPlayerRegistry().removePlayer(player.getUniqueId());
boolean removedSession = MiniGames.getInstance().getDropperArenaPlayerRegistry().removePlayer(player.getUniqueId());
if (!removedSession) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to remove dropper arena session for " +
player.getName() + ". This will have unintended consequences.");
MiniGames.log(Level.SEVERE, "Unable to remove dropper arena session for " + player.getName() + ". " +
"This will have unintended consequences.");
}
}
@ -110,7 +124,7 @@ public class DropperArenaSession {
* Registers the player's record if necessary, and prints record information to the player
*/
private void registerRecord() {
DropperArenaRecordsRegistry recordsRegistry = this.arena.getData().recordRegistries().get(this.gameMode);
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");
@ -150,11 +164,13 @@ public class DropperArenaSession {
this.deaths++;
//Teleport the player back to the top
PlayerTeleporter.teleportPlayer(this.player, this.arena.getSpawnLocation(), true, false);
this.entryState.setArenaState(this.arena.getPlayerHorizontalVelocity());
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

View File

@ -1,11 +1,11 @@
package net.knarcraft.dropper.property;
package net.knarcraft.minigames.arena.dropper;
import org.jetbrains.annotations.NotNull;
/**
* A representation of each key used for storing arena data
*/
public enum ArenaStorageKey {
public enum DropperArenaStorageKey {
/**
* The key for an arena's id
@ -55,7 +55,7 @@ public enum ArenaStorageKey {
*
* @param key <p>The string path of the configuration key this value represents.</p>
*/
ArenaStorageKey(@NotNull String key) {
DropperArenaStorageKey(@NotNull String key) {
this.key = key;
}

View File

@ -0,0 +1,57 @@
package net.knarcraft.minigames.arena.dropper;
import net.knarcraft.minigames.arena.AbstractPlayerEntryState;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* The state of a player before entering a dropper arena
*/
public class DropperPlayerEntryState extends AbstractPlayerEntryState {
private final float originalFlySpeed;
private final boolean disableHitCollision;
private final float horizontalVelocity;
private final DropperArenaGameMode arenaGameMode;
/**
* Instantiates a new player state
*
* @param player <p>The player whose state should be stored</p>
*/
public DropperPlayerEntryState(@NotNull Player player, @NotNull DropperArenaGameMode arenaGameMode,
boolean makePlayerInvisible, boolean disableHitCollision, float horizontalVelocity) {
super(player, makePlayerInvisible);
this.originalFlySpeed = player.getFlySpeed();
this.arenaGameMode = arenaGameMode;
this.disableHitCollision = disableHitCollision;
this.horizontalVelocity = horizontalVelocity;
}
@Override
public void setArenaState() {
super.setArenaState();
this.player.setAllowFlight(true);
this.player.setFlying(true);
this.player.setGameMode(GameMode.ADVENTURE);
this.player.setSwimming(false);
if (this.disableHitCollision) {
this.player.setCollidable(false);
}
// If playing on the inverted game-mode, negate the horizontal velocity to swap the controls
if (this.arenaGameMode == DropperArenaGameMode.INVERTED) {
this.player.setFlySpeed(-this.horizontalVelocity);
} else {
this.player.setFlySpeed(this.horizontalVelocity);
}
}
@Override
public void restore() {
super.restore();
this.player.setFlySpeed(this.originalFlySpeed);
}
}

View File

@ -0,0 +1,413 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.Arena;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.util.MaterialHelper;
import net.knarcraft.minigames.util.ParkourArenaStorageHelper;
import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static net.knarcraft.minigames.util.InputValidationHelper.isInvalid;
/**
* A representation of one parkour arena
*/
public class ParkourArena implements Arena {
/**
* An unique and persistent identifier for this arena
*/
private final @NotNull UUID arenaId;
/**
* A name used when listing and storing this arena.
*/
private @NotNull String arenaName;
/**
* The location players are teleported to when joining this arena.
*/
private @NotNull Location spawnLocation;
/**
* The location players will be sent to when they win or lose the arena. If not set, their entry location should be
* used instead.
*/
private @Nullable Location exitLocation;
/**
* The material of the block players have to hit to win this parkour arena
*/
private @NotNull Material winBlockType;
/**
* The location the player has to reach to win. If not set, winBlockType is used instead
*/
private @Nullable Location winLocation;
/**
* The names of the block types constituting this arena's kill plane
*/
private @Nullable Set<String> killPlaneBlockNames;
/**
* The block types constituting this arena's kill plane
*/
private @Nullable Set<Material> killPlaneBlocks;
/**
* The checkpoints for this arena. Entering a checkpoint overrides the player's spawn location.
*/
private final @NotNull List<Location> checkpoints;
/**
* The arena data for this arena
*/
private final @NotNull ParkourArenaData parkourArenaData;
private final @NotNull ParkourArenaHandler parkourArenaHandler;
/**
* Instantiates a new parkour arena
*
* @param arenaId <p>The id of the arena</p>
* @param arenaName <p>The name of the arena</p>
* @param spawnLocation <p>The location players spawn in when entering the arena</p>
* @param exitLocation <p>The location the players are teleported to when exiting the arena, or null</p>
* @param winBlockType <p>The material of the block players have to hit to win this parkour arena</p>
* @param winLocation <p>The location a player has to reach to win this arena</p>
* @param parkourArenaData <p>The arena data keeping track of which players have done what in this arena</p>
* @param arenaHandler <p>The arena handler used for saving any changes</p>
*/
public ParkourArena(@NotNull UUID arenaId, @NotNull String arenaName, @NotNull Location spawnLocation,
@Nullable Location exitLocation, @NotNull Material winBlockType, @Nullable Location winLocation,
@Nullable Set<String> killPlaneBlockNames, @NotNull List<Location> checkpoints,
@NotNull ParkourArenaData parkourArenaData, @NotNull ParkourArenaHandler arenaHandler) {
this.arenaId = arenaId;
this.arenaName = arenaName;
this.spawnLocation = spawnLocation;
this.exitLocation = exitLocation;
this.winBlockType = winBlockType;
this.winLocation = winLocation;
this.killPlaneBlockNames = killPlaneBlockNames;
this.killPlaneBlocks = this.killPlaneBlockNames == null ? null : MaterialHelper.loadMaterialList(
new ArrayList<>(killPlaneBlockNames));
this.checkpoints = checkpoints;
this.parkourArenaData = parkourArenaData;
this.parkourArenaHandler = arenaHandler;
}
/**
* Instantiates a new parkour arena
*
* <p>Note that this minimal constructor can be used to quickly create a new parkour arena at the player's given
* location, simply by them giving an arena name.</p>
*
* @param arenaName <p>The name of the arena</p>
* @param spawnLocation <p>The location players spawn in when entering the arena</p>
* @param arenaHandler <p>The arena handler used for saving any changes</p>
*/
public ParkourArena(@NotNull String arenaName, @NotNull Location spawnLocation,
@NotNull ParkourArenaHandler arenaHandler) {
this.arenaId = UUID.randomUUID();
this.arenaName = arenaName;
this.spawnLocation = spawnLocation;
this.exitLocation = null;
this.winLocation = null;
Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries = new HashMap<>();
for (ParkourArenaGameMode arenaGameMode : ParkourArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new ParkourArenaRecordsRegistry(this.arenaId));
}
this.parkourArenaData = new ParkourArenaData(this.arenaId, recordRegistries, new HashMap<>());
this.winBlockType = Material.EMERALD_BLOCK;
this.killPlaneBlocks = null;
this.checkpoints = new ArrayList<>();
this.parkourArenaHandler = arenaHandler;
}
@Override
public @NotNull ParkourArenaData getData() {
return this.parkourArenaData;
}
@Override
public @NotNull UUID getArenaId() {
return this.arenaId;
}
@Override
public @NotNull String getArenaName() {
return this.arenaName;
}
@Override
public @NotNull Location getSpawnLocation() {
return this.spawnLocation;
}
/**
* 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() {
return this.exitLocation;
}
/**
* Gets the type of block a player has to hit to win this arena
*
* @return <p>The kind of block players must hit</p>
*/
public @NotNull Material getWinBlockType() {
return this.winBlockType;
}
/**
* The location a player has to reach to win this arena
*
* <p></p>
*
* @return <p>The win trigger's location</p>
*/
public @Nullable Location getWinLocation() {
return this.winLocation != null ? this.winLocation.clone() : null;
}
/**
* Gets the block types used for this parkour arena's kill plane
*
* @return <p>The types of blocks that cause a loss</p>
*/
public @NotNull Set<Material> getKillPlaneBlocks() {
if (this.killPlaneBlocks != null) {
return new HashSet<>(this.killPlaneBlocks);
} else {
return MiniGames.getInstance().getParkourConfiguration().getKillPlaneBlocks();
}
}
/**
* Gets the names of the block types used for this parkour arena's kill plane
*
* @return <p>The names of the types of blocks that cause a loss</p>
*/
public @Nullable Set<String> getKillPlaneBlockNames() {
return this.killPlaneBlockNames;
}
/**
* Gets all checkpoint locations for this arena
*
* @return <p>All checkpoint locations for this arena</p>
*/
public List<Location> getCheckpoints() {
List<Location> copy = new ArrayList<>(this.checkpoints.size());
for (Location location : this.checkpoints) {
copy.add(location.clone());
}
return copy;
}
/**
* Gets this arena's sanitized name
*
* @return <p>This arena's sanitized name</p>
*/
public @NotNull String getArenaNameSanitized() {
return StringSanitizer.sanitizeArenaName(this.getArenaName());
}
@Override
public boolean removeData() {
return ParkourArenaStorageHelper.removeParkourArenaData(getArenaId());
}
@Override
public boolean saveData() {
try {
ParkourArenaStorageHelper.saveParkourArenaData(getData());
return true;
} catch (IOException e) {
return false;
}
}
@Override
public boolean willCauseWin(Block block) {
return (this.winLocation != null && this.winLocation.getBlock().equals(block)) ||
(this.winLocation == null && this.winBlockType == block.getType());
}
@Override
public boolean willCauseLoss(Block block) {
return this.getKillPlaneBlocks().contains(block.getType());
}
@Override
public boolean winLocationIsSolid() {
return (this.winLocation != null && this.winLocation.getBlock().getType().isSolid()) ||
this.winBlockType.isSolid();
}
/**
* Sets the spawn location for this arena
*
* @param newLocation <p>The new spawn location</p>
* @return <p>True if successfully updated</p>
*/
public boolean setSpawnLocation(@NotNull Location newLocation) {
if (isInvalid(newLocation)) {
return false;
} else {
this.spawnLocation = newLocation;
this.parkourArenaHandler.saveArenas();
return true;
}
}
/**
* Sets the exit location for this arena
*
* @param newLocation <p>The new exit location</p>
* @return <p>True if successfully updated</p>
*/
public boolean setExitLocation(@NotNull Location newLocation) {
if (isInvalid(newLocation)) {
return false;
} else {
this.exitLocation = newLocation;
this.parkourArenaHandler.saveArenas();
return true;
}
}
/**
* Sets the name of this arena
*
* @param arenaName <p>The new name</p>
* @return <p>True if successfully updated</p>
*/
public boolean setName(@NotNull String arenaName) {
if (!arenaName.isBlank()) {
String oldName = this.getArenaNameSanitized();
this.arenaName = arenaName;
// Update the arena lookup map to make sure the new name can be used immediately
this.parkourArenaHandler.updateLookupName(oldName, this.getArenaNameSanitized());
this.parkourArenaHandler.saveArenas();
return true;
} else {
return false;
}
}
/**
* Sets the material of the win block type
*
* <p>The win block type is the type of block a player must hit to win in this arena</p>
*
* @param material <p>The material to set for the win block type</p>
* @return <p>True if successfully updated</p>
*/
public boolean setWinBlockType(@NotNull Material material) {
if (material.isAir() || !material.isBlock()) {
return false;
} else {
this.winBlockType = material;
this.parkourArenaHandler.saveArenas();
return true;
}
}
/**
* Sets the location players need to reach to win this arena
*
* @param newLocation <p>The location players have to reach</p>
* @return <p>True if successfully changed</p>
*/
public boolean setWinLocation(@NotNull Location newLocation) {
if (isInvalid(newLocation)) {
return false;
} else {
this.winLocation = newLocation.clone();
this.parkourArenaHandler.saveArenas();
return true;
}
}
/**
* Sets the type of blocks constituting this arena's kill plane
*
* @param killPlaneBlockNames <p>The names of the blocks that will cause players to lose</p>
*/
public boolean setKillPlaneBlocks(@NotNull Set<String> killPlaneBlockNames) {
if (killPlaneBlockNames.isEmpty()) {
this.killPlaneBlocks = null;
} else {
Set<Material> parsed = MaterialHelper.loadMaterialList(new ArrayList<>(killPlaneBlockNames));
if (parsed.isEmpty()) {
return false;
}
this.killPlaneBlocks = parsed;
}
this.parkourArenaHandler.saveArenas();
return true;
}
/**
* Adds a checkpoint to this arena
*
* @param checkpoint <p>The checkpoint to add</p>
* @return <p>True if successfully added</p>
*/
public boolean addCheckpoint(@NotNull Location checkpoint) {
if (isInvalid(checkpoint)) {
return false;
}
this.checkpoints.add(checkpoint.clone());
this.parkourArenaHandler.saveArenas();
return true;
}
/**
* Clears all checkpoints from this arena
*
* @return <p>True if successfully cleared</p>
*/
public boolean clearCheckpoints() {
if (checkpoints.isEmpty()) {
return false;
}
this.checkpoints.clear();
this.parkourArenaHandler.saveArenas();
return true;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ParkourArena otherArena)) {
return false;
}
return this.getArenaNameSanitized().equals(otherArena.getArenaNameSanitized());
}
}

View File

@ -0,0 +1,72 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaData;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.container.SerializableContainer;
import net.knarcraft.minigames.container.SerializableUUID;
import net.knarcraft.minigames.util.SerializableConverter;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Data stored for an arena
*/
public class ParkourArenaData extends ArenaData {
/**
* Instantiates a new parkour arena data object
*
* @param arenaId <p>The id of the arena this data belongs to</p>
* @param recordRegistries <p>The registry of this arena's records</p>
* @param playersCompleted <p>The set of the players that have cleared this arena</p>
*/
public ParkourArenaData(@NotNull UUID arenaId,
@NotNull Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries,
@NotNull Map<ArenaGameMode, Set<UUID>> playersCompleted) {
super(arenaId, recordRegistries, playersCompleted);
}
@Override
public void saveData() {
MiniGames.getInstance().getParkourArenaHandler().saveData(this.arenaId);
}
/**
* Deserializes a parkour arena data from the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized parkour arena data</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull ParkourArenaData deserialize(@NotNull Map<String, Object> data) {
SerializableUUID serializableUUID = (SerializableUUID) data.get("arenaId");
Map<ArenaGameMode, ArenaRecordsRegistry> recordsRegistry =
(Map<ArenaGameMode, ArenaRecordsRegistry>) data.get("recordsRegistry");
Map<ArenaGameMode, Set<SerializableContainer<UUID>>> playersCompletedData =
(Map<ArenaGameMode, Set<SerializableContainer<UUID>>>) data.get("playersCompleted");
if (recordsRegistry == null) {
recordsRegistry = new HashMap<>();
} else if (playersCompletedData == null) {
playersCompletedData = new HashMap<>();
}
// Convert the serializable UUIDs to normal UUIDs
Map<ArenaGameMode, Set<UUID>> allPlayersCompleted = new HashMap<>();
SerializableConverter.getRawValue(playersCompletedData, allPlayersCompleted);
for (ArenaGameMode arenaGameMode : playersCompletedData.keySet()) {
if (!recordsRegistry.containsKey(arenaGameMode) || recordsRegistry.get(arenaGameMode) == null) {
recordsRegistry.put(arenaGameMode, new ParkourArenaRecordsRegistry(serializableUUID.getRawValue()));
}
}
return new ParkourArenaData(serializableUUID.getRawValue(), recordsRegistry, allPlayersCompleted);
}
}

View File

@ -0,0 +1,107 @@
package net.knarcraft.minigames.arena.parkour;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
/**
* All editable properties of a parkour arena
*/
public enum ParkourArenaEditableProperty {
/**
* The name of the arena
*/
NAME("name", ParkourArena::getArenaName),
/**
* The arena's spawn location
*/
SPAWN_LOCATION("spawnLocation", (arena) -> String.valueOf(arena.getSpawnLocation())),
/**
* The arena's exit location
*/
EXIT_LOCATION("exitLocation", (arena) -> String.valueOf(arena.getExitLocation())),
/**
* The arena's win block type
*/
WIN_BLOCK_TYPE("winBlockType", (arena) -> arena.getWinBlockType().toString()),
/**
* The arena's win location (overrides the win block type)
*/
WIN_LOCATION("winLocation", (arena) -> {
if (arena.getWinLocation() != null) {
return arena.getWinLocation().toString();
} else {
return "null";
}
}),
/**
* The arena's check points. Specifically used for adding.
*/
CHECKPOINT_ADD("checkpointAdd", (arena) -> String.valueOf(arena.getCheckpoints())),
/**
* The arena's check points. Specifically used for clearing.
*/
CHECKPOINT_CLEAR("checkpointClear", (arena) -> String.valueOf(arena.getCheckpoints())),
/**
* The blocks constituting the arena's lethal blocks
*/
KILL_PLANE_BLOCKS("killPlaneBlocks", (arena) -> String.valueOf(arena.getKillPlaneBlockNames())),
;
private final @NotNull String argumentString;
private final Function<ParkourArena, String> currentValueProvider;
/**
* Instantiates a new arena editable property
*
* @param argumentString <p>The argument string used to specify this property</p>
*/
ParkourArenaEditableProperty(@NotNull String argumentString, Function<ParkourArena, String> currentValueProvider) {
this.argumentString = argumentString;
this.currentValueProvider = currentValueProvider;
}
/**
* Gets the string representation of this property's current value
*
* @param arena <p>The arena to check the value for</p>
* @return <p>The current value as a string</p>
*/
public String getCurrentValueAsString(ParkourArena arena) {
return this.currentValueProvider.apply(arena);
}
/**
* Gets the argument string used to specify this property
*
* @return <p>The argument string</p>
*/
public @NotNull String getArgumentString() {
return this.argumentString;
}
/**
* Gets the editable property corresponding to the given argument string
*
* @param argumentString <p>The argument string used to specify an editable property</p>
* @return <p>The corresponding editable property, or null if not found</p>
*/
public static @Nullable ParkourArenaEditableProperty getFromArgumentString(String argumentString) {
for (ParkourArenaEditableProperty property : ParkourArenaEditableProperty.values()) {
if (property.argumentString.equalsIgnoreCase(argumentString)) {
return property;
}
}
return null;
}
}

View File

@ -0,0 +1,59 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.arena.ArenaGameMode;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* A representation of possible arena game-modes
*/
public enum ParkourArenaGameMode implements ConfigurationSerializable, ArenaGameMode {
/**
* The default game-mode. Failing once throws the player out.
*/
DEFAULT,
;
/**
* Tries to match the correct game-mode according to the given string
*
* @param gameMode <p>The game-mode string to match</p>
* @return <p>The specified arena game-mode</p>
*/
public static @NotNull ParkourArenaGameMode matchGamemode(@NotNull String gameMode) {
try {
return ParkourArenaGameMode.valueOf(gameMode.toUpperCase());
} catch (IllegalArgumentException exception) {
return ParkourArenaGameMode.DEFAULT;
}
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("name", this.name());
return data;
}
/**
* Deserializes the arena game-mode specified by the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized arena game-mode</p>
*/
@SuppressWarnings("unused")
public static ParkourArenaGameMode deserialize(Map<String, Object> data) {
return ParkourArenaGameMode.valueOf((String) data.get("name"));
}
@Override
public @NotNull ParkourArenaGameMode[] getValues() {
return ParkourArenaGameMode.values();
}
}

View File

@ -0,0 +1,56 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGroup;
import net.knarcraft.minigames.container.SerializableUUID;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* A sorted group of arenas that must be completed in sequence
*/
public class ParkourArenaGroup extends ArenaGroup<ParkourArena, ParkourArenaGroup> {
/**
* Instantiates a new parkour arena group
*
* @param groupName <p>The name of this group</p>
*/
public ParkourArenaGroup(@NotNull String groupName) {
super(groupName, MiniGames.getInstance().getParkourArenaHandler());
}
/**
* Instantiates a new parkour arena group
*
* @param groupId <p>The unique id of this group</p>
* @param groupName <p>The name of this group</p>
* @param arenas <p>The arenas in this group</p>
*/
private ParkourArenaGroup(@NotNull UUID groupId, @NotNull String groupName, @NotNull List<UUID> arenas) {
super(groupId, groupName, arenas, MiniGames.getInstance().getParkourArenaHandler());
}
/**
* Deserializes the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized arena group</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static @NotNull ParkourArenaGroup deserialize(@NotNull Map<String, Object> data) {
UUID id = ((SerializableUUID) data.get("groupId")).getRawValue();
String name = (String) data.get("groupName");
List<SerializableUUID> serializableArenas = (List<SerializableUUID>) data.get("arenas");
List<UUID> arenas = new ArrayList<>();
for (SerializableUUID arenaId : serializableArenas) {
arenas.add(arenaId.getRawValue());
}
return new ParkourArenaGroup(id, name, arenas);
}
}

View File

@ -0,0 +1,75 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaHandler;
import net.knarcraft.minigames.util.ParkourArenaStorageHelper;
import java.io.IOException;
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 handler that keeps track of all parkour arenas
*/
public class ParkourArenaHandler extends ArenaHandler<ParkourArena, ParkourArenaGroup> {
/**
* Instantiates a new arena handler
*
* @param playerRegistry <p>The registry keeping track of player sessions</p>
*/
public ParkourArenaHandler(ParkourArenaPlayerRegistry playerRegistry) {
super(playerRegistry);
}
@Override
public void saveGroups() {
try {
ParkourArenaStorageHelper.saveParkourArenaGroups(new HashSet<>(this.arenaGroups.values()));
} catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save current arena groups! " +
"Data loss can occur!");
MiniGames.log(Level.SEVERE, e.getMessage());
}
}
@Override
protected void loadGroups() {
Set<ParkourArenaGroup> arenaGroups = ParkourArenaStorageHelper.loadParkourArenaGroups();
Map<UUID, ParkourArenaGroup> arenaGroupMap = new HashMap<>();
for (ParkourArenaGroup arenaGroup : arenaGroups) {
for (UUID arenaId : arenaGroup.getArenas()) {
arenaGroupMap.put(arenaId, arenaGroup);
}
}
this.arenaGroups = arenaGroupMap;
}
@Override
public void saveArenas() {
try {
ParkourArenaStorageHelper.saveParkourArenas(this.arenas);
} catch (IOException e) {
MiniGames.log(Level.SEVERE, "Unable to save current arenas! " +
"Data loss can occur!");
MiniGames.log(Level.SEVERE, e.getMessage());
}
}
@Override
protected void loadArenas() {
this.arenas = ParkourArenaStorageHelper.loadParkourArenas();
// Save a map from arena name to arena id for improved performance
this.arenaNameLookup = new HashMap<>();
for (Map.Entry<UUID, ParkourArena> arena : this.arenas.entrySet()) {
String sanitizedName = arena.getValue().getArenaNameSanitized();
this.arenaNameLookup.put(sanitizedName, arena.getKey());
}
}
}

View File

@ -0,0 +1,62 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.arena.ArenaPlayerRegistry;
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
*/
public class ParkourArenaPlayerRegistry implements ArenaPlayerRegistry<ParkourArena> {
private final Map<UUID, ParkourArenaSession> arenaPlayers = new HashMap<>();
/**
* 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

@ -0,0 +1,66 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.record.IntegerRecord;
import net.knarcraft.minigames.arena.record.LongRecord;
import net.knarcraft.minigames.container.SerializableUUID;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* A registry keeping track of all records
*/
public class ParkourArenaRecordsRegistry extends ArenaRecordsRegistry {
/**
* Instantiates a new empty records registry
*/
public ParkourArenaRecordsRegistry(@NotNull UUID arenaId) {
super(arenaId);
}
/**
* Instantiates a new records registry
*
* @param leastDeaths <p>The existing least death records to use</p>
* @param shortestTimeMilliSeconds <p>The existing leash time records to use</p>
*/
private ParkourArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
super(arenaId, leastDeaths, shortestTimeMilliSeconds);
}
/**
* Saves changed records
*/
@Override
protected void save() {
MiniGames.getInstance().getParkourArenaHandler().saveData(this.arenaId);
}
/**
* Deserializes the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized records registry</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static ParkourArenaRecordsRegistry deserialize(Map<String, Object> data) {
UUID arenaId = ((SerializableUUID) data.get("arenaId")).getRawValue();
Set<IntegerRecord> leastDeaths =
(Set<IntegerRecord>) data.getOrDefault("leastDeaths", new HashMap<>());
Set<LongRecord> shortestTimeMilliseconds =
(Set<LongRecord>) data.getOrDefault("shortestTime", new HashMap<>());
leastDeaths.removeIf(Objects::isNull);
shortestTimeMilliseconds.removeIf(Objects::isNull);
return new ParkourArenaRecordsRegistry(arenaId, leastDeaths, shortestTimeMilliseconds);
}
}

View File

@ -0,0 +1,223 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.PlayerEntryState;
import net.knarcraft.minigames.config.ParkourConfiguration;
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;
import org.jetbrains.annotations.Nullable;
import java.util.logging.Level;
/**
* A representation of a player's current session in a parkour arena
*/
public class ParkourArenaSession implements ArenaSession {
private final @NotNull ParkourArena arena;
private final @NotNull Player player;
private final @NotNull ParkourArenaGameMode gameMode;
private int deaths;
private final long startTime;
private final PlayerEntryState entryState;
private Location reachedCheckpoint = null;
/**
* Instantiates a new parkour arena session
*
* @param parkourArena <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 ParkourArenaSession(@NotNull ParkourArena parkourArena, @NotNull Player player,
@NotNull ParkourArenaGameMode gameMode) {
this.arena = parkourArena;
this.player = player;
this.gameMode = gameMode;
this.deaths = 0;
this.startTime = System.currentTimeMillis();
ParkourConfiguration configuration = MiniGames.getInstance().getParkourConfiguration();
boolean makeInvisible = configuration.makePlayersInvisible();
this.entryState = new ParkourPlayerEntryState(player, makeInvisible);
// Make the player fly to improve mobility in the air
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
*
* @param location <p>The location of the checkpoint</p>
*/
public void registerCheckpoint(@NotNull Location location) {
this.reachedCheckpoint = location;
}
/**
* Gets the checkpoint currently registered as the player's spawn location
*
* @return <p>The registered checkpoint, or null if not set</p>
*/
public @Nullable Location getRegisteredCheckpoint() {
return this.reachedCheckpoint;
}
/**
* 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.getParkourConfiguration().ignoreRecordsUntilGroupBeatenOnce();
ParkourArenaGroup group = miniGames.getParkourArenaHandler().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
*/
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() {
this.deaths++;
//Teleport the player back to the top
Location spawnLocation = this.reachedCheckpoint != null ? this.reachedCheckpoint : this.arena.getSpawnLocation();
PlayerTeleporter.teleportPlayer(this.player, spawnLocation, true, false);
this.entryState.setArenaState();
}
/**
* 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() {
return this.arena;
}
/**
* Gets the player playing in this session
*
* @return <p>This session's player</p>
*/
public @NotNull Player getPlayer() {
return this.player;
}
}

View File

@ -0,0 +1,76 @@
package net.knarcraft.minigames.arena.parkour;
import org.jetbrains.annotations.NotNull;
/**
* A representation of each key used for storing arena data
*/
public enum ParkourArenaStorageKey {
/**
* The key for an arena's id
*/
ID("arenaId"),
/**
* The key for an arena's name
*/
NAME("arenaName"),
/**
* The key for an arena's spawn location
*/
SPAWN_LOCATION("arenaSpawnLocation"),
/**
* The key for an arena's exit location
*/
EXIT_LOCATION("arenaExitLocation"),
/**
* The key for the type of this arena's win block
*/
WIN_BLOCK_TYPE("winBlockType"),
/**
* The key for this arena's win location (overrides win block type)
*/
WIN_LOCATION("winLocation"),
/**
* The key for this arena's kill plane blocks (overrides the config)
*/
KILL_PLANE_BLOCKS("killPlaneBlocks"),
/**
* The key for this arena's checkpoint locations
*/
CHECKPOINTS("checkpoints"),
/**
* The hey for this arena's data
*/
DATA("arenaData"),
;
private final @NotNull String key;
/**
* Instantiates a new arena storage key
*
* @param key <p>The string path of the configuration key this value represents.</p>
*/
ParkourArenaStorageKey(@NotNull String key) {
this.key = key;
}
/**
* Gets the configuration key this enum represents
*
* @return <p>The string key representation.</p>
*/
public @NotNull String getKey() {
return this.key;
}
}

View File

@ -0,0 +1,32 @@
package net.knarcraft.minigames.arena.parkour;
import net.knarcraft.minigames.arena.AbstractPlayerEntryState;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* The state of a player before entering a parkour arena
*/
public class ParkourPlayerEntryState extends AbstractPlayerEntryState {
/**
* Instantiates a new player state
*
* @param player <p>The player whose state should be stored</p>
*/
public ParkourPlayerEntryState(@NotNull Player player, boolean makePlayerInvisible) {
super(player, makePlayerInvisible);
}
@Override
public void setArenaState() {
super.setArenaState();
this.player.setAllowFlight(false);
this.player.setFlying(false);
this.player.setGameMode(GameMode.ADVENTURE);
this.player.setSwimming(false);
this.player.setCollidable(false);
}
}

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.arena.record;
package net.knarcraft.minigames.arena.record;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.arena.record;
package net.knarcraft.minigames.arena.record;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.minigames.container.SerializableUUID;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@ -37,7 +37,7 @@ public class IntegerRecord extends SummableArenaRecord<Integer> {
*/
@SuppressWarnings("unused")
public static IntegerRecord deserialize(@NotNull Map<String, Object> data) {
return new IntegerRecord(((SerializableUUID) data.get("userId")).uuid(), (Integer) data.get("record"));
return new IntegerRecord(((SerializableUUID) data.get("userId")).getRawValue(), (Integer) data.get("record"));
}
}

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.arena.record;
package net.knarcraft.minigames.arena.record;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.minigames.container.SerializableUUID;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@ -37,7 +37,7 @@ public class LongRecord extends SummableArenaRecord<Long> {
*/
@SuppressWarnings("unused")
public static LongRecord deserialize(@NotNull Map<String, Object> data) {
return new LongRecord(((SerializableUUID) data.get("userId")).uuid(), ((Number) data.get("record")).longValue());
return new LongRecord(((SerializableUUID) data.get("userId")).getRawValue(), ((Number) data.get("record")).longValue());
}
}

View File

@ -1,4 +1,4 @@
package net.knarcraft.dropper.arena.record;
package net.knarcraft.minigames.arena.record;
import java.util.UUID;

View File

@ -0,0 +1,49 @@
package net.knarcraft.minigames.command;
import net.knarcraft.minigames.arena.ArenaGameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* An abstract class for an arena joining tab-completer
*/
public abstract class JoinArenaTabCompleter implements TabCompleter {
private final ArenaGameMode gameMode;
private final Supplier<List<String>> arenaNameSupplier;
/**
* Implements a new join arena tab completer
*
* @param arenaNameSupplier <p>The supplier to ask for arena names</p>
* @param gameMode <p>An instance of one of the available game-modes</p>
*/
public JoinArenaTabCompleter(Supplier<List<String>> arenaNameSupplier, ArenaGameMode gameMode) {
this.arenaNameSupplier = arenaNameSupplier;
this.gameMode = gameMode;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] arguments) {
if (arguments.length == 1) {
return arenaNameSupplier.get();
} else if (arguments.length == 2) {
List<String> gameModes = new ArrayList<>();
for (ArenaGameMode gameMode : gameMode.getValues()) {
gameModes.add(gameMode.name().toLowerCase());
}
return gameModes;
} else {
return new ArrayList<>();
}
}
}

View File

@ -1,7 +1,7 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaSession;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@ -25,10 +25,9 @@ public class LeaveArenaCommand implements TabExecutor {
return false;
}
DropperArenaSession existingSession = Dropper.getInstance().getPlayerRegistry().getArenaSession(
player.getUniqueId());
ArenaSession existingSession = MiniGames.getInstance().getSession(player.getUniqueId());
if (existingSession == null) {
commandSender.sendMessage("You are not in a dropper arena!");
commandSender.sendMessage("You are not in a mini-games arena!");
return false;
}

View File

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

View File

@ -1,9 +1,9 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.dropper.util.StringSanitizer;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -13,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
/**
* The command for creating a new dropper arena
*/
public class CreateArenaCommand implements CommandExecutor {
public class CreateDropperArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@ -36,7 +36,7 @@ public class CreateArenaCommand implements CommandExecutor {
return false;
}
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
DropperArena existingArena = arenaHandler.getArena(arenaName);
if (existingArena != null) {

View File

@ -1,9 +1,9 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@ -17,12 +17,12 @@ import java.util.UUID;
/**
* The command for listing groups and the stages within
*/
public class GroupListCommand implements TabExecutor {
public class DropperGroupListCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
if (arguments.length == 0) {
displayExistingGroups(arenaHandler, commandSender);
return true;
@ -81,7 +81,7 @@ public class GroupListCommand implements TabExecutor {
@NotNull String[] arguments) {
if (arguments.length == 1) {
List<String> groupNames = new ArrayList<>();
for (DropperArenaGroup group : Dropper.getInstance().getArenaHandler().getAllGroups()) {
for (DropperArenaGroup group : MiniGames.getInstance().getDropperArenaHandler().getAllGroups()) {
groupNames.add(group.getGroupName());
}
return groupNames;

View File

@ -1,11 +1,11 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.dropper.util.StringSanitizer;
import net.knarcraft.dropper.util.TabCompleteHelper;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import net.knarcraft.minigames.util.StringSanitizer;
import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@ -18,7 +18,7 @@ import java.util.List;
/**
* The command for setting the group of an arena
*/
public class GroupSetCommand implements TabExecutor {
public class DropperGroupSetCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@ -27,7 +27,7 @@ public class GroupSetCommand implements TabExecutor {
return false;
}
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
DropperArena specifiedArena = arenaHandler.getArena(arguments[0]);
if (specifiedArena == null) {
@ -62,12 +62,12 @@ public class GroupSetCommand implements TabExecutor {
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
return TabCompleteHelper.getArenas();
return TabCompleteHelper.getDropperArenas();
} else if (arguments.length == 2) {
List<String> possibleValues = new ArrayList<>();
possibleValues.add("none");
possibleValues.add("GroupName");
for (DropperArenaGroup group : Dropper.getInstance().getArenaHandler().getAllGroups()) {
for (DropperArenaGroup group : MiniGames.getInstance().getDropperArenaHandler().getAllGroups()) {
possibleValues.add(group.getGroupName());
}
return possibleValues;

View File

@ -1,9 +1,9 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaHandler;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@ -17,7 +17,7 @@ import java.util.UUID;
/**
* The command for swapping the order of two arenas in a group
*/
public class GroupSwapCommand implements TabExecutor {
public class DropperGroupSwapCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@ -26,7 +26,7 @@ public class GroupSwapCommand implements TabExecutor {
return false;
}
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
DropperArena arena1 = arenaHandler.getArena(arguments[0]);
if (arena1 == null) {
@ -57,7 +57,7 @@ public class GroupSwapCommand implements TabExecutor {
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
if (arguments.length == 1) {
List<String> arenaNames = new ArrayList<>();
for (DropperArena dropperArena : arenaHandler.getArenasInAGroup()) {
@ -78,7 +78,7 @@ public class GroupSwapCommand implements TabExecutor {
* @return <p>The names of the arenas in the same group</p>
*/
private List<String> getArenaNamesInSameGroup(String arenaName) {
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
DropperArenaHandler arenaHandler = MiniGames.getInstance().getDropperArenaHandler();
DropperArena arena1 = arenaHandler.getArena(arenaName);
if (arena1 == null) {
return new ArrayList<>();

View File

@ -1,8 +1,9 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.property.ArenaEditableProperty;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaEditableProperty;
import net.knarcraft.minigames.config.DropperConfiguration;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.Command;
@ -14,7 +15,18 @@ import org.jetbrains.annotations.NotNull;
/**
* The command for editing an existing dropper arena
*/
public class EditArenaCommand implements CommandExecutor {
public class EditDropperArenaCommand implements CommandExecutor {
private final DropperConfiguration configuration;
/**
* Instantiates a new edit arena command
*
* @param configuration <p>The configuration to use</p>
*/
public EditDropperArenaCommand(DropperConfiguration configuration) {
this.configuration = configuration;
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@ -28,13 +40,13 @@ public class EditArenaCommand implements CommandExecutor {
return false;
}
DropperArena specifiedArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
DropperArena specifiedArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena.");
return false;
}
ArenaEditableProperty editableProperty = ArenaEditableProperty.getFromArgumentString(arguments[1]);
DropperArenaEditableProperty editableProperty = DropperArenaEditableProperty.getFromArgumentString(arguments[1]);
if (editableProperty == null) {
commandSender.sendMessage("Unknown property specified.");
return false;
@ -67,7 +79,7 @@ public class EditArenaCommand implements CommandExecutor {
* @param player <p>The player trying to change the value</p>
* @return <p>True if the value was successfully changed</p>
*/
private boolean changeValue(@NotNull DropperArena arena, @NotNull ArenaEditableProperty property,
private boolean changeValue(@NotNull DropperArena arena, @NotNull DropperArenaEditableProperty property,
@NotNull String value, @NotNull Player player) {
return switch (property) {
case WIN_BLOCK_TYPE -> arena.setWinBlockType(parseMaterial(value));
@ -92,7 +104,7 @@ public class EditArenaCommand implements CommandExecutor {
try {
velocity = Double.parseDouble(velocityString);
} catch (NumberFormatException exception) {
velocity = 3.92;
velocity = configuration.getVerticalVelocity();
}
// Require at least speed of 0.001, and at most 75 blocks/s
@ -111,12 +123,7 @@ public class EditArenaCommand implements CommandExecutor {
try {
velocity = Float.parseFloat(velocityString);
} catch (NumberFormatException exception) {
velocity = 1;
}
// Make sure the velocity isn't exactly 0
if (velocity == 0) {
velocity = 0.5f;
velocity = configuration.getHorizontalVelocity();
}
// If outside bonds, choose the most extreme value

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.util.TabCompleteHelper;
import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
@ -13,15 +13,15 @@ import java.util.List;
/**
* The tab-completer for the edit arena command
*/
public class EditArenaTabCompleter implements TabCompleter {
public class EditDropperArenaTabCompleter implements TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (args.length == 1) {
return TabCompleteHelper.getArenas();
return TabCompleteHelper.getDropperArenas();
} else if (args.length == 2) {
return TabCompleteHelper.getArenaProperties();
return TabCompleteHelper.getDropperArenaProperties();
} else if (args.length == 3) {
//TODO: Tab-complete possible values for the given property
return null;

View File

@ -1,12 +1,13 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.PlayerTeleporter;
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.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaPlayerRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaSession;
import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.util.PlayerTeleporter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -16,7 +17,7 @@ import org.jetbrains.annotations.NotNull;
/**
* The command used to join a dropper arena
*/
public class JoinArenaCommand implements CommandExecutor {
public class JoinDropperArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@ -30,15 +31,14 @@ public class JoinArenaCommand implements CommandExecutor {
return false;
}
// Disallow joining if the player is already in a dropper arena
DropperArenaSession existingSession = Dropper.getInstance().getPlayerRegistry().getArenaSession(player.getUniqueId());
if (existingSession != null) {
commandSender.sendMessage("You are already in a dropper arena!");
// Disallow joining if the player is already in a mini-game arena
if (MiniGames.getInstance().getSession(player.getUniqueId()) != null) {
commandSender.sendMessage("You are already playing a mini-game!");
return false;
}
// Make sure the arena exists
DropperArena specifiedArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
DropperArena specifiedArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena.");
return false;
@ -63,29 +63,30 @@ public class JoinArenaCommand implements CommandExecutor {
*/
private boolean joinArena(DropperArena specifiedArena, Player player, String[] arguments) {
// Find the specified game-mode
ArenaGameMode gameMode;
DropperArenaGameMode gameMode;
if (arguments.length > 1) {
gameMode = ArenaGameMode.matchGamemode(arguments[1]);
gameMode = DropperArenaGameMode.matchGamemode(arguments[1]);
} else {
gameMode = ArenaGameMode.DEFAULT;
gameMode = DropperArenaGameMode.DEFAULT;
}
// Make sure the player has beaten the necessary levels
DropperArenaGroup arenaGroup = Dropper.getInstance().getArenaHandler().getGroup(specifiedArena.getArenaId());
DropperArenaGroup arenaGroup = MiniGames.getInstance().getDropperArenaHandler().getGroup(specifiedArena.getArenaId());
if (arenaGroup != null && !doGroupChecks(specifiedArena, arenaGroup, gameMode, player)) {
return false;
}
// Make sure the player has beaten the arena once in normal mode before playing another mode
if (gameMode != ArenaGameMode.DEFAULT &&
specifiedArena.getData().hasNotCompleted(ArenaGameMode.DEFAULT, player)) {
if (MiniGames.getInstance().getDropperConfiguration().mustDoNormalModeFirst() &&
gameMode != DropperArenaGameMode.DEFAULT &&
specifiedArena.getData().hasNotCompleted(DropperArenaGameMode.DEFAULT, player)) {
player.sendMessage("You must complete this arena in normal mode first!");
return false;
}
// Register the player's session
DropperArenaSession newSession = new DropperArenaSession(specifiedArena, player, gameMode);
DropperArenaPlayerRegistry playerRegistry = Dropper.getInstance().getPlayerRegistry();
DropperArenaPlayerRegistry playerRegistry = MiniGames.getInstance().getDropperArenaPlayerRegistry();
playerRegistry.registerPlayer(player.getUniqueId(), newSession);
// Try to teleport the player to the arena
@ -97,7 +98,7 @@ public class JoinArenaCommand implements CommandExecutor {
return false;
} else {
// Make sure to update the state again in the air to remove a potential swimming state
newSession.getEntryState().setArenaState(specifiedArena.getPlayerHorizontalVelocity());
newSession.getEntryState().setArenaState();
return true;
}
}
@ -112,17 +113,18 @@ public class JoinArenaCommand implements CommandExecutor {
* @return <p>False if any checks failed</p>
*/
private boolean doGroupChecks(@NotNull DropperArena dropperArena, @NotNull DropperArenaGroup arenaGroup,
@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
@NotNull DropperArenaGameMode arenaGameMode, @NotNull Player player) {
DropperConfiguration configuration = MiniGames.getInstance().getDropperConfiguration();
// Require that players beat all arenas in the group in the normal game-mode before trying challenge modes
if (arenaGameMode != ArenaGameMode.DEFAULT) {
if (!arenaGroup.hasBeatenAll(ArenaGameMode.DEFAULT, player)) {
if (configuration.mustDoNormalModeFirst() && arenaGameMode != DropperArenaGameMode.DEFAULT &&
!arenaGroup.hasBeatenAll(DropperArenaGameMode.DEFAULT, player)) {
player.sendMessage("You have not yet beaten all arenas in this group!");
return false;
}
}
// Require that the player has beaten the previous arena on the same game-mode before trying this one
if (!arenaGroup.canPlay(arenaGameMode, player, dropperArena.getArenaId())) {
if (configuration.mustDoGroupedInSequence() &&
arenaGroup.cannotPlay(arenaGameMode, player, dropperArena.getArenaId())) {
player.sendMessage("You have not yet beaten the previous arena!");
return false;
}

View File

@ -0,0 +1,19 @@
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.command.JoinArenaTabCompleter;
import net.knarcraft.minigames.util.TabCompleteHelper;
/**
* The tab-completer for the join command
*/
public class JoinDropperArenaTabCompleter extends JoinArenaTabCompleter {
/**
* Implements a new join arena tab completer
*/
public JoinDropperArenaTabCompleter() {
super(TabCompleteHelper::getDropperArenas, DropperArenaGameMode.DEFAULT);
}
}

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.util.TabCompleteHelper;
import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@ -13,13 +13,13 @@ import java.util.List;
/**
* A command for listing existing dropper arenas
*/
public class ListArenaCommand implements TabExecutor {
public class ListDropperArenaCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
sender.sendMessage("Dropper arenas:");
for (String arenaName : TabCompleteHelper.getArenas()) {
for (String arenaName : TabCompleteHelper.getDropperArenas()) {
sender.sendMessage(arenaName);
}
return true;

View File

@ -1,7 +1,7 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull;
/**
* The method used for removing an existing arena
*/
public class RemoveArenaCommand implements CommandExecutor {
public class RemoveDropperArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@ -21,14 +21,14 @@ public class RemoveArenaCommand implements CommandExecutor {
}
// Get the specified arena
DropperArena targetArena = Dropper.getInstance().getArenaHandler().getArena(arguments[0]);
DropperArena targetArena = MiniGames.getInstance().getDropperArenaHandler().getArena(arguments[0]);
if (targetArena == null) {
commandSender.sendMessage("Unable to find the specified arena");
return false;
}
// Remove the arena
Dropper.getInstance().getArenaHandler().removeArena(targetArena);
MiniGames.getInstance().getDropperArenaHandler().removeArena(targetArena);
commandSender.sendMessage("The specified arena has been successfully removed");
return true;
}

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.command;
package net.knarcraft.minigames.command.dropper;
import net.knarcraft.dropper.util.TabCompleteHelper;
import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
@ -13,14 +13,14 @@ import java.util.List;
/**
* The tab-completer for the remove arena command
*/
public class RemoveArenaTabCompleter implements TabCompleter {
public class RemoveDropperArenaTabCompleter implements TabCompleter {
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
return TabCompleteHelper.getArenas();
return TabCompleteHelper.getDropperArenas();
} else {
return new ArrayList<>();
}

View File

@ -0,0 +1,53 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.util.StringSanitizer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* The command for creating a new parkour arena
*/
public class CreateParkourArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player");
return false;
}
// Abort if no name was specified
if (arguments.length < 1) {
return false;
}
// Remove known characters that are likely to cause trouble if used in an arena name
String arenaName = StringSanitizer.removeUnwantedCharacters(arguments[0]);
// An arena name is required
if (arenaName.isBlank()) {
return false;
}
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
ParkourArena existingArena = arenaHandler.getArena(arenaName);
if (existingArena != null) {
commandSender.sendMessage("There already exists a parkour arena with that name!");
return false;
}
ParkourArena arena = new ParkourArena(arenaName, player.getLocation(), arenaHandler);
arenaHandler.addArena(arena);
commandSender.sendMessage("The arena was successfully created!");
return true;
}
}

View File

@ -0,0 +1,127 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaEditableProperty;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
/**
* The command for editing an existing dropper arena
*/
public class EditParkourArenaCommand implements CommandExecutor {
/**
* Instantiates a new edit arena command
*/
public EditParkourArenaCommand() {
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player");
return false;
}
if (arguments.length < 2) {
return false;
}
ParkourArena specifiedArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified dropper arena.");
return false;
}
ParkourArenaEditableProperty editableProperty = ParkourArenaEditableProperty.getFromArgumentString(arguments[1]);
if (editableProperty == null) {
commandSender.sendMessage("Unknown property specified.");
return false;
}
String currentValueFormat = "Current value of %s is: %s";
if (arguments.length < 3) {
// Print the current value of the property
String value = editableProperty.getCurrentValueAsString(specifiedArena);
commandSender.sendMessage(String.format(currentValueFormat, editableProperty.getArgumentString(), value));
return true;
} else {
boolean successful = changeValue(specifiedArena, editableProperty, arguments[2], player);
if (successful) {
player.sendMessage(String.format("Property %s changed to: %s", editableProperty, arguments[2]));
} else {
player.sendMessage("Unable to change the property. Make sure your input is valid!");
}
return successful;
}
}
/**
* Changes the given property to the given value
*
* @param arena <p>The arena to change the property for</p>
* @param property <p>The property to change</p>
* @param value <p>The new value of the property</p>
* @param player <p>The player trying to change the value</p>
* @return <p>True if the value was successfully changed</p>
*/
private boolean changeValue(@NotNull ParkourArena arena, @NotNull ParkourArenaEditableProperty property,
@NotNull String value, @NotNull Player player) {
return switch (property) {
case WIN_BLOCK_TYPE -> arena.setWinBlockType(parseMaterial(value));
case SPAWN_LOCATION -> arena.setSpawnLocation(parseLocation(player, value));
case NAME -> arena.setName(value);
case EXIT_LOCATION -> arena.setExitLocation(parseLocation(player, value));
case WIN_LOCATION -> arena.setWinLocation(parseLocation(player, value));
case CHECKPOINT_ADD -> arena.addCheckpoint(parseLocation(player, value));
case CHECKPOINT_CLEAR -> arena.clearCheckpoints();
case KILL_PLANE_BLOCKS -> arena.setKillPlaneBlocks(new HashSet<>(List.of(value.split(","))));
};
}
/**
* Parses the given location string
*
* @param player <p>The player changing a location</p>
* @param locationString <p>The location string to parse</p>
* @return <p>The parsed location, or the player's location if not parse-able</p>
*/
private @NotNull Location parseLocation(Player player, String locationString) {
if ((locationString.trim() + ",").matches("([0-9]+.?[0-9]*,){3}")) {
String[] parts = locationString.split(",");
Location newLocation = player.getLocation().clone();
newLocation.setX(Double.parseDouble(parts[0].trim()));
newLocation.setY(Double.parseDouble(parts[1].trim()));
newLocation.setZ(Double.parseDouble(parts[2].trim()));
return newLocation;
} else {
return player.getLocation().clone();
}
}
/**
* Parses the given material name
*
* @param materialName <p>The material name to parse</p>
* @return <p>The parsed material, or AIR if not valid</p>
*/
private @NotNull Material parseMaterial(String materialName) {
Material material = Material.matchMaterial(materialName);
if (material == null) {
material = Material.AIR;
}
return material;
}
}

View File

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

View File

@ -0,0 +1,121 @@
package net.knarcraft.minigames.command.parkour;
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.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaPlayerRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArenaSession;
import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.util.PlayerTeleporter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* The command used to join a parkour arena
*/
public class JoinParkourArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) {
commandSender.sendMessage("This command must be used by a player");
return false;
}
if (arguments.length < 1) {
return false;
}
// Disallow joining if the player is already in a mini-game arena
if (MiniGames.getInstance().getSession(player.getUniqueId()) != null) {
commandSender.sendMessage("You are already playing a mini-game!");
return false;
}
// Make sure the arena exists
ParkourArena specifiedArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified parkour arena.");
return false;
}
// Deny vehicles as allowing this is tricky, and will cause problems in some cases
if (player.isInsideVehicle() || !player.getPassengers().isEmpty()) {
commandSender.sendMessage("You cannot join an arena while inside a vehicle or carrying a passenger.");
return false;
}
return joinArena(specifiedArena, player, arguments);
}
/**
* Performs the actual arena joining
*
* @param specifiedArena <p>The arena the player wants to join</p>
* @param player <p>The player joining the arena</p>
* @param arguments <p>The arguments given</p>
* @return <p>Whether the arena was joined successfully</p>
*/
private boolean joinArena(ParkourArena specifiedArena, Player player, String[] arguments) {
// Find the specified game-mode
ParkourArenaGameMode gameMode;
if (arguments.length > 1) {
gameMode = ParkourArenaGameMode.matchGamemode(arguments[1]);
} else {
gameMode = ParkourArenaGameMode.DEFAULT;
}
// Make sure the player has beaten the necessary levels
ParkourArenaGroup arenaGroup = MiniGames.getInstance().getParkourArenaHandler().getGroup(specifiedArena.getArenaId());
if (arenaGroup != null && !doGroupChecks(specifiedArena, arenaGroup, gameMode, player)) {
return false;
}
// Register the player's session
ParkourArenaSession newSession = new ParkourArenaSession(specifiedArena, player, gameMode);
ParkourArenaPlayerRegistry playerRegistry = MiniGames.getInstance().getParkourArenaPlayerRegistry();
playerRegistry.registerPlayer(player.getUniqueId(), newSession);
// Try to teleport the player to the arena
boolean teleported = PlayerTeleporter.teleportPlayer(player, specifiedArena.getSpawnLocation(), false, false);
if (!teleported) {
player.sendMessage("Unable to teleport you to the parkour arena. Make sure you're not in a vehicle," +
"and not carrying a passenger!");
newSession.triggerQuit(false);
return false;
} else {
// Make sure to update the state again in the air to remove a potential swimming state
newSession.getEntryState().setArenaState();
return true;
}
}
/**
* Performs necessary check for the given arena's group
*
* @param parkourArena <p>The arena the player is trying to join</p>
* @param arenaGroup <p>The arena group the arena belongs to</p>
* @param arenaGameMode <p>The game-mode the player selected</p>
* @param player <p>The the player trying to join the arena</p>
* @return <p>False if any checks failed</p>
*/
private boolean doGroupChecks(@NotNull ParkourArena parkourArena, @NotNull ParkourArenaGroup arenaGroup,
@NotNull ParkourArenaGameMode arenaGameMode, @NotNull Player player) {
ParkourConfiguration configuration = MiniGames.getInstance().getParkourConfiguration();
// Require that the player has beaten the previous arena on the same game-mode before trying this one
if (configuration.mustDoGroupedInSequence() &&
arenaGroup.cannotPlay(arenaGameMode, player, parkourArena.getArenaId())) {
player.sendMessage("You have not yet beaten the previous arena!");
return false;
}
return true;
}
}

View File

@ -0,0 +1,19 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.command.JoinArenaTabCompleter;
import net.knarcraft.minigames.util.TabCompleteHelper;
/**
* The tab-completer for the join command
*/
public class JoinParkourArenaTabCompleter extends JoinArenaTabCompleter {
/**
* Implements a new join arena tab completer
*/
public JoinParkourArenaTabCompleter() {
super(TabCompleteHelper::getParkourArenas, ParkourArenaGameMode.DEFAULT);
}
}

View File

@ -0,0 +1,35 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* A command for listing existing parkour arenas
*/
public class ListParkourArenaCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
sender.sendMessage("Parkour arenas:");
for (String arenaName : TabCompleteHelper.getParkourArenas()) {
sender.sendMessage(arenaName);
}
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
return new ArrayList<>();
}
}

View File

@ -0,0 +1,95 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* The command for listing groups and the stages within
*/
public class ParkourGroupListCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
if (arguments.length == 0) {
displayExistingGroups(arenaHandler, commandSender);
return true;
} else if (arguments.length == 1) {
return displayOrderedArenaNames(arenaHandler, commandSender, arguments[0]);
} else {
return false;
}
}
/**
* Displays all currently existing parkour arena groups
*
* @param arenaHandler <p>The arena handler to get groups from</p>
* @param sender <p>The command sender to display the groups to</p>
*/
private void displayExistingGroups(@NotNull ParkourArenaHandler arenaHandler, @NotNull CommandSender sender) {
StringBuilder builder = new StringBuilder("Parkour arena groups:").append("\n");
arenaHandler.getAllGroups().stream().sorted().forEachOrdered((group) ->
builder.append(group.getGroupName()).append("\n"));
sender.sendMessage(builder.toString());
}
/**
* Displays the ordered stages in a specified group to the specified command sender
*
* @param arenaHandler <p>The arena handler to get groups from</p>
* @param sender <p>The command sender to display the stages to</p>
* @param groupName <p>The name of the group to display stages for</p>
* @return <p>True if the stages were successfully displayed</p>
*/
private boolean displayOrderedArenaNames(@NotNull ParkourArenaHandler arenaHandler, @NotNull CommandSender sender,
@NotNull String groupName) {
ParkourArenaGroup arenaGroup = arenaHandler.getGroup(groupName);
if (arenaGroup == null) {
sender.sendMessage("Unable to find the specified group!");
return false;
}
// Send a list of all stages (arenas in the group)
StringBuilder builder = new StringBuilder(groupName).append("'s stages:").append("\n");
int counter = 1;
for (UUID arenaId : arenaGroup.getArenas()) {
ParkourArena arena = arenaHandler.getArena(arenaId);
if (arena != null) {
builder.append(counter++).append(". ").append(arena.getArenaName()).append("\n");
}
}
sender.sendMessage(builder.toString());
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
List<String> groupNames = new ArrayList<>();
Set<ParkourArenaGroup> arenaGroups = MiniGames.getInstance().getParkourArenaHandler().getAllGroups();
for (ParkourArenaGroup group : arenaGroups) {
groupNames.add(group.getGroupName());
}
return groupNames;
} else {
return new ArrayList<>();
}
}
}

View File

@ -0,0 +1,79 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import net.knarcraft.minigames.util.StringSanitizer;
import net.knarcraft.minigames.util.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* The command for setting the group of an arena
*/
public class ParkourGroupSetCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length < 2) {
return false;
}
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
ParkourArena specifiedArena = arenaHandler.getArena(arguments[0]);
if (specifiedArena == null) {
commandSender.sendMessage("Unable to find the specified parkour arena.");
return false;
}
String groupName = StringSanitizer.removeUnwantedCharacters(arguments[1]);
if (groupName.isBlank()) {
return false;
}
ParkourArenaGroup arenaGroup;
if (groupName.equalsIgnoreCase("null") || groupName.equalsIgnoreCase("none")) {
arenaGroup = null;
} else {
arenaGroup = arenaHandler.getGroup(groupName);
if (arenaGroup == null) {
arenaGroup = new ParkourArenaGroup(groupName);
}
}
arenaHandler.setGroup(specifiedArena.getArenaId(), arenaGroup);
commandSender.sendMessage("The arena's group has been updated");
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
return TabCompleteHelper.getParkourArenas();
} else if (arguments.length == 2) {
List<String> possibleValues = new ArrayList<>();
possibleValues.add("none");
possibleValues.add("GroupName");
for (ParkourArenaGroup group : MiniGames.getInstance().getParkourArenaHandler().getAllGroups()) {
possibleValues.add(group.getGroupName());
}
return possibleValues;
} else {
return new ArrayList<>();
}
}
}

View File

@ -0,0 +1,102 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaHandler;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* The command for swapping the order of two arenas in a group
*/
public class ParkourGroupSwapCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length < 2) {
return false;
}
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
ParkourArena arena1 = arenaHandler.getArena(arguments[0]);
if (arena1 == null) {
commandSender.sendMessage("Unable to find the first specified parkour arena.");
return false;
}
ParkourArena arena2 = arenaHandler.getArena(arguments[1]);
if (arena2 == null) {
commandSender.sendMessage("Unable to find the second specified parkour arena.");
return false;
}
ParkourArenaGroup arena1Group = arenaHandler.getGroup(arena1.getArenaId());
ParkourArenaGroup arena2Group = arenaHandler.getGroup(arena2.getArenaId());
if (arena1Group == null || !arena1Group.equals(arena2Group)) {
commandSender.sendMessage("You cannot swap arenas in different groups!");
return false;
}
arena1Group.swapArenas(arena1Group.getArenas().indexOf(arena1.getArenaId()),
arena1Group.getArenas().indexOf(arena2.getArenaId()));
commandSender.sendMessage("The arenas have been swapped!");
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
if (arguments.length == 1) {
List<String> arenaNames = new ArrayList<>();
for (ParkourArena parkourArena : arenaHandler.getArenasInAGroup()) {
arenaNames.add(parkourArena.getArenaName());
}
return arenaNames;
} else if (arguments.length == 2) {
return getArenaNamesInSameGroup(arguments[0]);
} else {
return new ArrayList<>();
}
}
/**
* Gets the names of all arenas in the same group as the specified arena
*
* @param arenaName <p>The name of the specified arena</p>
* @return <p>The names of the arenas in the same group</p>
*/
private List<String> getArenaNamesInSameGroup(String arenaName) {
ParkourArenaHandler arenaHandler = MiniGames.getInstance().getParkourArenaHandler();
ParkourArena arena1 = arenaHandler.getArena(arenaName);
if (arena1 == null) {
return new ArrayList<>();
}
// Only display other arenas in the selected group
List<String> arenaNames = new ArrayList<>();
ParkourArenaGroup group = arenaHandler.getGroup(arena1.getArenaId());
if (group == null) {
return new ArrayList<>();
}
for (UUID arenaId : group.getArenas()) {
ParkourArena arena = arenaHandler.getArena(arenaId);
if (arena != null && arena.getArenaId() != arena1.getArenaId()) {
arenaNames.add(arena.getArenaName());
}
}
return arenaNames;
}
}

View File

@ -0,0 +1,36 @@
package net.knarcraft.minigames.command.parkour;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
/**
* The method used for removing an existing arena
*/
public class RemoveParkourArenaCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
// Abort if no name was specified
if (arguments.length < 1) {
return false;
}
// Get the specified arena
ParkourArena targetArena = MiniGames.getInstance().getParkourArenaHandler().getArena(arguments[0]);
if (targetArena == null) {
commandSender.sendMessage("Unable to find the specified arena");
return false;
}
// Remove the arena
MiniGames.getInstance().getParkourArenaHandler().removeArena(targetArena);
commandSender.sendMessage("The specified arena has been successfully removed");
return true;
}
}

View File

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

View File

@ -0,0 +1,188 @@
package net.knarcraft.minigames.config;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.HashSet;
import java.util.Set;
/**
* The configuration keeping track of dropper settings
*/
public class DropperConfiguration extends MiniGameConfiguration {
private final static String rootNode = "dropper.";
private double verticalVelocity;
private float horizontalVelocity;
private int randomlyInvertedTimer;
private boolean mustDoGroupedInSequence;
private boolean ignoreRecordsUntilGroupBeatenOnce;
private boolean mustDoNormalModeFirst;
private boolean makePlayersInvisible;
private boolean disableHitCollision;
private boolean blockSneaking;
private boolean blockSprinting;
private Set<Material> blockWhitelist;
/**
* Instantiates a new dropper configuration
*
* @param configuration <p>The YAML configuration to use internally</p>
*/
public DropperConfiguration(FileConfiguration configuration) {
super(configuration);
}
/**
* Gets the default vertical velocity
*
* @return <p>The default vertical velocity</p>
*/
public double getVerticalVelocity() {
return this.verticalVelocity;
}
/**
* Gets the default horizontal velocity
*
* @return <p>The default horizontal velocity</p>
*/
public float getHorizontalVelocity() {
return this.horizontalVelocity;
}
/**
* Gets the number of seconds before the randomly inverted game-mode toggles
*
* @return <p>Number of seconds before the inversion toggles</p>
*/
public int getRandomlyInvertedTimer() {
return this.randomlyInvertedTimer;
}
/**
* Gets whether grouped arenas must be done in the set sequence
*
* @return <p>Whether grouped arenas must be done in sequence</p>
*/
public boolean mustDoGroupedInSequence() {
return this.mustDoGroupedInSequence;
}
/**
* Gets whether the normal/default mode must be beaten before playing another game-mode
*
* @return <p>Whether the normal game-mode must be beaten first</p>
*/
public boolean mustDoNormalModeFirst() {
return this.mustDoNormalModeFirst;
}
/**
* Gets the types of block which should not trigger a loss
*
* @return <p>The materials that should not trigger a loss</p>
*/
public Set<Material> getBlockWhitelist() {
return new HashSet<>(this.blockWhitelist);
}
/**
* Gets whether records should be discarded, unless the player has already beaten all arenas in the group
*
* @return <p>Whether to ignore records on the first play-through</p>
*/
public boolean ignoreRecordsUntilGroupBeatenOnce() {
return this.ignoreRecordsUntilGroupBeatenOnce;
}
/**
* Gets whether players should be made invisible while in an arena
*
* @return <p>Whether players should be made invisible</p>
*/
public boolean makePlayersInvisible() {
return this.makePlayersInvisible;
}
/**
* Gets whether entity hit-collision of players in an arena should be disabled
*
* @return <p>Whether to disable hit collision</p>
*/
public boolean disableHitCollision() {
return this.disableHitCollision;
}
/**
* Gets whether players trying to sneak while in a dropper arena to increase their downwards speed should be blocked
*
* @return <p>Whether to block sneak to speed up</p>
*/
public boolean blockSneaking() {
return blockSneaking;
}
/**
* Gets whether players trying to sprint to improve their horizontal speed while in a dropper arena should be blocked
*
* @return <p>Whether to block sprint to speed up</p>
*/
public boolean blockSprinting() {
return this.blockSprinting;
}
@Override
protected void load() {
this.verticalVelocity = configuration.getDouble(rootNode + "verticalVelocity", 1.0);
this.horizontalVelocity = (float) configuration.getDouble(rootNode + "horizontalVelocity", 1.0);
this.randomlyInvertedTimer = configuration.getInt(rootNode + "randomlyInvertedTimer", 7);
this.mustDoGroupedInSequence = configuration.getBoolean(rootNode + "mustDoGroupedInSequence", true);
this.ignoreRecordsUntilGroupBeatenOnce = configuration.getBoolean(rootNode + "ignoreRecordsUntilGroupBeatenOnce", false);
this.mustDoNormalModeFirst = configuration.getBoolean(rootNode + "mustDoNormalModeFirst", true);
this.makePlayersInvisible = configuration.getBoolean(rootNode + "makePlayersInvisible", false);
this.disableHitCollision = configuration.getBoolean(rootNode + "disableHitCollision", true);
this.blockSprinting = configuration.getBoolean(rootNode + "blockSprinting", true);
this.blockSneaking = configuration.getBoolean(rootNode + "blockSneaking", true);
this.blockWhitelist = loadMaterialList(rootNode + "blockWhitelist");
sanitizeValues();
}
/**
* Sanitizes configuration values to ensure they are within expected bounds
*/
private void sanitizeValues() {
if (this.horizontalVelocity > 1 || this.horizontalVelocity <= 0) {
this.horizontalVelocity = 1;
}
if (this.verticalVelocity <= 0 || this.verticalVelocity > 75) {
this.verticalVelocity = 1;
}
if (this.randomlyInvertedTimer <= 0 || this.randomlyInvertedTimer > 3600) {
this.randomlyInvertedTimer = 7;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(
"Current configuration:" +
"\n" + "Vertical velocity: " + verticalVelocity +
"\n" + "Horizontal velocity: " + horizontalVelocity +
"\n" + "Randomly inverted timer: " + randomlyInvertedTimer +
"\n" + "Must do groups in sequence: " + mustDoGroupedInSequence +
"\n" + "Ignore records until group beaten once: " + ignoreRecordsUntilGroupBeatenOnce +
"\n" + "Must do normal mode first: " + mustDoNormalModeFirst +
"\n" + "Make players invisible: " + makePlayersInvisible +
"\n" + "Disable hit collision: " + disableHitCollision +
"\n" + "Block whitelist: ");
for (Material material : blockWhitelist) {
builder.append("\n - ").append(material.name());
}
return builder.toString();
}
}

View File

@ -0,0 +1,52 @@
package net.knarcraft.minigames.config;
import net.knarcraft.minigames.util.MaterialHelper;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* A configuration for a mini-game
*/
public abstract class MiniGameConfiguration {
protected FileConfiguration configuration;
/**
* Instantiates a new mini-game configuration
*
* @param configuration <p>The YAML configuration to use internally</p>
*/
public MiniGameConfiguration(@NotNull FileConfiguration configuration) {
this.configuration = configuration;
this.load();
}
/**
* Loads all configuration values from disk
*
* @param configuration <p>The configuration to load</p>
*/
public void load(FileConfiguration configuration) {
this.configuration = configuration;
this.load();
}
/**
* Loads all configuration values from disk, using the current file configuration
*/
protected abstract void load();
/**
* Loads the materials specified in the block whitelist
*/
public @NotNull Set<Material> loadMaterialList(@NotNull String path) {
List<?> blockWhitelist = configuration.getList(path, new ArrayList<>());
return MaterialHelper.loadMaterialList(blockWhitelist);
}
}

View File

@ -0,0 +1,100 @@
package net.knarcraft.minigames.config;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.HashSet;
import java.util.Set;
/**
* The configuration keeping track of parkour settings
*/
public class ParkourConfiguration extends MiniGameConfiguration {
private final static String rootNode = "parkour.";
private boolean enforceCheckpointOrder;
private boolean mustDoGroupedInSequence;
private boolean ignoreRecordsUntilGroupBeatenOnce;
private boolean makePlayersInvisible;
private Set<Material> killPlaneBlocks;
/**
* Instantiates a new dropper configuration
*
* @param configuration <p>The YAML configuration to use internally</p>
*/
public ParkourConfiguration(FileConfiguration configuration) {
super(configuration);
}
/**
* Gets whether all checkpoints must be triggered in the order they are set when configuring the parkour arena
*
* @return <p>Whether checkpoints must be triggered in order</p>
*/
public boolean enforceCheckpointOrder() {
return this.enforceCheckpointOrder;
}
/**
* Gets whether grouped arenas must be done in the set sequence
*
* @return <p>Whether grouped arenas must be done in sequence</p>
*/
public boolean mustDoGroupedInSequence() {
return this.mustDoGroupedInSequence;
}
/**
* Gets whether records should be discarded, unless the player has already beaten all arenas in the group
*
* @return <p>Whether to ignore records on the first play-through</p>
*/
public boolean ignoreRecordsUntilGroupBeatenOnce() {
return this.ignoreRecordsUntilGroupBeatenOnce;
}
/**
* Gets whether players should be made invisible while in an arena
*
* @return <p>Whether players should be made invisible</p>
*/
public boolean makePlayersInvisible() {
return this.makePlayersInvisible;
}
/**
* Gets all types of blocks constituting parkour arenas' kill planes
*
* @return <p>The types of blocks causing a player to fail a parkour map</p>
*/
public Set<Material> getKillPlaneBlocks() {
return new HashSet<>(this.killPlaneBlocks);
}
@Override
protected void load() {
this.enforceCheckpointOrder = configuration.getBoolean(rootNode + "enforceCheckpointOrder", false);
this.mustDoGroupedInSequence = configuration.getBoolean(rootNode + "mustDoGroupedInSequence", true);
this.ignoreRecordsUntilGroupBeatenOnce = configuration.getBoolean(rootNode + "ignoreRecordsUntilGroupBeatenOnce", false);
this.makePlayersInvisible = configuration.getBoolean(rootNode + "makePlayersInvisible", false);
this.killPlaneBlocks = loadMaterialList(rootNode + "killPlaneBlocks");
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(
"Current configuration:" +
"Current configuration:" +
"\n" + "Must do groups in sequence: " + mustDoGroupedInSequence +
"\n" + "Ignore records until group beaten once: " + ignoreRecordsUntilGroupBeatenOnce +
"\n" + "Make players invisible: " + makePlayersInvisible +
"\n" + "Kill plane blocks: ");
for (Material material : killPlaneBlocks) {
builder.append("\n - ").append(material.name());
}
return builder.toString();
}
}

View File

@ -0,0 +1,69 @@
package net.knarcraft.minigames.config;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
/**
* The configuration keeping track of shared settings
*/
public class SharedConfiguration extends MiniGameConfiguration {
private final static String rootNode = "shared.";
private double liquidHitBoxDepth;
private double solidHitBoxDistance;
/**
* Instantiates a new shared configuration
*
* @param configuration <p>The YAML configuration to use internally</p>
*/
public SharedConfiguration(@NotNull FileConfiguration configuration) {
super(configuration);
}
/**
* Gets the negative depth a player must reach in a liquid block for fail/win detection to trigger
*
* <p>This decides how far inside a non-solid block the player must go before detection triggers. The closer to -1
* it is, the more accurate it will seem to the player, but the likelihood of not detecting the hit increases.</p>
*
* @return <p>The liquid hit box depth to use</p>
*/
public double getLiquidHitBoxDepth() {
return this.liquidHitBoxDepth;
}
/**
* Gets the positive distance a player must at most be from a block for fail/win detection to trigger
*
* <p>This decides the distance the player must be from a block below them before a hit triggers. If too low, the
* likelihood of detecting the hit decreases, but it won't look like the player hit the block without being near.</p>
*
* @return <p>The solid hit box distance to use</p>
*/
public double getSolidHitBoxDistance() {
return this.solidHitBoxDistance;
}
@Override
protected void load() {
this.liquidHitBoxDepth = configuration.getDouble(rootNode + "liquidHitBoxDepth", -0.8);
this.solidHitBoxDistance = configuration.getDouble(rootNode + "solidHitBoxDistance", 0.2);
if (this.liquidHitBoxDepth <= -1 || this.liquidHitBoxDepth > 0) {
this.liquidHitBoxDepth = -0.8;
}
if (this.solidHitBoxDistance <= 0 || this.solidHitBoxDistance > 1) {
this.solidHitBoxDistance = 0.2;
}
}
@Override
public String toString() {
return "Current configuration:" +
"\n" + "Liquid hit box depth: " + liquidHitBoxDepth +
"\n" + "Solid hit box distance: " + solidHitBoxDistance;
}
}

View File

@ -0,0 +1,49 @@
package net.knarcraft.minigames.container;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
/**
* A container that is serializable
*
* @param <K> <p>The type of the contained object</p>
*/
public abstract class SerializableContainer<K> implements ConfigurationSerializable {
private final K value;
/**
* Instantiates a new serializable container
*
* @param value <p>The value to contain</p>
*/
public SerializableContainer(K value) {
this.value = value;
}
/**
* Gets the raw, non-serializable object
*
* @return <p>The raw stored value</p>
*/
public K getRawValue() {
return value;
}
/**
* Gets a serializable container containing the given value
*
* @param value <p>The value to make serializable</p>
* @return <p>The serializable value</p>
*/
public abstract SerializableContainer<K> getSerializable(K value);
@Override
public boolean equals(Object object) {
if (object instanceof SerializableContainer<?>) {
return this.getRawValue().equals(((SerializableContainer<?>) object).getRawValue());
} else {
return false;
}
}
}

View File

@ -0,0 +1,52 @@
package net.knarcraft.minigames.container;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* A material container able to be serialized
*/
public class SerializableMaterial extends SerializableContainer<Material> {
/**
* Instantiates a new serializable material
*
* @param material <p>The material to contain</p>
*/
public SerializableMaterial(Material material) {
super(material);
}
@Override
public SerializableContainer<Material> getSerializable(Material value) {
return new SerializableMaterial(value);
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("name", getRawValue().name());
return data;
}
/**
* Deserializes a serialized material
*
* @param data <p>The serialized data</p>
* @return <p>The deserialized material</p>
*/
@SuppressWarnings("unused")
public static SerializableMaterial deserialize(Map<String, Object> data) {
Material material = Material.matchMaterial((String) data.get("name"));
if (material == null) {
return null;
} else {
return new SerializableMaterial(material);
}
}
}

View File

@ -1,6 +1,5 @@
package net.knarcraft.dropper.container;
package net.knarcraft.minigames.container;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@ -9,16 +8,28 @@ import java.util.UUID;
/**
* A UUID container able to be serialized
*
* @param uuid <p>The UUID stored by this record</p>
*/
public record SerializableUUID(UUID uuid) implements ConfigurationSerializable {
public class SerializableUUID extends SerializableContainer<UUID> {
/**
* Instantiates a new serializable uuid
*
* @param value <p>The uuid to contain</p>
*/
public SerializableUUID(UUID value) {
super(value);
}
@Override
public SerializableContainer<UUID> getSerializable(UUID value) {
return new SerializableUUID(value);
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("id", uuid.toString());
data.put("id", getRawValue().toString());
return data;
}
@ -38,13 +49,4 @@ public record SerializableUUID(UUID uuid) implements ConfigurationSerializable {
}
}
@Override
public boolean equals(Object object) {
if (object instanceof SerializableUUID) {
return this.uuid.equals(((SerializableUUID) object).uuid);
} else {
return false;
}
}
}

View File

@ -1,7 +1,7 @@
package net.knarcraft.dropper.listener;
package net.knarcraft.minigames.listener;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaSession;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@ -18,15 +18,16 @@ public class CommandListener implements Listener {
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
DropperArenaSession existingSession = Dropper.getInstance().getPlayerRegistry().getArenaSession(
player.getUniqueId());
ArenaSession existingSession = MiniGames.getInstance().getSession(player.getUniqueId());
if (existingSession == null) {
return;
}
List<String> allowedCommands = new ArrayList<>();
allowedCommands.add("/dropperleave");
allowedCommands.add("/dleave");
allowedCommands.add("/miniGamesLeave");
allowedCommands.add("/mLeave");
allowedCommands.add("/dLeave");
allowedCommands.add("/pLeave");
String message = event.getMessage();
if (!message.startsWith("/")) {
@ -34,7 +35,7 @@ public class CommandListener implements Listener {
}
for (String command : allowedCommands) {
if (message.equals(command)) {
if (message.equalsIgnoreCase(command)) {
return;
}
}

View File

@ -0,0 +1,54 @@
package net.knarcraft.minigames.listener;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.dropper.DropperArenaSession;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityCombustEvent;
import org.bukkit.event.entity.EntityDamageEvent;
/**
* A listener for checking if a player takes damage within a dropper arena
*/
public class DamageListener implements Listener {
@EventHandler
public void onPlayerDamage(EntityDamageEvent event) {
// Only player damage matters
if (event.getEntityType() != EntityType.PLAYER) {
return;
}
Player player = (Player) event.getEntity();
// We don't care about damage outside arenas
ArenaSession arenaSession = MiniGames.getInstance().getSession(player.getUniqueId());
if (arenaSession == null) {
return;
}
event.setCancelled(true);
// Only trigger a loss when a player suffers fall damage in a dropper arena
if (arenaSession instanceof DropperArenaSession && event.getCause() == EntityDamageEvent.DamageCause.FALL) {
arenaSession.triggerLoss();
}
}
@EventHandler(ignoreCancelled = true)
public void onPlayerCombustion(EntityCombustEvent event) {
if (event.getEntityType() != EntityType.PLAYER) {
return;
}
ArenaSession arenaSession = MiniGames.getInstance().getSession(event.getEntity().getUniqueId());
if (arenaSession != null) {
// Cancel combustion for any player in an arena
event.setCancelled(true);
}
}
}

View File

@ -1,12 +1,16 @@
package net.knarcraft.dropper.listener;
package net.knarcraft.minigames.listener;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
import net.knarcraft.dropper.arena.DropperArenaSession;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.Arena;
import net.knarcraft.minigames.arena.ArenaSession;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaSession;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaSession;
import net.knarcraft.minigames.config.DropperConfiguration;
import net.knarcraft.minigames.config.ParkourConfiguration;
import net.knarcraft.minigames.config.SharedConfiguration;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -17,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@ -24,6 +29,20 @@ import java.util.Set;
*/
public class MoveListener implements Listener {
private final DropperConfiguration dropperConfiguration;
private final ParkourConfiguration parkourConfiguration;
/**
* Instantiates a new move listener
*
* @param dropperConfiguration <p>The dropper configuration to use</p>
* @param parkourConfiguration <p>The parkour configuration to use</p>
*/
public MoveListener(DropperConfiguration dropperConfiguration, ParkourConfiguration parkourConfiguration) {
this.dropperConfiguration = dropperConfiguration;
this.parkourConfiguration = parkourConfiguration;
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
// Ignore if no actual movement is happening
@ -31,15 +50,66 @@ public class MoveListener implements Listener {
return;
}
Player player = event.getPlayer();
DropperArenaPlayerRegistry playerRegistry = Dropper.getInstance().getPlayerRegistry();
DropperArenaSession arenaSession = playerRegistry.getArenaSession(player.getUniqueId());
if (arenaSession == null) {
ArenaSession session = MiniGames.getInstance().getSession(event.getPlayer().getUniqueId());
if (session instanceof DropperArenaSession dropperSession) {
doDropperArenaChecks(event, dropperSession);
} else if (session instanceof ParkourArenaSession parkourSession) {
doParkourArenaChecks(event, parkourSession);
}
}
/**
* Performs the necessary checks and tasks for the player's session
*
* @param event <p>The move event triggered</p>
* @param arenaSession <p>The dropper session of the player triggering the event</p>
*/
private void doParkourArenaChecks(@NotNull PlayerMoveEvent event, ParkourArenaSession arenaSession) {
// Ignore movement which won't cause the player's block to change
if (event.getTo() == null || event.getFrom().getBlock() == event.getTo().getBlock()) {
return;
}
// Only do block type checking if the block beneath the player changes
if (checkForSpecialBlock(arenaSession, event.getTo())) {
return;
}
// Check if the player reached one of the checkpoints for the arena
ParkourArena arena = arenaSession.getArena();
List<Location> checkpoints = arena.getCheckpoints();
for (Location checkpoint : checkpoints) {
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());
event.getPlayer().sendMessage("Checkpoint reached!");
return;
}
}
}
/**
* Performs the necessary checks and tasks for the player's session
*
* @param event <p>The move event triggered</p>
* @param arenaSession <p>The dropper session of the player triggering the event</p>
*/
private void doDropperArenaChecks(@NotNull PlayerMoveEvent event, @NotNull DropperArenaSession arenaSession) {
if (event.getTo() == null) {
return;
}
// Prevent the player from flying upwards while in flight mode
if (event.getFrom().getY() < event.getTo().getY()) {
if (event.getFrom().getY() < event.getTo().getY() ||
(dropperConfiguration.blockSneaking() && event.getPlayer().isSneaking()) ||
(dropperConfiguration.blockSprinting() && event.getPlayer().isSprinting())) {
event.setCancelled(true);
return;
}
@ -64,20 +134,17 @@ public class MoveListener implements Listener {
* @param toLocation <p>The location the player's session is about to hit</p>
* @return <p>True if a special block has been hit</p>
*/
private boolean checkForSpecialBlock(DropperArenaSession arenaSession, Location toLocation) {
/* This decides how far inside a non-solid block the player must go before detection triggers. The closer to -1
it is, the more accurate it will seem to the player, but the likelihood of not detecting the hit decreases */
double liquidDepth = -0.8;
/* This decides the distance the player must be from the block below before a hit triggers. If too low, the
likelihood of detecting the hit decreases, but the immersion increases. */
double solidDepth = 0.2;
private boolean checkForSpecialBlock(ArenaSession arenaSession, Location toLocation) {
SharedConfiguration sharedConfiguration = MiniGames.getInstance().getSharedConfiguration();
double liquidDepth = sharedConfiguration.getLiquidHitBoxDepth();
double solidDepth = sharedConfiguration.getSolidHitBoxDistance();
Arena arena = arenaSession.getArena();
// Check if the player enters water
Material winBlockType = arenaSession.getArena().getWinBlockType();
// For water, only trigger when the player enters the water, but trigger earlier for everything else
double depth = !winBlockType.isSolid() ? liquidDepth : solidDepth;
double depth = arena.winLocationIsSolid() ? solidDepth : liquidDepth;
for (Block block : getBlocksBeneathLocation(toLocation, depth)) {
if (block.getType() == winBlockType) {
if (arena.willCauseWin(block)) {
arenaSession.triggerWin();
return true;
}
@ -85,9 +152,7 @@ public class MoveListener implements Listener {
// Check if the player is about to hit a non-air and non-liquid block
for (Block block : getBlocksBeneathLocation(toLocation, solidDepth)) {
Material blockType = block.getType();
if (!blockType.isAir() && blockType != Material.STRUCTURE_VOID && blockType != Material.WATER &&
blockType != Material.LAVA && !Tag.WALL_SIGNS.isTagged(blockType)) {
if (!block.getType().isAir() && arena.willCauseLoss(block)) {
arenaSession.triggerLoss();
return true;
}
@ -118,10 +183,11 @@ public class MoveListener implements Listener {
* @param session <p>The session to update the velocity for</p>
*/
private void updatePlayerVelocity(@NotNull DropperArenaSession session) {
// Override the vertical velocity
Player player = session.getPlayer();
Vector playerVelocity = player.getVelocity();
double arenaVelocity = session.getArena().getPlayerVerticalVelocity();
Vector newVelocity = new Vector(playerVelocity.getX(), -arenaVelocity, playerVelocity.getZ());
Vector newVelocity = new Vector(playerVelocity.getX() * 5, -arenaVelocity, playerVelocity.getZ() * 5);
player.setVelocity(newVelocity);
// Toggle the direction of the player's flying, as necessary
@ -134,12 +200,12 @@ public class MoveListener implements Listener {
* @param session <p>The session to possibly invert flying for</p>
*/
private void toggleFlyInversion(@NotNull DropperArenaSession session) {
if (session.getGameMode() != ArenaGameMode.RANDOM_INVERTED) {
if (session.getGameMode() != DropperArenaGameMode.RANDOM_INVERTED) {
return;
}
Player player = session.getPlayer();
float horizontalVelocity = session.getArena().getPlayerHorizontalVelocity();
float secondsBetweenToggle = 7;
float secondsBetweenToggle = dropperConfiguration.getRandomlyInvertedTimer();
int seconds = Calendar.getInstance().get(Calendar.SECOND);
/*

View File

@ -1,16 +1,16 @@
package net.knarcraft.dropper.listener;
package net.knarcraft.minigames.listener;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArenaSession;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
@ -22,17 +22,18 @@ import java.util.logging.Level;
*/
public class PlayerLeaveListener implements Listener {
private final Map<UUID, DropperArenaSession> leftSessions = new HashMap<>();
private final Map<UUID, ArenaSession> leftSessions = new HashMap<>();
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
Player player = event.getPlayer();
DropperArenaSession arenaSession = getSession(player);
ArenaSession arenaSession = MiniGames.getInstance().getSession(event.getPlayer().getUniqueId());
if (arenaSession == null) {
return;
}
Dropper.getInstance().getLogger().log(Level.WARNING, "Found player " + player.getUniqueId() +
MiniGames.log(Level.WARNING, "Found player " + player.getUniqueId() +
" leaving in the middle of a session!");
leftSessions.put(player.getUniqueId(), arenaSession);
}
@ -42,10 +43,10 @@ public class PlayerLeaveListener implements Listener {
UUID playerId = event.getPlayer().getUniqueId();
// Force the player to quit from the session once they re-join
if (leftSessions.containsKey(playerId)) {
Dropper.getInstance().getLogger().log(Level.WARNING, "Found un-exited dropper session!");
Bukkit.getScheduler().runTaskLater(Dropper.getInstance(), () -> {
MiniGames.log(Level.WARNING, "Found un-exited dropper session!");
Bukkit.getScheduler().runTaskLater(MiniGames.getInstance(), () -> {
leftSessions.get(playerId).triggerQuit(false);
Dropper.getInstance().getLogger().log(Level.WARNING, "Triggered a quit!");
MiniGames.log(Level.WARNING, "Triggered a quit!");
leftSessions.remove(playerId);
}, 80);
}
@ -53,30 +54,26 @@ public class PlayerLeaveListener implements Listener {
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
if (event.getTo() == null || event.isCancelled()) {
Location targetLocation = event.getTo();
if (targetLocation == null || event.isCancelled()) {
return;
}
DropperArenaSession arenaSession = getSession(event.getPlayer());
ArenaSession arenaSession = MiniGames.getInstance().getSession(event.getPlayer().getUniqueId());
if (arenaSession == null) {
return;
}
if (event.getTo().equals(arenaSession.getArena().getSpawnLocation())) {
if (targetLocation.equals(arenaSession.getArena().getSpawnLocation())) {
return;
}
if (arenaSession instanceof ParkourArenaSession parkourArenaSession &&
targetLocation.equals(parkourArenaSession.getRegisteredCheckpoint())) {
return;
}
arenaSession.triggerQuit(false);
}
/**
* Gets the arena session for the given player
*
* @param player <p>The player to get the arena session for</p>
* @return <p>The player's session, or null if not in a session</p>
*/
private @Nullable DropperArenaSession getSession(@NotNull Player player) {
return Dropper.getInstance().getPlayerRegistry().getArenaSession(player.getUniqueId());
}
}

View File

@ -0,0 +1,32 @@
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

@ -1,8 +1,8 @@
package net.knarcraft.dropper.placeholder;
package net.knarcraft.minigames.placeholder;
import net.knarcraft.dropper.arena.record.ArenaRecord;
import net.knarcraft.dropper.placeholder.parsing.RecordType;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.record.ArenaRecord;
import net.knarcraft.minigames.property.RecordType;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
@ -15,7 +15,8 @@ import java.util.Set;
* @param records <p>The stored records</p>
* @param createdTime <p>The time this cache was created</p>
*/
public record GroupRecordCache<K extends Comparable<K>>(@NotNull ArenaGameMode gameMode, @NotNull RecordType recordType,
public record GroupRecordCache<K extends Comparable<K>>(@NotNull ArenaGameMode gameMode,
@NotNull RecordType recordType,
@NotNull Set<ArenaRecord<K>> records,
@NotNull Long createdTime) {
}

View File

@ -0,0 +1,32 @@
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

@ -1,17 +1,16 @@
package net.knarcraft.dropper.placeholder;
package net.knarcraft.minigames.placeholder;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.arena.record.ArenaRecord;
import net.knarcraft.dropper.placeholder.parsing.InfoType;
import net.knarcraft.dropper.placeholder.parsing.RecordType;
import net.knarcraft.dropper.placeholder.parsing.SelectionType;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.util.DropperGroupRecordHelper;
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.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.record.ArenaRecord;
import net.knarcraft.minigames.placeholder.parsing.InfoType;
import net.knarcraft.minigames.placeholder.parsing.SelectionType;
import net.knarcraft.minigames.property.RecordType;
import net.knarcraft.minigames.util.GroupRecordHelper;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
@ -29,28 +28,21 @@ import java.util.UUID;
import java.util.function.Supplier;
/**
* A placeholder expansion for dropper record placeholders
* A placeholder expansion for parkour record placeholders
*/
public class DropperRecordExpansion extends PlaceholderExpansion {
public abstract class RecordExpansion extends PlaceholderExpansion {
private final Dropper plugin;
private final ArenaHandler<?, ?> arenaHandler;
private final Map<UUID, Set<GroupRecordCache<Integer>>> groupRecordDeathsCache;
private final Map<UUID, Set<GroupRecordCache<Long>>> groupRecordTimeCache;
/**
* Initializes a new record expansion
*
* @param plugin <p>A reference to the dropper plugin</p>
*/
public DropperRecordExpansion(Dropper plugin) {
this.plugin = plugin;
public RecordExpansion(ArenaHandler<?, ?> arenaHandler) {
this.groupRecordDeathsCache = new HashMap<>();
this.groupRecordTimeCache = new HashMap<>();
}
@Override
public String getIdentifier() {
return "dropper";
this.arenaHandler = arenaHandler;
}
@Override
@ -76,7 +68,7 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
return parameters;
}
RecordType recordType = RecordType.getFromString(parts[1]);
ArenaGameMode gameMode = ArenaGameMode.matchGamemode(parts[2]);
ArenaGameMode gameMode = parseGameMode(parts[2]);
SelectionType selectionType = SelectionType.getFromString(parts[3]);
String identifier = parts[4];
int recordNumber = Integer.parseInt(parts[5]) - 1;
@ -87,7 +79,6 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
}
String info = null;
DropperArenaHandler arenaHandler = plugin.getArenaHandler();
if (selectionType == SelectionType.GROUP) {
info = getGroupRecord(arenaHandler, identifier, gameMode, recordType, recordNumber, infoType);
} else if (selectionType == SelectionType.ARENA) {
@ -98,7 +89,23 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
}
/**
* Gets a piece of record information from a dropper arena group
* 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
*/
public void clearCaches() {
this.groupRecordDeathsCache.clear();
this.groupRecordTimeCache.clear();
}
/**
* Gets a piece of record information from an arena group
*
* @param arenaHandler <p>The arena handler to get the group from</p>
* @param identifier <p>The identifier (name/uuid) selecting the group</p>
@ -108,11 +115,11 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
* @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>
*/
private @Nullable String getGroupRecord(@NotNull DropperArenaHandler arenaHandler, @NotNull String identifier,
private @Nullable String getGroupRecord(@NotNull ArenaHandler<?, ?> arenaHandler, @NotNull String identifier,
@NotNull ArenaGameMode gameMode, @NotNull RecordType recordType,
int recordNumber, @NotNull InfoType infoType) {
// Allow specifying the group UUID or the arena name
DropperArenaGroup group;
ArenaGroup<?, ?> group;
try {
group = arenaHandler.getGroup(UUID.fromString(identifier));
} catch (IllegalArgumentException exception) {
@ -124,9 +131,9 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
ArenaRecord<?> record;
if (recordType == RecordType.DEATHS) {
record = getGroupDeathRecord(group, gameMode, recordNumber);
record = getGroupDeathRecord(group, gameMode, recordNumber, arenaHandler);
} else {
record = getGroupTimeRecord(group, gameMode, recordNumber);
record = getGroupTimeRecord(group, gameMode, recordNumber, arenaHandler);
}
// If a record number is not found, leave it blank, so it looks neat
@ -143,12 +150,14 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
* @param group <p>The group to get the record from</p>
* @param gameMode <p>The game-mode to get the record from</p>
* @param recordNumber <p>The placing of the record to get (1st place, 2nd place, etc.)</p>
* @param arenaHandler <p>The handler to get arenas from</p>
* @return <p>The record, or null if not found</p>
*/
private @Nullable ArenaRecord<?> getGroupTimeRecord(@NotNull DropperArenaGroup group,
@NotNull ArenaGameMode gameMode, int recordNumber) {
private @Nullable ArenaRecord<?> getGroupTimeRecord(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode, int recordNumber,
@NotNull ArenaHandler<?, ?> arenaHandler) {
return getCachedGroupRecord(group, gameMode, RecordType.TIME, recordNumber, groupRecordTimeCache,
() -> DropperGroupRecordHelper.getCombinedTime(group, gameMode));
() -> GroupRecordHelper.getCombinedTime(group, gameMode, arenaHandler));
}
/**
@ -157,12 +166,14 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
* @param group <p>The group to get the record from</p>
* @param gameMode <p>The game-mode to get the record from</p>
* @param recordNumber <p>The placing of the record to get (1st place, 2nd place, etc.)</p>
* @param arenaHandler <p>The handler to get arenas from</p>
* @return <p>The record, or null if not found</p>
*/
private @Nullable ArenaRecord<?> getGroupDeathRecord(@NotNull DropperArenaGroup group,
@NotNull ArenaGameMode gameMode, int recordNumber) {
private @Nullable ArenaRecord<?> getGroupDeathRecord(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode, int recordNumber,
@NotNull ArenaHandler<?, ?> arenaHandler) {
return getCachedGroupRecord(group, gameMode, RecordType.DEATHS, recordNumber, groupRecordDeathsCache,
() -> DropperGroupRecordHelper.getCombinedDeaths(group, gameMode));
() -> GroupRecordHelper.getCombinedDeaths(group, gameMode, arenaHandler));
}
/**
@ -177,7 +188,7 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
* @param <K> <p>The type of the provided records</p>
* @return <p>The specified record, or null if not found</p>
*/
private <K extends Comparable<K>> @Nullable ArenaRecord<?> getCachedGroupRecord(@NotNull DropperArenaGroup group,
private <K extends Comparable<K>> @Nullable ArenaRecord<?> getCachedGroupRecord(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode,
@NotNull RecordType recordType,
int recordNumber,
@ -214,7 +225,7 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
}
/**
* Gets a piece of record information from a dropper arena
* Gets a piece of record information from an arena
*
* @param arenaHandler <p>The arena handler to get the arena from</p>
* @param identifier <p>The identifier (name/uuid) selecting the arena</p>
@ -224,11 +235,11 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
* @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>
*/
private @Nullable String getArenaRecord(@NotNull DropperArenaHandler arenaHandler, @NotNull String identifier,
private @Nullable String getArenaRecord(@NotNull ArenaHandler<?, ?> arenaHandler, @NotNull String identifier,
@NotNull ArenaGameMode gameMode, @NotNull RecordType recordType,
int recordNumber, @NotNull InfoType infoType) {
// Allow specifying the arena UUID or the arena name
DropperArena arena;
Arena arena;
try {
arena = arenaHandler.getArena(UUID.fromString(identifier));
} catch (IllegalArgumentException exception) {
@ -237,8 +248,8 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
if (arena == null) {
return null;
}
@NotNull Map<ArenaGameMode, DropperArenaRecordsRegistry> registries = arena.getData().recordRegistries();
DropperArenaRecordsRegistry recordsRegistry = registries.get(gameMode);
@NotNull Map<ArenaGameMode, ArenaRecordsRegistry> registries = arena.getData().getRecordRegistries();
ArenaRecordsRegistry recordsRegistry = registries.get(gameMode);
ArenaRecord<?> record = getRecord(recordsRegistry, recordType, recordNumber);
@ -258,7 +269,7 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
* @param recordNumber <p>The placing of the record to get (1st place, 2nd place, etc.)</p>
* @return <p>The record, or null if not found</p>
*/
private @Nullable ArenaRecord<?> getRecord(@NotNull DropperArenaRecordsRegistry recordsRegistry,
private @Nullable ArenaRecord<?> getRecord(@NotNull ArenaRecordsRegistry recordsRegistry,
@NotNull RecordType recordType, int recordNumber) {
return switch (recordType) {
case TIME -> getRecord(new HashSet<>(recordsRegistry.getShortestTimeMilliSecondsRecords()), recordNumber);

View File

@ -1,4 +1,4 @@
package net.knarcraft.dropper.placeholder.parsing;
package net.knarcraft.minigames.placeholder.parsing;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -1,4 +1,4 @@
package net.knarcraft.dropper.placeholder.parsing;
package net.knarcraft.minigames.placeholder.parsing;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -1,4 +1,4 @@
package net.knarcraft.dropper.property;
package net.knarcraft.minigames.property;
/**
* A representation of all possible record results

View File

@ -1,4 +1,4 @@
package net.knarcraft.dropper.placeholder.parsing;
package net.knarcraft.minigames.property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -0,0 +1,31 @@
package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.UUID;
import java.util.logging.Level;
public final class ArenaStorageHelper {
private ArenaStorageHelper() {
}
/**
* Gets the file used to store the given arena id's data
*
* @param root <p>The root directory for the file</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>
*/
static @NotNull File getArenaDataFile(File root, @NotNull UUID arenaId) {
File arenaDataFile = new File(root, arenaId + ".yml");
if (!root.exists() && !root.mkdirs()) {
MiniGames.log(Level.SEVERE, "Unable to create the arena data directories");
}
return arenaDataFile;
}
}

View File

@ -1,14 +1,16 @@
package net.knarcraft.dropper.util;
package net.knarcraft.minigames.util;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaData;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaRecordsRegistry;
import net.knarcraft.dropper.container.SerializableMaterial;
import net.knarcraft.dropper.container.SerializableUUID;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.property.ArenaStorageKey;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArena;
import net.knarcraft.minigames.arena.dropper.DropperArenaData;
import net.knarcraft.minigames.arena.dropper.DropperArenaGameMode;
import net.knarcraft.minigames.arena.dropper.DropperArenaGroup;
import net.knarcraft.minigames.arena.dropper.DropperArenaRecordsRegistry;
import net.knarcraft.minigames.arena.dropper.DropperArenaStorageKey;
import net.knarcraft.minigames.container.SerializableMaterial;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
@ -24,20 +26,22 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import static net.knarcraft.minigames.util.ArenaStorageHelper.getArenaDataFile;
/**
* A helper class for saving and loading arenas
*/
public final class ArenaStorageHelper {
public final class DropperArenaStorageHelper {
private final static String arenasConfigurationSection = "arenas";
private final static String groupsConfigurationSection = "groups";
private static final File arenaFile = new File(Dropper.getInstance().getDataFolder(), "arenas.yml");
private static final File groupFile = new File(Dropper.getInstance().getDataFolder(), "groups.yml");
private static final File arenaDataFolder = new File(Dropper.getInstance().getDataFolder(), "arena_data");
private final static File dataFolder = MiniGames.getInstance().getDataFolder();
private final static String dropperArenasConfigurationSection = "dropperArenas";
private final static String dropperGroupsConfigurationSection = "dropperGroups";
private static final File dropperArenaFile = new File(dataFolder, "dropper_arenas.yml");
private static final File dropperGroupFile = new File(dataFolder, "dropper_groups.yml");
private static final File dropperArenaDataFolder = new File(dataFolder, "dropper_arena_data");
private ArenaStorageHelper() {
private DropperArenaStorageHelper() {
}
@ -49,13 +53,13 @@ public final class ArenaStorageHelper {
*/
public static void saveDropperArenaGroups(@NotNull Set<DropperArenaGroup> arenaGroups) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
ConfigurationSection groupSection = configuration.createSection(groupsConfigurationSection);
ConfigurationSection groupSection = configuration.createSection(dropperGroupsConfigurationSection);
for (DropperArenaGroup arenaGroup : arenaGroups) {
groupSection.set(arenaGroup.getGroupId().toString(), arenaGroup);
}
configuration.save(groupFile);
configuration.save(dropperGroupFile);
}
/**
@ -64,8 +68,8 @@ public final class ArenaStorageHelper {
* @return <p>The loaded arena groups</p>
*/
public static @NotNull Set<DropperArenaGroup> loadDropperArenaGroups() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(groupFile);
ConfigurationSection groupSection = configuration.getConfigurationSection(groupsConfigurationSection);
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(dropperGroupFile);
ConfigurationSection groupSection = configuration.getConfigurationSection(dropperGroupsConfigurationSection);
//If no such section exists, it must be the case that there is no data to load
if (groupSection == null) {
return new HashSet<>();
@ -86,23 +90,23 @@ public final class ArenaStorageHelper {
* @param arenas <p>The arenas to save</p>
* @throws IOException <p>If unable to write to the file</p>
*/
public static void saveArenas(@NotNull Map<UUID, DropperArena> arenas) throws IOException {
public static void saveDropperArenas(@NotNull Map<UUID, DropperArena> arenas) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
ConfigurationSection arenaSection = configuration.createSection(arenasConfigurationSection);
ConfigurationSection arenaSection = configuration.createSection(dropperArenasConfigurationSection);
for (DropperArena arena : arenas.values()) {
//Note: While the arena name is used as the key, as the key has to be sanitized, the un-sanitized arena name
// must be stored as well
@NotNull ConfigurationSection configSection = arenaSection.createSection(arena.getArenaId().toString());
configSection.set(ArenaStorageKey.ID.getKey(), new SerializableUUID(arena.getArenaId()));
configSection.set(ArenaStorageKey.NAME.getKey(), arena.getArenaName());
configSection.set(ArenaStorageKey.SPAWN_LOCATION.getKey(), arena.getSpawnLocation());
configSection.set(ArenaStorageKey.EXIT_LOCATION.getKey(), arena.getExitLocation());
configSection.set(ArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey(), arena.getPlayerVerticalVelocity());
configSection.set(ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey(), arena.getPlayerHorizontalVelocity());
configSection.set(ArenaStorageKey.WIN_BLOCK_TYPE.getKey(), new SerializableMaterial(arena.getWinBlockType()));
saveArenaData(arena.getData());
configSection.set(DropperArenaStorageKey.ID.getKey(), new SerializableUUID(arena.getArenaId()));
configSection.set(DropperArenaStorageKey.NAME.getKey(), arena.getArenaName());
configSection.set(DropperArenaStorageKey.SPAWN_LOCATION.getKey(), arena.getSpawnLocation());
configSection.set(DropperArenaStorageKey.EXIT_LOCATION.getKey(), arena.getExitLocation());
configSection.set(DropperArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey(), arena.getPlayerVerticalVelocity());
configSection.set(DropperArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey(), arena.getPlayerHorizontalVelocity());
configSection.set(DropperArenaStorageKey.WIN_BLOCK_TYPE.getKey(), new SerializableMaterial(arena.getWinBlockType()));
saveDropperArenaData(arena.getData());
}
configuration.save(arenaFile);
configuration.save(dropperArenaFile);
}
/**
@ -110,9 +114,9 @@ public final class ArenaStorageHelper {
*
* @return <p>The loaded arenas, or null if the arenas configuration section is missing.</p>
*/
public static @NotNull Map<UUID, DropperArena> loadArenas() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(arenaFile);
ConfigurationSection arenaSection = configuration.getConfigurationSection(arenasConfigurationSection);
public static @NotNull Map<UUID, DropperArena> loadDropperArenas() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(dropperArenaFile);
ConfigurationSection arenaSection = configuration.getConfigurationSection(dropperArenasConfigurationSection);
//If no such section exists, it must be the case that there is no data to load
if (arenaSection == null) {
return new HashMap<>();
@ -127,7 +131,7 @@ public final class ArenaStorageHelper {
continue;
}
DropperArena arena = loadArena(configurationSection);
DropperArena arena = loadDropperArena(configurationSection);
if (arena != null) {
loadedArenas.put(arena.getArenaId(), arena);
}
@ -142,41 +146,52 @@ public final class ArenaStorageHelper {
* @param configurationSection <p>The configuration section containing arena data</p>
* @return <p>The loaded arena, or null if invalid</p>
*/
private static @Nullable DropperArena loadArena(@NotNull ConfigurationSection configurationSection) {
UUID arenaId = ((SerializableUUID) configurationSection.get(ArenaStorageKey.ID.getKey(),
new SerializableUUID(UUID.randomUUID()))).uuid();
String arenaName = configurationSection.getString(ArenaStorageKey.NAME.getKey());
Location spawnLocation = (Location) configurationSection.get(ArenaStorageKey.SPAWN_LOCATION.getKey());
Location exitLocation = (Location) configurationSection.get(ArenaStorageKey.EXIT_LOCATION.getKey());
double verticalVelocity = configurationSection.getDouble(ArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey());
private static @Nullable DropperArena loadDropperArena(@NotNull ConfigurationSection configurationSection) {
UUID arenaId = ((SerializableUUID) configurationSection.get(DropperArenaStorageKey.ID.getKey(),
new SerializableUUID(UUID.randomUUID()))).getRawValue();
String arenaName = configurationSection.getString(DropperArenaStorageKey.NAME.getKey());
Location spawnLocation = (Location) configurationSection.get(DropperArenaStorageKey.SPAWN_LOCATION.getKey());
Location exitLocation = (Location) configurationSection.get(DropperArenaStorageKey.EXIT_LOCATION.getKey());
double verticalVelocity = configurationSection.getDouble(DropperArenaStorageKey.PLAYER_VERTICAL_VELOCITY.getKey());
float horizontalVelocity = sanitizeHorizontalVelocity((float) configurationSection.getDouble(
ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey()));
DropperArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey()));
SerializableMaterial winBlockType = (SerializableMaterial) configurationSection.get(
ArenaStorageKey.WIN_BLOCK_TYPE.getKey());
Logger logger = Dropper.getInstance().getLogger();
DropperArenaStorageKey.WIN_BLOCK_TYPE.getKey());
if (arenaName == null || spawnLocation == null) {
logger.log(Level.SEVERE, "Could not load the arena at configuration " +
"section " + configurationSection.getName() + ". Please check the arenas storage file for issues.");
MiniGames.log(Level.SEVERE, "Could not load the arena at configuration " +
"section " + configurationSection.getName() + ". Please check the dropper_arenas storage file for issues.");
return null;
}
if (winBlockType == null) {
winBlockType = new SerializableMaterial(Material.WATER);
}
DropperArenaData arenaData = loadArenaData(arenaId);
// Generate new, empty arena data if not available
DropperArenaData arenaData = loadDropperArenaData(arenaId);
if (arenaData == null) {
logger.log(Level.SEVERE, "Unable to load arena data for " + arenaId);
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries = new HashMap<>();
for (ArenaGameMode arenaGameMode : ArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new DropperArenaRecordsRegistry(arenaId));
}
arenaData = new DropperArenaData(arenaId, recordRegistries, new HashMap<>());
MiniGames.log(Level.SEVERE, "Unable to load arena data for dropper arena" + arenaId);
arenaData = getEmptyDropperData(arenaId);
}
return new DropperArena(arenaId, arenaName, spawnLocation, exitLocation, verticalVelocity, horizontalVelocity,
winBlockType.material(), arenaData, Dropper.getInstance().getArenaHandler());
winBlockType.getRawValue(), arenaData, MiniGames.getInstance().getDropperArenaHandler());
}
/**
* Gets empty dropper data
*
* @param arenaId <p>The id to get parkour data for</p>
* @return <p>Empty parkour data</p>
*/
private static @NotNull DropperArenaData getEmptyDropperData(@NotNull UUID arenaId) {
Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries = new HashMap<>();
Map<ArenaGameMode, Set<UUID>> playersCompleted = new HashMap<>();
for (ArenaGameMode arenaGameMode : DropperArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new DropperArenaRecordsRegistry(arenaId));
playersCompleted.put(arenaGameMode, new HashSet<>());
}
return new DropperArenaData(arenaId, recordRegistries, playersCompleted);
}
/**
@ -184,11 +199,11 @@ public final class ArenaStorageHelper {
*
* @param arenaData <p>The arena data to store</p>
*/
public static void saveArenaData(@NotNull DropperArenaData arenaData) throws IOException {
public static void saveDropperArenaData(@NotNull DropperArenaData arenaData) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
configuration.set(ArenaStorageKey.DATA.getKey(), arenaData);
configuration.set(DropperArenaStorageKey.DATA.getKey(), arenaData);
configuration.save(getArenaDataFile(arenaData.arenaId()));
configuration.save(getDropperArenaDataFile(arenaData.getArenaId()));
}
/**
@ -197,10 +212,10 @@ public final class ArenaStorageHelper {
* @param arenaId <p>The id of the arena to get data for</p>
* @return <p>The loaded arena data</p>
*/
private static @Nullable DropperArenaData loadArenaData(@NotNull UUID arenaId) {
File arenaDataFile = getArenaDataFile(arenaId);
private static @Nullable DropperArenaData loadDropperArenaData(@NotNull UUID arenaId) {
File arenaDataFile = getDropperArenaDataFile(arenaId);
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(arenaDataFile);
return (DropperArenaData) configuration.get(ArenaStorageKey.DATA.getKey());
return (DropperArenaData) configuration.get(DropperArenaStorageKey.DATA.getKey());
}
/**
@ -209,8 +224,8 @@ public final class ArenaStorageHelper {
* @param arenaId <p>The id of the arena to remove data for</p>
* @return <p>True if the data was successfully removed</p>
*/
public static boolean removeArenaData(@NotNull UUID arenaId) {
return getArenaDataFile(arenaId).delete();
public static boolean removeDropperArenaData(@NotNull UUID arenaId) {
return getDropperArenaDataFile(arenaId).delete();
}
/**
@ -219,12 +234,8 @@ public final class ArenaStorageHelper {
* @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>
*/
private static @NotNull File getArenaDataFile(@NotNull UUID arenaId) {
File arenaDataFile = new File(arenaDataFolder, arenaId + ".yml");
if (!arenaDataFolder.exists() && !arenaDataFolder.mkdirs()) {
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to create the arena data directories");
}
return arenaDataFile;
private static @NotNull File getDropperArenaDataFile(@NotNull UUID arenaId) {
return getArenaDataFile(dropperArenaDataFolder, arenaId);
}
/**

View File

@ -1,12 +1,11 @@
package net.knarcraft.dropper.util;
package net.knarcraft.minigames.util;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.arena.DropperArena;
import net.knarcraft.dropper.arena.DropperArenaGroup;
import net.knarcraft.dropper.arena.DropperArenaHandler;
import net.knarcraft.dropper.arena.record.ArenaRecord;
import net.knarcraft.dropper.arena.record.SummableArenaRecord;
import net.knarcraft.dropper.property.ArenaGameMode;
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.record.ArenaRecord;
import net.knarcraft.minigames.arena.record.SummableArenaRecord;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@ -19,9 +18,9 @@ import java.util.function.BiFunction;
/**
* A helper class for getting combined record data for a dropper group
*/
public final class DropperGroupRecordHelper {
public final class GroupRecordHelper {
private DropperGroupRecordHelper() {
private GroupRecordHelper() {
}
@ -30,15 +29,17 @@ public final class DropperGroupRecordHelper {
*
* @param group <p>The group to get records from</p>
* @param gameMode <p>The game-mode to get records for</p>
* @param arenaHandler <p>The handler to get arenas from</p>
* @return <p>The combined death records</p>
*/
public static @NotNull Set<ArenaRecord<Integer>> getCombinedDeaths(@NotNull DropperArenaGroup group,
@NotNull ArenaGameMode gameMode) {
public static @NotNull Set<ArenaRecord<Integer>> getCombinedDeaths(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode,
@NotNull ArenaHandler<?, ?> arenaHandler) {
Map<UUID, SummableArenaRecord<Integer>> records = new HashMap<>();
@NotNull BiFunction<DropperArena, ArenaGameMode, Set<SummableArenaRecord<Integer>>> recordSupplier =
(arena, aGameMode) -> arena.getData().recordRegistries().get(gameMode).getLeastDeathsRecords();
@NotNull BiFunction<Arena, ArenaGameMode, Set<SummableArenaRecord<Integer>>> recordSupplier =
(arena, aGameMode) -> arena.getData().getRecordRegistries().get(gameMode).getLeastDeathsRecords();
return getCombined(group, gameMode, records, recordSupplier);
return getCombined(group, gameMode, records, recordSupplier, arenaHandler);
}
/**
@ -46,15 +47,17 @@ public final class DropperGroupRecordHelper {
*
* @param group <p>The group to get records from</p>
* @param gameMode <p>The game-mode to get records for</p>
* @param arenaHandler <p>The handler to get arenas from</p>
* @return <p>The combined least-time records</p>
*/
public static @NotNull Set<ArenaRecord<Long>> getCombinedTime(@NotNull DropperArenaGroup group,
@NotNull ArenaGameMode gameMode) {
public static @NotNull Set<ArenaRecord<Long>> getCombinedTime(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode,
@NotNull ArenaHandler<?, ?> arenaHandler) {
Map<UUID, SummableArenaRecord<Long>> records = new HashMap<>();
@NotNull BiFunction<DropperArena, ArenaGameMode, Set<SummableArenaRecord<Long>>> recordSupplier =
(arena, aGameMode) -> arena.getData().recordRegistries().get(gameMode).getShortestTimeMilliSecondsRecords();
@NotNull BiFunction<Arena, ArenaGameMode, Set<SummableArenaRecord<Long>>> recordSupplier =
(arena, aGameMode) -> arena.getData().getRecordRegistries().get(gameMode).getShortestTimeMilliSecondsRecords();
return getCombined(group, gameMode, records, recordSupplier);
return getCombined(group, gameMode, records, recordSupplier, arenaHandler);
}
/**
@ -64,20 +67,20 @@ public final class DropperGroupRecordHelper {
* @param gameMode <p>The game-mode to get records for</p>
* @param records <p>The map to store the combined records to</p>
* @param recordSupplier <p>The function that supplies records of this type</p>
* @param arenaHandler <p>The handler to get arenas from</p>
* @param <K> <p>The type of the records to combine</p>
* @return <p>The combined records</p>
*/
private static <K extends Comparable<K>> @NotNull Set<ArenaRecord<K>> getCombined(@NotNull DropperArenaGroup group,
private static <K extends Comparable<K>> @NotNull Set<ArenaRecord<K>> getCombined(@NotNull ArenaGroup<?, ?> group,
@NotNull ArenaGameMode gameMode,
@NotNull Map<UUID,
SummableArenaRecord<K>> records,
@NotNull BiFunction<DropperArena,
@NotNull BiFunction<Arena,
ArenaGameMode,
Set<SummableArenaRecord<K>>> recordSupplier) {
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
Set<SummableArenaRecord<K>>> recordSupplier,
@NotNull ArenaHandler<?, ?> arenaHandler) {
// Get all arenas in the group
Set<DropperArena> arenas = getArenas(arenaHandler, group);
Set<Arena> arenas = getArenas(arenaHandler, group);
// Calculate the combined records
Map<UUID, Integer> recordsFound = new HashMap<>();
@ -113,12 +116,12 @@ public final class DropperGroupRecordHelper {
* @param group <p>The group to get arenas for</p>
* @return <p>The arenas found in the group</p>
*/
private static @NotNull Set<DropperArena> getArenas(@NotNull DropperArenaHandler arenaHandler,
@NotNull DropperArenaGroup group) {
private static @NotNull Set<Arena> getArenas(@NotNull ArenaHandler<?, ?> arenaHandler,
@NotNull ArenaGroup<?, ?> group) {
// Get all arenas in the group
Set<DropperArena> arenas = new HashSet<>();
Set<Arena> arenas = new HashSet<>();
for (UUID arenaId : group.getArenas()) {
DropperArena arena = arenaHandler.getArena(arenaId);
Arena arena = arenaHandler.getArena(arenaId);
if (arena != null) {
arenas.add(arena);
}
@ -136,14 +139,14 @@ public final class DropperGroupRecordHelper {
* @param recordSupplier <p>The function that supplies record data of this type</p>
* @param <K> <p>The type of record to combine</p>
*/
private static <K extends Comparable<K>> void combineRecords(@NotNull Set<DropperArena> arenas,
private static <K extends Comparable<K>> void combineRecords(@NotNull Set<Arena> arenas,
@NotNull ArenaGameMode gameMode,
@NotNull Map<UUID,
SummableArenaRecord<K>> combinedRecords,
@NotNull Map<UUID, Integer> recordsFound,
@NotNull BiFunction<DropperArena, ArenaGameMode,
@NotNull BiFunction<Arena, ArenaGameMode,
Set<SummableArenaRecord<K>>> recordSupplier) {
for (DropperArena arena : arenas) {
for (Arena arena : arenas) {
Set<SummableArenaRecord<K>> existingRecords = recordSupplier.apply(arena, gameMode);
// For each arena's record registry, calculate the combined records
for (SummableArenaRecord<K> value : existingRecords) {

View File

@ -0,0 +1,26 @@
package net.knarcraft.minigames.util;
import org.bukkit.Location;
import org.bukkit.World;
/**
* A helper class for validating whether given input is valid
*/
public final class InputValidationHelper {
private InputValidationHelper() {
}
/**
* Checks whether the given location is valid
*
* @param location <p>The location to validate</p>
* @return <p>False if the location is valid</p>
*/
public static boolean isInvalid(Location location) {
World world = location.getWorld();
return world == null || !world.getWorldBorder().isInside(location);
}
}

View File

@ -0,0 +1,76 @@
package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class for dealing with and parsing materials
*/
public final class MaterialHelper {
private MaterialHelper() {
}
/**
* Loads the materials specified in the block whitelist
*/
public static @NotNull Set<Material> loadMaterialList(@NotNull List<?> materials) {
Set<Material> parsedMaterials = new HashSet<>();
for (Object value : materials) {
if (!(value instanceof String string)) {
continue;
}
// Try to parse a material tag first
if (parseMaterialTag(parsedMaterials, string)) {
continue;
}
// Try to parse a material name
Material matched = Material.matchMaterial(string);
if (matched != null) {
parsedMaterials.add(matched);
} else {
MiniGames.log(Level.WARNING, "Unable to parse: " + string);
}
}
return parsedMaterials;
}
/**
* Tries to parse the material tag in the specified material name
*
* @param targetSet <p>The set all parsed materials should be added to</p>
* @param materialName <p>The material name that might be a material tag</p>
* @return <p>True if a tag was found</p>
*/
private static boolean parseMaterialTag(@NotNull Set<Material> targetSet, @NotNull String materialName) {
Pattern pattern = Pattern.compile("^\\+([a-zA-Z_]+)");
Matcher matcher = pattern.matcher(materialName);
if (matcher.find()) {
// The material is a material tag
Tag<Material> tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(
matcher.group(1).toLowerCase()), Material.class);
if (tag != null) {
targetSet.addAll(tag.getValues());
} else {
MiniGames.log(Level.WARNING, "Unable to parse: " + materialName);
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,252 @@
package net.knarcraft.minigames.util;
import net.knarcraft.minigames.MiniGames;
import net.knarcraft.minigames.arena.ArenaGameMode;
import net.knarcraft.minigames.arena.ArenaRecordsRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArena;
import net.knarcraft.minigames.arena.parkour.ParkourArenaData;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGameMode;
import net.knarcraft.minigames.arena.parkour.ParkourArenaGroup;
import net.knarcraft.minigames.arena.parkour.ParkourArenaRecordsRegistry;
import net.knarcraft.minigames.arena.parkour.ParkourArenaStorageKey;
import net.knarcraft.minigames.container.SerializableMaterial;
import net.knarcraft.minigames.container.SerializableUUID;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import static net.knarcraft.minigames.util.ArenaStorageHelper.getArenaDataFile;
/**
* A helper class for saving and loading parkour arenas
*/
public final class ParkourArenaStorageHelper {
private ParkourArenaStorageHelper() {
}
private final static File dataFolder = MiniGames.getInstance().getDataFolder();
private final static String parkourArenasConfigurationSection = "parkourArenas";
private final static String parkourGroupsConfigurationSection = "parkourGroups";
private static final File parkourGroupFile = new File(dataFolder, "parkour_groups.yml");
private static final File parkourArenaFile = new File(dataFolder, "parkour_arenas.yml");
private static final File parkourArenaDataFolder = new File(dataFolder, "parkour_arena_data");
/**
* Saves the given parkour arena groups
*
* @param arenaGroups <p>The arena groups to save</p>
* @throws IOException <p>If unable to write to the file</p>
*/
public static void saveParkourArenaGroups(@NotNull Set<ParkourArenaGroup> arenaGroups) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
ConfigurationSection groupSection = configuration.createSection(parkourGroupsConfigurationSection);
for (ParkourArenaGroup arenaGroup : arenaGroups) {
groupSection.set(arenaGroup.getGroupId().toString(), arenaGroup);
}
configuration.save(parkourGroupFile);
}
/**
* Loads all existing parkour arena groups
*
* @return <p>The loaded arena groups</p>
*/
public static @NotNull Set<ParkourArenaGroup> loadParkourArenaGroups() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(parkourGroupFile);
ConfigurationSection groupSection = configuration.getConfigurationSection(parkourGroupsConfigurationSection);
//If no such section exists, it must be the case that there is no data to load
if (groupSection == null) {
return new HashSet<>();
}
Set<ParkourArenaGroup> arenaGroups = new HashSet<>();
for (String sectionName : groupSection.getKeys(false)) {
arenaGroups.add((ParkourArenaGroup) groupSection.get(sectionName));
}
return arenaGroups;
}
/**
* Saves the given arenas
*
* @param arenas <p>The arenas to save</p>
* @throws IOException <p>If unable to write to the file</p>
*/
public static void saveParkourArenas(@NotNull Map<UUID, ParkourArena> arenas) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
ConfigurationSection arenaSection = configuration.createSection(parkourArenasConfigurationSection);
for (ParkourArena arena : arenas.values()) {
//Note: While the arena name is used as the key, as the key has to be sanitized, the un-sanitized arena name
// must be stored as well
@NotNull ConfigurationSection configSection = arenaSection.createSection(arena.getArenaId().toString());
configSection.set(ParkourArenaStorageKey.ID.getKey(), new SerializableUUID(arena.getArenaId()));
configSection.set(ParkourArenaStorageKey.NAME.getKey(), arena.getArenaName());
configSection.set(ParkourArenaStorageKey.SPAWN_LOCATION.getKey(), arena.getSpawnLocation());
configSection.set(ParkourArenaStorageKey.EXIT_LOCATION.getKey(), arena.getExitLocation());
configSection.set(ParkourArenaStorageKey.WIN_BLOCK_TYPE.getKey(), new SerializableMaterial(arena.getWinBlockType()));
configSection.set(ParkourArenaStorageKey.WIN_LOCATION.getKey(), arena.getWinLocation());
configSection.set(ParkourArenaStorageKey.KILL_PLANE_BLOCKS.getKey(), arena.getKillPlaneBlockNames());
configSection.set(ParkourArenaStorageKey.CHECKPOINTS.getKey(), arena.getCheckpoints());
saveParkourArenaData(arena.getData());
}
configuration.save(parkourArenaFile);
}
/**
* Loads all arenas
*
* @return <p>The loaded arenas, or null if the arenas configuration section is missing.</p>
*/
public static @NotNull Map<UUID, ParkourArena> loadParkourArenas() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(parkourArenaFile);
ConfigurationSection arenaSection = configuration.getConfigurationSection(parkourArenasConfigurationSection);
//If no such section exists, it must be the case that there is no data to load
if (arenaSection == null) {
return new HashMap<>();
}
Map<UUID, ParkourArena> loadedArenas = new HashMap<>();
for (String sectionName : arenaSection.getKeys(false)) {
ConfigurationSection configurationSection = arenaSection.getConfigurationSection(sectionName);
//I'm not sure whether this could actually happen
if (configurationSection == null) {
continue;
}
ParkourArena arena = loadParkourArena(configurationSection);
if (arena != null) {
loadedArenas.put(arena.getArenaId(), arena);
}
}
return loadedArenas;
}
/**
* Loads an arena from the given configuration section
*
* @param configurationSection <p>The configuration section containing arena data</p>
* @return <p>The loaded arena, or null if invalid</p>
*/
@SuppressWarnings("unchecked")
private static @Nullable ParkourArena loadParkourArena(@NotNull ConfigurationSection configurationSection) {
UUID arenaId = ((SerializableUUID) configurationSection.get(ParkourArenaStorageKey.ID.getKey(),
new SerializableUUID(UUID.randomUUID()))).getRawValue();
String arenaName = configurationSection.getString(ParkourArenaStorageKey.NAME.getKey());
Location spawnLocation = (Location) configurationSection.get(ParkourArenaStorageKey.SPAWN_LOCATION.getKey());
Location exitLocation = (Location) configurationSection.get(ParkourArenaStorageKey.EXIT_LOCATION.getKey());
Location winLocation = (Location) configurationSection.get(ParkourArenaStorageKey.WIN_LOCATION.getKey());
SerializableMaterial winBlockType = (SerializableMaterial) configurationSection.get(
ParkourArenaStorageKey.WIN_BLOCK_TYPE.getKey());
List<?> killPlaneBlockNames = configurationSection.getList(ParkourArenaStorageKey.KILL_PLANE_BLOCKS.getKey());
List<Location> checkpoints = (List<Location>) configurationSection.get(ParkourArenaStorageKey.CHECKPOINTS.getKey());
// The arena name and spawn location must be present
if (arenaName == null || spawnLocation == null) {
MiniGames.log(Level.SEVERE, "Could not load the arena at configuration " +
"section " + configurationSection.getName() + ". Please check the parkour_arenas storage file for issues.");
return null;
}
// Fall back to the default win block
if (winBlockType == null) {
winBlockType = new SerializableMaterial(Material.EMERALD_BLOCK);
}
// Generate new, empty arena data if not available
ParkourArenaData arenaData = loadParkourArenaData(arenaId);
if (arenaData == null) {
MiniGames.log(Level.SEVERE, "Unable to load arena data for parkour arena" + arenaId);
arenaData = getEmptyParkourData(arenaId);
}
if (checkpoints == null) {
checkpoints = new ArrayList<>();
}
return new ParkourArena(arenaId, arenaName, spawnLocation, exitLocation, winBlockType.getRawValue(), winLocation,
(Set<String>) killPlaneBlockNames, checkpoints, arenaData, MiniGames.getInstance().getParkourArenaHandler());
}
/**
* Gets empty parkour data
*
* @param arenaId <p>The id to get parkour data for</p>
* @return <p>Empty parkour data</p>
*/
private static @NotNull ParkourArenaData getEmptyParkourData(@NotNull UUID arenaId) {
Map<ArenaGameMode, ArenaRecordsRegistry> recordRegistries = new HashMap<>();
Map<ArenaGameMode, Set<UUID>> playersCompleted = new HashMap<>();
for (ArenaGameMode arenaGameMode : ParkourArenaGameMode.values()) {
recordRegistries.put(arenaGameMode, new ParkourArenaRecordsRegistry(arenaId));
playersCompleted.put(arenaGameMode, new HashSet<>());
}
return new ParkourArenaData(arenaId, recordRegistries, playersCompleted);
}
/**
* Stores the given arena data to a file
*
* @param arenaData <p>The arena data to store</p>
*/
public static void saveParkourArenaData(@NotNull ParkourArenaData arenaData) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
configuration.set(ParkourArenaStorageKey.DATA.getKey(), arenaData);
configuration.save(getParkourArenaDataFile(arenaData.getArenaId()));
}
/**
* Loads arena data for the given arena id
*
* @param arenaId <p>The id of the arena to get data for</p>
* @return <p>The loaded arena data</p>
*/
private static @Nullable ParkourArenaData loadParkourArenaData(@NotNull UUID arenaId) {
File arenaDataFile = getParkourArenaDataFile(arenaId);
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(arenaDataFile);
return (ParkourArenaData) configuration.get(ParkourArenaStorageKey.DATA.getKey());
}
/**
* Removes data for the arena with the given id
*
* @param arenaId <p>The id of the arena to remove data for</p>
* @return <p>True if the data was successfully removed</p>
*/
public static boolean removeParkourArenaData(@NotNull UUID arenaId) {
return getParkourArenaDataFile(arenaId).delete();
}
/**
* Gets the file used to store the given arena id's data
*
* @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>
*/
private static @NotNull File getParkourArenaDataFile(@NotNull UUID arenaId) {
return getArenaDataFile(parkourArenaDataFolder, arenaId);
}
}

View File

@ -1,6 +1,6 @@
package net.knarcraft.dropper.util;
package net.knarcraft.minigames.util;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.minigames.MiniGames;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
@ -57,7 +57,7 @@ public final class PlayerTeleporter {
//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
if (!immediately) {
Bukkit.getScheduler().runTaskLater(Dropper.getInstance(), () -> player.setInvulnerable(false), 5);
Bukkit.getScheduler().runTaskLater(MiniGames.getInstance(), () -> player.setInvulnerable(false), 5);
} else {
player.setInvulnerable(false);
}

View File

@ -0,0 +1,85 @@
package net.knarcraft.minigames.util;
import net.knarcraft.minigames.container.SerializableContainer;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A converter for converting between normal and serializable classes
*/
public final class SerializableConverter {
private SerializableConverter() {
}
/**
* Converts the given collection into a collection of serializable objects
*
* @param values <p>The values to make serializable</p>
* @param targetCollection <p>The collection to store the converted objects to</p>
* @param target <p>An instance of the target serializable container</p>
* @param <T> <p>The type of the value to make serializable</p>
*/
public static <T> void makeSerializable(@NotNull Collection<T> values,
@NotNull Collection<SerializableContainer<T>> targetCollection,
@NotNull SerializableContainer<T> target) {
for (T item : values) {
targetCollection.add(target.getSerializable(item));
}
}
/**
* Converts the given collection of serializable containers into a collection of normal objects
*
* @param values <p>The values to convert to normal</p>
* @param targetCollection <p>The collection to store the converted objects to</p>
* @param <T> <p>The type of the value to convert to normal</p>
*/
public static <T> void getRawValue(@NotNull Collection<SerializableContainer<T>> values,
@NotNull Collection<T> targetCollection) {
for (SerializableContainer<T> item : values) {
targetCollection.add(item.getRawValue());
}
}
/**
* Converts the given collection into a collection of serializable objects
*
* @param values <p>The values to make serializable</p>
* @param targetMap <p>The map to store the converted objects to</p>
* @param target <p>An instance of the target serializable container</p>
* @param <T> <p>The type of the value to make serializable</p>
*/
public static <S, T> void makeSerializable(@NotNull Map<S, Set<T>> values,
@NotNull Map<S, Set<SerializableContainer<T>>> targetMap,
@NotNull SerializableContainer<T> target) {
for (Map.Entry<S, Set<T>> item : values.entrySet()) {
Set<SerializableContainer<T>> conversionCollection = new HashSet<>();
makeSerializable(item.getValue(), conversionCollection, target);
targetMap.put(item.getKey(), conversionCollection);
}
}
/**
* Converts the given collection of serializable containers into a collection of normal objects
*
* @param values <p>The values to convert to normal</p>
* @param targetMap <p>The map to store the converted objects to</p>
* @param <S> <p>The type of the map's key</p>
* @param <T> <p>The type of the value to convert to normal</p>
*/
public static <S, T> void getRawValue(@NotNull Map<S, Set<SerializableContainer<T>>> values,
@NotNull Map<S, Set<T>> targetMap) {
for (Map.Entry<S, Set<SerializableContainer<T>>> item : values.entrySet()) {
Set<T> conversionCollection = new HashSet<>();
getRawValue(item.getValue(), conversionCollection);
targetMap.put(item.getKey(), conversionCollection);
}
}
}

View File

@ -1,4 +1,4 @@
package net.knarcraft.dropper.util;
package net.knarcraft.minigames.util;
import org.jetbrains.annotations.NotNull;

Some files were not shown because too many files have changed in this diff Show More