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/.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/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/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..7fedef3 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;
@@ -23,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();
@@ -41,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);
@@ -55,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;
@@ -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
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