Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.eclipse.jdt.annotation.NonNull;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import world.bentobox.chunkblock.ChunkBlock;
import world.bentobox.chunkblock.dataobjects.OneBlockIslands;
import world.bentobox.chunkblock.events.MagicBlockEvent;
Expand All @@ -28,18 +27,14 @@
import world.bentobox.bentobox.api.events.island.IslandExitEvent;
import world.bentobox.bentobox.api.metadata.MetaDataValue;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.Util;
import world.bentobox.bentobox.database.objects.Island;

public class BossBarListener implements Listener {

private static final String BOSSBAR_METADATA = "chunkblock.bossbar";
public static final String ACTIONBAR_METADATA = "chunkblock.actionbar";

private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexColors() // Enables support for modern hex codes (e.g., &#FF0000) alongside legacy codes.
.build();

public BossBarListener(ChunkBlock addon) {
super();
this.addon = addon;
Expand Down Expand Up @@ -94,16 +89,21 @@ public void onFlagChange(FlagSettingChangeEvent e) {
}

/**
* Converts a string containing Bukkit color codes ('&') into an Adventure Component.
* Converts a formatted string into an Adventure Component.
* <p>
* Accepts MiniMessage tags, {@code &} or {@code §} legacy codes, hex ({@code &#RRGGBB}), or a
* mixture of them. Handling {@code §} matters here because translations arrive already
* converted to {@code §} codes by BentoBox - a serializer bound to {@code &} would leave those
* in the output as literal text.
*
* @param legacyString The string with Bukkit color and format codes.
* @param text The string with color and format codes.
* @return The resulting Adventure Component.
*/
public static Component bukkitToAdventure(String legacyString) {
if (legacyString == null) {
public static Component bukkitToAdventure(String text) {
if (text == null) {
return Component.empty();
}
return LEGACY_SERIALIZER.deserialize(legacyString);
return Util.parseMiniMessageOrLegacy(text);
}

private void tryToShowActionBar(UUID uuid, Island island) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;

import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import world.bentobox.chunkblock.ChunkBlock;
import world.bentobox.bentobox.util.Util;
import world.bentobox.chunkblock.dataobjects.OneBlockIslands;
Expand Down Expand Up @@ -131,6 +130,10 @@ private Location getHologramLocation(Island island) {
/**
* Creates a new hologram (TextDisplay) at the given location.
* Caches the hologram for future reference.
* <p>
* The text may use MiniMessage tags, {@code &} or {@code §} legacy codes, hex
* ({@code &#RRGGBB}), or a mixture. Phase file hologram lines are read straight from YAML and
* never see BentoBox's translation, so this is the only place their formatting is resolved.
*
* @param pos the location to create the hologram at
* @param text the text to display
Expand All @@ -140,7 +143,7 @@ private void createHologram(Location pos, String text) {
display.setAlignment(TextDisplay.TextAlignment.CENTER);
display.setBillboard(Billboard.CENTER);
display.setPersistent(true);
display.text(LegacyComponentSerializer.legacyAmpersand().deserialize(text));
display.text(Util.parseMiniMessageOrLegacy(text));
activeHolograms.add(pos);
}

Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/phases/0_plains.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
700: CHEST_WITH_WATER_BUCKET
# Hologram Lines to Display
# The First (Before Phase 1) Hologram is Located in your Locale.
# The text can use any of these, and they can be mixed:
# &a&lGood Luck! legacy colour and format codes
# &#55FF55Good Luck! hex colour
# <green><bold>Good Luck! MiniMessage tags
# MiniMessage also gives you gradients, e.g.
# <gradient:#55FF55:#00AA00>Good Luck!</gradient>
holograms:
0: "&aGood Luck!"
biome: PLAINS
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package world.bentobox.chunkblock.listeners;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
Expand All @@ -23,6 +25,9 @@
import org.mockito.Mock;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import world.bentobox.bentobox.api.events.flags.FlagSettingChangeEvent;
import world.bentobox.bentobox.api.events.island.IslandEnterEvent;
import world.bentobox.bentobox.api.events.island.IslandExitEvent;
Expand Down Expand Up @@ -179,4 +184,69 @@ void testAllHandlersInertWhenAddonNeverEnabled() {
verify(bossBar, never()).addPlayer(any());
verify(bossBar, never()).removePlayer(any());
}

/**
* Serializes to legacy section codes so a test can assert on the formatting that actually
* comes out, without depending on how the component tree happens to be nested.
*/
private static String legacy(Component c) {
return LegacyComponentSerializer.legacySection().serialize(c);
}

/**
* MiniMessage tags used to be rendered as literal text because the serializer only understood
* legacy codes.
*/
@Test
void testBukkitToAdventureParsesMiniMessage() {
Component c = BossBarListener.bukkitToAdventure("<green><bold>Plains</bold></green>");
assertEquals("Plains", PlainTextComponentSerializer.plainText().serialize(c));
assertTrue(legacy(c).contains("\u00a7a"), "expected green in " + legacy(c));
assertTrue(legacy(c).contains("\u00a7l"), "expected bold in " + legacy(c));
}

/**
* MiniMessage gradients, which legacy codes cannot express at all.
*/
@Test
void testBukkitToAdventureParsesGradient() {
Component c = BossBarListener.bukkitToAdventure("<gradient:#55FF55:#00AA00>Plains</gradient>");
assertEquals("Plains", PlainTextComponentSerializer.plainText().serialize(c));
}

/**
* Translations reach this method already converted to section codes by BentoBox, so a
* serializer bound to '&' would leave them in the output as literal text.
*/
@Test
void testBukkitToAdventureParsesSectionCodes() {
Component c = BossBarListener.bukkitToAdventure("\u00a7aPlains");
assertEquals("Plains", PlainTextComponentSerializer.plainText().serialize(c));
assertTrue(legacy(c).contains("\u00a7a"), "expected green in " + legacy(c));
}

/**
* Legacy '&' codes must keep working - every existing locale file uses them.
*/
@Test
void testBukkitToAdventureParsesLegacyAmpersand() {
Component c = BossBarListener.bukkitToAdventure("&aPlains");
assertEquals("Plains", PlainTextComponentSerializer.plainText().serialize(c));
assertTrue(legacy(c).contains("\u00a7a"), "expected green in " + legacy(c));
}

/**
* Hex colours, which the previous serializer supported here and must not regress.
*/
@Test
void testBukkitToAdventureParsesHex() {
Component c = BossBarListener.bukkitToAdventure("&#55FF55Plains");
assertEquals("Plains", PlainTextComponentSerializer.plainText().serialize(c));
assertEquals(TextColor.fromHexString("#55FF55"), c.color());
}

@Test
void testBukkitToAdventureNullIsEmpty() {
assertEquals(Component.empty(), BossBarListener.bukkitToAdventure(null));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package world.bentobox.chunkblock.listeners;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
Expand Down Expand Up @@ -29,8 +31,14 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;

import world.bentobox.chunkblock.ChunkBlock;
import world.bentobox.chunkblock.CommonTestSetup;
import world.bentobox.chunkblock.Settings;
Expand Down Expand Up @@ -198,4 +206,62 @@ void testProcess() {
verify(sch).runTaskLater(isNull(), any(Runnable.class), anyLong());
}

/**
* Captures the component the hologram was actually given.
*/
private Component displayed(String hologramLine) {
when(phase.getHologramLine(anyInt())).thenReturn(hologramLine);
// process() writes the line to the data object then reads it straight back, and that
// object is a mock, so the read has to be stubbed too or it returns the setUp default.
when(is.getHologram()).thenReturn(hologramLine);
hl.process(island, is, phase);
ArgumentCaptor<Component> captor = ArgumentCaptor.forClass(Component.class);
verify(hologram).text(captor.capture());
return captor.getValue();
}

/**
* Phase file hologram lines are read straight from YAML, so this is the only place their
* formatting is resolved. MiniMessage tags used to appear as literal text.
*/
@Test
void testHologramParsesMiniMessage() {
Component c = displayed("<green><bold>Plains</bold></green>");
assertEquals("Plains", PlainTextComponentSerializer.plainText().serialize(c));
String legacy = LegacyComponentSerializer.legacySection().serialize(c);
assertTrue(legacy.contains("\u00a7a"), "expected green in " + legacy);
assertTrue(legacy.contains("\u00a7l"), "expected bold in " + legacy);
}

/**
* Legacy '&' codes must keep working - every existing phase file uses them.
*/
@Test
void testHologramParsesLegacyAmpersand() {
Component c = displayed("&aGood Luck!");
assertEquals("Good Luck!", PlainTextComponentSerializer.plainText().serialize(c));
assertTrue(LegacyComponentSerializer.legacySection().serialize(c).contains("\u00a7a"));
}

/**
* Hex was not supported here before - the serializer was built without hex enabled.
*/
@Test
void testHologramParsesHex() {
Component c = displayed("&#55FF55Good Luck!");
assertEquals("Good Luck!", PlainTextComponentSerializer.plainText().serialize(c));
assertEquals(TextColor.fromHexString("#55FF55"), c.color());
}

/**
* The starting hologram comes from the locale file via User.getTranslation, which hands back
* section codes. A serializer bound to '&' left those in as literal text.
*/
@Test
void testHologramParsesSectionCodes() {
Component c = displayed("\u00a7aWelcome");
assertEquals("Welcome", PlainTextComponentSerializer.plainText().serialize(c));
assertTrue(LegacyComponentSerializer.legacySection().serialize(c).contains("\u00a7a"));
}

}
Loading