mirror of
https://github.com/SunNetservers/MiniGames.git
synced 2025-07-06 16:14:45 +02:00
Compare commits
25 Commits
v0.0.3
...
v1.0.2-bet
Author | SHA1 | Date | |
---|---|---|---|
b1e86a928b | |||
4de5ae469b | |||
2f4d4ff4c6 | |||
50978d8baf | |||
3bbf41206c | |||
6a41664fef | |||
d1964e9d7b | |||
cd7d8eded0 | |||
b95cc294ab | |||
458dbc2beb | |||
efaca03434 | |||
58b5b422f0 | |||
483a0a16dc | |||
e1c4a6a97c | |||
5be6f0d00e | |||
f6a272b0c0 | |||
46e52812af | |||
fe016fd620 | |||
8e9b274fc0 | |||
2b9cfeebb1 | |||
096f23d468 | |||
3ebf5fa924 | |||
579c1ea0f9 | |||
d41154281b | |||
f852de7309 |
71
README.md
71
README.md
@ -19,21 +19,21 @@ To modify
|
||||
## Commands
|
||||
|
||||
| Command | Alias | Arguments | Description |
|
||||
|-----------------------------------------|----------|-----------------------------|-------------------------------------------------------------------------------------|
|
||||
|----------------------------------------|----------|-----------------------------|-------------------------------------------------------------------------------------|
|
||||
| /dropperList | /dlist | | Lists available dropper arenas. |
|
||||
| [/dropperJoin](#/dropperJoin) | /djoin | \<arena> \[mode] | Joins the selected arena. |
|
||||
| [/dropperJoin](#dropperjoin) | /djoin | \<arena> \[mode] | Joins the selected arena. |
|
||||
| /dropperLeave | /dleave | | Leaves the current dropper 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. |
|
||||
| [/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. |
|
||||
| [/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. |
|
||||
|
||||
## Command explanation
|
||||
### Command explanation
|
||||
|
||||
### /dropperJoin
|
||||
#### /dropperJoin
|
||||
|
||||
This command is used for joining a dropper arena.
|
||||
|
||||
@ -44,7 +44,7 @@ This command is used for joining a dropper arena.
|
||||
| arena | The name of the arena to join. |
|
||||
| mode | Additional challenge modes can be played after an arena has been cleared once. Available modes: inverted and random. |
|
||||
|
||||
### /dropperEdit
|
||||
#### /dropperEdit
|
||||
|
||||
This command allows editing the specified property for the specified dropper arena.
|
||||
|
||||
@ -67,7 +67,7 @@ These are all the options that can be changed for an arena.
|
||||
| horizontalVelocity | The horizontal velocity (technically fly speed) set for players in the arena. It must be between 0 and 1, and cannot be 0. Decimals are allowed. |
|
||||
| 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. |
|
||||
|
||||
### /dropperGroupSet
|
||||
#### /dropperGroupSet
|
||||
|
||||
This command is used to set the group of an arena
|
||||
|
||||
@ -78,7 +78,7 @@ will be used again if you specify the "potato" group for another arena. You use
|
||||
its group. If the group has no arenas, it will be automatically removed. If the arena already is in a group, it will be
|
||||
moved to the new group.
|
||||
|
||||
### /dropperGroupSwap
|
||||
#### /dropperGroupSwap
|
||||
|
||||
This command is used for changing the order of arenas within a group.
|
||||
|
||||
@ -98,3 +98,54 @@ You could use `/droppergroupswap Sea Savanna` to change the order to:
|
||||
2. Savanna
|
||||
3. Nether
|
||||
4. Sea
|
||||
|
||||
## Configuration options
|
||||
|
||||
| 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. |
|
||||
|
||||
#### blockWhitelist default:
|
||||
|
||||
- WATER
|
||||
- LAVA
|
||||
- +WALL_SIGNS
|
||||
- +STANDING_SIGNS
|
||||
- STRUCTURE_VOID
|
||||
- WALL_TORCH
|
||||
- SOUL_WALL_TORCH
|
||||
- REDSTONE_WALL_TORCH
|
||||
- +BANNERS
|
||||
- +BUTTONS
|
||||
- +CORALS
|
||||
- +WALL_CORALS
|
||||
|
||||
## 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%`
|
||||
|
||||
| Variable | Values | Description |
|
||||
|----------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| dropper_record | | Denotes that it's a placeholder for a dropper record. Must be present as-is. |
|
||||
| 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". |
|
10
pom.xml
10
pom.xml
@ -58,6 +58,10 @@
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>placeholderapi</id>
|
||||
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@ -79,5 +83,11 @@
|
||||
<version>5.9.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.10.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,11 +1,14 @@
|
||||
package net.knarcraft.dropper;
|
||||
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
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;
|
||||
@ -19,13 +22,15 @@ 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.config.DropperConfiguration;
|
||||
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.property.ArenaGameMode;
|
||||
import net.knarcraft.dropper.placeholder.DropperRecordExpansion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
@ -45,8 +50,10 @@ import java.util.logging.Level;
|
||||
public final class Dropper extends JavaPlugin {
|
||||
|
||||
private static Dropper instance;
|
||||
private DropperConfiguration configuration;
|
||||
private DropperArenaHandler arenaHandler;
|
||||
private DropperArenaPlayerRegistry playerRegistry;
|
||||
private DropperRecordExpansion dropperRecordExpansion;
|
||||
|
||||
/**
|
||||
* Gets an instance of this plugin
|
||||
@ -75,6 +82,25 @@ public final class Dropper extends JavaPlugin {
|
||||
return this.playerRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dropper configuration
|
||||
*
|
||||
* @return <p>The dropper configuration</p>
|
||||
*/
|
||||
public DropperConfiguration getDropperConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Dropper.getInstance().getLogger().log(level, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads all configurations and data from disk
|
||||
*/
|
||||
@ -82,6 +108,13 @@ public final class Dropper extends JavaPlugin {
|
||||
// Load all arenas again
|
||||
this.arenaHandler.loadArenas();
|
||||
this.arenaHandler.loadGroups();
|
||||
|
||||
// Reload configuration
|
||||
this.reloadConfig();
|
||||
this.configuration.load(this.getConfig());
|
||||
|
||||
// Clear record caches
|
||||
this.dropperRecordExpansion.clearCaches();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -94,12 +127,20 @@ public final class Dropper extends JavaPlugin {
|
||||
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.saveDefaultConfig();
|
||||
getConfig().options().copyDefaults(true);
|
||||
saveConfig();
|
||||
reloadConfig();
|
||||
this.configuration = new DropperConfiguration(this.getConfig());
|
||||
this.configuration.load();
|
||||
this.playerRegistry = new DropperArenaPlayerRegistry();
|
||||
this.arenaHandler = new DropperArenaHandler();
|
||||
this.arenaHandler.loadArenas();
|
||||
@ -107,7 +148,7 @@ public final class Dropper extends JavaPlugin {
|
||||
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
pluginManager.registerEvents(new DamageListener(), this);
|
||||
pluginManager.registerEvents(new MoveListener(), this);
|
||||
pluginManager.registerEvents(new MoveListener(this.configuration), this);
|
||||
pluginManager.registerEvents(new PlayerLeaveListener(), this);
|
||||
pluginManager.registerEvents(new CommandListener(), this);
|
||||
|
||||
@ -116,11 +157,18 @@ public final class Dropper extends JavaPlugin {
|
||||
registerCommand("dropperList", new ListArenaCommand(), null);
|
||||
registerCommand("dropperJoin", new JoinArenaCommand(), new JoinArenaTabCompleter());
|
||||
registerCommand("dropperLeave", new LeaveArenaCommand(), null);
|
||||
registerCommand("dropperEdit", new EditArenaCommand(), new EditArenaTabCompleter());
|
||||
registerCommand("dropperEdit", new EditArenaCommand(this.configuration), 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) {
|
||||
this.dropperRecordExpansion = new DropperRecordExpansion(this);
|
||||
if (!this.dropperRecordExpansion.register()) {
|
||||
log(Level.WARNING, "Unable to register PlaceholderAPI expansion!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,7 +198,7 @@ public final class Dropper extends JavaPlugin {
|
||||
command.setTabCompleter(tabCompleter);
|
||||
}
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to register the command " + commandName);
|
||||
log(Level.SEVERE, "Unable to register the command " + commandName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.knarcraft.dropper.property;
|
||||
package net.knarcraft.dropper.arena;
|
||||
|
||||
import net.knarcraft.dropper.arena.DropperArena;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.knarcraft.dropper.property;
|
||||
package net.knarcraft.dropper.arena;
|
||||
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
@ -1,4 +1,4 @@
|
||||
package net.knarcraft.dropper.property;
|
||||
package net.knarcraft.dropper.arena;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.knarcraft.dropper.arena;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.property.ArenaGameMode;
|
||||
import net.knarcraft.dropper.config.DropperConfiguration;
|
||||
import net.knarcraft.dropper.util.StringSanitizer;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
@ -61,7 +61,7 @@ public class DropperArena {
|
||||
*/
|
||||
private final DropperArenaData dropperArenaData;
|
||||
|
||||
private static DropperArenaHandler dropperArenaHandler = null;
|
||||
private final DropperArenaHandler dropperArenaHandler;
|
||||
|
||||
/**
|
||||
* Instantiates a new dropper arena
|
||||
@ -74,10 +74,12 @@ public class DropperArena {
|
||||
* @param playerHorizontalVelocity <p>The velocity to use for players' horizontal velocity (-1 to 1)</p>
|
||||
* @param winBlockType <p>The material of the block players have to hit to win this dropper arena</p>
|
||||
* @param dropperArenaData <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 DropperArena(@NotNull UUID arenaId, @NotNull String arenaName, @NotNull Location spawnLocation,
|
||||
@Nullable Location exitLocation, double playerVerticalVelocity, float playerHorizontalVelocity,
|
||||
@NotNull Material winBlockType, @NotNull DropperArenaData dropperArenaData) {
|
||||
@NotNull Material winBlockType, @NotNull DropperArenaData dropperArenaData,
|
||||
@NotNull DropperArenaHandler arenaHandler) {
|
||||
this.arenaId = arenaId;
|
||||
this.arenaName = arenaName;
|
||||
this.spawnLocation = spawnLocation;
|
||||
@ -86,10 +88,7 @@ public class DropperArena {
|
||||
this.playerHorizontalVelocity = playerHorizontalVelocity;
|
||||
this.winBlockType = winBlockType;
|
||||
this.dropperArenaData = dropperArenaData;
|
||||
|
||||
if (dropperArenaHandler == null) {
|
||||
dropperArenaHandler = Dropper.getInstance().getArenaHandler();
|
||||
}
|
||||
this.dropperArenaHandler = arenaHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,14 +99,17 @@ public class DropperArena {
|
||||
*
|
||||
* @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 DropperArena(@NotNull String arenaName, @NotNull Location spawnLocation) {
|
||||
public DropperArena(@NotNull String arenaName, @NotNull Location spawnLocation,
|
||||
@NotNull DropperArenaHandler arenaHandler) {
|
||||
DropperConfiguration configuration = Dropper.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()) {
|
||||
@ -116,6 +118,7 @@ public class DropperArena {
|
||||
|
||||
this.dropperArenaData = new DropperArenaData(this.arenaId, recordRegistries, new HashMap<>());
|
||||
this.winBlockType = Material.WATER;
|
||||
this.dropperArenaHandler = arenaHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@ 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;
|
||||
@ -103,6 +102,12 @@ public record DropperArenaData(@NotNull UUID arenaId,
|
||||
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()) {
|
||||
@ -111,6 +116,10 @@ public record DropperArenaData(@NotNull UUID arenaId,
|
||||
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);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package net.knarcraft.dropper.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 org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -130,7 +129,7 @@ public class DropperArenaGroup implements ConfigurationSerializable {
|
||||
DropperArena dropperArena = arenaHandler.getArena(anArenaId);
|
||||
if (dropperArena == 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() +
|
||||
Dropper.log(Level.WARNING, "The dropper group " + this.getGroupName() +
|
||||
" contains the arena id " + anArenaId + " which is not a valid arena id!");
|
||||
continue;
|
||||
}
|
||||
@ -160,14 +159,14 @@ public class DropperArenaGroup implements ConfigurationSerializable {
|
||||
|
||||
for (UUID anArenaId : this.getArenas()) {
|
||||
// If the target arena is reached, allow, as all previous arenas must have been cleared
|
||||
if (arenaId == anArenaId) {
|
||||
if (arenaId.equals(anArenaId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DropperArena dropperArena = arenaHandler.getArena(anArenaId);
|
||||
if (dropperArena == 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" +
|
||||
Dropper.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;
|
||||
}
|
||||
|
@ -158,10 +158,15 @@ public class DropperArenaHandler {
|
||||
* @param arena <p>The arena to remove</p>
|
||||
*/
|
||||
public void removeArena(@NotNull DropperArena arena) {
|
||||
UUID arenaId = arena.getArenaId();
|
||||
Dropper.getInstance().getPlayerRegistry().removeForArena(arena);
|
||||
this.arenas.remove(arena.getArenaId());
|
||||
this.arenas.remove(arenaId);
|
||||
this.arenaNameLookup.remove(arena.getArenaNameSanitized());
|
||||
this.arenaGroups.remove(arena.getArenaId());
|
||||
this.arenaGroups.remove(arenaId);
|
||||
if (!ArenaStorageHelper.removeArenaData(arenaId)) {
|
||||
Dropper.log(Level.WARNING, "Unable to remove dropper arena data file " + arenaId + ".yml. " +
|
||||
"You must remove it manually!");
|
||||
}
|
||||
this.saveArenas();
|
||||
}
|
||||
|
||||
@ -174,8 +179,8 @@ public class DropperArenaHandler {
|
||||
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());
|
||||
Dropper.log(Level.SEVERE, "Unable to save arena data! Data loss can occur!");
|
||||
Dropper.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,9 +191,9 @@ public class DropperArenaHandler {
|
||||
try {
|
||||
ArenaStorageHelper.saveDropperArenaGroups(new HashSet<>(this.arenaGroups.values()));
|
||||
} catch (IOException e) {
|
||||
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to save current arena groups! " +
|
||||
Dropper.log(Level.SEVERE, "Unable to save current arena groups! " +
|
||||
"Data loss can occur!");
|
||||
Dropper.getInstance().getLogger().log(Level.SEVERE, e.getMessage());
|
||||
Dropper.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,9 +218,9 @@ public class DropperArenaHandler {
|
||||
try {
|
||||
ArenaStorageHelper.saveArenas(this.arenas);
|
||||
} catch (IOException e) {
|
||||
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to save current arenas! " +
|
||||
Dropper.log(Level.SEVERE, "Unable to save current arenas! " +
|
||||
"Data loss can occur!");
|
||||
Dropper.getInstance().getLogger().log(Level.SEVERE, e.getMessage());
|
||||
Dropper.log(Level.SEVERE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,24 @@
|
||||
package net.knarcraft.dropper.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 org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A registry keeping track of all records
|
||||
@ -17,16 +26,16 @@ import java.util.stream.Stream;
|
||||
public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
|
||||
private final UUID arenaId;
|
||||
private final @NotNull Map<UUID, Number> leastDeaths;
|
||||
private final @NotNull Map<UUID, Number> shortestTimeMilliSeconds;
|
||||
private final @NotNull Set<IntegerRecord> leastDeaths;
|
||||
private final @NotNull Set<LongRecord> shortestTimeMilliSeconds;
|
||||
|
||||
/**
|
||||
* Instantiates a new empty records registry
|
||||
*/
|
||||
public DropperArenaRecordsRegistry(@NotNull UUID arenaId) {
|
||||
this.arenaId = arenaId;
|
||||
this.leastDeaths = new HashMap<>();
|
||||
this.shortestTimeMilliSeconds = new HashMap<>();
|
||||
this.leastDeaths = new HashSet<>();
|
||||
this.shortestTimeMilliSeconds = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,11 +44,11 @@ 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 Map<UUID, Integer> leastDeaths,
|
||||
@NotNull Map<UUID, Long> shortestTimeMilliSeconds) {
|
||||
private DropperArenaRecordsRegistry(@NotNull UUID arenaId, @NotNull Set<IntegerRecord> leastDeaths,
|
||||
@NotNull Set<LongRecord> shortestTimeMilliSeconds) {
|
||||
this.arenaId = arenaId;
|
||||
this.leastDeaths = new HashMap<>(leastDeaths);
|
||||
this.shortestTimeMilliSeconds = new HashMap<>(shortestTimeMilliSeconds);
|
||||
this.leastDeaths = new HashSet<>(leastDeaths);
|
||||
this.shortestTimeMilliSeconds = new HashSet<>(shortestTimeMilliSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,12 +56,8 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
*
|
||||
* @return <p>Existing death records</p>
|
||||
*/
|
||||
public Map<UUID, Integer> getLeastDeathsRecords() {
|
||||
Map<UUID, Integer> leastDeathRecords = new HashMap<>();
|
||||
for (Map.Entry<UUID, Number> entry : this.leastDeaths.entrySet()) {
|
||||
leastDeathRecords.put(entry.getKey(), entry.getValue().intValue());
|
||||
}
|
||||
return leastDeathRecords;
|
||||
public Set<SummableArenaRecord<Integer>> getLeastDeathsRecords() {
|
||||
return new HashSet<>(this.leastDeaths);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,12 +65,8 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
*
|
||||
* @return <p>Existing time records</p>
|
||||
*/
|
||||
public Map<UUID, Long> getShortestTimeMilliSecondsRecords() {
|
||||
Map<UUID, Long> leastTimeRecords = new HashMap<>();
|
||||
for (Map.Entry<UUID, Number> entry : this.shortestTimeMilliSeconds.entrySet()) {
|
||||
leastTimeRecords.put(entry.getKey(), entry.getValue().longValue());
|
||||
}
|
||||
return leastTimeRecords;
|
||||
public Set<SummableArenaRecord<Long>> getShortestTimeMilliSecondsRecords() {
|
||||
return new HashSet<>(this.shortestTimeMilliSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +77,12 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
* @return <p>The result explaining what type of record was achieved</p>
|
||||
*/
|
||||
public @NotNull RecordResult registerDeathRecord(@NotNull UUID playerId, int deaths) {
|
||||
return registerRecord(leastDeaths, playerId, deaths);
|
||||
Consumer<Integer> consumer = (value) -> {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +93,12 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
* @return <p>The result explaining what type of record was achieved</p>
|
||||
*/
|
||||
public @NotNull RecordResult registerTimeRecord(@NotNull UUID playerId, long milliseconds) {
|
||||
return registerRecord(shortestTimeMilliSeconds, playerId, milliseconds);
|
||||
Consumer<Long> consumer = (value) -> {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,29 +112,33 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
* Registers a new record if applicable
|
||||
*
|
||||
* @param existingRecords <p>The map of existing records to use</p>
|
||||
* @param recordSetter <p>The consumer used to set a new record</p>
|
||||
* @param playerId <p>The id of the player that potentially achieved a record</p>
|
||||
* @param amount <p>The amount of whatever the player achieved</p>
|
||||
* @return <p>The result of the player's record attempt</p>
|
||||
*/
|
||||
private @NotNull RecordResult registerRecord(@NotNull Map<UUID, Number> existingRecords, @NotNull UUID playerId,
|
||||
Number amount) {
|
||||
private <T extends Comparable<T>> @NotNull RecordResult registerRecord(@NotNull Set<ArenaRecord<T>> existingRecords,
|
||||
@NotNull Consumer<T> recordSetter,
|
||||
@NotNull UUID playerId, T amount) {
|
||||
RecordResult result;
|
||||
Stream<Map.Entry<UUID, Number>> records = existingRecords.entrySet().stream();
|
||||
long amountLong = amount.longValue();
|
||||
|
||||
if (records.allMatch((entry) -> amountLong < entry.getValue().longValue())) {
|
||||
if (existingRecords.stream().allMatch((entry) -> amount.compareTo(entry.getRecord()) < 0)) {
|
||||
// If the given value is less than all other values, that's a world record!
|
||||
result = RecordResult.WORLD_RECORD;
|
||||
existingRecords.put(playerId, amount);
|
||||
recordSetter.accept(amount);
|
||||
save();
|
||||
} else if (existingRecords.containsKey(playerId) && amountLong < existingRecords.get(playerId).longValue()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ArenaRecord<T> playerRecord = getRecord(existingRecords, playerId);
|
||||
if (playerRecord != null && amount.compareTo(playerRecord.getRecord()) < 0) {
|
||||
// If the given value is less than the player's previous value, that's a personal best!
|
||||
result = RecordResult.PERSONAL_BEST;
|
||||
existingRecords.put(playerId, amount);
|
||||
recordSetter.accept(amount);
|
||||
save();
|
||||
} else {
|
||||
// Make sure to save the record if this is the user's first attempt
|
||||
if (!existingRecords.containsKey(playerId)) {
|
||||
if (playerRecord == null) {
|
||||
recordSetter.accept(amount);
|
||||
save();
|
||||
}
|
||||
result = RecordResult.NONE;
|
||||
@ -132,23 +147,32 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the record stored for the given player
|
||||
*
|
||||
* @param existingRecords <p>The existing records to look through</p>
|
||||
* @param playerId <p>The id of the player to look for</p>
|
||||
* @param <T> <p>The type of the stored record</p>
|
||||
* @return <p>The record, or null if not found</p>
|
||||
*/
|
||||
private <T extends Comparable<T>> @Nullable ArenaRecord<T> getRecord(@NotNull Set<ArenaRecord<T>> existingRecords,
|
||||
@NotNull UUID playerId) {
|
||||
AtomicReference<ArenaRecord<T>> record = new AtomicReference<>();
|
||||
existingRecords.forEach((item) -> {
|
||||
if (item.getUserId().equals(playerId)) {
|
||||
record.set(item);
|
||||
}
|
||||
});
|
||||
return record.get();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("arenaId", new SerializableUUID(this.arenaId));
|
||||
|
||||
Map<SerializableUUID, Number> leastDeaths = new HashMap<>();
|
||||
for (Map.Entry<UUID, Number> entry : this.leastDeaths.entrySet()) {
|
||||
leastDeaths.put(new SerializableUUID(entry.getKey()), entry.getValue());
|
||||
}
|
||||
data.put("leastDeaths", leastDeaths);
|
||||
|
||||
Map<SerializableUUID, Number> shortestTimeMilliSeconds = new HashMap<>();
|
||||
for (Map.Entry<UUID, Number> entry : this.shortestTimeMilliSeconds.entrySet()) {
|
||||
shortestTimeMilliSeconds.put(new SerializableUUID(entry.getKey()), entry.getValue());
|
||||
}
|
||||
data.put("shortestTime", shortestTimeMilliSeconds);
|
||||
data.put("leastDeaths", this.leastDeaths);
|
||||
data.put("shortestTime", this.shortestTimeMilliSeconds);
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -161,20 +185,13 @@ public class DropperArenaRecordsRegistry implements ConfigurationSerializable {
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static DropperArenaRecordsRegistry deserialize(Map<String, Object> data) {
|
||||
UUID arenaId = ((SerializableUUID) data.get("arenaId")).uuid();
|
||||
Map<SerializableUUID, Integer> leastDeathsData =
|
||||
(Map<SerializableUUID, Integer>) data.getOrDefault("leastDeaths", new HashMap<>());
|
||||
Map<UUID, Integer> leastDeaths = new HashMap<>();
|
||||
for (Map.Entry<SerializableUUID, Integer> entry : leastDeathsData.entrySet()) {
|
||||
leastDeaths.put(entry.getKey().uuid(), entry.getValue());
|
||||
}
|
||||
|
||||
Map<SerializableUUID, Number> shortestTimeMillisecondsData =
|
||||
(Map<SerializableUUID, Number>) data.getOrDefault("shortestTime", new HashMap<>());
|
||||
Map<UUID, Long> shortestTimeMilliseconds = new HashMap<>();
|
||||
for (Map.Entry<SerializableUUID, Number> entry : shortestTimeMillisecondsData.entrySet()) {
|
||||
shortestTimeMilliseconds.put(entry.getKey().uuid(), entry.getValue().longValue());
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.knarcraft.dropper.arena;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.property.ArenaGameMode;
|
||||
import net.knarcraft.dropper.config.DropperConfiguration;
|
||||
import net.knarcraft.dropper.property.RecordResult;
|
||||
import net.knarcraft.dropper.util.PlayerTeleporter;
|
||||
import org.bukkit.Location;
|
||||
@ -37,7 +37,10 @@ public class DropperArenaSession {
|
||||
this.deaths = 0;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
this.entryState = new PlayerEntryState(player, gameMode);
|
||||
DropperConfiguration configuration = Dropper.getInstance().getDropperConfiguration();
|
||||
boolean makeInvisible = configuration.makePlayersInvisible();
|
||||
boolean disableCollision = configuration.disableHitCollision();
|
||||
this.entryState = new PlayerEntryState(player, gameMode, makeInvisible, disableCollision);
|
||||
// Make the player fly to improve mobility in the air
|
||||
this.entryState.setArenaState(this.arena.getPlayerHorizontalVelocity());
|
||||
}
|
||||
@ -68,7 +71,12 @@ public class DropperArenaSession {
|
||||
stopSession();
|
||||
|
||||
// Check for, and display, records
|
||||
Dropper dropper = Dropper.getInstance();
|
||||
boolean ignore = dropper.getDropperConfiguration().ignoreRecordsUntilGroupBeatenOnce();
|
||||
DropperArenaGroup group = dropper.getArenaHandler().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)) {
|
||||
@ -101,8 +109,8 @@ public class DropperArenaSession {
|
||||
// Remove this session for game sessions to stop listeners from fiddling more with the player
|
||||
boolean removedSession = Dropper.getInstance().getPlayerRegistry().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.");
|
||||
Dropper.log(Level.SEVERE, "Unable to remove dropper arena session for " + player.getName() + ". " +
|
||||
"This will have unintended consequences.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -19,6 +20,9 @@ public class PlayerEntryState {
|
||||
private final boolean originalAllowFlight;
|
||||
private final boolean originalInvulnerable;
|
||||
private final boolean originalIsSwimming;
|
||||
private final boolean originalCollideAble;
|
||||
private final boolean makePlayerInvisible;
|
||||
private final boolean disableHitCollision;
|
||||
private final ArenaGameMode arenaGameMode;
|
||||
|
||||
/**
|
||||
@ -26,7 +30,8 @@ public class PlayerEntryState {
|
||||
*
|
||||
* @param player <p>The player whose state should be stored</p>
|
||||
*/
|
||||
public PlayerEntryState(@NotNull Player player, @NotNull ArenaGameMode arenaGameMode) {
|
||||
public PlayerEntryState(@NotNull Player player, @NotNull ArenaGameMode arenaGameMode, boolean makePlayerInvisible,
|
||||
boolean disableHitCollision) {
|
||||
this.player = player;
|
||||
this.entryLocation = player.getLocation().clone();
|
||||
this.originalFlySpeed = player.getFlySpeed();
|
||||
@ -36,6 +41,9 @@ public class PlayerEntryState {
|
||||
this.originalInvulnerable = player.isInvulnerable();
|
||||
this.originalIsSwimming = player.isSwimming();
|
||||
this.arenaGameMode = arenaGameMode;
|
||||
this.originalCollideAble = player.isCollidable();
|
||||
this.makePlayerInvisible = makePlayerInvisible;
|
||||
this.disableHitCollision = disableHitCollision;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,6 +56,13 @@ public class PlayerEntryState {
|
||||
this.player.setFlying(true);
|
||||
this.player.setGameMode(GameMode.ADVENTURE);
|
||||
this.player.setSwimming(false);
|
||||
if (this.disableHitCollision) {
|
||||
this.player.setCollidable(false);
|
||||
}
|
||||
if (this.makePlayerInvisible) {
|
||||
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) {
|
||||
@ -67,6 +82,12 @@ public class PlayerEntryState {
|
||||
this.player.setFlySpeed(this.originalFlySpeed);
|
||||
this.player.setInvulnerable(this.originalInvulnerable);
|
||||
this.player.setSwimming(this.originalIsSwimming);
|
||||
if (this.disableHitCollision) {
|
||||
this.player.setCollidable(this.originalCollideAble);
|
||||
}
|
||||
if (this.makePlayerInvisible) {
|
||||
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,76 @@
|
||||
package net.knarcraft.dropper.arena.record;
|
||||
|
||||
import net.knarcraft.dropper.container.SerializableUUID;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A record stored for an arena
|
||||
*/
|
||||
public abstract class ArenaRecord<K extends Comparable<K>> implements Comparable<ArenaRecord<K>>, ConfigurationSerializable {
|
||||
|
||||
private final UUID userId;
|
||||
private final K record;
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public ArenaRecord(UUID userId, K record) {
|
||||
this.userId = userId;
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the user this record belongs to
|
||||
*
|
||||
* @return <p>The record's achiever</p>
|
||||
*/
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the stored record
|
||||
*
|
||||
* @return <p>The record value</p>
|
||||
*/
|
||||
public K getRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof ArenaRecord<?> && userId.equals(((ArenaRecord<?>) other).userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ArenaRecord<K> other) {
|
||||
return record.compareTo(other.record);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("userId", new SerializableUUID(getUserId()));
|
||||
data.put("record", record);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(userId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return userId + ": " + record;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.knarcraft.dropper.arena.record;
|
||||
|
||||
import net.knarcraft.dropper.container.SerializableUUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A record storing an integer
|
||||
*/
|
||||
public class IntegerRecord extends SummableArenaRecord<Integer> {
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public IntegerRecord(UUID userId, Integer record) {
|
||||
super(userId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SummableArenaRecord<Integer> sum(Integer value) {
|
||||
return new IntegerRecord(this.getUserId(), this.getRecord() + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof IntegerRecord && this.getUserId().equals(((IntegerRecord) other).getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the saved arena record
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized data</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static IntegerRecord deserialize(@NotNull Map<String, Object> data) {
|
||||
return new IntegerRecord(((SerializableUUID) data.get("userId")).uuid(), (Integer) data.get("record"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.knarcraft.dropper.arena.record;
|
||||
|
||||
import net.knarcraft.dropper.container.SerializableUUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A record storing a Long
|
||||
*/
|
||||
public class LongRecord extends SummableArenaRecord<Long> {
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public LongRecord(UUID userId, Long record) {
|
||||
super(userId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof LongRecord && this.getUserId().equals(((LongRecord) other).getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SummableArenaRecord<Long> sum(Long value) {
|
||||
return new LongRecord(this.getUserId(), this.getRecord() + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the saved arena record
|
||||
*
|
||||
* @param data <p>The data to deserialize</p>
|
||||
* @return <p>The deserialized data</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static LongRecord deserialize(@NotNull Map<String, Object> data) {
|
||||
return new LongRecord(((SerializableUUID) data.get("userId")).uuid(), ((Number) data.get("record")).longValue());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.knarcraft.dropper.arena.record;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A type of arena record which can be summed together
|
||||
*
|
||||
* @param <K> <p>The type of the stored value</p>
|
||||
*/
|
||||
public abstract class SummableArenaRecord<K extends Comparable<K>> extends ArenaRecord<K> {
|
||||
|
||||
/**
|
||||
* @param userId <p>The id of the player that achieved the record</p>
|
||||
* @param record <p>The record achieved</p>
|
||||
*/
|
||||
public SummableArenaRecord(UUID userId, K record) {
|
||||
super(userId, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summable record with the resulting sum
|
||||
*
|
||||
* @param value <p>The value to add to the existing value</p>
|
||||
* @return <p>A record with the sum of this record and the given value</p>
|
||||
*/
|
||||
public abstract SummableArenaRecord<K> sum(K value);
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package net.knarcraft.dropper.command;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.DropperArena;
|
||||
import net.knarcraft.dropper.arena.DropperArenaHandler;
|
||||
import net.knarcraft.dropper.util.StringSanitizer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@ -35,14 +36,16 @@ public class CreateArenaCommand implements CommandExecutor {
|
||||
return false;
|
||||
}
|
||||
|
||||
DropperArena existingArena = Dropper.getInstance().getArenaHandler().getArena(arenaName);
|
||||
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
|
||||
|
||||
DropperArena existingArena = arenaHandler.getArena(arenaName);
|
||||
if (existingArena != null) {
|
||||
commandSender.sendMessage("There already exists a dropper arena with that name!");
|
||||
return false;
|
||||
}
|
||||
|
||||
DropperArena arena = new DropperArena(arenaName, player.getLocation());
|
||||
Dropper.getInstance().getArenaHandler().addArena(arena);
|
||||
DropperArena arena = new DropperArena(arenaName, player.getLocation(), arenaHandler);
|
||||
arenaHandler.addArena(arena);
|
||||
commandSender.sendMessage("The arena was successfully created!");
|
||||
return true;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package net.knarcraft.dropper.command;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaEditableProperty;
|
||||
import net.knarcraft.dropper.arena.DropperArena;
|
||||
import net.knarcraft.dropper.property.ArenaEditableProperty;
|
||||
import net.knarcraft.dropper.config.DropperConfiguration;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
@ -16,6 +17,17 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public class EditArenaCommand implements CommandExecutor {
|
||||
|
||||
private final DropperConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Instantiates a new edit arena command
|
||||
*
|
||||
* @param configuration <p>The configuration to use</p>
|
||||
*/
|
||||
public EditArenaCommand(DropperConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
|
||||
@NotNull String[] arguments) {
|
||||
@ -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
|
||||
|
@ -1,11 +1,12 @@
|
||||
package net.knarcraft.dropper.command;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
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.config.DropperConfiguration;
|
||||
import net.knarcraft.dropper.util.PlayerTeleporter;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@ -77,7 +78,8 @@ public class JoinArenaCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
// Make sure the player has beaten the arena once in normal mode before playing another mode
|
||||
if (gameMode != ArenaGameMode.DEFAULT &&
|
||||
if (Dropper.getInstance().getDropperConfiguration().mustDoNormalModeFirst() &&
|
||||
gameMode != ArenaGameMode.DEFAULT &&
|
||||
specifiedArena.getData().hasNotCompleted(ArenaGameMode.DEFAULT, player)) {
|
||||
player.sendMessage("You must complete this arena in normal mode first!");
|
||||
return false;
|
||||
@ -113,16 +115,17 @@ public class JoinArenaCommand implements CommandExecutor {
|
||||
*/
|
||||
private boolean doGroupChecks(@NotNull DropperArena dropperArena, @NotNull DropperArenaGroup arenaGroup,
|
||||
@NotNull ArenaGameMode arenaGameMode, @NotNull Player player) {
|
||||
DropperConfiguration configuration = Dropper.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 != ArenaGameMode.DEFAULT &&
|
||||
!arenaGroup.hasBeatenAll(ArenaGameMode.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.canPlay(arenaGameMode, player, dropperArena.getArenaId())) {
|
||||
player.sendMessage("You have not yet beaten the previous arena!");
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,298 @@
|
||||
package net.knarcraft.dropper.config;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The configuration keeping track of the player's current configuration
|
||||
*/
|
||||
public class DropperConfiguration {
|
||||
|
||||
private FileConfiguration configuration;
|
||||
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 double liquidHitBoxDepth;
|
||||
private double solidHitBoxDistance;
|
||||
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) {
|
||||
this.configuration = 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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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.liquidHitBoxDepth = configuration.getDouble(rootNode + "liquidHitBoxDepth", -0.8);
|
||||
this.solidHitBoxDistance = configuration.getDouble(rootNode + "solidHitBoxDistance", 0.2);
|
||||
this.blockSprinting = configuration.getBoolean(rootNode + "blockSprinting", true);
|
||||
this.blockSneaking = configuration.getBoolean(rootNode + "blockSneaking", true);
|
||||
sanitizeValues();
|
||||
|
||||
loadBlockWhitelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes configuration values to ensure they are within expected bounds
|
||||
*/
|
||||
private void sanitizeValues() {
|
||||
if (this.liquidHitBoxDepth <= -1 || this.liquidHitBoxDepth > 0) {
|
||||
this.liquidHitBoxDepth = -0.8;
|
||||
}
|
||||
|
||||
if (this.solidHitBoxDistance <= 0 || this.solidHitBoxDistance > 1) {
|
||||
this.solidHitBoxDistance = 0.2;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the materials specified in the block whitelist
|
||||
*/
|
||||
private void loadBlockWhitelist() {
|
||||
this.blockWhitelist = new HashSet<>();
|
||||
List<?> blockWhitelist = configuration.getList(rootNode + "blockWhitelist", new ArrayList<>());
|
||||
for (Object value : blockWhitelist) {
|
||||
if (!(value instanceof String string)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to parse a material tag first
|
||||
if (parseMaterialTag(string)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to parse a material name
|
||||
Material matched = Material.matchMaterial(string);
|
||||
if (matched != null) {
|
||||
this.blockWhitelist.add(matched);
|
||||
} else {
|
||||
Dropper.log(Level.WARNING, "Unable to parse: " + string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse the material tag in the specified material name
|
||||
*
|
||||
* @param materialName <p>The material name that might be a material tag</p>
|
||||
* @return <p>True if a tag was found</p>
|
||||
*/
|
||||
private boolean parseMaterialTag(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) {
|
||||
this.blockWhitelist.addAll(tag.getValues());
|
||||
} else {
|
||||
Dropper.log(Level.WARNING, "Unable to parse: " + materialName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@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" + "Liquid hit box depth: " + liquidHitBoxDepth +
|
||||
"\n" + "Solid hit box distance: " + solidHitBoxDistance +
|
||||
"\n" + "Block whitelist: ");
|
||||
for (Material material : blockWhitelist) {
|
||||
builder.append("\n - ").append(material.name());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
package net.knarcraft.dropper.listener;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
|
||||
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.EntityCombustEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
|
||||
/**
|
||||
@ -36,4 +38,18 @@ public class DamageListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerCombustion(EntityCombustEvent event) {
|
||||
if (event.getEntityType() != EntityType.PLAYER) {
|
||||
return;
|
||||
}
|
||||
|
||||
DropperArenaPlayerRegistry registry = Dropper.getInstance().getPlayerRegistry();
|
||||
DropperArenaSession arenaSession = registry.getArenaSession(event.getEntity().getUniqueId());
|
||||
if (arenaSession != null) {
|
||||
// Cancel combustion for any player in an arena
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package net.knarcraft.dropper.listener;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
import net.knarcraft.dropper.arena.DropperArenaPlayerRegistry;
|
||||
import net.knarcraft.dropper.arena.DropperArenaSession;
|
||||
import net.knarcraft.dropper.property.ArenaGameMode;
|
||||
import net.knarcraft.dropper.config.DropperConfiguration;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
@ -23,6 +24,17 @@ import java.util.Set;
|
||||
*/
|
||||
public class MoveListener implements Listener {
|
||||
|
||||
private final DropperConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Instantiates a new move listener
|
||||
*
|
||||
* @param configuration <p>The configuration to use</p>
|
||||
*/
|
||||
public MoveListener(DropperConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerMove(PlayerMoveEvent event) {
|
||||
// Ignore if no actual movement is happening
|
||||
@ -38,7 +50,9 @@ public class MoveListener implements Listener {
|
||||
}
|
||||
|
||||
// Prevent the player from flying upwards while in flight mode
|
||||
if (event.getFrom().getY() < event.getTo().getY()) {
|
||||
if (event.getFrom().getY() < event.getTo().getY() ||
|
||||
(configuration.blockSneaking() && event.getPlayer().isSneaking()) ||
|
||||
(configuration.blockSprinting() && event.getPlayer().isSprinting())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
@ -64,12 +78,8 @@ public class MoveListener implements Listener {
|
||||
* @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;
|
||||
double liquidDepth = configuration.getLiquidHitBoxDepth();
|
||||
double solidDepth = configuration.getSolidHitBoxDistance();
|
||||
|
||||
// Check if the player enters water
|
||||
Material winBlockType = arenaSession.getArena().getWinBlockType();
|
||||
@ -83,9 +93,10 @@ public class MoveListener implements Listener {
|
||||
}
|
||||
|
||||
// Check if the player is about to hit a non-air and non-liquid block
|
||||
Set<Material> whitelisted = configuration.getBlockWhitelist();
|
||||
for (Block block : getBlocksBeneathLocation(toLocation, solidDepth)) {
|
||||
if (!block.getType().isAir() && block.getType() != Material.STRUCTURE_VOID &&
|
||||
block.getType() != Material.WATER && block.getType() != Material.LAVA) {
|
||||
Material blockType = block.getType();
|
||||
if (!blockType.isAir() && !whitelisted.contains(blockType)) {
|
||||
arenaSession.triggerLoss();
|
||||
return true;
|
||||
}
|
||||
@ -116,10 +127,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
|
||||
@ -137,7 +149,7 @@ public class MoveListener implements Listener {
|
||||
}
|
||||
Player player = session.getPlayer();
|
||||
float horizontalVelocity = session.getArena().getPlayerHorizontalVelocity();
|
||||
float secondsBetweenToggle = 7;
|
||||
float secondsBetweenToggle = configuration.getRandomlyInvertedTimer();
|
||||
int seconds = Calendar.getInstance().get(Calendar.SECOND);
|
||||
|
||||
/*
|
||||
|
@ -32,7 +32,7 @@ public class PlayerLeaveListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
Dropper.getInstance().getLogger().log(Level.WARNING, "Found player " + player.getUniqueId() +
|
||||
Dropper.log(Level.WARNING, "Found player " + player.getUniqueId() +
|
||||
" leaving in the middle of a session!");
|
||||
leftSessions.put(player.getUniqueId(), arenaSession);
|
||||
}
|
||||
@ -42,10 +42,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!");
|
||||
Dropper.log(Level.WARNING, "Found un-exited dropper session!");
|
||||
Bukkit.getScheduler().runTaskLater(Dropper.getInstance(), () -> {
|
||||
leftSessions.get(playerId).triggerQuit(false);
|
||||
Dropper.getInstance().getLogger().log(Level.WARNING, "Triggered a quit!");
|
||||
Dropper.log(Level.WARNING, "Triggered a quit!");
|
||||
leftSessions.remove(playerId);
|
||||
}, 80);
|
||||
}
|
||||
|
@ -0,0 +1,333 @@
|
||||
package net.knarcraft.dropper.placeholder;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
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.SelectionType;
|
||||
import net.knarcraft.dropper.property.RecordType;
|
||||
import net.knarcraft.dropper.util.DropperGroupRecordHelper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A placeholder expansion for dropper record placeholders
|
||||
*/
|
||||
public class DropperRecordExpansion extends PlaceholderExpansion {
|
||||
|
||||
private final Dropper plugin;
|
||||
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;
|
||||
this.groupRecordDeathsCache = new HashMap<>();
|
||||
this.groupRecordTimeCache = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "dropper";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return "EpicKnarvik97";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "1.0.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer player, String parameters) {
|
||||
String[] parts = parameters.split("_");
|
||||
// Record is used as the prefix for all record placeholders in case more placeholder types are added
|
||||
if (parts.length < 7 || !parts[0].equals("record")) {
|
||||
return parameters;
|
||||
}
|
||||
RecordType recordType = RecordType.getFromString(parts[1]);
|
||||
ArenaGameMode gameMode = ArenaGameMode.matchGamemode(parts[2]);
|
||||
SelectionType selectionType = SelectionType.getFromString(parts[3]);
|
||||
String identifier = parts[4];
|
||||
int recordNumber = Integer.parseInt(parts[5]) - 1;
|
||||
InfoType infoType = InfoType.getFromString(parts[6]);
|
||||
|
||||
if (recordType == null || infoType == null) {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
String info = null;
|
||||
DropperArenaHandler arenaHandler = plugin.getArenaHandler();
|
||||
if (selectionType == SelectionType.GROUP) {
|
||||
info = getGroupRecord(arenaHandler, identifier, gameMode, recordType, recordNumber, infoType);
|
||||
} else if (selectionType == SelectionType.ARENA) {
|
||||
info = getArenaRecord(arenaHandler, identifier, gameMode, recordType, recordNumber, infoType);
|
||||
}
|
||||
|
||||
return Objects.requireNonNullElse(info, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all record caches
|
||||
*/
|
||||
public void clearCaches() {
|
||||
this.groupRecordDeathsCache.clear();
|
||||
this.groupRecordTimeCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a piece of record information from a dropper 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>
|
||||
* @param gameMode <p>The game-mode to get a record for</p>
|
||||
* @param recordType <p>The type of record to get</p>
|
||||
* @param recordNumber <p>The placing of the record to get (1st place, 2nd place, etc.)</p>
|
||||
* @param infoType <p>The type of info (player, value, combined) to get</p>
|
||||
* @return <p>The selected information about the record, or null if not found</p>
|
||||
*/
|
||||
private @Nullable String getGroupRecord(@NotNull DropperArenaHandler 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;
|
||||
try {
|
||||
group = arenaHandler.getGroup(UUID.fromString(identifier));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
group = arenaHandler.getGroup(identifier);
|
||||
}
|
||||
if (group == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArenaRecord<?> record;
|
||||
if (recordType == RecordType.DEATHS) {
|
||||
record = getGroupDeathRecord(group, gameMode, recordNumber);
|
||||
} else {
|
||||
record = getGroupTimeRecord(group, gameMode, recordNumber);
|
||||
}
|
||||
|
||||
// If a record number is not found, leave it blank, so it looks neat
|
||||
if (record == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return getRecordData(infoType, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a time record from a group, using the cache if possible
|
||||
*
|
||||
* @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>
|
||||
* @return <p>The record, or null if not found</p>
|
||||
*/
|
||||
private @Nullable ArenaRecord<?> getGroupTimeRecord(@NotNull DropperArenaGroup group,
|
||||
@NotNull ArenaGameMode gameMode, int recordNumber) {
|
||||
return getCachedGroupRecord(group, gameMode, RecordType.TIME, recordNumber, groupRecordTimeCache,
|
||||
() -> DropperGroupRecordHelper.getCombinedTime(group, gameMode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a death record from a group, using the cache if possible
|
||||
*
|
||||
* @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>
|
||||
* @return <p>The record, or null if not found</p>
|
||||
*/
|
||||
private @Nullable ArenaRecord<?> getGroupDeathRecord(@NotNull DropperArenaGroup group,
|
||||
@NotNull ArenaGameMode gameMode, int recordNumber) {
|
||||
return getCachedGroupRecord(group, gameMode, RecordType.DEATHS, recordNumber, groupRecordDeathsCache,
|
||||
() -> DropperGroupRecordHelper.getCombinedDeaths(group, gameMode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a group record, fetching from a cache if possible
|
||||
*
|
||||
* @param group <p>The group to get the record for</p>
|
||||
* @param gameMode <p>The game-mode to get the record for</p>
|
||||
* @param recordType <p>The type of record to get</p>
|
||||
* @param recordNumber <p>The placing of the record to get (1st place, 2nd place, etc.)</p>
|
||||
* @param caches <p>The caches to use for looking for and saving the record</p>
|
||||
* @param recordProvider <p>The provider of records if the cache cannot provide the record</p>
|
||||
* @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,
|
||||
@NotNull ArenaGameMode gameMode,
|
||||
@NotNull RecordType recordType,
|
||||
int recordNumber,
|
||||
@NotNull Map<UUID, Set<GroupRecordCache<K>>> caches,
|
||||
@NotNull Supplier<Set<ArenaRecord<K>>> recordProvider) {
|
||||
UUID groupId = group.getGroupId();
|
||||
if (!caches.containsKey(groupId)) {
|
||||
caches.put(groupId, new HashSet<>());
|
||||
}
|
||||
|
||||
Set<GroupRecordCache<K>> existingCaches = caches.get(groupId);
|
||||
Set<GroupRecordCache<K>> expired = new HashSet<>();
|
||||
Set<ArenaRecord<K>> cachedRecords = null;
|
||||
|
||||
for (GroupRecordCache<K> cache : existingCaches) {
|
||||
// Expire caches after 30 seconds
|
||||
if (System.currentTimeMillis() - cache.createdTime() > 30000) {
|
||||
expired.add(cache);
|
||||
}
|
||||
// If of the correct type, and not expired, use the cache
|
||||
if (cache.gameMode() == gameMode && cache.recordType() == recordType) {
|
||||
cachedRecords = cache.records();
|
||||
break;
|
||||
}
|
||||
}
|
||||
existingCaches.removeAll(expired);
|
||||
|
||||
// If not found, generate and cache the specified record
|
||||
if (cachedRecords == null) {
|
||||
cachedRecords = recordProvider.get();
|
||||
existingCaches.add(new GroupRecordCache<>(gameMode, recordType, cachedRecords, System.currentTimeMillis()));
|
||||
}
|
||||
return getRecord(cachedRecords, recordNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a piece of record information from a dropper arena
|
||||
*
|
||||
* @param arenaHandler <p>The arena handler to get the arena from</p>
|
||||
* @param identifier <p>The identifier (name/uuid) selecting the arena</p>
|
||||
* @param gameMode <p>The game-mode to get a record for</p>
|
||||
* @param recordType <p>The type of record to get</p>
|
||||
* @param recordNumber <p>The placing of the record to get (1st place, 2nd place, etc.)</p>
|
||||
* @param infoType <p>The type of info (player, value, combined) to get</p>
|
||||
* @return <p>The selected information about the record, or null if not found</p>
|
||||
*/
|
||||
private @Nullable String getArenaRecord(@NotNull DropperArenaHandler 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;
|
||||
try {
|
||||
arena = arenaHandler.getArena(UUID.fromString(identifier));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
arena = arenaHandler.getArena(identifier);
|
||||
}
|
||||
if (arena == null) {
|
||||
return null;
|
||||
}
|
||||
@NotNull Map<ArenaGameMode, DropperArenaRecordsRegistry> registries = arena.getData().recordRegistries();
|
||||
DropperArenaRecordsRegistry recordsRegistry = registries.get(gameMode);
|
||||
|
||||
ArenaRecord<?> record = getRecord(recordsRegistry, recordType, recordNumber);
|
||||
|
||||
// If a record number is not found, leave it blank, so it looks neat
|
||||
if (record == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return getRecordData(infoType, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified record
|
||||
*
|
||||
* @param recordsRegistry <p>The records registry to get the record from</p>
|
||||
* @param recordType <p>The type of record to get</p>
|
||||
* @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,
|
||||
@NotNull RecordType recordType, int recordNumber) {
|
||||
return switch (recordType) {
|
||||
case TIME -> getRecord(new HashSet<>(recordsRegistry.getShortestTimeMilliSecondsRecords()), recordNumber);
|
||||
case DEATHS -> getRecord(new HashSet<>(recordsRegistry.getLeastDeathsRecords()), recordNumber);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the record at the given index
|
||||
*
|
||||
* @param records <p>The records to search through</p>
|
||||
* @param index <p>The index of the record to get</p>
|
||||
* @param <K> <p>The type of record in the record list</p>
|
||||
* @return <p>The record, or null if index is out of bounds</p>
|
||||
*/
|
||||
private <K extends Comparable<K>> @Nullable ArenaRecord<K> getRecord(Set<ArenaRecord<K>> records, int index) {
|
||||
List<ArenaRecord<K>> sorted = getSortedRecords(records);
|
||||
if (index < sorted.size() && index >= 0) {
|
||||
return sorted.get(index);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a piece of data from a record as a string
|
||||
*
|
||||
* @param infoType <p>The type of info to get data for</p>
|
||||
* @param arenaRecord <p>The record to get the data from</p>
|
||||
* @return <p>The requested data as a string, or null</p>
|
||||
*/
|
||||
private String getRecordData(@NotNull InfoType infoType, @NotNull ArenaRecord<?> arenaRecord) {
|
||||
return switch (infoType) {
|
||||
case PLAYER -> getPlayerName(arenaRecord.getUserId());
|
||||
case VALUE -> arenaRecord.getRecord().toString();
|
||||
case COMBINED -> getPlayerName(arenaRecord.getUserId()) + ": " + arenaRecord.getRecord().toString();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the given set of records as a sorted list
|
||||
*
|
||||
* @param recordSet <p>The set of records to sort</p>
|
||||
* @param <K> <p>The type of the records</p>
|
||||
* @return <p>The sorted records</p>
|
||||
*/
|
||||
private <K extends Comparable<K>> @NotNull List<ArenaRecord<K>> getSortedRecords(
|
||||
@NotNull Set<ArenaRecord<K>> recordSet) {
|
||||
List<ArenaRecord<K>> records = new ArrayList<>(recordSet);
|
||||
Collections.sort(records);
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a player, given the player's UUID
|
||||
*
|
||||
* @param playerId <p>The id of the player to get the name for</p>
|
||||
* @return <p>The name of the player, or a string representation of the UUID if not found</p>
|
||||
*/
|
||||
private String getPlayerName(@NotNull UUID playerId) {
|
||||
return Bukkit.getOfflinePlayer(playerId).getName();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.knarcraft.dropper.placeholder;
|
||||
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
import net.knarcraft.dropper.arena.record.ArenaRecord;
|
||||
import net.knarcraft.dropper.property.RecordType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A record for keeping track of records for a dropper group
|
||||
*
|
||||
* @param gameMode <p>The game-mode this cache is storing records for</p>
|
||||
* @param recordType <p>The type of record stored</p>
|
||||
* @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,
|
||||
@NotNull Set<ArenaRecord<K>> records,
|
||||
@NotNull Long createdTime) {
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package net.knarcraft.dropper.placeholder.parsing;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The type of information returned by a placeholder
|
||||
*/
|
||||
public enum InfoType {
|
||||
|
||||
/**
|
||||
* The player that achieved the record
|
||||
*/
|
||||
PLAYER,
|
||||
|
||||
/**
|
||||
* The value of the record, whatever it is
|
||||
*/
|
||||
VALUE,
|
||||
|
||||
/**
|
||||
* A combined PLAYER: VALUE
|
||||
*/
|
||||
COMBINED,
|
||||
;
|
||||
|
||||
/**
|
||||
* Gets the info type specified in the given string
|
||||
*
|
||||
* @param type <p>The string specifying the info type</p>
|
||||
* @return <p>The info type, or null if not found</p>
|
||||
*/
|
||||
public static @Nullable InfoType getFromString(@NotNull String type) {
|
||||
for (InfoType infoType : InfoType.values()) {
|
||||
if (infoType.name().equalsIgnoreCase(type)) {
|
||||
return infoType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.knarcraft.dropper.placeholder.parsing;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A type of selection performed by a placeholder
|
||||
*/
|
||||
public enum SelectionType {
|
||||
|
||||
/**
|
||||
* The identifier is trying to select a group
|
||||
*/
|
||||
GROUP,
|
||||
|
||||
/**
|
||||
* The identifier is trying to select an arena
|
||||
*/
|
||||
ARENA,
|
||||
;
|
||||
|
||||
/**
|
||||
* Gets the selection type specified in the given string
|
||||
*
|
||||
* @param type <p>The string specifying the selection type</p>
|
||||
* @return <p>The selection type, or null if not found</p>
|
||||
*/
|
||||
public static @Nullable SelectionType getFromString(@NotNull String type) {
|
||||
for (SelectionType selectionType : SelectionType.values()) {
|
||||
if (selectionType.name().equalsIgnoreCase(type)) {
|
||||
return selectionType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
37
src/main/java/net/knarcraft/dropper/property/RecordType.java
Normal file
37
src/main/java/net/knarcraft/dropper/property/RecordType.java
Normal file
@ -0,0 +1,37 @@
|
||||
package net.knarcraft.dropper.property;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A type of record a player can achieve
|
||||
*/
|
||||
public enum RecordType {
|
||||
|
||||
/**
|
||||
* A least-deaths record
|
||||
*/
|
||||
DEATHS,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
TIME,
|
||||
;
|
||||
|
||||
/**
|
||||
* Gets the record type specified in the given string
|
||||
*
|
||||
* @param type <p>The string specifying the record type</p>
|
||||
* @return <p>The record type, or null if not found</p>
|
||||
*/
|
||||
public static @Nullable RecordType getFromString(@NotNull String type) {
|
||||
for (RecordType recordType : RecordType.values()) {
|
||||
if (recordType.name().equalsIgnoreCase(type)) {
|
||||
return recordType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
package net.knarcraft.dropper.util;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
import net.knarcraft.dropper.arena.ArenaStorageKey;
|
||||
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 org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
@ -154,7 +154,7 @@ public final class ArenaStorageHelper {
|
||||
ArenaStorageKey.WIN_BLOCK_TYPE.getKey());
|
||||
|
||||
if (arenaName == null || spawnLocation == null) {
|
||||
Dropper.getInstance().getLogger().log(Level.SEVERE, "Could not load the arena at configuration " +
|
||||
Dropper.log(Level.SEVERE, "Could not load the arena at configuration " +
|
||||
"section " + configurationSection.getName() + ". Please check the arenas storage file for issues.");
|
||||
return null;
|
||||
}
|
||||
@ -164,7 +164,7 @@ public final class ArenaStorageHelper {
|
||||
|
||||
DropperArenaData arenaData = loadArenaData(arenaId);
|
||||
if (arenaData == null) {
|
||||
Dropper.getInstance().getLogger().log(Level.SEVERE, "Unable to load arena data for " + arenaId);
|
||||
Dropper.log(Level.SEVERE, "Unable to load arena data for " + arenaId);
|
||||
|
||||
Map<ArenaGameMode, DropperArenaRecordsRegistry> recordRegistries = new HashMap<>();
|
||||
for (ArenaGameMode arenaGameMode : ArenaGameMode.values()) {
|
||||
@ -174,7 +174,7 @@ public final class ArenaStorageHelper {
|
||||
}
|
||||
|
||||
return new DropperArena(arenaId, arenaName, spawnLocation, exitLocation, verticalVelocity, horizontalVelocity,
|
||||
winBlockType.material(), arenaData);
|
||||
winBlockType.material(), arenaData, Dropper.getInstance().getArenaHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,6 +201,16 @@ public final class ArenaStorageHelper {
|
||||
return (DropperArenaData) configuration.get(ArenaStorageKey.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 removeArenaData(@NotNull UUID arenaId) {
|
||||
return getArenaDataFile(arenaId).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file used to store the given arena id's data
|
||||
*
|
||||
@ -210,7 +220,7 @@ public final class ArenaStorageHelper {
|
||||
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");
|
||||
Dropper.log(Level.SEVERE, "Unable to create the arena data directories");
|
||||
}
|
||||
return arenaDataFile;
|
||||
}
|
||||
|
@ -0,0 +1,171 @@
|
||||
package net.knarcraft.dropper.util;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaGameMode;
|
||||
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 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 java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* A helper class for getting combined record data for a dropper group
|
||||
*/
|
||||
public final class DropperGroupRecordHelper {
|
||||
|
||||
private DropperGroupRecordHelper() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the combined least-death records for the given group and game-mode
|
||||
*
|
||||
* @param group <p>The group to get records from</p>
|
||||
* @param gameMode <p>The game-mode to get records for</p>
|
||||
* @return <p>The combined death records</p>
|
||||
*/
|
||||
public static @NotNull Set<ArenaRecord<Integer>> getCombinedDeaths(@NotNull DropperArenaGroup group,
|
||||
@NotNull ArenaGameMode gameMode) {
|
||||
Map<UUID, SummableArenaRecord<Integer>> records = new HashMap<>();
|
||||
@NotNull BiFunction<DropperArena, ArenaGameMode, Set<SummableArenaRecord<Integer>>> recordSupplier =
|
||||
(arena, aGameMode) -> arena.getData().recordRegistries().get(gameMode).getLeastDeathsRecords();
|
||||
|
||||
return getCombined(group, gameMode, records, recordSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the combined least-time records for the given group and game-mode
|
||||
*
|
||||
* @param group <p>The group to get records from</p>
|
||||
* @param gameMode <p>The game-mode to get records for</p>
|
||||
* @return <p>The combined least-time records</p>
|
||||
*/
|
||||
public static @NotNull Set<ArenaRecord<Long>> getCombinedTime(@NotNull DropperArenaGroup group,
|
||||
@NotNull ArenaGameMode gameMode) {
|
||||
Map<UUID, SummableArenaRecord<Long>> records = new HashMap<>();
|
||||
@NotNull BiFunction<DropperArena, ArenaGameMode, Set<SummableArenaRecord<Long>>> recordSupplier =
|
||||
(arena, aGameMode) -> arena.getData().recordRegistries().get(gameMode).getShortestTimeMilliSecondsRecords();
|
||||
|
||||
return getCombined(group, gameMode, records, recordSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the combined records for a group and game-mode
|
||||
*
|
||||
* @param group <p>The group to get combined records for</p>
|
||||
* @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 <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,
|
||||
@NotNull ArenaGameMode gameMode,
|
||||
@NotNull Map<UUID,
|
||||
SummableArenaRecord<K>> records,
|
||||
@NotNull BiFunction<DropperArena,
|
||||
ArenaGameMode,
|
||||
Set<SummableArenaRecord<K>>> recordSupplier) {
|
||||
DropperArenaHandler arenaHandler = Dropper.getInstance().getArenaHandler();
|
||||
|
||||
// Get all arenas in the group
|
||||
Set<DropperArena> arenas = getArenas(arenaHandler, group);
|
||||
|
||||
// Calculate the combined records
|
||||
Map<UUID, Integer> recordsFound = new HashMap<>();
|
||||
combineRecords(arenas, gameMode, records, recordsFound, recordSupplier);
|
||||
|
||||
// Filter out any players that haven't played through all arenas
|
||||
filterRecords(records, recordsFound, arenas.size());
|
||||
|
||||
return new HashSet<>(records.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters away any records that belong to users who haven't set records for all arenas in the group
|
||||
*
|
||||
* @param records <p>The records to filter</p>
|
||||
* @param recordsFound <p>The map of how many records have been registered for each user</p>
|
||||
* @param arenas <p>The number of arenas in the group</p>
|
||||
* @param <K> <p>The type of the given records</p>
|
||||
*/
|
||||
private static <K extends Comparable<K>> void filterRecords(@NotNull Map<UUID, SummableArenaRecord<K>> records,
|
||||
@NotNull Map<UUID, Integer> recordsFound, int arenas) {
|
||||
for (UUID userId : recordsFound.keySet()) {
|
||||
if (recordsFound.get(userId) != arenas) {
|
||||
records.remove(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all arenas in the given group
|
||||
*
|
||||
* @param arenaHandler <p>The arena handler to get arenas from</p>
|
||||
* @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) {
|
||||
// Get all arenas in the group
|
||||
Set<DropperArena> arenas = new HashSet<>();
|
||||
for (UUID arenaId : group.getArenas()) {
|
||||
DropperArena arena = arenaHandler.getArena(arenaId);
|
||||
if (arena != null) {
|
||||
arenas.add(arena);
|
||||
}
|
||||
}
|
||||
return arenas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines arena records
|
||||
*
|
||||
* @param arenas <p>The arenas whose records should be combined</p>
|
||||
* @param gameMode <p>The game-mode to combine records for</p>
|
||||
* @param combinedRecords <p>The map to store the combined records to</p>
|
||||
* @param recordsFound <p>The map used to store the number of records registered for each player</p>
|
||||
* @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,
|
||||
@NotNull ArenaGameMode gameMode,
|
||||
@NotNull Map<UUID,
|
||||
SummableArenaRecord<K>> combinedRecords,
|
||||
@NotNull Map<UUID, Integer> recordsFound,
|
||||
@NotNull BiFunction<DropperArena, ArenaGameMode,
|
||||
Set<SummableArenaRecord<K>>> recordSupplier) {
|
||||
for (DropperArena 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) {
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
UUID userId = value.getUserId();
|
||||
|
||||
// Bump the number of records found for the user
|
||||
if (!recordsFound.containsKey(userId)) {
|
||||
recordsFound.put(userId, 0);
|
||||
}
|
||||
recordsFound.put(userId, recordsFound.get(userId) + 1);
|
||||
|
||||
// Put the value, or the sum with the existing value, into combined records
|
||||
if (!combinedRecords.containsKey(userId)) {
|
||||
combinedRecords.put(value.getUserId(), value);
|
||||
} else {
|
||||
combinedRecords.put(userId, combinedRecords.get(userId).sum(value.getRecord()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package net.knarcraft.dropper.util;
|
||||
|
||||
import net.knarcraft.dropper.Dropper;
|
||||
import net.knarcraft.dropper.arena.ArenaEditableProperty;
|
||||
import net.knarcraft.dropper.arena.DropperArena;
|
||||
import net.knarcraft.dropper.property.ArenaEditableProperty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
59
src/main/resources/config.yml
Normal file
59
src/main/resources/config.yml
Normal file
@ -0,0 +1,59 @@
|
||||
# Configuration values for droppers
|
||||
dropper:
|
||||
# Whether to block using the shift key to drop faster than the intended drop speed
|
||||
blockSneaking: true
|
||||
|
||||
# Whether to block using the sprint key for slightly improved air speed
|
||||
blockSprinting: true
|
||||
|
||||
# The vertical velocity used as default for all arenas. Must be greater than 0. 3.92 is the max speed of a falling
|
||||
# player.
|
||||
verticalVelocity: 1.0
|
||||
|
||||
# The horizontal velocity used as default for all arenas (technically fly-speed). Must be between 0 (exclusive) and 1
|
||||
# (inclusive).
|
||||
horizontalVelocity: 1.0
|
||||
|
||||
# The number of seconds before the randomly inverted game-mode switches between normal and inverted movement (0, 3600]
|
||||
randomlyInvertedTimer: 7
|
||||
|
||||
# Whether grouped dropper arenas must be played in the correct sequence
|
||||
mustDoGroupedInSequence: true
|
||||
|
||||
# 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.
|
||||
ignoreRecordsUntilGroupBeatenOnce: false
|
||||
|
||||
# Whether a player must do the normal/default game-mode before playing any other game-modes
|
||||
mustDoNormalModeFirst: true
|
||||
|
||||
# Whether players should be made invisible while playing in a dropper arena
|
||||
makePlayersInvisible: false
|
||||
|
||||
# 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.
|
||||
disableHitCollision: true
|
||||
|
||||
# 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.
|
||||
liquidHitBoxDepth: -0.8
|
||||
|
||||
# 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.
|
||||
solidHitBoxDistance: 0.2
|
||||
|
||||
# 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.
|
||||
blockWhitelist:
|
||||
- WATER
|
||||
- LAVA
|
||||
- +WALL_SIGNS
|
||||
- +STANDING_SIGNS
|
||||
- STRUCTURE_VOID
|
||||
- WALL_TORCH
|
||||
- SOUL_WALL_TORCH
|
||||
- REDSTONE_WALL_TORCH
|
||||
- +BANNERS
|
||||
- +BUTTONS
|
||||
- +CORALS
|
||||
- +WALL_CORALS
|
@ -3,6 +3,8 @@ version: '${project.version}'
|
||||
main: net.knarcraft.dropper.Dropper
|
||||
api-version: 1.19
|
||||
description: A plugin for dropper mini-games
|
||||
softdepend:
|
||||
- PlaceholderAPI
|
||||
|
||||
# Note to self: Aliases must be lowercase!
|
||||
commands:
|
||||
|
Reference in New Issue
Block a user