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 e4d7c51..cf1b3eb 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,32 @@ public void onEnable() { @Override public void onDisable() { - if (gameManager.isRunning()) { + if (gameManager != null && gameManager.isRunning()) { gameManager.stop(null); } } + @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; + 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 = 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 2b4a14f..a98afa6 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,48 +11,50 @@ 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; public class AnnouncementCommand extends BukkitCommand { - private final EventCore plugin; - public AnnouncementCommand(EventCore plugin) { super("annoucement", "event.command.annoucement", "announce"); - this.plugin = plugin; } @Override public void onCommand(CommandSender sender, String label, String[] args) { - 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 (!sender.hasPermission("event.command")) return; - 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..1fea297 100644 --- a/src/main/java/me/david/command/impl/EventCommand.java +++ b/src/main/java/me/david/command/impl/EventCommand.java @@ -3,8 +3,8 @@ 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; import org.bukkit.Bukkit; import org.bukkit.GameMode; @@ -13,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) { @@ -25,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(" "); } @@ -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; } @@ -167,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/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..1c2acdf 100644 --- a/src/main/java/me/david/listener/BlockBreakListener.java +++ b/src/main/java/me/david/listener/BlockBreakListener.java @@ -3,21 +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 + @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")) { - event.setCancelled(false); - return; - } + final Player player = event.getPlayer(); + if (player.hasPermission("event.bypass")) return; - event.setCancelled(!(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..6d1c8e7 100644 --- a/src/main/java/me/david/listener/BlockExplodeListener.java +++ b/src/main/java/me/david/listener/BlockExplodeListener.java @@ -1,16 +1,23 @@ package me.david.listener; 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) { - 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..1178e27 100644 --- a/src/main/java/me/david/listener/BlockPlaceListener.java +++ b/src/main/java/me/david/listener/BlockPlaceListener.java @@ -1,28 +1,28 @@ 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.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPlaceEvent; public class BlockPlaceListener implements Listener { - @EventHandler + @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")) { - event.setCancelled(false); + if (event.getBlock().getY() > ConfigCache.maxBuildHeight) { + event.setCancelled(true); return; } - if (event.getBlock().getLocation().getBlockY() > EventCore.getInstance().getConfig().getLong("Settings.MaxBuildHeight", 0L)) { + if (!EventCore.getInstance().getGameManager().isRunning()) { event.setCancelled(true); - return; } - - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); } } diff --git a/src/main/java/me/david/listener/CreatureSpawnListener.java b/src/main/java/me/david/listener/CreatureSpawnListener.java index 2632952..4c0ccfa 100644 --- a/src/main/java/me/david/listener/CreatureSpawnListener.java +++ b/src/main/java/me/david/listener/CreatureSpawnListener.java @@ -1,14 +1,27 @@ 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; +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(priority = EventPriority.HIGH, 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..c68dbce 100644 --- a/src/main/java/me/david/listener/EntityDamageByEntityListener.java +++ b/src/main/java/me/david/listener/EntityDamageByEntityListener.java @@ -1,43 +1,45 @@ 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; +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(); - - 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..1909eb9 100644 --- a/src/main/java/me/david/listener/EntityDamageListener.java +++ b/src/main/java/me/david/listener/EntityDamageListener.java @@ -1,56 +1,61 @@ 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; +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) { - 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..c3e51e1 100644 --- a/src/main/java/me/david/listener/EntityExplodeListener.java +++ b/src/main/java/me/david/listener/EntityExplodeListener.java @@ -1,16 +1,23 @@ package me.david.listener; 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) { - 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..a98c89d 100644 --- a/src/main/java/me/david/listener/PlayerDropItemListener.java +++ b/src/main/java/me/david/listener/PlayerDropItemListener.java @@ -1,25 +1,25 @@ 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.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerDropItemEvent; public class PlayerDropItemListener implements Listener { - @EventHandler + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerDropItem(PlayerDropItemEvent event) { - final Player player = event.getPlayer(); + if (ConfigCache.allowItemDropBeforeStart) return; - if (EventCore.getInstance().getConfig().getBoolean("Settings.AllowItemDropBeforeStart")) return; + final Player player = event.getPlayer(); + if (player.hasPermission("event.bypass")) return; - if (player.hasPermission("event.bypass")) { - event.setCancelled(false); - return; + if (!EventCore.getInstance().getGameManager().isRunning()) { + event.setCancelled(true); } - - event.setCancelled(!(EventCore.getInstance().getGameManager().isRunning())); } } diff --git a/src/main/java/me/david/listener/PlayerInteractListener.java b/src/main/java/me/david/listener/PlayerInteractListener.java index 58d3e8a..52f3979 100644 --- a/src/main/java/me/david/listener/PlayerInteractListener.java +++ b/src/main/java/me/david/listener/PlayerInteractListener.java @@ -3,21 +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 + @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")) { - event.setCancelled(false); - return; - } + final Player player = event.getPlayer(); + if (player.hasPermission("event.bypass")) return; - event.setCancelled(!(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..295f7a1 100644 --- a/src/main/java/me/david/listener/PlayerPickupItemListener.java +++ b/src/main/java/me/david/listener/PlayerPickupItemListener.java @@ -1,25 +1,29 @@ package me.david.listener; 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 - public void onPlayerPickUpItem(PlayerPickupItemEvent event) { - final Player player = event.getPlayer(); - - if (EventCore.getInstance().getConfig().getBoolean("Settings.AllowItemDropBeforeStart")) return; + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerPickUpItem(EntityPickupItemEvent event) { + if (ConfigCache.allowItemDropBeforeStart) return; + final LivingEntity entity = event.getEntity(); + if (!(entity instanceof final Player player)) return; 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..6e12adb 100644 --- a/src/main/java/me/david/listener/PlayerTeleportListener.java +++ b/src/main/java/me/david/listener/PlayerTeleportListener.java @@ -1,25 +1,26 @@ 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.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerTeleportEvent; public class PlayerTeleportListener implements Listener { - @EventHandler + @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; final World world = to.getWorld(); - - if (EventCore.getInstance().getConfig().getBoolean("Settings.WorldBorder.DisableEnderPeals")) { - if (event.getCause() == PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { - if (!(world.getWorldBorder().isInside(to))) { - event.setCancelled(true); - } - } + 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 e760bf7..3e010ba 100644 --- a/src/main/java/me/david/manager/GameManager.java +++ b/src/main/java/me/david/manager/GameManager.java @@ -7,28 +7,35 @@ 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 org.bukkit.scheduler.BukkitTask; +import java.util.List; 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; - 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 AtomicInteger timer; + private int timer; private long inGameTimer; private boolean autoDropped = false; @@ -40,78 +47,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 +73,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 +84,87 @@ 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 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 + ); + 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) { + 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 +181,11 @@ 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) { + 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..ae9e38b 100644 --- a/src/main/java/me/david/manager/MapManager.java +++ b/src/main/java/me/david/manager/MapManager.java @@ -5,74 +5,114 @@ 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); - - Scheduler.dispatchCommand(() -> { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "/world " + spawnLocation.getWorld().getName()); - 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))); - }); + 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(); + + 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() { - 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)))); + 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/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..d277241 100644 --- a/src/main/java/me/david/util/BorderUtil.java +++ b/src/main/java/me/david/util/BorderUtil.java @@ -1,44 +1,67 @@ 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; + // 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)); } } 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..76ed021 --- /dev/null +++ b/src/main/java/me/david/util/ConfigCache.java @@ -0,0 +1,150 @@ +package me.david.util; + +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; + +/** + * 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; + /** 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; + 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); + + // 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); + + 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")); + + // 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 3b3d5c5..e194b88 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()); + 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()); + 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/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/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/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 1778c70..8de4430 100644 --- a/src/main/java/me/david/util/Scheduler.java +++ b/src/main/java/me/david/util/Scheduler.java @@ -1,161 +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; - } - - public interface TaskWrapper { - void cancel(); - boolean isCancelled(); + private Scheduler() { } - 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 BukkitScheduler scheduler() { + return Bukkit.getScheduler(); } - 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); - } - } - - @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); - } + scheduler().runTask(plugin(), runnable); } - @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 void runAsync(@NotNull Runnable runnable) { + scheduler().runTaskAsynchronously(plugin(), runnable); } - @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 @NotNull BukkitTask wait(@NotNull Runnable runnable, long delay) { + return scheduler().runTaskLater(plugin(), runnable, Math.max(delay, 1)); } - 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/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 +} 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