diff --git a/build.gradle b/build.gradle index 2668680..01c3ce1 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ ext.modid = "titlechanger" ext.modname = "Title Changer" ext.moddescription = "Changes the Minecraft window title." ext.modauthors = "maxwell-lt" -ext.modversion = "2.2" +ext.modversion = "3.1.0" ext.modpackage = "maxwell_lt.titlechanger" ext.modarchive = "titlechanger" @@ -36,7 +36,7 @@ minecraft { // stable_# Stables are built at the discretion of the MCP team. // Use non-default mappings at your own risk. they may not always work. // Simply re-run your setup task after changing the mappings to update your workspace. - mappings channel: 'snapshot', version: '20190719-1.14.3' + mappings channel: 'snapshot', version: '20200622-1.15.1' // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') @@ -54,7 +54,7 @@ minecraft { property 'forge.logging.console.level', 'debug' mods { - examplemod { + titlechanger { source sourceSets.main } } @@ -70,7 +70,7 @@ minecraft { property 'forge.logging.console.level', 'debug' mods { - examplemod { + titlechanger { source sourceSets.main } } @@ -88,7 +88,7 @@ minecraft { args '--mod', 'titlechanger', '--all', '--output', file('src/generated/resources/') mods { - examplemod { + titlechanger { source sourceSets.main } } @@ -96,11 +96,21 @@ minecraft { } } +repositories { + mavenCentral() + jcenter() +} + dependencies { // Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed // that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied. // The userdev artifact is a special name and will get all sorts of transformations applied to it. - minecraft 'net.minecraftforge:forge:1.14.4-28.1.0' + minecraft 'net.minecraftforge:forge:1.15.2-31.2.22' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' + testCompile 'org.mockito:mockito-core:2.+' + testCompile 'org.assertj:assertj-core:3.11.1' // You may put jars on which you depend on in ./libs or you may define them like so.. // compile "some.group:artifact:version:classifier" @@ -122,6 +132,10 @@ dependencies { } +test { + useJUnitPlatform() +} + // Example for how to get properties into the manifest for reading by the runtime.. jar { manifest { diff --git a/src/main/java/maxwell_lt/titlechanger/ClientProxy.java b/src/main/java/maxwell_lt/titlechanger/ClientProxy.java index c844c6a..8e8f1a9 100644 --- a/src/main/java/maxwell_lt/titlechanger/ClientProxy.java +++ b/src/main/java/maxwell_lt/titlechanger/ClientProxy.java @@ -8,8 +8,8 @@ public class ClientProxy implements IProxy { @Override public void init() { - MinecraftForge.EVENT_BUS.register(new ReplaceTitle()); - ReplaceTitle.Replace(); + ReplaceTitle replaceTitle = new ReplaceTitle(); + MinecraftForge.EVENT_BUS.register(replaceTitle); } @Override diff --git a/src/main/java/maxwell_lt/titlechanger/Config.java b/src/main/java/maxwell_lt/titlechanger/Config.java deleted file mode 100644 index 7f8be74..0000000 --- a/src/main/java/maxwell_lt/titlechanger/Config.java +++ /dev/null @@ -1,68 +0,0 @@ -package maxwell_lt.titlechanger; - -import com.electronwill.nightconfig.core.file.CommentedFileConfig; -import com.electronwill.nightconfig.core.io.WritingMode; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.config.ModConfig; -import net.minecraftforge.common.ForgeConfigSpec; - -import java.nio.file.Path; - -public class Config { - - private static final String CATEGORY_GENERAL = "general"; - - private static final ForgeConfigSpec.Builder GENERAL_BUILDER = new ForgeConfigSpec.Builder(); - - public static ForgeConfigSpec.ConfigValue WINDOW_TITLE; - public static ForgeConfigSpec.ConfigValue TIME_FORMAT; - public static ForgeConfigSpec.ConfigValue PLACEHOLDER_TEXT; - - public static ForgeConfigSpec GENERAL_CONFIG; - - static { - GENERAL_BUILDER.comment("General Configuration").push(CATEGORY_GENERAL); - WINDOW_TITLE = GENERAL_BUILDER - .comment("The title of the Minecraft window. Leave blank to keep the default window title for your version of Minecraft." + - "\nSome special values that will be inserted at runtime:" + - "\n%mcver% -> The current Minecraft version" + - "\n%modcount% -> Number of loaded mods" + - "\n%time% -> Current system time" + - "\n%playerloc% -> Location of the player, if available" + - "\n%chunk% -> Current chunk, if available" + - "\n%biome% -> Current biome, if available" + - "\n%score% -> Current score of the player, if available\n") - .define("windowtitle", ""); - TIME_FORMAT = GENERAL_BUILDER - .comment("Format to display time in. See http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns") - .define("timeformat", "h:mm a"); - PLACEHOLDER_TEXT = GENERAL_BUILDER - .comment("String to use as placeholder when a value is unavailable.") - .define("placeholdertext", "--"); - GENERAL_BUILDER.pop(); - - GENERAL_CONFIG = GENERAL_BUILDER.build(); - } - - public static void loadConfig(ForgeConfigSpec spec, Path path) { - - final CommentedFileConfig configData = CommentedFileConfig.builder(path) - .sync() - .autosave() - .writingMode(WritingMode.REPLACE) - .build(); - - configData.load(); - spec.setConfig(configData); - } - - @SubscribeEvent - public static void onLoad(final ModConfig.Loading configEvent) { - - } - - @SubscribeEvent - public static void onReload(final ModConfig.ConfigReloading configEvent) { - - } -} diff --git a/src/main/java/maxwell_lt/titlechanger/ReplaceTitle.java b/src/main/java/maxwell_lt/titlechanger/ReplaceTitle.java index 2c34dec..f18e486 100644 --- a/src/main/java/maxwell_lt/titlechanger/ReplaceTitle.java +++ b/src/main/java/maxwell_lt/titlechanger/ReplaceTitle.java @@ -1,118 +1,132 @@ package maxwell_lt.titlechanger; +import maxwell_lt.titlechanger.config.Config; +import maxwell_lt.titlechanger.util.InfoRetriever; import net.minecraft.client.Minecraft; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -import net.minecraft.world.biome.Biome; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.loading.FMLLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; + +import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; -import static org.lwjgl.glfw.GLFW.glfwSetWindowTitle; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import static org.lwjgl.glfw.GLFW.*; public class ReplaceTitle { + public static final String VERSION = "%mcver%"; + public static final String MOD_COUNT = "%modcount%"; + public static final String TIME = "%time%"; + public static final String PLAYER_LOCATION = "%playerloc%"; + public static final String SCORE = "%score%"; + public static final String BIOME = "%biome%"; + public static final String CHUNK = "%chunk%"; + private static final Logger LOGGER = LogManager.getLogger(); + private final InfoRetriever infoRetriever; - @SubscribeEvent(priority = EventPriority.LOWEST) - public void clientTick(TickEvent.ClientTickEvent e) { - Replace(); - } + private final String mcVersion; + private final String modCount; + private final DateFormat timeFormatter; + private final Map> transformations; - public static void Replace() { - if (!Config.WINDOW_TITLE.get().equals("")) { - glfwSetWindowTitle(Minecraft.getInstance().mainWindow.getHandle(), processText(Config.WINDOW_TITLE.get())); - } - } + private PlayerEntity playerEntity; + private World world; - private static String processText(String formatString) { - String mcVersion = Minecraft.getInstance().getVersion(); - String modCount = Integer.toString(FMLLoader.getLoadingModList().getMods().size()); - String time = new SimpleDateFormat(Config.TIME_FORMAT.get()).format(new Date()).toString(); - String location = getPlayerLocationOrPlaceholder(); - String score = getPlayerScoreOrPlaceholder(); - String biome = getPlayerBiomeOrPlaceholder(); - String chunk = getPlayerChunkOrPlaceholder(); - - formatString = formatString.replaceAll("%mcver%", mcVersion); - formatString = formatString.replaceAll("%modcount%", modCount); - formatString = formatString.replaceAll("%time%", time); - formatString = formatString.replaceAll("%playerloc%", location); - formatString = formatString.replaceAll("%score%", score); - formatString = formatString.replaceAll("%biome%", biome); - formatString = formatString.replaceAll("%chunk%", chunk); + public ReplaceTitle() { + this.infoRetriever = new InfoRetriever(Config.getPlaceholderText()); + this.mcVersion = Minecraft.getInstance().getVersion(); + this.modCount = Integer.toString(FMLLoader.getLoadingModList().getMods().size()); + this.timeFormatter = new SimpleDateFormat(Config.getTimeFormat()); - return formatString; + this.transformations = generateTransformationMap(); } - private static String getPlayerBiomeOrPlaceholder() { - PlayerEntity playerEntity = null; - World world = null; - try { - playerEntity = TitleChanger.proxy.getClientPlayer(); - world = TitleChanger.proxy.getClientWorld(); - } catch (IllegalStateException e) { - LOGGER.debug("Attempted to call proxy.getClientPlayer() in serverside code."); + /** + * Builds a map of required string replacements based on the config + *

+ * This prevents unneeded information retrieval methods from running every client tick. Because the map contains + * suppliers, the output values can vary based on current conditions. + * + * @return Map with patterns and replacement suppliers + */ + private Map> generateTransformationMap() { + Map> transformationMap = new HashMap<>(); + if (Config.getWindowTitle().contains(VERSION)) { + transformationMap.put(VERSION, () -> mcVersion); } - if (playerEntity != null && world != null) { - Biome biome = world.getBiome(new BlockPos(playerEntity.posX, playerEntity.posY, playerEntity.posZ)); - return biome.getDisplayName().getString(); - } else { - return Config.PLACEHOLDER_TEXT.get(); + if (Config.getWindowTitle().contains(MOD_COUNT)) { + transformationMap.put(MOD_COUNT, () -> modCount); } - } - - private static String getPlayerScoreOrPlaceholder() { - PlayerEntity playerEntity = null; - try { - playerEntity = TitleChanger.proxy.getClientPlayer(); - } catch (IllegalStateException e) { - LOGGER.debug("Attempted to call proxy.getClientPlayer() in serverside code."); + if (Config.getWindowTitle().contains(TIME)) { + transformationMap.put(TIME, () -> timeFormatter.format(new Date())); } - if (playerEntity != null) { - return Integer.toString(playerEntity.getScore()); - } else { - return Config.PLACEHOLDER_TEXT.get(); + if (Config.getWindowTitle().contains(PLAYER_LOCATION)) { + transformationMap.put(PLAYER_LOCATION, this::getLocation); } - } - - private static String getPlayerLocationOrPlaceholder() { - PlayerEntity playerEntity = null; - String posX, posY, posZ; - try { - playerEntity = TitleChanger.proxy.getClientPlayer(); - } catch (IllegalStateException e) { - LOGGER.debug("Attempted to call proxy.getClientPlayer() in serverside code."); + if (Config.getWindowTitle().contains(SCORE)) { + transformationMap.put(SCORE, this::getScore); + } + if (Config.getWindowTitle().contains(BIOME)) { + transformationMap.put(BIOME, this::getBiome); } - if (playerEntity != null) { - posX = String.format("%.0f", playerEntity.posX); - posY = String.format("%.0f", playerEntity.posY); - posZ = String.format("%.0f", playerEntity.posZ); - } else { - posX = posY = posZ = Config.PLACEHOLDER_TEXT.get(); + if (Config.getWindowTitle().contains(CHUNK)) { + transformationMap.put(CHUNK, this::getChunk); } - return String.format("%s %s %s", posX, posY, posZ); + return transformationMap; } - private static String getPlayerChunkOrPlaceholder() { - PlayerEntity playerEntity = null; - String chunkX, chunkY, chunkZ; + @SubscribeEvent(priority = EventPriority.LOWEST) + public void clientTick(TickEvent.ClientTickEvent event) { + if (Config.getWindowTitle().equals("")) { + return; + } + + playerEntity = null; + world = null; try { playerEntity = TitleChanger.proxy.getClientPlayer(); + world = TitleChanger.proxy.getClientWorld(); + glfwSetWindowTitle(Minecraft.getInstance().getMainWindow().getHandle(), processText(Config.getWindowTitle())); } catch (IllegalStateException e) { LOGGER.debug("Attempted to call proxy.getClientPlayer() in serverside code."); } - if (playerEntity != null) { - chunkX = String.format("%d", playerEntity.chunkCoordX); - chunkY = String.format("%d", playerEntity.chunkCoordY); - chunkZ = String.format("%d", playerEntity.chunkCoordZ); - } else { - chunkX = chunkY = chunkZ = Config.PLACEHOLDER_TEXT.get(); + } + + /** + * Apply transformations to the provided format string to generate the window title + * + * @param formatString Format template + * @return Final window title + */ + private String processText(String formatString) { + for (Map.Entry> entry : transformations.entrySet()) { + formatString = formatString.replace(entry.getKey(), entry.getValue().get()); } - return String.format("%s %s %s", chunkX, chunkY, chunkZ); + return formatString; + } + + private String getLocation() { + return infoRetriever.getLocation(playerEntity); + } + + private String getScore() { + return infoRetriever.getScore(playerEntity); + } + + private String getBiome() { + return infoRetriever.getBiome(playerEntity, world); + } + + private String getChunk() { + return infoRetriever.getChunk(playerEntity); } } diff --git a/src/main/java/maxwell_lt/titlechanger/TitleChanger.java b/src/main/java/maxwell_lt/titlechanger/TitleChanger.java index fcadaec..b2a5c4d 100644 --- a/src/main/java/maxwell_lt/titlechanger/TitleChanger.java +++ b/src/main/java/maxwell_lt/titlechanger/TitleChanger.java @@ -1,5 +1,6 @@ package maxwell_lt.titlechanger; +import maxwell_lt.titlechanger.config.Config; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.ModLoadingContext; @@ -7,7 +8,6 @@ import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fml.loading.FMLPaths; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,19 +16,15 @@ public class TitleChanger { public static final String MODID = "titlechanger"; - public static IProxy proxy = DistExecutor.runForDist(() -> ClientProxy::new, () -> ServerProxy::new); + public static IProxy proxy = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); private static final Logger LOGGER = LogManager.getLogger(); public TitleChanger() { - ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.GENERAL_CONFIG); + ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC); - // Register the setup method for modloading FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); - Config.loadConfig(Config.GENERAL_CONFIG, FMLPaths.CONFIGDIR.get().resolve("titlechanger-client.toml")); - - // Register ourselves for server and other game events we are interested in MinecraftForge.EVENT_BUS.register(this); } diff --git a/src/main/java/maxwell_lt/titlechanger/config/ClientConfig.java b/src/main/java/maxwell_lt/titlechanger/config/ClientConfig.java new file mode 100644 index 0000000..efbf1e4 --- /dev/null +++ b/src/main/java/maxwell_lt/titlechanger/config/ClientConfig.java @@ -0,0 +1,34 @@ +package maxwell_lt.titlechanger.config; + +import net.minecraftforge.common.ForgeConfigSpec; + +public class ClientConfig { + + private static final String CATEGORY_GENERAL = "general"; + + public final ForgeConfigSpec.ConfigValue windowTitle; + public final ForgeConfigSpec.ConfigValue timeFormat; + public final ForgeConfigSpec.ConfigValue placeholderText; + + public ClientConfig(ForgeConfigSpec.Builder builder) { + builder.comment("General Configuration").push(CATEGORY_GENERAL); + windowTitle = builder + .comment("The title of the Minecraft window. Leave blank to keep the default window title for your version of Minecraft." + + "\nSome special values that will be inserted at runtime:" + + "\n%mcver% -> The current Minecraft version" + + "\n%modcount% -> Number of loaded mods" + + "\n%time% -> Current system time" + + "\n%playerloc% -> Location of the player, if available" + + "\n%chunk% -> Current chunk, if available" + + "\n%biome% -> Current biome, if available" + + "\n%score% -> Current score of the player, if available\n") + .define("windowtitle", ""); + timeFormat = builder + .comment("Format to display time in. See http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns") + .define("timeformat", "h:mm a"); + placeholderText = builder + .comment("String to use as placeholder when a value is unavailable.") + .define("placeholdertext", "--"); + builder.pop(); + } +} diff --git a/src/main/java/maxwell_lt/titlechanger/config/Config.java b/src/main/java/maxwell_lt/titlechanger/config/Config.java new file mode 100644 index 0000000..4b48f70 --- /dev/null +++ b/src/main/java/maxwell_lt/titlechanger/config/Config.java @@ -0,0 +1,48 @@ +package maxwell_lt.titlechanger.config; + +import maxwell_lt.titlechanger.TitleChanger; +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import org.apache.commons.lang3.tuple.Pair; + +@Mod.EventBusSubscriber(modid = TitleChanger.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class Config { + public static final ClientConfig CLIENT_CONFIG; + public static final ForgeConfigSpec CLIENT_SPEC; + private static String windowTitle; + private static String timeFormat; + private static String placeholderText; + + static { + final Pair specPair = new ForgeConfigSpec.Builder().configure(ClientConfig::new); + CLIENT_SPEC = specPair.getRight(); + CLIENT_CONFIG = specPair.getLeft(); + } + + public static void bakeConfig() { + windowTitle = CLIENT_CONFIG.windowTitle.get(); + timeFormat = CLIENT_CONFIG.timeFormat.get(); + placeholderText = CLIENT_CONFIG.placeholderText.get(); + } + + @SubscribeEvent + public static void onModConfigEvent(final ModConfig.ModConfigEvent configEvent) { + if (configEvent.getConfig().getSpec() == Config.CLIENT_SPEC) { + bakeConfig(); + } + } + + public static String getWindowTitle() { + return windowTitle; + } + + public static String getTimeFormat() { + return timeFormat; + } + + public static String getPlaceholderText() { + return placeholderText; + } +} diff --git a/src/main/java/maxwell_lt/titlechanger/util/InfoRetriever.java b/src/main/java/maxwell_lt/titlechanger/util/InfoRetriever.java new file mode 100644 index 0000000..615e91c --- /dev/null +++ b/src/main/java/maxwell_lt/titlechanger/util/InfoRetriever.java @@ -0,0 +1,64 @@ +package maxwell_lt.titlechanger.util; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; + +public class InfoRetriever { + private final String placeholderText; + + public InfoRetriever(String placeholderText) { + this.placeholderText = placeholderText; + } + + public String getBiome(PlayerEntity playerEntity, World world) { + if (playerEntity != null && world != null) { + Vec3d pos = playerEntity.getPositionVector(); + Biome biome = world.getBiome(new BlockPos(pos.x, pos.y, pos.z)); + return biome.getDisplayName().getString(); + } else { + return placeholderText; + } + } + + public String getScore(PlayerEntity playerEntity) { + if (playerEntity != null) { + return Integer.toString(playerEntity.getScore()); + } else { + return placeholderText; + } + } + + public String getLocation(PlayerEntity playerEntity) { + String posX; + String posY; + String posZ; + + if (playerEntity != null) { + Vec3d pos = playerEntity.getPositionVector(); + posX = String.format("%.0f", pos.x); + posY = String.format("%.0f", pos.y); + posZ = String.format("%.0f", pos.z); + } else { + posX = posY = posZ = placeholderText; + } + return String.format("%s %s %s", posX, posY, posZ); + } + + public String getChunk(PlayerEntity playerEntity) { + String chunkX; + String chunkY; + String chunkZ; + + if (playerEntity != null) { + chunkX = String.format("%d", playerEntity.chunkCoordX); + chunkY = String.format("%d", playerEntity.chunkCoordY); + chunkZ = String.format("%d", playerEntity.chunkCoordZ); + } else { + chunkX = chunkY = chunkZ = placeholderText; + } + return String.format("%s %s %s", chunkX, chunkY, chunkZ); + } +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 6b7c3b1..749a0ff 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -13,6 +13,6 @@ updateJSONURL="https://raw.githubusercontent.com/Maxwell-lt/TitleChanger/master/ [[dependencies.titlechanger]] modId="minecraft" mandatory=true - versionRange="[1.14.4]" + versionRange="[1.15.2]" ordering="NONE" side="BOTH" \ No newline at end of file diff --git a/src/test/java/maxwell_lt/titlechanger/util/InfoRetrieverTest.java b/src/test/java/maxwell_lt/titlechanger/util/InfoRetrieverTest.java new file mode 100644 index 0000000..1d26758 --- /dev/null +++ b/src/test/java/maxwell_lt/titlechanger/util/InfoRetrieverTest.java @@ -0,0 +1,129 @@ +package maxwell_lt.titlechanger.util; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +class InfoRetrieverTest { + static final String DEFAULT_PLACEHOLDER = "--"; + + InfoRetriever infoRetriever; + + @BeforeEach + void setUp() { + infoRetriever = new InfoRetriever(DEFAULT_PLACEHOLDER); + } + + @Test + void testGetScoreWhenPlayerEntityNotNull() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + when(playerEntity.getScore()) + .thenReturn(125); + assertThat(infoRetriever.getScore(playerEntity)) + .isEqualTo("125"); + } + + @Test + void testGetScoreWhenPlayerEntityIsNull() { + assertThat(infoRetriever.getScore(null)) + .isEqualTo(DEFAULT_PLACEHOLDER); + } + + @Test + void testGetLocation() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + when(playerEntity.getPositionVector()) + .thenReturn(new Vec3d(1024D, 63D, 1024D)); + assertThat(infoRetriever.getLocation(playerEntity)) + .isEqualTo("1024 63 1024"); + } + + @Test + void testGetLocationWhenNegative() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + when(playerEntity.getPositionVector()) + .thenReturn(new Vec3d(-1024D, 255D, -1024D)); + assertThat(infoRetriever.getLocation(playerEntity)) + .isEqualTo("-1024 255 -1024"); + } + + @Test + void testGetLocationWithDecimalComponent() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + when(playerEntity.getPositionVector()) + .thenReturn(new Vec3d(5.12345D, 63.33339D, -4.5000001)); + assertThat(infoRetriever.getLocation(playerEntity)) + .isEqualTo("5 63 -5"); + } + + @Test + void testGetLocationWhenPlayerEntityIsNull() { + assertThat(infoRetriever.getLocation(null)) + .isEqualTo(String.format( + "%s %s %s", + DEFAULT_PLACEHOLDER, + DEFAULT_PLACEHOLDER, + DEFAULT_PLACEHOLDER)); + } + + @Test + void testGetChunk() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + playerEntity.chunkCoordX = -5; + playerEntity.chunkCoordY = 3; + playerEntity.chunkCoordZ = 15; + + assertThat(infoRetriever.getChunk(playerEntity)) + .isEqualTo("-5 3 15"); + } + + @Test + void testGetChunkWhenPlayerEntityIsNull() { + assertThat(infoRetriever.getChunk(null)) + .isEqualTo(String.format( + "%s %s %s", + DEFAULT_PLACEHOLDER, + DEFAULT_PLACEHOLDER, + DEFAULT_PLACEHOLDER)); + } + + @Test + void testGetBiomeWhenPlayerEntityIsNull() { + World world = mock(World.class); + assertThat(infoRetriever.getBiome(null, world)) + .isEqualTo(DEFAULT_PLACEHOLDER); + } + + @Test + void testGetBiomeWhenWorldIsNull() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + assertThat(infoRetriever.getBiome(playerEntity, null)) + .isEqualTo(DEFAULT_PLACEHOLDER); + } + + @Test + void testGetBiome() { + PlayerEntity playerEntity = mock(PlayerEntity.class); + World world = mock(World.class); + Biome biome = mock(Biome.class); + ITextComponent biomeName = new TranslationTextComponent("Beach"); + Vec3d vecCoords = new Vec3d(0D, 63D, 0D); + BlockPos blockCoords = new BlockPos(vecCoords); + + when(playerEntity.getPositionVector()).thenReturn(vecCoords); + when(world.getBiome(eq(blockCoords))).thenReturn(biome); + when(biome.getDisplayName()).thenReturn(biomeName); + + assertThat(infoRetriever.getBiome(playerEntity, world)) + .isEqualTo("Beach"); + } +} \ No newline at end of file