Merge remote-tracking branch 'refs/remotes/origin/master' into develop-feedback

This commit is contained in:
Elias Müller 2024-11-28 22:16:13 +01:00
commit f0ffb3ad21
37 changed files with 1036 additions and 48 deletions

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

19
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -83,7 +83,8 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac 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. # 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"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command:
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# shell script including quotes and variable substitutions, so put them in # and any embedded shellness will be escaped.
# double quotes to make sure that they get re-expanded; and # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# * put everything else in single quotes, so that it's not re-expanded. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View File

@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@ -19,7 +19,6 @@ public final class Main extends JavaPlugin {
private static Logger logger; private static Logger logger;
private List<Appliance> appliances; private List<Appliance> appliances;
private HttpServer httpApi;
@Override @Override
public void onEnable() { 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(String.format("Initialized %d appliances!", appliances.size()));
Main.logger().info("Starting HTTP API..."); Main.logger().info("Starting HTTP API...");
this.httpApi = new HttpServer(); new HttpServer();
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
Main.logger().info("Startup complete!"); Main.logger().info("Startup complete!");
@ -76,7 +75,7 @@ public final class Main extends JavaPlugin {
appliance.onDisable(); appliance.onDisable();
appliance.destruct(this); appliance.destruct(this);
}); });
this.httpApi.stop();
HandlerList.unregisterAll(this); HandlerList.unregisterAll(this);
Bukkit.getScheduler().cancelTasks(this); Bukkit.getScheduler().cancelTasks(this);
Main.logger().info("Disabled " + appliances.size() + " appliances!"); Main.logger().info("Disabled " + appliances.size() + " appliances!");

View File

@ -24,10 +24,6 @@ public class HttpServer {
Main.instance().getAppliances().forEach(appliance -> appliance.httpApi(new ApiBuilder(appliance))); 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 record Response(Status status, Object error, Object response) {
public enum Status { public enum Status {
FAILURE, FAILURE,

View File

@ -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()
);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -4,7 +4,9 @@ import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener; 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.outlawed.Outlawed;
import eu.mhsl.craftattack.spawn.appliances.sleepTag.SleepTag;
import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank; import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder; 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.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level; import java.util.logging.Level;
public class DisplayName extends Appliance { public class DisplayName extends Appliance {
public interface DisplayNamed {
@Nullable Component getNamePrefix(Player player);
}
public void update(Player player) { public void update(Player player) {
TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player); TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player);
List<Supplier<Component>> prefixes = List.of( List<Supplier<Component>> prefixes = List.of(
() -> queryAppliance(Outlawed.class).getNamePrefix(player), () -> 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(); ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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];
}
}

View File

@ -1,6 +1,8 @@
package eu.mhsl.craftattack.spawn.appliances.maintenance; package eu.mhsl.craftattack.spawn.appliances.maintenance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; 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.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -10,7 +12,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class MaintenanceCommand extends ApplianceCommand<Maintenance> { 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() { public MaintenanceCommand() {
super("maintanance"); super("maintanance");
@ -18,9 +20,14 @@ public class MaintenanceCommand extends ApplianceCommand<Maintenance> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { 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!"); if(args.length == 1 && arguments.containsKey(args[0])) {
getAppliance().setState(arguments.get(args[0])); getAppliance().setState(arguments.get(args[0]));
sender.sendMessage(String.format("Maintanance: %b", getAppliance().isInMaintenance())); sender.sendMessage(Component.text("Maintanance mode updated!", NamedTextColor.GREEN));
}
sender.sendMessage(Component.text(
String.format("Maintanance mode is %b", getAppliance().isInMaintenance()),
NamedTextColor.GOLD
));
} }
@Override @Override

View File

@ -12,9 +12,9 @@ public class PreventMaintenanceJoinListener extends ApplianceListener<Maintenanc
if(event.getPlayer().hasPermission("bypassMaintainance")) return; if(event.getPlayer().hasPermission("bypassMaintainance")) return;
DisconnectInfo disconnectInfo = new DisconnectInfo( DisconnectInfo disconnectInfo = new DisconnectInfo(
"Wartunsarbeiten", "Server für Spieler nicht verfügbar",
"Zurzeit können nur Admins dem Server beitreten!", "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() event.getPlayer().getUniqueId()
); );

View File

@ -17,7 +17,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
public class Outlawed extends Appliance { public class Outlawed extends Appliance implements DisplayName.DisplayNamed {
public final int timeoutInMs = 1000 * 60 * 60 * 6; public final int timeoutInMs = 1000 * 60 * 60 * 6;
private final Map<UUID, Long> timeouts = new HashMap<>(); private final Map<UUID, Long> timeouts = new HashMap<>();
@ -110,6 +110,7 @@ public class Outlawed extends Appliance {
}; };
} }
@Override
public Component getNamePrefix(Player player) { public Component getNamePrefix(Player player) {
if(isOutlawed(player)) { if(isOutlawed(player)) {
return Component.text("[☠]", NamedTextColor.RED) return Component.text("[☠]", NamedTextColor.RED)

View File

@ -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());
}
}

View File

@ -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));
});
}
}

