8 Commits

Author SHA1 Message Date
b1e86a928b Adds a config options section to the README 2023-04-11 20:26:08 +02:00
4de5ae469b Fixes a few problems
Makes sure to actually load the configuration when starting the plugin
Makes sure all numeric configuration values are within expected bounds
Adds improved descriptions of configuration values' bounds
Removes the option to not override the vertical speed
Adds options for whether sneaking and sprinting should be blocked while in an arena
Adds more materials to the default config's whitelist
Fixes reloading not properly loading the new config
Fixes config options not loading because the root node was missing
Prevents players combusting while in an arena
2023-04-11 20:04:04 +02:00
2f4d4ff4c6 Fixes a few problems
Makes sure to actually load the configuration when starting the plugin
Makes sure all numeric configuration values are within expected bounds
Adds improved descriptions of configuration values' bounds
2023-04-11 13:55:46 +02:00
50978d8baf Implements several configuration options #15 2023-04-11 13:25:45 +02:00
3bbf41206c Makes sure arena data is deleted with the arena 2023-04-09 18:22:45 +02:00
6a41664fef Adds some missing comments and annotations 2023-04-07 20:33:03 +02:00
d1964e9d7b Caches group records for 30 seconds 2023-04-07 20:23:46 +02:00
cd7d8eded0 Fixes a bug causing duplicate records to be stored 2023-04-07 18:51:07 +02:00
27 changed files with 716 additions and 89 deletions

View File

@ -21,15 +21,15 @@ To modify
| 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
@ -99,6 +99,39 @@ You could use `/droppergroupswap Sea Savanna` to change the order to:
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

View File

@ -1,5 +1,6 @@
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;
@ -21,6 +22,7 @@ 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;
@ -28,7 +30,6 @@ import net.knarcraft.dropper.listener.DamageListener;
import net.knarcraft.dropper.listener.MoveListener;
import net.knarcraft.dropper.listener.PlayerLeaveListener;
import net.knarcraft.dropper.placeholder.DropperRecordExpansion;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
@ -49,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
@ -79,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
*/
@ -86,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
@ -106,6 +135,12 @@ public final class Dropper extends JavaPlugin {
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();
@ -113,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);
@ -122,15 +157,16 @@ 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) {
if (!new DropperRecordExpansion(this).register()) {
getLogger().log(Level.WARNING, "Unable to register PlaceholderAPI expansion!");
this.dropperRecordExpansion = new DropperRecordExpansion(this);
if (!this.dropperRecordExpansion.register()) {
log(Level.WARNING, "Unable to register PlaceholderAPI expansion!");
}
}
}
@ -162,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);
}
}

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -1,6 +1,7 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.Dropper;
import net.knarcraft.dropper.config.DropperConfiguration;
import net.knarcraft.dropper.util.StringSanitizer;
import org.bukkit.Location;
import org.bukkit.Material;
@ -102,12 +103,13 @@ public class DropperArena {
*/
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()) {

View File

@ -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;

View File

@ -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;
}
@ -167,7 +166,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, 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;
}

View File

@ -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());
}
}

View File

@ -77,7 +77,10 @@ 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) {
Consumer<Integer> consumer = (value) -> leastDeaths.add(new IntegerRecord(playerId, value));
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);
}
@ -90,7 +93,10 @@ 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) {
Consumer<Long> consumer = (value) -> shortestTimeMilliSeconds.add(new LongRecord(playerId, value));
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);
}

View File

@ -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
registerRecord();
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.");
}
}

View File

