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/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, 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..b7db6f2 --- /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()) throw new ApplianceCommand.Error("acinform command needs a player (--playerName)"); + + 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..149cd46 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/acInform/AcInformCommand.java @@ -0,0 +1,19 @@ +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> { + 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/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..71b2522 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminChat/AdminChatCommand.java @@ -0,0 +1,22 @@ +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/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..538efcd --- /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) this.updateAfkPrefix(player); + } + + private void checkAfkPlayers() { + this.afkTimings.keySet().forEach((player) -> { + if(!isAfk(player)) return; + this.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 player) { + if(isAfk(player)) return Component.text("[ᵃᶠᵏ]", 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..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 @@ -4,7 +4,9 @@ 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.sleepTag.SleepTag; import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentBuilder; @@ -13,17 +15,24 @@ 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 player); + } + 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), + () -> queryAppliance(SleepTag.class).getNamePrefix(player) ); ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text(); 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/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..60046a5 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/MsptBar.java @@ -0,0 +1,57 @@ +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 java.time.Duration; + +public class MsptBar extends Bar { + @Override + protected Duration refresh() { + return Duration.ofSeconds(3); + } + + @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)) + .append(Component.text(": ")) + .append(Component.text(String.format("%.2f", this.currentMSPT()), ColorUtil.msptColor(this.currentMSPT()))) + .build(); + } + + @Override + protected float progress() { + return this.currentMSPT() / 50f; + } + + @Override + protected BossBar.Color color() { + return BossBar.Color.BLUE; + } + + @Override + protected BossBar.Overlay overlay() { + 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 new file mode 100644 index 0000000..32c6966 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/PlayerCounterBar.java @@ -0,0 +1,56 @@ +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; + +public class PlayerCounterBar extends Bar { + @Override + protected Duration refresh() { + return Duration.ofSeconds(3); + } + + @Override + protected String name() { + return "playerCounter"; + } + + @Override + protected Component title() { + 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() { + return (float) this.getCurrentPlayerCount() / this.getMaxPlayerCount(); + } + + @Override + protected BossBar.Color color() { + return BossBar.Color.BLUE; + } + + @Override + protected BossBar.Overlay overlay() { + 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 new file mode 100644 index 0000000..0d82b40 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/infoBars/bars/TpsBar.java @@ -0,0 +1,55 @@ +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; +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)) + .append(Component.text(": ")) + .append(Component.text(String.format("%.2f", this.currentTps()), ColorUtil.tpsColor(this.currentTps()))) + .build(); + } + + @Override + protected float progress() { + return this.currentTps() / 20; + } + + @Override + protected BossBar.Color color() { + 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/appliances/maintenance/MaintenanceCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java index 9372b17..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 @@ -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; @@ -10,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"); @@ -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 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() ); 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/playtime/Playtime.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java new file mode 100644 index 0000000..6a59694 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playtime/Playtime.java @@ -0,0 +1,32 @@ +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 getFormattedPlaytime(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..03cffc7 --- /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.Main; +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 = args.length == 1 ? args[0] : getPlayer().getName(); + + 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 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); } } 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..1d92aff --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/sleepTag/SleepTag.java @@ -0,0 +1,69 @@ +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() { + List<Player> invalidEntries = this.sleepingPlayers.stream() + .filter(player -> !player.isConnected()) + .filter(player -> !player.isSleeping()) + .toList(); + + invalidEntries.forEach(this.sleepingPlayers::remove); + invalidEntries.forEach(this::updateDisplayName); + } + + @Override + public @Nullable Component getNamePrefix(Player player) { + if(this.sleepingPlayers.contains(player)) + return Component.text("[\uD83D\uDCA4]", 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()); + } +} 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(); 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(); } 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/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 0a3437b..b4a0607 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -41,4 +41,10 @@ commands: maintanance: yearRank: msg: - r: \ No newline at end of file + r: + playtime: + adminchat: + aliases: [ "sc" ] + acInform: + infobar: + endPrevent: