From d17b27549e6b755cf800df66ab666afd7334fac5 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sun, 31 Aug 2025 19:53:41 +0200 Subject: [PATCH 01/14] Bump dependencies and Gradle --- build.gradle.kts | 11 +++++------ buildSrc/build.gradle.kts | 6 +++--- buildSrc/src/main/kotlin/eternalcode.java.gradle.kts | 2 +- chatformatter-core/build.gradle.kts | 2 +- chatformatter-paper-plugin/build.gradle.kts | 4 ++-- gradle.properties | 5 +++++ gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 gradle.properties diff --git a/build.gradle.kts b/build.gradle.kts index d6bf1e8d..27572446 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,13 +4,13 @@ import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream -plugins{ +plugins { id("eternalcode.java") - id("com.github.johnrengelman.shadow") - id("xyz.jpenilla.run-paper") version "2.3.1" + id("com.gradleup.shadow") + id("xyz.jpenilla.run-paper") version "3.0.0-beta.1" } -tasks.create("shadowAll") { +tasks.register("shadowAll") { group = "shadow" val projects = listOf( @@ -64,8 +64,7 @@ fun merge(archiveFileName: String, projects: List) { outputJar.putNextEntry(newEntry) outputJar.write(bytes) outputJar.closeEntry() - } - catch (exception: IOException) { + } catch (exception: IOException) { if (exception.message?.contains("duplicate entry: ") == true) { continue } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index bcf6e1b8..2b3faf75 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21") - implementation("com.github.johnrengelman:shadow:8.1.1") - implementation("net.minecrell:plugin-yml:0.6.0") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.10") + implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.1.0") + implementation("de.eldoria.plugin-yml.bukkit:de.eldoria.plugin-yml.bukkit.gradle.plugin:0.8.0") } diff --git a/buildSrc/src/main/kotlin/eternalcode.java.gradle.kts b/buildSrc/src/main/kotlin/eternalcode.java.gradle.kts index 2d48576b..bb34f2e2 100644 --- a/buildSrc/src/main/kotlin/eternalcode.java.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcode.java.gradle.kts @@ -13,7 +13,7 @@ repositories { maven { url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") } maven { url = uri("https://jitpack.io") } maven { url = uri("https://repo.eternalcode.pl/releases") } - maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") } } java { diff --git a/chatformatter-core/build.gradle.kts b/chatformatter-core/build.gradle.kts index f991312e..fa5ca0f7 100644 --- a/chatformatter-core/build.gradle.kts +++ b/chatformatter-core/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("eternalcode.java") - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") } dependencies { diff --git a/chatformatter-paper-plugin/build.gradle.kts b/chatformatter-paper-plugin/build.gradle.kts index bec22153..bbb91d9a 100644 --- a/chatformatter-paper-plugin/build.gradle.kts +++ b/chatformatter-paper-plugin/build.gradle.kts @@ -2,8 +2,8 @@ import net.minecrell.pluginyml.bukkit.BukkitPluginDescription.Permission.Default plugins { id("eternalcode.java") - id("net.minecrell.plugin-yml.bukkit") - id("com.github.johnrengelman.shadow") + id("de.eldoria.plugin-yml.bukkit") + id("com.gradleup.shadow") } bukkit { diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..1c037243 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da4..2a84e188 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 2dbe929dcd57f6c9dc5d34534f5ffbd80dde91d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:16:11 +0000 Subject: [PATCH 02/14] Initial plan From a5108532f2da095ea3b9b29af70009b56da82656 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:19:29 +0000 Subject: [PATCH 03/14] Initial commit - planning mentions system implementation Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 839a39f0d3191204ebb2d3f013ac97cc03dacaee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:23:30 +0000 Subject: [PATCH 04/14] Implement mentions system with @player detection and sound notifications Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> --- .../formatter/ChatFormatterApi.java | 3 + .../formatter/ChatFormatterCommand.java | 87 ++++++++++++++++--- .../formatter/ChatFormatterPlugin.java | 17 +++- .../formatter/ChatHandlerImpl.java | 14 ++- .../formatter/ChatRenderedMessage.java | 8 +- .../formatter/config/PluginConfig.java | 8 ++ .../formatter/mention/MentionConfig.java | 23 +++++ .../formatter/mention/MentionDetector.java | 36 ++++++++ .../formatter/mention/MentionService.java | 48 ++++++++++ .../formatter/mention/MentionSettings.java | 80 +++++++++++++++++ .../paper/ChatFormatterPaperPlugin.java | 5 +- .../paper/PaperChatEventExecutor.java | 12 +++ .../paper/mention/MentionSoundHandler.java | 42 +++++++++ 13 files changed, 362 insertions(+), 21 deletions(-) create mode 100644 chatformatter-core/src/main/java/com/eternalcode/formatter/mention/MentionConfig.java create mode 100644 chatformatter-core/src/main/java/com/eternalcode/formatter/mention/MentionDetector.java create mode 100644 chatformatter-core/src/main/java/com/eternalcode/formatter/mention/MentionService.java create mode 100644 chatformatter-core/src/main/java/com/eternalcode/formatter/mention/MentionSettings.java create mode 100644 chatformatter-paper-plugin/src/main/java/com/eternalcode/formatter/paper/mention/MentionSoundHandler.java diff --git a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterApi.java b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterApi.java index 806ff2ae..d160de99 100644 --- a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterApi.java +++ b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterApi.java @@ -1,5 +1,6 @@ package com.eternalcode.formatter; +import com.eternalcode.formatter.mention.MentionService; import com.eternalcode.formatter.rank.ChatRankProvider; import com.eternalcode.formatter.template.TemplateService; import com.eternalcode.formatter.placeholder.PlaceholderRegistry; @@ -14,4 +15,6 @@ public interface ChatFormatterApi { ChatHandler getChatHandler(); + MentionService getMentionService(); + } diff --git a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterCommand.java b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterCommand.java index 7e224c38..95f40636 100644 --- a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterCommand.java +++ b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterCommand.java @@ -1,6 +1,7 @@ package com.eternalcode.formatter; import com.eternalcode.formatter.config.ConfigManager; +import com.eternalcode.formatter.mention.MentionService; import com.google.common.base.Stopwatch; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.platform.AudienceProvider; @@ -14,6 +15,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -21,50 +23,107 @@ class ChatFormatterCommand implements CommandExecutor, TabCompleter { private static final String RELOAD_MESSAGE = "ChatFormatter: Successfully reloaded configs in %sms!"; + private static final String MENTION_TOGGLE_ENABLED = "ChatFormatter: Mention sounds enabled!"; + private static final String MENTION_TOGGLE_DISABLED = "ChatFormatter: Mention sounds disabled!"; + private static final String PLAYER_ONLY_MESSAGE = "ChatFormatter: This command can only be used by players!"; + public static final String RELOAD_PERMISSION = "chatformatter.reload"; + public static final String MENTION_TOGGLE_PERMISSION = "chatformatter.mentiontoggle"; private final ConfigManager configManager; private final AudienceProvider provider; private final MiniMessage miniMessage; + private final MentionService mentionService; - ChatFormatterCommand(ConfigManager configManager, AudienceProvider provider, MiniMessage miniMessage) { + ChatFormatterCommand(ConfigManager configManager, AudienceProvider provider, MiniMessage miniMessage, MentionService mentionService) { this.configManager = configManager; this.provider = provider; this.miniMessage = miniMessage; + this.mentionService = mentionService; } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (args.length == 0 || !args[0].equalsIgnoreCase("reload")) { + if (args.length == 0) { sender.sendMessage(command.getUsage()); return true; } - if (sender.hasPermission(RELOAD_PERMISSION)) { - Stopwatch stopwatch = Stopwatch.createStarted(); + String subCommand = args[0].toLowerCase(); + + switch (subCommand) { + case "reload" -> { + return this.handleReload(sender); + } + case "mentiontoggle" -> { + return this.handleMentionToggle(sender); + } + default -> { + sender.sendMessage(command.getUsage()); + return true; + } + } + } + + private boolean handleReload(CommandSender sender) { + if (!sender.hasPermission(RELOAD_PERMISSION)) { + return false; + } + + Stopwatch stopwatch = Stopwatch.createStarted(); - this.configManager.loadAndRenderConfigs(); - long millis = stopwatch.elapsed(TimeUnit.MILLISECONDS); + this.configManager.loadAndRenderConfigs(); + long millis = stopwatch.elapsed(TimeUnit.MILLISECONDS); - Component deserialized = this.miniMessage.deserialize(String.format(RELOAD_MESSAGE, millis)); - Audience audience = sender instanceof Player player - ? this.provider.player(player.getUniqueId()) - : this.provider.console(); + Component deserialized = this.miniMessage.deserialize(String.format(RELOAD_MESSAGE, millis)); + Audience audience = sender instanceof Player player + ? this.provider.player(player.getUniqueId()) + : this.provider.console(); - audience.sendMessage(deserialized); + audience.sendMessage(deserialized); + return true; + } + + private boolean handleMentionToggle(CommandSender sender) { + if (!(sender instanceof Player player)) { + Component message = this.miniMessage.deserialize(PLAYER_ONLY_MESSAGE); + Audience audience = this.provider.console(); + audience.sendMessage(message); return true; } - return false; + if (!player.hasPermission(MENTION_TOGGLE_PERMISSION)) { + return false; + } + + this.mentionService.getSettings().toggleMentionSound(player.getUniqueId()); + boolean enabled = this.mentionService.getSettings().isMentionSoundEnabled(player.getUniqueId()); + + String messageText = enabled ? MENTION_TOGGLE_ENABLED : MENTION_TOGGLE_DISABLED; + Component message = this.miniMessage.deserialize(messageText); + Audience audience = this.provider.player(player.getUniqueId()); + audience.sendMessage(message); + + return true; } @Nullable @Override public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length == 1 && sender.hasPermission(RELOAD_PERMISSION)) { - return List.of("reload"); + if (args.length == 1) { + List completions = new ArrayList<>(); + + if (sender.hasPermission(RELOAD_PERMISSION) && "reload".startsWith(args[0].toLowerCase())) { + completions.add("reload"); + } + + if (sender.hasPermission(MENTION_TOGGLE_PERMISSION) && "mentiontoggle".startsWith(args[0].toLowerCase())) { + completions.add("mentiontoggle"); + } + + return completions; } return Collections.emptyList(); diff --git a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterPlugin.java b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterPlugin.java index 3d4ff69a..cd53c58f 100644 --- a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterPlugin.java +++ b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatFormatterPlugin.java @@ -2,6 +2,8 @@ import com.eternalcode.formatter.config.ConfigManager; import com.eternalcode.formatter.config.PluginConfig; +import com.eternalcode.formatter.mention.MentionService; +import com.eternalcode.formatter.mention.MentionSettings; import com.eternalcode.formatter.placeholder.ConfiguredReplacer; import com.eternalcode.formatter.placeholderapi.PlaceholderAPIInitializer; import com.eternalcode.formatter.placeholder.PlaceholderRegistry; @@ -26,6 +28,7 @@ public class ChatFormatterPlugin implements ChatFormatterApi { private final TemplateService templateService; private final ChatRankProvider rankProvider; private final ChatHandler chatHandler; + private final MentionService mentionService; public ChatFormatterPlugin(Plugin plugin) { Server server = plugin.getServer(); @@ -44,14 +47,19 @@ public ChatFormatterPlugin(Plugin plugin) { this.rankProvider = VaultInitializer.initialize(server); UpdaterService updaterService = new UpdaterService(plugin.getDescription()); + // Initialize mention service + MentionConfig mentionConfig = pluginConfig.getMentionConfig(); + MentionSettings mentionSettings = new MentionSettings(plugin.getDataFolder(), plugin.getLogger(), mentionConfig); + this.mentionService = new MentionService(server, mentionConfig, mentionSettings); + AudienceProvider audienceProvider = BukkitAudiences.create(plugin); MiniMessage miniMessage = MiniMessage.miniMessage(); // bStats metrics new Metrics(plugin, 15199); - this.chatHandler = new ChatHandlerImpl(miniMessage, pluginConfig, this.rankProvider, this.placeholderRegistry, this.templateService); + this.chatHandler = new ChatHandlerImpl(miniMessage, pluginConfig, this.rankProvider, this.placeholderRegistry, this.templateService, this.mentionService); - server.getPluginCommand("chatformatter").setExecutor(new ChatFormatterCommand(configManager, audienceProvider, miniMessage)); + server.getPluginCommand("chatformatter").setExecutor(new ChatFormatterCommand(configManager, audienceProvider, miniMessage, this.mentionService)); server.getPluginManager().registerEvents(new UpdaterController(updaterService, pluginConfig, audienceProvider, miniMessage), plugin); ChatFormatterApiProvider.enable(this); @@ -83,4 +91,9 @@ public ChatHandler getChatHandler() { return this.chatHandler; } + @Override + public MentionService getMentionService() { + return this.mentionService; + } + } diff --git a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatHandlerImpl.java b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatHandlerImpl.java index 0b82fb04..e9f81026 100644 --- a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatHandlerImpl.java +++ b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatHandlerImpl.java @@ -1,6 +1,9 @@ package com.eternalcode.formatter; import com.eternalcode.formatter.adventure.AdventureUrlPostProcessor; +import com.eternalcode.formatter.mention.MentionService; + +import java.util.List; import java.util.Optional; import net.kyori.adventure.text.serializer.json.JSONOptions; import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection; @@ -22,7 +25,6 @@ import org.bukkit.entity.Player; import java.util.ArrayList; -import java.util.List; import java.util.Map; class ChatHandlerImpl implements ChatHandler { @@ -85,13 +87,15 @@ class ChatHandlerImpl implements ChatHandler { private final ChatRankProvider rankProvider; private final PlaceholderRegistry placeholderRegistry; private final TemplateService templateService; + private final MentionService mentionService; - ChatHandlerImpl(MiniMessage miniMessage, ChatSettings settings, ChatRankProvider rankProvider, PlaceholderRegistry placeholderRegistry, TemplateService templateService) { + ChatHandlerImpl(MiniMessage miniMessage, ChatSettings settings, ChatRankProvider rankProvider, PlaceholderRegistry placeholderRegistry, TemplateService templateService, MentionService mentionService) { this.miniMessage = miniMessage; this.settings = settings; this.rankProvider = rankProvider; this.placeholderRegistry = placeholderRegistry; this.templateService = templateService; + this.mentionService = mentionService; } @Override @@ -110,7 +114,11 @@ public ChatRenderedMessage process(ChatMessage chatMessage) { Component renderedMessage = this.miniMessage.deserialize(format, this.createTags(chatMessage)); - return new ChatRenderedMessage(sender, GSON.serialize(renderedMessage)); + // Detect mentions in the original message + String rawMessage = legacySection().serialize(GSON.deserialize(chatMessage.jsonMessage())); + List mentionedPlayers = this.mentionService.detectMentions(rawMessage, sender); + + return new ChatRenderedMessage(sender, GSON.serialize(renderedMessage), mentionedPlayers); } private TagResolver createTags(ChatMessage chatMessage) { diff --git a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatRenderedMessage.java b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatRenderedMessage.java index 4e042c7b..7dae5737 100644 --- a/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatRenderedMessage.java +++ b/chatformatter-core/src/main/java/com/eternalcode/formatter/ChatRenderedMessage.java @@ -2,5 +2,11 @@ import org.bukkit.entity.Player; -public record ChatRenderedMessage(Player sender, String jsonMessage) { +import java.util.List; + +public record ChatRenderedMessage(Player sender, String jsonMessage, List mentionedPlayers) { + + public ChatRenderedMessage(Player sender, String jsonMessage) { + this(sender, jsonMessage, List.of()); + } } diff --git a/chatformatter-core/src/main/java/com/eternalcode/formatter/config/PluginConfig.java b/chatformatter-core/src/main/java/com/eternalcode/formatter/config/PluginConfig.java index 90ca6989..4981fb1f 100644 --- a/chatformatter-core/src/main/java/com/eternalcode/formatter/config/PluginConfig.java +++ b/chatformatter-core/src/main/java/com/eternalcode/formatter/config/PluginConfig.java @@ -1,5 +1,6 @@ package com.eternalcode.formatter.config; +import com.eternalcode.formatter.mention.MentionConfig; import com.eternalcode.formatter.template.Template; import com.eternalcode.formatter.template.TemplateRepository; import com.google.common.collect.ImmutableList; @@ -83,6 +84,9 @@ public class PluginConfig implements ChatSettings, TemplateRepository { .add(Template.of("hoverName", List.of("name"), "Name: $name

{rankDescription}
{joinDate}
{health}
{lvl}

{privateMessage}'>{displayname}")) .build(); + @Description({ " ", "# Mention system settings" }) + public MentionConfig mentions = new MentionConfig(); + @Override public boolean isReceiveUpdates() { @@ -98,4 +102,8 @@ public String getRawFormat(String rank) { public List