From fbb937e438b245d60af5950036075984920c4437 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:03 -0600 Subject: [PATCH 01/13] Add terminal-only formatting boundary cleanup for player text --- .../api/messages/DisplayTextBoundaries.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java new file mode 100644 index 0000000000..aaadacf2ba --- /dev/null +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java @@ -0,0 +1,33 @@ +package com.bencodez.advancedcore.api.messages; + +import java.util.List; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +/** + * Drops temporary WORD JOINER formatting guards only at a final player-facing + * sink. The guards must survive placeholder, JavaScript, color, and interactive + * message parsing; removing them earlier would make external identifiers active + * formatting syntax again. + */ +public final class DisplayTextBoundaries { + + private DisplayTextBoundaries() { + } + + public static String afterFormatting(String prepared) { + return prepared == null ? null : prepared.replace("\u2060", ""); + } + + public static void afterFormatting(BaseComponent component) { + if (component == null) return; + if (component instanceof TextComponent text) { + text.setText(afterFormatting(text.getText())); + } + List children = component.getExtra(); + if (children != null) { + for (BaseComponent child : children) afterFormatting(child); + } + } +} From 587118c0864f6cee8af7e55c0cee51e80ebaf5b0 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:19 -0600 Subject: [PATCH 02/13] Keep guards through JSON parsing, remove from rendered chat components --- .../bencodez/advancedcore/api/messages/PlaceholderUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/PlaceholderUtils.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/PlaceholderUtils.java index 8e6b270001..60b06e6656 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/PlaceholderUtils.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/PlaceholderUtils.java @@ -62,6 +62,7 @@ public static TextComponent parseJson(String msg) { if (type.equalsIgnoreCase("hover")) { BaseComponent[] hoverContent = TextComponent.fromLegacyText(typeData); + for (BaseComponent hover : hoverContent) DisplayTextBoundaries.afterFormatting(hover); t.setHoverEvent(MessageAPI.getHoverEventSupport().createHoverEvent(hoverContent)); } else if (type.equalsIgnoreCase("command")) { t.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, typeData)); @@ -87,6 +88,9 @@ public static TextComponent parseJson(String msg) { comp.addExtra(new TextComponent(TextComponent.fromLegacyText(msg))); } + // Syntax and legacy-color parsing are finished; restore the literal site + // name for the client without allowing another formatting pass. + DisplayTextBoundaries.afterFormatting(comp); return comp; } From 0e82884665f46416a74a064982c6d4ad6ecc46f4 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:48 -0600 Subject: [PATCH 03/13] Finish guarded display text at BInventory.java output sink --- .../com/bencodez/advancedcore/api/inventory/BInventory.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java index 38c21349ac..4906d4b47e 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java @@ -497,8 +497,9 @@ private void cancelLegacyTimers() { } private String getDisplayName(Player player) { - return PlaceholderUtils.replaceJavascript(player, - PlaceholderUtils.replacePlaceHolder(inventoryName, placeholders)); + return com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting( + PlaceholderUtils.replaceJavascript(player, + PlaceholderUtils.replacePlaceHolder(inventoryName, placeholders))); } private int getProperSize(int size) { From f8097f61bec0b5a1f4dc192aa4c7d0bc575f5350 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:50 -0600 Subject: [PATCH 04/13] Finish guarded display text at Layout.java output sink --- .../java/com/bencodez/advancedcore/api/bookgui/Layout.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/Layout.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/Layout.java index fe56a4b6ae..31055bd17d 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/Layout.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/Layout.java @@ -50,7 +50,8 @@ public Layout addPlaceholder(String toReplace, String replaceWith) { } private String colorize(String s) { - return ChatColor.translateAlternateColorCodes('&', s); + return com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting( + ChatColor.translateAlternateColorCodes('&', s)); } /** From 0151323254f15721793fecdea61787e434413439 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:52 -0600 Subject: [PATCH 05/13] Finish guarded display text at BookWrapper.java output sink --- .../com/bencodez/advancedcore/api/bookgui/BookWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/BookWrapper.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/BookWrapper.java index ac34c942a7..382e152c53 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/BookWrapper.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/bookgui/BookWrapper.java @@ -137,7 +137,8 @@ public BookWrapper addToCurrentPage(BaseComponent... baseComponents) { * @return the colorized string */ public String colorize(String s) { - return MessageAPI.colorize(s); + return com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting( + MessageAPI.colorize(s)); } /** From d84c6780abfd3f4d2053c730036773dde165e237 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:54 -0600 Subject: [PATCH 06/13] Finish guarded display text at Title.java output sink --- .../com/bencodez/advancedcore/api/misc/effects/Title.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/Title.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/Title.java index e265052808..be35d7197b 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/Title.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/Title.java @@ -168,7 +168,9 @@ public void send(Player player) { } public void send(Player player, String title, String subtitle, int fadeInTime, int stayTime, int fadeOutTime) { - player.sendTitle(MessageAPI.colorize(title), MessageAPI.colorize(subtitle), fadeInTime, stayTime, fadeOutTime); + player.sendTitle(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(title)), + com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(subtitle)), + fadeInTime, stayTime, fadeOutTime); } /** @@ -226,7 +228,7 @@ public void updateSubtitle(Player player) { } public void updateSubtitle(Player player, String subtitle) { - player.sendTitle("", MessageAPI.colorize(subtitle), -1, -1, -1); + player.sendTitle("", com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(subtitle)), -1, -1, -1); } /** @@ -252,6 +254,6 @@ public void updateTitle(Player player) { } public void updateTitle(Player player, String title) { - player.sendTitle(MessageAPI.colorize(title), "", -1, -1, -1); + player.sendTitle(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(title)), "", -1, -1, -1); } } \ No newline at end of file From b23b3ef64d02b718e7ed6f0a5ef09397e3674150 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:30:56 -0600 Subject: [PATCH 07/13] Finish guarded display text at BossBar.java output sink --- .../com/bencodez/advancedcore/api/misc/effects/BossBar.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/BossBar.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/BossBar.java index ea43811bd5..36a6cbf59c 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/BossBar.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/effects/BossBar.java @@ -20,7 +20,7 @@ public class BossBar { private org.bukkit.boss.BossBar bossBar; public BossBar(String msg, String barColor, String barStyle, double progress) { - bossBar = Bukkit.createBossBar(MessageAPI.colorize(msg), EffectCompatibility.parseBarColor(barColor), + bossBar = Bukkit.createBossBar(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(msg)), EffectCompatibility.parseBarColor(barColor), EffectCompatibility.parseBarStyle(barStyle)); bossBar.setProgress(EffectCompatibility.clampProgress(progress)); } @@ -116,7 +116,7 @@ public void setStyle(String barStyle) { public void setTitle(String title) { if (title != null) { - bossBar.setTitle(MessageAPI.colorize(title)); + bossBar.setTitle(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(title))); } } From 00ff785c557b62b9fb78a169f0ad9f1ddf1f0bb4 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:31:47 -0600 Subject: [PATCH 08/13] Render clean item names and lore without altering reusable guarded templates --- .../advancedcore/api/item/ItemBuilder.java | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/item/ItemBuilder.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/item/ItemBuilder.java index 70264008e8..713eb86e2a 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/item/ItemBuilder.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/item/ItemBuilder.java @@ -42,6 +42,7 @@ import com.bencodez.advancedcore.AdvancedCorePlugin; import com.bencodez.advancedcore.api.javascript.JavascriptEngine; +import com.bencodez.advancedcore.api.messages.DisplayTextBoundaries; import com.bencodez.advancedcore.api.messages.PlaceholderUtils; import com.bencodez.advancedcore.api.rewards.RewardDisplayPlaceholders; import com.bencodez.advancedcore.api.misc.MiscUtils; @@ -1736,7 +1737,7 @@ public ItemStack parsePlaceholders(OfflinePlayer player) { setSkullOwner(PlaceholderUtils.replaceJavascript(player, PlaceholderUtils.replacePlaceHolder(skull, placeholders))); } - return is; + return finishedDisplayItem(); } /** @@ -2141,6 +2142,38 @@ public ItemBuilder setUnbreakable(boolean unbreakable) { return this; } + /** + * Creates a final client-facing copy only when protected identifiers are + * present. The builder retains its guarded template for later GUI refreshes, + * which must never re-parse a previously unguarded display name. + */ + private ItemStack finishedDisplayItem() { + ItemMeta sourceMeta = is.getItemMeta(); + if (sourceMeta == null) return is; + String name = sourceMeta.hasDisplayName() ? sourceMeta.getDisplayName() : null; + List lore = sourceMeta.hasLore() ? sourceMeta.getLore() : null; + boolean guarded = name != null && name.indexOf('\u2060') >= 0; + if (lore != null) { + for (String line : lore) { + if (line != null && line.indexOf('\u2060') >= 0) { + guarded = true; + break; + } + } + } + if (!guarded) return is; + ItemStack displayed = is.clone(); + ItemMeta displayMeta = displayed.getItemMeta(); + if (name != null) displayMeta.setDisplayName(DisplayTextBoundaries.afterFormatting(name)); + if (lore != null) { + ArrayList visibleLore = new ArrayList<>(lore.size()); + for (String line : lore) visibleLore.add(DisplayTextBoundaries.afterFormatting(line)); + displayMeta.setLore(visibleLore); + } + displayed.setItemMeta(displayMeta); + return displayed; + } + /** * Retrieves the itemstack from the ItemBuilder. * @@ -2167,7 +2200,7 @@ public ItemStack toItemStack() { if (checkLoreLength) { checkLoreLength(); } - return is; + return finishedDisplayItem(); } /** @@ -2191,7 +2224,7 @@ public ItemStack toItemStack(OfflinePlayer player) { if (checkLoreLength) { checkLoreLength(); } - return is; + return finishedDisplayItem(); } /** @@ -2215,7 +2248,7 @@ public ItemStack toItemStack(Player player) { if (checkLoreLength) { checkLoreLength(); } - return is; + return finishedDisplayItem(); } @Override From 2c31e7dc031eee64d88a47fce37fa16df9901fda Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:32:27 -0600 Subject: [PATCH 09/13] Test end-of-pipeline rendering of guarded vote site labels --- .../messages/DisplayTextBoundariesTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java new file mode 100644 index 0000000000..aeb62ccc7b --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java @@ -0,0 +1,42 @@ +package com.bencodez.advancedcore.api.messages; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.TextComponent; + +class DisplayTextBoundariesTest { + + @Test + void chatRemovesTemporaryMarkersOnlyAfterLegacyParsing() { + TextComponent text = PlaceholderUtils.parseJson("\u00A7eSite: \u2060Minecraft Menu\u2060"); + + assertEquals("Site: Minecraft Menu", text.toPlainText()); + assertFalse(text.toPlainText().contains("\u2060")); + assertEquals("\u00A7eSite: Minecraft Menu", text.toLegacyText()); + } + + @Test + void guardedColorSequenceRemainsLiteralAfterLastParsingPass() { + TextComponent text = PlaceholderUtils.parseJson("&\u2060a"); + + assertEquals("&a", text.toPlainText()); + assertFalse(text.toPlainText().contains("\u2060")); + } + + @Test + void interactiveActionsStayExactWhileVisibleTextIsClean() { + TextComponent text = PlaceholderUtils.parseJson( + "[Text=\"\u2060Site name\u2060\",Url=\"https://example.com/vote\"]"); + + assertEquals("Site name", text.toPlainText()); + TextComponent link = (TextComponent) text.getExtra().get(1); + ClickEvent click = link.getClickEvent(); + assertNotNull(click); + assertEquals("https://example.com/vote", click.getValue()); + } +} From 5c0896dd0bc3293c032be8ce9f34d40857a2511f Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:37:36 -0600 Subject: [PATCH 10/13] Allow Bungee's default legacy color prefix in display regression test --- .../advancedcore/api/messages/DisplayTextBoundariesTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java index aeb62ccc7b..669d2777ff 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java @@ -17,7 +17,8 @@ void chatRemovesTemporaryMarkersOnlyAfterLegacyParsing() { assertEquals("Site: Minecraft Menu", text.toPlainText()); assertFalse(text.toPlainText().contains("\u2060")); - assertEquals("\u00A7eSite: Minecraft Menu", text.toLegacyText()); + org.junit.jupiter.api.Assertions.assertTrue(text.toLegacyText().contains("\u00A7eSite: Minecraft Menu")); + assertFalse(text.toLegacyText().contains("\u2060")); } @Test From 3ef6c6de71424c356be093dda0404beef99057ca Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:46:00 -0600 Subject: [PATCH 11/13] Avoid reactivating incomplete legacy section-sign codes at String output sinks --- .../api/messages/DisplayTextBoundaries.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java index aaadacf2ba..3ac9b4e886 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java @@ -17,7 +17,28 @@ private DisplayTextBoundaries() { } public static String afterFormatting(String prepared) { - return prepared == null ? null : prepared.replace("\u2060", ""); + if (prepared == null || prepared.indexOf('\u2060') < 0) return prepared; + StringBuilder visible = new StringBuilder(prepared.length()); + for (int i = 0; i < prepared.length(); i++) { + char next = prepared.charAt(i); + if (next != '\u2060') { + visible.append(next); + continue; + } + // Some legacy String sinks (inventory titles, item metadata, boss + // bars) still parse § codes on the client. Prevent an incomplete + // § in a trusted template from joining the next site-name letter + // into an active legacy color code when dropping this guard. + if (i > 0 && prepared.charAt(i - 1) == '\u00A7' + && i + 1 < prepared.length() && isLegacyColorCode(prepared.charAt(i + 1))) { + visible.append(' '); + } + } + return visible.toString(); + } + + private static boolean isLegacyColorCode(char code) { + return "0123456789abcdefklmnorx".indexOf(Character.toLowerCase(code)) >= 0; } public static void afterFormatting(BaseComponent component) { From 6d18a319eefafe687bd8f56c4741c8b3f5e6f616 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:46:16 -0600 Subject: [PATCH 12/13] Test section-sign boundaries at legacy String-only GUI sinks --- .../api/messages/DisplayTextBoundariesTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java index 669d2777ff..4697ec2fa9 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundariesTest.java @@ -29,6 +29,13 @@ void guardedColorSequenceRemainsLiteralAfterLastParsingPass() { assertFalse(text.toPlainText().contains("\u2060")); } + @Test + void unfinishedSectionColorCodesCannotBecomeActiveAtLegacyStringSinks() { + assertEquals("\u00A7 a", DisplayTextBoundaries.afterFormatting("\u00A7\u2060a\u2060")); + assertEquals("Minecraft Menu", DisplayTextBoundaries.afterFormatting("\u2060Minecraft Menu\u2060")); + assertEquals("&aGreen", DisplayTextBoundaries.afterFormatting("\u2060&\u2060aGreen\u2060")); + } + @Test void interactiveActionsStayExactWhileVisibleTextIsClean() { TextComponent text = PlaceholderUtils.parseJson( From 25e771da2eaab52f19d96ee77a6f133d667d0107 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 11 Oct 2026 10:46:26 -0600 Subject: [PATCH 13/13] Preserve exact text in parsed components while guarding legacy string sinks --- .../advancedcore/api/messages/DisplayTextBoundaries.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java index 3ac9b4e886..f8ab50567f 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/messages/DisplayTextBoundaries.java @@ -44,7 +44,9 @@ private static boolean isLegacyColorCode(char code) { public static void afterFormatting(BaseComponent component) { if (component == null) return; if (component instanceof TextComponent text) { - text.setText(afterFormatting(text.getText())); + // A component already passed legacy parsing, so it can retain the + // exact literal text without inserting a String-sink separator. + text.setText(text.getText() == null ? null : text.getText().replace("\u2060", "")); } List children = component.getExtra(); if (children != null) {