Compare commits

...

4 Commits

Author SHA1 Message Date
Thiago Gebrim
0979af326d changes the dev asked for 2024-04-14 11:41:50 -03:00
Thiago Gebrim
b34e658a69
Merge branch 'mcMMO-Dev:master' into master 2024-04-14 11:07:05 -03:00
nossr50
1cac6b1165 2.2.006 2024-04-13 15:18:12 -07:00
nossr50
c0952a2ba3 Add support for hex color codes in locale 2024-04-13 12:58:52 -07:00
11 changed files with 187 additions and 47 deletions

View File

@ -1,12 +1,25 @@
Version 2.2.006 Version 2.2.006
Added new config custom_item_support.yml
Added support for hex color codes in the locale file, uses the format &#RRGGBB (see notes)
Added setting to disable repair on items with custom models, this is not on by default
Fixed a bug where sometimes the locale name of a skill would get lowercased
Fixed a bug where JSON text components did not get colored properly some of the time
Fixed en_US locale string 'Commands.Skill.Leaderboard' not being colored properly
Fixed skill commands incorrectly telling you to use their locale name, this isn't currently possible
Updated outdated wiki URLs in commands to point to the new wiki Updated outdated wiki URLs in commands to point to the new wiki
Removed the msg about skills being migrated to a new system when using /mmoinfo command Removed the msg about skills being migrated to a new system when using /mmoinfo command
Added new config custom_item_support.yml
Added setting to disable repair on items with custom models, this is not on by default
Added new locale entry 'Anvil.Repair.Reject.CustomModelData' Added new locale entry 'Anvil.Repair.Reject.CustomModelData'
Added new locale entry 'Anvil.Salvage.Reject.CustomModelData' Added new locale entry 'Anvil.Salvage.Reject.CustomModelData'
Updated en_US locale entry 'JSON.DescriptionHeader'
(API/Codebase) Added some util methods and basic unit tests for LocaleLoader
NOTES: NOTES:
Hex Color support in locale files is here!
The hex color code format for the locale files is &#RRGGBB
An example entry applying yellow as a hex color code would look like this:
Axes.SkillName=&#FFFF00Axes
In general, JSON locale entries will either not work with hex color codes or will have the color code stripped out, in the future I will add support for the JSON components to use hex colors from the locale
Let me know in detail what kind of support you'd like to see in mcMMO regarding custom items, I'm open to suggestions. Let me know in detail what kind of support you'd like to see in mcMMO regarding custom items, I'm open to suggestions.
This update adds a new config file to allow server owners to disable repair or salvage on items with custom models, This update adds a new config file to allow server owners to disable repair or salvage on items with custom models,
This prevention mechanism is not enabled by default, change the settings in custom_item_support.yml if you want to enable it. This prevention mechanism is not enabled by default, change the settings in custom_item_support.yml if you want to enable it.

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gmail.nossr50.mcMMO</groupId> <groupId>com.gmail.nossr50.mcMMO</groupId>
<artifactId>mcMMO</artifactId> <artifactId>mcMMO</artifactId>
<version>2.2.006-SNAPSHOT</version> <version>2.2.006</version>
<name>mcMMO</name> <name>mcMMO</name>
<url>https://github.com/mcMMO-Dev/mcMMO</url> <url>https://github.com/mcMMO-Dev/mcMMO</url>
<scm> <scm>
@ -259,6 +259,12 @@
</repositories> </repositories>
<dependencies> <dependencies>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 --> <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>

View File

