diff --git a/README.md b/README.md index 9cc7747..9a4c101 100644 --- a/README.md +++ b/README.md @@ -12,49 +12,53 @@ Please report all suspicious behavior. You can do so on any of those: Stuff not many other plugins do. Dependencies: -- **1.21.1** this is mandatory as the plugin uses NMS for some stuff\ - The focus is on [a widely used version](https://bstats.org/global/bukkit) that has [good mod support](https://modrinth.com/modpack/fabulously-optimized/versions?c=release) -- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) (optional, but you lose a lot) +- **1.21.1**, mandatory as the plugin uses NMS for some stuff\ + Why not latest? The focus is on [a widely used version](https://bstats.org/global/bukkit) that has [good mod support](https://modrinth.com/modpack/fabulously-optimized/versions?c=release) +- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) # Features -Those with P need ProtocolLib - ### Expand world border Expands the world border to 30,000,000 (from 29,999,984) -### Hide world border P +### Hide world border Hides the world border. It's still there, just invisible. -### Server brand P +### Server brand Modify the F3 brand, optionally include player's ping and server performance ### Doors Open two doors with one click. Knock on doors. -### MOTD P +### MOTD Random MOTD for every ping ### Chat rooms Chat rooms players can freely create and join. Alerts like death and join messages are only within the player's chat room. +`/chat` - switch chat room \ +`/chatmanage` - create, delete, modify etc. (`tweaks724.chatmanage`) + ### Compass Holding a compass shows a bar with 4 directions and stuff like beds, lodestones, death pos (TODO) etc. ### Pomodoro Self-discipline with a pomodoro timer that's actually forced +`/pomodoro` (`tweaks724.pomodoro`) + ### Updater Updates ALL* your plugins \ *Those on SpigotMC and that release updates there -### Hardcore P +`/updates` - shows available updates (`tweaks724.updates`) + +### Hardcore Hardcore hearts by chance ### Sleep Sleeping doesn't skip night, but speeds it up. The more players, the faster it goes. - -### Instant sleep +- Instant sleep \ One can instantly skip, but only a part of the night. \ There's 5 players on the server. A night is 10 minutes long. \ Each player can instantly skip 2 minutes of the night at any time, even if others aren't sleeping @@ -63,29 +67,19 @@ Each player can instantly skip 2 minutes of the night at any time, even if other Players are given a unique subdomain like "\.example.com" and they must use it to join \ It can be enabled that new players can't join the server without a key -# Commands -### /chat -Changes chatroom +`/tauth` (`tweaks724.tauth`) -### /chatmanage -`tweaks724.chatmanage` \ -Manages chatroom, like create, delete etc. +### Full join +Players with `tweaks724.bypass-full` can join even when the server is full -### /ping -Displays your ping. \ +### Emergency alerts +Issue messages that the player needs to read to keep playing, and that make an attention grabbing sound + +`/emergencyalerts` (`tweaks724.emergencyalerts`) + +### Utility commands + +- `/ping` - displays player ping \ **Ping is calculated by the plugin**. \ That allows for more precision (decimal places) and to get the ping immediately after a player join -### /pomodoro -`tweaks724.pomodoro` \ -Manage your pomodoro -- `/pom start` to start -- `/pom stop` to stop -- `/pom` to skip stage - -### /tauth -`tweaks724.tauth` \ -Manages authentication keys. -- `/tauth ` to see who the key is bound to -- `/tauth new` to create a key -- `/tauth delete ` to delete a key \ No newline at end of file diff --git a/src/main/java/eu/m724/tweaks/TweaksPlugin.java b/src/main/java/eu/m724/tweaks/TweaksPlugin.java index c5c4530..b829fcd 100644 --- a/src/main/java/eu/m724/tweaks/TweaksPlugin.java +++ b/src/main/java/eu/m724/tweaks/TweaksPlugin.java @@ -7,6 +7,7 @@ package eu.m724.tweaks; import eu.m724.mstats.MStatsPlugin; +import eu.m724.tweaks.alert.AlertManager; import eu.m724.tweaks.auth.AuthManager; import eu.m724.tweaks.chat.ChatCommands; import eu.m724.tweaks.chat.ChatManager; @@ -100,6 +101,8 @@ public class TweaksPlugin extends MStatsPlugin { new AuthManager(this).init(getCommand("tauth")); } + new AlertManager(this).init(getCommand("emergencyalert")); + this.getServer().getPluginManager().registerEvents(new FullListener(), this); if (config.metrics()) diff --git a/src/main/java/eu/m724/tweaks/alert/Alert.java b/src/main/java/eu/m724/tweaks/alert/Alert.java new file mode 100644 index 0000000..be73459 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/alert/Alert.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 Minecon724 + * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file + * in the project root for the full license text. + */ + +package eu.m724.tweaks.alert; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; + +public class Alert { + public final String[] content; + public final long issued; + public final Inventory inventory; + + public Alert(String... content) { + this.content = content; + this.issued = System.currentTimeMillis(); + this.inventory = inventory(content); + } + + public boolean isOpen(Player player) { + var item = player.getOpenInventory().getTopInventory().getItem(0); + if (item != null) { + var meta = item.getItemMeta(); + return meta != null && meta.getCustomModelData() == 4198203; + } + return false; + } + + private Inventory inventory(String... pages) { + var inv = Bukkit.createInventory(null, InventoryType.LECTERN); + var book = new ItemStack(Material.WRITTEN_BOOK); + var bookMeta = (BookMeta) book.getItemMeta(); + bookMeta.setTitle("ALERT"); + //bookMeta.setAuthor("a"); + bookMeta.setCustomModelData(4198203); + for (String page : pages) { + bookMeta.addPage(page); + } + book.setItemMeta(bookMeta); + inv.setItem(0, book); + + return inv; + } +} diff --git a/src/main/java/eu/m724/tweaks/alert/AlertCommand.java b/src/main/java/eu/m724/tweaks/alert/AlertCommand.java new file mode 100644 index 0000000..e5748a7 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/alert/AlertCommand.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 Minecon724 + * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file + * in the project root for the full license text. + */ + +package eu.m724.tweaks.alert; + +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.BookMeta; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class AlertCommand implements CommandExecutor { + private List pending; + private long when; + + private final AlertManager manager; + + public AlertCommand(AlertManager manager) { + this.manager = manager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 0) { + sender.sendMessage("/emergencyalert manages emergency alerts"); + sender.sendMessage("/emergencyalert cancel - cancels a pending or an active alert"); + sender.sendMessage("/emergencyalert new - creates a new alert with given text"); + sender.sendMessage("/emergencyalert new - creates a new alert with contents of held book"); + } else { + if (args[0].equals("CONFIRM")) { + if (pending != null) { + if (System.currentTimeMillis() - when > 15 * 1000) { + sender.sendMessage("There was a pending alert, but you took too long to confirm it, please create it again"); + pending = null; + return true; + } + + sender.sendMessage("Alert broadcast"); + manager.start(pending.toArray(String[]::new)); + pending = null; + } else { + sender.sendMessage("There is no pending alert to confirm"); + } + } else if (args[0].equalsIgnoreCase("confirm")) { + sender.sendMessage("CONFIRM must be in all caps"); + } else if (args[0].equalsIgnoreCase("cancel")) { + if (AlertManager.current != null) { + manager.stop(); + sender.sendMessage("Cancelled alert"); + } else if (pending != null) { + sender.sendMessage("Cancelled pending alert"); + pending = null; + } else { + sender.sendMessage("There is no alert to cancel"); + } + } else if (args[0].equalsIgnoreCase("new")) { + if (pending != null) { + sender.sendMessage("Another alert already waiting for confirmation. /emergencyalert cancel?"); + return true; + } + + if (args.length == 1) { + if (sender instanceof Player player) { + var is = player.getInventory().getItemInMainHand(); + if (is.getType() == Material.WRITTEN_BOOK) { + player.swingMainHand(); + pending = ((BookMeta) is.getItemMeta()).getPages(); + sender.sendMessage("Used book content as alert text"); + } else { + sender.sendMessage("You must hold a written book in your hand or pass a command argument to use as alert text"); + } + } else { + sender.sendMessage("You must pass some text to alert, or be a player to use a book"); + } + } else { + pending = List.of(String.join(" " , args)); + sender.sendMessage("Used command argument as alert text"); + } + + if (pending != null) { + when = System.currentTimeMillis(); + if (AlertManager.current != null) { + sender.sendMessage("Broadcasting a new alert will cancel the currently active one"); + } + sender.sendMessage("Please confirm broadcast with /emergencyalert CONFIRM within 15 seconds"); + } + } else { + sender.sendMessage("Unknown argument \"%s\". Run this command without any arguments to see help.".formatted(args[0])); + } + } + + return true; + } +} diff --git a/src/main/java/eu/m724/tweaks/alert/AlertManager.java b/src/main/java/eu/m724/tweaks/alert/AlertManager.java new file mode 100644 index 0000000..f2b72d9 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/alert/AlertManager.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 Minecon724 + * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file + * in the project root for the full license text. + */ + +package eu.m724.tweaks.alert; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import eu.m724.tweaks.TweaksPlugin; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashMap; +import java.util.Map; + +public class AlertManager { + private final TweaksPlugin plugin; + + private BukkitTask notifyTask; + static Alert current; + static Map pages = new HashMap<>(); + + public AlertManager(TweaksPlugin plugin) { + this.plugin = plugin; + } + + public void init(PluginCommand command) { + command.setExecutor(new AlertCommand(this)); + + ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter( + plugin, + ListenerPriority.NORMAL, + PacketType.Play.Client.ENCHANT_ITEM + ) { + @Override + public void onPacketReceiving(PacketEvent event) { + if (current == null) return; + if (!current.isOpen(event.getPlayer())) return; + + PacketContainer packet = event.getPacket(); + + int windowId, buttonId; + windowId = packet.getIntegers().read(0); + buttonId = packet.getIntegers().read(1); + + var page = pages.getOrDefault(event.getPlayer(),1); + + if (buttonId == 1) { // prev page + page--; + } else if (buttonId == 2) { // nextc page + page++; + } else { + return; + } + + pages.put(event.getPlayer(), page); + var npacket = new PacketContainer(PacketType.Play.Server.WINDOW_DATA); + npacket.getIntegers().write(0, windowId); + npacket.getIntegers().write(1, 0); + npacket.getIntegers().write(2, page); + ProtocolLibrary.getProtocolManager().sendServerPacket(event.getPlayer(), npacket); + } + }); + } + + public Alert start(String... content) { + stop(); + current = new Alert(content); + notifyTask = new AlertRunnable(current, v -> this.stop()).runTaskTimer(plugin, 0, 10); + return current; + } + + public void stop() { + if (current == null) return; + for (Player player : plugin.getServer().getOnlinePlayers()) { + if (current.isOpen(player)) + player.closeInventory(); + } + pages.clear(); + notifyTask.cancel(); + current = null; + } +} diff --git a/src/main/java/eu/m724/tweaks/alert/AlertRunnable.java b/src/main/java/eu/m724/tweaks/alert/AlertRunnable.java new file mode 100644 index 0000000..d2c3400 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/alert/AlertRunnable.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 Minecon724 + * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file + * in the project root for the full license text. + */ + +package eu.m724.tweaks.alert; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.ComponentBuilder; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.function.Consumer; + +public class AlertRunnable extends BukkitRunnable { + private final Alert alert; + private final Consumer onEnd; + + public AlertRunnable(Alert alert, Consumer onEnd) { + this.alert = alert; + this.onEnd = onEnd; + } + + @Override + public void run() { + if (alert != null) { + var ago = (System.currentTimeMillis() - alert.issued) / 1000; + Bukkit.getOnlinePlayers().forEach(p -> { + if (ago < 15) { + p.playSound(p, Sound.BLOCK_PORTAL_TRIGGER, 1f, 2f); + //p.playSound(p, Sound.ENTITY_GHAST_HURT, 1f, 2f); + if (ago % 2 == 0) { + p.playSound(p, Sound.BLOCK_ANVIL_PLACE, 1f, 0.5f); + } + } + + if (ago < 10) { + if (!alert.isOpen(p)) { + p.openInventory(alert.inventory); + } + } + + p.spigot().sendMessage( + ChatMessageType.ACTION_BAR, + new ComponentBuilder("An important event is ongoing").color(ChatColor.YELLOW) + .append(" %d:%02d".formatted(ago / 60, ago % 60)).color(ChatColor.GRAY) + .build() + ); + }); + + if (ago > 300) + onEnd.accept(null); + } + } +} diff --git a/src/main/java/eu/m724/tweaks/full/FullListener.java b/src/main/java/eu/m724/tweaks/full/FullListener.java index 7cc502a..29f4572 100644 --- a/src/main/java/eu/m724/tweaks/full/FullListener.java +++ b/src/main/java/eu/m724/tweaks/full/FullListener.java @@ -13,7 +13,7 @@ import org.bukkit.event.player.PlayerLoginEvent; public class FullListener implements Listener { @EventHandler public void onPlayerLogin(PlayerLoginEvent event) { - if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL && event.getPlayer().hasPermission("tweaks724.full.exempt")) { + if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL && event.getPlayer().hasPermission("tweaks724.bypass-full")) { event.allow(); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0495773..c2a5d00 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,7 +14,6 @@ libraries: commands: chat: description: Chatroom user commands - usage: / [room] [password, optional] aliases: [c, chatroom, cr, room] chatmanage: description: Chatroom user management commands @@ -33,16 +32,22 @@ commands: tauth: description: Authentication management permission: tweaks724.tauth + emergencyalert: + description: Send emergency alert + permission: tweaks724.emergencyalert permissions: - tweaks724.chatmanage: - default: true - tweaks724.pomodoro: - default: true - tweaks724.updates: - default: op - tweaks724.tauth: - default: op - tweaks724.full.exempt: - default: op + tweaks724: + chatmanage: + default: true + pomodoro: + default: true + updates: + default: op + tauth: + default: op + bypass-full: + default: op + emergencyalert: + default: op