mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2026-08-18 03:05:10 +00:00
Merge branch 'mcMMO-Dev:master' into master
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
Version 2.2.054
|
||||
Fixed party/admin chat allowing player color code tokens without the 'mcmmo.chat.colors' permission
|
||||
Fixed diminished returns resetting in certain situations when players reconnected
|
||||
|
||||
Version 2.2.053
|
||||
!! -- This build has important fixes for anyone using Paper (or forks of Paper), please read the notes carefully.
|
||||
It is completely safe to update to this version of mcMMO.
|
||||
@ -11,7 +15,6 @@ Version 2.2.053
|
||||
Fixed KnockOnWood XP orbs never spawning on nether/warped tree cap blocks during Tree Feller
|
||||
Fixed Impale (Tridents) damage bonus formula applying one fewer rank of the multiplier than intended
|
||||
Fixed melee attack strength scale resolving to near-zero after Paper fixed a vanilla attack cooldown bug in 26.1.2 (See notes)
|
||||
Fixed server-side diminished returns state being evicted too early, allowing reconnects to bypass the DR window (See notes)
|
||||
Added option to allow Magic Hunter (Fishing) to grant items with conflicting enchantments; disabled by default (Thanks Warriorrrr)
|
||||
Added 'Skills.Fishing.Allow_Conflicting_Enchants' to config.yml (Thanks Warriorrrr)
|
||||
(Codebase) Removed 27 dead JSON.* locale keys from all locale files (See notes)
|
||||
@ -5523,3 +5526,4 @@ Version 0.1
|
||||
|
||||
Releasing my awesome plugin
|
||||
|
||||
|
||||
|
||||
2
pom.xml
2
pom.xml
@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gmail.nossr50.mcMMO</groupId>
|
||||
<artifactId>mcMMO</artifactId>
|
||||
<version>2.2.053-SNAPSHOT</version>
|
||||
<version>2.2.053</version>
|
||||
<name>mcMMO</name>
|
||||
<url>https://github.com/mcMMO-Dev/mcMMO</url>
|
||||
<scm>
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
package com.gmail.nossr50.chat.mailer;
|
||||
|
||||
import com.gmail.nossr50.locale.LocaleLoader;
|
||||
import com.gmail.nossr50.util.text.TextUtils;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -10,4 +14,21 @@ public abstract class AbstractChatMailer implements ChatMailer {
|
||||
public AbstractChatMailer(@NotNull Plugin pluginRef) {
|
||||
this.pluginRef = pluginRef;
|
||||
}
|
||||
|
||||
protected final @NotNull TextComponent formatLocaleStyleWithLiteralMessage(
|
||||
@NotNull String localeKey,
|
||||
@NotNull String authoredName,
|
||||
@NotNull String literalMessage) {
|
||||
final String messageStartMarker = createLiteralMessageMarker("START");
|
||||
final String messageEndMarker = createLiteralMessageMarker("END");
|
||||
final String formattedTemplate = LocaleLoader.getString(localeKey, authoredName,
|
||||
messageStartMarker + messageEndMarker);
|
||||
|
||||
return TextUtils.insertLiteralTextAtMarkers(formattedTemplate, messageStartMarker,
|
||||
messageEndMarker, literalMessage);
|
||||
}
|
||||
|
||||
private static @NotNull String createLiteralMessageMarker(@NotNull String markerRole) {
|
||||
return "\u0002MCMMO_" + markerRole + "_" + UUID.randomUUID() + "\u0003";
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,9 +63,9 @@ public class AdminChatMailer extends AbstractChatMailer {
|
||||
"Chat.Style.Admin", author.getAuthoredName(ChatChannel.ADMIN),
|
||||
message);
|
||||
} else {
|
||||
return TextUtils.ofLegacyTextRaw(
|
||||
LocaleLoader.getString("Chat.Style.Admin",
|
||||
author.getAuthoredName(ChatChannel.ADMIN), message));
|
||||
final String literalMessage = TextUtils.literalizeLegacyColorCodes(message);
|
||||
return formatLocaleStyleWithLiteralMessage("Chat.Style.Admin",
|
||||
author.getAuthoredName(ChatChannel.ADMIN), literalMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -77,15 +77,14 @@ public class PartyChatMailer extends AbstractChatMailer {
|
||||
message);
|
||||
}
|
||||
} else {
|
||||
final String literalMessage = TextUtils.literalizeLegacyColorCodes(message);
|
||||
|
||||
if (isLeader) {
|
||||
return TextUtils.ofLegacyTextRaw(
|
||||
LocaleLoader.getString(
|
||||
"Chat.Style.Party.Leader",
|
||||
author.getAuthoredName(ChatChannel.PARTY), message));
|
||||
return formatLocaleStyleWithLiteralMessage("Chat.Style.Party.Leader",
|
||||
author.getAuthoredName(ChatChannel.PARTY), literalMessage);
|
||||
} else {
|
||||
return TextUtils.ofLegacyTextRaw(
|
||||
LocaleLoader.getString("Chat.Style.Party",
|
||||
author.getAuthoredName(ChatChannel.PARTY), message));
|
||||
return formatLocaleStyleWithLiteralMessage("Chat.Style.Party",
|
||||
author.getAuthoredName(ChatChannel.PARTY), literalMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,8 +98,7 @@ public class PlayerProfile {
|
||||
this.playerName = playerName;
|
||||
this.uuid = uuid;
|
||||
this.scoreboardTipsShown = scoreboardTipsShown;
|
||||
// This constructor is used for save copies only — do not pull DR state from cache.
|
||||
this.diminishedReturnsState = DiminishedReturnsCache.getOrCreate(null);
|
||||
this.diminishedReturnsState = DiminishedReturnsCache.getOrCreate(uuid);
|
||||
|
||||
skills.putAll(levelData);
|
||||
skillsXp.putAll(xpData);
|
||||
|
||||
@ -136,4 +136,51 @@ public class TextUtils {
|
||||
TextComponent componentForm = ofLegacyTextRaw(string);
|
||||
return customLegacySerializer.serialize(componentForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts literal message text between two unique markers in a pre-formatted legacy template.
|
||||
*
|
||||
* <p>Everything before and after the marker pair is parsed as legacy text, while the inserted
|
||||
* message is kept literal so legacy color tokens are not interpreted.
|
||||
*/
|
||||
public static @NotNull TextComponent insertLiteralTextAtMarkers(
|
||||
@NotNull String formattedTemplate,
|
||||
@NotNull String startMarker,
|
||||
@NotNull String endMarker,
|
||||
@NotNull String literalMessage) {
|
||||
final int startMarkerIndex = formattedTemplate.indexOf(startMarker);
|
||||
|
||||
if (startMarkerIndex < 0) {
|
||||
return ofLegacyTextRaw(formattedTemplate);
|
||||
}
|
||||
|
||||
final int afterStartMarkerIndex = startMarkerIndex + startMarker.length();
|
||||
final int endMarkerIndex = formattedTemplate.indexOf(endMarker, afterStartMarkerIndex);
|
||||
|
||||
if (endMarkerIndex < 0) {
|
||||
return Component.text()
|
||||
.append(ofLegacyTextRaw(formattedTemplate.replace(startMarker, "")))
|
||||
.append(Component.text(literalMessage))
|
||||
.build();
|
||||
}
|
||||
|
||||
final String prefixText = formattedTemplate.substring(0, startMarkerIndex);
|
||||
final String suffixText = formattedTemplate.substring(endMarkerIndex + endMarker.length());
|
||||
|
||||
return Component.text()
|
||||
.append(ofLegacyTextRaw(prefixText))
|
||||
.append(Component.text(literalMessage))
|
||||
.append(ofLegacyTextRaw(suffixText))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts section-sign legacy color prefixes to ampersands so formatting tokens are displayed
|
||||
* as plain text instead of being applied.
|
||||
*
|
||||
* <p>This is intended for message inputs when a sender lacks permission to use chat colors.
|
||||
*/
|
||||
public static @NotNull String literalizeLegacyColorCodes(@NotNull String text) {
|
||||
return text.replace('\u00A7', '&');
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,13 @@ import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.gmail.nossr50.config.experience.ExperienceConfig;
|
||||
import com.gmail.nossr50.datatypes.player.PlayerProfile;
|
||||
import com.gmail.nossr50.datatypes.player.UniqueDataType;
|
||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
|
||||
import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@ -288,4 +294,29 @@ class DiminishedReturnsCacheTest {
|
||||
"state with a future latestExpiryTimeMillis should report active entries");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PlayerProfileConstructorIntegration {
|
||||
|
||||
@Test
|
||||
void mapBasedConstructorShouldUseUuidCacheState() {
|
||||
// Given - a player UUID with an existing cached DR state
|
||||
final UUID playerUuid = UUID.randomUUID();
|
||||
final DiminishedReturnsState cachedState = DiminishedReturnsCache.getOrCreate(playerUuid);
|
||||
|
||||
final Map<PrimarySkillType, Integer> levelData = new EnumMap<>(PrimarySkillType.class);
|
||||
final Map<PrimarySkillType, Float> xpData = new EnumMap<>(PrimarySkillType.class);
|
||||
final Map<SuperAbilityType, Integer> cooldownData = new EnumMap<>(SuperAbilityType.class);
|
||||
final Map<UniqueDataType, Integer> uniqueData = new EnumMap<>(UniqueDataType.class);
|
||||
|
||||
// When - profile is created using the map-based constructor used by DB loaders
|
||||
final PlayerProfile loadedProfile = new PlayerProfile("TestPlayer", playerUuid,
|
||||
levelData, xpData, cooldownData, 0, uniqueData, null);
|
||||
loadedProfile.registerXpGain(PrimarySkillType.MINING, 50F);
|
||||
|
||||
// Then - cached state should reflect the XP registered by that loaded profile
|
||||
assertEquals(50F, cachedState.getRegisteredXpGain(PrimarySkillType.MINING),
|
||||
"loaded profiles with UUID must use the UUID-cached DR state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
package com.gmail.nossr50.util.text;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
@ -15,17 +20,115 @@ import org.junit.jupiter.api.Test;
|
||||
*/
|
||||
class TextUtilsTest {
|
||||
|
||||
@Test
|
||||
void testColorizeText() {
|
||||
String inputText = "&4This text should be red.";
|
||||
@Nested
|
||||
class ColorizeText {
|
||||
|
||||
/*
|
||||
* If this method raises an exception, we know Adventure is not set up correctly.
|
||||
* This will also make the test fail and warn us about it.
|
||||
*/
|
||||
TextComponent component = TextUtils.colorizeText(inputText);
|
||||
@Test
|
||||
void parsesLegacyColorCodes() {
|
||||
// Given
|
||||
final String inputText = "&4This text should be red.";
|
||||
|
||||
String message = "Looks like Adventure is not working correctly. We likely need to update our dependency!";
|
||||
Assertions.assertEquals(NamedTextColor.DARK_RED, component.color(), message);
|
||||
// When
|
||||
final TextComponent component = TextUtils.colorizeText(inputText);
|
||||
|
||||
// Then
|
||||
final String failureMessage = "Looks like Adventure is not working correctly. We likely need to update our dependency!";
|
||||
assertThat(component.color()).withFailMessage(failureMessage)
|
||||
.isEqualTo(NamedTextColor.DARK_RED);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class LiteralizeLegacyColorCodes {
|
||||
|
||||
@Test
|
||||
void keepsAmpersandFormattingCodesLiteral() {
|
||||
// Given
|
||||
final String rawMessage = "&aHello &lWorld&r!";
|
||||
|
||||
// When
|
||||
final String literalizedMessage = TextUtils.literalizeLegacyColorCodes(rawMessage);
|
||||
|
||||
// Then
|
||||
assertThat(literalizedMessage).isEqualTo("&aHello &lWorld&r!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsSectionSignFormattingCodesToAmpersands() {
|
||||
// Given
|
||||
final String rawMessage = "\u00A7aHello \u00A7lWorld\u00A7r!";
|
||||
|
||||
// When
|
||||
final String literalizedMessage = TextUtils.literalizeLegacyColorCodes(rawMessage);
|
||||
|
||||
// Then
|
||||
assertThat(literalizedMessage).isEqualTo("&aHello &lWorld&r!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void preservesHexTokensAndNormalAmpersandsAsLiteralText() {
|
||||
// Given
|
||||
final String rawMessage = "Color: AB9FHello & welcome";
|
||||
|
||||
// When
|
||||
final String literalizedMessage = TextUtils.literalizeLegacyColorCodes(rawMessage);
|
||||
|
||||
// Then
|
||||
assertThat(literalizedMessage).isEqualTo("Color: AB9FHello & welcome");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InsertLiteralTextAtMarkers {
|
||||
|
||||
@Test
|
||||
void preservesLiteralColorTokensInInsertedMessage() {
|
||||
// Given
|
||||
final String startMarker = "\u0002START\u0003";
|
||||
final String endMarker = "\u0002END\u0003";
|
||||
final String template = "\u00A7aPrefix " + startMarker + endMarker + " \u00A7bSuffix";
|
||||
final String literalMessage = "&cMessage &lTokens";
|
||||
|
||||
// When
|
||||
final TextComponent formattedComponent = TextUtils.insertLiteralTextAtMarkers(template,
|
||||
startMarker, endMarker, literalMessage);
|
||||
final String aggregatedContent = aggregateTextContent(formattedComponent);
|
||||
|
||||
// Then
|
||||
assertThat(aggregatedContent).isEqualTo("Prefix &cMessage &lTokens Suffix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyReplacesTextBetweenMarkerBoundaries() {
|
||||
// Given
|
||||
final String startMarker = "\u0002START\u0003";
|
||||
final String endMarker = "\u0002END\u0003";
|
||||
final String template = "Header START " + startMarker + endMarker + " END Footer";
|
||||
final String literalMessage = "payload";
|
||||
|
||||
// When
|
||||
final TextComponent formattedComponent = TextUtils.insertLiteralTextAtMarkers(template,
|
||||
startMarker, endMarker, literalMessage);
|
||||
final String aggregatedContent = aggregateTextContent(formattedComponent);
|
||||
|
||||
// Then
|
||||
assertThat(aggregatedContent).isEqualTo("Header START payload END Footer");
|
||||
}
|
||||
|
||||
private String aggregateTextContent(final Component rootComponent) {
|
||||
final List<String> parts = new ArrayList<>();
|
||||
collectTextContent(rootComponent, parts);
|
||||
return String.join("", parts);
|
||||
}
|
||||
|
||||
private void collectTextContent(final Component component, final List<String> parts) {
|
||||
if (component instanceof TextComponent textComponent) {
|
||||
parts.add(textComponent.content());
|
||||
}
|
||||
|
||||
for (final Component childComponent : component.children()) {
|
||||
collectTextContent(childComponent, parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user