@ -106,7 +106,7 @@ public class AxesCommand extends SkillCommand {
@Override @Override
protected List<Component> getTextComponents(Player player) { protected List<Component> getTextComponents(Player player) {
List<Component> textComponents = new ArrayList<>(); final List<Component> textComponents = new ArrayList<>();
TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkillType.AXES); TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkillType.AXES);

View File

@ -31,7 +31,6 @@ import java.util.Locale;
public abstract class SkillCommand implements TabExecutor { public abstract class SkillCommand implements TabExecutor {
protected PrimarySkillType skill; protected PrimarySkillType skill;
private final String skillName;
protected DecimalFormat percent = new DecimalFormat("##0.00%"); protected DecimalFormat percent = new DecimalFormat("##0.00%");
protected DecimalFormat decimal = new DecimalFormat("##0.00"); protected DecimalFormat decimal = new DecimalFormat("##0.00");
@ -40,7 +39,6 @@ public abstract class SkillCommand implements TabExecutor {
public SkillCommand(PrimarySkillType skill) { public SkillCommand(PrimarySkillType skill) {
this.skill = skill; this.skill = skill;
skillName = mcMMO.p.getSkillTools().getLocalizedSkillName(skill);
skillGuideCommand = new SkillGuideCommand(skill); skillGuideCommand = new SkillGuideCommand(skill);
} }
@ -76,7 +74,8 @@ public abstract class SkillCommand implements TabExecutor {
permissionsCheck(player); permissionsCheck(player);
dataCalculations(player, skillValue); dataCalculations(player, skillValue);
sendSkillCommandHeader(player, mcMMOPlayer, (int) skillValue); sendSkillCommandHeader(mcMMO.p.getSkillTools().getLocalizedSkillName(skill),
player, mcMMOPlayer, (int) skillValue);
//Make JSON text components //Make JSON text components
List<Component> subskillTextComponents = getTextComponents(player); List<Component> subskillTextComponents = getTextComponents(player);
@ -139,15 +138,14 @@ public abstract class SkillCommand implements TabExecutor {
} }
} }
player.sendMessage(LocaleLoader.getString("Guides.Available", skillName, skillName.toLowerCase(Locale.ENGLISH))); final String skillName = mcMMO.p.getSkillTools().getLocalizedSkillName(skill);
player.sendMessage(LocaleLoader.getString("Guides.Available",
skillName,
skillName.toLowerCase(Locale.ENGLISH)));
} }
private void sendSkillCommandHeader(Player player, McMMOPlayer mcMMOPlayer, int skillValue) { private void sendSkillCommandHeader(String skillName, Player player, McMMOPlayer mcMMOPlayer, int skillValue) {
ChatColor hd1 = ChatColor.DARK_AQUA; // send header
ChatColor c1 = ChatColor.GOLD;
ChatColor c2 = ChatColor.RED;
player.sendMessage(LocaleLoader.getString("Skills.Overhaul.Header", skillName)); player.sendMessage(LocaleLoader.getString("Skills.Overhaul.Header", skillName));
if(!SkillTools.isChildSkill(skill)) if(!SkillTools.isChildSkill(skill))

View File

@ -16,6 +16,8 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class LocaleLoader { public final class LocaleLoader {
private static final String BUNDLE_ROOT = "com.gmail.nossr50.locale.locale"; private static final String BUNDLE_ROOT = "com.gmail.nossr50.locale.locale";
@ -24,6 +26,9 @@ public final class LocaleLoader {
private static ResourceBundle bundle = null; private static ResourceBundle bundle = null;
private static ResourceBundle filesystemBundle = null; private static ResourceBundle filesystemBundle = null;
private static ResourceBundle enBundle = null; private static ResourceBundle enBundle = null;
// Matches the pattern &#RRGGBB
private static final Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})");
private static final Pattern minecraftHexPattern = Pattern.compile("§x(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])");
private LocaleLoader() {} private LocaleLoader() {}
@ -48,8 +53,6 @@ public final class LocaleLoader {
return formatString(rawMessage, messageArguments); return formatString(rawMessage, messageArguments);
} }
//TODO: Remove this hacky crap with something better later
/** /**
* Gets the appropriate TextComponent representation of a formatted string from the Locale files. * Gets the appropriate TextComponent representation of a formatted string from the Locale files.
* *
@ -258,9 +261,14 @@ public final class LocaleLoader {
@NotNull @NotNull
private static String getExamples() { private static String getExamples() {
return """ return """
This.Is.An.Example.Put.Locale.Keys.Here.One=&aExample text using hex color codes This.Is.An.Example.Put.Locale.Keys.Here.One=&aExample text using simplified minecraft color codes
This.Is.An.Example.Put.Locale.Keys.Here.Two=[[DARK_AQUA]]Example text using our own color codes This.Is.An.Example.Put.Locale.Keys.Here.Two=[[DARK_AQUA]]Example text using our own color codes
This.Is.An.Example.Put.Locale.Keys.Here.Three=Example text with no colors This.Is.An.Example.Put.Locale.Keys.Here.Three=Example text with no colors
This.Is.An.Example.Put.Locale.Keys.Here.Four=&#FF0000Example text with red color hex code
This.Is.An.Example.Put.Locale.Keys.Here.Five=&#00FF00Example text with green color hex code
This.Is.An.Example.Put.Locale.Keys.Here.Six=&#0000FFExample text with blue color hex code
This.Is.An.Example.Put.Locale.Keys.Here.Seven=&#FFFF00Example text with yellow color hex code
This.Is.An.Example.Put.Locale.Keys.Here.Eight=&lExample text with bold using simplified minecraft color codes
"""; """;
} }
@ -304,6 +312,10 @@ public final class LocaleLoader {
} }
public static String addColors(String input) { public static String addColors(String input) {
// First check for hex color codes and insert them
input = translateHexColorCodes(input);
// Then check for our own color codes
input = input.replaceAll("\\Q[[BLACK]]\\E", ChatColor.BLACK.toString()); input = input.replaceAll("\\Q[[BLACK]]\\E", ChatColor.BLACK.toString());
input = input.replaceAll("\\Q[[DARK_BLUE]]\\E", ChatColor.DARK_BLUE.toString()); input = input.replaceAll("\\Q[[DARK_BLUE]]\\E", ChatColor.DARK_BLUE.toString());
input = input.replaceAll("\\Q[[DARK_GREEN]]\\E", ChatColor.DARK_GREEN.toString()); input = input.replaceAll("\\Q[[DARK_GREEN]]\\E", ChatColor.DARK_GREEN.toString());
@ -327,6 +339,7 @@ public final class LocaleLoader {
input = input.replaceAll("\\Q[[MAGIC]]\\E", ChatColor.MAGIC.toString()); input = input.replaceAll("\\Q[[MAGIC]]\\E", ChatColor.MAGIC.toString());
input = input.replaceAll("\\Q[[RESET]]\\E", ChatColor.RESET.toString()); input = input.replaceAll("\\Q[[RESET]]\\E", ChatColor.RESET.toString());
// Then check for the typical color codes
input = input.replaceAll("\\Q&0\\E", ChatColor.BLACK.toString()); input = input.replaceAll("\\Q&0\\E", ChatColor.BLACK.toString());
input = input.replaceAll("\\Q&1\\E", ChatColor.DARK_BLUE.toString()); input = input.replaceAll("\\Q&1\\E", ChatColor.DARK_BLUE.toString());
input = input.replaceAll("\\Q&2\\E", ChatColor.DARK_GREEN.toString()); input = input.replaceAll("\\Q&2\\E", ChatColor.DARK_GREEN.toString());
@ -352,4 +365,52 @@ public final class LocaleLoader {
return input; return input;
} }
/**
* Translates hex color codes to the appropriate Minecraft color codes.
* <p>
* Hex color codes are in the format of &#RRGGBB
* Minecraft color codes are in the format of §x§R§R§G§G§B§B
* Where R, G, and B are the red, green, and blue values respectively.
* The §x is a special character that tells Minecraft to use the following color codes as hex values.
* The §R§R is the red value, the §G§G is the green value, and the §B§B is the blue value.
* Example: §x§R§R§G§G§B§B is the equivalent of the hex color code &#RRGGBB
* </p>
* @param messageWithHex The message with hex color codes to translate
* @return The message with the hex color codes translated to Minecraft color codes
*/
public static String translateHexColorCodes(String messageWithHex) {
if(messageWithHex == null) {
return null;
}
final Matcher matcher = hexPattern.matcher(messageWithHex);
final StringBuilder buffer = new StringBuilder(messageWithHex.length() + 4 * 8);
while (matcher.find()) {
String group = matcher.group(1);
String hexEquivalent = "§x" +
"§" + group.charAt(0) + "§" + group.charAt(1) +
"§" + group.charAt(2) + "§" + group.charAt(3) +
"§" + group.charAt(4) + "§" + group.charAt(5);
matcher.appendReplacement(buffer, hexEquivalent);
}
return matcher.appendTail(buffer).toString();
}
// Method to reverse the transformation from Minecraft color codes to hex codes
public static String reverseTranslateHexColorCodes(String minecraftColorString) {
// Matches the Minecraft color pattern: §x§R§R§G§G§B§B
Matcher matcher = minecraftHexPattern.matcher(minecraftColorString);
StringBuilder buffer = new StringBuilder();
while (matcher.find()) {
String hexColor = "#" +
matcher.group(1).substring(1) + matcher.group(2).substring(1) +
matcher.group(3).substring(1) + matcher.group(4).substring(1) +
matcher.group(5).substring(1) + matcher.group(6).substring(1);
matcher.appendReplacement(buffer, "&" + hexColor);
}
matcher.appendTail(buffer);
return buffer.toString();
}
} }

