From ab9f18e9abe3b0c00bf785cbe3a701f73728ba2d Mon Sep 17 00:00:00 2001 From: acrylic-style Date: Thu, 14 Sep 2023 21:15:32 +0900 Subject: [PATCH] feat: add /schedulerestart and related features --- build.gradle.kts | 2 +- .../com/github/mori01231/lifecore/LifeCore.kt | 6 +- .../GCListenerRestartExtendTimeCommand.java | 41 ++++++ .../command/ScheduleRestartCommand.java | 135 ++++++++++++++++++ .../listener/item/LavaSpongeItemListener.java | 54 +++++++ .../mori01231/lifecore/util/GCListener.java | 72 ++++++---- src/main/resources/plugin.yml | 16 +++ 7 files changed, 300 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/github/mori01231/lifecore/command/GCListenerRestartExtendTimeCommand.java create mode 100644 src/main/java/com/github/mori01231/lifecore/command/ScheduleRestartCommand.java create mode 100644 src/main/java/com/github/mori01231/lifecore/listener/item/LavaSpongeItemListener.java diff --git a/build.gradle.kts b/build.gradle.kts index cd3cf2f..69aa3dc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "net.azisaba" -version = "6.6.13" +version = "6.7.0" java { toolchain.languageVersion.set(JavaLanguageVersion.of(8)) diff --git a/src/main/java/com/github/mori01231/lifecore/LifeCore.kt b/src/main/java/com/github/mori01231/lifecore/LifeCore.kt index a08aecd..63bda65 100644 --- a/src/main/java/com/github/mori01231/lifecore/LifeCore.kt +++ b/src/main/java/com/github/mori01231/lifecore/LifeCore.kt @@ -5,6 +5,7 @@ import com.github.mori01231.lifecore.config.* import com.github.mori01231.lifecore.gui.DropProtectScreen import com.github.mori01231.lifecore.listener.* import com.github.mori01231.lifecore.listener.item.GlassHammerItemListener +import com.github.mori01231.lifecore.listener.item.LavaSpongeItemListener import com.github.mori01231.lifecore.listener.item.OreOnlyItemListener import com.github.mori01231.lifecore.listener.item.WandItemListener import com.github.mori01231.lifecore.network.PacketHandler @@ -31,7 +32,7 @@ import java.util.concurrent.Executor import java.util.concurrent.Executors class LifeCore : JavaPlugin() { - private val gcListener = GCListener(this) + val gcListener = GCListener(this) @JvmField val ngWordsCache = NGWordsCache() private val executorService = Executors.newFixedThreadPool(2) @@ -124,6 +125,8 @@ class LifeCore : JavaPlugin() { registerCommand("fixtime", FixTimeCommand) registerCommand("lifecoreconfig", LifeCoreConfigCommand(this)) registerCommand("townconfig", TownConfigCommand(this)) + registerCommand("schedulerestart", ScheduleRestartCommand(this)) + registerCommand("gclistenerrestartextendtimecommand", GCListenerRestartExtendTimeCommand(this)) registerCommand("respawn") { _, _, _, args -> args.getOrNull(0)?.let { Bukkit.getPlayerExact(it)?.spigot()?.respawn() } true @@ -253,6 +256,7 @@ class LifeCore : JavaPlugin() { // Items pm.registerEvents(OreOnlyItemListener(), this) pm.registerEvents(GlassHammerItemListener(), this) + pm.registerEvents(LavaSpongeItemListener(), this) if (config.getBoolean("destroy-experience-orb-on-chunk-load", false)) { pm.registerEvents(DestroyExperienceOrbListener(), this) } diff --git a/src/main/java/com/github/mori01231/lifecore/command/GCListenerRestartExtendTimeCommand.java b/src/main/java/com/github/mori01231/lifecore/command/GCListenerRestartExtendTimeCommand.java new file mode 100644 index 0000000..c5c791b --- /dev/null +++ b/src/main/java/com/github/mori01231/lifecore/command/GCListenerRestartExtendTimeCommand.java @@ -0,0 +1,41 @@ +package com.github.mori01231.lifecore.command; + +import com.github.mori01231.lifecore.LifeCore; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class GCListenerRestartExtendTimeCommand implements TabExecutor { + private final LifeCore plugin; + private final Set players = new HashSet<>(); + + public GCListenerRestartExtendTimeCommand(@NotNull LifeCore plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!plugin.getGcListener().isTriggered()) return true; + if (!(sender instanceof Player)) { + return true; + } + Player player = (Player) sender; + players.add(player.getUniqueId()); + sender.sendMessage(ChatColor.GREEN + "投票しました。"); + if (sender.hasPermission("lifecore.extend-time-immediately") || players.size() == 5) { + ScheduleRestartCommand.schedule(30); + } + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/github/mori01231/lifecore/command/ScheduleRestartCommand.java b/src/main/java/com/github/mori01231/lifecore/command/ScheduleRestartCommand.java new file mode 100644 index 0000000..ebe4e92 --- /dev/null +++ b/src/main/java/com/github/mori01231/lifecore/command/ScheduleRestartCommand.java @@ -0,0 +1,135 @@ +package com.github.mori01231.lifecore.command; + +import com.github.mori01231.lifecore.LifeCore; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class ScheduleRestartCommand implements TabExecutor { + private static final Map> ACTIONS = new HashMap<>(); + private static final List tasks = new ArrayList<>(); + private final LifeCore plugin; + + public ScheduleRestartCommand(@NotNull LifeCore plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 0) { + sender.sendMessage(ChatColor.RED + "/schedulerestart "); + return true; + } + if (args[0].equalsIgnoreCase("gc")) { + plugin.getGcListener().triggerNow(); + return true; + } + int minutes = Integer.parseInt(args[0]); + schedule(minutes); + return true; + } + + public static void schedule(int minutes) { + for (BukkitTask task : tasks) { + task.cancel(); + } + tasks.clear(); + Bukkit.broadcastMessage("§6[§dお知らせ§6] §aこのサーバーは§d" + minutes + "分後" + "§aに再起動されます。"); + int maxTicks = minutes * 60 * 20; + ACTIONS.forEach((seconds, actions) -> { + if (seconds < minutes * 60) { + BukkitTask task = Bukkit.getScheduler().runTaskLater(LifeCore.getPlugin(LifeCore.class), () -> { + for (Action action : actions) { + action.execute(seconds); + } + }, maxTicks - seconds * 20); + tasks.add(task); + } + }); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + return Collections.singletonList("gc"); + } + + public enum Action { + BROADCAST { + @Override + public void execute(int seconds) { + if (seconds >= 60) { + int minutes = seconds / 60; + Bukkit.broadcastMessage("§6[§dお知らせ§6] §aこのサーバーは§d" + minutes + "分後" + "§aに再起動されます。"); + } else { + Bukkit.broadcastMessage("§6[§dお知らせ§6] §aこのサーバーは§c" + seconds + "秒後" + "§aに再起動されます。"); + } + } + }, + ENABLE_WHITELIST { + @Override + public void execute(int seconds) { + Bukkit.setWhitelist(true); + } + }, + SAVE_AND_KICK_ALL_PLAYERS { + @Override + public void execute(int seconds) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mpdb saveAndKick"); + for (Player player : Bukkit.getOnlinePlayers()) { + player.kickPlayer("Server closed"); + } + } + }, + SCHEDULE_SHUTDOWN_SERVER { + @Override + public void execute(int seconds) { + Bukkit.getScheduler().runTaskLater(LifeCore.getPlugin(LifeCore.class), Bukkit::shutdown, 20 * 60); + } + }, + ; + + public abstract void execute(int seconds); + } + + @NotNull + @Contract("_ -> new") + private static Set setOf(@NotNull Action... actions) { + return new HashSet<>(Arrays.asList(actions)); + } + + static { + ACTIONS.put(60 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(55 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(50 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(45 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(40 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(35 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(30 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(25 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(20 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(15 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(10 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(5 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(4 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(3 * 60, setOf(Action.BROADCAST)); + ACTIONS.put(2 * 60, setOf(Action.BROADCAST, Action.ENABLE_WHITELIST)); + ACTIONS.put(60, setOf(Action.BROADCAST)); + ACTIONS.put(30, setOf(Action.BROADCAST)); + ACTIONS.put(10, setOf(Action.BROADCAST)); + ACTIONS.put(5, setOf(Action.BROADCAST)); + ACTIONS.put(4, setOf(Action.BROADCAST)); + ACTIONS.put(3, setOf(Action.BROADCAST)); + ACTIONS.put(2, setOf(Action.BROADCAST)); + ACTIONS.put(1, setOf(Action.BROADCAST)); + ACTIONS.put(0, setOf(Action.SAVE_AND_KICK_ALL_PLAYERS, Action.SCHEDULE_SHUTDOWN_SERVER)); + } +} diff --git a/src/main/java/com/github/mori01231/lifecore/listener/item/LavaSpongeItemListener.java b/src/main/java/com/github/mori01231/lifecore/listener/item/LavaSpongeItemListener.java new file mode 100644 index 0000000..1573543 --- /dev/null +++ b/src/main/java/com/github/mori01231/lifecore/listener/item/LavaSpongeItemListener.java @@ -0,0 +1,54 @@ +package com.github.mori01231.lifecore.listener.item; + +import com.github.mori01231.lifecore.util.ItemUtil; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.util.RayTraceResult; + +public class LavaSpongeItemListener implements Listener { + private static final String ITEM_ID = "56fabea9-e1f9-4e7f-ae78-83e07e8b8767"; + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerInteract(PlayerInteractEvent e) { + if (e.getAction() != Action.RIGHT_CLICK_AIR && e.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.useItemInHand() == Event.Result.DENY) return; + RayTraceResult result = e.getPlayer().rayTraceBlocks(4, FluidCollisionMode.ALWAYS); + if (result == null || result.getHitBlock() == null) { + return; + } + if (!ITEM_ID.equals(ItemUtil.getStringTag(e.getItem(), "LifeItemId"))) { + // wrong item + return; + } + int affected = 0; + int radius = 2; + for (int dx = -radius; dx <= radius; dx++) { + for (int dy = -radius; dy <= radius; dy++) { + for (int dz = -radius; dz <= radius; dz++) { + Location loc = result.getHitBlock().getLocation().clone().add(dx, dy, dz); + Block block = loc.getBlock(); + if (block.getType() != Material.LAVA && block.getType() != Material.WATER) { + // wrong block + continue; + } + if (new BlockBreakEvent(block, e.getPlayer()).callEvent()) { + block.setType(Material.AIR, true); + affected++; + } + } + } + } + if (affected > 0 && e.getPlayer().getGameMode() != GameMode.CREATIVE) { + // TODO: reduce durability + } + } +} diff --git a/src/main/java/com/github/mori01231/lifecore/util/GCListener.java b/src/main/java/com/github/mori01231/lifecore/util/GCListener.java index c649f39..dc7f620 100644 --- a/src/main/java/com/github/mori01231/lifecore/util/GCListener.java +++ b/src/main/java/com/github/mori01231/lifecore/util/GCListener.java @@ -1,7 +1,11 @@ package com.github.mori01231.lifecore.util; import com.github.mori01231.lifecore.LifeCore; +import com.github.mori01231.lifecore.command.ScheduleRestartCommand; import com.sun.management.GarbageCollectionNotificationInfo; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.Bukkit; import javax.management.ListenerNotFoundException; @@ -17,7 +21,7 @@ public class GCListener implements NotificationListener { private final LifeCore plugin; private long lastGcTime = 0L; private int count = 0; - + public GCListener(LifeCore plugin) { this.plugin = plugin; } @@ -52,9 +56,13 @@ public void unregister() { } } + public boolean isTriggered() { + return count < 0; + } + public synchronized void handleGc() { - if (count < 0) { - // exceeded max count + if (isTriggered()) { + // exceeded max count or triggered manually return; } long minutes = (System.currentTimeMillis() - lastGcTime) / 1000 / 60; @@ -66,29 +74,45 @@ public synchronized void handleGc() { return; } if (count >= plugin.getConfig().getInt("gc-threshold-count", 10)) { - count = -1; - for (String command : plugin.getConfig().getStringList("gc-triggered-command")) { - try { - if (command.startsWith("@delay ")) { - try { - int delayTicks = Integer.parseInt(command.substring(7, command.indexOf(' ', 7))); - String actualCommand = command.substring(command.indexOf(' ', 7) + 1); - Bukkit.getScheduler().runTaskLater( - plugin, - () -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), actualCommand), - delayTicks); - } catch (IllegalArgumentException e) { - plugin.getLogger().warning("Invalid @delay: " + command); - e.printStackTrace(); - } - } else { - Bukkit.getScheduler().runTask(plugin, () -> - Bukkit.dispatchCommand(plugin.getServer().getConsoleSender(), command)); + triggerNow(); + } + } + + public synchronized void triggerNow() { + if (isTriggered()) return; + count = -1; + for (String command : plugin.getConfig().getStringList("gc-triggered-command")) { + try { + if (command.startsWith("@delay ")) { + try { + int delayTicks = Integer.parseInt(command.substring(7, command.indexOf(' ', 7))); + String actualCommand = command.substring(command.indexOf(' ', 7) + 1); + Bukkit.getScheduler().runTaskLater( + plugin, + () -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), actualCommand), + delayTicks); + } catch (IllegalArgumentException e) { + plugin.getSLF4JLogger().warn("Invalid @delay: " + command, e); + } + } else if (command.startsWith("@schedulerestart ")) { + try { + int delayMinutes = Integer.parseInt(command.substring("@schedulerestart ".length())); + ScheduleRestartCommand.schedule(delayMinutes); + TextComponent component = new TextComponent("再起動までの時間を延長するには、このメッセージをクリックしてください。"); + component.setColor(ChatColor.AQUA); + component.setUnderlined(true); + component.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/gclistenerrestartextendtimecommand")); + Bukkit.broadcast(component); + Bukkit.broadcastMessage("§a5人が上のメッセージをクリックすると、30分に延長されます。"); + } catch (IllegalArgumentException e) { + plugin.getSLF4JLogger().warn("Invalid @schedulerestart: " + command, e); } - } catch (Exception e) { - plugin.getLogger().warning("Failed to process command: " + command); - e.printStackTrace(); + } else { + Bukkit.getScheduler().runTask(plugin, () -> + Bukkit.dispatchCommand(plugin.getServer().getConsoleSender(), command)); } + } catch (Exception e) { + plugin.getSLF4JLogger().warn("Failed to process command: " + command, e); } } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index bb5542d..6a5fe21 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -153,6 +153,13 @@ commands: description: "manage town config" permission: lifecore.townconfig usage: "/townconfig" + schedulerestart: + description: "schedule a restart" + permission: lifecore.schedulerestart + usage: "/" + gclistenerrestartextendtimecommand: + permission: lifecore.gclistenerrestartextendtimecommand + usage: "/" permissions: lifecore.*: @@ -192,6 +199,9 @@ permissions: lifecore.lifecoreconfig: true lifecore.lobby-bypass: true lifecore.bypass-town-restrictions: true + lifecore.schedulerestart: true + lifecore.gclistenerrestartextendtimecommand: true + lifecore.lifecore.extend-time-immediately: true lifecore.wiki: description: Allows you to show the wiki URL. default: true @@ -292,3 +302,9 @@ permissions: default: true lifecore.bypass-town-restrictions: default: false + lifecore.schedulerestart: + default: false + lifecore.gclistenerrestartextendtimecommand: + default: true + lifecore.lifecore.extend-time-immediately: + default: false