Make floor restore instant and play sounds per-player instead of at a fixed location
Build / build (push) Successful in 1m13s

This commit is contained in:
Michael Burgess
2026-08-07 08:20:15 -04:00
parent 07d210d8db
commit 86f118f9e6
3 changed files with 34 additions and 27 deletions
+5 -4
View File
@@ -142,10 +142,11 @@ disabled, deleted, or the plugin shuts down, so it never leaks entities.
## Configuration overview
- **`config.yml`** — global settings: `ui.*` toggles for scoreboard/bossbar/titles/actionbar,
`sounds.*` (Bukkit `Sound` enum names; invalid names are logged and skipped, never crash
the plugin), `performance.floor-blocks-per-tick` (batch size for floor *generation* and
*restoration*, spread across ticks — the round-end removal of non-target blocks is always
instantaneous, all at once, by design),
`sounds.*` (Bukkit `Sound` enum names, played directly to each participating player rather
than at one world location, so distance never mutes them; invalid names are logged and
skipped, never crash the plugin), `performance.floor-blocks-per-tick` (reserved for
future/optional batched floor operations — round-end removal and each round's floor reset
are always instantaneous, all blocks at once, by design),
`defaults.*` (used only when `/bp create` seeds a new arena), `default-floor-materials`
(the full 16-color concrete palette by default), `rewards.winner.commands`, and
`integrations.placeholderapi`.
@@ -358,7 +358,7 @@ public class Arena {
}
private void processRoundEnd() {
plugin.getSoundUtil().play(config.getSpawn(), plugin.getConfigManager().getSound("floor-disappear"), 1f, 1f);
playToParticipants("floor-disappear");
floorManager.removeNonTarget(currentTarget, plugin.getConfigManager().getFloorBatchBlocksPerTick(), () -> {
BukkitTask delay = plugin.getServer().getScheduler().runTaskLater(plugin, this::evaluateEliminations,
config.getFloorRemoveDelaySeconds() * 20L);
@@ -366,6 +366,19 @@ public class Arena {
});
}
/** Plays a configured sound directly to every current player/spectator (each at their own
* location), instead of once at a single world coordinate — Bukkit's location-based
* playSound falls off with distance, so anyone far from that one spot would hear nothing. */
private void playToParticipants(String soundKey) {
String soundName = plugin.getConfigManager().getSound(soundKey);
for (UUID uuid : allParticipants()) {
Player p = plugin.getServer().getPlayer(uuid);
if (p != null) {
plugin.getSoundUtil().play(p, soundName, 1f, 1f);
}
}
}
private void evaluateEliminations() {
List<EliminationLogic.PlayerFloorState<UUID, Material>> states = new ArrayList<>();
for (UUID uuid : players) {
@@ -406,7 +419,10 @@ public class Arena {
}
BukkitTask restoreDelay = plugin.getServer().getScheduler().runTaskLater(plugin, () ->
floorManager.restoreFull(plugin.getConfigManager().getFloorBatchBlocksPerTick(), this::startNextRound),
floorManager.restoreFull(plugin.getConfigManager().getFloorBatchBlocksPerTick(), () -> {
playToParticipants("round-complete");
startNextRound();
}),
config.getFloorRestoreDelaySeconds() * 20L);
tasks.add(restoreDelay);
}
@@ -89,7 +89,11 @@ public class FloorManager {
return new ArrayList<>(layout.values());
}
/** Places every block from the stored layout, batched across ticks. */
/**
* Places every block from the stored layout, all within a single tick, so the floor
* visibly resets instantly at the start of each round instead of filling in gradually.
* See {@link #removeNonTarget} for the same instant-vs-batched rationale.
*/
public void restoreFull(int blocksPerTick, Runnable onComplete) {
World world = config.getWorld();
if (world == null) {
@@ -99,26 +103,12 @@ public class FloorManager {
return;
}
cancelActiveTask();
Deque<Map.Entry<String, Material>> queue = new ArrayDeque<>(layout.entrySet());
org.bukkit.scheduler.BukkitRunnable runnable = new org.bukkit.scheduler.BukkitRunnable() {
@Override
public void run() {
int processed = 0;
while (processed < blocksPerTick && !queue.isEmpty()) {
Map.Entry<String, Material> entry = queue.poll();
setBlock(world, entry.getKey(), entry.getValue());
processed++;
}
if (queue.isEmpty()) {
cancel();
activeTask = null;
if (onComplete != null) {
onComplete.run();
}
}
}
};
activeTask = runnable.runTaskTimer(plugin, 0L, 1L);
for (Map.Entry<String, Material> entry : layout.entrySet()) {
setBlock(world, entry.getKey(), entry.getValue());
}
if (onComplete != null) {
onComplete.run();
}
}
/**