diff --git a/.idea/modules.xml b/.idea/modules.xml index 26a7d9b..04242aa 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,6 +2,7 @@ + diff --git a/.idea/modules/BlazeSMP.main.iml b/.idea/modules/BlazeSMP.main.iml index d329813..bbeeb3e 100644 --- a/.idea/modules/BlazeSMP.main.iml +++ b/.idea/modules/BlazeSMP.main.iml @@ -1,10 +1,5 @@ - - - - - diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/BlazeSMP.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/BlazeSMP.java index bb22480..6fbfc51 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/BlazeSMP.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/BlazeSMP.java @@ -1,8 +1,10 @@ package me.freezy.plugins.papermc.blazesmp; import lombok.Getter; +import me.freezy.plugins.papermc.blazesmp.command.ClaimCommand; import me.freezy.plugins.papermc.blazesmp.command.ClanCommand; -import me.freezy.plugins.papermc.blazesmp.listener.JoinListener; +import me.freezy.plugins.papermc.blazesmp.command.ReportCommand; +import me.freezy.plugins.papermc.blazesmp.listener.*; import me.freezy.plugins.papermc.blazesmp.module.manager.Clans; import me.freezy.plugins.papermc.blazesmp.module.manager.Homes; import me.freezy.plugins.papermc.blazesmp.module.manager.ProtectedBlocks; @@ -60,11 +62,17 @@ public final class BlazeSMP extends JavaPlugin { this.log.info("Registering Commands..."); new ClanCommand().register(); + new ReportCommand().register(); + new ClaimCommand().register(); this.log.info("Registered Commands!"); this.log.info("Registering EventListeners..."); PluginManager pm = getServer().getPluginManager(); - pm.registerEvents(new JoinListener(), this); + pm.registerEvents(new PlayerJoinListener(), this); + pm.registerEvents(new PlayerChatListener(), this); + pm.registerEvents(new PlayerCommandBlockerListener(), this); + pm.registerEvents(new PlayerClaimListener(), this); + pm.registerEvents(new ChunkInventoryManager(), this); this.log.info("Registered EventListeners!"); this.log.info("Starting Timer tasks..."); diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClaimCommand.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClaimCommand.java new file mode 100644 index 0000000..873c7dd --- /dev/null +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClaimCommand.java @@ -0,0 +1,93 @@ +package me.freezy.plugins.papermc.blazesmp.command; + +import me.freezy.plugins.papermc.blazesmp.BlazeSMP; +import me.freezy.plugins.papermc.blazesmp.command.util.SimpleCommand; +import me.freezy.plugins.papermc.blazesmp.listener.ChunkInventoryManager; +import me.freezy.plugins.papermc.blazesmp.module.Clan; +import me.freezy.plugins.papermc.blazesmp.module.manager.Clans; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Chunk; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class ClaimCommand extends SimpleCommand { + + private final Clans clans; + + public ClaimCommand() { + super("claim", List.of("unclaim")); + this.clans = BlazeSMP.getInstance().getClans(); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage(MiniMessage.miniMessage().deserialize("You must be a player to use this command!")); + return true; + } + UUID playerUUID = player.getUniqueId(); + if (!clans.isInClan(playerUUID)) { + player.sendMessage(MiniMessage.miniMessage().deserialize("You must be in a clan to claim/unclaim chunks!")); + return true; + } else { + if (label.equalsIgnoreCase("claim")) { + if (args.length != 0 && args[0].equalsIgnoreCase("see")) { + ChunkInventoryManager.openInv(player); + return true; + } + Clan playerClan = clans.getClanByMember(playerUUID); + LinkedHashMap> existingClaims=clans.getClanChunks(playerClan); + if (!existingClaims.containsKey(playerUUID)) { + existingClaims.put(playerUUID, new LinkedList<>()); + } + LinkedList playerClaims = existingClaims.get(playerUUID); + int MAX_CLAIMS = 50; + if (playerClaims.size() >= MAX_CLAIMS) { + player.sendMessage(MiniMessage.miniMessage().deserialize("You have reached the maximum amount of claims!")); + } else { + Chunk playerChunk = player.getLocation().getChunk(); + if (clans.isChunkClaimed(playerChunk)) { + player.sendMessage(MiniMessage.miniMessage().deserialize("This chunk is already claimed!")); + } else { + playerClaims.add(playerChunk); + player.sendMessage(MiniMessage.miniMessage().deserialize("Claimed chunk!")); + existingClaims.put(playerUUID, playerClaims); + clans.setClanChunks(playerClan, existingClaims); + playerClan.save(); + clans.saveAllClans(); + } + } + return true; + } else if (label.equalsIgnoreCase("unclaim")) { + Clan playerClan = clans.getClanByMember(playerUUID); + LinkedHashMap> existingClaims=clans.getClanChunks(playerClan); + if (existingClaims.containsKey(playerUUID)) { + LinkedList playerClaims = existingClaims.get(playerUUID); + Chunk playerChunk = player.getLocation().getChunk(); + if (playerClaims.contains(playerChunk)) { + playerClaims.remove(playerChunk); + player.sendMessage(MiniMessage.miniMessage().deserialize("Unclaimed chunk!")); + existingClaims.put(playerUUID, playerClaims); + clans.setClanChunks(playerClan, existingClaims); + playerClan.save(); + clans.saveAllClans(); + } else { + player.sendMessage(MiniMessage.miniMessage().deserialize("You do not own this chunk!")); + } + return true; + } + } + } + return false; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return List.of(); + } +} diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClanCommand.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClanCommand.java index a9f68a9..de3627f 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClanCommand.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ClanCommand.java @@ -76,6 +76,27 @@ public class ClanCommand extends SimpleCommand { player.sendMessage(miniMessage().deserialize("Clan created successfully!")); return true; } + case "chat" -> { + if (!clans.isInClan(playerUUID)) { + player.sendMessage(miniMessage().deserialize("You are not in a clan!")); + return true; + } + Clan clan = clans.getClanByMember(playerUUID); + if (args.length < 2) { + player.sendMessage(miniMessage().deserialize("Usage: /clan chat ")); + return true; + } + String message = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + Component chatMessage = miniMessage().deserialize( + String.format("[Clan] %s: %s", player.getName(), message)); + for (UUID mem : clan.getMembers()) { + Player member = Bukkit.getPlayer(mem); + if (member != null && member.isOnline()) { + member.sendMessage(chatMessage); + } + } + return true; + } // ========== JOIN ========== case "join" -> { @@ -797,18 +818,18 @@ public class ClanCommand extends SimpleCommand { // Erste Ebene der Subcommands if (args.length == 1) { if (clans.isLeader(playerUUID)) { - return Stream.of("info", "invite", "kick", "transfer", "promote", + return Stream.of("info", "chat", "invite", "kick", "transfer", "promote", "demote", "disband", "leave", "accept", "deny", "modify", "list") .filter(s -> s.startsWith(args[0])) .collect(Collectors.toList()); } else if (clans.isVice(playerUUID)) { - return Stream.of("info", "invite", "kick", "demote", + return Stream.of("info", "chat", "invite", "kick", "demote", "leave", "accept", "deny", "list") .filter(s -> s.startsWith(args[0])) .collect(Collectors.toList()); } else if (clans.isMember(playerUUID)) { - return Stream.of("info", "leave", "list") + return Stream.of("info", "chat", "leave", "list") .filter(s -> s.startsWith(args[0])) .collect(Collectors.toList()); } else { diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ReportCommand.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ReportCommand.java new file mode 100644 index 0000000..bdb02b8 --- /dev/null +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/ReportCommand.java @@ -0,0 +1,115 @@ +package me.freezy.plugins.papermc.blazesmp.command; + +import me.freezy.plugins.papermc.blazesmp.BlazeSMP; +import me.freezy.plugins.papermc.blazesmp.command.util.SimpleCommand; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class ReportCommand extends SimpleCommand { + MiniMessage miniMessage = MiniMessage.miniMessage(); + + public ReportCommand() { + super("report"); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player reporter)) { + sender.sendMessage(miniMessage.deserialize("Du darfst das nicht")); + return true; + } + + if (args.length < 2) { + sender.sendMessage(miniMessage.deserialize("Benutze bitte: /report ")); + return true; + } + + OfflinePlayer reportedPlayer = Bukkit.getOfflinePlayer(args[0]); + if (!reportedPlayer.hasPlayedBefore() && !reportedPlayer.isOnline()) { + reporter.sendMessage(miniMessage.deserialize("Der Spieler existiert nicht oder war noch nie online!")); + return true; + } + + String reason = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + + reporter.sendMessage(miniMessage.deserialize(String.format("Du hast %s erfolgreich wegen %s gemeldet", reportedPlayer.getName(), reason))); + + sendReportToDiscord(reporter.getName(), reportedPlayer.getName(), reason, reporter.getUniqueId()); + + return true; + } + + private void sendReportToDiscord(String reporter, String reported, String reason, UUID reporterUUID) { + try { + String thumbnailUrl = "http://209.25.141.65:40018/v1/head/getHead/"; + String jsonPayload = "{" + + "\"username\": \"ReportBot\"," + + "\"embeds\": [{" + + "\"title\": \"Neuer Report\"," + + "\"color\": 16711680," // Red color + + "\"thumbnail\": {\"url\": \"" + thumbnailUrl+reporterUUID.toString() + "\"}," // Thumbnail + + "\"fields\": [" + + "{\"name\": \"Reporter\", \"value\": \"" + reporter + "\", \"inline\": true}," + + "{\"name\": \"Gemeldeter Spieler\", \"value\": \"" + reported + "\", \"inline\": true}," + + "{\"name\": \"Grund\", \"value\": \"" + reason + "\", \"inline\": false}" + + "]" + + "}]" + + "}"; + + HttpURLConnection connection = getHttpURLConnection(jsonPayload); + + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_NO_CONTENT || responseCode == HttpURLConnection.HTTP_OK) { + System.out.println("Report sent to Discord successfully!"); + } else { + System.out.println("Failed to send report to Discord. Response code: " + responseCode); + } + connection.disconnect(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static @NotNull HttpURLConnection getHttpURLConnection(String jsonPayload) throws IOException { + String webhookUrl = BlazeSMP.getInstance().getConfig().getString("discord-report-webhook"); + assert webhookUrl != null; + URL url = new URL(webhookUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setDoOutput(true); + + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + return connection; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 1) { + return Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .filter(name -> name.startsWith(args[0])) + .collect(Collectors.toList()); + } + return List.of(); + } +} \ No newline at end of file diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/command/util/SimpleCommand.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/util/SimpleCommand.java index 53e9057..8cba3e6 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/command/util/SimpleCommand.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/command/util/SimpleCommand.java @@ -7,8 +7,8 @@ import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.util.List; +import java.util.Objects; import java.util.logging.Logger; - @SuppressWarnings("ALL") public abstract class SimpleCommand implements CommandExecutor, TabExecutor { protected static Logger logger = Logger.getLogger(BlazeSMP.class.getName()); @@ -17,43 +17,37 @@ public abstract class SimpleCommand implements CommandExecutor, TabExecutor { protected final String description; protected final List alias; protected final String usage; - protected final String permissionMessage; protected final String permission; public SimpleCommand(String command) { - this(command, null, null, null, null, null); + this(command, null, null, null, null); } public SimpleCommand(String command, String usage) { - this(command, usage, null, null, null, null); + this(command, usage, null, null, null); + } + + public SimpleCommand(String command, List alias) { + this(command, null, null, null, alias); } public SimpleCommand(String command, String usage, String description) { - this(command, usage, description, null, null, null); + this(command, usage, description, null, null); } public SimpleCommand(String command, String usage, String description, List alias) { - this(command, usage, description, null, null, alias); + this(command, usage, description, null, alias); } - public SimpleCommand(String command, String usage, String description, String permissionMessage) { - this(command, usage, description, permissionMessage, null, null); - } - - public SimpleCommand(String command, String usage, String description, String permissionMessage, String permission) { - this(command, usage, description, permissionMessage, permission, null); + public SimpleCommand(String command, String usage, String description, String permission) { + this(command, usage, description, permission, null); } public SimpleCommand(String command, String usage, String description, String permission, List alias) { - this(command, usage, description, null, permission, alias); - } - - public SimpleCommand(String command, String usage, String description, String permissionMessage, String permission, List alias) { this.command = command; this.description = description; this.alias = alias; this.usage = usage; - this.permissionMessage = permissionMessage; this.permission = permission; } @@ -62,7 +56,6 @@ public abstract class SimpleCommand implements CommandExecutor, TabExecutor { if (this.alias != null) cmd.setAliases(this.alias); if (this.description != null) cmd.setDescription(this.description); if (this.usage != null) cmd.setUsage(this.usage); - if (this.permissionMessage != null) cmd.setPermissionMessage(this.permissionMessage); if (this.permission != null) cmd.setPermission(this.permission); getCommandMap().register("blazesmp", cmd); cmd.setExecutor(this); @@ -104,11 +97,11 @@ public abstract class SimpleCommand implements CommandExecutor, TabExecutor { } @Override - public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, String[] args) { + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, String[] args) { if (exe != null) { - return exe.onTabComplete(sender, this, alias, args); + return Objects.requireNonNull(exe.onTabComplete(sender, this, alias, args)); } - return null; + return List.of(); } } -} \ No newline at end of file +} diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/ChunkInventoryManager.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/ChunkInventoryManager.java new file mode 100644 index 0000000..3a441bd --- /dev/null +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/ChunkInventoryManager.java @@ -0,0 +1,201 @@ +package me.freezy.plugins.papermc.blazesmp.listener; + +import me.freezy.plugins.papermc.blazesmp.BlazeSMP; +import me.freezy.plugins.papermc.blazesmp.module.Clan; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import java.util.*; + +public class ChunkInventoryManager implements Listener { + + // Speichert pro Spieler den aktuellen Seitenindex + private final PaginatedData paginatedData = new PaginatedData(); + + public static void openInv(Player player) { + Clan clan = BlazeSMP.getInstance().getClans().getClanByMember(player.getUniqueId()); + if (clan == null) { + player.sendMessage(MiniMessage.miniMessage().deserialize("Du bist in keinem Clan.")); + return; + } + new ChunkInventoryManager().chunksInv(player, clan); + } + + /** + * Öffnet das Clan-Chunks-Inventar für den Spieler. + * Die Chunks des Clans werden als Kopf-Items (PLAYER_HEAD) angezeigt. + * + * @param player Der Spieler, der das Inventar öffnet. + * @param clan Der Clan, dessen Chunks angezeigt werden sollen. + */ + private void chunksInv(Player player, Clan clan) { + // Erstelle eine Liste der Map-Einträge (Chunk -> Besitzer UUID) aus dem Clan + List> chunkEntries = new ArrayList<>(clan.getChunkOwnerMap().entrySet()); + int itemsPerPage = 45; // Plätze 0-44 für Items, untere Reihe für Navigation + int totalPages = (int) Math.ceil(chunkEntries.size() / (double) itemsPerPage); + int currentPage = 0; + paginatedData.setPage(player.getUniqueId(), currentPage); + openChunksMenu(player, chunkEntries, currentPage, totalPages, itemsPerPage, clan); + } + + /** + * Baut das Inventar basierend auf der aktuellen Seite auf und öffnet es für den Spieler. + * + * @param player Der Spieler, der das Inventar sieht. + * @param chunkEntries Liste der Clan-Chunks (als Map.Entry von Chunk und Besitzer UUID). + * @param currentPage Aktuelle Seite. + * @param totalPages Gesamtzahl der Seiten. + * @param itemsPerPage Items pro Seite (hier 45). + * @param clan Der Clan, dessen Chunks angezeigt werden. + */ + private void openChunksMenu(Player player, List> chunkEntries, + int currentPage, int totalPages, int itemsPerPage, + Clan clan) { + // Erstelle ein 54-Slot Inventar mit farbigem Titel (Adventure Component) + Component title = MiniMessage.miniMessage().deserialize("Clan Chunks"); + Inventory inv = Bukkit.createInventory(null, 54, title); + + // Berechne Start- und Endindex für die aktuelle Seite + int startIndex = currentPage * itemsPerPage; + int endIndex = Math.min(startIndex + itemsPerPage, chunkEntries.size()); + + // Füge für jeden Chunk ein Kopf-Item hinzu + for (int i = startIndex; i < endIndex; i++) { + Map.Entry entry = chunkEntries.get(i); + Chunk chunk = entry.getKey(); + UUID ownerUUID = entry.getValue(); + OfflinePlayer ownerPlayer = Bukkit.getOfflinePlayer(ownerUUID); + + // Erstelle ein Kopf-Item + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta skullMeta = (SkullMeta) head.getItemMeta(); + skullMeta.setOwningPlayer(ownerPlayer); + // Titel als Component und dann in reinen Text umwandeln, falls nötig + Component itemName = MiniMessage.miniMessage().deserialize("Chunk [" + chunk.getX() + ", " + chunk.getZ() + "]"); + skullMeta.displayName(itemName); + List lore = new ArrayList<>(); + lore.add(MiniMessage.miniMessage().deserialize("World: " + chunk.getWorld().getName() + "")); + lore.add(MiniMessage.miniMessage().deserialize("Owner: " + ownerPlayer.getName() + "")); + lore.add(MiniMessage.miniMessage().deserialize("Index: " + (i + 1) + "")); + skullMeta.lore(lore); + head.setItemMeta(skullMeta); + + // Platziere das Item in den Slots 0 bis 44 + inv.setItem(i - startIndex, head); + } + + // Navigation: Falls mehrere Seiten vorhanden sind, füge Navigations-Buttons in der untersten Reihe hinzu + if (totalPages > 1) { + // Vorherige Seite (Slot 45) + if (currentPage > 0) { + ItemStack prev = new ItemStack(Material.ARROW); + prev.editMeta(meta -> meta.displayName(MiniMessage.miniMessage().deserialize("Previous Page"))); + inv.setItem(45, prev); + } + // Nächste Seite (Slot 53) + if (currentPage < totalPages - 1) { + ItemStack next = new ItemStack(Material.ARROW); + next.editMeta(meta -> meta.displayName(MiniMessage.miniMessage().deserialize("Next Page"))); + inv.setItem(53, next); + } + } + + // Öffne das Inventar für den Spieler + player.openInventory(inv); + } + + // Listener für Inventarklicks mit Paper-Event (Adventure Components) + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) return; + // Verwende Paper's getView() um den Titel als Component zu erhalten + Component invTitle = event.getView().title(); + Component expectedTitle = MiniMessage.miniMessage().deserialize("Inv Chunks Bust"); + // Vergleiche die reinen Texte der Components + if (!PlainTextComponentSerializer.plainText().serialize(invTitle) + .equals(PlainTextComponentSerializer.plainText().serialize(expectedTitle))) { + return; + } + event.setCancelled(true); // Standardverhalten verhindern + + ItemStack clickedItem = event.getCurrentItem(); + if (clickedItem == null || !clickedItem.hasItemMeta()) return; + // Holen des angezeigten Namens als reiner Text + Component itemNameComp = clickedItem.getItemMeta().displayName(); + assert itemNameComp != null; + String displayName = PlainTextComponentSerializer.plainText().serialize(itemNameComp); + + // Hole den Clan des Spielers (angenommen, der Spieler ist in einem Clan) + me.freezy.plugins.papermc.blazesmp.module.Clan clan = BlazeSMP.getInstance().getClans().getClanByMember(player.getUniqueId()); + if (clan == null) return; + + // Hole alle Einträge (Chunks) des Clans + List> chunkEntries = new ArrayList<>(clan.getChunkOwnerMap().entrySet()); + int itemsPerPage = 45; + int totalPages = (int) Math.ceil(chunkEntries.size() / (double) itemsPerPage); + int currentPage = paginatedData.getPage(player.getUniqueId()); + + // Navigation behandeln + if (displayName.contains("Previous Page")) { + if (currentPage > 0) { + currentPage--; + paginatedData.setPage(player.getUniqueId(), currentPage); + openChunksMenu(player, chunkEntries, currentPage, totalPages, itemsPerPage, clan); + } + } else if (displayName.contains("Next Page")) { + if (currentPage < totalPages - 1) { + currentPage++; + paginatedData.setPage(player.getUniqueId(), currentPage); + openChunksMenu(player, chunkEntries, currentPage, totalPages, itemsPerPage, clan); + } + } else { + // Reagiere auf Klicks auf einzelne Chunk-Items + player.sendMessage(MiniMessage.miniMessage().deserialize("Du hast Chunk-Item: " + displayName + " angeklickt.")); + } + } + + // Listener, um die gespeicherten Seiteninformationen aufzuräumen + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (!(event.getPlayer() instanceof Player player)) return; + paginatedData.removePage(player.getUniqueId()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + paginatedData.removePage(event.getPlayer().getUniqueId()); + } + + /** + * Hilfsklasse zur Verwaltung der aktuellen Seite pro Spieler. + */ + private static class PaginatedData { + private final Map playerPages = new HashMap<>(); + + public void setPage(UUID playerUUID, int page) { + playerPages.put(playerUUID, page); + } + + public int getPage(UUID playerUUID) { + return playerPages.getOrDefault(playerUUID, 1); + } + + public void removePage(UUID playerUUID) { + playerPages.remove(playerUUID); + } + } +} diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerChatListener.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerChatListener.java new file mode 100644 index 0000000..e626b39 --- /dev/null +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerChatListener.java @@ -0,0 +1,37 @@ +package me.freezy.plugins.papermc.blazesmp.listener; + +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.scoreboard.Team; + +public class PlayerChatListener implements Listener { + @EventHandler + public void onChat(AsyncChatEvent event) { + Player player = event.getPlayer(); + Team team = player.getScoreboard().getEntryTeam(player.getName()); + + Component prefix = Component.empty(); + Component suffix = Component.empty(); + + if (team != null) { + team.prefix(); + prefix = team.prefix(); + team.suffix(); + suffix = team.suffix(); + } + + Component messageComponent = event.message(); + + Component chatComponent = Component.empty() + .append(prefix) + .append(Component.text(player.getName())) + .append(suffix) + .append(Component.text(": ")) + .append(messageComponent); + + event.renderer((source, sourceDisplayName, msg, viewer) -> chatComponent); + } +} diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerClaimListener.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerClaimListener.java new file mode 100644 index 0000000..f9cf88a --- /dev/null +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerClaimListener.java @@ -0,0 +1,123 @@ +package me.freezy.plugins.papermc.blazesmp.listener; + +import me.freezy.plugins.papermc.blazesmp.BlazeSMP; +import me.freezy.plugins.papermc.blazesmp.module.Clan; +import me.freezy.plugins.papermc.blazesmp.module.manager.Clans; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; + +public class PlayerClaimListener implements Listener { + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Location location = event.getBlock().getLocation(); + Chunk chunk = location.getChunk(); + Clans clans = BlazeSMP.getInstance().getClans(); + Clan clan = clans.getClanByChunk(chunk); + if (clan != null) { + if (clans.isInClan(event.getPlayer().getUniqueId(), clan)) { + return; + } + event.setCancelled(true); + } + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + Location location = event.getBlock().getLocation(); + Chunk chunk = location.getChunk(); + Clans clans = BlazeSMP.getInstance().getClans(); + Clan clan = clans.getClanByChunk(chunk); + if (clan != null) { + if (clans.isInClan(event.getPlayer().getUniqueId(), clan)) { + return; + } + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerInteractEvent(PlayerInteractEvent event) { + Location location = event.getPlayer().getLocation(); + Chunk chunk = location.getChunk(); + Clans clans = BlazeSMP.getInstance().getClans(); + Clan clan = clans.getClanByChunk(chunk); + if (clan != null) { + if (clans.isInClan(event.getPlayer().getUniqueId(), clan)) { + return; + } + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + Location location = event.getPlayer().getLocation(); + Chunk chunk = location.getChunk(); + Clans clans = BlazeSMP.getInstance().getClans(); + Clan clan = clans.getClanByChunk(chunk); + if (clan != null) { + if (clans.isInClan(event.getPlayer().getUniqueId(), clan)) { + return; + } + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { + Location location = event.getPlayer().getLocation(); + Chunk chunk = location.getChunk(); + Clans clans = BlazeSMP.getInstance().getClans(); + Clan clan = clans.getClanByChunk(chunk); + if (clan != null) { + if (clans.isInClan(event.getPlayer().getUniqueId(), clan)) { + return; + } + event.setCancelled(true); + } + } + + @EventHandler + public void onEnterClanClaim(PlayerMoveEvent event) { + Chunk fromChunk = event.getFrom().getChunk(); + Chunk toChunk = event.getTo().getChunk(); + if (fromChunk.equals(toChunk)) { + return; + } + + Player player = event.getPlayer(); + Clans clans = BlazeSMP.getInstance().getClans(); + + Clan oldClan = clans.getClanByChunk(fromChunk); + Clan newClan = clans.getClanByChunk(toChunk); + + if (oldClan != null && (!oldClan.equals(newClan))) { + player.sendActionBar( + MiniMessage.miniMessage().deserialize( + "You left the claim of " + oldClan.getName() + "!" + ) + ); + } + + if (newClan != null && (!newClan.equals(oldClan))) { + player.sendActionBar( + MiniMessage.miniMessage().deserialize( + "Territory of " + newClan.getName() + " - " + + Bukkit.getOfflinePlayer(newClan.getChunkOwnerMap().get(toChunk)).getName() + + "!" + ) + ); + } + } +} diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerCommandBlockerListener.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerCommandBlockerListener.java new file mode 100644 index 0000000..878e469 --- /dev/null +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerCommandBlockerListener.java @@ -0,0 +1,227 @@ +package me.freezy.plugins.papermc.blazesmp.listener; + +import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +public class PlayerCommandBlockerListener implements Listener { + private final Set blockedCommands = new LinkedHashSet<>(Arrays.asList( + "/bukkit:?", + "/?", + "/bukkit:about", + "/about", + "/bukkit:help", + "/help", + "/bukkit:pl", + "/pl", + "/bukkit:plugins", + "/plugins", + "/bukkit:reload", + "/reload", + "/bukkit:rl", + "/rl", + "/bukkit:timings", + "/timings", + "/bukkit:ver", + "/ver", + "/bukkit:version", + "/version", + "/paper:callback", + "/callback", + "/paper:mspt", + "/mspt", + "/paper:paper", + "/paper", + "/paper:spark", + "/spark", + "/minecraft:advancement", + "/advancement", + "/minecraft:attribute", + "/attribute", + "/minecraft:ban", + "/ban", + "/minecraft:ban-ip", + "/ban-ip", + "/minecraft:banlist", + "/banlist", + "/minecraft:bossbar", + "/bossbar", + "/minecraft:clear", + "/clear", + "/minecraft:clone", + "/clone", + "/minecraft:damage", + "/damage", + "/minecraft:data", + "/data", + "/minecraft:datapack", + "/datapack", + "/minecraft:difficulty", + "/difficulty", + "/minecraft:effect", + "/effect", + "/minecraft:enchant", + "/enchant", + "/minecraft:execute", + "/execute", + "/minecraft:experience", + "/experience", + "/minecraft:fill", + "/fill", + "/minecraft:fillbiome", + "/fillbiome", + "/minecraft:foreload", + "/foreload", + "/minecraft:gamerule", + "/gamerule", + "/minecraft:give", + "/give", + "/minecraft:help", + "/help", + "/minecraft:item", + "/item", + "/minecraft:jfr", + "/jfr", + "/minecraft:kick", + "/kick", + "/minecraft:kill", + "/kill", + "/minecraft:list", + "/list", + "/minecraft:locate", + "/locate", + "/minecraft:loot", + "/loot", + "/minecraft:me", + "/me", + "/minecraft:op", + "/op", + "/minecraft:pardon", + "/pardon", + "/minecraft:pardon-ip", + "/pardon-ip", + "/minecraft:particle", + "/particle", + "/minecraft:perf", + "/perf", + "/minecraft:place", + "/place", + "/minecraft:playsound", + "/playsound", + "/minecraft:random", + "/random", + "/minecraft:recipe", + "/recipe", + "/minecraft:reload", + "/reload", + "/minecraft:ride", + "/ride", + "/minecraft:rotate", + "/rotate", + "/minecraft:save-all", + "/save-all", + "/minecraft:save-off", + "/save-off", + "/minecraft:save-on", + "/save-on", + "/minecraft:say", + "/say", + "/minecraft:schedule", + "/schedule", + "/minecraft:scoreboard", + "/scoreboard", + "/minecraft:seed", + "/seed", + "/minecraft:setblock", + "/setblock", + "/minecraft:setidletimeout", + "/setidletimeout", + "/minecraft:setworldspawn", + "/setworldspawn", + "/minecraft:spawnpoint", + "/spawnpoint", + "/minecraft:spectate", + "/spectate", + "/minecraft:tag", + "/tag", + "/minecraft:team", + "/team", + "/minecraft:teammsg", + "/teammsg", + "/minecraft:teleport", + "/teleport", + "/minecraft:tellraw", + "/tellraw", + "/minecraft:tick", + "/tick", + "/minecraft:time", + "/time", + "/minecraft:title", + "/title", + "/minecraft:tm", + "/tm", + "/minecraft:tp", + "/tp", + "/minecraft:transfer", + "/transfer", + "/minecraft:trigger", + "/trigger", + "/minecraft:weather", + "/weather", + "/minecraft:whitelist", + "/whitelist", + "/minecraft:worldborder", + "/worldborder", + "/minecraft:xp", + "/xp", + "/icanhasbukkit" + )); + + private boolean isBlocked(String input) { + String normalized = input.toLowerCase().trim(); + for (String blocked : blockedCommands) { + blocked = blocked.toLowerCase().trim(); + if (normalized.equals(blocked) || normalized.startsWith(blocked + " ")) { + return true; + } + } + return false; + } + + @EventHandler + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + if (player.isOp()) return; // OPs werden nicht blockiert + + String message = event.getMessage(); + if (isBlocked(message)) { + event.setCancelled(true); + // Zeige den blockierten Befehl als Hinweis (hier der erste Teil der Nachricht) + String blockedPart = message.split(" ")[0]; + player.sendMessage(MiniMessage.miniMessage().deserialize( + "Unknown or incomplete command, see below for error\n" + blockedPart + " <--[HERE]" + )); + } + } + + @EventHandler + public void onTabComplete(AsyncTabCompleteEvent event) { + Player player = (Player) event.getSender(); + if (player.isOp()) return; // OPs erhalten weiterhin Tab-Vorschläge + + String buffer = event.getBuffer(); + if (isBlocked(buffer)) { + // Alle Tab-Vorschläge entfernen und das Event abbrechen, + // wenn der eingegebene Befehl blockiert ist. + event.getCompletions().clear(); + event.setCancelled(true); + } + } +} diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/JoinListener.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerJoinListener.java similarity index 63% rename from src/main/java/me/freezy/plugins/papermc/blazesmp/listener/JoinListener.java rename to src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerJoinListener.java index 0b378dc..9bc133f 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/JoinListener.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/listener/PlayerJoinListener.java @@ -1,15 +1,18 @@ package me.freezy.plugins.papermc.blazesmp.listener; import me.freezy.plugins.papermc.blazesmp.manager.PlayerManager; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -public class JoinListener implements Listener { +public class PlayerJoinListener implements Listener { @EventHandler public void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); new PlayerManager().setPlayerTeam(player); + + event.joinMessage(MiniMessage.miniMessage().deserialize("[+] ").append(player.teamDisplayName())); } } diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/manager/PlayerManager.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/manager/PlayerManager.java index ac1ca01..0dadac2 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/manager/PlayerManager.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/manager/PlayerManager.java @@ -5,6 +5,7 @@ import me.freezy.plugins.papermc.blazesmp.module.Clan; import me.freezy.plugins.papermc.blazesmp.module.manager.Clans; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Team; @@ -63,14 +64,28 @@ public class PlayerManager { "[Player] " ) ); + if (clans.isInClan(playerUUID)) { + if (clans.isLeader(playerUUID)) { + prefix = prefix.append(MiniMessage.miniMessage().deserialize("*")); + } else if (clans.isVice(playerUUID)) { + prefix = prefix.append(MiniMessage.miniMessage().deserialize("*")); + } + } team.prefix(prefix); Clan clan = clans.getClanByMember(playerUUID); + Component suffix; if (clan != null) { - team.suffix(Component.text(" ").append(clan.getTag())); + suffix = (Component.text(" ").append(clan.getTag())); } else { - team.suffix(Component.empty()); + suffix = (Component.empty()); } + team.suffix(); team.addEntity(player); + team.addEntry(player.getName()); + + Component displayName = prefix.append(Component.text(player.getName())).append(suffix); + player.playerListName(displayName); + player.displayName(displayName); } private String generateRandomString(int length) { diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/module/Clan.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/module/Clan.java index d96171e..5fb4df0 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/module/Clan.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/module/Clan.java @@ -32,7 +32,6 @@ public class Clan { @Setter private UUID leaderUUID; @Setter private UUID viceUUID; private final LinkedList members; - private final LinkedList chunks; private final LinkedHashMap chunkOwnerMap; private int chunkAmount; @@ -43,22 +42,20 @@ public class Clan { this.leaderUUID = leaderUUID; this.viceUUID = null; this.members = new LinkedList<>(); - this.chunks = new LinkedList<>(); this.chunkOwnerMap = new LinkedHashMap<>(); this.chunkAmount = 0; } public Clan(UUID clanUUID, String name, Component tag, UUID leaderUUID, UUID viceUUID, - LinkedList members, LinkedList chunks, LinkedHashMap chunkOwnerMap) { + LinkedList members, LinkedHashMap chunkOwnerMap) { this.uuid = clanUUID; this.name = name; this.tag = tag; this.leaderUUID = leaderUUID; this.viceUUID = viceUUID; this.members = members; - this.chunks = chunks; this.chunkOwnerMap = chunkOwnerMap; - this.chunkAmount = chunks.size(); + this.chunkAmount = chunkOwnerMap.size(); } /** @@ -94,7 +91,6 @@ public class Clan { } // Process chunks with world information - LinkedList chunkList = new LinkedList<>(); LinkedHashMap chunkOwnerMap = new LinkedHashMap<>(); if (jsonClan.chunks != null && jsonClan.chunks.locations != null) { for (LocationJson loc : jsonClan.chunks.locations) { @@ -106,14 +102,13 @@ public class Clan { int x = Integer.parseInt(loc.x); int z = Integer.parseInt(loc.z); Chunk chunk = world.getChunkAt(x, z); - chunkList.add(chunk); UUID ownerUUID = (loc.owner == null || loc.owner.isEmpty()) ? null : UUID.fromString(loc.owner); chunkOwnerMap.put(chunk, ownerUUID); } } - Clan clan = new Clan(uuid, jsonClan.name, tagComponent, leader, vice, memberUUIDs, chunkList, chunkOwnerMap); - clan.chunkAmount = (jsonClan.chunks != null) ? jsonClan.chunks.amount : chunkList.size(); + Clan clan = new Clan(uuid, jsonClan.name, tagComponent, leader, vice, memberUUIDs, chunkOwnerMap); + clan.chunkAmount = (jsonClan.chunks != null) ? jsonClan.chunks.amount : chunkOwnerMap.size(); return clan; } catch (IOException e) { LOGGER.severe("Error loading clan: " + e.getMessage()); @@ -136,8 +131,6 @@ public class Clan { this.viceUUID = loaded.viceUUID; this.members.clear(); this.members.addAll(loaded.members); - this.chunks.clear(); - this.chunks.addAll(loaded.chunks); this.chunkOwnerMap.clear(); this.chunkOwnerMap.putAll(loaded.chunkOwnerMap); this.chunkAmount = loaded.chunkAmount; @@ -158,7 +151,7 @@ public class Clan { jsonClan.chunks = new ChunksJson(); jsonClan.chunks.amount = this.chunkAmount; jsonClan.chunks.locations = new LinkedList<>(); - for (Chunk chunk : this.chunks) { + for (Chunk chunk : this.chunkOwnerMap.keySet()) { LocationJson loc = new LocationJson(); // Assuming the owner mapping may be null UUID owner = this.chunkOwnerMap.getOrDefault(chunk, null); diff --git a/src/main/java/me/freezy/plugins/papermc/blazesmp/module/manager/Clans.java b/src/main/java/me/freezy/plugins/papermc/blazesmp/module/manager/Clans.java index 85653bf..d232e58 100644 --- a/src/main/java/me/freezy/plugins/papermc/blazesmp/module/manager/Clans.java +++ b/src/main/java/me/freezy/plugins/papermc/blazesmp/module/manager/Clans.java @@ -2,12 +2,16 @@ package me.freezy.plugins.papermc.blazesmp.module.manager; import lombok.Getter; import me.freezy.plugins.papermc.blazesmp.module.Clan; +import org.bukkit.Chunk; import java.io.File; +import java.util.LinkedHashMap; import java.util.LinkedList; +import java.util.Map; import java.util.UUID; import java.util.logging.Logger; +@SuppressWarnings("unused") @Getter public class Clans { private static final Logger LOGGER = Logger.getLogger("ClanManager"); @@ -31,7 +35,11 @@ public class Clans { File dir = new File(CLAN_STORAGE_PATH); if (!dir.exists()) { LOGGER.info("Clan storage directory does not exist. Creating directory..."); - dir.mkdirs(); + if (!dir.mkdirs()) { + LOGGER.warning("Failed to create clan storage directory."); + } else { + LOGGER.info("Created clan storage directory."); + } return; } File[] files = dir.listFiles((file, name) -> name.endsWith(".json")); @@ -123,6 +131,33 @@ public class Clans { } } + public LinkedHashMap> getClanChunks(Clan clan) { + LinkedHashMap> clanChunks = new LinkedHashMap<>(); + for (Map.Entry entry : clan.getChunkOwnerMap().entrySet()) { + UUID ownerUUID = entry.getValue(); + Chunk chunk = entry.getKey(); + if (!clanChunks.containsKey(ownerUUID)) { + clanChunks.put(ownerUUID, new LinkedList<>()); + } + clanChunks.get(ownerUUID).add(chunk); + } + return clanChunks; + } + + public LinkedList getPlayerChunks(UUID playerUUID) { + LinkedList playerChunks = new LinkedList<>(); + for (Clan clan : clans) { + if (clan.getChunkOwnerMap().containsValue(playerUUID)) { + for (Map.Entry entry : clan.getChunkOwnerMap().entrySet()) { + if (entry.getValue().equals(playerUUID)) { + playerChunks.add(entry.getKey()); + } + } + } + } + return playerChunks; + } + public boolean isLeader(UUID playerUUID) { for (Clan clan : clans) { if (clan.getLeaderUUID().equals(playerUUID)) { @@ -159,6 +194,10 @@ public class Clans { return false; } + public boolean isInClan(UUID playerUUID,Clan clan) { + return clan.getMembers().contains(playerUUID) || clan.getLeaderUUID().equals(playerUUID) || (clan.getViceUUID() != null && clan.getViceUUID().equals(playerUUID)); + } + public Clan getClanByMember(UUID playerUUID) { for (Clan clan : clans) { if (clan.getMembers().contains(playerUUID) || clan.getLeaderUUID().equals(playerUUID) || (clan.getViceUUID() != null && clan.getViceUUID().equals(playerUUID))) { @@ -167,4 +206,32 @@ public class Clans { } return null; } + + public void setClanChunks(Clan playerClan, LinkedHashMap> existingClaims) { + for (Map.Entry> entry : existingClaims.entrySet()) { + UUID ownerUUID = entry.getKey(); + LinkedList chunks = entry.getValue(); + for (Chunk chunk : chunks) { + playerClan.getChunkOwnerMap().put(chunk, ownerUUID); + } + } + playerClan.save(); + } + + public Clan getClanByChunk(Chunk chunk) { + for (Clan clan : clans) { + if (clan.getChunkOwnerMap().containsKey(chunk)) { + return clan; + } + } + return null; + } + public boolean isChunkClaimed(Chunk chunk) { + for (Clan clan : clans) { + if (clan.getChunkOwnerMap().containsKey(chunk)) { + return true; + } + } + return false; + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index dd8f05c..bafae36 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,2 +1,3 @@ op-prefix: '[Team] ' -player-prefix: '[Player] ' \ No newline at end of file +player-prefix: '[Player] ' +discord-report-webhook: 'https://discord.com/api/webhooks/1234567890/abcdefghijklmnopqrstuvwxyz' \ No newline at end of file