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