From 577e99fcfc263434687a16d6eb3b113800da288c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Wed, 28 Aug 2024 19:32:07 +0200
Subject: [PATCH 1/8] added basic reply

---
 .../java/eu/mhsl/craftattack/spawn/Main.java  |  4 +-
 .../chatMention/ChatMentionListener.java      | 12 ++-
 .../appliances/chatMessages/ChatMessages.java | 10 +-
 .../privateMessage/PrivateMessage.java        | 93 +++++++++++++++++++
 .../commands/PrivateMessageCommand.java       | 18 ++++
 .../commands/PrivateReplyCommand.java         | 18 ++++
 .../spawn/appliances/whitelist/Whitelist.java | 17 ++--
 .../spawn/util/LimitedSizedList.java          | 27 ++++++
 .../spawn/util/statistics/NetworkMonitor.java |  5 +-
 src/main/resources/config.yml                 |  3 +-
 src/main/resources/plugin.yml                 |  4 +-
 11 files changed, 193 insertions(+), 18 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/Main.java b/src/main/java/eu/mhsl/craftattack/spawn/Main.java
index 692b58f..4d948cf 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/Main.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/Main.java
@@ -19,6 +19,7 @@ import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
 import eu.mhsl.craftattack.spawn.appliances.panicBan.PanicBan;
 import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
 import eu.mhsl.craftattack.spawn.appliances.portableCrafting.PortableCrafting;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
 import eu.mhsl.craftattack.spawn.appliances.projectStart.ProjectStart;
 import eu.mhsl.craftattack.spawn.appliances.report.Report;
 import eu.mhsl.craftattack.spawn.appliances.restart.Restart;
@@ -75,7 +76,8 @@ public final class Main extends JavaPlugin {
             new AutoShulker(),
             new AntiSignEdit(),
             new HotbarRefill(),
-            new ChatMention()
+            new ChatMention(),
+            new PrivateMessage()
         );
 
         Main.logger.info("Loading appliances...");
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java
index db27a13..182cf69 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java
@@ -1,6 +1,8 @@
 package eu.mhsl.craftattack.spawn.appliances.chatMention;
 
+import eu.mhsl.craftattack.spawn.Main;
 import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
+import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
 import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
 import eu.mhsl.craftattack.spawn.appliances.settings.settings.ChatMentionSetting;
 import io.papermc.paper.event.player.AsyncChatDecorateEvent;
@@ -8,6 +10,7 @@ import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
 import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerJoinEvent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -22,6 +25,7 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
 
         ChatMentionSetting.ChatMentionConfig config = Settings.instance()
             .getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
