From 23d5e83cdbf18c740cd9cb68a3c2e8fb0da7caf1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 01:17:08 +0000 Subject: [PATCH 1/2] perf: optimize hot paths and remove main-thread I/O for Paper Populate a ConfigCache on enable so event handlers no longer walk the YAML map on every tick. Move kit/spawn/host-rank saveConfig() calls off the main thread via a new Scheduler.runAsync helper. Replace the per-join UpdateChecker (which spawned a new virtual thread per connection) with a singleton cached on the plugin. Fix a Component+String concatenation bug in KitCommand/KitManager that rendered the prefix as its toString. Other improvements: - GameManager: drop AtomicInteger, cache titles/messages per tick, use cached custom-command lists - MapManager: eager spawn-location load with scheduled retry only on world-not-loaded fallback; null-safe drop() - BorderUtil: safe static init via reload(), tick guards against missing world, async save for setAutoBorder - CreatureSpawnListener: EnumSet lookup instead of OR chain - PlaceholderHook: cache %eventcore_tps% for 1s; use stack amounts for totem count; use PluginMeta instead of deprecated description - LocationUtil: preserve yaw through fromString/toString round-trip - AnnouncementCommand: stop double-sending usage on success, fix broken UTF-8 in usage string - /event reload: also refresh ConfigCache, BorderUtil and KitManager https://claude.ai/code/session_01AqVRRmETiWkTPadN2jcffW --- src/main/java/me/david/EventCore.java | 109 +++++++---- .../command/impl/AnnouncementCommand.java | 48 +++-- .../me/david/command/impl/EventCommand.java | 15 +- .../me/david/command/impl/KitCommand.java | 12 +- .../me/david/listener/BlockBreakListener.java | 7 +- .../david/listener/BlockExplodeListener.java | 10 +- .../me/david/listener/BlockPlaceListener.java | 10 +- .../david/listener/CreatureSpawnListener.java | 16 +- .../EntityDamageByEntityListener.java | 33 ++-- .../david/listener/EntityDamageListener.java | 64 +++--- .../david/listener/EntityExplodeListener.java | 10 +- .../david/listener/PlayerDeathListener.java | 18 +- .../listener/PlayerDropItemListener.java | 13 +- .../listener/PlayerInteractListener.java | 7 +- .../me/david/listener/PlayerJoinListener.java | 49 +++-- .../listener/PlayerPickupItemListener.java | 13 +- .../me/david/listener/PlayerQuitListener.java | 7 +- .../david/listener/PlayerRespawnListener.java | 7 +- .../listener/PlayerTeleportListener.java | 19 +- .../java/me/david/manager/GameManager.java | 183 +++++++++--------- .../java/me/david/manager/KitManager.java | 60 ++++-- .../java/me/david/manager/MapManager.java | 90 ++++++--- .../java/me/david/util/AutoBroadcast.java | 39 ++-- src/main/java/me/david/util/BorderUtil.java | 57 ++++-- src/main/java/me/david/util/ConfigCache.java | 129 ++++++++++++ src/main/java/me/david/util/HostUtil.java | 49 ++--- src/main/java/me/david/util/LocationUtil.java | 20 +- .../java/me/david/util/PlaceholderHook.java | 46 +++-- src/main/java/me/david/util/Scheduler.java | 8 + .../java/me/david/util/UpdateChecker.java | 70 +++---- 30 files changed, 783 insertions(+), 435 deletions(-) create mode 100644 src/main/java/me/david/util/ConfigCache.java diff --git a/src/main/java/me/david/EventCore.java b/src/main/java/me/david/EventCore.java index e4d7c51..e29cdd7 100644 --- a/src/main/java/me/david/EventCore.java +++ b/src/main/java/me/david/EventCore.java @@ -12,8 +12,10 @@ import org.bukkit.Bukkit; import org.bukkit.Difficulty; import org.bukkit.GameRule; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,67 +27,66 @@ public class EventCore extends JavaPlugin { @Getter private static EventCore instance; + private MapManager mapManager; private GameManager gameManager; private KitManager kitManager; + private UpdateChecker updateChecker; @Override public void onEnable() { - saveDefaultConfig(); instance = this; - new UpdateChecker(instance, "DavidArchive", "EventCore").check(); + saveDefaultConfig(); + ConfigCache.reload(); + BorderUtil.reload(); + + updateChecker = new UpdateChecker(this, "DavidArchive", "EventCore"); + updateChecker.check(); mapManager = new MapManager(); gameManager = new GameManager(); kitManager = new KitManager(); EventCoreAPI.initialize(this); - new AnnouncementCommand(instance); - new EventCommand(instance); - new KitCommand(instance); + new AnnouncementCommand(this); + new EventCommand(this); + new KitCommand(this); new ReviveCommand(); - new SpawnCommand(instance); - - Bukkit.getPluginManager().registerEvents(new BlockBreakListener(), instance); - Bukkit.getPluginManager().registerEvents(new BlockExplodeListener(), instance); - Bukkit.getPluginManager().registerEvents(new BlockPlaceListener(), instance); - Bukkit.getPluginManager().registerEvents(new CreatureSpawnListener(), instance); - Bukkit.getPluginManager().registerEvents(new EntityDamageByEntityListener(), instance); - Bukkit.getPluginManager().registerEvents(new EntityDamageListener(), instance); - Bukkit.getPluginManager().registerEvents(new EntityExplodeListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerDeathListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerDropItemListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerInteractListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerJoinListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerPickupItemListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerQuitListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerRespawnListener(), instance); - Bukkit.getPluginManager().registerEvents(new PlayerTeleportListener(), instance); - - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { + new SpawnCommand(this); + + final PluginManager pm = Bukkit.getPluginManager(); + pm.registerEvents(new BlockBreakListener(), this); + pm.registerEvents(new BlockExplodeListener(), this); + pm.registerEvents(new BlockPlaceListener(), this); + pm.registerEvents(new CreatureSpawnListener(), this); + pm.registerEvents(new EntityDamageByEntityListener(), this); + pm.registerEvents(new EntityDamageListener(), this); + pm.registerEvents(new EntityExplodeListener(), this); + pm.registerEvents(new PlayerDeathListener(), this); + pm.registerEvents(new PlayerDropItemListener(), this); + pm.registerEvents(new PlayerInteractListener(), this); + pm.registerEvents(new PlayerJoinListener(), this); + pm.registerEvents(new PlayerPickupItemListener(), this); + pm.registerEvents(new PlayerQuitListener(), this); + pm.registerEvents(new PlayerRespawnListener(), this); + pm.registerEvents(new PlayerTeleportListener(), this); + + if (pm.getPlugin("PlaceholderAPI") != null) { new PlaceholderHook().register(); } + // Border check runs async every 10 ticks (0.5s) – negligible cost, and the + // actual world border mutation happens on the sync scheduler. Scheduler.timerAsync(new BorderUtil(), 20, 10); - Scheduler.timerAsync(new AutoBroadcast(), 20, 20 * getConfig().getLong("AutoBroadcast.Interval", 60)); - Scheduler.wait(() -> { - World world = mapManager.getSpawnLocation().getWorld(); - world.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); - world.setDifficulty(Difficulty.PEACEFUL); - world.getWorldBorder().setSize(BorderUtil.borderDefault); - world.getWorldBorder().setDamageBuffer(BorderUtil.borderDamageBuffer); - world.getWorldBorder().setDamageAmount(BorderUtil.borderDamageAmount); - }, 2); - - if (getConfig().getBoolean("Messages.Actionbar.Enabled")) { - Scheduler.timerAsync(() -> { - String raw = getConfig().getString("Messages.Actionbar.Message", "&aYou are playing the best Event!"); - for (Player player : Bukkit.getOnlinePlayers()) { - String parsed = PlaceholderAPI.setPlaceholders(player, raw); - player.sendActionBar(MessageUtil.translateColorCodes(parsed)); - } - }, 0, 20); + + // AutoBroadcast period comes from the cached config value + Scheduler.timerAsync(new AutoBroadcast(), 20, 20L * ConfigCache.autoBroadcastInterval); + + Scheduler.wait(this::applyWorldDefaults, 2); + + if (ConfigCache.actionBarEnabled) { + Scheduler.timerAsync(this::tickActionBar, 0, 20); } new Metrics(this, 28277); @@ -93,9 +94,31 @@ public void onEnable() { @Override public void onDisable() { - if (gameManager.isRunning()) { + if (gameManager != null && gameManager.isRunning()) { gameManager.stop(null); } } + private void applyWorldDefaults() { + final Location spawn = mapManager.getSpawnLocation(); + if (spawn == null) return; + final World world = spawn.getWorld(); + if (world == null) return; + + world.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + world.setDifficulty(Difficulty.PEACEFUL); + world.getWorldBorder().setSize(BorderUtil.borderDefault); + world.getWorldBorder().setDamageBuffer(BorderUtil.borderDamageBuffer); + world.getWorldBorder().setDamageAmount(BorderUtil.borderDamageAmount); + } + + private void tickActionBar() { + final String raw = ConfigCache.actionBarMessage; + final boolean papiAvailable = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + for (Player player : Bukkit.getOnlinePlayers()) { + final String parsed = papiAvailable ? PlaceholderAPI.setPlaceholders(player, raw) : raw; + player.sendActionBar(MessageUtil.translateColorCodes(parsed)); + } + } + } diff --git a/src/main/java/me/david/command/impl/AnnouncementCommand.java b/src/main/java/me/david/command/impl/AnnouncementCommand.java index 2b4a14f..57bcef4 100644 --- a/src/main/java/me/david/command/impl/AnnouncementCommand.java +++ b/src/main/java/me/david/command/impl/AnnouncementCommand.java @@ -2,6 +2,7 @@ import me.david.EventCore; import me.david.command.BukkitCommand; +import me.david.util.ConfigCache; import me.david.util.MessageUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; @@ -10,7 +11,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -25,33 +26,38 @@ public AnnouncementCommand(EventCore plugin) { @Override public void onCommand(CommandSender sender, String label, String[] args) { - if (!(sender.hasPermission("event.command"))) return; + if (!sender.hasPermission("event.command")) return; - if (args.length >= 1) { - String message = String.join(" ", args); - var replacements = Map.of( - "%prefix%", MessageUtil.getPrefix(), - "%message%", MessageUtil.translateColorCodes(message) - ); - - for (Player player : Bukkit.getOnlinePlayers()) { - player.sendMessage(MessageUtil.format("Messages.AnnoucementCommand.MessageFormat", replacements)); - - if (plugin.getConfig().getBoolean("Messages.AnnoucementCommand.Title.Enabled")) { - Component titleComponent = MessageUtil.format("Messages.AnnoucementCommand.Title.Title", replacements); - Component subTitleComponent = MessageUtil.format("Messages.AnnoucementCommand.Title.SubTitle", replacements); + if (args.length == 0) { + sender.sendMessage(MessageUtil.getPrefix().append( + MessageUtil.translateColorCodes("Usage: §c/annoucement "))); + return; + } - Title title = Title.title(titleComponent, subTitleComponent); - player.showTitle(title); - } + final String message = String.join(" ", args); + final Map replacements = Map.of( + "%prefix%", MessageUtil.getPrefix(), + "%message%", MessageUtil.translateColorCodes(message) + ); + + final Component formatted = MessageUtil.format("Messages.AnnoucementCommand.MessageFormat", replacements); + final boolean titleEnabled = ConfigCache.announcementTitleEnabled; + final Title title = titleEnabled + ? Title.title( + MessageUtil.format("Messages.AnnoucementCommand.Title.Title", replacements), + MessageUtil.format("Messages.AnnoucementCommand.Title.SubTitle", replacements)) + : null; + + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage(formatted); + if (title != null) { + player.showTitle(title); } } - - sender.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("Usage: §c/annoucement "))); } @Override public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, String[] args) throws IllegalArgumentException { - return new ArrayList<>(); + return Collections.emptyList(); } } diff --git a/src/main/java/me/david/command/impl/EventCommand.java b/src/main/java/me/david/command/impl/EventCommand.java index cef4ab5..d585fd8 100644 --- a/src/main/java/me/david/command/impl/EventCommand.java +++ b/src/main/java/me/david/command/impl/EventCommand.java @@ -3,6 +3,7 @@ import me.david.EventCore; import me.david.command.BukkitCommand; import me.david.util.BorderUtil; +import me.david.util.ConfigCache; import me.david.util.MessageUtil; import me.david.util.Scheduler; import net.kyori.adventure.text.Component; @@ -62,11 +63,17 @@ public void onCommand(CommandSender sender, String label, String[] args) { } if (args[0].equalsIgnoreCase("reload")) { - double currentMS = System.currentTimeMillis(); + final long startNanos = System.nanoTime(); plugin.reloadConfig(); - double reloadMS = System.currentTimeMillis() - currentMS; - - player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§aYou successfully reloaded the config within %ms%ms!").replaceText(b -> b.matchLiteral("%ms%").replacement(Component.text(String.valueOf(reloadMS)))))); + ConfigCache.reload(); + BorderUtil.reload(); + plugin.getKitManager().loadAllKits(); + final double reloadMS = (System.nanoTime() - startNanos) / 1_000_000.0; + + player.sendMessage(MessageUtil.getPrefix().append( + MessageUtil.translateColorCodes("§aYou successfully reloaded the config within %ms%ms!") + .replaceText(b -> b.matchLiteral("%ms%") + .replacement(Component.text(String.format("%.2f", reloadMS)))))); return; } diff --git a/src/main/java/me/david/command/impl/KitCommand.java b/src/main/java/me/david/command/impl/KitCommand.java index 8928626..9760e07 100644 --- a/src/main/java/me/david/command/impl/KitCommand.java +++ b/src/main/java/me/david/command/impl/KitCommand.java @@ -48,34 +48,34 @@ public void onCommand(CommandSender sender, String label, String[] args) { if (args[0].equalsIgnoreCase("enable")) { if (!plugin.getKitManager().getKits().containsKey(kit)) { - player.sendMessage(MessageUtil.getPrefix() + "§cThis kit does not exist!"); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§cThis kit does not exist!"))); return; } plugin.getKitManager().enable(kit); - player.sendMessage(MessageUtil.getPrefix() + "§a" + kit + " §7has been enabled!"); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§a" + kit + " §7has been enabled!"))); return; } if (args[0].equalsIgnoreCase("save")) { plugin.getKitManager().save(kit, player); - player.sendMessage(MessageUtil.getPrefix() + "§a" + kit + " §7has been saved!"); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§a" + kit + " §7has been saved!"))); return; } if (args[0].equalsIgnoreCase("delete")) { if (!plugin.getKitManager().getKits().containsKey(kit)) { - player.sendMessage(MessageUtil.getPrefix() + "§cThis kit does not exist!"); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§cThis kit does not exist!"))); return; } if (plugin.getKitManager().getEnabledKit().equalsIgnoreCase(kit)) { - player.sendMessage(MessageUtil.getPrefix() + "§cYou can't delete the enabled kit!"); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§cYou can't delete the enabled kit!"))); return; } plugin.getKitManager().delete(kit); - player.sendMessage(MessageUtil.getPrefix() + "§a" + kit + " §7has been deleted!"); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§a" + kit + " §7has been deleted!"))); return; } } diff --git a/src/main/java/me/david/listener/BlockBreakListener.java b/src/main/java/me/david/listener/BlockBreakListener.java index 976c324..9b9d2d4 100644 --- a/src/main/java/me/david/listener/BlockBreakListener.java +++ b/src/main/java/me/david/listener/BlockBreakListener.java @@ -8,16 +8,17 @@ public class BlockBreakListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { final Player player = event.getPlayer(); if (player.hasPermission("event.bypass")) { - event.setCancelled(false); return; } - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + } } } diff --git a/src/main/java/me/david/listener/BlockExplodeListener.java b/src/main/java/me/david/listener/BlockExplodeListener.java index 5b74251..74dba06 100644 --- a/src/main/java/me/david/listener/BlockExplodeListener.java +++ b/src/main/java/me/david/listener/BlockExplodeListener.java @@ -1,6 +1,7 @@ package me.david.listener; import me.david.EventCore; +import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockExplodeEvent; @@ -9,8 +10,13 @@ public class BlockExplodeListener implements Listener { @EventHandler public void onBlockExplode(BlockExplodeEvent event) { - event.blockList().forEach(block -> block.getDrops().clear()); - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + return; + } + for (Block block : event.blockList()) { + block.getDrops().clear(); + } } } diff --git a/src/main/java/me/david/listener/BlockPlaceListener.java b/src/main/java/me/david/listener/BlockPlaceListener.java index 00fca38..3e20a80 100644 --- a/src/main/java/me/david/listener/BlockPlaceListener.java +++ b/src/main/java/me/david/listener/BlockPlaceListener.java @@ -1,6 +1,7 @@ package me.david.listener; import me.david.EventCore; +import me.david.util.ConfigCache; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -8,21 +9,22 @@ public class BlockPlaceListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true) public void onBlockPlace(BlockPlaceEvent event) { final Player player = event.getPlayer(); if (player.hasPermission("event.bypass")) { - event.setCancelled(false); return; } - if (event.getBlock().getLocation().getBlockY() > EventCore.getInstance().getConfig().getLong("Settings.MaxBuildHeight", 0L)) { + if (event.getBlock().getY() > ConfigCache.maxBuildHeight) { event.setCancelled(true); return; } - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + } } } diff --git a/src/main/java/me/david/listener/CreatureSpawnListener.java b/src/main/java/me/david/listener/CreatureSpawnListener.java index 2632952..764a285 100644 --- a/src/main/java/me/david/listener/CreatureSpawnListener.java +++ b/src/main/java/me/david/listener/CreatureSpawnListener.java @@ -4,11 +4,23 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; +import java.util.EnumSet; +import java.util.Set; + public class CreatureSpawnListener implements Listener { - @EventHandler + private static final Set BLOCKED = EnumSet.of( + CreatureSpawnEvent.SpawnReason.BREEDING, + CreatureSpawnEvent.SpawnReason.EGG, + CreatureSpawnEvent.SpawnReason.NATURAL, + CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION, + CreatureSpawnEvent.SpawnReason.RAID, + CreatureSpawnEvent.SpawnReason.ENDER_PEARL + ); + + @EventHandler(ignoreCancelled = true) public void onCreatureSpawn(CreatureSpawnEvent event) { - if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.BREEDING || event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.EGG || event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.NATURAL || event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION || event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.RAID || event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.ENDER_PEARL) { + if (BLOCKED.contains(event.getSpawnReason())) { event.setCancelled(true); } } diff --git a/src/main/java/me/david/listener/EntityDamageByEntityListener.java b/src/main/java/me/david/listener/EntityDamageByEntityListener.java index e9ab741..b02b555 100644 --- a/src/main/java/me/david/listener/EntityDamageByEntityListener.java +++ b/src/main/java/me/david/listener/EntityDamageByEntityListener.java @@ -1,6 +1,7 @@ package me.david.listener; import me.david.EventCore; +import me.david.util.ConfigCache; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.event.EventHandler; @@ -14,30 +15,30 @@ public class EntityDamageByEntityListener implements Listener { public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { final Entity entity = event.getEntity(); final Entity damager = event.getDamager(); - - if (EventCore.getInstance().getConfig().getBoolean("Settings.DisableItemExplosions", true)) { - if (entity.getType() == EntityType.ITEM && damager.getType() == EntityType.END_CRYSTAL) { - if (event.getCause() == EntityDamageEvent.DamageCause.ENTITY_EXPLOSION || event.getCause() == EntityDamageEvent.DamageCause.BLOCK_EXPLOSION) { - event.setCancelled(true); - event.setDamage(0); - return; - } - } + final EntityDamageEvent.DamageCause cause = event.getCause(); + + if (ConfigCache.disableItemExplosions + && entity.getType() == EntityType.ITEM + && damager.getType() == EntityType.END_CRYSTAL + && (cause == EntityDamageEvent.DamageCause.ENTITY_EXPLOSION + || cause == EntityDamageEvent.DamageCause.BLOCK_EXPLOSION)) { + event.setCancelled(true); + event.setDamage(0); + return; } - if (EventCore.getInstance().getConfig().getBoolean("Settings.DisableFallDamage", true)) { - if (event.getCause() == EntityDamageEvent.DamageCause.FALL) { - event.setCancelled(true); - return; - } + if (ConfigCache.disableFallDamage && cause == EntityDamageEvent.DamageCause.FALL) { + event.setCancelled(true); + return; } if (damager.hasPermission("event.bypass")) { - event.setCancelled(false); return; } - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + } } } diff --git a/src/main/java/me/david/listener/EntityDamageListener.java b/src/main/java/me/david/listener/EntityDamageListener.java index a5a88ef..e63832f 100644 --- a/src/main/java/me/david/listener/EntityDamageListener.java +++ b/src/main/java/me/david/listener/EntityDamageListener.java @@ -1,7 +1,8 @@ package me.david.listener; import me.david.EventCore; -import org.bukkit.World; +import me.david.util.ConfigCache; +import org.bukkit.Location; import org.bukkit.WorldBorder; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -13,44 +14,47 @@ public class EntityDamageListener implements Listener { @EventHandler public void onEntityDamage(EntityDamageEvent event) { - if (EventCore.getInstance().getConfig().getBoolean("Settings.DisableFallDamage", true)) { - if (event.getCause() == EntityDamageEvent.DamageCause.FALL) { - event.setCancelled(true); - return; - } + final EntityDamageEvent.DamageCause cause = event.getCause(); + + if (ConfigCache.disableFallDamage && cause == EntityDamageEvent.DamageCause.FALL) { + event.setCancelled(true); + return; } - if (!(EventCore.getInstance().getGameManager().isRunning())) { + if (!EventCore.getInstance().getGameManager().isRunning()) { event.setCancelled(true); return; } + if (!ConfigCache.borderBoostEnabled || cause != EntityDamageEvent.DamageCause.SUFFOCATION) return; if (!(event.getEntity() instanceof Player player)) return; - if (EventCore.getInstance().getConfig().getBoolean("Settings.WorldBorder.Boost.Enabled")) { - if (event.getCause() == EntityDamageEvent.DamageCause.SUFFOCATION) { - World world = player.getWorld(); - WorldBorder worldBorder = world.getWorldBorder(); - if (!(worldBorder.isInside(player.getLocation()))) { - double boostXZ = EventCore.getInstance().getConfig().getDouble("Settings.WorldBorder.Boost.StrengthXZ", 1.3); - double boostY = EventCore.getInstance().getConfig().getDouble("Settings.WorldBorder.Boost.StrengthY", 0.1); - double maxX = worldBorder.getCenter().getBlockX() + worldBorder.getSize() / 2; - double minX = worldBorder.getCenter().getBlockX() - worldBorder.getSize() / 2; - double maxZ = worldBorder.getCenter().getBlockZ() + worldBorder.getSize() / 2; - double minZ = worldBorder.getCenter().getBlockZ() - worldBorder.getSize() / 2; - if (player.getLocation().getBlockX() > maxX) { - player.setVelocity(player.getVelocity().add(new Vector(-boostXZ, boostY, 0))); - } else if (player.getLocation().getBlockX() < minX) { - player.setVelocity(player.getVelocity().add(new Vector(boostXZ, boostY, 0))); - } - if (player.getLocation().getBlockZ() > maxZ) { - player.setVelocity(player.getVelocity().add(new Vector(0, boostY, -boostXZ))); - } else if (player.getLocation().getBlockZ() < minZ) { - player.setVelocity(player.getVelocity().add(new Vector(0, boostY, boostXZ))); - } - } - } + final WorldBorder worldBorder = player.getWorld().getWorldBorder(); + final Location location = player.getLocation(); + if (worldBorder.isInside(location)) return; + + final double boostXZ = ConfigCache.borderBoostStrengthXZ; + final double boostY = ConfigCache.borderBoostStrengthY; + final double halfSize = worldBorder.getSize() / 2.0; + final double centerX = worldBorder.getCenter().getX(); + final double centerZ = worldBorder.getCenter().getZ(); + final double maxX = centerX + halfSize; + final double minX = centerX - halfSize; + final double maxZ = centerZ + halfSize; + final double minZ = centerZ - halfSize; + + final Vector velocity = player.getVelocity(); + if (location.getX() > maxX) { + velocity.add(new Vector(-boostXZ, boostY, 0)); + } else if (location.getX() < minX) { + velocity.add(new Vector(boostXZ, boostY, 0)); + } + if (location.getZ() > maxZ) { + velocity.add(new Vector(0, boostY, -boostXZ)); + } else if (location.getZ() < minZ) { + velocity.add(new Vector(0, boostY, boostXZ)); } + player.setVelocity(velocity); } } diff --git a/src/main/java/me/david/listener/EntityExplodeListener.java b/src/main/java/me/david/listener/EntityExplodeListener.java index 99cffa8..9a8ccc9 100644 --- a/src/main/java/me/david/listener/EntityExplodeListener.java +++ b/src/main/java/me/david/listener/EntityExplodeListener.java @@ -1,6 +1,7 @@ package me.david.listener; import me.david.EventCore; +import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityExplodeEvent; @@ -9,8 +10,13 @@ public class EntityExplodeListener implements Listener { @EventHandler public void onEntityExplode(EntityExplodeEvent event) { - event.blockList().forEach(block -> block.getDrops().clear()); - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + return; + } + for (Block block : event.blockList()) { + block.getDrops().clear(); + } } } diff --git a/src/main/java/me/david/listener/PlayerDeathListener.java b/src/main/java/me/david/listener/PlayerDeathListener.java index 83495b4..a6046bb 100644 --- a/src/main/java/me/david/listener/PlayerDeathListener.java +++ b/src/main/java/me/david/listener/PlayerDeathListener.java @@ -1,6 +1,6 @@ package me.david.listener; -import me.david.EventCore; +import me.david.util.ConfigCache; import me.david.util.MessageUtil; import me.david.util.PlayerUtil; import net.kyori.adventure.text.Component; @@ -23,16 +23,16 @@ public void onPlayerDeath(PlayerDeathEvent event) { return; } - if (EventCore.getInstance().getConfig().getBoolean("Messages.PlayerDeath.Enabled")) { - if (player.getKiller() != null) { + if (ConfigCache.playerDeathMessageEnabled) { + final Player killer = player.getKiller(); + if (killer != null) { event.deathMessage(MessageUtil.format("Messages.PlayerDeath.Message1", Map.of( - "%player%", Component.text(player.getName()), - "%killer%", Component.text(player.getKiller().getName())) - )); + "%player%", Component.text(player.getName()), + "%killer%", Component.text(killer.getName()) + ))); } else { - event.deathMessage(MessageUtil.format( - "Messages.PlayerDeath.Message2", Map.of("%player%", Component.text(player.getName())) - )); + event.deathMessage(MessageUtil.format("Messages.PlayerDeath.Message2", + Map.of("%player%", Component.text(player.getName())))); } } else { event.deathMessage(Component.empty()); diff --git a/src/main/java/me/david/listener/PlayerDropItemListener.java b/src/main/java/me/david/listener/PlayerDropItemListener.java index 9c85bdb..3cb2a9e 100644 --- a/src/main/java/me/david/listener/PlayerDropItemListener.java +++ b/src/main/java/me/david/listener/PlayerDropItemListener.java @@ -1,6 +1,7 @@ package me.david.listener; import me.david.EventCore; +import me.david.util.ConfigCache; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -8,18 +9,18 @@ public class PlayerDropItemListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true) public void onPlayerDropItem(PlayerDropItemEvent event) { - final Player player = event.getPlayer(); - - if (EventCore.getInstance().getConfig().getBoolean("Settings.AllowItemDropBeforeStart")) return; + if (ConfigCache.allowItemDropBeforeStart) return; + final Player player = event.getPlayer(); if (player.hasPermission("event.bypass")) { - event.setCancelled(false); return; } - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + } } } diff --git a/src/main/java/me/david/listener/PlayerInteractListener.java b/src/main/java/me/david/listener/PlayerInteractListener.java index 58d3e8a..c325453 100644 --- a/src/main/java/me/david/listener/PlayerInteractListener.java +++ b/src/main/java/me/david/listener/PlayerInteractListener.java @@ -8,16 +8,17 @@ public class PlayerInteractListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true) public void onPlayerInteractEvent(PlayerInteractEvent event) { final Player player = event.getPlayer(); if (player.hasPermission("event.bypass")) { - event.setCancelled(false); return; } - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + } } } diff --git a/src/main/java/me/david/listener/PlayerJoinListener.java b/src/main/java/me/david/listener/PlayerJoinListener.java index 493163f..3145306 100644 --- a/src/main/java/me/david/listener/PlayerJoinListener.java +++ b/src/main/java/me/david/listener/PlayerJoinListener.java @@ -1,9 +1,15 @@ package me.david.listener; import me.david.EventCore; -import me.david.util.*; +import me.david.util.ConfigCache; +import me.david.util.HostUtil; +import me.david.util.MessageUtil; +import me.david.util.PlayerUtil; +import me.david.util.Scheduler; +import me.david.util.UpdateChecker; import net.kyori.adventure.text.Component; import org.bukkit.GameMode; +import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -19,39 +25,46 @@ public void onPlayerJoin(PlayerJoinEvent event) { HostUtil.giveHost(player); - if (EventCore.getInstance().getConfig().getBoolean("Messages.PlayerJoin.Enabled")) { - Component message = MessageUtil.getPrefix().append(MessageUtil.format("Messages.PlayerJoin.Message", Map.of("%player%", Component.text(player.getName())))); + if (ConfigCache.playerJoinMessageEnabled) { + Component message = MessageUtil.getPrefix().append(MessageUtil.format("Messages.PlayerJoin.Message", + Map.of("%player%", Component.text(player.getName())))); event.joinMessage(message); } else { event.joinMessage(Component.empty()); } - player.teleportAsync(EventCore.getInstance().getMapManager().getSpawnLocation()); + final Location spawn = EventCore.getInstance().getMapManager().getSpawnLocation(); + if (spawn != null) { + player.teleportAsync(spawn); + } PlayerUtil.cleanPlayer(player); - if (EventCore.getInstance().getGameManager().isRunning()) { + + final boolean gameRunning = EventCore.getInstance().getGameManager().isRunning(); + if (gameRunning) { player.getInventory().setArmorContents(null); player.getInventory().clear(); player.setGameMode(GameMode.SPECTATOR); } + Scheduler.wait(() -> { - player.teleportAsync(EventCore.getInstance().getMapManager().getSpawnLocation()); + final Location current = EventCore.getInstance().getMapManager().getSpawnLocation(); + if (current != null) { + player.teleportAsync(current); + } if (EventCore.getInstance().getGameManager().isRunning()) { player.setGameMode(GameMode.SPECTATOR); } }, 2); - if (player.hasPermission("event.notify") && EventCore.getInstance().getConfig().getBoolean("Settings.Updates.NotifyOnJoin")) { - UpdateChecker updateChecker = new UpdateChecker(EventCore.getInstance(), "DavidArchive", "EventCore"); - updateChecker.check(); - - Scheduler.wait(() -> { - if (updateChecker.isHasUpdate()) { - player.sendMessage(Component.empty()); - player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("You're running an outdated version of EventCore. Please update to the latest version:"))); - player.sendMessage(updateChecker.getUpdateComponent()); - player.sendMessage(Component.empty()); - } - }, 20L); + if (ConfigCache.updatesNotifyOnJoin && player.hasPermission("event.notify")) { + final UpdateChecker updateChecker = EventCore.getInstance().getUpdateChecker(); + if (updateChecker != null && updateChecker.isHasUpdate()) { + player.sendMessage(Component.empty()); + player.sendMessage(MessageUtil.getPrefix().append( + MessageUtil.translateColorCodes("You're running an outdated version of EventCore. Please update to the latest version:"))); + player.sendMessage(updateChecker.getUpdateComponent()); + player.sendMessage(Component.empty()); + } } } diff --git a/src/main/java/me/david/listener/PlayerPickupItemListener.java b/src/main/java/me/david/listener/PlayerPickupItemListener.java index 3b084d7..ebdd2a3 100644 --- a/src/main/java/me/david/listener/PlayerPickupItemListener.java +++ b/src/main/java/me/david/listener/PlayerPickupItemListener.java @@ -1,6 +1,7 @@ package me.david.listener; import me.david.EventCore; +import me.david.util.ConfigCache; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -8,18 +9,18 @@ public class PlayerPickupItemListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true) public void onPlayerPickUpItem(PlayerPickupItemEvent event) { - final Player player = event.getPlayer(); - - if (EventCore.getInstance().getConfig().getBoolean("Settings.AllowItemDropBeforeStart")) return; + if (ConfigCache.allowItemDropBeforeStart) return; + final Player player = event.getPlayer(); if (player.hasPermission("event.bypass")) { - event.setCancelled(false); return; } - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); + } } } diff --git a/src/main/java/me/david/listener/PlayerQuitListener.java b/src/main/java/me/david/listener/PlayerQuitListener.java index abfbf8e..93f8e00 100644 --- a/src/main/java/me/david/listener/PlayerQuitListener.java +++ b/src/main/java/me/david/listener/PlayerQuitListener.java @@ -1,6 +1,6 @@ package me.david.listener; -import me.david.EventCore; +import me.david.util.ConfigCache; import me.david.util.HostUtil; import me.david.util.MessageUtil; import net.kyori.adventure.text.Component; @@ -19,8 +19,9 @@ public void onPlayerQuit(PlayerQuitEvent event) { HostUtil.removeHost(player); - if (EventCore.getInstance().getConfig().getBoolean("Messages.PlayerQuit.Enabled")) { - Component message = MessageUtil.format("Messages.PlayerQuit.Message", Map.of("%player%", Component.text(player.getName()))); + if (ConfigCache.playerQuitMessageEnabled) { + Component message = MessageUtil.format("Messages.PlayerQuit.Message", + Map.of("%player%", Component.text(player.getName()))); event.quitMessage(message); } else { event.quitMessage(Component.empty()); diff --git a/src/main/java/me/david/listener/PlayerRespawnListener.java b/src/main/java/me/david/listener/PlayerRespawnListener.java index 93dabd5..635e6e7 100644 --- a/src/main/java/me/david/listener/PlayerRespawnListener.java +++ b/src/main/java/me/david/listener/PlayerRespawnListener.java @@ -3,6 +3,7 @@ import me.david.EventCore; import me.david.util.PlayerUtil; import org.bukkit.GameMode; +import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -13,7 +14,11 @@ public class PlayerRespawnListener implements Listener { @EventHandler public void onPlayerRespawn(PlayerRespawnEvent event) { final Player player = event.getPlayer(); - event.setRespawnLocation(EventCore.getInstance().getMapManager().getSpawnLocation()); + + final Location spawn = EventCore.getInstance().getMapManager().getSpawnLocation(); + if (spawn != null) { + event.setRespawnLocation(spawn); + } PlayerUtil.cleanPlayer(player); player.setGameMode(GameMode.SPECTATOR); } diff --git a/src/main/java/me/david/listener/PlayerTeleportListener.java b/src/main/java/me/david/listener/PlayerTeleportListener.java index c843377..3ac8ab1 100644 --- a/src/main/java/me/david/listener/PlayerTeleportListener.java +++ b/src/main/java/me/david/listener/PlayerTeleportListener.java @@ -1,25 +1,22 @@ package me.david.listener; -import me.david.EventCore; +import me.david.util.ConfigCache; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerTeleportEvent; public class PlayerTeleportListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true) public void onPlayerTeleport(PlayerTeleportEvent event) { - final Location to = event.getTo(); - final World world = to.getWorld(); + if (!ConfigCache.borderDisableEnderPearls) return; + if (event.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) return; - if (EventCore.getInstance().getConfig().getBoolean("Settings.WorldBorder.DisableEnderPeals")) { - if (event.getCause() == PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { - if (!(world.getWorldBorder().isInside(to))) { - event.setCancelled(true); - } - } + final Location to = event.getTo(); + if (to == null) return; + if (!to.getWorld().getWorldBorder().isInside(to)) { + event.setCancelled(true); } } diff --git a/src/main/java/me/david/manager/GameManager.java b/src/main/java/me/david/manager/GameManager.java index e760bf7..bb47651 100644 --- a/src/main/java/me/david/manager/GameManager.java +++ b/src/main/java/me/david/manager/GameManager.java @@ -7,20 +7,25 @@ import me.david.api.events.game.GameTimerTickEvent; import me.david.api.events.game.InGameTimerTickEvent; import me.david.util.BorderUtil; +import me.david.util.ConfigCache; import me.david.util.MessageUtil; import me.david.util.PlayerUtil; import me.david.util.Scheduler; +import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.Difficulty; +import org.bukkit.GameMode; +import org.bukkit.Sound; +import org.bukkit.World; import org.bukkit.entity.Player; import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; @Getter public class GameManager { - private boolean running = false; + private volatile boolean running = false; private volatile boolean timerRunning = false; private Scheduler.TaskWrapper startTask; @@ -28,7 +33,7 @@ public class GameManager { private Scheduler.TaskWrapper autoDropTask; private Scheduler.TaskWrapper timerTask; - private AtomicInteger timer; + private int timer; private long inGameTimer; private boolean autoDropped = false; @@ -40,78 +45,18 @@ public void start() { autoDropped = false; timerRunning = true; - timer = new AtomicInteger(EventCore.getInstance().getConfig().getInt("Messages.StartTimer.Timer", 5)); + timer = ConfigCache.startTimerSeconds; - GameStartEvent gameStartEvent = new GameStartEvent(timer.get()); + final GameStartEvent gameStartEvent = new GameStartEvent(timer); Bukkit.getPluginManager().callEvent(gameStartEvent); - if (gameStartEvent.isCancelled()) { timerRunning = false; return; } - startTask = Scheduler.timer(() -> { - if (!timerRunning || running) return; - - int current = timer.get(); - - Bukkit.getPluginManager().callEvent(new GameTimerTickEvent(current)); - - if (current > 0) { - String color = EventCore.getInstance().getConfig().getString("Messages.StartTimer.Colors." + current + "sec", ""); - Component prefix = MessageUtil.getPrefix(); - var replacements = Map.of( - "%timer%", MessageUtil.translateColorCodes(color + current + "§7"), - "%prefix%", prefix - ); - Component message = prefix.append(MessageUtil.format("Messages.StartTimer.Message", replacements)); - Title title = Title.title( - MessageUtil.format("Messages.StartTimer.Title", replacements), - MessageUtil.format("Messages.StartTimer.SubTitle", replacements) - ); - for (Player player : Bukkit.getOnlinePlayers()) { - player.sendMessage(message); - player.showTitle(title); - player.playSound(player.getLocation(), Sound.ENTITY_CHICKEN_EGG, 5, 5); - } - timer.decrementAndGet(); - } else { - Component message = MessageUtil.getPrefix().append(MessageUtil.get("Messages.Start.Message")); - Title title = Title.title( - MessageUtil.get("Messages.Start.Title"), - MessageUtil.get("Messages.Start.SubTitle") - ); - for (Player player : Bukkit.getOnlinePlayers()) { - player.sendMessage(message); - player.showTitle(title); - player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 5, 5); - } - - for (World world : Bukkit.getWorlds()) { - world.setDifficulty(Difficulty.HARD); - } - - if (EventCore.getInstance().getConfig().getBoolean("Settings.IngameTimer.Enabled") - && !EventCore.getInstance().getConfig().getBoolean("Messages.Actionbar.Enabled")) { - startInGameTimer(); - } - - EventCore.getInstance().getConfig().getStringList("Settings.Start.CustomCommands") - .forEach(command -> Scheduler.dispatchCommand( - () -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1))) - ); + startTask = Scheduler.timer(this::tickStart, 0, 20); - running = true; - timerRunning = false; - - if (startTask != null) { - startTask.cancel(); - startTask = null; - } - } - }, 0, 20); - - if (EventCore.getInstance().getConfig().getBoolean("Settings.AutoStop1Player")) { + if (ConfigCache.autoStop1Player) { autoStopTask = Scheduler.timer(() -> { if (running && PlayerUtil.getAlive() == 1) { running = false; @@ -126,9 +71,10 @@ public void start() { }, 0, 20); } - if (EventCore.getInstance().getConfig().getBoolean("Settings.DropOnPlayerCount.Enabled")) { + if (ConfigCache.dropOnPlayerCountEnabled) { + final long dropThreshold = ConfigCache.dropOnPlayerCount; autoDropTask = Scheduler.timer(() -> { - if (running && PlayerUtil.getAlive() <= EventCore.getInstance().getConfig().getLong("Settings.DropOnPlayerCount.Count") && !autoDropped) { + if (running && !autoDropped && PlayerUtil.getAlive() <= dropThreshold) { autoDropped = true; EventCore.getInstance().getMapManager().drop(); } @@ -136,28 +82,88 @@ public void start() { } } + private void tickStart() { + if (!timerRunning || running) return; + + final int current = timer; + Bukkit.getPluginManager().callEvent(new GameTimerTickEvent(current)); + + final Component prefix = MessageUtil.getPrefix(); + if (current > 0) { + final String color = EventCore.getInstance().getConfig() + .getString("Messages.StartTimer.Colors." + current + "sec", ""); + final Map replacements = Map.of( + "%timer%", MessageUtil.translateColorCodes(color + current + "§7"), + "%prefix%", prefix + ); + final Component message = prefix.append(MessageUtil.format("Messages.StartTimer.Message", replacements)); + final Title title = Title.title( + MessageUtil.format("Messages.StartTimer.Title", replacements), + MessageUtil.format("Messages.StartTimer.SubTitle", replacements) + ); + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage(message); + player.showTitle(title); + player.playSound(player.getLocation(), Sound.ENTITY_CHICKEN_EGG, 5, 5); + } + timer = current - 1; + return; + } + + final Component message = prefix.append(MessageUtil.get("Messages.Start.Message")); + final Title title = Title.title( + MessageUtil.get("Messages.Start.Title"), + MessageUtil.get("Messages.Start.SubTitle") + ); + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage(message); + player.showTitle(title); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 5, 5); + } + + for (World world : Bukkit.getWorlds()) { + world.setDifficulty(Difficulty.HARD); + } + + if (ConfigCache.ingameTimerEnabled && !ConfigCache.actionBarEnabled) { + startInGameTimer(); + } + + for (String command : ConfigCache.startCustomCommands) { + Scheduler.dispatchCommand(() -> + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1))); + } + + running = true; + timerRunning = false; + + if (startTask != null) { + startTask.cancel(); + startTask = null; + } + } + public void stop(final String winner) { - GameStopEvent gameStopEvent = new GameStopEvent(winner); + final GameStopEvent gameStopEvent = new GameStopEvent(winner); Bukkit.getPluginManager().callEvent(gameStopEvent); - if (gameStopEvent.isCancelled()) { return; } running = false; timerRunning = false; - BorderUtil.lastOptimal = 200; + BorderUtil.lastOptimal = BorderUtil.borderDefault; stopInGameTimer(); stopAllTimers(); - Component prefix = MessageUtil.getPrefix(); - var replacements = Map.of( + final Component prefix = MessageUtil.getPrefix(); + final Map replacements = Map.of( "%winner%", MessageUtil.translateColorCodes(winner), "%prefix%", prefix ); - Component stopMessage = prefix.append(MessageUtil.format("Messages.Stop.Message", replacements)); - Title stopTitle = Title.title( + final Component stopMessage = prefix.append(MessageUtil.format("Messages.Stop.Message", replacements)); + final Title stopTitle = Title.title( MessageUtil.format("Messages.Stop.Title", replacements), MessageUtil.format("Messages.Stop.SubTitle", replacements) ); @@ -174,12 +180,12 @@ public void stop(final String winner) { world.getWorldBorder().setSize(BorderUtil.borderDefault); } - EventCore.getInstance().getConfig().getStringList("Settings.Stop.CustomCommands") - .forEach(cmd -> Scheduler.dispatchCommand( - () -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.substring(1))) - ); + for (String command : ConfigCache.stopCustomCommands) { + Scheduler.dispatchCommand(() -> + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1))); + } - if (EventCore.getInstance().getConfig().getBoolean("Settings.MapReset.AutoReset")) { + if (ConfigCache.autoMapReset) { EventCore.getInstance().getMapManager().reset(); } } @@ -197,11 +203,14 @@ public void startInGameTimer() { Bukkit.getPluginManager().callEvent(new InGameTimerTickEvent(inGameTimer)); - String raw = EventCore.getInstance().getConfig().getString("Settings.IngameTimer.Format", "hh:mm:ss") - .replace("hh", String.format("%02d", (inGameTimer / 3600))) - .replace("mm", String.format("%02d", ((inGameTimer % 3600) / 60))) - .replace("ss", String.format("%02d", (inGameTimer % 60))); - Component actionBar = MessageUtil.translateColorCodes(raw); + final long hours = inGameTimer / 3600; + final long minutes = (inGameTimer % 3600) / 60; + final long seconds = inGameTimer % 60; + final String raw = ConfigCache.ingameTimerFormat + .replace("hh", String.format("%02d", hours)) + .replace("mm", String.format("%02d", minutes)) + .replace("ss", String.format("%02d", seconds)); + final Component actionBar = MessageUtil.translateColorCodes(raw); for (Player player : Bukkit.getOnlinePlayers()) { player.sendActionBar(actionBar); diff --git a/src/main/java/me/david/manager/KitManager.java b/src/main/java/me/david/manager/KitManager.java index 491fa76..a99c2c3 100644 --- a/src/main/java/me/david/manager/KitManager.java +++ b/src/main/java/me/david/manager/KitManager.java @@ -7,13 +7,17 @@ import me.david.api.events.kit.KitGiveEvent; import me.david.api.events.kit.KitSaveEvent; import me.david.util.MessageUtil; +import me.david.util.Scheduler; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; import org.jetbrains.annotations.NotNull; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; @@ -21,9 +25,10 @@ @Getter public class KitManager { - // Mio - All of these changes can be seen as a temporary fix – the plan for the future is still to rewrite the entire class. + private static final Logger LOGGER = Logger.getLogger("KitManager"); - private final Logger LOGGER = Logger.getLogger("KitManager"); + /** Player inventory is 36 main slots + 4 armor + 1 offhand = 41 slots. */ + private static final int INVENTORY_SLOTS = 41; private volatile String enabledKit = "default"; private final Map> kits = new ConcurrentHashMap<>(); @@ -33,18 +38,19 @@ public KitManager() { } public void give(@NotNull final Player player) { - Map kitItems = kits.get(enabledKit); + final Map kitItems = kits.get(enabledKit); if (kitItems == null || kitItems.isEmpty()) return; - KitGiveEvent kitGiveEvent = new KitGiveEvent(player, enabledKit); + final KitGiveEvent kitGiveEvent = new KitGiveEvent(player, enabledKit); Bukkit.getPluginManager().callEvent(kitGiveEvent); if (kitGiveEvent.isCancelled()) return; - player.getInventory().clear(); - player.getInventory().setArmorContents(null); + final PlayerInventory inventory = player.getInventory(); + inventory.clear(); + inventory.setArmorContents(null); for (Map.Entry entry : kitItems.entrySet()) { - player.getInventory().setItem(entry.getKey(), entry.getValue()); + inventory.setItem(entry.getKey(), entry.getValue()); } } @@ -60,15 +66,17 @@ public void loadAllKits() { final ConfigurationSection kitSection = section.getConfigurationSection(kitName); if (kitSection == null) continue; - final Map map = new ConcurrentHashMap<>(); + final Map map = new HashMap<>(); for (String key : kitSection.getKeys(false)) { try { int slot = Integer.parseInt(key); ItemStack item = kitSection.getItemStack(key); if (item != null) map.put(slot, item.clone()); - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + // non-numeric keys (e.g. "-" placeholder) are skipped + } } - kits.put(kitName, map); + kits.put(kitName, Map.copyOf(map)); } LOGGER.info("Loaded " + kits.size() + " kits into memory."); } @@ -80,23 +88,25 @@ public void save(@NotNull final String kit, @NotNull final Player player) { try { final FileConfiguration config = EventCore.getInstance().getConfig(); + config.set("Kits.Kits." + kit, null); final ConfigurationSection kitSection = config.createSection("Kits.Kits." + kit); - Map cacheMap = new ConcurrentHashMap<>(); - for (int i = 0; i < 41; i++) { - ItemStack item = player.getInventory().getItem(i); - if (item != null) { + final Map cacheMap = new HashMap<>(); + final PlayerInventory inventory = player.getInventory(); + for (int i = 0; i < INVENTORY_SLOTS; i++) { + final ItemStack item = inventory.getItem(i); + if (item != null && !item.getType().isAir()) { kitSection.set(String.valueOf(i), item); cacheMap.put(i, item.clone()); } } - kits.put(kit, cacheMap); - EventCore.getInstance().saveConfig(); + kits.put(kit, Map.copyOf(cacheMap)); + saveConfigAsync(); - player.sendMessage(MessageUtil.getPrefix() + "§aKit saved & cached!"); + player.sendMessage(MessageUtil.getPrefix().append(Component.text("§aKit saved & cached!"))); } catch (Exception e) { - player.sendMessage(MessageUtil.getPrefix() + "§cFailed to save kit!"); + player.sendMessage(MessageUtil.getPrefix().append(Component.text("§cFailed to save kit!"))); LOGGER.warning("[KitManager] Failed to save kit: " + kit + " :" + e.getMessage()); } } @@ -114,7 +124,7 @@ public void enable(@NotNull final String kit) { enabledKit = kit; EventCore.getInstance().getConfig().set("Kits.EnabledKit", kit); - EventCore.getInstance().saveConfig(); + saveConfigAsync(); Bukkit.getOnlinePlayers().forEach(this::give); } @@ -126,6 +136,16 @@ public void delete(@NotNull final String kit) { kits.remove(kit); EventCore.getInstance().getConfig().set("Kits.Kits." + kit, null); - EventCore.getInstance().saveConfig(); + saveConfigAsync(); + } + + private void saveConfigAsync() { + Scheduler.runAsync(() -> { + try { + EventCore.getInstance().saveConfig(); + } catch (Exception e) { + LOGGER.warning("[KitManager] Failed to persist config: " + e.getMessage()); + } + }); } } diff --git a/src/main/java/me/david/manager/MapManager.java b/src/main/java/me/david/manager/MapManager.java index 5fce64a..1453d0d 100644 --- a/src/main/java/me/david/manager/MapManager.java +++ b/src/main/java/me/david/manager/MapManager.java @@ -5,74 +5,116 @@ import me.david.api.events.map.MapDropEvent; import me.david.api.events.map.MapResetEvent; import me.david.api.events.map.SpawnLocationChangeEvent; +import me.david.util.ConfigCache; import me.david.util.LocationUtil; import me.david.util.Scheduler; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.WorldBorder; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.logging.Logger; @Getter public class MapManager { - private Location spawnLocation; + private static final Logger LOGGER = Logger.getLogger("MapManager"); + + private volatile @Nullable Location spawnLocation; public MapManager() { - Scheduler.wait(() -> spawnLocation = LocationUtil.fromString(EventCore.getInstance().getConfig().getString("Settings.SpawnLocation", "world/0/200/0")), 2); + // Attempt an immediate load – on a normal plugin enable the world is already + // loaded, so the eager attempt avoids the 2-tick window where callers would + // otherwise see a null spawn location. Fall back to a scheduled retry if the + // world is not yet available (e.g. delayed world loads from other plugins). + if (!loadSpawnLocation()) { + Scheduler.wait(() -> loadSpawnLocation(), 2); + } + } + + private boolean loadSpawnLocation() { + final Location location = LocationUtil.fromString(ConfigCache.spawnLocationRaw); + if (location == null) { + return false; + } + spawnLocation = location; + return true; } public void saveSpawnLocation(@NotNull final Player player) { - Location oldLocation = spawnLocation; - Location newLocation = player.getLocation(); + final Location oldLocation = spawnLocation; + final Location newLocation = player.getLocation(); - SpawnLocationChangeEvent spawnLocationChangeEvent = new SpawnLocationChangeEvent(player, newLocation, oldLocation); + final SpawnLocationChangeEvent spawnLocationChangeEvent = + new SpawnLocationChangeEvent(player, newLocation, oldLocation); Bukkit.getPluginManager().callEvent(spawnLocationChangeEvent); - if (spawnLocationChangeEvent.isCancelled()) { return; } - String location = LocationUtil.toString(spawnLocationChangeEvent.getNewLocation()); - spawnLocation = spawnLocationChangeEvent.getNewLocation(); - - EventCore.getInstance().getConfig().set("Settings.SpawnLocation", location); - EventCore.getInstance().saveConfig(); + final Location resolved = spawnLocationChangeEvent.getNewLocation(); + final String serialized = LocationUtil.toString(resolved); + spawnLocation = resolved; + + EventCore.getInstance().getConfig().set("Settings.SpawnLocation", serialized); + ConfigCache.spawnLocationRaw = serialized; + Scheduler.runAsync(() -> { + try { + EventCore.getInstance().saveConfig(); + } catch (Exception e) { + LOGGER.warning("Failed to persist spawn location: " + e.getMessage()); + } + }); } public void drop() { - long borderExtra = EventCore.getInstance().getConfig().getLong("Settings.Drop.BorderExtra", 3); - double borderSize = spawnLocation.getWorld().getWorldBorder().getSize(); + final Location spawn = spawnLocation; + if (spawn == null) return; - MapDropEvent mapDropEvent = new MapDropEvent(spawnLocation, borderSize); - Bukkit.getPluginManager().callEvent(mapDropEvent); + final World world = spawn.getWorld(); + if (world == null) return; + final WorldBorder border = world.getWorldBorder(); + final double borderSize = border.getSize(); + final long borderExtra = ConfigCache.dropBorderExtra; + + final MapDropEvent mapDropEvent = new MapDropEvent(spawn, borderSize); + Bukkit.getPluginManager().callEvent(mapDropEvent); if (mapDropEvent.isCancelled()) { return; } - Location edgeMin = spawnLocation.clone().subtract(borderSize / 2D + borderExtra, 0, borderSize / 2D + borderExtra); - Location edgeMax = spawnLocation.clone().add(borderSize / 2D + borderExtra, 0, borderSize / 2D + borderExtra); + final double halfExtent = borderSize / 2D + borderExtra; + final Location edgeMin = spawn.clone().subtract(halfExtent, 0, halfExtent); + final Location edgeMax = spawn.clone().add(halfExtent, 0, halfExtent); + final String worldName = world.getName(); Scheduler.dispatchCommand(() -> { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/world " + spawnLocation.getWorld().getName()); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/world " + worldName); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/pos1 " + edgeMin.getBlockX() + ",-63," + edgeMin.getBlockZ()); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/pos2 " + edgeMax.getBlockX() + ",350," + edgeMax.getBlockZ()); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/set 0"); - EventCore.getInstance().getConfig().getStringList("Settings.Drop.CustomCommands").forEach(command -> - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1))); + for (String command : ConfigCache.dropCustomCommands) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)); + } }); } public void reset() { - MapResetEvent mapResetEvent = new MapResetEvent(); + final MapResetEvent mapResetEvent = new MapResetEvent(); Bukkit.getPluginManager().callEvent(mapResetEvent); - if (mapResetEvent.isCancelled()) { return; } - Scheduler.dispatchCommand(() -> EventCore.getInstance().getConfig().getStringList("Settings.MapReset.Commands").forEach(command -> - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)))); + Scheduler.dispatchCommand(() -> { + for (String command : ConfigCache.mapResetCommands) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)); + } + }); } } diff --git a/src/main/java/me/david/util/AutoBroadcast.java b/src/main/java/me/david/util/AutoBroadcast.java index 0894e82..cccc74d 100644 --- a/src/main/java/me/david/util/AutoBroadcast.java +++ b/src/main/java/me/david/util/AutoBroadcast.java @@ -1,6 +1,5 @@ package me.david.util; -import me.david.EventCore; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -9,37 +8,35 @@ public class AutoBroadcast implements Runnable { - private final List messages; private int index = 0; - public AutoBroadcast() { - messages = EventCore.getInstance().getConfig().getStringList("AutoBroadcast.Messages"); - } - @Override public void run() { - if (!(EventCore.getInstance().getConfig().getBoolean("AutoBroadcast.Enabled")) || messages.isEmpty()) { - return; - } + if (!ConfigCache.autoBroadcastEnabled) return; + + final List messages = ConfigCache.autoBroadcastMessages; + if (messages.isEmpty()) return; if (index >= messages.size()) { index = 0; } - String message = messages.get(index); - if (EventCore.getInstance().getConfig().getBoolean("AutoBroadcast.UseBroadcastCommand")) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), EventCore.getInstance().getConfig().getString("AutoBroadcast.BroadcastCommand", "").replaceAll("%message%", message)); - } else { - for (Player player : Bukkit.getOnlinePlayers()) { - player.sendMessage(Component.empty()); - player.sendMessage(Component.empty()); - player.sendMessage(MessageUtil.translateColorCodes(message)); - player.sendMessage(Component.empty()); - player.sendMessage(Component.empty()); - } + final String message = messages.get(index++); + if (ConfigCache.autoBroadcastUseCommand) { + final String command = ConfigCache.autoBroadcastCommand.replace("%message%", message); + Scheduler.runSync(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); + return; } - index++; + final Component empty = Component.empty(); + final Component body = MessageUtil.translateColorCodes(message); + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage(empty); + player.sendMessage(empty); + player.sendMessage(body); + player.sendMessage(empty); + player.sendMessage(empty); + } } } diff --git a/src/main/java/me/david/util/BorderUtil.java b/src/main/java/me/david/util/BorderUtil.java index 1990e9d..b9ad7f4 100644 --- a/src/main/java/me/david/util/BorderUtil.java +++ b/src/main/java/me/david/util/BorderUtil.java @@ -1,44 +1,63 @@ package me.david.util; import me.david.EventCore; +import me.david.manager.MapManager; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.WorldBorder; public class BorderUtil implements Runnable { - public static int borderDefault = 200; - public static double borderDamageBuffer = 0.0; - public static double borderDamageAmount = 0.2; - public static int lastOptimal = borderDefault; - public static boolean autoBorder = EventCore.getInstance().getConfig().getBoolean("Settings.WorldBorder.AutoBorder", false); + public static volatile int borderDefault = 200; + public static volatile double borderDamageBuffer = 0.0; + public static volatile double borderDamageAmount = 0.2; + public static volatile int lastOptimal = borderDefault; + public static volatile boolean autoBorder = false; public BorderUtil() { - borderDefault = EventCore.getInstance().getConfig().getInt("Settings.WorldBorder.DefaultSize", borderDefault); - borderDamageBuffer = EventCore.getInstance().getConfig().getDouble("Settings.WorldBorder.Damage.Buffer", borderDamageBuffer); - borderDamageAmount = EventCore.getInstance().getConfig().getDouble("Settings.WorldBorder.Damage.Amount", borderDamageAmount); + reload(); + } + + /** Refreshes cached border values from the backing configuration. */ + public static void reload() { + var cfg = EventCore.getInstance().getConfig(); + borderDefault = cfg.getInt("Settings.WorldBorder.DefaultSize", 200); + borderDamageBuffer = cfg.getDouble("Settings.WorldBorder.Damage.Buffer", 0.0); + borderDamageAmount = cfg.getDouble("Settings.WorldBorder.Damage.Amount", 0.2); + autoBorder = cfg.getBoolean("Settings.WorldBorder.AutoBorder", false); lastOptimal = borderDefault; } public static void setAutoBorder(boolean value) { autoBorder = value; EventCore.getInstance().getConfig().set("Settings.WorldBorder.AutoBorder", value); - EventCore.getInstance().saveConfig(); + Scheduler.runAsync(EventCore.getInstance()::saveConfig); } - @Override public void run() { - if (EventCore.getInstance().getGameManager().isRunning() && autoBorder) { - double current = EventCore.getInstance().getMapManager().getSpawnLocation().getWorld().getWorldBorder().getSize(); - int optimal = getOptimalSize(); - if (lastOptimal > optimal) { - lastOptimal = optimal; - Scheduler.runSync(() -> EventCore.getInstance().getMapManager().getSpawnLocation().getWorld().getWorldBorder().setSize(optimal, (long) (current - optimal))); - } + if (!autoBorder) return; + if (!EventCore.getInstance().getGameManager().isRunning()) return; + + final MapManager mapManager = EventCore.getInstance().getMapManager(); + final Location spawn = mapManager.getSpawnLocation(); + if (spawn == null) return; + final World world = spawn.getWorld(); + if (world == null) return; + + final WorldBorder border = world.getWorldBorder(); + final double current = border.getSize(); + final int optimal = getOptimalSize(); + if (lastOptimal > optimal) { + lastOptimal = optimal; + Scheduler.runSync(() -> border.setSize(optimal, (long) (current - optimal))); } } private int getOptimalSize() { - int optimal = (int) (((Math.pow(PlayerUtil.getAlive(), 2)) / 60 + 4 + 0.6 * PlayerUtil.getAlive()) * 2); - return Math.min(200, optimal); + final int alive = PlayerUtil.getAlive(); + final int optimal = (int) (((Math.pow(alive, 2)) / 60 + 4 + 0.6 * alive) * 2); + return Math.min(borderDefault, optimal); } } diff --git a/src/main/java/me/david/util/ConfigCache.java b/src/main/java/me/david/util/ConfigCache.java new file mode 100644 index 0000000..2799ddf --- /dev/null +++ b/src/main/java/me/david/util/ConfigCache.java @@ -0,0 +1,129 @@ +package me.david.util; + +import lombok.experimental.UtilityClass; +import me.david.EventCore; +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.List; + +/** + * Snapshot of frequently-read config values. + *

