From c7034040003eb12ed9531f4f2638b12a032dace1 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Sun, 14 Sep 2025 16:08:44 +0300 Subject: [PATCH 1/3] added PlaceholderAPI optional dependency --- .github/workflows/release.yml | 5 +++++ pom.xml | 10 ++++++++++ src/main/resources/plugin.yml | 2 ++ 3 files changed, 17 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f429ca..f9408f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,6 +58,11 @@ jobs: version: ${{ github.event.release.tag_name }} changelog: ${{ github.event.release.body }} loaders: paper + dependencies: |- + [{ + "project_id": "lKEzGugV", + "dependency_type": "optional" + }] game-versions: |- 1.20.x 1.21.x diff --git a/pom.xml b/pom.xml index 6a163a2..5b4ba4a 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,10 @@ sonatype https://oss.sonatype.org/content/groups/public/ + + placeholderapi + https://repo.extendedclip.com/releases/ + @@ -81,5 +85,11 @@ 1.20.2-R0.1-SNAPSHOT provided + + me.clip + placeholderapi + 2.11.6 + provided + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2f88c00..a3f2259 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,6 +3,8 @@ version: '${project.version}' main: pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG api-version: '1.20' author: Cloudnode +softdepend: + - PlaceholderAPI commands: message: description: Send a private message From 4d47a75ca702b0131463489b1ecf9ab7568508c1 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 15 Sep 2025 16:16:36 +0300 Subject: [PATCH 2/3] added config option to enable chat format --- .run/Package.run.xml | 32 ++++++++++++ .../smp/cloudnodemsg/PluginConfig.java | 51 +++++++++++++++++++ .../listener/AsyncChatListener.java | 8 +++ src/main/resources/config.yml | 14 +++++ 4 files changed, 105 insertions(+) create mode 100644 .run/Package.run.xml diff --git a/.run/Package.run.xml b/.run/Package.run.xml new file mode 100644 index 0000000..598b4f7 --- /dev/null +++ b/.run/Package.run.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java index 1341017..2aa305e 100644 --- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java +++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java @@ -1,13 +1,26 @@ package pro.cloudnode.smp.cloudnodemsg; +import io.papermc.paper.chat.ChatRenderer; +import me.clip.placeholderapi.PlaceholderAPI; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.OfflinePlayer; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import org.bukkit.scoreboard.Team; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Objects; +import java.util.Optional; public final class PluginConfig { public @NotNull FileConfiguration config; @@ -305,6 +318,44 @@ public PluginConfig(final @NotNull FileConfiguration config) { ); } + /** + * Chat format + */ + public @NotNull Optional<@NotNull ChatRenderer> chatFormat() { + final @Nullable String str = config.getString("chat-format"); + if (str == null || str.equals("null") || str.isBlank()) + return Optional.empty(); + return Optional.of((source, sourceDisplayName, message, viewer) -> MiniMessage.miniMessage().deserialize( + str, + Placeholder.component("message", message), + TagResolver.resolver("papi", (args, ctx) -> { + String placeholder = args.popOr("placeholder expected").value().trim(); + if (!placeholder.startsWith("%") && !placeholder.endsWith("%")) + placeholder = "%" + placeholder + "%"; + if (!CloudnodeMSG.getInstance().getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) + CloudnodeMSG.getInstance().getLogger().severe("Attempted to use PlaceholderAPI placeholder `" + placeholder + + "` in chat format, but PlaceholderAPI is not present!"); + return Tag.inserting(Component.text(PlaceholderAPI.setPlaceholders(source, placeholder))); + }), + TagResolver.resolver("has-team", (args, ctx) -> { + final String text = args.popOr("text expected").value(); + final Team team = source.getScoreboard().getPlayerTeam(source); + if (team == null) { + if (args.hasNext()) + return Tag.inserting(MiniMessage.miniMessage().deserialize(args.popOr("expected fallback value").value())); + else return Tag.inserting(Component.empty()); + } + return Tag.inserting(ctx.deserialize(text, + Placeholder.component("team", team.displayName()) + )); + }), + TagResolver.resolver("player", (args, ctx) -> Tag.inserting( + Component.text(source.getName()) + .clickEvent(ClickEvent.suggestCommand("/tell " + source.getName() + " ")) + )) + )); + } + /** * No permission */ diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java index af4bfe6..5ea5c03 100644 --- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java +++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java @@ -1,7 +1,9 @@ package pro.cloudnode.smp.cloudnodemsg.listener; +import io.papermc.paper.chat.ChatRenderer; import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; import org.bukkit.OfflinePlayer; import org.bukkit.Server; import org.bukkit.entity.Player; @@ -68,4 +70,10 @@ public void teamChannel(final @NotNull AsyncChatEvent event) { } TeamMessageCommand.sendTeamMessage(sender, team.get(), event.message()); } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void chatFormat(final @NotNull AsyncChatEvent event) { + final @NotNull Optional<@NotNull ChatRenderer> format = CloudnodeMSG.getInstance().config().chatFormat(); + format.ifPresent(event::renderer); + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ade838b..6012eb5 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -88,6 +88,20 @@ toggle: # - the player's username other: "(!) Re-enabled receiving of private messages for ." +# Set custom global/public chat format. Disabled by default. +# +# Placeholders: +# - show a different message based on whether the player is in a team +# - the player's team display name (only works in ) +# - the name of the player, when clicked suggests command `/tell` followed by the player's name +# - the message being sent in chat +# - use a PlaceholderAPI placeholder, example `` or ``. +# Requires PlaceholderAPI to be installed. +# +# Example: +#chat-format: [] '>: +chat-format: null + # Error messages errors: # No permission From 07f06db2849ca39e52e7adf093026688ad3f94d2 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 15 Sep 2025 17:06:50 +0300 Subject: [PATCH 3/3] Revert "Merge branch 'ignore-cancelled-events' into chat-format" This reverts commit f737b0e27dafdf5eefd723d8316b209b1fe99a1a, reversing changes made to c7034040003eb12ed9531f4f2638b12a032dace1. --- .../smp/cloudnodemsg/listener/AsyncChatListener.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java index 5ea5c03..7fedef3 100644 --- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java +++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/AsyncChatListener.java @@ -25,7 +25,7 @@ import java.util.Set; public final class AsyncChatListener implements Listener { - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + @EventHandler(priority = EventPriority.LOWEST) public void ignore(final @NotNull AsyncChatEvent event) { final @NotNull Set<@NotNull Audience> audience = event.viewers(); final @NotNull Iterator<@NotNull Audience> iterator = audience.iterator(); @@ -43,7 +43,7 @@ public void ignore(final @NotNull AsyncChatEvent event) { } } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGHEST) public void channels(final @NotNull AsyncChatEvent event) { final @NotNull Player sender = event.getPlayer(); final @NotNull Optional<@NotNull OfflinePlayer> channelRecipient = Message.getChannel(sender); @@ -57,7 +57,7 @@ public void channels(final @NotNull AsyncChatEvent event) { } } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGHEST) public void teamChannel(final @NotNull AsyncChatEvent event) { final @NotNull Player sender = event.getPlayer(); if (!Message.hasTeamChannel(sender)) return;