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