diff --git a/.gitignore b/.gitignore index c2a09ec..5ed1226 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ +# Compiled class files +*.class + ### IntelliJ IDEA ### .idea/ .idea/modules.xml diff --git a/build.gradle.kts b/build.gradle.kts index 12b6f1d..6b0ad4f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,8 +39,8 @@ allprojects { dependencies { implementation("de.exlll:configlib-yaml:4.6.3") - compileOnly("net.kyori:adventure-api:4.25.0") - compileOnly("net.kyori:adventure-text-minimessage:4.25.0") + compileOnly("net.kyori:adventure-api:5.2.0") + compileOnly("net.kyori:adventure-text-minimessage:5.2.0") compileOnly("me.clip:placeholderapi:2.11.6") compileOnly("io.github.miniplaceholders:miniplaceholders-api:3.0.1") diff --git a/gradle.properties b/gradle.properties index 1d8f96f..4d46092 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ pluginName=FancyTags -version=unspecified \ No newline at end of file +version=0.1.0-SNAPSHOT +org.gradle.java.home=C:/Program Files/Eclipse Adoptium/jdk-21.0.11.10-hotspot diff --git a/paper/build.gradle b/paper/build.gradle index cc03bd3..3623085 100644 --- a/paper/build.gradle +++ b/paper/build.gradle @@ -9,10 +9,15 @@ repositories { name = "papermc-repo" url = "https://repo.papermc.io/repository/maven-public/" } + maven { + name = "arim-mvn-lgpl3" + url = "https://mvn-repo.arim.space/lesser-gpl3/" + } } dependencies { compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") + implementation("space.arim.morepaperlib:morepaperlib:0.4.3") implementation("org.bstats:bstats-bukkit:3.0.2") implementation(project(":common")) implementation(project(":plugin")) diff --git a/paper/src/main/java/dev/vatten/baserad/BukkitScheduledTask.java b/paper/src/main/java/dev/vatten/baserad/BukkitScheduledTask.java index 0f3f7a7..677e713 100644 --- a/paper/src/main/java/dev/vatten/baserad/BukkitScheduledTask.java +++ b/paper/src/main/java/dev/vatten/baserad/BukkitScheduledTask.java @@ -16,10 +16,8 @@ package dev.vatten.baserad; -import org.bukkit.scheduler.BukkitTask; - -public class BukkitScheduledTask extends ScheduledTask { - public BukkitScheduledTask(BukkitTask task) { +public class BukkitScheduledTask extends ScheduledTask { + public BukkitScheduledTask(space.arim.morepaperlib.scheduling.ScheduledTask task) { super(task); } diff --git a/paper/src/main/java/dev/vatten/baserad/PaperFancyTagsExpansion.java b/paper/src/main/java/dev/vatten/baserad/PaperFancyTagsExpansion.java index 61af074..9fdab60 100644 --- a/paper/src/main/java/dev/vatten/baserad/PaperFancyTagsExpansion.java +++ b/paper/src/main/java/dev/vatten/baserad/PaperFancyTagsExpansion.java @@ -58,7 +58,7 @@ public boolean persist() { @Override public @Nullable String onPlaceholderRequest(Player player, @NotNull String params) { if(plugin.getTagStore().getTagNames().contains(params)) { - return plugin.getMiniMessage().serialize(plugin.getTagStore().getTag(params).asComponent()); + return plugin.getTagStore().getTag(params).asMiniMessage(); } return null; } diff --git a/paper/src/main/java/dev/vatten/baserad/PaperVattenPlatform.java b/paper/src/main/java/dev/vatten/baserad/PaperVattenPlatform.java index 3db0669..7bedafa 100644 --- a/paper/src/main/java/dev/vatten/baserad/PaperVattenPlatform.java +++ b/paper/src/main/java/dev/vatten/baserad/PaperVattenPlatform.java @@ -29,45 +29,46 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; import org.jspecify.annotations.Nullable; +import space.arim.morepaperlib.MorePaperLib; import java.util.Collection; -public class PaperVattenPlatform extends JavaPlugin implements VattenPlatform, Listener { +public class PaperVattenPlatform extends JavaPlugin implements VattenPlatform, Listener { private VattenPlugin plugin; + private MorePaperLib morePaperLib; + private boolean papiRegistered; + private boolean miniPlaceholdersRegistered; @Override public void onEnable() { + this.morePaperLib = new MorePaperLib(this); this.plugin = new Plugin( this, VattenPlugin.Type.SERVER, this.getDataFolder().toPath() ); - if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { - new PaperFancyTagsExpansion((Plugin) this.plugin).register(); - } - - if (Bukkit.getPluginManager().isPluginEnabled("MiniPlaceholders")) { - ((Plugin) this.plugin).registerMiniPlaceholders(); - } - getServer().getPluginManager().registerEvents(this, this); + registerExpansions(); + int pluginId = 27900; Metrics metrics = new Metrics(this, pluginId); } @Override public BukkitScheduledTask scheduleTask(Runnable runnable, int delay) { - return new BukkitScheduledTask(getServer().getScheduler().runTaskLater(this, runnable, delay/50)); + long initialDelayTicks = Math.max(1L, delay / 50L); + return new BukkitScheduledTask(morePaperLib.scheduling().globalRegionalScheduler().runDelayed(runnable, initialDelayTicks)); } @Override public BukkitScheduledTask scheduleRepeatingTask(Runnable runnable, int delay, int period) { - return new BukkitScheduledTask(getServer().getScheduler().runTaskTimer(this, runnable, delay/50, period/50)); + long initialDelayTicks = Math.max(1L, delay / 50L); + return new BukkitScheduledTask(morePaperLib.scheduling().globalRegionalScheduler().runAtFixedRate(runnable, initialDelayTicks, period / 50L)); } @Override @@ -97,6 +98,26 @@ public VattenPlayer wrapPlayer(Player player) { return new VattenPlayer(player.getUniqueId(), player.getName(), player); } + private void registerExpansions() { + if (!papiRegistered && Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + new PaperFancyTagsExpansion((Plugin) this.plugin).register(); + papiRegistered = true; + } + + if (!miniPlaceholdersRegistered && Bukkit.getPluginManager().isPluginEnabled("MiniPlaceholders")) { + ((Plugin) this.plugin).registerMiniPlaceholders(); + miniPlaceholdersRegistered = true; + } + } + + @EventHandler + public void onPluginEnable(PluginEnableEvent event) { + String name = event.getPlugin().getName(); + if ("PlaceholderAPI".equals(name) || "MiniPlaceholders".equals(name)) { + registerExpansions(); + } + } + @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { plugin.getEventHandler().dispatchEvent(new dev.vatten.baserad.events.PlayerJoinEvent(wrapPlayer(event.getPlayer()))); @@ -111,4 +132,8 @@ public void onPlayerLeave(PlayerQuitEvent event) { public void onPlayerLoadedIn(PlayerClientLoadedWorldEvent event) { plugin.getEventHandler().dispatchEvent(new PlayerLoadInEvent(wrapPlayer(event.getPlayer()))); } + + public VattenPlugin getPlugin() { + return this.plugin; + } } diff --git a/paper/src/main/resources/paper-plugin.yml b/paper/src/main/resources/paper-plugin.yml index cc4db2a..12f4d2a 100644 --- a/paper/src/main/resources/paper-plugin.yml +++ b/paper/src/main/resources/paper-plugin.yml @@ -18,6 +18,8 @@ name: "${pluginName}" version: "${version}" main: "dev.vatten.baserad.PaperVattenPlatform" api-version: '1.21.11' +folia-supported: true +load: STARTUP dependencies: server: diff --git a/plugin/src/main/java/dev/vatten/baserad/Plugin.java b/plugin/src/main/java/dev/vatten/baserad/Plugin.java index 041e62a..4bfaedb 100644 --- a/plugin/src/main/java/dev/vatten/baserad/Plugin.java +++ b/plugin/src/main/java/dev/vatten/baserad/Plugin.java @@ -37,7 +37,7 @@ import java.util.function.Consumer; public class Plugin extends VattenPlugin { - private MiniMessage MINIMESSAGE = null; + private MiniMessage MINIMESSAGE; private ConfigInstance PLUGIN_CONFIG; private ConfigInstance TAGS_CONFIG; @Getter @@ -75,7 +75,7 @@ protected void onEnable() { getEventHandler().registerEventHandler(PlayerLoadInEvent.class, this::onPlayerLoadIn); - MINIMESSAGE = MiniMessage.builder().tags(TagResolver.builder().resolver(StandardTags.defaults()).resolver(TagResolver.resolver("fancytags", (ArgumentQueue queue, Context ctx) -> Tag.selfClosingInserting(getTagStore().getTag(queue.pop().value()).asComponent()))).resolver(TagResolver.resolver("fancytags_internal", (ArgumentQueue queue, Context ctx) -> Tag.selfClosingInserting(getInternalTagStore().getTag(queue.pop().value()).asComponent()))).build()).build(); + getMiniMessage(); } @Override @@ -128,6 +128,15 @@ TagsConfig getTagsConfig() { } public MiniMessage getMiniMessage() { + if(MINIMESSAGE == null) { + MINIMESSAGE = MiniMessage.builder() + .tags(TagResolver.builder() + .resolver(StandardTags.defaults()) + .resolver(TagResolver.resolver("fancytags", (ArgumentQueue queue, Context ctx) -> Tag.selfClosingInserting(getTagStore().getTag(queue.pop().value()).asComponent()))) + .resolver(TagResolver.resolver("fancytags_internal", (ArgumentQueue queue, Context ctx) -> Tag.selfClosingInserting(getInternalTagStore().getTag(queue.pop().value()).asComponent()))) + .build() + ).build(); + } return MINIMESSAGE; } diff --git a/plugin/src/main/java/dev/vatten/baserad/Tag.java b/plugin/src/main/java/dev/vatten/baserad/Tag.java index 303f347..ecf8e07 100644 --- a/plugin/src/main/java/dev/vatten/baserad/Tag.java +++ b/plugin/src/main/java/dev/vatten/baserad/Tag.java @@ -23,12 +23,15 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.minimessage.MiniMessage; +import dev.vatten.baserad.symbols.PlayerSymbol; +import java.util.ArrayList; import java.util.List; @Configuration @NoArgsConstructor public class Tag implements Renderable { private List symbols; + private String fallback; private transient Component component; private transient String miniMessage; @@ -36,13 +39,54 @@ public Tag(List symbols) { this.symbols = symbols; } + public String getFallback() { + return fallback; + } + + public void setFallback(String fallback) { + this.fallback = fallback; + } + Component serialize(MiniMessage miniMessage) { + return serialize(miniMessage, ""); + } + + Component serialize(MiniMessage miniMessage, String key) { TextComponent.Builder componentBuilder = Component.text(); - for(Symbol symbol : symbols) { - componentBuilder.append(symbol.serialize()); + StringBuilder minimessageBuilder = new StringBuilder(); + + List headSymbols = new ArrayList<>(); + for (Symbol symbol : symbols) { + if (symbol instanceof PlayerSymbol playerSymbol) { + headSymbols.add(playerSymbol); + } + } + + int numHeads = headSymbols.size(); + String fallbackStr = (this.fallback != null && !this.fallback.isEmpty()) ? this.fallback : key; + + int headIndex = 0; + for (Symbol symbol : symbols) { + if (symbol instanceof PlayerSymbol playerSymbol) { + Component fallbackComponent; + if (fallbackStr.length() == numHeads) { + fallbackComponent = Component.text(String.valueOf(fallbackStr.charAt(headIndex))); + } else { + if (headIndex == 0) { + fallbackComponent = Component.text(fallbackStr); + } else { + fallbackComponent = Component.text(""); + } + } + componentBuilder.append(playerSymbol.serialize(fallbackComponent)); + headIndex++; + } else { + componentBuilder.append(symbol.serialize()); + } + minimessageBuilder.append(symbol.serializeMiniMessage()); } this.component = componentBuilder.build(); - this.miniMessage = miniMessage.serialize(this.component); + this.miniMessage = minimessageBuilder.toString(); return this.component; } diff --git a/plugin/src/main/java/dev/vatten/baserad/TagStore.java b/plugin/src/main/java/dev/vatten/baserad/TagStore.java index 4edcae2..b485c7f 100644 --- a/plugin/src/main/java/dev/vatten/baserad/TagStore.java +++ b/plugin/src/main/java/dev/vatten/baserad/TagStore.java @@ -71,11 +71,16 @@ int getTagAtlasLength() { return tagAtlasLength; } + public void saveAndRefresh() { + refresh(); + plugin.saveTags(); + } + private void refresh() { List names = new ArrayList<>(); Component atlas = Component.empty(); for(Map.Entry entry : tags.entrySet()) { - Component serialized = entry.getValue().serialize(plugin.getMiniMessage()); + Component serialized = entry.getValue().serialize(plugin.getMiniMessage(), entry.getKey()); names.add(entry.getKey()); atlas = atlas.append(serialized); tagAtlasLength += entry.getValue().getLength(); diff --git a/plugin/src/main/java/dev/vatten/baserad/commands/TagsCommand.java b/plugin/src/main/java/dev/vatten/baserad/commands/TagsCommand.java index 87c25ca..960ffc8 100644 --- a/plugin/src/main/java/dev/vatten/baserad/commands/TagsCommand.java +++ b/plugin/src/main/java/dev/vatten/baserad/commands/TagsCommand.java @@ -41,6 +41,44 @@ public TagsCommand(Plugin plugin, Path importDirectory) { public void execute(VattenPlayer player, String[] args) { TagStore tagStore = plugin.getTagStore(); if(args.length >= 1) { + if(args[0].equalsIgnoreCase("fallback")) { + if(args.length == 1) { + player.sendMessage(TextFormatter.WARN.format("Usage: /tags fallback [value]")); + return; + } + if(!tagStore.getTagNames().contains(args[1])) { + player.sendMessage(TextFormatter.WARN.format("No tag corresponding to the name '" + args[1] + "' was found.")); + return; + } + Tag tag = tagStore.getTag(args[1]); + if(args.length == 2) { + String currentFallback = tag.getFallback(); + if(currentFallback == null || currentFallback.isEmpty()) { + player.sendMessage(plugin.getPluginTextFormatter().format(TextFormatter.INFO.format("Tag '" + args[1] + "' fallback is not set (defaulting to: '" + args[1] + "')."))); + } else { + player.sendMessage(plugin.getPluginTextFormatter().format(TextFormatter.INFO.format("Tag '" + args[1] + "' fallback is set to: '" + currentFallback + "'."))); + } + return; + } + + StringBuilder builder = new StringBuilder(); + for(int i = 2; i < args.length; i++) { + if(i > 2) builder.append(" "); + builder.append(args[i]); + } + String fallbackValue = builder.toString(); + + if(fallbackValue.equalsIgnoreCase("clear") || fallbackValue.equalsIgnoreCase("reset") || fallbackValue.equalsIgnoreCase("none")) { + tag.setFallback(null); + tagStore.saveAndRefresh(); + player.sendMessage(plugin.getPluginTextFormatter().format(TextFormatter.SUCCESS.format("Tag '" + args[1] + "' fallback has been cleared."))); + } else { + tag.setFallback(fallbackValue); + tagStore.saveAndRefresh(); + player.sendMessage(plugin.getPluginTextFormatter().format(TextFormatter.SUCCESS.format("Tag '" + args[1] + "' fallback has been set to: '" + fallbackValue + "'."))); + } + return; + } if(args[0].equalsIgnoreCase("reload")) { plugin.getApi().reload(); player.sendMessage(plugin.getPluginTextFormatter().format(TextFormatter.SUCCESS.format("Tags plugin has been reloaded!"))); @@ -145,14 +183,19 @@ public void execute(VattenPlayer player, String[] args) { public List onTabComplete(VattenPlayer player, String[] args) { List completions = new ArrayList<>(); if(args.length <= 1) { - completions.addAll(List.of("reload", "list", "view", "rename", "delete")); + completions.addAll(List.of("reload", "list", "view", "rename", "delete", "fallback")); completions.add(generateCommand.getName()); } if(args.length == 2) { - if(List.of("view", "rename", "delete").contains(args[0].toLowerCase())) { + if(List.of("view", "rename", "delete", "fallback").contains(args[0].toLowerCase())) { completions.addAll(plugin.getTagStore().getTagNames()); } } + if(args.length == 3) { + if(args[0].equalsIgnoreCase("fallback")) { + completions.addAll(List.of("clear", "reset", "none")); + } + } if(args.length > 1) { List check = new ArrayList<>(); check.add(generateCommand.getName()); diff --git a/plugin/src/main/java/dev/vatten/baserad/symbols/AtlasSymbol.java b/plugin/src/main/java/dev/vatten/baserad/symbols/AtlasSymbol.java index f9afa8c..ff4822c 100644 --- a/plugin/src/main/java/dev/vatten/baserad/symbols/AtlasSymbol.java +++ b/plugin/src/main/java/dev/vatten/baserad/symbols/AtlasSymbol.java @@ -37,4 +37,9 @@ public AtlasSymbol(String atlas, String sprite) { public Component serialize() { return Component.object(ObjectContents.sprite(Key.key(this.atlas), Key.key(this.sprite))); } + + @Override + public String serializeMiniMessage() { + return ""; + } } diff --git a/plugin/src/main/java/dev/vatten/baserad/symbols/PlayerSymbol.java b/plugin/src/main/java/dev/vatten/baserad/symbols/PlayerSymbol.java index 1431596..e0909f7 100644 --- a/plugin/src/main/java/dev/vatten/baserad/symbols/PlayerSymbol.java +++ b/plugin/src/main/java/dev/vatten/baserad/symbols/PlayerSymbol.java @@ -28,6 +28,19 @@ public class PlayerSymbol extends Symbol { private String value; private String signature; + private static final boolean HAS_FALLBACK; + static { + boolean hasMethod = false; + try { + net.kyori.adventure.text.ObjectComponent.class.getMethod("fallback", net.kyori.adventure.text.ComponentLike.class); + hasMethod = true; + } catch (Throwable ignored) {} + HAS_FALLBACK = hasMethod; + if (!hasMethod) { + System.err.println("[FancyTags] Adventure 5+ is not available on this server version. Fallback tags will not be used."); + } + } + public PlayerSymbol(String value, String signature) { this.value = value; this.signature = signature; @@ -37,4 +50,29 @@ public PlayerSymbol(String value, String signature) { public Component serialize() { return Component.object(ObjectContents.playerHead().profileProperty(PlayerHeadObjectContents.property("textures", this.value, this.signature)).build()); } + + @Override + public Component serialize(Component fallback) { + Component component = Component.object(ObjectContents.playerHead().profileProperty(PlayerHeadObjectContents.property("textures", this.value, this.signature)).build()); + if (HAS_FALLBACK) { + try { + return applyFallback(component, fallback); + } catch (LinkageError e) { + // Return gracefully without fallback if linkage error occurs + } + } + return component; + } + + private Component applyFallback(Component component, Component fallback) { + if (component instanceof net.kyori.adventure.text.ObjectComponent objectComponent) { + return objectComponent.fallback(fallback); + } + return component; + } + + @Override + public String serializeMiniMessage() { + return ""; + } } diff --git a/plugin/src/main/java/dev/vatten/baserad/symbols/Symbol.java b/plugin/src/main/java/dev/vatten/baserad/symbols/Symbol.java index f207cb4..14764b7 100644 --- a/plugin/src/main/java/dev/vatten/baserad/symbols/Symbol.java +++ b/plugin/src/main/java/dev/vatten/baserad/symbols/Symbol.java @@ -24,4 +24,8 @@ @Configuration public abstract class Symbol { public abstract Component serialize(); + public Component serialize(Component fallback) { + return serialize(); + } + public abstract String serializeMiniMessage(); }