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
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

19
gradlew vendored
View File

@ -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" \

20
gradlew.bat vendored
View File

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

View File

@ -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!");

View File

@ -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,

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.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();

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

View File

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

View File

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

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

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.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();

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

View File

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

View File

@ -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
icon: "" # base64 player-head texture, can be obtained from sites like https://minecraft-heads.com/ under developers > Value
endPrevent:
endDisabled: true

View File

@ -41,4 +41,10 @@ commands:
maintanance:
yearRank:
msg:
r:
r:
playtime:
adminchat:
aliases: [ "sc" ]
acInform:
infobar:
endPrevent: