From 918ee5ed00a613022f5f5587d8197d8951fa2f47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Wed, 9 Oct 2024 14:36:08 +0200
Subject: [PATCH 01/18] updated interfering listener

---
 gradle/wrapper/gradle-wrapper.properties      |  2 +-
 gradlew                                       | 19 +++++++++---------
 gradlew.bat                                   | 20 +++++++++----------
 .../listener/PlayerInvincibleListener.java    |  7 ++++---
 4 files changed, 25 insertions(+), 23 deletions(-)

diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9f4197d..a441313 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index fcb6fca..b740cf1 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
 #       Darwin, MinGW, and NonStop.
 #
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,7 +83,8 @@ done
 # This is normally unused
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
     case $MAX_FD in #(
       max*)
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045
+        # shellcheck disable=SC2039,SC3045
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
     esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
       '' | soft) :;; #(
       *)
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045
+        # shellcheck disable=SC2039,SC3045
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
@@ -201,11 +202,11 @@ fi
 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 
-# Collect all arguments for the java command;
-#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-#     shell script including quotes and variable substitutions, so put them in
-#     double quotes to make sure that they get re-expanded; and
-#   * put everything else in single quotes, so that it's not re-expanded.
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
 
 set -- \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \
diff --git a/gradlew.bat b/gradlew.bat
index 93e3f59..25da30d 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 goto fail
 
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 if exist "%JAVA_EXE%" goto execute
 
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 goto fail
 
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/listener/PlayerInvincibleListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/listener/PlayerInvincibleListener.java
index 4fd1c30..89e116f 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/listener/PlayerInvincibleListener.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/listener/PlayerInvincibleListener.java
@@ -11,16 +11,17 @@ import org.bukkit.event.entity.FoodLevelChangeEvent;
 public class PlayerInvincibleListener extends ApplianceListener<ProjectStart> {
     @EventHandler
     public void onDamage(EntityDamageEvent event) {
-        if(event.getEntity() instanceof Player) event.setCancelled(getAppliance().isEnabled());
+        if(!(event.getEntity() instanceof Player)) return;
+        if(getAppliance().isEnabled()) event.setCancelled(true);
     }
 
     @EventHandler
     public void onHunger(FoodLevelChangeEvent event) {
-        event.setCancelled(getAppliance().isEnabled());
+        if(getAppliance().isEnabled()) event.setCancelled(true);
     }
 
     @EventHandler
     public void onHit(PrePlayerAttackEntityEvent event) {
-        event.setCancelled(getAppliance().isEnabled());
+        if(getAppliance().isEnabled()) event.setCancelled(true);
     }
 }

From b1427ac90e980eb043c9b9682fe9a931d45052cc Mon Sep 17 00:00:00 2001
From: Hannes Frommann <elias@elias-mueller.com>
Date: Sat, 19 Oct 2024 15:46:34 +0200
Subject: [PATCH 02/18] added playertime command

---
 .../spawn/appliances/playtime/Playtime.java   | 31 +++++++++++++++++++
 .../appliances/playtime/PlaytimeCommand.java  | 31 +++++++++++++++++++
 .../spawn/util/text/DataSizeConverter.java    | 14 +++++++++
 src/main/resources/plugin.yml                 |  3 +-
 4 files changed, 78 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java