+        ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
 
         Component result = words.stream()
             .map(word -> {
@@ -29,10 +33,11 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
                 boolean isPlayer = getAppliance().getPlayerNames().contains(wordWithoutAnnotation);
                 if(isPlayer && config.applyMentions()) {
                     mentioned.add(wordWithoutAnnotation);
-                    return Component.text(
+                    Component mention = Component.text(
                         getAppliance().formatPlayer(wordWithoutAnnotation),
                         NamedTextColor.GOLD
                     );
+                    return chatMessages.addReportActions(mention, wordWithoutAnnotation);
                 } else {
                     return Component.text(word);
                 }
@@ -43,4 +48,9 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
         getAppliance().notifyPlayers(mentioned);
         event.result(result);
     }
+
+    @EventHandler
+    public void onJoin(PlayerJoinEvent event) {
+        getAppliance().refreshPlayers();
+    }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java
index 53108ec..eb5652f 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java
@@ -13,11 +13,13 @@ import java.util.List;
 
 public class ChatMessages extends Appliance {
     public Component getReportablePlayerName(Player player) {
-        return Component
-            .text("")
-            .append(player.displayName())
+        return addReportActions(player.displayName(), player.getName());
+    }
+
+    public Component addReportActions(Component message, String username) {
+        return message
             .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
-            .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/report " + player.getName() + " "));
+            .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format("/report %s ", username)));
     }
 
     @Override
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
new file mode 100644
index 0000000..ce9922c
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -0,0 +1,93 @@
+package eu.mhsl.craftattack.spawn.appliances.privateMessage;
+
+import eu.mhsl.craftattack.spawn.Main;
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateMessageCommand;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateReplyCommand;
+import eu.mhsl.craftattack.spawn.util.LimitedSizedList;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class PrivateMessage extends Appliance {
+    public final int targetChangeTimeout = 30;
+
+    private record Conversation(UUID target, Long lastSet) {}
+    private final Map<Player, LimitedSizedList<Conversation>> replyMapping = new WeakHashMap<>();
+
+    public void reply(Player sender, String message) {
+        Conversation senderConversation = this.replyMapping.get(sender).getLast();
+        if(senderConversation == null) throw new ApplianceCommand.Error("Du kannst nicht auf eine konversation antworten, da keine vorhanden ist.");
+//        if(!senderConversation.isChecked && senderConversation.lastSet > System.currentTimeMillis() - targetChangeTimeout) {
+//            throw new ApplianceCommand.Error("ziel geändedrt.......");
+//        }
+        Player target = Bukkit.getPlayer(senderConversation.target);
+        if(target == null) throw new ApplianceCommand.Error("Der Spieler ist nicht mehr verfügbar.");
+        sendWhisper(sender, new ResolvedPmUserArguments(target, message));
+    }
+
+    public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
+//        this.replyMapping.put(
+//            sender,
+//            new Conversation(
+//                userArguments.receiver.getUniqueId(),
+//                System.currentTimeMillis()
+//            )
+//        );
+//        this.replyMapping.put(
+//            userArguments.receiver,
+//            new Conversation(
+//                sender.getUniqueId(),
+//                System.currentTimeMillis()
+//            )
+//        );
+
+        ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
+        Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
+
+        sender.sendMessage(
+            Component.text()
+                .append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", userArguments.receiver.getName()))))
+                .append(sender.displayName())
+                .append(Component.text(" zu ", NamedTextColor.GRAY))
+                .append(chatMessages.getReportablePlayerName(userArguments.receiver))
+                .append(Component.text(" > ", NamedTextColor.GRAY))
+                .append(Component.text(userArguments.message))
+        );
+        userArguments.receiver.sendMessage(
+            Component.text()
+                .append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", sender.getName()))))
+                .append(chatMessages.getReportablePlayerName(sender))
+                .append(Component.text(" zu ", NamedTextColor.GRAY))
+                .append(userArguments.receiver.displayName())
+                .append(Component.text(" > ", NamedTextColor.GRAY))
+                .append(Component.text(userArguments.message))
+        );
+    }
+
+    public record ResolvedPmUserArguments(Player receiver, String message) {}
+    public ResolvedPmUserArguments resolveImplicit(String[] args) {
+        if(args.length < 2) throw new ApplianceCommand.Error("Es muss ein Spieler sowie eine Nachricht angegeben werden.");
+        List<String> arguments = List.of(args);
+        Player targetPlayer = Bukkit.getPlayer(arguments.getFirst());
+        if(targetPlayer == null) throw new ApplianceCommand.Error(String.format("Der Spieler %s konnte nicht gefunden werden.", arguments.getFirst()));
+        String message = arguments.stream().skip(1).collect(Collectors.joining(" "));
+        return new ResolvedPmUserArguments(targetPlayer, message);
+    }
+
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(
+            new PrivateMessageCommand(),
+            new PrivateReplyCommand()
+        );
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java
new file mode 100644
index 0000000..711bbe3
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java
@@ -0,0 +1,18 @@
+package eu.mhsl.craftattack.spawn.appliances.privateMessage.commands;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class PrivateMessageCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
+    public PrivateMessageCommand() {
+        super("msg");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        getAppliance().sendWhisper(getPlayer(), getAppliance().resolveImplicit(args));
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java
new file mode 100644
index 0000000..fd7de78
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java
@@ -0,0 +1,18 @@
+package eu.mhsl.craftattack.spawn.appliances.privateMessage.commands;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class PrivateReplyCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
+    public PrivateReplyCommand() {
+        super("r");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        getAppliance().reply(getPlayer(), String.join(" ", args));
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java
index abfd06a..920c2c6 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java
@@ -43,7 +43,10 @@ public class Whitelist extends Appliance {
     public void integrityCheck(Player player) throws DisconnectInfo.Throwable {
         try {
             Main.instance().getLogger().info(String.format("Running integrityCheck for %s", player.getName()));
-            UserData user = this.fetchUserData(player.getUniqueId());
+            boolean overrideCheck = localConfig().getBoolean("overrideIntegrityCheck", false);
+            UserData user = overrideCheck
+                ? new UserData(player.getUniqueId(), player.getName(), "", "", 0L, 0L)
+                : this.fetchUserData(player.getUniqueId());
 
             if(timestampRelevant(user.banned_until)) {
                 Instant bannedDate = new Date(user.banned_until * 1000L)
@@ -63,12 +66,9 @@ public class Whitelist extends Appliance {
 
             Main.instance().getAppliance(Outlawed.class).updateForcedStatus(player, timestampRelevant(user.outlawed_until));
 
-            String purePlayerName;
-            if(Floodgate.isBedrock(player)) {
-                purePlayerName = Floodgate.getBedrockPlayer(player).getUsername();
-            } else {
-                purePlayerName = player.getName();
-            }
+            String purePlayerName = Floodgate.isBedrock(player)
+                ? Floodgate.getBedrockPlayer(player).getUsername()
+                : player.getName();
 
             if(!user.username.trim().equalsIgnoreCase(purePlayerName))
                 throw new DisconnectInfo.Throwable(
@@ -100,8 +100,7 @@ public class Whitelist extends Appliance {
         URIBuilder uriBuilder = new URIBuilder(apiEndpoint);
         uriBuilder.addParameter("uuid", uuid.toString());
 
-        try {
-            HttpClient client = HttpClient.newHttpClient();
+        try(HttpClient client = HttpClient.newHttpClient()) {
             HttpRequest httpRequest = HttpRequest.newBuilder()
                 .uri(uriBuilder.build())
                 .header("Content-Type", "application/json")
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java b/src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java
new file mode 100644
index 0000000..1b03907
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java
@@ -0,0 +1,27 @@
+package eu.mhsl.craftattack.spawn.util;
+
+import java.util.ArrayList;
+
+public class LimitedSizedList<T> extends ArrayList<T> {
+    private final int maxSize;
+
+    public LimitedSizedList(int size){
+        this.maxSize = size;
+    }
+
+    public boolean add(T element){
+        boolean r = super.add(element);
+        if (size() > maxSize){
+            removeRange(0, size() - maxSize);
+        }
+        return r;
+    }
+
+    public T getYoungest() {
+        return get(size() - 1);
+    }
+
+    public T getOldest() {
+        return get(0);
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java b/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
index 4604e31..88719e2 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
@@ -9,6 +9,7 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.time.Duration;
+import java.util.logging.Level;
 
 public class NetworkMonitor {
     private long previousRxBytes = 0;
@@ -78,7 +79,9 @@ public class NetworkMonitor {
             String content = new String(Files.readAllBytes(Paths.get(path)));
             return Long.parseLong(content.trim());
         } catch(IOException e) {
-            throw new RuntimeException("Failed recieving Network statistic", e);
+            Main.logger().log(Level.SEVERE, "Statistics are only supported on Linux! Is tablist.interface config set correctly?");
+            this.stop();
+            throw new RuntimeException("Failed reading network statistic", e);
         }
     }
 }
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index af22dde..1e2a49f 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -42,9 +42,10 @@ help:
   spawn: "Der Weltspawn befindet sich bei x:0 y:0 z:0"
 
 playerLimit:
-  maxPlayers: 0
+  maxPlayers: 100
 
 whitelist:
+  overrideIntegrityCheck: false
   api: https://mhsl.eu/craftattack/api/user
 
 tablist:
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index a9e385b..9e86bca 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -36,4 +36,6 @@ commands:
   kick:
   panicBan:
   vogelfrei:
-  settings:
\ No newline at end of file
+  settings:
+  msg:
+  r:
\ No newline at end of file

From 26b9fd5e0f65e1b264cc217e279c561eff5574bf Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Sun, 29 Sep 2024 22:43:55 +0200
Subject: [PATCH 2/8] changed basic reply logic

---
 .../privateMessage/PrivateMessage.java        | 110 ++++++++++++++----
 1 file changed, 85 insertions(+), 25 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index ce9922c..2f265aa 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -6,7 +6,6 @@ import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
 import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
 import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateMessageCommand;
 import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateReplyCommand;
-import eu.mhsl.craftattack.spawn.util.LimitedSizedList;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.event.ClickEvent;
 import net.kyori.adventure.text.format.NamedTextColor;
@@ -18,37 +17,98 @@ import java.util.*;
 import java.util.stream.Collectors;
 
 public class PrivateMessage extends Appliance {
-    public final int targetChangeTimeout = 30;
+    public final int targetChangeTimeoutSeconds = 30;
 
     private record Conversation(UUID target, Long lastSet) {}
-    private final Map<Player, LimitedSizedList<Conversation>> replyMapping = new WeakHashMap<>();
+    private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
 
     public void reply(Player sender, String message) {
-        Conversation senderConversation = this.replyMapping.get(sender).getLast();
-        if(senderConversation == null) throw new ApplianceCommand.Error("Du kannst nicht auf eine konversation antworten, da keine vorhanden ist.");
-//        if(!senderConversation.isChecked && senderConversation.lastSet > System.currentTimeMillis() - targetChangeTimeout) {
-//            throw new ApplianceCommand.Error("ziel geändedrt.......");
-//        }
-        Player target = Bukkit.getPlayer(senderConversation.target);
-        if(target == null) throw new ApplianceCommand.Error("Der Spieler ist nicht mehr verfügbar.");
-        sendWhisper(sender, new ResolvedPmUserArguments(target, message));
+        if(this.replyMapping.get(sender) == null || this.replyMapping.get(sender).isEmpty()) throw new ApplianceCommand.Error("Du kannst nicht auf eine Konversation antworten, da keine vorhanden ist.");
+
+        Conversation youngestEntry = this.replyMapping.get(sender).stream()
+            .max(Comparator.comparingLong(o -> o.lastSet))
+            .orElse(this.replyMapping.get(sender).getLast());
+
+        if(youngestEntry.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000) || this.replyMapping.get(sender).size() == 1) {
+            Player target = Bukkit.getPlayer(youngestEntry.target());
+            if(target == null) throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
+
+            this.replyMapping.get(sender).clear();
+            sendWhisper(sender, new ResolvedPmUserArguments(target, message));
+            return;
+        }
+
+        List<Conversation> oldConversations = this.replyMapping.get(sender).stream()
+            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
+            .toList();
+        this.replyMapping.get(sender).removeAll(oldConversations);
+
+        List<String> playerNames = this.replyMapping.get(sender).stream()
+            .map(conversation -> {
+                if(Bukkit.getPlayer(conversation.target()) != null) {
+                    return Objects.requireNonNull(Bukkit.getPlayer(conversation.target())).getName();
+                } else {
+                    return Bukkit.getOfflinePlayer(conversation.target()).getName();
+                }
+            })
+            .toList();
+
+        sender.sendMessage(
+            Component.text()
+                .append(Component.text("Das Ziel für /r hat sich bei dir in den letzten "))
+                .append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds)))
+                .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? "))
+        );
+        // TODO: add options to click on with the players names
+//        sender.sendMessage(
+//            Component.text()
+//                .append(Component.text())
+//        );
+
     }
 
     public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
-//        this.replyMapping.put(
-//            sender,
-//            new Conversation(
-//                userArguments.receiver.getUniqueId(),
-//                System.currentTimeMillis()
-//            )
-//        );
-//        this.replyMapping.put(
-//            userArguments.receiver,
-//            new Conversation(
-//                sender.getUniqueId(),
-//                System.currentTimeMillis()
-//            )
-//        );
+
+        // Ältere Einträge bei replyMapping für den receiver entfernen
+        if(!(this.replyMapping.get(userArguments.receiver) == null)) {
+            List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
+                .filter(conversation -> conversation.target() == sender.getUniqueId())
+                .toList();
+            this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
+        }
+
+        // Neuen Eintrag bei replyMapping für den receiver
+        Conversation newReceiverConversation = new Conversation(
+            sender.getUniqueId(),
+            System.currentTimeMillis()
+        );
+
+        if(this.replyMapping.get(userArguments.receiver) == null) {
+            List<Conversation> receiverConversationList = new ArrayList<>();
+            receiverConversationList.add(newReceiverConversation);
+
+            this.replyMapping.put(
+                userArguments.receiver,
+                receiverConversationList
+            );
+        } else {
+            this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
+        }
+
+        // Einträge für sender bei replyMapping überschreiben mit receiver
+        List<Conversation> senderConversationList = new ArrayList<>();
+        senderConversationList.add(
+            new Conversation(
+                userArguments.receiver.getUniqueId(),
+                System.currentTimeMillis()
+            )
+        );
+
+        this.replyMapping.put(
+            sender,
+            senderConversationList
+        );
+
 
         ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
         Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);

From f96356b620686404b77e27fd13f7f4627f2276e2 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Mon, 30 Sep 2024 16:23:32 +0200
Subject: [PATCH 3/8] added choose options

---
 .../privateMessage/PrivateMessage.java        | 34 ++++++++++++++++---
 1 file changed, 29 insertions(+), 5 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index 2f265aa..b732f87 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -59,11 +59,35 @@ public class PrivateMessage extends Appliance {
                 .append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds)))
                 .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? "))
         );
-        // TODO: add options to click on with the players names
-//        sender.sendMessage(
-//            Component.text()
-//                .append(Component.text())
-//        );
+
+        
+        Component newTextComponent = Component.text("");
+
+        String firstTargetName;
+        if(Bukkit.getPlayer(youngestEntry.target()) == null) {
+            firstTargetName = Bukkit.getOfflinePlayer(youngestEntry.target()).getName();
+        } else {
+            firstTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestEntry.target())).getName();
+        }
+
+        if(firstTargetName != null) {
+            Component nameComponent = Component.text("");
+            nameComponent.append(Component.text(firstTargetName, NamedTextColor.LIGHT_PURPLE));
+
+            newTextComponent
+                    .append(nameComponent.clickEvent(ClickEvent.runCommand("/msg " + firstTargetName + " " + message)));
+            newTextComponent.append(Component.text(" "));
+        }
+
+
+        playerNames.forEach(playerName -> {
+            Component nameComponent = Component.text(playerName, NamedTextColor.LIGHT_PURPLE);
+            newTextComponent
+                    .append(nameComponent.clickEvent(ClickEvent.runCommand("/msg " + playerName + " " + message)));
+            newTextComponent.append(Component.text(" "));
+        });
+
+        sender.sendMessage(newTextComponent);
 
     }
 

From 471cd8e610562ae4fe8a1a5d29cdcc52b945e8f3 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Wed, 2 Oct 2024 22:27:16 +0200
Subject: [PATCH 4/8] finished logic for reply command

---
 .../privateMessage/PrivateMessage.java        | 34 +++++++++----------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index b732f87..f497fec 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -8,6 +8,7 @@ import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateMessa
 import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateReplyCommand;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.event.HoverEvent;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Bukkit;
 import org.bukkit.entity.Player;
@@ -51,8 +52,10 @@ public class PrivateMessage extends Appliance {
                     return Bukkit.getOfflinePlayer(conversation.target()).getName();
                 }
             })
+            .distinct()
             .toList();
 
+        sender.sendMessage("");
         sender.sendMessage(
             Component.text()
                 .append(Component.text("Das Ziel für /r hat sich bei dir in den letzten "))
@@ -60,8 +63,8 @@ public class PrivateMessage extends Appliance {
                 .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? "))
         );
 
-        
-        Component newTextComponent = Component.text("");
+
+        final Component[] finalComponent = {Component.text("")};
 
         String firstTargetName;
         if(Bukkit.getPlayer(youngestEntry.target()) == null) {
@@ -70,24 +73,21 @@ public class PrivateMessage extends Appliance {
             firstTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestEntry.target())).getName();
         }
 
-        if(firstTargetName != null) {
-            Component nameComponent = Component.text("");
-            nameComponent.append(Component.text(firstTargetName, NamedTextColor.LIGHT_PURPLE));
-
-            newTextComponent
-                    .append(nameComponent.clickEvent(ClickEvent.runCommand("/msg " + firstTargetName + " " + message)));
-            newTextComponent.append(Component.text(" "));
+        if(firstTargetName != null && !playerNames.contains(firstTargetName)) {
+            finalComponent[0] = finalComponent[0].append(Component.text(firstTargetName, NamedTextColor.GOLD)
+                .clickEvent(ClickEvent.runCommand("/msg " + firstTargetName + " " + message))
+                .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
+                .append(Component.text("  "));
         }
 
+        playerNames.forEach(playerName -> finalComponent[0] = finalComponent[0].append(Component.text(playerName, NamedTextColor.GOLD)
+            .clickEvent(ClickEvent.runCommand("/msg " + playerName + " " + message))
+            .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
+            .append(Component.text("  ")));
 
-        playerNames.forEach(playerName -> {
-            Component nameComponent = Component.text(playerName, NamedTextColor.LIGHT_PURPLE);
-            newTextComponent
-                    .append(nameComponent.clickEvent(ClickEvent.runCommand("/msg " + playerName + " " + message)));
-            newTextComponent.append(Component.text(" "));
-        });
-
-        sender.sendMessage(newTextComponent);
+        sender.sendMessage("");
+        sender.sendMessage(finalComponent[0]);
+        sender.sendMessage("");
 
     }
 

From 497c5ad749d072d8ceda87be2f47a2c039cc0e53 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Wed, 2 Oct 2024 23:37:45 +0200
Subject: [PATCH 5/8] too old conversations get deleted, added prefix, changed
 some logic

---
 .../privateMessage/PrivateMessage.java        | 86 ++++++++++++++-----
 1 file changed, 66 insertions(+), 20 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index f497fec..407019e 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -19,17 +19,52 @@ import java.util.stream.Collectors;
 
 public class PrivateMessage extends Appliance {
     public final int targetChangeTimeoutSeconds = 30;
+    public final int conversationTimeoutMinutes = 30;
 
     private record Conversation(UUID target, Long lastSet) {}
     private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
 
     public void reply(Player sender, String message) {
-        if(this.replyMapping.get(sender) == null || this.replyMapping.get(sender).isEmpty()) throw new ApplianceCommand.Error("Du kannst nicht auf eine Konversation antworten, da keine vorhanden ist.");
+        if(this.replyMapping.get(sender) != null) {
+            List<Conversation> tooOldConversations = this.replyMapping.get(sender).stream()
+                .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (conversationTimeoutMinutes*60*1000))
+                .toList();
+            this.replyMapping.get(sender).removeAll(tooOldConversations);
+        }
+
+        if(this.replyMapping.get(sender) == null || this.replyMapping.get(sender).isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
+
+
+        Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
+        ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
 
         Conversation youngestEntry = this.replyMapping.get(sender).stream()
             .max(Comparator.comparingLong(o -> o.lastSet))
             .orElse(this.replyMapping.get(sender).getLast());
 
+        String youngestTargetName;
+        if(Bukkit.getPlayer(youngestEntry.target()) == null) {
+            youngestTargetName = Bukkit.getOfflinePlayer(youngestEntry.target()).getName();
+        } else {
+            youngestTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestEntry.target())).getName();
+        }
+
+
+        if(message.isBlank() && youngestTargetName != null) {
+            sender.sendMessage(
+                privatePrefix
+                    .append(Component.text("Du schreibst aktuell mit ", NamedTextColor.GRAY))
+                    .append(chatMessages.getReportablePlayerName(sender))
+            );
+            return;
+        } else if(message.isBlank()) {
+            sender.sendMessage(
+                privatePrefix
+                    .append(Component.text("Du schreibst aktuell mit niemandem.", NamedTextColor.GRAY))
+            );
+            return;
+        }
+
         if(youngestEntry.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000) || this.replyMapping.get(sender).size() == 1) {
             Player target = Bukkit.getPlayer(youngestEntry.target());
             if(target == null) throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
@@ -42,7 +77,14 @@ public class PrivateMessage extends Appliance {
         List<Conversation> oldConversations = this.replyMapping.get(sender).stream()
             .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
             .toList();
-        this.replyMapping.get(sender).removeAll(oldConversations);
+
+        Conversation youngestOldConversation = null;
+        if(!oldConversations.isEmpty()) {
+            youngestOldConversation = oldConversations.stream()
+                .max(Comparator.comparingLong(o -> o.lastSet))
+                .orElse(oldConversations.getLast());
+            this.replyMapping.get(sender).removeAll(oldConversations);
+        }
 
         List<String> playerNames = this.replyMapping.get(sender).stream()
             .map(conversation -> {
@@ -55,34 +97,41 @@ public class PrivateMessage extends Appliance {
             .distinct()
             .toList();
 
+
         sender.sendMessage("");
         sender.sendMessage(
             Component.text()
-                .append(Component.text("Das Ziel für /r hat sich bei dir in den letzten "))
-                .append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds)))
-                .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? "))
+                .append(privatePrefix)
+                .append(Component.text("Das Ziel für /r hat sich bei dir in den letzten ", NamedTextColor.RED))
+                .append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds), NamedTextColor.RED))
+                .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? ", NamedTextColor.RED))
         );
 
-
         final Component[] finalComponent = {Component.text("")};
 
-        String firstTargetName;
-        if(Bukkit.getPlayer(youngestEntry.target()) == null) {
-            firstTargetName = Bukkit.getOfflinePlayer(youngestEntry.target()).getName();
-        } else {
-            firstTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestEntry.target())).getName();
+        String firstTargetName = null;
+        if(youngestOldConversation != null && Bukkit.getPlayer(youngestOldConversation.target()) == null) {
+            firstTargetName = Bukkit.getOfflinePlayer(youngestOldConversation.target()).getName();
+        } else if(youngestOldConversation != null){
+            firstTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestOldConversation.target())).getName();
         }
 
         if(firstTargetName != null && !playerNames.contains(firstTargetName)) {
-            finalComponent[0] = finalComponent[0].append(Component.text(firstTargetName, NamedTextColor.GOLD)
-                .clickEvent(ClickEvent.runCommand("/msg " + firstTargetName + " " + message))
-                .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
+            finalComponent[0] = finalComponent[0].append(
+                Component.text("[")
+                    .append(Component.text(firstTargetName, NamedTextColor.GOLD))
+                    .append(Component.text("]"))
+                    .clickEvent(ClickEvent.runCommand("/msg " + firstTargetName + " " + message))
+                    .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
                 .append(Component.text("  "));
         }
 
-        playerNames.forEach(playerName -> finalComponent[0] = finalComponent[0].append(Component.text(playerName, NamedTextColor.GOLD)
-            .clickEvent(ClickEvent.runCommand("/msg " + playerName + " " + message))
-            .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
+        playerNames.forEach(playerName -> finalComponent[0] = finalComponent[0].append(
+            Component.text("[")
+                .append(Component.text(playerName, NamedTextColor.GOLD))
+                .append(Component.text("]"))
+                .clickEvent(ClickEvent.runCommand("/msg " + playerName + " " + message))
+                .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
             .append(Component.text("  ")));
 
         sender.sendMessage("");
@@ -93,7 +142,6 @@ public class PrivateMessage extends Appliance {
 
     public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
 
-        // Ältere Einträge bei replyMapping für den receiver entfernen
         if(!(this.replyMapping.get(userArguments.receiver) == null)) {
             List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
                 .filter(conversation -> conversation.target() == sender.getUniqueId())
@@ -101,7 +149,6 @@ public class PrivateMessage extends Appliance {
             this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
         }
 
-        // Neuen Eintrag bei replyMapping für den receiver
         Conversation newReceiverConversation = new Conversation(
             sender.getUniqueId(),
             System.currentTimeMillis()
@@ -119,7 +166,6 @@ public class PrivateMessage extends Appliance {
             this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
         }
 
-        // Einträge für sender bei replyMapping überschreiben mit receiver
         List<Conversation> senderConversationList = new ArrayList<>();
         senderConversationList.add(
             new Conversation(

From d69089a0ebe13d7123a35923f89205dbe5be8515 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Sun, 6 Oct 2024 12:32:20 +0200
Subject: [PATCH 6/8] cleaned up the code

---
 .../privateMessage/PrivateMessage.java        | 83 ++++++-------------
 1 file changed, 25 insertions(+), 58 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index 407019e..205b977 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -34,33 +34,23 @@ public class PrivateMessage extends Appliance {
 
         if(this.replyMapping.get(sender) == null || this.replyMapping.get(sender).isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
 
-
         Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
-        ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
 
         Conversation youngestEntry = this.replyMapping.get(sender).stream()
             .max(Comparator.comparingLong(o -> o.lastSet))
             .orElse(this.replyMapping.get(sender).getLast());
 
-        String youngestTargetName;
-        if(Bukkit.getPlayer(youngestEntry.target()) == null) {
-            youngestTargetName = Bukkit.getOfflinePlayer(youngestEntry.target()).getName();
-        } else {
-            youngestTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestEntry.target())).getName();
-        }
+        if(message.isBlank()) {
+            Component currentTargetComponent = Component.text("niemandem.");
+            Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
+            if(currentTargetPlayer != null) {
+                currentTargetComponent = Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer);
+            }
 
-
-        if(message.isBlank() && youngestTargetName != null) {
             sender.sendMessage(
                 privatePrefix
                     .append(Component.text("Du schreibst aktuell mit ", NamedTextColor.GRAY))
-                    .append(chatMessages.getReportablePlayerName(sender))
-            );
-            return;
-        } else if(message.isBlank()) {
-            sender.sendMessage(
-                privatePrefix
-                    .append(Component.text("Du schreibst aktuell mit niemandem.", NamedTextColor.GRAY))
+                    .append(currentTargetComponent)
             );
             return;
         }
@@ -74,30 +64,6 @@ public class PrivateMessage extends Appliance {
             return;
         }
 
-        List<Conversation> oldConversations = this.replyMapping.get(sender).stream()
-            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
-            .toList();
-
-        Conversation youngestOldConversation = null;
-        if(!oldConversations.isEmpty()) {
-            youngestOldConversation = oldConversations.stream()
-                .max(Comparator.comparingLong(o -> o.lastSet))
-                .orElse(oldConversations.getLast());
-            this.replyMapping.get(sender).removeAll(oldConversations);
-        }
-
-        List<String> playerNames = this.replyMapping.get(sender).stream()
-            .map(conversation -> {
-                if(Bukkit.getPlayer(conversation.target()) != null) {
-                    return Objects.requireNonNull(Bukkit.getPlayer(conversation.target())).getName();
-                } else {
-                    return Bukkit.getOfflinePlayer(conversation.target()).getName();
-                }
-            })
-            .distinct()
-            .toList();
-
-
         sender.sendMessage("");
         sender.sendMessage(
             Component.text()
@@ -107,25 +73,26 @@ public class PrivateMessage extends Appliance {
                 .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? ", NamedTextColor.RED))
         );
 
+        List<Conversation> oldConversations = this.replyMapping.get(sender).stream()
+            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
+            .toList();
+
+        Conversation youngestOldConversation;
+        if(!oldConversations.isEmpty()) {
+            youngestOldConversation = oldConversations.stream()
+                .max(Comparator.comparingLong(o -> o.lastSet))
+                .orElse(oldConversations.getLast());
+            this.replyMapping.get(sender).removeAll(oldConversations);
+            this.replyMapping.get(sender).add(youngestOldConversation);
+        }
+
+        List<String> playerNames = this.replyMapping.get(sender).stream()
+            .map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
+            .distinct()
+            .toList();
+
         final Component[] finalComponent = {Component.text("")};
 
-        String firstTargetName = null;
-        if(youngestOldConversation != null && Bukkit.getPlayer(youngestOldConversation.target()) == null) {
-            firstTargetName = Bukkit.getOfflinePlayer(youngestOldConversation.target()).getName();
-        } else if(youngestOldConversation != null){
-            firstTargetName = Objects.requireNonNull(Bukkit.getPlayer(youngestOldConversation.target())).getName();
-        }
-
-        if(firstTargetName != null && !playerNames.contains(firstTargetName)) {
-            finalComponent[0] = finalComponent[0].append(
-                Component.text("[")
-                    .append(Component.text(firstTargetName, NamedTextColor.GOLD))
-                    .append(Component.text("]"))
-                    .clickEvent(ClickEvent.runCommand("/msg " + firstTargetName + " " + message))
-                    .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
-                .append(Component.text("  "));
-        }
-
         playerNames.forEach(playerName -> finalComponent[0] = finalComponent[0].append(
             Component.text("[")
                 .append(Component.text(playerName, NamedTextColor.GOLD))

From 985b36ddc814777612dc90c48f281879bf5333c6 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Sun, 6 Oct 2024 15:23:37 +0200
Subject: [PATCH 7/8] solved pr comments except making chatMessages an object
 variable

---
 .../privateMessage/PrivateMessage.java        | 98 +++++++++----------
 .../spawn/util/LimitedSizedList.java          | 27 -----
 2 files changed, 48 insertions(+), 77 deletions(-)
 delete mode 100644 src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index 205b977..62e6883 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -7,6 +7,8 @@ import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
 import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateMessageCommand;
 import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateReplyCommand;
 import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.ComponentBuilder;
+import net.kyori.adventure.text.TextComponent;
 import net.kyori.adventure.text.event.ClickEvent;
 import net.kyori.adventure.text.event.HoverEvent;
 import net.kyori.adventure.text.format.NamedTextColor;
@@ -25,27 +27,29 @@ public class PrivateMessage extends Appliance {
     private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
 
     public void reply(Player sender, String message) {
-        if(this.replyMapping.get(sender) != null) {
-            List<Conversation> tooOldConversations = this.replyMapping.get(sender).stream()
-                .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (conversationTimeoutMinutes*60*1000))
-                .toList();
-            this.replyMapping.get(sender).removeAll(tooOldConversations);
-        }
+        this.replyMapping.computeIfAbsent(sender, player -> new ArrayList<>());
 
-        if(this.replyMapping.get(sender) == null || this.replyMapping.get(sender).isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
+        List<Conversation> tooOldConversations = this.replyMapping.get(sender).stream()
+            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (conversationTimeoutMinutes*60*1000))
+            .toList();
+        this.replyMapping.get(sender).removeAll(tooOldConversations);
+
+        List<Conversation> replyMap = this.replyMapping.get(sender);
+
+        if(replyMap.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
 
         Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
 
-        Conversation youngestEntry = this.replyMapping.get(sender).stream()
+        Conversation youngestEntry = replyMap.stream()
             .max(Comparator.comparingLong(o -> o.lastSet))
-            .orElse(this.replyMapping.get(sender).getLast());
+            .orElse(replyMap.getLast());
 
         if(message.isBlank()) {
-            Component currentTargetComponent = Component.text("niemandem.");
             Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
-            if(currentTargetPlayer != null) {
-                currentTargetComponent = Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer);
-            }
+
+            Component currentTargetComponent = currentTargetPlayer != null
+                ? Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer)
+                : Component.text("niemandem.");
 
             sender.sendMessage(
                 privatePrefix
@@ -55,84 +59,79 @@ public class PrivateMessage extends Appliance {
             return;
         }
 
-        if(youngestEntry.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000) || this.replyMapping.get(sender).size() == 1) {
+        List<Conversation> oldConversations = replyMap.stream()
+            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
+            .toList();
+
+        if(oldConversations.contains(youngestEntry) || replyMap.size() == 1) {
             Player target = Bukkit.getPlayer(youngestEntry.target());
             if(target == null) throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
 
             this.replyMapping.get(sender).clear();
-            sendWhisper(sender, new ResolvedPmUserArguments(target, message));
+            this.sendWhisper(sender, new ResolvedPmUserArguments(target, message));
             return;
         }
 
-        sender.sendMessage("");
-        sender.sendMessage(
-            Component.text()
+        ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
+
+        component.append(
+            Component.newline()
                 .append(privatePrefix)
                 .append(Component.text("Das Ziel für /r hat sich bei dir in den letzten ", NamedTextColor.RED))
                 .append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds), NamedTextColor.RED))
-                .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht bekommen? ", NamedTextColor.RED))
+                .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht erhalten? ", NamedTextColor.RED))
+                .appendNewline()
+                .appendNewline()
         );
 
-        List<Conversation> oldConversations = this.replyMapping.get(sender).stream()
-            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
-            .toList();
-
-        Conversation youngestOldConversation;
         if(!oldConversations.isEmpty()) {
-            youngestOldConversation = oldConversations.stream()
+            Conversation youngestOldConversation = oldConversations.stream()
                 .max(Comparator.comparingLong(o -> o.lastSet))
                 .orElse(oldConversations.getLast());
             this.replyMapping.get(sender).removeAll(oldConversations);
             this.replyMapping.get(sender).add(youngestOldConversation);
+            replyMap = this.replyMapping.get(sender);
         }
 
-        List<String> playerNames = this.replyMapping.get(sender).stream()
+        List<String> playerNames = replyMap.stream()
             .map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
             .distinct()
             .toList();
 
-        final Component[] finalComponent = {Component.text("")};
-
-        playerNames.forEach(playerName -> finalComponent[0] = finalComponent[0].append(
+        playerNames.forEach(playerName -> component.append(
             Component.text("[")
                 .append(Component.text(playerName, NamedTextColor.GOLD))
                 .append(Component.text("]"))
-                .clickEvent(ClickEvent.runCommand("/msg " + playerName + " " + message))
+                .clickEvent(ClickEvent.runCommand(String.format("/msg %s %s", playerName, message)))
                 .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
-            .append(Component.text("  ")));
+            .append(Component.text("  "))
+        );
+        component.appendNewline();
 
-        sender.sendMessage("");
-        sender.sendMessage(finalComponent[0]);
-        sender.sendMessage("");
+        sender.sendMessage(component.build());
 
     }
 
     public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
-
-        if(!(this.replyMapping.get(userArguments.receiver) == null)) {
-            List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
-                .filter(conversation -> conversation.target() == sender.getUniqueId())
-                .toList();
-            this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
-        }
-
         Conversation newReceiverConversation = new Conversation(
             sender.getUniqueId(),
             System.currentTimeMillis()
         );
 
-        if(this.replyMapping.get(userArguments.receiver) == null) {
-            List<Conversation> receiverConversationList = new ArrayList<>();
-            receiverConversationList.add(newReceiverConversation);
-
+        if(this.replyMapping.get(userArguments.receiver) != null) {
+            List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
+                .filter(conversation -> conversation.target() == sender.getUniqueId())
+                .toList();
+            this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
+        } else {
             this.replyMapping.put(
                 userArguments.receiver,
-                receiverConversationList
+                new ArrayList<>()
             );
-        } else {
-            this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
         }
 
+        this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
+
         List<Conversation> senderConversationList = new ArrayList<>();
         senderConversationList.add(
             new Conversation(
@@ -146,7 +145,6 @@ public class PrivateMessage extends Appliance {
             senderConversationList
         );
 
-
         ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
         Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
 
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java b/src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java
deleted file mode 100644
index 1b03907..0000000
--- a/src/main/java/eu/mhsl/craftattack/spawn/util/LimitedSizedList.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package eu.mhsl.craftattack.spawn.util;
-
-import java.util.ArrayList;
-
-public class LimitedSizedList<T> extends ArrayList<T> {
-    private final int maxSize;
-
-    public LimitedSizedList(int size){
-        this.maxSize = size;
-    }
-
-    public boolean add(T element){
-        boolean r = super.add(element);
-        if (size() > maxSize){
-            removeRange(0, size() - maxSize);
-        }
-        return r;
-    }
-
-    public T getYoungest() {
-        return get(size() - 1);
-    }
-
-    public T getOldest() {
-        return get(0);
-    }
-}

From 8462e43e89842d4d9b95e5b8dc1d05c363a323e8 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Sun, 6 Oct 2024 15:50:17 +0200
Subject: [PATCH 8/8] replaced replyMapping.get(sender) with variable replyList

---
 .../privateMessage/PrivateMessage.java        | 27 +++++++++----------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
index 62e6883..6ab32d1 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -29,20 +29,20 @@ public class PrivateMessage extends Appliance {
     public void reply(Player sender, String message) {
         this.replyMapping.computeIfAbsent(sender, player -> new ArrayList<>());
 
-        List<Conversation> tooOldConversations = this.replyMapping.get(sender).stream()
+        List<Conversation> replyList = this.replyMapping.get(sender);
+
+        List<Conversation> tooOldConversations = replyList.stream()
             .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (conversationTimeoutMinutes*60*1000))
             .toList();
-        this.replyMapping.get(sender).removeAll(tooOldConversations);
+        replyList.removeAll(tooOldConversations);
 
-        List<Conversation> replyMap = this.replyMapping.get(sender);
-
-        if(replyMap.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
+        if(replyList.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
 
         Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
 
-        Conversation youngestEntry = replyMap.stream()
+        Conversation youngestEntry = replyList.stream()
             .max(Comparator.comparingLong(o -> o.lastSet))
-            .orElse(replyMap.getLast());
+            .orElse(replyList.getLast());
 
         if(message.isBlank()) {
             Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
@@ -59,15 +59,15 @@ public class PrivateMessage extends Appliance {
             return;
         }
 
-        List<Conversation> oldConversations = replyMap.stream()
+        List<Conversation> oldConversations = replyList.stream()
             .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
             .toList();
 
-        if(oldConversations.contains(youngestEntry) || replyMap.size() == 1) {
+        if(oldConversations.contains(youngestEntry) || replyList.size() == 1) {
             Player target = Bukkit.getPlayer(youngestEntry.target());
             if(target == null) throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
 
-            this.replyMapping.get(sender).clear();
+            replyList.clear();
             this.sendWhisper(sender, new ResolvedPmUserArguments(target, message));
             return;
         }
@@ -88,12 +88,11 @@ public class PrivateMessage extends Appliance {
             Conversation youngestOldConversation = oldConversations.stream()
                 .max(Comparator.comparingLong(o -> o.lastSet))
                 .orElse(oldConversations.getLast());
-            this.replyMapping.get(sender).removeAll(oldConversations);
-            this.replyMapping.get(sender).add(youngestOldConversation);
-            replyMap = this.replyMapping.get(sender);
+            replyList.removeAll(oldConversations);
+            replyList.add(youngestOldConversation);
         }
 
-        List<String> playerNames = replyMap.stream()
+        List<String> playerNames = replyList.stream()
             .map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
             .distinct()
             .toList();