diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e0a8ca --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# tweaks724 + +Dependencies: ProtocolLib + +### Caveats +Disable "secure chat" + +### Chatrooms +- `/c` to see info about current chatroom +- `/c ` to join a chatroom +- `/cm create ` to create a chatroom +- `/cm set* ` to change a setting (for owners) + +### Commands +- `/dkick` - discreet kick. The player will be kicked with a cryptic error message, and those smarter will think it's a plugin bug.[^1] Brilliant! +- `/ping` - checks ping. Ping is checked using a more accurate[^2] method + +[^1]: or, better, client bug, if they don't trust their client +[^2]: not \ No newline at end of file diff --git a/pom.xml b/pom.xml index 76e29b6..e18aee6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 eu.m724 - mutils + tweaks 1.0-SNAPSHOT @@ -21,6 +21,40 @@ true + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + false + true + + + eu.m724:tweaks + + + + + * + + META-INF/** + + + + + + + package + + shade + + + + + + diff --git a/src/main/java/eu/m724/tweaks/TweaksPlugin.java b/src/main/java/eu/m724/tweaks/TweaksPlugin.java index 588924f..325c402 100644 --- a/src/main/java/eu/m724/tweaks/TweaksPlugin.java +++ b/src/main/java/eu/m724/tweaks/TweaksPlugin.java @@ -25,5 +25,6 @@ public class TweaksPlugin extends JavaPlugin { new F3NameListener(this).init(); new PingChecker(this).init(); Objects.requireNonNull(getCommand("ping")).setExecutor(new PingCommands()); + Objects.requireNonNull(getCommand("dkick")).setExecutor(new PingCommands()); } } diff --git a/src/main/java/eu/m724/tweaks/chat/ChatCommands.java b/src/main/java/eu/m724/tweaks/chat/ChatCommands.java index c00286a..571b783 100644 --- a/src/main/java/eu/m724/tweaks/chat/ChatCommands.java +++ b/src/main/java/eu/m724/tweaks/chat/ChatCommands.java @@ -30,17 +30,12 @@ public class ChatCommands implements CommandExecutor { ChatRoom chatRoom = manager.getPlayerChatRoom(player); if (args.length == 0) { // show room - BaseComponent[] component = new ComponentBuilder("Active chat room: ").color(ChatColor.GOLD) - .append(chatRoom.id).color(chatRoom.color) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.getInfoComponent()))) - .create(); - player.spigot().sendMessage(component); + player.spigot().sendMessage(chatRoom.getInfoComponent()); } else { // join room - // TODO move joining logic String id = args[0]; String password = null; if (args.length > 1) { - password = Arrays.stream(args).skip(1).collect(Collectors.joining(" ")).strip(); + password = Arrays.stream(args).skip(1).collect(Collectors.joining(" ")); } boolean authenticated = false; @@ -105,7 +100,8 @@ public class ChatCommands implements CommandExecutor { case "delete" -> { if (argument.equals(chatRoom.id)) { if (isOwner) { - // TODO + manager.deleteChatRoom(chatRoom); + sender.sendMessage("Room %s deleted".formatted(chatRoom.id)); } else { sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id)); } @@ -134,7 +130,7 @@ public class ChatCommands implements CommandExecutor { } case "setpassword" -> { if (isOwner) { - chatRoom.password = Arrays.stream(args).skip(1).collect(Collectors.joining(" ")).strip(); + chatRoom.password = Arrays.stream(args).skip(1).collect(Collectors.joining(" ")); try { manager.saveChatRoom(chatRoom); sender.sendMessage("Password changed"); @@ -174,13 +170,13 @@ public class ChatCommands implements CommandExecutor { case "create" -> sender.sendMessage("Please pass a room name as an argument. The room name must be of characters and digits."); case "delete" -> - sender.sendMessage("You want to delete room %s. Confirm by passing its name as an argument for this action.".formatted(chatRoom)); + sender.sendMessage("You want to delete room %s. Confirm by passing its name as an argument for this action.".formatted(chatRoom.id)); case "setowner" -> - sender.sendMessage("To transfer ownership of room %s, pass the new owner name as an argument for this action.".formatted(chatRoom)); + sender.sendMessage("To transfer ownership of room %s, pass the new owner name as an argument for this action.".formatted(chatRoom.id)); case "setpassword" -> - sender.sendMessage("To change the password of room %s, pass the new password as an argument for this action.".formatted(chatRoom)); + sender.sendMessage("To change the password of room %s, pass the new password as an argument for this action.".formatted(chatRoom.id)); case "setcolor" -> - sender.sendMessage("To change the message color of room %s, pass the new color as an argument for this action. #hex or color name.".formatted(chatRoom)); + sender.sendMessage("To change the message color of room %s, pass the new color as an argument for this action. #hex or color name.".formatted(chatRoom.id)); default -> sender.sendMessage("Actions: create, delete, setowner, setpassword, setcolor"); } diff --git a/src/main/java/eu/m724/tweaks/chat/ChatFormatUtils.java b/src/main/java/eu/m724/tweaks/chat/ChatFormatUtils.java new file mode 100644 index 0000000..9b0ca41 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/chat/ChatFormatUtils.java @@ -0,0 +1,39 @@ +package eu.m724.tweaks.chat; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.hover.content.Text; +import org.bukkit.entity.Player; + +public class ChatFormatUtils { + public static BaseComponent[] formatPlayer(Player player) { + ChatColor nameColor = ChatColor.of("#" + Integer.toHexString(player.getName().hashCode()).substring(0, 6)); + + if (player.getCustomName() != null) { + return new ComponentBuilder() + .append("~" + player.getCustomName()).color(nameColor) + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(player.getName()))) + .create(); + } else { + return new ComponentBuilder() + .append(player.getName()).color(nameColor) + .create(); + } + } + + public static BaseComponent[] chatRoomPrefixShort(ChatRoom chatRoom) { + ChatColor prefixColor = ChatColor.of(chatRoom.color.getColor().darker()); + + return new ComponentBuilder(chatRoom.id.charAt(0) + " ").color(prefixColor) + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.getInfoComponent()))) + .create(); + } + + public static BaseComponent[] formatChatRoom(ChatRoom chatRoom) { + return new ComponentBuilder(chatRoom.id).color(chatRoom.color) + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.getInfoComponent()))) + .create(); + } +} diff --git a/src/main/java/eu/m724/tweaks/chat/ChatListener.java b/src/main/java/eu/m724/tweaks/chat/ChatListener.java index a22305c..a00ece5 100644 --- a/src/main/java/eu/m724/tweaks/chat/ChatListener.java +++ b/src/main/java/eu/m724/tweaks/chat/ChatListener.java @@ -3,13 +3,12 @@ package eu.m724.tweaks.chat; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.hover.content.Text; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; public class ChatListener implements Listener { private final ChatManager chatManager; @@ -24,12 +23,37 @@ public class ChatListener implements Listener { ChatRoom chatRoom = chatManager.getPlayerChatRoom(player); BaseComponent[] component = new ComponentBuilder("Chat room: ").color(ChatColor.GOLD) - .append(chatRoom.id).color(ChatColor.AQUA) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.getInfoComponent()))) + .append(ChatFormatUtils.formatChatRoom(chatRoom)) .create(); player.spigot().sendMessage(component); - event.setJoinMessage(null); // TODO room messages + chatRoom.broadcast( + new ComponentBuilder() + .append(ChatFormatUtils.chatRoomPrefixShort(chatRoom)) + .append(ChatFormatUtils.formatPlayer(player)) + .append(" has joined the server").color(ChatColor.GREEN) + .create() + ); + + // remove Minecraft join message + event.setJoinMessage(null); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + ChatRoom chatRoom = chatManager.removePlayer(player); + + chatRoom.broadcast( + new ComponentBuilder() + .append(ChatFormatUtils.chatRoomPrefixShort(chatRoom)) + .append(ChatFormatUtils.formatPlayer(player)) + .append(" has left the server").color(ChatColor.RED) + .create() + ); + + // remove Minecraft quit message + event.setQuitMessage(null); } @EventHandler @@ -38,24 +62,14 @@ public class ChatListener implements Listener { ChatRoom chatRoom = chatManager.getPlayerChatRoom(player); String message = event.getMessage(); - // TODO move sending logic - ChatColor prefixColor = ChatColor.of(chatRoom.color.getColor().darker()); - ChatColor nameColor = ChatColor.of("#" + Integer.toHexString(player.getName().hashCode()).substring(0, 6)); - - ComponentBuilder builder = new ComponentBuilder(chatRoom.id.charAt(0) + " ").color(prefixColor) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.getInfoComponent()))); - - if (player.getCustomName() != null) { - builder = builder.append("~" + player.getCustomName() + ": ").color(nameColor) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.getInfoComponent()))); - } else { - builder = builder.append(player.getName() + ": ").color(nameColor); - } - - builder = builder.append(message).color(chatRoom.color); + ComponentBuilder builder = new ComponentBuilder(); + builder.append(ChatFormatUtils.chatRoomPrefixShort(chatRoom)); + builder.append(ChatFormatUtils.formatPlayer(player)).append(": "); + builder.append(message).color(chatRoom.color); chatRoom.broadcast(builder.create()); + // remove the original message event.setCancelled(true); } } diff --git a/src/main/java/eu/m724/tweaks/chat/ChatManager.java b/src/main/java/eu/m724/tweaks/chat/ChatManager.java index 1a3025a..39c99ef 100644 --- a/src/main/java/eu/m724/tweaks/chat/ChatManager.java +++ b/src/main/java/eu/m724/tweaks/chat/ChatManager.java @@ -1,5 +1,7 @@ package eu.m724.tweaks.chat; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ComponentBuilder; import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; @@ -27,6 +29,18 @@ public class ChatManager { plugin.getServer().getPluginManager().registerEvents(new ChatListener(this), plugin); } + /** + * removes player from the server and their chatroom + * @param player the player + * @return the chatroom the player was removed from + */ + public ChatRoom removePlayer(Player player) { + ChatRoom chatRoom = playerMap.remove(player); + chatRoom.players.remove(player); + + return chatRoom; + } + /** * Get a chat room by id.
* If the chat room is not loaded, it's loaded. @@ -59,6 +73,22 @@ public class ChatManager { player.getPersistentDataContainer().set(chatRoomKey, PersistentDataType.STRING, chatRoom.id); playerMap.put(player, chatRoom); chatRoom.players.add(player); + + chatRoom.broadcast( + new ComponentBuilder() + .append(ChatFormatUtils.chatRoomPrefixShort(chatRoom)) + .append(ChatFormatUtils.formatPlayer(player)) + .append(" has left the chat room").color(ChatColor.RED) + .create() + ); + + chatRoom.broadcast( + new ComponentBuilder() + .append(ChatFormatUtils.chatRoomPrefixShort(chatRoom)) + .append(ChatFormatUtils.formatPlayer(player)) + .append(" has joined the chat room").color(ChatColor.GREEN) + .create() + ); } /** @@ -123,6 +153,14 @@ public class ChatManager { ChatRoomLoader.save(plugin, chatRoom); } + public void deleteChatRoom(ChatRoom chatRoom) { + roomIdMap.remove(chatRoom.id); + ChatRoomLoader.getFile(plugin, chatRoom.id).delete(); + chatRoom.players.forEach(player -> { + setPlayerChatRoom(getById("global"), player); + }); + } + /** * If an ID is too short, too long, wrong composition, etc. */ diff --git a/src/main/java/eu/m724/tweaks/chat/ChatRoom.java b/src/main/java/eu/m724/tweaks/chat/ChatRoom.java index c963e6d..7ee8334 100644 --- a/src/main/java/eu/m724/tweaks/chat/ChatRoom.java +++ b/src/main/java/eu/m724/tweaks/chat/ChatRoom.java @@ -25,7 +25,6 @@ public class ChatRoom { this.owner = owner; } - // TODO not recompute every time /** * @return A nicely formatted text block with info such as room id, owner, online, etc. */ @@ -46,7 +45,7 @@ public class ChatRoom { builder = builder.append(playersList.removeFirst().getName()).color(ChatColor.GRAY); for (Player player : playersList) { - builder = builder.append(", ").color(ChatColor.GRAY) + builder = builder.append(", ").color(ChatColor.GREEN) .append(player.getName()).color(ChatColor.AQUA); } } diff --git a/src/main/java/eu/m724/tweaks/chat/ChatRoomLoader.java b/src/main/java/eu/m724/tweaks/chat/ChatRoomLoader.java index 8a93ee7..fd2f702 100644 --- a/src/main/java/eu/m724/tweaks/chat/ChatRoomLoader.java +++ b/src/main/java/eu/m724/tweaks/chat/ChatRoomLoader.java @@ -12,13 +12,18 @@ import java.nio.file.Paths; import java.util.UUID; public class ChatRoomLoader { - private static File getFile(Plugin plugin, String id) { + /** + * Get the file of persistent storage of a chat room + * @return the file or null if ID is invalid + */ + static File getFile(Plugin plugin, String id) { Path chatRoomsPath = Paths.get(plugin.getDataFolder().getPath(), "rooms"); chatRoomsPath.toFile().mkdirs(); - // TODO sanitize - File chatRoomFile = Paths.get(chatRoomsPath.toFile().getPath(), id + ".yml").toFile(); - return chatRoomFile; + if (validateId(id) != 0) + throw new RuntimeException("Invalid id: " + id); + + return Paths.get(chatRoomsPath.toFile().getPath(), id + ".yml").toFile(); } /** diff --git a/src/main/java/eu/m724/tweaks/door/DoorListener.java b/src/main/java/eu/m724/tweaks/door/DoorListener.java index 7bddda7..f2d948d 100644 --- a/src/main/java/eu/m724/tweaks/door/DoorListener.java +++ b/src/main/java/eu/m724/tweaks/door/DoorListener.java @@ -20,7 +20,7 @@ public class DoorListener implements Listener { @EventHandler public void onBlockDamage(BlockDamageEvent event) { Block block = event.getBlock(); - if (!(block.getBlockData() instanceof Door door)) return; + if (!(block.getBlockData() instanceof Door)) return; World world = block.getLocation().getWorld(); Player player = event.getPlayer(); @@ -41,11 +41,10 @@ public class DoorListener implements Listener { pitch = ThreadLocalRandom.current().nextFloat(0.5f, 0.7f); } - PotionEffect weakness = player.getPotionEffect(PotionEffectType.WEAKNESS); PotionEffect fatigue = player.getPotionEffect(PotionEffectType.MINING_FATIGUE); - int level = (weakness != null ? weakness.getAmplifier() : 0) + (fatigue != null ? fatigue.getAmplifier() : 0); - if (weakness != null || fatigue != null) { + if (fatigue != null) { + int level = fatigue.getAmplifier(); volume /= level / 3f; pitch /= level; } @@ -53,7 +52,7 @@ public class DoorListener implements Listener { volume *= (float) ((10.0 - Math.min(distance - 2, 10.0)) / 10.0); world.playSound(hitLocation, sound, volume, pitch); - world.spawnParticle(Particle.BLOCK, hitLocation, (int) (10 * volume), door); + //world.spawnParticle(Particle.BLOCK, hitLocation, (int) (10 * volume), door); } @EventHandler diff --git a/src/main/java/eu/m724/tweaks/ping/MsptChecker.java b/src/main/java/eu/m724/tweaks/ping/MsptChecker.java index c9d03f5..8442577 100644 --- a/src/main/java/eu/m724/tweaks/ping/MsptChecker.java +++ b/src/main/java/eu/m724/tweaks/ping/MsptChecker.java @@ -14,6 +14,6 @@ public class MsptChecker extends BukkitRunnable { } public void init(Plugin plugin) { - this.runTaskTimerAsynchronously(plugin, 0, 100); // 5 secs + this.runTaskTimer(plugin, 0, 100); // 5 secs } } diff --git a/src/main/java/eu/m724/tweaks/ping/PingChecker.java b/src/main/java/eu/m724/tweaks/ping/PingChecker.java index 5f305e3..19002a5 100644 --- a/src/main/java/eu/m724/tweaks/ping/PingChecker.java +++ b/src/main/java/eu/m724/tweaks/ping/PingChecker.java @@ -17,7 +17,6 @@ import java.util.concurrent.ConcurrentHashMap; public class PingChecker extends BukkitRunnable { private final Plugin plugin; - // TODO concurrnet too? // this is in nanoseconds private final Map pending = new ConcurrentHashMap<>(); @@ -39,8 +38,13 @@ public class PingChecker extends BukkitRunnable { long start = pending.remove(player); PlayerPing.pings.put(player, System.nanoTime() - start); + // gotta cancel because the server will kick - event.setCancelled(true); + if (!PlayerPing.kickQueue.contains(player)) { + event.setCancelled(true); + } else { + PlayerPing.kickQueue.remove(player); + } } } }); @@ -52,7 +56,8 @@ public class PingChecker extends BukkitRunnable { @Override public void run() { plugin.getServer().getOnlinePlayers().forEach(player -> { - if (pending.containsKey(player)) return; + // dropped packets happen so timing out a request after 30 seconds + if (System.nanoTime() - pending.getOrDefault(player, 0L) < 30000000000L) return; pending.put(player, System.nanoTime()); // here or at the bottom? probably doesn't matter PacketContainer packet = new PacketContainer(PacketType.Play.Server.COOKIE_REQUEST); diff --git a/src/main/java/eu/m724/tweaks/ping/PingCommands.java b/src/main/java/eu/m724/tweaks/ping/PingCommands.java index eda7e00..de80367 100644 --- a/src/main/java/eu/m724/tweaks/ping/PingCommands.java +++ b/src/main/java/eu/m724/tweaks/ping/PingCommands.java @@ -3,6 +3,7 @@ package eu.m724.tweaks.ping; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ComponentBuilder; +import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -18,6 +19,25 @@ public class PingCommands implements CommandExecutor { .append("%.2fms".formatted(PlayerPing.getPingMillis(player))).color(ChatColor.AQUA) .create(); player.spigot().sendMessage(component); + } else if (command.getName().equals("dkick")) { + if (args.length == 0) { + sender.sendMessage("Include one or more player names"); + } else { + for (String name : args) { + Player player = Bukkit.getPlayer(name); + if (player == null) { + sender.sendMessage("Player %s is not online".formatted(name)); + } + if (PlayerPing.kick(player)) { + if (player.getName().equals(sender.getName())) { + sender.sendMessage("Kicking yourself? Very well"); + } + sender.sendMessage("Player %s will be kicked shortly".formatted(name)); + } else { + sender.sendMessage("Player %s is already queued".formatted(name)); + } + } + } } return true; } diff --git a/src/main/java/eu/m724/tweaks/ping/PlayerPing.java b/src/main/java/eu/m724/tweaks/ping/PlayerPing.java index bbf18c2..2e9e59e 100644 --- a/src/main/java/eu/m724/tweaks/ping/PlayerPing.java +++ b/src/main/java/eu/m724/tweaks/ping/PlayerPing.java @@ -3,14 +3,16 @@ package eu.m724.tweaks.ping; import org.bukkit.entity.Player; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class PlayerPing { - // TODO concurrnet? // in nanos static final Map pings = new ConcurrentHashMap<>(); // nanos per tick - static long nspt = 0L; + static volatile long nspt = 0L; + + static final Set kickQueue = ConcurrentHashMap.newKeySet(); public static long getPingNanos(Player player) { return pings.getOrDefault(player, -1L); @@ -27,4 +29,8 @@ public class PlayerPing { public static double getMillisPerTick() { return nspt / 1000000.0; // a mil ns in ms } + + public static boolean kick(Player player) { + return kickQueue.add(player); + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f87310d..908f849 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -15,4 +15,11 @@ commands: aliases: [cm, crm] ping: description: Your ping + dkick: + description: Kick a player discreetly + permission: tweaks724.dkick + +permissions: + tweaks724.dkick: + default: op