View File

@ -11,16 +11,17 @@ import org.bukkit.event.entity.FoodLevelChangeEvent;
public class PlayerInvincibleListener extends ApplianceListener<ProjectStart> { public class PlayerInvincibleListener extends ApplianceListener<ProjectStart> {
@EventHandler @EventHandler
public void onDamage(EntityDamageEvent event) { 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 @EventHandler
public void onHunger(FoodLevelChangeEvent event) { public void onHunger(FoodLevelChangeEvent event) {
event.setCancelled(getAppliance().isEnabled()); if(getAppliance().isEnabled()) event.setCancelled(true);
} }
@EventHandler @EventHandler
public void onHit(PrePlayerAttackEntityEvent event) { public void onHit(PrePlayerAttackEntityEvent event) {
event.setCancelled(getAppliance().isEnabled()); if(getAppliance().isEnabled()) event.setCancelled(true);
} }
} }

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.appliances.yearRank;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; 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.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
@ -15,7 +16,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
public class YearRank extends Appliance { public class YearRank extends Appliance implements DisplayName.DisplayNamed {
record CraftAttackYear(String name) {} record CraftAttackYear(String name) {}
private final Map<UUID, List<CraftAttackYear>> rankMap = new HashMap<>(); 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) { public @Nullable Component getNamePrefix(Player player) {
if(!rankMap.containsKey(player.getUniqueId())) return null; if(!rankMap.containsKey(player.getUniqueId())) return null;
int yearCount = rankMap.get(player.getUniqueId()).size(); int yearCount = rankMap.get(player.getUniqueId()).size();

View File

@ -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;
}
}

View File

@ -15,4 +15,13 @@ public class ColorUtil {
return TextColor.color(Color.getHSBColor(hue / 360, 1f, 1f).getRGB()); 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);
}
} }

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.util.text; package eu.mhsl.craftattack.spawn.util.text;
import eu.mhsl.craftattack.spawn.util.statistics.NetworkMonitor; 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.Component;
import net.kyori.adventure.text.ComponentBuilder; import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
@ -12,7 +13,6 @@ import org.bukkit.entity.Player;
import java.awt.*; import java.awt.*;
import java.lang.management.OperatingSystemMXBean; import java.lang.management.OperatingSystemMXBean;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -55,15 +55,12 @@ public class ComponentUtil {
} }
public static Component getFormattedTickTimes(boolean detailed) { public static Component getFormattedTickTimes(boolean detailed) {
long[] times = Bukkit.getServer().getTickTimes(); float mspt = ServerMonitor.getServerMSPT();
float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
float roundedMspt = Math.round(mspt * 100f) / 100f; float roundedMspt = Math.round(mspt * 100f) / 100f;
int loadPercentage = (int) (Math.min(100, (mspt / 50.0) * 100)); int loadPercentage = (int) (Math.min(100, (mspt / 50.0) * 100));
float roundedTPS = Math.round(Bukkit.getTPS()[0] * 100f) / 100f; 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 percentageColor = ColorUtil.mapGreenToRed(loadPercentage, 80, 100, true);
TextColor tpsColor = ColorUtil.mapGreenToRed(roundedTPS, 15, 20, false);
ComponentBuilder<TextComponent, TextComponent.Builder> tickTimes = Component.text() ComponentBuilder<TextComponent, TextComponent.Builder> tickTimes = Component.text()
.append(Component.text("Serverlast: ", NamedTextColor.GRAY)) .append(Component.text("Serverlast: ", NamedTextColor.GRAY))
@ -72,12 +69,12 @@ public class ComponentUtil {
if(detailed) { if(detailed) {
tickTimes tickTimes
.append(Component.text(roundedMspt + "mspt", msptColor)) .append(Component.text(roundedMspt + "mspt", ColorUtil.msptColor(mspt)))
.append(Component.text(" | ", NamedTextColor.GRAY)); .append(Component.text(" | ", NamedTextColor.GRAY));
} }
return tickTimes return tickTimes
.append(Component.text(roundedTPS + "tps", tpsColor)) .append(Component.text(roundedTPS + "tps", ColorUtil.tpsColor(roundedTPS)))
.build(); .build();
} }

View File

@ -38,4 +38,18 @@ public class DataSizeConverter {
hours %= 60; hours %= 60;
return String.format("%dd%dh%dm%ds", days, hours, minutes, seconds); 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);
}
} }

View File

@ -67,3 +67,6 @@ packselect:
url: "https://example.com/download/pack.zip" 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) 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 icon: "" # base64 player-head texture, can be obtained from sites like https://minecraft-heads.com/ under developers > Value
endPrevent:
endDisabled: true

View File

@ -42,3 +42,9 @@ commands:
yearRank: yearRank:
msg: msg:
r: r:
playtime:
adminchat:
aliases: [ "sc" ]
acInform:
infobar:
endPrevent: