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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
group = "world.bentobox" // From <groupId>

// Base properties from <properties>
val buildVersion = "3.14.1"
val buildVersion = "3.14.2"
val buildNumberDefault = "-LOCAL" // Local build identifier
val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version

Expand Down
6 changes: 4 additions & 2 deletions src/main/java/world/bentobox/bentobox/api/flags/Flag.java
Original file line number Diff line number Diff line change
Expand Up @@ -797,8 +797,10 @@ public Builder hideWhen(HideWhen hideWhen) {
* @return Flag
*/
public Flag build() {
// Ensure the default rank is not below the minimum selectable rank
if (defaultRank < minimumRank) {
// Ensure the default rank is not below the minimum selectable rank.
// Only applies to PROTECTION flags — SETTING/WORLD_SETTING flags use -1
// as a valid "disabled" state (Island.isAllowed checks >= 0).
if (type == Type.PROTECTION && defaultRank < minimumRank) {
BentoBox.getInstance().logWarning("Flag " + id + " defaultRank (" + defaultRank
+ ") is below minimumRank (" + minimumRank + "); raising defaultRank to minimumRank.");
defaultRank = minimumRank;
Expand Down
25 changes: 22 additions & 3 deletions src/main/java/world/bentobox/bentobox/managers/LocalesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
*
* @param localeFolder - locale folder location relative to the plugin's data folder
*/
public void loadLocalesFromFile(String localeFolder) {

Check failure on line 260 in src/main/java/world/bentobox/bentobox/managers/LocalesManager.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=BentoBoxWorld_BentoBox&issues=AZ2OA1QrfdtVFtrWUEkc&open=AZ2OA1QrfdtVFtrWUEkc&pullRequest=2944
// Filter for files ending with .yml with a name whose length is >= 6 (xx.yml)
FilenameFilter ymlFilter = (dir, name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(".yml") && name.length() >= 6;

Expand All @@ -268,13 +268,32 @@
return;
}
// Run through the files and store the locales
for (File language : Objects.requireNonNull(localeDir.listFiles(ymlFilter))) {

Check warning on line 271 in src/main/java/world/bentobox/bentobox/managers/LocalesManager.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reduce the total number of break and continue statements in this loop to use at most one.

See more on https://sonarcloud.io/project/issues?id=BentoBoxWorld_BentoBox&issues=AZ2OA1QrfdtVFtrWUEkb&open=AZ2OA1QrfdtVFtrWUEkb&pullRequest=2944
String tag = language.getName().substring(0, language.getName().length() - 4);
Locale localeObject = Locale.forLanguageTag(tag);

// Skip files whose name does not parse to a real BCP-47 language tag.
// e.g. "zh_CN.yml" (underscore) yields Locale.ROOT, which would otherwise show
// up as a blank entry in the language selector panel.
// If the tag uses underscores (e.g. pt_BR) it won't parse as a valid BCP-47 tag.
// Silently fix it: rename the file to use '-' and load it under the corrected locale.
if (localeObject.getLanguage().isEmpty() && tag.contains("_")) {
String fixedTag = tag.replace('_', '-');
File fixedFile = new File(language.getParentFile(), fixedTag + ".yml");
if (!fixedFile.exists() && language.renameTo(fixedFile)) {
plugin.logWarning("Locale file '" + localeFolder + "/" + language.getName()
+ "' has been renamed to '" + fixedTag + ".yml' to conform to BCP-47 (use '-' not '_').");
language = fixedFile;
} else if (fixedFile.exists()) {
plugin.logWarning("Duplicate locale file '" + localeFolder + "/" + language.getName()
+ "': '" + fixedTag + ".yml' already exists and will be used instead. Please remove the underscore version.");
continue;
} else {
plugin.logWarning("Locale file '" + localeFolder + "/" + language.getName()
+ "' uses '_' instead of '-' and could not be renamed; loading it as '" + fixedTag + "'.");
}
tag = fixedTag;
localeObject = Locale.forLanguageTag(tag);
}

// Skip files that still don't parse to a real BCP-47 language tag after the fix attempt.
if (localeObject.getLanguage().isEmpty()) {
plugin.logWarning("Ignoring locale file '" + localeFolder + "/" + language.getName()
+ "': '" + tag + "' is not a valid BCP-47 language tag (use '-' not '_').");
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/world/bentobox/bentobox/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ public class Util {
*/
private static final Pattern LEGACY_HEX_CODE_PATTERN = Pattern.compile("&#[0-9a-fA-F]{3,6}|\u00A7x(\u00A7[0-9a-fA-F]){6}");

/**
* Pattern to match the BungeeCord/Spigot {@code &x&R&R&G&G&B&B} hex format
* (after {@code §} has been normalised to {@code &}).
* Produced by {@link LegacyComponentSerializer} with {@code useUnusualXRepeatedCharacterHexFormat()}.
*/
private static final Pattern BUNGEE_HEX_PATTERN = Pattern.compile("&x(&[0-9a-fA-F]){6}");

/**
* MiniMessage instance for parsing MiniMessage-formatted strings.
*/
Expand Down Expand Up @@ -1047,6 +1054,10 @@ public static String legacyToMiniMessage(@NonNull String legacy) {
// First, normalize § to & for uniform processing
String text = legacy.replace('\u00A7', '&');

// Convert BungeeCord/Spigot §x§R§R§G§G§B§B hex format (now &x&R&R...) to &#RRGGBB
// so the HEX_PATTERN step below handles all hex input uniformly.
text = normalizeBungeeHex(text);

// Convert hex codes &#RRGGBB → <color:#RRGGBB>
Matcher hexMatcher = HEX_PATTERN.matcher(text);
StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -1211,6 +1222,8 @@ public static String legacyToMiniMessage(@NonNull String legacy) {
public static String replaceLegacyCodesInline(@NonNull String text) {
// Normalize § to &
text = text.replace('\u00A7', '&');
// Convert BungeeCord/Spigot &x&R&R&G&G&B&B hex format to &#RRGGBB (see legacyToMiniMessage)
text = normalizeBungeeHex(text);
// Replace hex codes &#RRGGBB → <color:#RRGGBB>
Matcher hexMatcher = HEX_PATTERN.matcher(text);
StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -1532,6 +1545,30 @@ private static String legacyColorCode(TextColor color) {
return COLOR_CHAR + Character.toString(code);
}

/**
* Converts the BungeeCord/Spigot {@code &x&R&R&G&G&B&B} repeated-character hex format
* (produced after {@code §} → {@code &} normalisation) to the {@code &#RRGGBB} form
* that {@link #HEX_PATTERN} understands.
*
* @param text input string with {@code &} normalised from {@code §}
* @return string with {@code &x&R&R&G&G&B&B} sequences replaced by {@code &#RRGGBB}
*/
private static String normalizeBungeeHex(@NonNull String text) {
Matcher m = BUNGEE_HEX_PATTERN.matcher(text);
if (!m.find()) {
return text;
}
StringBuilder sb = new StringBuilder();
m.reset();
while (m.find()) {
// "&x&2&3&8&a&f&0" → strip "&x" prefix and remaining "&" chars → "238af0"
String digits = m.group(0).substring(2).replace("&", "");
m.appendReplacement(sb, "&#" + digits);
}
m.appendTail(sb);
return sb.toString();
}

/**
* Serializes an Adventure Component to plain text with no formatting.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,11 @@ public enum ServerVersion {
/**
* @since 3.12.2
*/
V26_1_1(Compatibility.COMPATIBLE),;
V26_1_1(Compatibility.COMPATIBLE),
/**
* @since 3.14.2
*/
V26_1_2(Compatibility.COMPATIBLE),;

private final Compatibility compatibility;

Expand Down
13 changes: 13 additions & 0 deletions src/test/java/world/bentobox/bentobox/api/flags/FlagTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -450,4 +450,17 @@ void testDefaultRankClampedToMinimumRank() {
.build();
assertEquals(RanksManager.MEMBER_RANK, flag.getDefaultRank());
}

/**
* SETTING flags should allow -1 (disabled) as defaultRank without clamping,
* since Island.isAllowed() uses >= 0 as the enabled threshold.
*/
@Test
void testSettingFlagAllowsNegativeDefaultRank() {
Flag flag = new Flag.Builder("pvp_test", Material.ARROW)
.type(Flag.Type.SETTING)
.defaultRank(-1)
.build();
assertEquals(-1, flag.getDefaultRank());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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 static org.junit.jupiter.api.Assertions.assertTrue;

import net.kyori.adventure.text.Component;
Expand Down Expand Up @@ -285,4 +286,45 @@ void testStripSpaceAfterColorCodesRespectsBoundary() {
// Reset must NOT strip
assertEquals("\u00A7r world", Util.stripSpaceAfterColorCodes("\u00A7r world"));
}

/**
* Regression for <a href="https://github.com/BentoBoxWorld/BentoBox/issues/2943">BentoBox#2943</a>:
* hex colors using the {@code &#RRGGBB} format were broken because
* {@code translateColorCodes} serialises them to the BungeeCord
* {@code §x§R§R§G§G§B§B} format, which {@code legacyToMiniMessage} (and
* {@code replaceLegacyCodesInline}) did not recognise. The colour was then
* corrupted to a sequence of named colours (&amp;2, &amp;3, …) instead of
* the intended hex value.
*/
@Test
void testBungeeCordHexFormatRoundTrip() {
// Simulate the full path: user writes &#238af0&l in a locale file.
// convertToLegacy (pure-legacy path) calls translateColorCodes which
// serialises to §x§2§3§8§a§f§0§l. sendRawMessage then calls
// parseMiniMessageOrLegacy which must reconstruct the original colour.
String bungeeHex = "\u00A7x\u00A72\u00A73\u00A78\u00A7a\u00A7f\u00A70\u00A7l";
Component component = Util.parseMiniMessageOrLegacy(bungeeHex + "test");
// The component must carry the original hex colour, not a named-colour approximation.
net.kyori.adventure.text.format.TextColor color = component.children().isEmpty()
? component.color()
: component.children().get(0).color();
assertNotNull(color, "Expected a colour on the component");
assertEquals(0x238af0, color.value(), "Hex colour #238af0 must survive the BungeeCord round-trip");
}

/**
* {@code &#RRGGBB&l} (the raw user-written format) must also round-trip correctly
* through {@code legacyToMiniMessage}.
*/
@Test
void testRawHexWithBoldRoundTrip() {
String mm = Util.legacyToMiniMessage("&#238af0&lBold text");
Component component = Util.parseMiniMessage(mm);
// Must have the correct hex colour
net.kyori.adventure.text.format.TextColor color = component.color() != null
? component.color()
: component.children().isEmpty() ? null : component.children().get(0).color();
assertNotNull(color, "Expected a colour");
assertEquals(0x238af0, color.value());
}
}
Loading