@ -1,6 +1,5 @@
package net.knarcraft.dropper.arena;
import net.knarcraft.dropper.property.ArenaGameMode;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@ -22,6 +21,8 @@ public class PlayerEntryState {
private final boolean originalInvulnerable;
private final boolean originalIsSwimming;
private final boolean originalCollideAble;
private final boolean makePlayerInvisible;
private final boolean disableHitCollision;
private final ArenaGameMode arenaGameMode;
/**
@ -29,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();
@ -40,6 +42,8 @@ public class PlayerEntryState {
this.originalIsSwimming = player.isSwimming();
this.arenaGameMode = arenaGameMode;
this.originalCollideAble = player.isCollidable();
this.makePlayerInvisible = makePlayerInvisible;
this.disableHitCollision = disableHitCollision;
}
/**
@ -52,8 +56,13 @@ public class PlayerEntryState {
this.player.setFlying(true);
this.player.setGameMode(GameMode.ADVENTURE);
this.player.setSwimming(false);
this.player.setCollidable(false);
this.player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, PotionEffect.INFINITE_DURATION, 3));
if (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) {
@ -73,8 +82,12 @@ public class PlayerEntryState {
this.player.setFlySpeed(this.originalFlySpeed);
this.player.setInvulnerable(this.originalInvulnerable);
this.player.setSwimming(this.originalIsSwimming);
this.player.setCollidable(this.originalCollideAble);
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
if (this.disableHitCollision) {
this.player.setCollidable(this.originalCollideAble);
}
if (this.makePlayerInvisible) {
this.player.removePotionEffect(PotionEffectType.INVISIBILITY);
}
}
/**

View File

@ -24,6 +24,11 @@ public class IntegerRecord extends SummableArenaRecord<Integer> {
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
*

View File

@ -19,6 +19,11 @@ public class LongRecord extends SummableArenaRecord<Long> {
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);

View File

@ -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

View File

@ -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)) {
player.sendMessage("You have not yet beaten all arenas in this group!");
return false;
}
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;
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -1,12 +1,12 @@
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.Tag;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -24,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
@ -37,9 +48,11 @@ public class MoveListener implements Listener {
if (arenaSession == null) {
return;
}
// Prevent the player from flying upwards while in flight mode
if (event.getFrom().getY() < event.getTo().getY()) {
if (event.getFrom().getY() < event.getTo().getY() ||
(configuration.blockSneaking() && event.getPlayer().isSneaking()) ||
(configuration.blockSprinting() && event.getPlayer().isSprinting())) {
event.setCancelled(true);
return;
}
@ -65,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();
@ -84,10 +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)) {
Material blockType = block.getType();
if (!blockType.isAir() && blockType != Material.STRUCTURE_VOID && blockType != Material.WATER &&
blockType != Material.LAVA && !Tag.WALL_SIGNS.isTagged(blockType)) {
if (!blockType.isAir() && !whitelisted.contains(blockType)) {
arenaSession.triggerLoss();
return true;
}
@ -118,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
@ -139,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);
/*

View File

@ -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);
}

View File

@ -2,15 +2,15 @@ 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.RecordType;
import net.knarcraft.dropper.placeholder.parsing.SelectionType;
import net.knarcraft.dropper.property.ArenaGameMode;
import net.knarcraft.dropper.property.RecordType;
import net.knarcraft.dropper.util.DropperGroupRecordHelper;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -19,12 +19,14 @@ 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
@ -32,9 +34,18 @@ import java.util.UUID;
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
@ -86,6 +97,14 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
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
*
@ -113,9 +132,9 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
ArenaRecord<?> record;
if (recordType == RecordType.DEATHS) {
record = getRecord(DropperGroupRecordHelper.getCombinedDeaths(group, gameMode), recordNumber);
record = getGroupDeathRecord(group, gameMode, recordNumber);
} else {
record = getRecord(DropperGroupRecordHelper.getCombinedTime(group, gameMode), recordNumber);
record = getGroupTimeRecord(group, gameMode, recordNumber);
}
// If a record number is not found, leave it blank, so it looks neat
@ -126,6 +145,82 @@ public class DropperRecordExpansion extends PlaceholderExpansion {
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
*

View File

@ -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) {
}

View File

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

View File

@ -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;
@ -24,7 +24,6 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A helper class for saving and loading arenas
@ -153,10 +152,9 @@ public final class ArenaStorageHelper {
ArenaStorageKey.PLAYER_HORIZONTAL_VELOCITY.getKey()));
SerializableMaterial winBlockType = (SerializableMaterial) configurationSection.get(
ArenaStorageKey.WIN_BLOCK_TYPE.getKey());
Logger logger = Dropper.getInstance().getLogger();
if (arenaName == null || spawnLocation == null) {
logger.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;
}
@ -166,7 +164,7 @@ public final class ArenaStorageHelper {
DropperArenaData arenaData = loadArenaData(arenaId);
if (arenaData == null) {
logger.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()) {
@ -203,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
*
@ -212,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;
}

View File

@ -1,12 +1,12 @@
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 net.knarcraft.dropper.property.ArenaGameMode;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;

View File

@ -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;

View 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