View File

@ -798,8 +798,7 @@ public class HerbalismManager extends SkillManager {
return false; return false;
} }
// Check for seeds in both the main hand and off-hand if (!playerInventory.contains(seedStack)) {
if (!(playerInventory.containsAtLeast(seedStack, 1) || playerInventory.getItemInOffHand().getType() == seed)) {
NotificationManager.sendPlayerInformation(player, NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyItemString(seed)); NotificationManager.sendPlayerInformation(player, NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyItemString(seed));
return false; return false;
} }
@ -808,20 +807,13 @@ public class HerbalismManager extends SkillManager {
return false; return false;
} }
// Check the SubSkillEvent before removing items
if (EventUtils.callSubSkillBlockEvent(player, SubSkillType.HERBALISM_GREEN_THUMB, blockState.getBlock()).isCancelled()) { if (EventUtils.callSubSkillBlockEvent(player, SubSkillType.HERBALISM_GREEN_THUMB, blockState.getBlock()).isCancelled()) {
return false; return false;
} else { } else {
// Remove seed from the appropriate inventory slot (main hand or off-hand) playerInventory.removeItem(new ItemStack(seed, 1));
if (playerInventory.getItemInMainHand().getType() == seed) {
playerInventory.removeItem(new ItemStack(seed, 1));
} else if (playerInventory.getItemInOffHand().getType() == seed) {
playerInventory.getItemInOffHand().setAmount(playerInventory.getItemInOffHand().getAmount() - 1);
}
player.updateInventory(); // Needed until replacement available player.updateInventory();
// Play sound
SoundManager.sendSound(player, player.getLocation(), SoundType.ITEM_CONSUMED); SoundManager.sendSound(player, player.getLocation(), SoundType.ITEM_CONSUMED);
return true; return true;
} }