new file mode 100644
index 0000000..d1d6203
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java
@@ -0,0 +1,31 @@
+package eu.mhsl.craftattack.spawn.appliances.playtime;
+
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.util.text.DataSizeConverter;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.util.Ticks;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Statistic;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Objects;
+
+public class Playtime extends Appliance {
+    public Component getFormattedPlayerTime(OfflinePlayer player){
+        int playtimeInTicks = player.getStatistic(Statistic.PLAY_ONE_MINUTE);
+        String playtime = DataSizeConverter.formatSecondsToHumanReadable(playtimeInTicks / Ticks.TICKS_PER_SECOND);
+        return Component.text()
+            .append(Component.text("Der Spieler ", NamedTextColor.GRAY))
+            .append(Component.text(Objects.requireNonNull(player.getName())))
+            .append(Component.text(" hat eine Spielzeit von ", NamedTextColor.GRAY))
+            .append(Component.text(playtime, NamedTextColor.GOLD))
+            .build();
+    }
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(new PlaytimeCommand());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java
new file mode 100644
index 0000000..f24e14c
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java
@@ -0,0 +1,31 @@
+package eu.mhsl.craftattack.spawn.appliances.playtime;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class PlaytimeCommand extends ApplianceCommand.PlayerChecked<Playtime> {
+    public PlaytimeCommand() {
+        super("playtime");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        String playerName = getPlayer().getName();
+        if(args.length == 1) {
+            playerName = args[0];
+        }
+
+        OfflinePlayer player = Bukkit.getOfflinePlayer(playerName);
+        if (!player.hasPlayedBefore()) {
+            sender.sendMessage(Component.text("Der Spieler existiert nicht!", NamedTextColor.RED));
+            return;
+        }
+        sender.sendMessage(getAppliance().getFormattedPlayerTime(player));
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java b/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java
index 2154c3c..43a24a8 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java
@@ -38,4 +38,18 @@ public class DataSizeConverter {
         hours %= 60;
         return String.format("%dd%dh%dm%ds", days, hours, minutes, seconds);
     }
+
+    public static String formatSecondsToHumanReadable(int seconds) {
+        if (seconds < 0) return "unsupported";
+
+        int minutes = seconds / 60;
+        int hours = minutes / 60;
+        int days = hours / 24;
+
+        seconds %= 60;
+        minutes %= 60;
+        hours %= 60;
+
+        return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
+    }
 }
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 0a3437b..fe67e51 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -41,4 +41,5 @@ commands:
   maintanance:
   yearRank:
   msg:
-  r:
\ No newline at end of file
+  r:
+  playtime:

From bc84b06f0df35354067bac05cc00ced8870500e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sat, 19 Oct 2024 18:11:19 +0200
Subject: [PATCH 03/18] added AdminChat

---
 .../spawn/appliances/adminChat/AdminChat.java | 37 +++++++++++++++++++
 .../adminChat/AdminChatCommand.java           | 24 ++++++++++++
 src/main/resources/plugin.yml                 |  2 +
 3 files changed, 63 insertions(+)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChat.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChat.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChat.java
new file mode 100644
index 0000000..8891bba
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChat.java
@@ -0,0 +1,37 @@
+package eu.mhsl.craftattack.spawn.appliances.adminChat;
+
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+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.List;
+
+public class AdminChat extends Appliance {
+    public void sendMessage(Player sender, String message) {
+        Component privatePrefix = Component
+            .text("[Admin] ", NamedTextColor.LIGHT_PURPLE)
+            .clickEvent(ClickEvent.suggestCommand(AdminChatCommand.commandName));
+
+        Bukkit.getOnlinePlayers().stream()
+            .filter(player -> player.hasPermission("admin"))
+            .forEach(player -> {
+                Component formattedMessage = Component.text()
+                    .append(privatePrefix)
+                    .append(sender.displayName())
+                    .append(Component.text(" > ", NamedTextColor.DARK_GRAY))
+                    .append(Component.text(message))
+                    .build();
+                player.sendMessage(formattedMessage);
+            });
+    }
+
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(new AdminChatCommand());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java
new file mode 100644
index 0000000..7adaa72
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java
@@ -0,0 +1,24 @@
+package eu.mhsl.craftattack.spawn.appliances.adminChat;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class AdminChatCommand extends ApplianceCommand.PlayerChecked<AdminChat> {
+    public static final String commandName = "adminchat";
+
+    public AdminChatCommand() {
+        super(commandName);
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        if(!sender.hasPermission("admin")) {
+            return;
+        }
+
+        String message = String.join(" ", args);
+        getAppliance().sendMessage(getPlayer(), message);
+    }
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index fe67e51..4847a79 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -43,3 +43,5 @@ commands:
   msg:
   r:
   playtime:
+  adminchat:
+    aliases: [ "sc" ]

From 125b604393ae7b0be928e53377d4975cc30761b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 20 Oct 2024 14:24:12 +0200
Subject: [PATCH 04/18] small refactoring and added missing async task

---
 .../adminChat/AdminChatCommand.java           |  4 +---
 .../spawn/appliances/playtime/Playtime.java   |  3 ++-
 .../appliances/playtime/PlaytimeCommand.java  | 20 +++++++++----------
 3 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java
index 7adaa72..71b2522 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java
@@ -14,9 +14,7 @@ public class AdminChatCommand extends ApplianceCommand.PlayerChecked<AdminChat>
 
     @Override
     protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
-        if(!sender.hasPermission("admin")) {
-            return;
-        }
+        if(!sender.hasPermission("admin")) return;
 
         String message = String.join(" ", args);
         getAppliance().sendMessage(getPlayer(), message);
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java
index d1d6203..6a59694 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java
@@ -14,7 +14,7 @@ import java.util.List;
 import java.util.Objects;
 
 public class Playtime extends Appliance {
-    public Component getFormattedPlayerTime(OfflinePlayer player){
+    public Component getFormattedPlaytime(OfflinePlayer player) {
         int playtimeInTicks = player.getStatistic(Statistic.PLAY_ONE_MINUTE);
         String playtime = DataSizeConverter.formatSecondsToHumanReadable(playtimeInTicks / Ticks.TICKS_PER_SECOND);
         return Component.text()
@@ -24,6 +24,7 @@ public class Playtime extends Appliance {
             .append(Component.text(playtime, NamedTextColor.GOLD))
             .build();
     }
+
     @Override
     protected @NotNull List<ApplianceCommand<?>> commands() {
         return List.of(new PlaytimeCommand());
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java
index f24e14c..03cffc7 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/PlaytimeCommand.java
@@ -1,5 +1,6 @@
 package eu.mhsl.craftattack.spawn.appliances.playtime;
 
+import eu.mhsl.craftattack.spawn.Main;
 import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
@@ -16,16 +17,15 @@ public class PlaytimeCommand extends ApplianceCommand.PlayerChecked<Playtime> {
 
     @Override
     protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
-        String playerName = getPlayer().getName();
-        if(args.length == 1) {
-            playerName = args[0];
-        }
+        String playerName = args.length == 1 ? args[0] : getPlayer().getName();
 
-        OfflinePlayer player = Bukkit.getOfflinePlayer(playerName);
-        if (!player.hasPlayedBefore()) {
-            sender.sendMessage(Component.text("Der Spieler existiert nicht!", NamedTextColor.RED));
-            return;
-        }
-        sender.sendMessage(getAppliance().getFormattedPlayerTime(player));
+        Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
+            OfflinePlayer player = Bukkit.getOfflinePlayer(playerName);
+            if (!player.hasPlayedBefore()) {
+                sender.sendMessage(Component.text("Der Spieler existiert nicht!", NamedTextColor.RED));
+                return;
+            }
+            sender.sendMessage(getAppliance().getFormattedPlaytime(player));
+        });
     }
 }
\ No newline at end of file

From e7075140e35e9102c9a218190a1997d6a53e27d3 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Tue, 5 Nov 2024 20:48:42 +0100
Subject: [PATCH 05/18] added anticheat info command

---
 .../spawn/appliances/acInform/AcInform.java   | 132 ++++++++++++++++++
 .../appliances/acInform/AcInformCommand.java  |  18 +++
 src/main/resources/plugin.yml                 |   1 +
 3 files changed, 151 insertions(+)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
new file mode 100644
index 0000000..bd976c3
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
@@ -0,0 +1,132 @@
+package eu.mhsl.craftattack.spawn.appliances.acInform;
+
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+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.format.NamedTextColor;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+public class AcInform extends Appliance {
+    public void processCommand(@NotNull String[] args) {
+        String anticheatName = null;
+        String playerName = null;
+        String checkName = null;
+        Integer violationCount = null;
+
+        for(int i = 0; i < args.length; i++) {
+            if(!args[i].startsWith("--")) continue;
+            if(i == args.length-1) continue;
+            String nextArgument = args[i+1];
+            if(nextArgument.startsWith("--")) continue;
+
+            switch(args[i]) {
+                case "--anticheatName" -> anticheatName = nextArgument;
+                case "--playerName" -> playerName = nextArgument;
+                case "--check" -> checkName = nextArgument;
+                case "--violationCount" -> violationCount = Integer.valueOf(nextArgument);
+            }
+        }
+
+        this.notifyAdmins(anticheatName, playerName, checkName, violationCount);
+    }
+
+    public void notifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Integer violationCount) {
+        ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
+        Component prefix = Component.text("# ", NamedTextColor.DARK_RED);
+        NamedTextColor textColor = NamedTextColor.GRAY;
+
+        if(playerName == null || playerName.isBlank()) return;
+
+        if(anticheatName != null && !anticheatName.isBlank()) {
+            component.append(
+                Component.newline()
+                    .append(prefix)
+                    .append(Component.text("[", textColor))
+                    .append(Component.text("Anticheat", NamedTextColor.RED))
+                    .append(Component.text("] ", textColor))
+                    .append(Component.text(anticheatName, NamedTextColor.WHITE))
+                    .append(Component.text(":", textColor))
+            );
+        }
+
+        component.append(
+            Component.newline()
+                .append(prefix)
+                .append(Component.text("Player ", textColor))
+                .append(Component.text(playerName, NamedTextColor.WHITE))
+                .append(Component.text(" "))
+        );
+
+        if(checkName == null || checkName.isBlank()) {
+            component.append(Component.text("got detected by Anticheat", textColor));
+        } else if(violationCount != null){
+            component.append(
+                Component.text("failed ", textColor)
+                    .append(Component.text(String.format("%sx ", violationCount), NamedTextColor.WHITE))
+                    .append(Component.text("'", textColor))
+                    .append(Component.text(checkName, NamedTextColor.WHITE))
+                    .append(Component.text("'", textColor))
+            );
+        } else {
+            component.append(
+                Component.text("failed ", textColor)
+                    .append(Component.text("'", textColor))
+                    .append(Component.text(checkName, NamedTextColor.WHITE))
+                    .append(Component.text("'", textColor))
+            );
+        }
+
+        component.append(
+            Component.newline()
+                .append(prefix)
+
+                .append(Component.text("[", NamedTextColor.GRAY))
+                .append(Component.text("Report", NamedTextColor.GOLD))
+                .append(Component.text("] ", NamedTextColor.GRAY))
+                .clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName)))
+        );
+
+        component.append(
+            Component.text(" [", NamedTextColor.GRAY)
+                .append(Component.text("Kick", NamedTextColor.GOLD))
+                .append(Component.text("] ", NamedTextColor.GRAY))
+                .clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName)))
+        );
+
+        component.append(
+            Component.text(" [", NamedTextColor.GRAY)
+                .append(Component.text("Panic Ban", NamedTextColor.GOLD))
+                .append(Component.text("]", NamedTextColor.GRAY))
+                .clickEvent(ClickEvent.suggestCommand(String.format("/panicban %s", playerName)))
+        );
+
+        component.append(
+            Component.text(" [", NamedTextColor.GRAY)
+                .append(Component.text("Teleport", NamedTextColor.GOLD))
+                .append(Component.text("]", NamedTextColor.GRAY))
+                .clickEvent(ClickEvent.suggestCommand(String.format("/tp %s", playerName)))
+        );
+
+        component.appendNewline();
+        TextComponent finalMessage = component.build();
+
+        Bukkit.getOnlinePlayers().stream()
+            .filter(player -> player.hasPermission("admin"))
+            .forEach(player -> player.sendMessage(finalMessage));
+
+    }
+
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(
+            new AcInformCommand()
+        );
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java
new file mode 100644
index 0000000..15707e5
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java
@@ -0,0 +1,18 @@
+package eu.mhsl.craftattack.spawn.appliances.acInform;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class AcInformCommand extends ApplianceCommand<AcInform> {
+    public AcInformCommand() {
+        super("acInform");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+//        if(sender instanceof Player) throw new ApplianceCommand.Error("Dieser Command ist nicht für Spieler!");
+        getAppliance().processCommand(args);
+    }
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 4847a79..f7108d5 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -45,3 +45,4 @@ commands:
   playtime:
   adminchat:
     aliases: [ "sc" ]
+  acInform:

From 01d1926104b9578d3a62b47cd93d904855366543 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Tue, 5 Nov 2024 21:23:43 +0100
Subject: [PATCH 06/18] removed webserver shutdown

---
 src/main/java/eu/mhsl/craftattack/spawn/Main.java           | 5 ++---
 src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java | 4 ----
 2 files changed, 2 insertions(+), 7 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/Main.java b/src/main/java/eu/mhsl/craftattack/spawn/Main.java
index 56d8198..00f837d 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/Main.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/Main.java
@@ -19,7 +19,6 @@ public final class Main extends JavaPlugin {
     private static Logger logger;
 
     private List<Appliance> appliances;
-    private HttpServer httpApi;
 
     @Override
     public void onEnable() {
@@ -62,7 +61,7 @@ public final class Main extends JavaPlugin {
         Main.logger().info(String.format("Initialized %d appliances!", appliances.size()));
 
         Main.logger().info("Starting HTTP API...");
-        this.httpApi = new HttpServer();
+        new HttpServer();
 
         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
         Main.logger().info("Startup complete!");
@@ -76,7 +75,7 @@ public final class Main extends JavaPlugin {
             appliance.onDisable();
             appliance.destruct(this);
         });
-        this.httpApi.stop();
+
         HandlerList.unregisterAll(this);
         Bukkit.getScheduler().cancelTasks(this);
         Main.logger().info("Disabled " + appliances.size() + " appliances!");
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java b/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java
index 4961f1d..412d95b 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java
@@ -24,10 +24,6 @@ public class HttpServer {
         Main.instance().getAppliances().forEach(appliance -> appliance.httpApi(new ApiBuilder(appliance)));
     }
 
-    public void stop() {
-        Spark.stop();
-    }
-
     public record Response(Status status, Object error, Object response) {
         public enum Status {
             FAILURE,

From 3abf5a95e8c64a43ae6b86bbde446cea5c35c6dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Tue, 5 Nov 2024 21:24:06 +0100
Subject: [PATCH 07/18] updated maintenance command feedback

---
 .../appliances/maintenance/MaintenanceCommand.java  | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java
index 9372b17..d23e3e2 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java
@@ -1,6 +1,8 @@
 package eu.mhsl.craftattack.spawn.appliances.maintenance;
 
 import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 import org.jetbrains.annotations.NotNull;
@@ -18,9 +20,14 @@ public class MaintenanceCommand extends ApplianceCommand<Maintenance> {
 
     @Override
     protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
-        if(args.length != 1 || !arguments.containsKey(args[0])) throw new Error("Argument 'enable' oder 'disable' gefordert!");
-        getAppliance().setState(arguments.get(args[0]));
-        sender.sendMessage(String.format("Maintanance: %b", getAppliance().isInMaintenance()));
+        if(args.length == 1 && arguments.containsKey(args[0])) {
+            getAppliance().setState(arguments.get(args[0]));
+            sender.sendMessage(Component.text("Maintanance mode updated!", NamedTextColor.GREEN));
+        }
+        sender.sendMessage(Component.text(
+            String.format("Maintanance mode is %b", getAppliance().isInMaintenance()),
+            NamedTextColor.GOLD
+        ));
     }
 
     @Override

From e8534b42aca7a0788840e4f50358a49066453a67 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Tue, 5 Nov 2024 22:28:11 +0100
Subject: [PATCH 08/18] prevented players from using ac inform command

---
 .../craftattack/spawn/appliances/acInform/AcInformCommand.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java
index 15707e5..149cd46 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java
@@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.appliances.acInform;
 import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
 import org.jetbrains.annotations.NotNull;
 
 public class AcInformCommand extends ApplianceCommand<AcInform> {
@@ -12,7 +13,7 @@ public class AcInformCommand extends ApplianceCommand<AcInform> {
 
     @Override
     protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
-//        if(sender instanceof Player) throw new ApplianceCommand.Error("Dieser Command ist nicht für Spieler!");
+        if(sender instanceof Player) throw new ApplianceCommand.Error("Dieser Command ist nicht für Spieler!");
         getAppliance().processCommand(args);
     }
 }

From 0a7052b6f5276fe91959f1bd5a4ff9d31fd33998 Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Wed, 6 Nov 2024 10:29:20 +0100
Subject: [PATCH 09/18] added error message for missing player name AcInform

---
 .../eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
index bd976c3..336ad42 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
@@ -42,7 +42,7 @@ public class AcInform extends Appliance {
         Component prefix = Component.text("# ", NamedTextColor.DARK_RED);
         NamedTextColor textColor = NamedTextColor.GRAY;
 
-        if(playerName == null || playerName.isBlank()) return;
+        if(playerName == null || playerName.isBlank()) throw new ApplianceCommand.Error("acinform command needs a player (--playerName)");
 
         if(anticheatName != null && !anticheatName.isBlank()) {
             component.append(

From fd902f4ec62bd279206e6fa6d8b118bd6b094ae0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 10 Nov 2024 12:23:44 +0100
Subject: [PATCH 10/18] updated maintenance kick message

---
 .../maintenance/PreventMaintenanceJoinListener.java           | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java
index 9990d23..aacc3bd 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java
@@ -12,9 +12,9 @@ public class PreventMaintenanceJoinListener extends ApplianceListener<Maintenanc
         if(event.getPlayer().hasPermission("bypassMaintainance")) return;
 
         DisconnectInfo disconnectInfo = new DisconnectInfo(
-            "Wartunsarbeiten",
+            "Server für Spieler nicht verfügbar",
             "Zurzeit können nur Admins dem Server beitreten!",
-            "Bitte warte bis die Warungsarbeiten wieder deaktiviert werden.",
+            "Bitte versuche es später erneut.",
             event.getPlayer().getUniqueId()
         );
 

From 0f976d2316e312e4c4e8d402ce896728e31ec73a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 10 Nov 2024 17:28:02 +0100
Subject: [PATCH 11/18] added infobars

---
 .../spawn/appliances/infoBars/Bar.java        | 67 ++++++++++++++
 .../appliances/infoBars/InfoBarCommand.java   | 32 +++++++
 .../spawn/appliances/infoBars/InfoBars.java   | 91 +++++++++++++++++++
 .../infoBars/ShowPreviousBarsListener.java    | 12 +++
 .../appliances/infoBars/bars/MsptBar.java     | 50 ++++++++++
 .../infoBars/bars/PlayerCounterBar.java       | 44 +++++++++
 .../appliances/infoBars/bars/TpsBar.java      | 48 ++++++++++
 src/main/resources/plugin.yml                 |  1 +
 8 files changed, 345 insertions(+)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/Bar.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBarCommand.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBars.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/ShowPreviousBarsListener.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/Bar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/Bar.java
new file mode 100644
index 0000000..503a674
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/Bar.java
@@ -0,0 +1,67 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars;
+
+import eu.mhsl.craftattack.spawn.Main;
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.util.Ticks;
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+public abstract class Bar {
+    private BossBar bossBar;
+    private final BukkitTask updateTask;
+
+    public Bar() {
+        long refreshRateInTicks = this.refresh().get(ChronoUnit.SECONDS) * Ticks.TICKS_PER_SECOND;
+        this.updateTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
+            Main.instance(),
+            this::update,
+            refreshRateInTicks,
+            refreshRateInTicks
+        );
+    }
+
+    public BossBar getBossBar() {
+        if(this.bossBar == null) this.bossBar = this.createBar();
+        return this.bossBar;
+    }
+
+    private BossBar createBar() {
+        return BossBar.bossBar(
+            this.title(),
+            this.correctedProgress(),
+            this.color(),
+            this.overlay()
+        );
+    }
+
+    private void update() {
+        if(this.bossBar == null) return;
+
+        this.beforeRefresh();
+        this.bossBar.name(this.title());
+        this.bossBar.progress(this.correctedProgress());
+        this.bossBar.color(this.color());
+        this.bossBar.overlay(this.overlay());
+    }
+
+    public void stopUpdate() {
+        this.updateTask.cancel();
+    }
+
+    private float correctedProgress() {
+        return Math.clamp(this.progress(), 0, 1);
+    }
+
+    protected void beforeRefresh() {}
+    protected abstract Duration refresh();
+    protected abstract String name();
+
+    protected abstract Component title();
+    protected abstract float progress();
+    protected abstract BossBar.Color color();
+    protected abstract BossBar.Overlay overlay();
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBarCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBarCommand.java
new file mode 100644
index 0000000..d3aa097
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBarCommand.java
@@ -0,0 +1,32 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class InfoBarCommand extends ApplianceCommand.PlayerChecked<InfoBars> {
+    public InfoBarCommand() {
+        super("infobar");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        if(args.length == 0) throw new Error("<show|hide|hideall> [bar name]");
+        switch(args[0]) {
+            case "hideAll" -> getAppliance().hideAll(getPlayer());
+            case "show" -> getAppliance().show(getPlayer(), args[1]);
+            case "hide" -> getAppliance().hide(getPlayer(), args[1]);
+            default -> throw new Error("Erlaubte Optionen sind 'show', 'hide', 'hideAll'!");
+        }
+    }
+
+    @Override
+    public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
+        if(args.length == 1) return List.of("show", "hide", "hideAll");
+        return getAppliance().getInfoBars().stream().map(Bar::name).toList();
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBars.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBars.java
new file mode 100644
index 0000000..5e08c60
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/InfoBars.java
@@ -0,0 +1,91 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars;
+
+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.infoBars.bars.MsptBar;
+import eu.mhsl.craftattack.spawn.appliances.infoBars.bars.PlayerCounterBar;
+import eu.mhsl.craftattack.spawn.appliances.infoBars.bars.TpsBar;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Listener;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InfoBars extends Appliance {
+    private final NamespacedKey infoBarKey = new NamespacedKey(Main.instance(), "infobars");
+    private final List<Bar> infoBars = List.of(
+        new TpsBar(),
+        new MsptBar(),
+        new PlayerCounterBar()
+    );
+
+    public void showAll(Player player) {
+        this.getStoredBars(player).forEach(bar -> this.show(player, bar));
+    }
+
+    public void hideAll(Player player) {
+        this.getStoredBars(player).forEach(bar -> this.hide(player, bar));
+        this.setStoredBars(player, List.of());
+    }
+
+    public void show(Player player, String bar) {
+        this.validateBarName(bar);
+        List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
+        existingBars.add(bar);
+        player.showBossBar(this.getBarByName(bar).getBossBar());
+        this.setStoredBars(player, existingBars);
+    }
+
+    public void hide(Player player, String bar) {
+        this.validateBarName(bar);
+        List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
+        existingBars.remove(bar);
+        player.hideBossBar(this.getBarByName(bar).getBossBar());
+        this.setStoredBars(player, existingBars);
+    }
+
+    private List<String> getStoredBars(Player player) {
+        PersistentDataContainer container = player.getPersistentDataContainer();
+        if(!container.has(infoBarKey)) return List.of();
+        return container.get(infoBarKey, PersistentDataType.LIST.strings());
+    }
+
+    private void setStoredBars(Player player, List<String> bars) {
+        player.getPersistentDataContainer().set(infoBarKey, PersistentDataType.LIST.strings(), bars);
+    }
+
+    private Bar getBarByName(String name) {
+        return infoBars.stream()
+            .filter(bar -> bar.name().equalsIgnoreCase(name))
+            .findFirst()
+            .orElse(null);
+    }
+
+    private void validateBarName(String name) {
+        if(getBarByName(name) == null) throw new ApplianceCommand.Error(String.format("Ungültiger infobar name '%s'", name));
+    }
+
+    public List<Bar> getInfoBars() {
+        return infoBars;
+    }
+
+    @Override
+    public void onDisable() {
+        infoBars.forEach(Bar::stopUpdate);
+    }
+
+    @Override
+    protected @NotNull List<Listener> listeners() {
+        return List.of(new ShowPreviousBarsListener());
+    }
+
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(new InfoBarCommand());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/ShowPreviousBarsListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/ShowPreviousBarsListener.java
new file mode 100644
index 0000000..fa5006a
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/ShowPreviousBarsListener.java
@@ -0,0 +1,12 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+public class ShowPreviousBarsListener extends ApplianceListener<InfoBars> {
+    @EventHandler
+    public void onJoin(PlayerJoinEvent event) {
+        getAppliance().showAll(event.getPlayer());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java
new file mode 100644
index 0000000..abdc4e0
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java
@@ -0,0 +1,50 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars.bars;
+
+import eu.mhsl.craftattack.spawn.appliances.infoBars.Bar;
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
+
+import java.time.Duration;
+
+public class MsptBar extends Bar {
+    @Override
+    protected Duration refresh() {
+        return Duration.ofSeconds(1);
+    }
+
+    @Override
+    protected String name() {
+        return "mspt";
+    }
+
+    @Override
+    protected Component title() {
+        return Component.text()
+            .append(Component.text("M"))
+            .append(Component.text("illi", NamedTextColor.GRAY))
+            .append(Component.text("S"))
+            .append(Component.text("econds ", NamedTextColor.GRAY))
+            .append(Component.text("P"))
+            .append(Component.text("er ", NamedTextColor.GRAY))
+            .append(Component.text("T"))
+            .append(Component.text("ick", NamedTextColor.GRAY))
+            .build();
+    }
+
+    @Override
+    protected float progress() {
+        return Bukkit.getServer().getTickTimes()[0] / 50f;
+    }
+
+    @Override
+    protected BossBar.Color color() {
+        return BossBar.Color.YELLOW;
+    }
+
+    @Override
+    protected BossBar.Overlay overlay() {
+        return BossBar.Overlay.NOTCHED_10;
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java
new file mode 100644
index 0000000..eabc4bb
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java
@@ -0,0 +1,44 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars.bars;
+
+import eu.mhsl.craftattack.spawn.Main;
+import eu.mhsl.craftattack.spawn.appliances.infoBars.Bar;
+import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+
+import java.time.Duration;
+
+public class PlayerCounterBar extends Bar {
+    @Override
+    protected Duration refresh() {
+        return Duration.ofSeconds(3);
+    }
+
+    @Override
+    protected String name() {
+        return "playerCounter";
+    }
+
+    @Override
+    protected Component title() {
+        return Component.text("Spieler online");
+    }
+
+    @Override
+    protected float progress() {
+        int maxPlayers = Main.instance().getAppliance(PlayerLimit.class).getLimit();
+        int currentPlayers = Bukkit.getOnlinePlayers().size();
+        return (float) currentPlayers / maxPlayers;
+    }
+
+    @Override
+    protected BossBar.Color color() {
+        return BossBar.Color.BLUE;
+    }
+
+    @Override
+    protected BossBar.Overlay overlay() {
+        return BossBar.Overlay.NOTCHED_10;
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java
new file mode 100644
index 0000000..0e48657
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java
@@ -0,0 +1,48 @@
+package eu.mhsl.craftattack.spawn.appliances.infoBars.bars;
+
+import eu.mhsl.craftattack.spawn.appliances.infoBars.Bar;
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
+
+import java.time.Duration;
+
+public class TpsBar extends Bar {
+    @Override
+    protected Duration refresh() {
+        return Duration.ofSeconds(3);
+    }
+
+    @Override
+    protected String name() {
+        return "tps";
+    }
+
+    @Override
+    protected Component title() {
+        return Component.text()
+            .append(Component.text("T"))
+            .append(Component.text("icks ", NamedTextColor.GRAY))
+            .append(Component.text("P"))
+            .append(Component.text("er ", NamedTextColor.GRAY))
+            .append(Component.text("S"))
+            .append(Component.text("econds", NamedTextColor.GRAY))
+            .build();
+    }
+
+    @Override
+    protected float progress() {
+        return (float) (Bukkit.getTPS()[0] / 20);
+    }
+
+    @Override
+    protected BossBar.Color color() {
+        return BossBar.Color.YELLOW;
+    }
+
+    @Override
+    protected BossBar.Overlay overlay() {
+        return BossBar.Overlay.NOTCHED_20;
+    }
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index f7108d5..dd31f27 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -46,3 +46,4 @@ commands:
   adminchat:
     aliases: [ "sc" ]
   acInform:
+  infobar:

From 8470115a74f0fb57bcdec824b19910f7f6811e8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 24 Nov 2024 02:33:36 +0100
Subject: [PATCH 12/18] added endPrevent

---
 .../appliances/endPrevent/EndPrevent.java     | 39 +++++++++++++++++++
 .../endPrevent/EndPreventCommand.java         | 37 ++++++++++++++++++
 .../PreventEnderEyeUseListener.java           | 23 +++++++++++
 .../maintenance/MaintenanceCommand.java       |  2 +-
 src/main/resources/config.yml                 |  5 ++-
 src/main/resources/plugin.yml                 |  1 +
 6 files changed, 105 insertions(+), 2 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPrevent.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPreventCommand.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/PreventEnderEyeUseListener.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPrevent.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPrevent.java
new file mode 100644
index 0000000..2013ad9
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPrevent.java
@@ -0,0 +1,39 @@
+package eu.mhsl.craftattack.spawn.appliances.endPrevent;
+
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.config.Configuration;
+import org.bukkit.event.Listener;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class EndPrevent extends Appliance {
+    private final String endDisabledKey = "endDisabled";
+    private boolean endDisabled;
+
+    public EndPrevent() {
+        super("endPrevent");
+        this.endDisabled = localConfig().getBoolean(endDisabledKey);
+    }
+
+    public void setEndDisabled(boolean disabled) {
+        localConfig().set(endDisabledKey, disabled);
+        Configuration.saveChanges();
+        this.endDisabled = disabled;
+    }
+
+    public boolean isEndDisabled() {
+        return endDisabled;
+    }
+
+    @Override
+    protected @NotNull List<Listener> listeners() {
+        return List.of(new PreventEnderEyeUseListener());
+    }
+
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(new EndPreventCommand());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPreventCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPreventCommand.java
new file mode 100644
index 0000000..863b703
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/EndPreventCommand.java
@@ -0,0 +1,37 @@
+package eu.mhsl.craftattack.spawn.appliances.endPrevent;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+public class EndPreventCommand extends ApplianceCommand<EndPrevent> {
+    private final Map<String, Boolean> arguments = Map.of("preventEnd", true, "allowEnd", false);
+
+    public EndPreventCommand() {
+        super("endPrevent");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        if(args.length == 1 && arguments.containsKey(args[0])) {
+            getAppliance().setEndDisabled(arguments.get(args[0]));
+            sender.sendMessage(Component.text("Setting updated!", NamedTextColor.GREEN));
+        }
+        sender.sendMessage(Component.text(
+            String.format("The End is %s!", getAppliance().isEndDisabled() ? "open" : "closed"),
+            NamedTextColor.GOLD
+        ));
+    }
+
+    @Override
+    public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
+        return arguments.keySet().stream().toList();
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/PreventEnderEyeUseListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/PreventEnderEyeUseListener.java
new file mode 100644
index 0000000..078c848
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/endPrevent/PreventEnderEyeUseListener.java
@@ -0,0 +1,23 @@
+package eu.mhsl.craftattack.spawn.appliances.endPrevent;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Material;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerInteractEvent;
+
+public class PreventEnderEyeUseListener extends ApplianceListener<EndPrevent> {
+    @EventHandler
+    public void onEnderEyeInteraction(PlayerInteractEvent event) {
+        if(event.getClickedBlock() == null) return;
+        if(!event.getClickedBlock().getType().equals(Material.END_PORTAL_FRAME)) return;
+        if(event.getItem() == null) return;
+        if(!event.getItem().getType().equals(Material.ENDER_EYE)) return;
+
+        if(!getAppliance().isEndDisabled()) return;
+
+        event.setCancelled(true);
+        event.getPlayer().sendActionBar(Component.text("Das End ist noch nicht freigeschaltet!", NamedTextColor.RED));
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java
index d23e3e2..38ad1d9 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java
@@ -12,7 +12,7 @@ import java.util.List;
 import java.util.Map;
 
 public class MaintenanceCommand extends ApplianceCommand<Maintenance> {
-    Map<String, Boolean> arguments = Map.of("enable", true, "disable", false);
+    private final Map<String, Boolean> arguments = Map.of("enable", true, "disable", false);
 
     public MaintenanceCommand() {
         super("maintanance");
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index a6240d1..476f32b 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -66,4 +66,7 @@ packselect:
         author: "Pack Author(s)"
         url: "https://example.com/download/pack.zip"
         hash: "" # SHA1 hash of ZIP file (will be auto determined by the server on startup when not set)
-        icon: "" # base64 player-head texture, can be obtained from sites like https://minecraft-heads.com/ under developers > Value
\ No newline at end of file
+        icon: "" # base64 player-head texture, can be obtained from sites like https://minecraft-heads.com/ under developers > Value
+
+endPrevent:
+  endDisabled: true
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index dd31f27..b4a0607 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -47,3 +47,4 @@ commands:
     aliases: [ "sc" ]
   acInform:
   infobar:
+  endPrevent:

From b9f88bc4b2002083d1a2ab2f211d32af40e599a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 24 Nov 2024 20:06:18 +0100
Subject: [PATCH 13/18] extended infobars, added coloring

---
 .../appliances/infoBars/bars/MsptBar.java     | 17 +++++++++-----
 .../infoBars/bars/PlayerCounterBar.java       | 22 ++++++++++++++-----
 .../appliances/infoBars/bars/TpsBar.java      | 11 ++++++++--
 .../spawn/util/statistics/ServerMonitor.java  | 12 ++++++++++
 .../spawn/util/text/ColorUtil.java            |  9 ++++++++
 .../spawn/util/text/ComponentUtil.java        | 11 ++++------
 6 files changed, 63 insertions(+), 19 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/util/statistics/ServerMonitor.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java
index abdc4e0..60046a5 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java
@@ -1,17 +1,18 @@
 package eu.mhsl.craftattack.spawn.appliances.infoBars.bars;
 
 import eu.mhsl.craftattack.spawn.appliances.infoBars.Bar;
+import eu.mhsl.craftattack.spawn.util.statistics.ServerMonitor;
+import eu.mhsl.craftattack.spawn.util.text.ColorUtil;
 import net.kyori.adventure.bossbar.BossBar;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.Bukkit;
 
 import java.time.Duration;
 
 public class MsptBar extends Bar {
     @Override
     protected Duration refresh() {
-        return Duration.ofSeconds(1);
+        return Duration.ofSeconds(3);
     }
 
     @Override
@@ -30,21 +31,27 @@ public class MsptBar extends Bar {
             .append(Component.text("er ", NamedTextColor.GRAY))
             .append(Component.text("T"))
             .append(Component.text("ick", NamedTextColor.GRAY))
+            .append(Component.text(": "))
+            .append(Component.text(String.format("%.2f", this.currentMSPT()), ColorUtil.msptColor(this.currentMSPT())))
             .build();
     }
 
     @Override
     protected float progress() {
-        return Bukkit.getServer().getTickTimes()[0] / 50f;
+        return this.currentMSPT() / 50f;
     }
 
     @Override
     protected BossBar.Color color() {
-        return BossBar.Color.YELLOW;
+        return BossBar.Color.BLUE;
     }
 
     @Override
     protected BossBar.Overlay overlay() {
-        return BossBar.Overlay.NOTCHED_10;
+        return BossBar.Overlay.PROGRESS;
+    }
+
+    private float currentMSPT() {
+        return ServerMonitor.getServerMSPT();
     }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java
index eabc4bb..32c6966 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java
@@ -3,8 +3,10 @@ package eu.mhsl.craftattack.spawn.appliances.infoBars.bars;
 import eu.mhsl.craftattack.spawn.Main;
 import eu.mhsl.craftattack.spawn.appliances.infoBars.Bar;
 import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
+import eu.mhsl.craftattack.spawn.util.text.ColorUtil;
 import net.kyori.adventure.bossbar.BossBar;
 import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
 import org.bukkit.Bukkit;
 
 import java.time.Duration;
@@ -22,14 +24,16 @@ public class PlayerCounterBar extends Bar {
 
     @Override
     protected Component title() {
-        return Component.text("Spieler online");
+        TextColor color = ColorUtil.mapGreenToRed(this.getCurrentPlayerCount(), 0, this.getMaxPlayerCount(), true);
+        return Component.text()
+            .append(Component.text("Spieler online: "))
+            .append(Component.text(this.getCurrentPlayerCount(), color))
+            .build();
     }
 
     @Override
     protected float progress() {
-        int maxPlayers = Main.instance().getAppliance(PlayerLimit.class).getLimit();
-        int currentPlayers = Bukkit.getOnlinePlayers().size();
-        return (float) currentPlayers / maxPlayers;
+        return (float) this.getCurrentPlayerCount() / this.getMaxPlayerCount();
     }
 
     @Override
@@ -39,6 +43,14 @@ public class PlayerCounterBar extends Bar {
 
     @Override
     protected BossBar.Overlay overlay() {
-        return BossBar.Overlay.NOTCHED_10;
+        return BossBar.Overlay.PROGRESS;
+    }
+
+    private int getCurrentPlayerCount() {
+        return Bukkit.getOnlinePlayers().size();
+    }
+
+    private int getMaxPlayerCount() {
+        return Main.instance().getAppliance(PlayerLimit.class).getLimit();
     }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java
index 0e48657..0d82b40 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java
@@ -1,6 +1,7 @@
 package eu.mhsl.craftattack.spawn.appliances.infoBars.bars;
 
 import eu.mhsl.craftattack.spawn.appliances.infoBars.Bar;
+import eu.mhsl.craftattack.spawn.util.text.ColorUtil;
 import net.kyori.adventure.bossbar.BossBar;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
@@ -28,21 +29,27 @@ public class TpsBar extends Bar {
             .append(Component.text("er ", NamedTextColor.GRAY))
             .append(Component.text("S"))
             .append(Component.text("econds", NamedTextColor.GRAY))
+            .append(Component.text(": "))
+            .append(Component.text(String.format("%.2f", this.currentTps()), ColorUtil.tpsColor(this.currentTps())))
             .build();
     }
 
     @Override
     protected float progress() {
-        return (float) (Bukkit.getTPS()[0] / 20);
+        return this.currentTps() / 20;
     }
 
     @Override
     protected BossBar.Color color() {
-        return BossBar.Color.YELLOW;
+        return BossBar.Color.BLUE;
     }
 
     @Override
     protected BossBar.Overlay overlay() {
         return BossBar.Overlay.NOTCHED_20;
     }
+
+    private float currentTps() {
+        return (float) Bukkit.getTPS()[0];
+    }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/ServerMonitor.java b/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/ServerMonitor.java
new file mode 100644
index 0000000..6c3fc89
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/ServerMonitor.java
@@ -0,0 +1,12 @@
+package eu.mhsl.craftattack.spawn.util.statistics;
+
+import org.bukkit.Bukkit;
+
+import java.util.Arrays;
+
+public class ServerMonitor {
+    public static float getServerMSPT() {
+        long[] times = Bukkit.getServer().getTickTimes();
+        return ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/text/ColorUtil.java b/src/main/java/eu/mhsl/craftattack/spawn/util/text/ColorUtil.java
index e64aa99..2fd9f47 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/util/text/ColorUtil.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/text/ColorUtil.java
@@ -15,4 +15,13 @@ public class ColorUtil {
 
         return TextColor.color(Color.getHSBColor(hue / 360, 1f, 1f).getRGB());
     }
+
+    public static TextColor msptColor(float mspt) {
+        if(mspt > 50) return TextColor.color(255, 0, 0);
+        return ColorUtil.mapGreenToRed(mspt, 25, 60, true);
+    }
+
+    public static TextColor tpsColor(float tps) {
+        return ColorUtil.mapGreenToRed(tps, 15, 20, false);
+    }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java b/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java
index 978ddd5..2364b27 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java
@@ -1,6 +1,7 @@
 package eu.mhsl.craftattack.spawn.util.text;
 
 import eu.mhsl.craftattack.spawn.util.statistics.NetworkMonitor;
+import eu.mhsl.craftattack.spawn.util.statistics.ServerMonitor;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.ComponentBuilder;
 import net.kyori.adventure.text.TextComponent;
@@ -12,7 +13,6 @@ import org.bukkit.entity.Player;
 import java.awt.*;
 import java.lang.management.OperatingSystemMXBean;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -55,15 +55,12 @@ public class ComponentUtil {
     }
 
     public static Component getFormattedTickTimes(boolean detailed) {
-        long[] times = Bukkit.getServer().getTickTimes();
-        float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
+        float mspt = ServerMonitor.getServerMSPT();
         float roundedMspt = Math.round(mspt * 100f) / 100f;
         int loadPercentage = (int) (Math.min(100, (mspt / 50.0) * 100));
         float roundedTPS = Math.round(Bukkit.getTPS()[0] * 100f) / 100f;
 
-        TextColor msptColor = ColorUtil.mapGreenToRed(roundedMspt, 0, 50, true);
         TextColor percentageColor = ColorUtil.mapGreenToRed(loadPercentage, 80, 100, true);
-        TextColor tpsColor = ColorUtil.mapGreenToRed(roundedTPS, 15, 20, false);
 
         ComponentBuilder<TextComponent, TextComponent.Builder> tickTimes = Component.text()
             .append(Component.text("Serverlast: ", NamedTextColor.GRAY))
@@ -72,12 +69,12 @@ public class ComponentUtil {
 
         if(detailed) {
             tickTimes
-                .append(Component.text(roundedMspt + "mspt", msptColor))
+                .append(Component.text(roundedMspt + "mspt", ColorUtil.msptColor(mspt)))
                 .append(Component.text(" | ", NamedTextColor.GRAY));
         }
 
         return tickTimes
-            .append(Component.text(roundedTPS + "tps", tpsColor))
+            .append(Component.text(roundedTPS + "tps", ColorUtil.tpsColor(roundedTPS)))
             .build();
     }
 

From 7e3b043c98e5c7933c149424f28709ea14e469dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 24 Nov 2024 21:13:25 +0100
Subject: [PATCH 14/18] added afkTag

---
 .../appliances/afkTag/AfkResetListener.java   | 30 ++++++++
 .../spawn/appliances/afkTag/AfkTag.java       | 71 +++++++++++++++++++
 .../appliances/displayName/DisplayName.java   |  9 ++-
 .../spawn/appliances/outlawed/Outlawed.java   |  3 +-
 .../spawn/appliances/yearRank/YearRank.java   |  4 +-
 5 files changed, 114 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkResetListener.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkResetListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkResetListener.java
new file mode 100644
index 0000000..4c758f9
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkResetListener.java
@@ -0,0 +1,30 @@
+package eu.mhsl.craftattack.spawn.appliances.afkTag;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
+import io.papermc.paper.event.player.AsyncChatEvent;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+public class AfkResetListener extends ApplianceListener<AfkTag> {
+    @EventHandler
+    public void onMove(PlayerMoveEvent event) {
+        getAppliance().resetTiming(event.getPlayer());
+    }
+
+    @EventHandler
+    public void onInteract(PlayerInteractEvent event) {
+        getAppliance().resetTiming(event.getPlayer());
+    }
+
+    @EventHandler
+    public void onChat(AsyncChatEvent event) {
+        getAppliance().resetTiming(event.getPlayer());
+    }
+
+    @EventHandler
+    public void onJoin(PlayerJoinEvent event) {
+        getAppliance().resetTiming(event.getPlayer());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
new file mode 100644
index 0000000..055aa4e
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
@@ -0,0 +1,71 @@
+package eu.mhsl.craftattack.spawn.appliances.afkTag;
+
+import eu.mhsl.craftattack.spawn.Main;
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliances.displayName.DisplayName;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.util.Ticks;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Listener;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.WeakHashMap;
+
+public class AfkTag extends Appliance implements DisplayName.DisplayNamed {
+    private final WeakHashMap<Player, Long> afkTimings = new WeakHashMap<>();
+    private static final int updateIntervalSeconds = 30;
+    private static final int afkWhenMillis = 5 * 60 * 1000;
+
+    @Override
+    public void onEnable() {
+        Bukkit.getScheduler().runTaskTimerAsynchronously(
+            Main.instance(),
+            this::checkAfkPlayers,
+            Ticks.TICKS_PER_SECOND,
+            Ticks.TICKS_PER_SECOND * updateIntervalSeconds
+        );
+    }
+
+    public void resetTiming(Player player) {
+        boolean wasAfk = isAfk(player);
+        this.afkTimings.put(player, System.currentTimeMillis());
+        if (wasAfk) updateAfkPrefix(player);
+    }
+
+    private void checkAfkPlayers() {
+        this.afkTimings.keySet().forEach((player) -> {
+            if(!isAfk(player)) return;
+            updateAfkPrefix(player);
+        });
+    }
+
+    private boolean isAfk(Player player) {
+        if(player.isSleeping()) return false;
+
+        long lastTimeActive = this.afkTimings.getOrDefault(player, 0L);
+        long timeSinceLastActive = System.currentTimeMillis() - lastTimeActive;
+        return timeSinceLastActive >= afkWhenMillis;
+    }
+
+    private void updateAfkPrefix(Player player) {
+        Main.instance().getAppliance(DisplayName.class).update(player);
+    }
+
+    @Override
+    public @Nullable Component getNamePrefix(Player p) {
+        if(isAfk(p)) return Component.text("[\uD83D\uDECC]", NamedTextColor.GRAY)
+            .hoverEvent(HoverEvent.showText(Component.text("Der Spieler ist AFK")));
+
+        return null;
+    }
+
+    @Override
+    protected @NotNull List<Listener> listeners() {
+        return List.of(new AfkResetListener());
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java
index 429cfdd..8271157 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java
@@ -4,6 +4,7 @@ import eu.mhsl.craftattack.spawn.Main;
 import eu.mhsl.craftattack.spawn.appliance.Appliance;
 import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
 import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener;
+import eu.mhsl.craftattack.spawn.appliances.afkTag.AfkTag;
 import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
 import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank;
 import net.kyori.adventure.text.Component;
@@ -13,17 +14,23 @@ import net.kyori.adventure.text.format.TextColor;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Listener;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
 import java.util.function.Supplier;
 import java.util.logging.Level;
 
 public class DisplayName extends Appliance {
+    public interface DisplayNamed {
+        @Nullable Component getNamePrefix(Player p);
+    }
+
     public void update(Player player) {
         TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player);
         List<Supplier<Component>> prefixes = List.of(
             () -> queryAppliance(Outlawed.class).getNamePrefix(player),
-            () -> queryAppliance(YearRank.class).getNamePrefix(player)
+            () -> queryAppliance(YearRank.class).getNamePrefix(player),
+            () -> queryAppliance(AfkTag.class).getNamePrefix(player)
         );
 
         ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java
index e71ffc0..3fb23fb 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java
@@ -17,7 +17,7 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.*;
 
-public class Outlawed extends Appliance {
+public class Outlawed extends Appliance implements DisplayName.DisplayNamed {
     public final int timeoutInMs = 1000 * 60 * 60 * 6;
     private final Map<UUID, Long> timeouts = new HashMap<>();
 
@@ -110,6 +110,7 @@ public class Outlawed extends Appliance {
         };
     }
 
+    @Override
     public Component getNamePrefix(Player player) {
         if(isOutlawed(player)) {
             return Component.text("[☠]", NamedTextColor.RED)
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java
index 75e3acb..0b97f9b 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java
@@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.appliances.yearRank;
 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.displayName.DisplayName;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.TextComponent;
 import net.kyori.adventure.text.event.ClickEvent;
@@ -15,7 +16,7 @@ import org.jetbrains.annotations.Nullable;
 import java.io.File;
 import java.util.*;
 
-public class YearRank extends Appliance {
+public class YearRank extends Appliance implements DisplayName.DisplayNamed {
     record CraftAttackYear(String name) {}
     private final Map<UUID, List<CraftAttackYear>> rankMap = new HashMap<>();
 
@@ -44,6 +45,7 @@ public class YearRank extends Appliance {
         });
     }
 
+    @Override
     public @Nullable Component getNamePrefix(Player player) {
         if(!rankMap.containsKey(player.getUniqueId())) return null;
         int yearCount = rankMap.get(player.getUniqueId()).size();

From 39814dae05167ad700c0024af8931ffbe46d13eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 24 Nov 2024 22:44:48 +0100
Subject: [PATCH 15/18] added sleepTag

---
 .../spawn/appliances/afkTag/AfkTag.java       |  8 +-
 .../appliances/displayName/DisplayName.java   |  6 +-
 .../sleepTag/SleepStateChangeListener.java    | 19 +++++
 .../spawn/appliances/sleepTag/SleepTag.java   | 74 +++++++++++++++++++
 4 files changed, 101 insertions(+), 6 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepStateChangeListener.java
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
index 055aa4e..0ea049e 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
@@ -34,13 +34,13 @@ public class AfkTag extends Appliance implements DisplayName.DisplayNamed {
     public void resetTiming(Player player) {
         boolean wasAfk = isAfk(player);
         this.afkTimings.put(player, System.currentTimeMillis());
-        if (wasAfk) updateAfkPrefix(player);
+        if (wasAfk) this.updateAfkPrefix(player);
     }
 
     private void checkAfkPlayers() {
         this.afkTimings.keySet().forEach((player) -> {
             if(!isAfk(player)) return;
-            updateAfkPrefix(player);
+            this.updateAfkPrefix(player);
         });
     }
 
@@ -57,8 +57,8 @@ public class AfkTag extends Appliance implements DisplayName.DisplayNamed {
     }
 
     @Override
-    public @Nullable Component getNamePrefix(Player p) {
-        if(isAfk(p)) return Component.text("[\uD83D\uDECC]", NamedTextColor.GRAY)
+    public @Nullable Component getNamePrefix(Player player) {
+        if(isAfk(player)) return Component.text("[\uD83D\uDEAB]", NamedTextColor.GRAY)
             .hoverEvent(HoverEvent.showText(Component.text("Der Spieler ist AFK")));
 
         return null;
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java
index 8271157..8d19a54 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java
@@ -6,6 +6,7 @@ import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
 import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener;
 import eu.mhsl.craftattack.spawn.appliances.afkTag.AfkTag;
 import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
+import eu.mhsl.craftattack.spawn.appliances.sleepTag.SleepTag;
 import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.ComponentBuilder;
@@ -22,7 +23,7 @@ import java.util.logging.Level;
 
 public class DisplayName extends Appliance {
     public interface DisplayNamed {
-        @Nullable Component getNamePrefix(Player p);
+        @Nullable Component getNamePrefix(Player player);
     }
 
     public void update(Player player) {
@@ -30,7 +31,8 @@ public class DisplayName extends Appliance {
         List<Supplier<Component>> prefixes = List.of(
             () -> queryAppliance(Outlawed.class).getNamePrefix(player),
             () -> queryAppliance(YearRank.class).getNamePrefix(player),
-            () -> queryAppliance(AfkTag.class).getNamePrefix(player)
+            () -> queryAppliance(AfkTag.class).getNamePrefix(player),
+            () -> queryAppliance(SleepTag.class).getNamePrefix(player)
         );
 
         ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepStateChangeListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepStateChangeListener.java
new file mode 100644
index 0000000..4179307
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepStateChangeListener.java
@@ -0,0 +1,19 @@
+package eu.mhsl.craftattack.spawn.appliances.sleepTag;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerBedEnterEvent;
+import org.bukkit.event.player.PlayerBedLeaveEvent;
+
+public class SleepStateChangeListener extends ApplianceListener<SleepTag> {
+    @EventHandler
+    public void onBedEnter(PlayerBedEnterEvent event) {
+        if(!event.getBedEnterResult().equals(PlayerBedEnterEvent.BedEnterResult.OK)) return;
+        getAppliance().updateSleeping(event.getPlayer(), true);
+    }
+
+    @EventHandler
+    public void onBedLeave(PlayerBedLeaveEvent event) {
+        getAppliance().updateSleeping(event.getPlayer(), false);
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
new file mode 100644
index 0000000..b021cf6
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
@@ -0,0 +1,74 @@
+package eu.mhsl.craftattack.spawn.appliances.sleepTag;
+
+import eu.mhsl.craftattack.spawn.Main;
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliances.displayName.DisplayName;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.util.Ticks;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Listener;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class SleepTag extends Appliance implements DisplayName.DisplayNamed {
+    private final Set<Player> sleepingPlayers = new HashSet<>();
+
+    @Override
+    public void onEnable() {
+        Bukkit.getScheduler().runTaskTimerAsynchronously(
+            Main.instance(),
+            this::cleanup,
+            Ticks.TICKS_PER_SECOND * 60,
+            Ticks.TICKS_PER_SECOND * 60
+        );
+    }
+
+    public void updateSleeping(Player player, boolean isSleeping) {
+        if(isSleeping) {
+            this.sleepingPlayers.add(player);
+        } else {
+            this.sleepingPlayers.remove(player);
+        }
+        this.updateDisplayName(player);
+    }
+
+    private void updateDisplayName(Player player) {
+        Main.instance().getAppliance(DisplayName.class).update(player);
+    }
+
+    private void cleanup() {
+        System.out.println("before: " + this.sleepingPlayers);
+        List<Player> invalidEntries = this.sleepingPlayers.stream()
+            .filter(player -> !player.isConnected())
+            .filter(player -> !player.isSleeping())
+            .toList();
+
+        System.out.println("Found invalid: " + invalidEntries);
+
+        invalidEntries.forEach(this.sleepingPlayers::remove);
+        invalidEntries.forEach(this::updateDisplayName);
+        System.out.println("after: " + this.sleepingPlayers);
+    }
+
+    @Override
+    public @Nullable Component getNamePrefix(Player player) {
+        System.out.println("Testfor sleeping");
+        if(this.sleepingPlayers.contains(player))
+            return Component.text("[\uD83D\uDECC]", NamedTextColor.GRAY)
+                .hoverEvent(HoverEvent.showText(Component.text("Der Spieler liegt in einem Bett")));
+
+        return null;
+    }
+
+    @Override
+    protected @NotNull List<Listener> listeners() {
+        return List.of(new SleepStateChangeListener());
+    }
+}

From b35c5b9240181d77e0f5899181b7bbf6bdb87f24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Mon, 25 Nov 2024 20:40:43 +0100
Subject: [PATCH 16/18] updated afk and sleep tag icons

---
 .../eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java     | 2 +-
 .../eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
index 0ea049e..538efcd 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/afkTag/AfkTag.java
@@ -58,7 +58,7 @@ public class AfkTag extends Appliance implements DisplayName.DisplayNamed {
 
     @Override
     public @Nullable Component getNamePrefix(Player player) {
-        if(isAfk(player)) return Component.text("[\uD83D\uDEAB]", NamedTextColor.GRAY)
+        if(isAfk(player)) return Component.text("[ᵃᶠᵏ]", NamedTextColor.GRAY)
             .hoverEvent(HoverEvent.showText(Component.text("Der Spieler ist AFK")));
 
         return null;
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
index b021cf6..cefde9b 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
@@ -61,7 +61,7 @@ public class SleepTag extends Appliance implements DisplayName.DisplayNamed {
     public @Nullable Component getNamePrefix(Player player) {
         System.out.println("Testfor sleeping");
         if(this.sleepingPlayers.contains(player))
-            return Component.text("[\uD83D\uDECC]", NamedTextColor.GRAY)
+            return Component.text("[\uD83D\uDCA4]", NamedTextColor.GRAY)
                 .hoverEvent(HoverEvent.showText(Component.text("Der Spieler liegt in einem Bett")));
 
         return null;

From c9935637b9ac5c9b8b0b82ac7c050becf9978766 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Thu, 28 Nov 2024 21:22:28 +0100
Subject: [PATCH 17/18] removed inconsistent spacing in acinform

---
 .../mhsl/craftattack/spawn/appliances/acInform/AcInform.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
index 336ad42..b7db6f2 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInform.java
@@ -89,14 +89,14 @@ public class AcInform extends Appliance {
 
                 .append(Component.text("[", NamedTextColor.GRAY))
                 .append(Component.text("Report", NamedTextColor.GOLD))
-                .append(Component.text("] ", NamedTextColor.GRAY))
+                .append(Component.text("]", NamedTextColor.GRAY))
                 .clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName)))
         );
 
         component.append(
             Component.text(" [", NamedTextColor.GRAY)
                 .append(Component.text("Kick", NamedTextColor.GOLD))
-                .append(Component.text("] ", NamedTextColor.GRAY))
+                .append(Component.text("]", NamedTextColor.GRAY))
                 .clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName)))
         );
 

From 3c829391208ee1bff39e1cca140629408330969d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Thu, 28 Nov 2024 21:24:10 +0100
Subject: [PATCH 18/18] removed unwanted logging

---
 .../mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
index cefde9b..1d92aff 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java
@@ -44,22 +44,17 @@ public class SleepTag extends Appliance implements DisplayName.DisplayNamed {
     }
 
     private void cleanup() {
-        System.out.println("before: " + this.sleepingPlayers);
         List<Player> invalidEntries = this.sleepingPlayers.stream()
             .filter(player -> !player.isConnected())
             .filter(player -> !player.isSleeping())
             .toList();
 
-        System.out.println("Found invalid: " + invalidEntries);
-
         invalidEntries.forEach(this.sleepingPlayers::remove);
         invalidEntries.forEach(this::updateDisplayName);
-        System.out.println("after: " + this.sleepingPlayers);
     }
 
     @Override
     public @Nullable Component getNamePrefix(Player player) {
-        System.out.println("Testfor sleeping");
         if(this.sleepingPlayers.contains(player))
             return Component.text("[\uD83D\uDCA4]", NamedTextColor.GRAY)
                 .hoverEvent(HoverEvent.showText(Component.text("Der Spieler liegt in einem Bett")));