+ * Event handlers fire thousands of times per second on a busy server. + * Reading from {@link FileConfiguration} involves map lookups and string + * splitting on every call, which is wasted work when the value rarely + * changes. This cache is populated on plugin enable and refreshed whenever + * {@code /event reload} is executed. + */ +@UtilityClass +public class ConfigCache { + + // Settings.* + public boolean disableFallDamage; + public boolean disableItemExplosions; + public long maxBuildHeight; + public boolean allowItemDropBeforeStart; + public String spawnLocationRaw; + + // Settings.WorldBorder.* + public boolean borderBoostEnabled; + public double borderBoostStrengthXZ; + public double borderBoostStrengthY; + public boolean borderDisableEnderPearls; + + // Settings.AutoStop / DropOnPlayerCount / IngameTimer + public boolean autoStop1Player; + public boolean dropOnPlayerCountEnabled; + public long dropOnPlayerCount; + public boolean ingameTimerEnabled; + public String ingameTimerFormat; + + // Messages.* + public boolean actionBarEnabled; + public String actionBarMessage; + public boolean playerJoinMessageEnabled; + public boolean playerQuitMessageEnabled; + public boolean playerDeathMessageEnabled; + public int startTimerSeconds; + public boolean announcementTitleEnabled; + + // Settings.Updates.* + public boolean updatesNotifyOnJoin; + public boolean updatesLogInConsole; + + // Settings.HostRank.* + public boolean hostRankEnabled; + public String hostRankPermission; + public String hostRankJoinCommand; + public String hostRankQuitCommand; + + // AutoBroadcast.* + public boolean autoBroadcastEnabled; + public long autoBroadcastInterval; + public boolean autoBroadcastUseCommand; + public String autoBroadcastCommand; + public List autoBroadcastMessages = List.of(); + + // Settings.MapReset / Drop / Start / Stop command lists + public boolean autoMapReset; + public long dropBorderExtra; + public List dropCustomCommands = List.of(); + public List mapResetCommands = List.of(); + public List startCustomCommands = List.of(); + public List stopCustomCommands = List.of(); + + /** + * Reloads every cached field from the backing {@link FileConfiguration}. + * Safe to call from any thread – the read-only side of Bukkit's + * memory-backed config is thread-safe once populated. + */ + public void reload() { + final FileConfiguration cfg = EventCore.getInstance().getConfig(); + + disableFallDamage = cfg.getBoolean("Settings.DisableFallDamage", true); + disableItemExplosions = cfg.getBoolean("Settings.DisableItemExplosions", true); + maxBuildHeight = cfg.getLong("Settings.MaxBuildHeight", 0L); + allowItemDropBeforeStart = cfg.getBoolean("Settings.AllowItemDropBeforeStart", true); + spawnLocationRaw = cfg.getString("Settings.SpawnLocation", "world/0/200/0"); + + borderBoostEnabled = cfg.getBoolean("Settings.WorldBorder.Boost.Enabled", true); + borderBoostStrengthXZ = cfg.getDouble("Settings.WorldBorder.Boost.StrengthXZ", 1.3); + borderBoostStrengthY = cfg.getDouble("Settings.WorldBorder.Boost.StrengthY", 0.1); + borderDisableEnderPearls = cfg.getBoolean("Settings.WorldBorder.DisableEnderPeals", true); + + autoStop1Player = cfg.getBoolean("Settings.AutoStop1Player", true); + dropOnPlayerCountEnabled = cfg.getBoolean("Settings.DropOnPlayerCount.Enabled", false); + dropOnPlayerCount = cfg.getLong("Settings.DropOnPlayerCount.Count", 15); + ingameTimerEnabled = cfg.getBoolean("Settings.IngameTimer.Enabled", true); + ingameTimerFormat = cfg.getString("Settings.IngameTimer.Format", "hh:mm:ss"); + + actionBarEnabled = cfg.getBoolean("Messages.Actionbar.Enabled", false); + actionBarMessage = cfg.getString("Messages.Actionbar.Message", "&aYou are playing the best Event!"); + playerJoinMessageEnabled = cfg.getBoolean("Messages.PlayerJoin.Enabled", true); + playerQuitMessageEnabled = cfg.getBoolean("Messages.PlayerQuit.Enabled", true); + playerDeathMessageEnabled = cfg.getBoolean("Messages.PlayerDeath.Enabled", true); + startTimerSeconds = cfg.getInt("Messages.StartTimer.Timer", 5); + announcementTitleEnabled = cfg.getBoolean("Messages.AnnoucementCommand.Title.Enabled", true); + + updatesNotifyOnJoin = cfg.getBoolean("Settings.Updates.NotifyOnJoin", true); + updatesLogInConsole = cfg.getBoolean("Settings.Updates.LogInConsole", false); + + hostRankEnabled = cfg.getBoolean("Settings.HostRank.Enabled", false); + hostRankPermission = cfg.getString("Settings.HostRank.Permission", "event.host"); + hostRankJoinCommand = cfg.getString("Settings.HostRank.JoinCommand", ""); + hostRankQuitCommand = cfg.getString("Settings.HostRank.QuitCommand", ""); + + autoBroadcastEnabled = cfg.getBoolean("AutoBroadcast.Enabled", false); + autoBroadcastInterval = cfg.getLong("AutoBroadcast.Interval", 60); + autoBroadcastUseCommand = cfg.getBoolean("AutoBroadcast.UseBroadcastCommand", false); + autoBroadcastCommand = cfg.getString("AutoBroadcast.BroadcastCommand", ""); + autoBroadcastMessages = List.copyOf(cfg.getStringList("AutoBroadcast.Messages")); + + autoMapReset = cfg.getBoolean("Settings.MapReset.AutoReset", true); + dropBorderExtra = cfg.getLong("Settings.Drop.BorderExtra", 3); + dropCustomCommands = List.copyOf(cfg.getStringList("Settings.Drop.CustomCommands")); + mapResetCommands = List.copyOf(cfg.getStringList("Settings.MapReset.Commands")); + startCustomCommands = List.copyOf(cfg.getStringList("Settings.Start.CustomCommands")); + stopCustomCommands = List.copyOf(cfg.getStringList("Settings.Stop.CustomCommands")); + } +} diff --git a/src/main/java/me/david/util/HostUtil.java b/src/main/java/me/david/util/HostUtil.java index 3b3d5c5..fec91e2 100644 --- a/src/main/java/me/david/util/HostUtil.java +++ b/src/main/java/me/david/util/HostUtil.java @@ -1,45 +1,50 @@ package me.david.util; import lombok.experimental.UtilityClass; -import me.david.EventCore; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.LinkedList; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; @UtilityClass public class HostUtil { - public LinkedList<@NotNull Player> host = new LinkedList<>(); + /** Identity by UUID rather than Player instance, since Player references can become stale. */ + private final Set host = ConcurrentHashMap.newKeySet(); public boolean isHost(final @NotNull Player player) { - return host.contains(player); + return host.contains(player.getUniqueId()); } public void giveHost(final @NotNull Player player) { - if (EventCore.getInstance().getConfig().getBoolean("Settings.HostRank.Enabled") && host.isEmpty()) { - String permission = EventCore.getInstance().getConfig().getString("Settings.HostRank.Permission", "event.host"); - if (player.hasPermission(permission)) { - String rawCommand = EventCore.getInstance().getConfig().getString("Settings.HostRank.JoinCommand", ""); - String command = rawCommand.replaceAll("%player%", player.getName()); - Scheduler.dispatchCommand(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); - } - host.add(player); + if (!ConfigCache.hostRankEnabled) return; + if (!host.isEmpty()) return; + if (!player.hasPermission(ConfigCache.hostRankPermission)) { + // Still track so a later hostless player isn't promoted by accident + host.add(player.getUniqueId()); + return; } + + final String rawCommand = ConfigCache.hostRankJoinCommand; + if (rawCommand != null && !rawCommand.isEmpty()) { + final String command = rawCommand.replace("%player%", player.getName()); + Scheduler.dispatchCommand(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); + } + host.add(player.getUniqueId()); } public void removeHost(final @NotNull Player player) { - if (EventCore.getInstance().getConfig().getBoolean("Settings.HostRank.Enabled")) { - String permission = EventCore.getInstance().getConfig().getString("Settings.HostRank.Permission", "event.host"); - if (player.hasPermission(permission) && isHost(player)) { - String rawCommand = EventCore.getInstance().getConfig().getString("Settings.HostRank.QuitCommand", ""); - String command = rawCommand.replaceAll("%player%", player.getName()); - Scheduler.dispatchCommand(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); - } - host.remove(player); + if (!ConfigCache.hostRankEnabled) return; + if (!host.remove(player.getUniqueId())) return; + if (!player.hasPermission(ConfigCache.hostRankPermission)) return; + + final String rawCommand = ConfigCache.hostRankQuitCommand; + if (rawCommand != null && !rawCommand.isEmpty()) { + final String command = rawCommand.replace("%player%", player.getName()); + Scheduler.dispatchCommand(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); } } } - - diff --git a/src/main/java/me/david/util/LocationUtil.java b/src/main/java/me/david/util/LocationUtil.java index c050ac3..f0a929f 100644 --- a/src/main/java/me/david/util/LocationUtil.java +++ b/src/main/java/me/david/util/LocationUtil.java @@ -12,22 +12,28 @@ public class LocationUtil { public @Nullable Location fromString(@Nullable String in) { if (in == null) return null; - String[] parts = in.split("/"); + final String[] parts = in.split("/"); if (parts.length < 4) return null; - World world = Bukkit.getWorld(parts[0]); + final World world = Bukkit.getWorld(parts[0]); if (world == null) return null; try { - double x = Double.parseDouble(parts[1]); - double y = Double.parseDouble(parts[2]); - double z = Double.parseDouble(parts[3]); - return new Location(world, x, y, z); + final double x = Double.parseDouble(parts[1]); + final double y = Double.parseDouble(parts[2]); + final double z = Double.parseDouble(parts[3]); + final float yaw = parts.length > 4 ? Float.parseFloat(parts[4]) : 0f; + return new Location(world, x, y, z, yaw, 0f); } catch (NumberFormatException e) { return null; } } public @NotNull String toString(@NotNull Location location) { - return location.getWorld().getName() + "/" + (location.getBlockX() + 0.5) + "/" + location.getBlockY() + "/" + (location.getBlockZ() + 0.5) + "/" + (Math.round(location.getYaw() / 45) * 45); + final World world = location.getWorld(); + final String worldName = world != null ? world.getName() : "world"; + final double centerX = location.getBlockX() + 0.5; + final double centerZ = location.getBlockZ() + 0.5; + final int snappedYaw = Math.round(location.getYaw() / 45f) * 45; + return worldName + "/" + centerX + "/" + location.getBlockY() + "/" + centerZ + "/" + snappedYaw; } } diff --git a/src/main/java/me/david/util/PlaceholderHook.java b/src/main/java/me/david/util/PlaceholderHook.java index 3411abd..7928ccd 100644 --- a/src/main/java/me/david/util/PlaceholderHook.java +++ b/src/main/java/me/david/util/PlaceholderHook.java @@ -3,21 +3,26 @@ import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.david.EventCore; -import org.bukkit.ChatColor; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import java.text.DecimalFormat; import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; +import java.util.regex.Pattern; public final class PlaceholderHook extends PlaceholderExpansion { private static final DecimalFormat KD_FORMAT = new DecimalFormat("#0.00"); + private static final long TPS_CACHE_TTL_MS = 1_000L; + private static final Pattern COLOR_CODE = Pattern.compile("[§&][0-9a-fk-orA-FK-OR]"); + + private volatile long tpsLastFetch = 0L; + private volatile String tpsCachedValue = ""; @Override public @NotNull String getIdentifier() { @@ -30,9 +35,8 @@ public final class PlaceholderHook extends PlaceholderExpansion { } @Override - @SuppressWarnings("deprecation") public @NotNull String getVersion() { - return EventCore.getInstance().getDescription().getVersion(); + return EventCore.getInstance().getPluginMeta().getVersion(); } @Override @@ -82,14 +86,32 @@ public boolean persist() { } private static int countTotems(final @NotNull Player player) { - return (int) Stream.of(player.getInventory().getContents()).filter(Objects::nonNull) - .filter(item -> item.getType() == Material.TOTEM_OF_UNDYING) - .count(); + int count = 0; + for (ItemStack item : player.getInventory().getContents()) { + if (item != null && item.getType() == Material.TOTEM_OF_UNDYING) { + count += item.getAmount(); + } + } + return count; } - @SuppressWarnings("deprecation") - private static @NotNull String formatTPS() { - final String raw = PlaceholderAPI.setPlaceholders(null, "%spark_tps_5m%"); - return ChatColor.stripColor(raw.replace("*", "").split("\\.")[0]); + private @NotNull String formatTPS() { + final long now = System.currentTimeMillis(); + if (now - tpsLastFetch < TPS_CACHE_TTL_MS && !tpsCachedValue.isEmpty()) { + return tpsCachedValue; + } + + String value; + if (Bukkit.getPluginManager().isPluginEnabled("spark")) { + final String raw = PlaceholderAPI.setPlaceholders(null, "%spark_tps_5m%"); + final String stripped = COLOR_CODE.matcher(raw).replaceAll("").replace("*", "").trim(); + value = stripped.isEmpty() ? "?" : stripped.split("\\.")[0]; + } else { + value = String.format("%.0f", Bukkit.getServer().getTPS()[0]); + } + + tpsCachedValue = value; + tpsLastFetch = now; + return value; } } diff --git a/src/main/java/me/david/util/Scheduler.java b/src/main/java/me/david/util/Scheduler.java index 1778c70..85dc787 100644 --- a/src/main/java/me/david/util/Scheduler.java +++ b/src/main/java/me/david/util/Scheduler.java @@ -91,6 +91,14 @@ public static void runSync(@NotNull Runnable runnable) { } } + public static void runAsync(@NotNull Runnable runnable) { + if (FOLIA) { + Bukkit.getAsyncScheduler().runNow(EventCore.getInstance(), (task) -> runnable.run()); + } else { + Bukkit.getScheduler().runTaskAsynchronously(EventCore.getInstance(), runnable); + } + } + @CanIgnoreReturnValue public static @NotNull TaskWrapper wait(@NotNull Runnable runnable, long delay) { final long safeDelay = Math.max(delay, 1); diff --git a/src/main/java/me/david/util/UpdateChecker.java b/src/main/java/me/david/util/UpdateChecker.java index 7145a19..58a997f 100644 --- a/src/main/java/me/david/util/UpdateChecker.java +++ b/src/main/java/me/david/util/UpdateChecker.java @@ -8,13 +8,13 @@ import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; -import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -26,45 +26,50 @@ public class UpdateChecker { private final String apiUrl; private final String currentVer; - private String latestVer; - private boolean hasUpdate; - private String downloadUrl; + private volatile String latestVer; + private volatile boolean hasUpdate; + private volatile String downloadUrl; - @SuppressWarnings("deprecation") public UpdateChecker(JavaPlugin plugin, String owner, String repo) { this.plugin = plugin; this.apiUrl = String.format("https://api.github.com/repos/%s/%s/releases/latest", owner, repo); - this.currentVer = normalize(plugin.getDescription().getVersion()); + this.currentVer = normalize(plugin.getPluginMeta().getVersion()); } - @SuppressWarnings("deprecation") + /** + * Runs the update probe on the plugin's async scheduler. Safe to call + * repeatedly – subsequent calls simply refresh the cached state. + */ public void check() { - Thread.ofVirtual().start(() -> { - try { - var connection = (HttpURLConnection) new URL(apiUrl).openConnection(); - connection.setRequestProperty("User-Agent", "Mozilla/5.0"); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - if (connection.getResponseCode() != 200) { - plugin.getLogger().warning("Update check failed: HTTP " + connection.getResponseCode()); - return; - } + Scheduler.runAsync(this::fetch); + } - try (var reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { - var json = JsonParser.parseReader(reader).getAsJsonObject(); - latestVer = normalize(json.get("tag_name").getAsString()); - downloadUrl = json.get("html_url").getAsString(); - hasUpdate = compare(currentVer, latestVer) < 0; + private void fetch() { + try { + final URL url = URI.create(apiUrl).toURL(); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("User-Agent", "EventCore-UpdateChecker"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + if (connection.getResponseCode() != 200) { + plugin.getLogger().warning("Update check failed: HTTP " + connection.getResponseCode()); + return; + } - if (EventCore.getInstance().getConfig().getBoolean("Settings.Updates.LogInConsole")) { - Bukkit.getScheduler().runTask(plugin, this::log); - } + try (var reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + var json = JsonParser.parseReader(reader).getAsJsonObject(); + latestVer = normalize(json.get("tag_name").getAsString()); + downloadUrl = json.get("html_url").getAsString(); + hasUpdate = compare(currentVer, latestVer) < 0; + + if (ConfigCache.updatesLogInConsole) { + Scheduler.runSync(this::log); } - } catch (Exception exception) { - EventCore.LOGGER.warn("Failed to check for updates: {}", exception.getMessage()); } - }); + } catch (Exception exception) { + EventCore.LOGGER.warn("Failed to check for updates: {}", exception.getMessage()); + } } private void log() { @@ -88,7 +93,6 @@ private void log() { return Component.text("Click to download", NamedTextColor.GREEN, TextDecoration.UNDERLINED) .hoverEvent(HoverEvent.showText(Component.text("Click to Open", NamedTextColor.GRAY))) .clickEvent(ClickEvent.openUrl(downloadUrl)); - } private String normalize(String v) { @@ -101,8 +105,8 @@ private String normalize(String v) { } private int compare(String current, String latest) { - var curr = current.split("\\."); - var last = latest.split("\\."); + final String[] curr = current.split("\\."); + final String[] last = latest.split("\\."); for (int i = 0; i < Math.max(curr.length, last.length); i++) { int c = i < curr.length ? parse(curr[i]) : 0; @@ -119,4 +123,4 @@ private int parse(String part) { return 0; } } -} \ No newline at end of file +} From 50a224c56223acd39d7dc88f26b22487eedcdcf4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 11:08:59 +0000 Subject: [PATCH 2/2] perf: drop Folia, target Paper 1.21.11, harden hot paths Remove the Folia runtime path entirely (Scheduler wrappers, plugin.yml flag, runPaper.folia task) now that only Paper 1.21.11 is supported, and bump the paper-api dependency accordingly. Hot-path cleanups: - MessageUtil.getPrefix() memoizes the translated Component and is invalidated from ConfigCache.reload() so per-tick call sites stop re-parsing legacy color codes. - ConfigCache preloads Messages.StartTimer.Colors into a List and the PlaceholderAPI availability flag so tickStart/tickActionBar no longer touch the YAML config or the plugin manager every tick. - PlayerUtil.getAlive() switches from a stream to a direct iteration. - PlayerPickupItemListener moves off the deprecated PlayerPickupItemEvent to EntityPickupItemEvent. - All gate-style listeners now run at EventPriority.HIGH so other plugins can veto earlier. Deprecation cleanup: WorldBorder.setSize -> changeSize, player max health read via the MAX_HEALTH attribute, and the one remaining ANNOUNCE_ADVANCEMENTS call is explicitly @SuppressWarnings'd since Bukkit has no non-deprecated alternative yet. Also removes the unused EventCore field from AnnouncementCommand and the now-dead getSoftware() helper in EventCommand. https://claude.ai/code/session_01AqVRRmETiWkTPadN2jcffW --- build.gradle.kts | 19 +- gradle/libs.versions.toml | 2 +- src/main/java/me/david/EventCore.java | 3 +- .../command/impl/AnnouncementCommand.java | 3 - .../me/david/command/impl/EventCommand.java | 33 ++-- .../me/david/listener/BlockBreakListener.java | 14 +- .../david/listener/BlockExplodeListener.java | 3 +- .../me/david/listener/BlockPlaceListener.java | 8 +- .../david/listener/CreatureSpawnListener.java | 3 +- .../EntityDamageByEntityListener.java | 3 +- .../david/listener/EntityDamageListener.java | 3 +- .../david/listener/EntityExplodeListener.java | 3 +- .../listener/PlayerDropItemListener.java | 7 +- .../listener/PlayerInteractListener.java | 14 +- .../listener/PlayerPickupItemListener.java | 11 +- .../listener/PlayerTeleportListener.java | 8 +- .../java/me/david/manager/GameManager.java | 20 +-- .../java/me/david/manager/MapManager.java | 26 ++- src/main/java/me/david/util/BorderUtil.java | 6 +- src/main/java/me/david/util/ConfigCache.java | 21 +++ src/main/java/me/david/util/HostUtil.java | 4 +- src/main/java/me/david/util/MessageUtil.java | 14 +- src/main/java/me/david/util/PlayerUtil.java | 15 +- src/main/java/me/david/util/Scheduler.java | 163 +++--------------- src/main/resources/plugin.yml | 1 - 25 files changed, 156 insertions(+), 251 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8a18a24..9ee0932 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -72,7 +72,7 @@ tasks { // 1.17 = Java 16 // 1.18 - 1.20.4 = Java 17 // 1-20.5+ = Java 21 - val version = "1.21.8" + val version = "1.21.11" val javaVersion = JavaLanguageVersion.of(21) val jvmArgsExternal = listOf( @@ -82,7 +82,7 @@ tasks { val sharedPlugins = runPaper.downloadPluginsSpec { url("https://github.com/ViaVersion/ViaVersion/releases/download/5.8.1/ViaVersion-5.8.1.jar") url("https://github.com/ViaVersion/ViaBackwards/releases/download/5.8.1/ViaBackwards-5.8.1.jar") - url("https://ci.athion.net/job/FastAsyncWorldEdit/1214/artifact/artifacts/FastAsyncWorldEdit-Bukkit-2.14.1-SNAPSHOT-1214.jar") // Not folia compatible + url("https://ci.athion.net/job/FastAsyncWorldEdit/1214/artifact/artifacts/FastAsyncWorldEdit-Bukkit-2.14.1-SNAPSHOT-1214.jar") } runServer { @@ -99,19 +99,4 @@ tasks { jvmArgs = jvmArgsExternal } - - runPaper.folia.registerTask { - minecraftVersion(version) - runDirectory = rootDir.resolve("run/folia/$version") - - javaLauncher = project.javaToolchains.launcherFor { - languageVersion = javaVersion - } - - downloadPlugins { - from(sharedPlugins) - } - - jvmArgs = jvmArgsExternal - } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5471632..0d39b8b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -paper-api = "1.21-R0.1-SNAPSHOT" +paper-api = "1.21.11-R0.1-SNAPSHOT" lombok = "1.18.44" placeholderapi = "2.12.2" shadow = "9.4.1" diff --git a/src/main/java/me/david/EventCore.java b/src/main/java/me/david/EventCore.java index e29cdd7..cf1b3eb 100644 --- a/src/main/java/me/david/EventCore.java +++ b/src/main/java/me/david/EventCore.java @@ -99,6 +99,7 @@ public void onDisable() { } } + @SuppressWarnings("removal") // GameRule.ANNOUNCE_ADVANCEMENTS is still the only API to toggle this private void applyWorldDefaults() { final Location spawn = mapManager.getSpawnLocation(); if (spawn == null) return; @@ -114,7 +115,7 @@ private void applyWorldDefaults() { private void tickActionBar() { final String raw = ConfigCache.actionBarMessage; - final boolean papiAvailable = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + final boolean papiAvailable = ConfigCache.papiAvailable; for (Player player : Bukkit.getOnlinePlayers()) { final String parsed = papiAvailable ? PlaceholderAPI.setPlaceholders(player, raw) : raw; player.sendActionBar(MessageUtil.translateColorCodes(parsed)); diff --git a/src/main/java/me/david/command/impl/AnnouncementCommand.java b/src/main/java/me/david/command/impl/AnnouncementCommand.java index 57bcef4..a98afa6 100644 --- a/src/main/java/me/david/command/impl/AnnouncementCommand.java +++ b/src/main/java/me/david/command/impl/AnnouncementCommand.java @@ -17,11 +17,8 @@ public class AnnouncementCommand extends BukkitCommand { - private final EventCore plugin; - public AnnouncementCommand(EventCore plugin) { super("annoucement", "event.command.annoucement", "announce"); - this.plugin = plugin; } @Override diff --git a/src/main/java/me/david/command/impl/EventCommand.java b/src/main/java/me/david/command/impl/EventCommand.java index d585fd8..1fea297 100644 --- a/src/main/java/me/david/command/impl/EventCommand.java +++ b/src/main/java/me/david/command/impl/EventCommand.java @@ -5,7 +5,6 @@ import me.david.util.BorderUtil; import me.david.util.ConfigCache; import me.david.util.MessageUtil; -import me.david.util.Scheduler; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.GameMode; @@ -14,11 +13,16 @@ import org.bukkit.entity.Player; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class EventCommand extends BukkitCommand { + private static final List SUBCOMMANDS = List.of( + "start", "stop", "drop", "reset", "autoBorder", + "kickspec", "kickall", "clearall", "setSpawn", "reload" + ); + private static final List AUTO_BORDER_OPTIONS = List.of("on", "off"); + private final EventCore plugin; public EventCommand(EventCore plugin) { @@ -26,17 +30,13 @@ public EventCommand(EventCore plugin) { this.plugin = plugin; } - private String getSoftware() { - return Scheduler.isFOLIA() ? "Folia" : "PaperMC"; - } - @Override public void onCommand(CommandSender sender, String label, String[] args) { if (!(sender instanceof final Player player)) return; if (args.length == 0) { player.sendMessage(" "); - player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§7Running §aEventCore §7v" + plugin.getPluginMeta().getVersion() + " §7on §a" + getSoftware()))); + player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§7Running §aEventCore §7v" + plugin.getPluginMeta().getVersion() + " §7on §aPaperMC"))); player.sendMessage(MessageUtil.getPrefix().append(MessageUtil.translateColorCodes("§7Download at §ahttps://github.com/VertrauterDavid"))); player.sendMessage(" "); } @@ -174,19 +174,18 @@ public List onTabComplete(CommandSender sender, String label, String[] a if (args.length == 2) { if (args[0].equalsIgnoreCase("stop")) { - list.addAll(Bukkit.getOnlinePlayers().stream().map(Player::getName).toList()); - } - - if (args[0].equalsIgnoreCase("autoBorder")) { - list.addAll(Arrays.asList("on", "off")); + for (Player online : Bukkit.getOnlinePlayers()) { + list.add(online.getName()); + } + } else if (args[0].equalsIgnoreCase("autoBorder")) { + list.addAll(AUTO_BORDER_OPTIONS); } + } else if (args.length == 1) { + list.addAll(SUBCOMMANDS); } - if (args.length == 1) { - list.addAll(Arrays.asList("start", "stop", "drop", "reset", "autoBorder", "kickspec", "kickall", "clearall", "setSpawn", "reload")); - } - - return list.stream().filter(content -> content.toLowerCase().startsWith(args[args.length - 1].toLowerCase())).sorted().toList(); + final String prefix = args[args.length - 1].toLowerCase(); + return list.stream().filter(content -> content.toLowerCase().startsWith(prefix)).sorted().toList(); } } diff --git a/src/main/java/me/david/listener/BlockBreakListener.java b/src/main/java/me/david/listener/BlockBreakListener.java index 9b9d2d4..1c2acdf 100644 --- a/src/main/java/me/david/listener/BlockBreakListener.java +++ b/src/main/java/me/david/listener/BlockBreakListener.java @@ -3,22 +3,20 @@ import me.david.EventCore; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; public class BlockBreakListener implements Listener { - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { - final Player player = event.getPlayer(); + if (EventCore.getInstance().getGameManager().isRunning()) return; - if (player.hasPermission("event.bypass")) { - return; - } + final Player player = event.getPlayer(); + if (player.hasPermission("event.bypass")) return; - if (!EventCore.getInstance().getGameManager().isRunning()) { - event.setCancelled(true); - } + event.setCancelled(true); } } diff --git a/src/main/java/me/david/listener/BlockExplodeListener.java b/src/main/java/me/david/listener/BlockExplodeListener.java index 74dba06..6d1c8e7 100644 --- a/src/main/java/me/david/listener/BlockExplodeListener.java +++ b/src/main/java/me/david/listener/BlockExplodeListener.java @@ -3,12 +3,13 @@ import me.david.EventCore; import org.bukkit.block.Block; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockExplodeEvent; public class BlockExplodeListener implements Listener { - @EventHandler + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockExplode(BlockExplodeEvent event) { if (!EventCore.getInstance().getGameManager().isRunning()) { event.setCancelled(true); diff --git a/src/main/java/me/david/listener/BlockPlaceListener.java b/src/main/java/me/david/listener/BlockPlaceListener.java index 3e20a80..1178e27 100644 --- a/src/main/java/me/david/listener/BlockPlaceListener.java +++ b/src/main/java/me/david/listener/BlockPlaceListener.java @@ -4,18 +4,16 @@ import me.david.util.ConfigCache; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPlaceEvent; public class BlockPlaceListener implements Listener { - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockPlace(BlockPlaceEvent event) { final Player player = event.getPlayer(); - - if (player.hasPermission("event.bypass")) { - return; - } + if (player.hasPermission("event.bypass")) return; if (event.getBlock().getY() > ConfigCache.maxBuildHeight) { event.setCancelled(true); diff --git a/src/main/java/me/david/listener/CreatureSpawnListener.java b/src/main/java/me/david/listener/CreatureSpawnListener.java index 764a285..4c0ccfa 100644 --- a/src/main/java/me/david/listener/CreatureSpawnListener.java +++ b/src/main/java/me/david/listener/CreatureSpawnListener.java @@ -1,6 +1,7 @@ package me.david.listener; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; @@ -18,7 +19,7 @@ public class CreatureSpawnListener implements Listener { CreatureSpawnEvent.SpawnReason.ENDER_PEARL ); - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onCreatureSpawn(CreatureSpawnEvent event) { if (BLOCKED.contains(event.getSpawnReason())) { event.setCancelled(true); diff --git a/src/main/java/me/david/listener/EntityDamageByEntityListener.java b/src/main/java/me/david/listener/EntityDamageByEntityListener.java index b02b555..c68dbce 100644 --- a/src/main/java/me/david/listener/EntityDamageByEntityListener.java +++ b/src/main/java/me/david/listener/EntityDamageByEntityListener.java @@ -5,13 +5,14 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; public class EntityDamageByEntityListener implements Listener { - @EventHandler + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { final Entity entity = event.getEntity(); final Entity damager = event.getDamager(); diff --git a/src/main/java/me/david/listener/EntityDamageListener.java b/src/main/java/me/david/listener/EntityDamageListener.java index e63832f..1909eb9 100644 --- a/src/main/java/me/david/listener/EntityDamageListener.java +++ b/src/main/java/me/david/listener/EntityDamageListener.java @@ -6,13 +6,14 @@ import org.bukkit.WorldBorder; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.util.Vector; public class EntityDamageListener implements Listener { - @EventHandler + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityDamage(EntityDamageEvent event) { final EntityDamageEvent.DamageCause cause = event.getCause(); diff --git a/src/main/java/me/david/listener/EntityExplodeListener.java b/src/main/java/me/david/listener/EntityExplodeListener.java index 9a8ccc9..c3e51e1 100644 --- a/src/main/java/me/david/listener/EntityExplodeListener.java +++ b/src/main/java/me/david/listener/EntityExplodeListener.java @@ -3,12 +3,13 @@ import me.david.EventCore; import org.bukkit.block.Block; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityExplodeEvent; public class EntityExplodeListener implements Listener { - @EventHandler + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityExplode(EntityExplodeEvent event) { if (!EventCore.getInstance().getGameManager().isRunning()) { event.setCancelled(true); diff --git a/src/main/java/me/david/listener/PlayerDropItemListener.java b/src/main/java/me/david/listener/PlayerDropItemListener.java index 3cb2a9e..a98c89d 100644 --- a/src/main/java/me/david/listener/PlayerDropItemListener.java +++ b/src/main/java/me/david/listener/PlayerDropItemListener.java @@ -4,19 +4,18 @@ import me.david.util.ConfigCache; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerDropItemEvent; public class PlayerDropItemListener implements Listener { - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerDropItem(PlayerDropItemEvent event) { if (ConfigCache.allowItemDropBeforeStart) return; final Player player = event.getPlayer(); - if (player.hasPermission("event.bypass")) { - return; - } + if (player.hasPermission("event.bypass")) return; if (!EventCore.getInstance().getGameManager().isRunning()) { event.setCancelled(true); diff --git a/src/main/java/me/david/listener/PlayerInteractListener.java b/src/main/java/me/david/listener/PlayerInteractListener.java index c325453..52f3979 100644 --- a/src/main/java/me/david/listener/PlayerInteractListener.java +++ b/src/main/java/me/david/listener/PlayerInteractListener.java @@ -3,22 +3,20 @@ import me.david.EventCore; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEvent; public class PlayerInteractListener implements Listener { - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerInteractEvent(PlayerInteractEvent event) { - final Player player = event.getPlayer(); + if (EventCore.getInstance().getGameManager().isRunning()) return; - if (player.hasPermission("event.bypass")) { - return; - } + final Player player = event.getPlayer(); + if (player.hasPermission("event.bypass")) return; - if (!EventCore.getInstance().getGameManager().isRunning()) { - event.setCancelled(true); - } + event.setCancelled(true); } } diff --git a/src/main/java/me/david/listener/PlayerPickupItemListener.java b/src/main/java/me/david/listener/PlayerPickupItemListener.java index ebdd2a3..295f7a1 100644 --- a/src/main/java/me/david/listener/PlayerPickupItemListener.java +++ b/src/main/java/me/david/listener/PlayerPickupItemListener.java @@ -2,18 +2,21 @@ import me.david.EventCore; import me.david.util.ConfigCache; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; public class PlayerPickupItemListener implements Listener { - @EventHandler(ignoreCancelled = true) - public void onPlayerPickUpItem(PlayerPickupItemEvent event) { + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerPickUpItem(EntityPickupItemEvent event) { if (ConfigCache.allowItemDropBeforeStart) return; - final Player player = event.getPlayer(); + final LivingEntity entity = event.getEntity(); + if (!(entity instanceof final Player player)) return; if (player.hasPermission("event.bypass")) { return; } diff --git a/src/main/java/me/david/listener/PlayerTeleportListener.java b/src/main/java/me/david/listener/PlayerTeleportListener.java index 3ac8ab1..6e12adb 100644 --- a/src/main/java/me/david/listener/PlayerTeleportListener.java +++ b/src/main/java/me/david/listener/PlayerTeleportListener.java @@ -2,20 +2,24 @@ import me.david.util.ConfigCache; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerTeleportEvent; public class PlayerTeleportListener implements Listener { - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerTeleport(PlayerTeleportEvent event) { if (!ConfigCache.borderDisableEnderPearls) return; if (event.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) return; final Location to = event.getTo(); if (to == null) return; - if (!to.getWorld().getWorldBorder().isInside(to)) { + final World world = to.getWorld(); + if (world == null) return; + if (!world.getWorldBorder().isInside(to)) { event.setCancelled(true); } } diff --git a/src/main/java/me/david/manager/GameManager.java b/src/main/java/me/david/manager/GameManager.java index bb47651..3e010ba 100644 --- a/src/main/java/me/david/manager/GameManager.java +++ b/src/main/java/me/david/manager/GameManager.java @@ -19,7 +19,9 @@ import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import java.util.List; import java.util.Map; @Getter @@ -28,10 +30,10 @@ public class GameManager { private volatile boolean running = false; private volatile boolean timerRunning = false; - private Scheduler.TaskWrapper startTask; - private Scheduler.TaskWrapper autoStopTask; - private Scheduler.TaskWrapper autoDropTask; - private Scheduler.TaskWrapper timerTask; + private BukkitTask startTask; + private BukkitTask autoStopTask; + private BukkitTask autoDropTask; + private BukkitTask timerTask; private int timer; private long inGameTimer; @@ -90,8 +92,8 @@ private void tickStart() { final Component prefix = MessageUtil.getPrefix(); if (current > 0) { - final String color = EventCore.getInstance().getConfig() - .getString("Messages.StartTimer.Colors." + current + "sec", ""); + final List colors = ConfigCache.startTimerColors; + final String color = current < colors.size() ? colors.get(current) : ""; final Map replacements = Map.of( "%timer%", MessageUtil.translateColorCodes(color + current + "§7"), "%prefix%", prefix @@ -130,8 +132,7 @@ private void tickStart() { } for (String command : ConfigCache.startCustomCommands) { - Scheduler.dispatchCommand(() -> - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1))); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)); } running = true; @@ -181,8 +182,7 @@ public void stop(final String winner) { } for (String command : ConfigCache.stopCustomCommands) { - Scheduler.dispatchCommand(() -> - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1))); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)); } if (ConfigCache.autoMapReset) { diff --git a/src/main/java/me/david/manager/MapManager.java b/src/main/java/me/david/manager/MapManager.java index 1453d0d..ae9e38b 100644 --- a/src/main/java/me/david/manager/MapManager.java +++ b/src/main/java/me/david/manager/MapManager.java @@ -92,15 +92,14 @@ public void drop() { final Location edgeMax = spawn.clone().add(halfExtent, 0, halfExtent); final String worldName = world.getName(); - Scheduler.dispatchCommand(() -> { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/world " + worldName); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/pos1 " + edgeMin.getBlockX() + ",-63," + edgeMin.getBlockZ()); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/pos2 " + edgeMax.getBlockX() + ",350," + edgeMax.getBlockZ()); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/set 0"); - for (String command : ConfigCache.dropCustomCommands) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)); - } - }); + final var console = Bukkit.getConsoleSender(); + Bukkit.dispatchCommand(console, "/world " + worldName); + Bukkit.dispatchCommand(console, "/pos1 " + edgeMin.getBlockX() + ",-63," + edgeMin.getBlockZ()); + Bukkit.dispatchCommand(console, "/pos2 " + edgeMax.getBlockX() + ",350," + edgeMax.getBlockZ()); + Bukkit.dispatchCommand(console, "/set 0"); + for (String command : ConfigCache.dropCustomCommands) { + Bukkit.dispatchCommand(console, command.substring(1)); + } } public void reset() { @@ -110,11 +109,10 @@ public void reset() { return; } - Scheduler.dispatchCommand(() -> { - for (String command : ConfigCache.mapResetCommands) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.substring(1)); - } - }); + final var console = Bukkit.getConsoleSender(); + for (String command : ConfigCache.mapResetCommands) { + Bukkit.dispatchCommand(console, command.substring(1)); + } } } diff --git a/src/main/java/me/david/util/BorderUtil.java b/src/main/java/me/david/util/BorderUtil.java index b9ad7f4..d277241 100644 --- a/src/main/java/me/david/util/BorderUtil.java +++ b/src/main/java/me/david/util/BorderUtil.java @@ -50,7 +50,11 @@ public void run() { final int optimal = getOptimalSize(); if (lastOptimal > optimal) { lastOptimal = optimal; - Scheduler.runSync(() -> border.setSize(optimal, (long) (current - optimal))); + // changeSize lerps the border from the current value to the target over + // the given number of seconds – this is the non-deprecated replacement + // for the old {@code setSize(double, long)} overload. + final long shrinkSeconds = (long) (current - optimal); + Scheduler.runSync(() -> border.changeSize(optimal, shrinkSeconds)); } } diff --git a/src/main/java/me/david/util/ConfigCache.java b/src/main/java/me/david/util/ConfigCache.java index 2799ddf..76ed021 100644 --- a/src/main/java/me/david/util/ConfigCache.java +++ b/src/main/java/me/david/util/ConfigCache.java @@ -2,8 +2,10 @@ import lombok.experimental.UtilityClass; import me.david.EventCore; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; +import java.util.ArrayList; import java.util.List; /** @@ -46,6 +48,11 @@ public class ConfigCache { public boolean playerDeathMessageEnabled; public int startTimerSeconds; public boolean announcementTitleEnabled; + /** Index = seconds remaining, value = color prefix string for that step (may be empty). */ + public List startTimerColors = List.of(); + + // PlaceholderAPI availability, re-checked on reload to track the plugin's state + public boolean papiAvailable; // Settings.Updates.* public boolean updatesNotifyOnJoin; @@ -105,6 +112,17 @@ public void reload() { startTimerSeconds = cfg.getInt("Messages.StartTimer.Timer", 5); announcementTitleEnabled = cfg.getBoolean("Messages.AnnoucementCommand.Title.Enabled", true); + // Preload every "sec" color so the per-tick hot path stops touching + // the config entirely. Slot 0 is unused (the zero-second tick skips into the + // start-message branch), slots 1..startTimerSeconds hold the configured colors. + final List colors = new ArrayList<>(startTimerSeconds + 1); + for (int i = 0; i <= startTimerSeconds; i++) { + colors.add(cfg.getString("Messages.StartTimer.Colors." + i + "sec", "")); + } + startTimerColors = List.copyOf(colors); + + papiAvailable = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + updatesNotifyOnJoin = cfg.getBoolean("Settings.Updates.NotifyOnJoin", true); updatesLogInConsole = cfg.getBoolean("Settings.Updates.LogInConsole", false); @@ -125,5 +143,8 @@ public void reload() { mapResetCommands = List.copyOf(cfg.getStringList("Settings.MapReset.Commands")); startCustomCommands = List.copyOf(cfg.getStringList("Settings.Start.CustomCommands")); stopCustomCommands = List.copyOf(cfg.getStringList("Settings.Stop.CustomCommands")); + + // Drop memoized Components so callers re-read the new values on next access. + MessageUtil.invalidatePrefix(); } } diff --git a/src/main/java/me/david/util/HostUtil.java b/src/main/java/me/david/util/HostUtil.java index fec91e2..e194b88 100644 --- a/src/main/java/me/david/util/HostUtil.java +++ b/src/main/java/me/david/util/HostUtil.java @@ -31,7 +31,7 @@ public void giveHost(final @NotNull Player player) { final String rawCommand = ConfigCache.hostRankJoinCommand; if (rawCommand != null && !rawCommand.isEmpty()) { final String command = rawCommand.replace("%player%", player.getName()); - Scheduler.dispatchCommand(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); } host.add(player.getUniqueId()); } @@ -44,7 +44,7 @@ public void removeHost(final @NotNull Player player) { final String rawCommand = ConfigCache.hostRankQuitCommand; if (rawCommand != null && !rawCommand.isEmpty()) { final String command = rawCommand.replace("%player%", player.getName()); - Scheduler.dispatchCommand(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); } } } diff --git a/src/main/java/me/david/util/MessageUtil.java b/src/main/java/me/david/util/MessageUtil.java index 8ebf698..bf6abd5 100644 --- a/src/main/java/me/david/util/MessageUtil.java +++ b/src/main/java/me/david/util/MessageUtil.java @@ -16,12 +16,24 @@ public class MessageUtil { private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})"); + private volatile Component cachedPrefix; + public Component get(@NotNull String key) { return translateColorCodes(EventCore.getInstance().getConfig().getString(key, "")); } public Component getPrefix() { - return translateColorCodes(EventCore.getInstance().getConfig().getString("Messages.Prefix", "")); + Component local = cachedPrefix; + if (local == null) { + local = translateColorCodes(EventCore.getInstance().getConfig().getString("Messages.Prefix", "")); + cachedPrefix = local; + } + return local; + } + + /** Invalidate the cached prefix so the next call re-reads from config. */ + public void invalidatePrefix() { + cachedPrefix = null; } public @NotNull Component format(@NotNull String key, @NotNull Map replacements) { diff --git a/src/main/java/me/david/util/PlayerUtil.java b/src/main/java/me/david/util/PlayerUtil.java index 800cc08..ad8b22b 100644 --- a/src/main/java/me/david/util/PlayerUtil.java +++ b/src/main/java/me/david/util/PlayerUtil.java @@ -4,6 +4,8 @@ import me.david.EventCore; import org.bukkit.Bukkit; import org.bukkit.GameMode; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -12,9 +14,13 @@ public class PlayerUtil { public int getAlive() { - return (int) Bukkit.getOnlinePlayers().stream() - .filter(p -> p.getGameMode() == GameMode.SURVIVAL) - .count(); + int alive = 0; + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getGameMode() == GameMode.SURVIVAL) { + alive++; + } + } + return alive; } public int getTotal() { @@ -23,7 +29,8 @@ public int getTotal() { public void cleanPlayer(@NotNull Player player) { player.setGameMode(GameMode.SURVIVAL); - player.setHealth(player.getMaxHealth()); + final AttributeInstance maxHealthAttr = player.getAttribute(Attribute.MAX_HEALTH); + player.setHealth(maxHealthAttr != null ? maxHealthAttr.getValue() : 20.0); player.setFoodLevel(20); player.setFireTicks(0); player.clearActivePotionEffects(); diff --git a/src/main/java/me/david/util/Scheduler.java b/src/main/java/me/david/util/Scheduler.java index 85dc787..8de4430 100644 --- a/src/main/java/me/david/util/Scheduler.java +++ b/src/main/java/me/david/util/Scheduler.java @@ -1,169 +1,46 @@ package me.david.util; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import lombok.Getter; import me.david.EventCore; import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.concurrent.TimeUnit; +/** + * Thin convenience layer around {@link BukkitScheduler}. The plugin targets + * Paper exclusively, so there is no Folia fallback – callers can interact + * directly with {@link BukkitTask}. + */ +public final class Scheduler { -@Getter -@SuppressWarnings("unused") -public class Scheduler { - - @Getter - private static final boolean FOLIA; - - static { - boolean folia; - try { - Class.forName("io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler"); - folia = true; - } catch (ClassNotFoundException e) { - folia = false; - } - FOLIA = folia; + private Scheduler() { } - public interface TaskWrapper { - void cancel(); - boolean isCancelled(); + private static BukkitScheduler scheduler() { + return Bukkit.getScheduler(); } - private static class BukkitTaskWrapper implements TaskWrapper { - private final @NotNull BukkitTask task; - - public BukkitTaskWrapper(@NotNull BukkitTask task) { - this.task = task; - } - - @Override - public void cancel() { - task.cancel(); - } - - @Override - public boolean isCancelled() { - return task.isCancelled(); - } - } - - private static class FoliaTaskWrapper implements TaskWrapper { - private @Nullable Object task; - - public FoliaTaskWrapper(@NotNull Object task) { - this.task = task; - } - - @Override - public void cancel() { - if (task != null) { - try { - Class iface = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask"); - iface.getMethod("cancel").invoke(task); - } catch (Exception ignored) { - } finally { - task = null; - } - } - } - - @Override - public boolean isCancelled() { - if (task == null) return true; - try { - Class iface = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask"); - return (Boolean) iface.getMethod("isCancelled").invoke(task); - } catch (Exception ignored) { - return true; - } - } + private static EventCore plugin() { + return EventCore.getInstance(); } public static void runSync(@NotNull Runnable runnable) { - if (FOLIA) { - Bukkit.getGlobalRegionScheduler().execute(EventCore.getInstance(), runnable); - } else { - Bukkit.getScheduler().runTask(EventCore.getInstance(), runnable); - } + scheduler().runTask(plugin(), runnable); } public static void runAsync(@NotNull Runnable runnable) { - if (FOLIA) { - Bukkit.getAsyncScheduler().runNow(EventCore.getInstance(), (task) -> runnable.run()); - } else { - Bukkit.getScheduler().runTaskAsynchronously(EventCore.getInstance(), runnable); - } + scheduler().runTaskAsynchronously(plugin(), runnable); } - @CanIgnoreReturnValue - public static @NotNull TaskWrapper wait(@NotNull Runnable runnable, long delay) { - final long safeDelay = Math.max(delay, 1); - - if (FOLIA) { - Object foliaTask = Bukkit.getGlobalRegionScheduler().runDelayed(EventCore.getInstance(), - (task) -> runnable.run(), safeDelay); - return new FoliaTaskWrapper(foliaTask); - } else { - BukkitTask bukkitTask = Bukkit.getScheduler().runTaskLater(EventCore.getInstance(), runnable, safeDelay); - return new BukkitTaskWrapper(bukkitTask); - } - } - - @CanIgnoreReturnValue - public static @NotNull TaskWrapper timer(@NotNull Runnable runnable, long delay, long period) { - final long safeDelay = Math.max(delay, 1); - final long safePeriod = Math.max(period, 1); - - if (FOLIA) { - Object foliaTask = Bukkit.getGlobalRegionScheduler().runAtFixedRate(EventCore.getInstance(), - (task) -> runnable.run(), safeDelay, safePeriod); - return new FoliaTaskWrapper(foliaTask); - } else { - BukkitTask bukkitTask = Bukkit.getScheduler().runTaskTimer(EventCore.getInstance(), runnable, safeDelay, safePeriod); - return new BukkitTaskWrapper(bukkitTask); - } + public static @NotNull BukkitTask wait(@NotNull Runnable runnable, long delay) { + return scheduler().runTaskLater(plugin(), runnable, Math.max(delay, 1)); } - @CanIgnoreReturnValue - public static @NotNull TaskWrapper timerAsync(@NotNull Runnable runnable, long delay, long period) { - final long safeDelay = Math.max(delay, 1); - final long safePeriod = Math.max(period, 1); - - if (FOLIA) { - Object foliaTask = Bukkit.getAsyncScheduler().runAtFixedRate(EventCore.getInstance(), - (task) -> runnable.run(), safeDelay * 50, safePeriod * 50, TimeUnit.MILLISECONDS); - return new FoliaTaskWrapper(foliaTask); - } else { - BukkitTask bukkitTask = Bukkit.getScheduler().runTaskTimerAsynchronously(EventCore.getInstance(), - runnable, safeDelay, safePeriod); - return new BukkitTaskWrapper(bukkitTask); - } - } - - public static void cancelTask(@Nullable Object task) { - if (task == null) return; - - if (task instanceof TaskWrapper wrapper) { - wrapper.cancel(); - } else if (task instanceof BukkitTask bukkitTask) { - bukkitTask.cancel(); - } else if (FOLIA) { - try { - task.getClass().getMethod("cancel").invoke(task); - } catch (Exception ignored) { - } - } + public static @NotNull BukkitTask timer(@NotNull Runnable runnable, long delay, long period) { + return scheduler().runTaskTimer(plugin(), runnable, Math.max(delay, 1), Math.max(period, 1)); } - public static void dispatchCommand(@NotNull Runnable commandRunnable) { - if (FOLIA) { - Bukkit.getGlobalRegionScheduler().execute(EventCore.getInstance(), commandRunnable); - } else { - commandRunnable.run(); - } + public static @NotNull BukkitTask timerAsync(@NotNull Runnable runnable, long delay, long period) { + return scheduler().runTaskTimerAsynchronously(plugin(), runnable, Math.max(delay, 1), Math.max(period, 1)); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 58b5224..273ab3d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,6 +4,5 @@ api-version: 1.21 main: me.david.EventCore author: VertrauterDavid, JavaMio website: https://github.com/DavidArchive/EventCore -folia-supported: true softdepend: [PlaceholderAPI] depend: [WorldEdit] \ No newline at end of file