View File

@ -42,8 +42,8 @@ public final class CommandRegistrationManager {
command.setDescription(LocaleLoader.getString("Commands.Description.Skill", StringUtils.getCapitalized(localizedName))); command.setDescription(LocaleLoader.getString("Commands.Description.Skill", StringUtils.getCapitalized(localizedName)));
command.setPermission("mcmmo.commands." + commandName); command.setPermission("mcmmo.commands." + commandName);
command.setPermissionMessage(permissionsMessage); command.setPermissionMessage(permissionsMessage);
command.setUsage(LocaleLoader.getString("Commands.Usage.0", localizedName)); command.setUsage(LocaleLoader.getString("Commands.Usage.0", commandName));
command.setUsage(command.getUsage() + "\n" + LocaleLoader.getString("Commands.Usage.2", localizedName, "?", "[" + LocaleLoader.getString("Commands.Usage.Page") + "]")); command.setUsage(command.getUsage() + "\n" + LocaleLoader.getString("Commands.Usage.2", commandName, "?", "[" + LocaleLoader.getString("Commands.Usage.Page") + "]"));
switch (skill) { switch (skill) {
case ACROBATICS: case ACROBATICS:

View File

@ -349,8 +349,7 @@ public class SkillTools {
* @return the localized name for a {@link PrimarySkillType} * @return the localized name for a {@link PrimarySkillType}
*/ */
public String getLocalizedSkillName(PrimarySkillType primarySkillType) { public String getLocalizedSkillName(PrimarySkillType primarySkillType) {
//TODO: Replace with current impl return LocaleLoader.getString(StringUtils.getCapitalized(primarySkillType.toString()) + ".SkillName");
return StringUtils.getCapitalized(LocaleLoader.getString(StringUtils.getCapitalized(primarySkillType.toString()) + ".SkillName"));
} }
public boolean doesPlayerHaveSkillPermission(Player player, PrimarySkillType primarySkillType) { public boolean doesPlayerHaveSkillPermission(Player player, PrimarySkillType primarySkillType) {

View File

@ -19,6 +19,7 @@ import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -235,8 +236,8 @@ public class TextComponentFactory {
} }
private static Component getSubSkillTextComponent(Player player, SubSkillType subSkillType) { private static Component getSubSkillTextComponent(Player player, SubSkillType subSkillType) {
//Get skill name //Get skill name and strip it of color
String skillName = subSkillType.getLocaleName(); final String skillName = ChatColor.stripColor(subSkillType.getLocaleName());
boolean skillUnlocked = RankUtils.hasUnlockedSubskill(player, subSkillType); boolean skillUnlocked = RankUtils.hasUnlockedSubskill(player, subSkillType);
@ -306,7 +307,7 @@ public class TextComponentFactory {
* @return the hover basecomponent object for this subskill * @return the hover basecomponent object for this subskill
*/ */
private static Component getSubSkillHoverEventJSON(AbstractSubSkill abstractSubSkill, Player player) { private static Component getSubSkillHoverEventJSON(AbstractSubSkill abstractSubSkill, Player player) {
String skillName = abstractSubSkill.getNiceName(); String skillName = ChatColor.stripColor(abstractSubSkill.getNiceName());
/* /*
* Hover Event BaseComponent color table * Hover Event BaseComponent color table
@ -399,7 +400,8 @@ public class TextComponentFactory {
} }
private static Component getSubSkillHoverEventJSON(SubSkillType subSkillType, Player player) { private static Component getSubSkillHoverEventJSON(SubSkillType subSkillType, Player player) {
String skillName = subSkillType.getLocaleName(); // Get skill name and strip it of color
String skillName = ChatColor.stripColor(subSkillType.getLocaleName());
/* /*
* Hover Event BaseComponent color table * Hover Event BaseComponent color table
@ -433,11 +435,9 @@ public class TextComponentFactory {
} }
componentBuilder.append(Component.newline()); componentBuilder.append(Component.newline());
componentBuilder.append(Component.text(LocaleLoader.getString("JSON.DescriptionHeader"))); componentBuilder.append(Component.text(LocaleLoader.getString("JSON.DescriptionHeader")).color(ccDescriptionHeader));
componentBuilder.color(ccDescriptionHeader);
componentBuilder.append(Component.newline()); componentBuilder.append(Component.newline());
componentBuilder.append(Component.text(subSkillType.getLocaleDescription())); componentBuilder.append(Component.text(ChatColor.stripColor(subSkillType.getLocaleDescription())).color(ccDescription));
componentBuilder.color(ccDescription);
} }
return componentBuilder.build(); return componentBuilder.build();

View File

@ -1,10 +1,5 @@
#I'm going to try to normalize our locale file, forgive the mess for now.
# TODO: Update JSON to support hex
#DO NOT USE COLOR CODES IN THE JSON KEYS
#COLORS ARE DEFINED IN advanced.yml IF YOU WISH TO CHANGE THEM
JSON.Rank=Rank JSON.Rank=Rank
JSON.DescriptionHeader=Description JSON.DescriptionHeader=Description:
JSON.JWrapper.Header=Details JSON.JWrapper.Header=Details
JSON.Type.Passive=Passive JSON.Type.Passive=Passive
JSON.Type.Active=Active JSON.Type.Active=Active
@ -784,7 +779,7 @@ Commands.XPBar.Reset=&6XP Bar settings for mcMMO have been reset.
Commands.XPBar.SettingChanged=&6XP Bar setting for &a{0}&6 is now set to &a{1} Commands.XPBar.SettingChanged=&6XP Bar setting for &a{0}&6 is now set to &a{1}
Commands.Skill.Invalid=That is not a valid skillname! Commands.Skill.Invalid=That is not a valid skillname!
Commands.Skill.ChildSkill=Child skills are not valid for this command! Commands.Skill.ChildSkill=Child skills are not valid for this command!
Commands.Skill.Leaderboard=--mcMMO &9{0}&e Leaderboard-- Commands.Skill.Leaderboard=-&e-mcMMO &9{0}&e Leaderboard--
Commands.SkillInfo=&a- View detailed information about a skill Commands.SkillInfo=&a- View detailed information about a skill
Commands.Stats=&a- View your mcMMO stats Commands.Stats=&a- View your mcMMO stats
Commands.ToggleAbility=&a- Toggle ability activation with right click Commands.ToggleAbility=&a- Toggle ability activation with right click

View File

@ -0,0 +1,76 @@
package com.gmail.nossr50.locale;
import org.bukkit.ChatColor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class LocaleLoaderTest {
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@ParameterizedTest
@ValueSource(strings = {"§cTest", "[[RED]]Test"})
void addColorsShouldAddColorRed(String testString) {
// When
final String result = LocaleLoader.addColors(testString);
// Then
assertThat(result).isEqualTo(ChatColor.RED + "Test");
}
// hex colors test
@Test
void translateHexColorCodesShouldAddRed() {
// Given
final String testString = "&#FF0000Test";
// When
final String result = LocaleLoader.translateHexColorCodes(testString);
// Then
final String expectedResult = "§x§F§F§0§0§0§0Test";
assertThat(result).isEqualTo(expectedResult);
}
@Test
void reverseTranslateHexColorCodesShouldRemoveRed() {
// Given
final String testString = "§x§F§F§0§0§0§0Test";
// When
final String result = LocaleLoader.reverseTranslateHexColorCodes(testString);
// Then
final String expectedResult = "&#FF0000Test";
assertThat(result).isEqualTo(expectedResult);
}
@ParameterizedTest
@ValueSource(strings = {"&#FF0000Te&#0000FFst", "&#FF0000Te[[RED]]st", "[[BLUE]]Te[[RED]]st", "§9Te§cst"})
void addColorsShouldAddRedAndBlue(String testString) {
// When
final String result = LocaleLoader.addColors(testString);
// TODO: Hacky, clean this up sometime in the future
// Then
// All legal representations of the same string
final List<String> expectedResults = List.of("§x§F§F§0§0§0§0Te§x§0§0§0§0§F§Fst",
"§x§F§F§0§0§0§0Te§x§0§0§0§0§F§Fst",
"§x§F§F§0§0§0§0Te§cst",
"§9Te§cst");
assertThat(expectedResults).contains(result);
}
}