diff --git a/CLAUDE.md b/CLAUDE.md index beb40d5..6991635 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,8 +31,6 @@ This is the core concept and touches almost everything: This is why first boot is extremely slow and RAM-hungry (see `README.md` warnings): the entire seed region is force-loaded up front. Any change to world generation, structure handling, or world naming must respect both worlds and the copy step in `Boxed.copyChunks()` / `createOverWorld()` / `createNether()`. The `generatorMaps` / `generatorMap` fields in `Boxed.java` route world names → generators for `getDefaultWorldGenerator` (used by Multiverse and similar world-management plugins) and for the hook in `allLoaded()` that calls `WorldManagementHook.registerWorld`. -`isUsesNewChunkGeneration()` returns `true`, which tells BentoBox this addon uses the modern chunk-generation API. - ### Advancements drive box size `AdvancementsManager` is the other key subsystem. Box growth is data-driven from `advancements.yml`: each advancement key maps to an integer "box growth" increment. `AdvancementListener` watches for player advancement events and asks the manager to update the island's protection-range. Per-island state lives in `objects/IslandAdvancements.java` (a BentoBox `DataObject` persisted via its database layer). `AdvancementsManager.save()` is called in `onDisable()` — any new cached state it holds should be flushed there too. diff --git a/src/main/java/world/bentobox/boxed/AdvancementsManager.java b/src/main/java/world/bentobox/boxed/AdvancementsManager.java index 2540103..1b0644f 100644 --- a/src/main/java/world/bentobox/boxed/AdvancementsManager.java +++ b/src/main/java/world/bentobox/boxed/AdvancementsManager.java @@ -62,18 +62,6 @@ public AdvancementsManager(Boxed addon) { addon.logError("advancements.yml cannot be found! " + e.getLocalizedMessage()); } } - /* - // DEBUG - lists all advancements to console - int scoreTotal = 0; - Iterator ad = Bukkit.getServer().advancementIterator(); - while (ad.hasNext()) { - Advancement a = ad.next(); - int score = getScore(a); - BentoBox.getInstance().logDebug(" 'minecraft:" + a.getKey().getKey() + "': " + score); - scoreTotal += score; - } - BentoBox.getInstance().logDebug("Sum total = " + scoreTotal); - */ } /** diff --git a/src/main/java/world/bentobox/boxed/Boxed.java b/src/main/java/world/bentobox/boxed/Boxed.java index 8255fb5..c82949f 100644 --- a/src/main/java/world/bentobox/boxed/Boxed.java +++ b/src/main/java/world/bentobox/boxed/Boxed.java @@ -186,11 +186,7 @@ public void createWorlds() { if (settings.isNetherGenerate()) { createNether(worldName); } - /* - // Make the end if it does not exist - if (settings.isEndGenerate()) { - //TODO - */ + // The End is not supported yet } private void createNether(String worldName) { @@ -400,9 +396,4 @@ public void allLoaded() { public AdvancementsManager getAdvManager() { return advManager; } - - @Override - public boolean isUsesNewChunkGeneration() { - return true; - } } diff --git a/src/main/java/world/bentobox/boxed/PlaceholdersManager.java b/src/main/java/world/bentobox/boxed/PlaceholdersManager.java index c14eb47..2e73485 100644 --- a/src/main/java/world/bentobox/boxed/PlaceholdersManager.java +++ b/src/main/java/world/bentobox/boxed/PlaceholdersManager.java @@ -33,12 +33,11 @@ public String getCount(User user) { * @return string of advancement count */ public String getCountByLocation(User user) { - if (user != null && user.getUniqueId() != null && user.getLocation() != null) { - return addon.getIslands().getIslandAt(user.getLocation()) - .map(i -> String.valueOf(addon.getAdvManager().getIsland(i).getAdvancements().size())).orElse(""); - } else { + if (user == null || user.getUniqueId() == null) { return ""; } + return addon.getIslands().getIslandAt(user.getLocation()) + .map(i -> String.valueOf(addon.getAdvManager().getIsland(i).getAdvancements().size())).orElse(""); } diff --git a/src/main/java/world/bentobox/boxed/Settings.java b/src/main/java/world/bentobox/boxed/Settings.java index 06240b7..420269c 100644 --- a/src/main/java/world/bentobox/boxed/Settings.java +++ b/src/main/java/world/bentobox/boxed/Settings.java @@ -1777,6 +1777,7 @@ public void setIgnoreAdvancements(boolean ignoreAdvancements) { /** * @return the concurrentIslands */ + @Override public int getConcurrentIslands() { if (concurrentIslands <= 0) { return BentoBox.getInstance().getSettings().getIslandNumber(); @@ -1794,6 +1795,7 @@ public void setConcurrentIslands(int concurrentIslands) { /** * @return the disallowTeamMemberIslands */ + @Override public boolean isDisallowTeamMemberIslands() { return disallowTeamMemberIslands; } diff --git a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java index a6a492e..03342a7 100644 --- a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java +++ b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java @@ -16,7 +16,6 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.block.structure.Mirror; import org.bukkit.block.structure.StructureRotation; @@ -119,9 +118,7 @@ public boolean canExecute(User user, String label, List args) { } // Next come the coordinates - there must be at least 3 of them - if ((!args.get(1).equals("~") && !Util.isInteger(args.get(1), true)) - || (!args.get(2).equals("~") && !Util.isInteger(args.get(2), true)) - || (!args.get(3).equals("~") && !Util.isInteger(args.get(3), true))) { + if (!isCoordinate(args.get(1)) || !isCoordinate(args.get(2)) || !isCoordinate(args.get(3))) { user.sendMessage("boxed.commands.boxadmin.place.use-integers"); return false; } @@ -132,10 +129,7 @@ public boolean canExecute(User user, String label, List args) { } // Handle rotation - sr = Enums.getIfPresent(StructureRotation.class, args.get(4).toUpperCase(Locale.ENGLISH)).orNull(); - if (sr == null) { - user.sendMessage("boxed.commands.boxadmin.place.unknown-rotation"); - Arrays.stream(StructureRotation.values()).map(StructureRotation::name).forEach(user::sendRawMessage); + if (!parseRotation(user, args.get(4))) { return false; } @@ -144,24 +138,67 @@ public boolean canExecute(User user, String label, List args) { } // Handle mirror - mirror = Enums.getIfPresent(Mirror.class, args.get(5).toUpperCase(Locale.ENGLISH)).orNull(); + if (!parseMirror(user, args.get(5))) { + return false; + } + + // Handle NO_MOBS + return args.size() < 7 || parseNoMobs(user, args.get(6)); + } + + /** + * @param arg command argument + * @return true if the argument is a relative marker (~) or an integer + */ + private static boolean isCoordinate(String arg) { + return arg.equals("~") || Util.isInteger(arg, true); + } + + /** + * Parses the rotation argument into {@link #sr} + * @param user user to inform if the rotation is unknown + * @param arg rotation argument + * @return true if the rotation is valid + */ + private boolean parseRotation(User user, String arg) { + sr = Enums.getIfPresent(StructureRotation.class, arg.toUpperCase(Locale.ENGLISH)).orNull(); + if (sr == null) { + user.sendMessage("boxed.commands.boxadmin.place.unknown-rotation"); + Arrays.stream(StructureRotation.values()).map(StructureRotation::name).forEach(user::sendRawMessage); + return false; + } + return true; + } + + /** + * Parses the mirror argument into {@link #mirror} + * @param user user to inform if the mirror is unknown + * @param arg mirror argument + * @return true if the mirror is valid + */ + private boolean parseMirror(User user, String arg) { + mirror = Enums.getIfPresent(Mirror.class, arg.toUpperCase(Locale.ENGLISH)).orNull(); if (mirror == null) { user.sendMessage("boxed.commands.boxadmin.place.unknown-mirror"); Arrays.stream(Mirror.values()).map(Mirror::name).forEach(user::sendRawMessage); return false; } + return true; + } - if (args.size() == 7) { - if (args.get(6).toUpperCase(Locale.ENGLISH).equals("NO_MOBS")) { - noMobs = true; - } else { - user.sendMessage("boxed.commands.boxadmin.place.unknown", TextVariables.LABEL, args.get(6).toUpperCase(Locale.ENGLISH)); - return false; - } + /** + * Parses the NO_MOBS argument into {@link #noMobs} + * @param user user to inform if the argument is unknown + * @param arg argument + * @return true if the argument is valid + */ + private boolean parseNoMobs(User user, String arg) { + if (arg.toUpperCase(Locale.ENGLISH).equals("NO_MOBS")) { + noMobs = true; + return true; } - - // Syntax is okay - return true; + user.sendMessage("boxed.commands.boxadmin.place.unknown", TextVariables.LABEL, arg.toUpperCase(Locale.ENGLISH)); + return false; } @Override @@ -193,7 +230,7 @@ public boolean execute(User user, String label, List args) { .removeJigsaw(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs, removedBlocks)); placedStructures.push(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs, removedBlocks)); // Track the placement - boolean result = saveStructure(spot, tag, user, sr, mirror); + boolean result = saveStructure(spot, tag, sr, mirror); if (result) { user.sendMessage("boxed.commands.boxadmin.place.saved"); } else { @@ -202,7 +239,7 @@ public boolean execute(User user, String label, List args) { return result; } - private boolean saveStructure(Location spot, NamespacedKey tag, User user, StructureRotation sr2, Mirror mirror2) { + private boolean saveStructure(Location spot, NamespacedKey tag, StructureRotation sr2, Mirror mirror2) { return getAddon().getIslands().getIslandAt(spot).map(i -> { int xx = spot.getBlockX() - i.getCenter().getBlockX(); int zz = spot.getBlockZ() - i.getCenter().getBlockZ(); @@ -218,8 +255,7 @@ private boolean saveStructure(Location spot, NamespacedKey tag, User user, Struc config.set(spot.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH) + "." + xx + "," + spot.getBlockY() + "," + zz, v.toString()); config.save(structures); } catch (IOException | InvalidConfigurationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + getAddon().logError("Could not save structure to " + STRUCTURE_FILE + ": " + e.getMessage()); return false; } return true; @@ -261,13 +297,13 @@ private boolean undoLastPlacement(User user) { Collections.emptyList() // No entity transformers ); lastRecord.removedBlocks().clear(); - removeStructure(lastRecord.location(), tag, user); // Remove from config + removeStructure(lastRecord.location()); // Remove from config user.sendMessage("boxed.commands.boxadmin.place.undo-success"); return true; } - private boolean removeStructure(Location spot, NamespacedKey tag, User user) { + private boolean removeStructure(Location spot) { return getAddon().getIslands().getIslandAt(spot).map(i -> { int xx = spot.getBlockX() - i.getCenter().getBlockX(); int zz = spot.getBlockZ() - i.getCenter().getBlockZ(); @@ -282,7 +318,7 @@ private boolean removeStructure(Location spot, NamespacedKey tag, User user) { return true; } } catch (IOException | InvalidConfigurationException e) { - e.printStackTrace(); + getAddon().logError("Could not remove structure from " + STRUCTURE_FILE + ": " + e.getMessage()); } return false; }).orElse(false); diff --git a/src/main/java/world/bentobox/boxed/generators/biomes/AbstractCopyBiomeProvider.java b/src/main/java/world/bentobox/boxed/generators/biomes/AbstractCopyBiomeProvider.java index 2742599..f57c5cc 100644 --- a/src/main/java/world/bentobox/boxed/generators/biomes/AbstractCopyBiomeProvider.java +++ b/src/main/java/world/bentobox/boxed/generators/biomes/AbstractCopyBiomeProvider.java @@ -2,14 +2,14 @@ import java.util.List; -import org.bukkit.Registry; -import org.bukkit.World.Environment; import org.bukkit.block.Biome; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.WorldInfo; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import world.bentobox.bentobox.BentoBox; import world.bentobox.boxed.Boxed; import world.bentobox.boxed.generators.chunks.AbstractBoxedChunkGenerator; @@ -27,7 +27,7 @@ public abstract class AbstractCopyBiomeProvider extends BiomeProvider { protected final int dist; - protected AbstractCopyBiomeProvider(Boxed boxed, Environment env, Biome defaultBiome) { + protected AbstractCopyBiomeProvider(Boxed boxed, Biome defaultBiome) { this.addon = boxed; this.defaultBiome = defaultBiome; dist = addon.getSettings().getIslandDistance(); @@ -35,11 +35,10 @@ protected AbstractCopyBiomeProvider(Boxed boxed, Environment env, Biome defaultB @Override public Biome getBiome(WorldInfo worldInfo, int x, int y, int z) { - int chunkX = x >> 4; - int chunkZ = z >> 4; - chunkX = AbstractBoxedChunkGenerator.repeatCalc(chunkX); - chunkZ = AbstractBoxedChunkGenerator.repeatCalc(chunkZ); - @Nullable ChunkStore c = addon.getChunkGenerator(worldInfo.getEnvironment()).getChunk(chunkX, chunkZ); + AbstractBoxedChunkGenerator gen = addon.getChunkGenerator(worldInfo.getEnvironment()); + int chunkX = gen.repeatCalc(x >> 4); + int chunkZ = gen.repeatCalc(z >> 4); + @Nullable ChunkStore c = gen.getChunk(chunkX, chunkZ); if (c != null) { int xx = Math.floorMod(x, 16); @@ -54,7 +53,7 @@ public Biome getBiome(WorldInfo worldInfo, int x, int y, int z) { @Override public List getBiomes(WorldInfo worldInfo) { // Return all of them for now! - return Registry.BIOME.stream().filter(b -> !b.equals(Biome.CUSTOM)).toList(); + return RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).stream().toList(); } } diff --git a/src/main/java/world/bentobox/boxed/generators/biomes/AbstractSeedBiomeProvider.java b/src/main/java/world/bentobox/boxed/generators/biomes/AbstractSeedBiomeProvider.java index 5ed1f8f..18697d0 100644 --- a/src/main/java/world/bentobox/boxed/generators/biomes/AbstractSeedBiomeProvider.java +++ b/src/main/java/world/bentobox/boxed/generators/biomes/AbstractSeedBiomeProvider.java @@ -7,10 +7,11 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.SortedMap; import java.util.TreeMap; -import org.bukkit.Registry; +import org.bukkit.NamespacedKey; import org.bukkit.World.Environment; import org.bukkit.block.Biome; import org.bukkit.block.BlockFace; @@ -21,6 +22,8 @@ import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import world.bentobox.boxed.Boxed; /** @@ -31,6 +34,11 @@ */ public abstract class AbstractSeedBiomeProvider extends BiomeProvider { + /** + * Config marker for "no custom biome here, use the vanilla one" + */ + private static final String CUSTOM_BIOME = "CUSTOM"; + private static final Map ENV_MAP; static { @@ -103,7 +111,7 @@ public static Biome getBiome(int humidity, double weirdness) { */ private enum MiddleBiome { X00(0, 0, Biome.SNOWY_PLAINS, Biome.ICE_SPIKES), X01(0, 1, Biome.PLAINS, Biome.PLAINS), - X02(0, 2, Biome.FLOWER_FOREST, Biome.SUNFLOWER_PLAINS), x03(0, 3, Biome.SAVANNA, Biome.SAVANNA), + X02(0, 2, Biome.FLOWER_FOREST, Biome.SUNFLOWER_PLAINS), X03(0, 3, Biome.SAVANNA, Biome.SAVANNA), X04(0, 4, Biome.DESERT, Biome.DESERT), X10(1, 0, Biome.SNOWY_PLAINS, Biome.SNOWY_PLAINS), X11(1, 1, Biome.PLAINS, Biome.PLAINS), @@ -153,25 +161,25 @@ public static Biome getBiome(int humidity, int temperature, double weirdness) { * Plateau biomes by temperature and humidity zones */ private enum PlateauBiome { - X00(0, 0, Biome.SNOWY_PLAINS, Biome.ICE_SPIKES), x01(0, 1, Biome.MEADOW, Biome.CHERRY_GROVE), - X02(0, 2, Biome.MEADOW, Biome.CHERRY_GROVE), x03(0, 3, Biome.SAVANNA_PLATEAU, Biome.SAVANNA_PLATEAU), + X00(0, 0, Biome.SNOWY_PLAINS, Biome.ICE_SPIKES), X01(0, 1, Biome.MEADOW, Biome.CHERRY_GROVE), + X02(0, 2, Biome.MEADOW, Biome.CHERRY_GROVE), X03(0, 3, Biome.SAVANNA_PLATEAU, Biome.SAVANNA_PLATEAU), X04(0, 4, Biome.BADLANDS, Biome.ERODED_BADLANDS), - X10(1, 0, Biome.SNOWY_PLAINS, Biome.SNOWY_PLAINS), x11(1, 1, Biome.MEADOW, Biome.MEADOW), - X12(1, 2, Biome.MEADOW, Biome.CHERRY_GROVE), x13(1, 3, Biome.SAVANNA_PLATEAU, Biome.SAVANNA_PLATEAU), + X10(1, 0, Biome.SNOWY_PLAINS, Biome.SNOWY_PLAINS), X11(1, 1, Biome.MEADOW, Biome.MEADOW), + X12(1, 2, Biome.MEADOW, Biome.CHERRY_GROVE), X13(1, 3, Biome.SAVANNA_PLATEAU, Biome.SAVANNA_PLATEAU), X14(1, 4, Biome.BADLANDS, Biome.ERODED_BADLANDS), - X20(2, 0, Biome.SNOWY_PLAINS, Biome.SNOWY_TAIGA), x21(2, 1, Biome.FOREST, Biome.MEADOW), - X22(2, 2, Biome.MEADOW, Biome.BIRCH_FOREST), x23(2, 3, Biome.FOREST, Biome.FOREST), + X20(2, 0, Biome.SNOWY_PLAINS, Biome.SNOWY_TAIGA), X21(2, 1, Biome.FOREST, Biome.MEADOW), + X22(2, 2, Biome.MEADOW, Biome.BIRCH_FOREST), X23(2, 3, Biome.FOREST, Biome.FOREST), X24(2, 4, Biome.BADLANDS, Biome.BADLANDS), - X30(3, 0, Biome.SNOWY_TAIGA, Biome.SNOWY_TAIGA), x31(3, 1, Biome.TAIGA, Biome.MEADOW), - X32(3, 2, Biome.MEADOW, Biome.BIRCH_FOREST), x33(3, 3, Biome.FOREST, Biome.FOREST), + X30(3, 0, Biome.SNOWY_TAIGA, Biome.SNOWY_TAIGA), X31(3, 1, Biome.TAIGA, Biome.MEADOW), + X32(3, 2, Biome.MEADOW, Biome.BIRCH_FOREST), X33(3, 3, Biome.FOREST, Biome.FOREST), X34(3, 4, Biome.WOODED_BADLANDS, Biome.WOODED_BADLANDS), X40(4, 0, Biome.SNOWY_TAIGA, Biome.SNOWY_TAIGA), X41(4, 1, Biome.OLD_GROWTH_SPRUCE_TAIGA, Biome.OLD_GROWTH_PINE_TAIGA), - X42(4, 2, Biome.DARK_FOREST, Biome.DARK_FOREST), x43(4, 3, Biome.JUNGLE, Biome.JUNGLE), + X42(4, 2, Biome.DARK_FOREST, Biome.DARK_FOREST), X43(4, 3, Biome.JUNGLE, Biome.JUNGLE), X44(4, 4, Biome.WOODED_BADLANDS, Biome.WOODED_BADLANDS),; private int temp; @@ -501,6 +509,113 @@ private int getTemp(double temp) { }; } + /* + * Shared building blocks for the erosion / ridge biome tables below. + * Temperature and humidity are levels 0-4, erosion is a level 0-6. + */ + + /** + * Erosion level 0 peaks: jagged/frozen peaks when cold, stony peaks when temperate, badlands when hot. + */ + private static @NonNull Biome peaksBiome(int humidity, int temperature, double weirdness) { + if (temperature <= 2) { + return weirdness < 0 ? Biome.JAGGED_PEAKS : Biome.FROZEN_PEAKS; + } + if (temperature == 3) { + return Biome.STONY_PEAKS; + } + return BadlandBiome.getBiome(humidity, weirdness); + } + + /** + * Snowy slopes when dry, grove when humid. + */ + private static @NonNull Biome slopesOrGrove(int humidity) { + return humidity < 2 ? Biome.SNOWY_SLOPES : Biome.GROVE; + } + + /** + * Middle biomes unless hot, in which case badlands. + */ + private static @NonNull Biome middleOrBadlands(int humidity, int temperature, double weirdness) { + return temperature < 4 ? MiddleBiome.getBiome(humidity, temperature, weirdness) + : BadlandBiome.getBiome(humidity, weirdness); + } + + /** + * Cold (temperature 0-2): slopes or grove, otherwise plateau biomes. + */ + private static @NonNull Biome coldSlopesOrPlateau(int humidity, int temperature, double weirdness) { + return temperature < 3 ? slopesOrGrove(humidity) : PlateauBiome.getBiome(humidity, temperature, weirdness); + } + + /** + * Frozen (temperature 0): slopes or grove, otherwise plateau biomes. + */ + private static @NonNull Biome frozenSlopesOrPlateau(int humidity, int temperature, double weirdness) { + return temperature == 0 ? slopesOrGrove(humidity) : PlateauBiome.getBiome(humidity, temperature, weirdness); + } + + /** + * Frozen (temperature 0): slopes or grove, otherwise middle biomes / badlands. + */ + private static @NonNull Biome frozenSlopesOrMiddle(int humidity, int temperature, double weirdness) { + return temperature == 0 ? slopesOrGrove(humidity) : middleOrBadlands(humidity, temperature, weirdness); + } + + /** + * Rivers: frozen when temperature is 0. + */ + private static @NonNull Biome riverBiome(int temperature) { + return temperature == 0 ? Biome.FROZEN_RIVER : Biome.RIVER; + } + + /** + * Swamps for temperate, mangrove swamps for hot. Only valid for temperature > 0. + */ + private static @NonNull Biome swampOrMangrove(int temperature) { + return temperature <= 2 ? Biome.SWAMP : Biome.MANGROVE_SWAMP; + } + + /** + * High erosion fall-through: middle biomes when frozen, otherwise swamps. + */ + private static @NonNull Biome swampBiome(int humidity, int temperature, double weirdness) { + return temperature == 0 ? MiddleBiome.getBiome(humidity, temperature, weirdness) : swampOrMangrove(temperature); + } + + /** + * High erosion fall-through in valleys: frozen river when frozen, otherwise swamps. + */ + private static @NonNull Biome valleySwampBiome(int temperature) { + return temperature == 0 ? Biome.FROZEN_RIVER : swampOrMangrove(temperature); + } + + /** + * Whether erosion level 5 with positive weirdness and a hot temperature yields windswept savanna. + */ + private static boolean isWindsweptSavanna(int temperature, double weirdness) { + return weirdness > 0 && temperature > 2; + } + + /** + * Erosion level 5 special cases shared by most ridge types: negative weirdness with a cold temperature + * or high humidity gives shattered (or middle) biomes, positive weirdness with a hot temperature gives + * windswept savanna. + * @param shattered true to use shattered biomes for the negative-weirdness case, false to use middle biomes + * @return the special-case biome, or empty if the caller's default applies + */ + private static Optional erosionFiveBiome(int humidity, int temperature, double weirdness, boolean shattered) { + if (weirdness < 0 && (temperature <= 1 || humidity == 4)) { + return Optional.of(shattered ? ShatteredBiome.getBiome(humidity, temperature, weirdness) + : MiddleBiome.getBiome(humidity, temperature, weirdness)); + } + if (isWindsweptSavanna(temperature, weirdness)) { + return Optional.of(Biome.WINDSWEPT_SAVANNA); + } + return Optional.empty(); + } + private @NonNull Biome farInlandBiome(int humidity, int temperature, int erosion, double weirdness) { return switch (Ridges.getRidge(convertToY(weirdness))) { case HIGH -> getFarInlandHighBiome(humidity, temperature, erosion, weirdness); @@ -512,308 +627,86 @@ private int getTemp(double temp) { } private @NonNull Biome getFarInlandValleysBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion >= 0 && erosion < 6) { - if (temperature > 0D) { - return Biome.RIVER; - } else { - return Biome.FROZEN_RIVER; - } - } - // e == 6 - if (temperature == 0) { - return Biome.FROZEN_RIVER; - } - if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; - } - - private @NonNull Biome getValleysNearInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - return getFarInlandValleysBiome(humidity, temperature, erosion, weirdness); + return erosion < 6 ? riverBiome(temperature) : valleySwampBiome(temperature); } + /** + * Peaks biomes. Shared by far inland, near inland and coast. + */ private @NonNull Biome getFarInlandPeaksBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature >= 0 && temperature <= 2) { - if (weirdness < 0) { - return Biome.JAGGED_PEAKS; - } else { - return Biome.FROZEN_PEAKS; - } - } else if (temperature == 3) { - return Biome.STONY_PEAKS; - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } else if (temperature == 0 && humidity > 1) { - return Biome.GROVE; - } else if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion <= 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Shattered biomes - return ShatteredBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - // middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); + return switch (erosion) { + case 0 -> peaksBiome(humidity, temperature, weirdness); + case 1 -> frozenSlopesOrMiddle(humidity, temperature, weirdness); + case 5 -> erosionFiveBiome(humidity, temperature, weirdness, true) + .orElseGet(() -> MiddleBiome.getBiome(humidity, temperature, weirdness)); + default -> MiddleBiome.getBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome getFarInlandMidBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature < 3 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature < 3 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature == 0 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 2) { - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 3) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1) || humidity == 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - if (temperature == 0) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; + return switch (erosion) { + case 0 -> coldSlopesOrPlateau(humidity, temperature, weirdness); + case 1 -> frozenSlopesOrPlateau(humidity, temperature, weirdness); + case 2 -> PlateauBiome.getBiome(humidity, temperature, weirdness); + case 3 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + case 5 -> erosionFiveBiome(humidity, temperature, weirdness, false) + .orElseGet(() -> swampBiome(humidity, temperature, weirdness)); + default -> swampBiome(humidity, temperature, weirdness); + }; } + /** + * Low biomes. Shared by far inland and near inland. + */ private @NonNull Biome getFarInlandLowBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion >= 0 && erosion < 2) { - if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion < 5) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - if (temperature == 0) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; + return switch (erosion) { + case 0, 1 -> middleOrBadlands(humidity, temperature, weirdness); + case 2, 3, 4 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + case 5 -> erosionFiveBiome(humidity, temperature, weirdness, false) + .orElseGet(() -> swampBiome(humidity, temperature, weirdness)); + default -> swampBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome getFarInlandHighBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature < 3 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature < 3 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature == 0 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - if (temperature > 0 && temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 2 || erosion == 3 || erosion == 4) { - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 4 || erosion == 6) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return ShatteredBiome.getBiome(humidity, temperature, weirdness); + return switch (erosion) { + case 0 -> coldSlopesOrPlateau(humidity, temperature, weirdness); + case 1 -> frozenSlopesOrMiddle(humidity, temperature, weirdness); + case 2, 3, 4 -> PlateauBiome.getBiome(humidity, temperature, weirdness); + case 6 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + default -> ShatteredBiome.getBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome nearInlandBiome(int humidity, int temperature, int erosion, double weirdness) { return switch (Ridges.getRidge(convertToY(weirdness))) { case HIGH -> getHighNearInlandBiome(humidity, temperature, erosion, weirdness); - case LOW -> getLowNearInlandBiome(humidity, temperature, erosion, weirdness); + case LOW -> getFarInlandLowBiome(humidity, temperature, erosion, weirdness); case MID -> getMidNearInlandBiome(humidity, temperature, erosion, weirdness); - case PEAKS -> getPeaksNearInlandBiome(humidity, temperature, erosion, weirdness); - default -> getValleysNearInlandBiome(humidity, temperature, erosion, weirdness); + case PEAKS -> getFarInlandPeaksBiome(humidity, temperature, erosion, weirdness); + default -> getFarInlandValleysBiome(humidity, temperature, erosion, weirdness); }; } - private @NonNull Biome getPeaksNearInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature >= 0 && temperature <= 2) { - if (weirdness < 0) { - return Biome.JAGGED_PEAKS; - } else { - return Biome.FROZEN_PEAKS; - } - } else if (temperature == 3) { - return Biome.STONY_PEAKS; - } - return BadlandBiome.getBiome(humidity, weirdness); - - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } else if (temperature == 0 && humidity > 1) { - return Biome.GROVE; - } else if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion <= 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Shattered biomes - return ShatteredBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - // middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - private @NonNull Biome getMidNearInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature < 3 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature < 3 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature == 0 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - if (temperature > 0 && temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion <= 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1) || humidity == 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - if (temperature == 0) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; - } - - private @NonNull Biome getLowNearInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion >= 0 && erosion < 2) { - if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion < 5) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - if (temperature == 0) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; + return switch (erosion) { + case 0 -> coldSlopesOrPlateau(humidity, temperature, weirdness); + case 1 -> frozenSlopesOrMiddle(humidity, temperature, weirdness); + case 2, 3, 4 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + case 5 -> erosionFiveBiome(humidity, temperature, weirdness, false) + .orElseGet(() -> swampBiome(humidity, temperature, weirdness)); + default -> swampBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome getHighNearInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature < 3 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature < 3 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature == 0 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - if (temperature > 0 && temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion <= 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1) || humidity == 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - return MiddleBiome.getBiome(humidity, temperature, weirdness); + return switch (erosion) { + case 0 -> coldSlopesOrPlateau(humidity, temperature, weirdness); + case 1 -> frozenSlopesOrMiddle(humidity, temperature, weirdness); + case 5 -> erosionFiveBiome(humidity, temperature, weirdness, false) + .orElseGet(() -> MiddleBiome.getBiome(humidity, temperature, weirdness)); + default -> MiddleBiome.getBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome midInlandBiome(int humidity, int temperature, int erosion, double weirdness) { @@ -827,297 +720,105 @@ private int getTemp(double temp) { } private @NonNull Biome getValleysMidInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0 || erosion == 1) { - if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else { - return BadlandBiome.getBiome(humidity, weirdness); - } - } - if (erosion >= 2 && erosion <= 5) { - if (temperature == 0) { - return Biome.FROZEN_RIVER; - } else { - return Biome.RIVER; - } - } - if (temperature == 0) { - return Biome.FROZEN_RIVER; - } - if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; + return switch (erosion) { + case 0, 1 -> middleOrBadlands(humidity, temperature, weirdness); + case 2, 3, 4, 5 -> riverBiome(temperature); + default -> valleySwampBiome(temperature); + }; } private @NonNull Biome getPeaksMidInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0 || erosion == 1) { - if (temperature >= 0 && temperature <= 2) { - if (weirdness < 0) { - return Biome.JAGGED_PEAKS; - } else { - return Biome.FROZEN_PEAKS; - } - } else if (temperature == 3) { - return Biome.STONY_PEAKS; - } - return BadlandBiome.getBiome(humidity, weirdness); - - } else if (erosion == 2) { - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 3) { - if (temperature < 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 4 || erosion == 6) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return ShatteredBiome.getBiome(humidity, temperature, weirdness); + return switch (erosion) { + case 0, 1 -> peaksBiome(humidity, temperature, weirdness); + case 2 -> PlateauBiome.getBiome(humidity, temperature, weirdness); + case 3 -> middleOrBadlands(humidity, temperature, weirdness); + case 4, 6 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + default -> ShatteredBiome.getBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome getMidMidInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature < 3 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature < 3 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature == 0 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - if (temperature > 0 && temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 2 || erosion == 3) { - if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - return ShatteredBiome.getBiome(humidity, temperature, weirdness); - } - if (temperature == 0) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; + return switch (erosion) { + case 0 -> coldSlopesOrPlateau(humidity, temperature, weirdness); + case 1 -> frozenSlopesOrMiddle(humidity, temperature, weirdness); + case 2, 3 -> middleOrBadlands(humidity, temperature, weirdness); + case 4 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + case 5 -> ShatteredBiome.getBiome(humidity, temperature, weirdness); + default -> swampBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome getLowMidInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0 || erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } else if (temperature == 0 && humidity > 1) { - return Biome.GROVE; - } else if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } - if (erosion == 2 || erosion == 3) { - if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } - // e == 6 - if (temperature == 0) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (temperature == 1 || temperature == 2) { - return Biome.SWAMP; - } - return Biome.MANGROVE_SWAMP; + return switch (erosion) { + case 0, 1 -> frozenSlopesOrMiddle(humidity, temperature, weirdness); + case 2, 3 -> middleOrBadlands(humidity, temperature, weirdness); + default -> swampBiome(humidity, temperature, weirdness); + }; } private @NonNull Biome getHighMidInlandBiome(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature < 3 && weirdness < 0D) { - return Biome.JAGGED_PEAKS; - } - if (temperature < 3 && weirdness > 0.0D) { - return Biome.FROZEN_PEAKS; - } - if (temperature == 3) { - return Biome.STONY_PEAKS; - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 1) { - if (temperature < 3 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } - if (temperature < 3 && (humidity == 2 || humidity == 3 || humidity == 4)) { - return Biome.GROVE; - } - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 2) { - return PlateauBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 3) { - if (temperature < 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion == 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - return ShatteredBiome.getBiome(humidity, temperature, weirdness); - } - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - - private @NonNull Biome coastBiome(int humidity, int temperature, int erosion, double weirdness) { - return switch (Ridges.getRidge(convertToY(weirdness))) { - case HIGH -> getHighCoastBionme(humidity, temperature, erosion, weirdness); - case LOW -> getLowCoastBionme(humidity, temperature, erosion, weirdness); - case MID -> getMidCoastBionme(humidity, temperature, erosion, weirdness); - case PEAKS -> getPeaksCoastBionme(humidity, temperature, erosion, weirdness); - default -> getValleysCoastBionme(humidity, temperature, erosion, weirdness); + return switch (erosion) { + case 0 -> highMidInlandPeaks(humidity, temperature, weirdness); + case 1 -> coldSlopesOrPlateau(humidity, temperature, weirdness); + case 2 -> PlateauBiome.getBiome(humidity, temperature, weirdness); + case 3 -> middleOrBadlands(humidity, temperature, weirdness); + case 5 -> ShatteredBiome.getBiome(humidity, temperature, weirdness); + default -> MiddleBiome.getBiome(humidity, temperature, weirdness); }; } - private @NonNull Biome getValleysCoastBionme(int humidity, int temperature, int erosion, double weirdness) { - if (temperature > 0D) { - return Biome.RIVER; + /** + * Erosion 0 peaks for the mid inland high ridge. Unlike {@link #peaksBiome} a weirdness of exactly zero + * with a cold temperature falls through to badlands. + */ + private static @NonNull Biome highMidInlandPeaks(int humidity, int temperature, double weirdness) { + if (temperature < 3 && weirdness < 0D) { + return Biome.JAGGED_PEAKS; } - return Biome.FROZEN_RIVER; - } - - private @NonNull Biome getPeaksCoastBionme(int humidity, int temperature, int erosion, double weirdness) { - if (erosion == 0) { - if (temperature >= 0 && temperature <= 2) { - if (weirdness < 0) { - return Biome.JAGGED_PEAKS; - } else { - return Biome.FROZEN_PEAKS; - } - } else if (temperature == 3) { - return Biome.STONY_PEAKS; - } - return BadlandBiome.getBiome(humidity, weirdness); - - } else if (erosion == 1) { - if (temperature == 0 && (humidity == 0 || humidity == 1)) { - return Biome.SNOWY_SLOPES; - } else if (temperature == 0 && humidity > 1) { - return Biome.GROVE; - } else if (temperature < 4) { - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - return BadlandBiome.getBiome(humidity, weirdness); - } else if (erosion >= 2 && erosion <= 4) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Shattered biomes - return ShatteredBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } + if (temperature < 3 && weirdness > 0D) { + return Biome.FROZEN_PEAKS; } - // middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); + if (temperature == 3) { + return Biome.STONY_PEAKS; + } + return BadlandBiome.getBiome(humidity, weirdness); } - private @NonNull Biome getMidCoastBionme(int humidity, int temperature, int erosion, double weirdness) { - if (erosion > 0 && erosion < 3) { - return Biome.STONY_SHORE; - } else if (erosion == 3) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 4) { - if (weirdness < 0) { - // Beach Biomes - return getBeachBiome(temperature); - } else { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - } else if (erosion == 5) { - if (weirdness < 0) { - // Beach Biomes - return getBeachBiome(temperature); - } - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } + private @NonNull Biome coastBiome(int humidity, int temperature, int erosion, double weirdness) { + return switch (Ridges.getRidge(convertToY(weirdness))) { + case HIGH -> getHighCoastBiome(humidity, temperature, erosion, weirdness); + case LOW -> getLowCoastBiome(humidity, temperature, erosion, weirdness); + case MID -> getMidCoastBiome(humidity, temperature, erosion, weirdness); + case PEAKS -> getFarInlandPeaksBiome(humidity, temperature, erosion, weirdness); + default -> riverBiome(temperature); + }; + } - } else if (erosion == 6) { - if (weirdness < 0D) { - // Beach Biomes - return getBeachBiome(temperature); - } else { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - } - // Else Beach biomes - return getBeachBiome(temperature); + private @NonNull Biome getMidCoastBiome(int humidity, int temperature, int erosion, double weirdness) { + return switch (erosion) { + case 1, 2 -> Biome.STONY_SHORE; + case 3 -> MiddleBiome.getBiome(humidity, temperature, weirdness); + case 4, 6 -> weirdness < 0 ? getBeachBiome(temperature) : MiddleBiome.getBiome(humidity, temperature, weirdness); + case 5 -> isWindsweptSavanna(temperature, weirdness) ? Biome.WINDSWEPT_SAVANNA : getBeachBiome(temperature); + default -> getBeachBiome(temperature); + }; } - private @NonNull Biome getLowCoastBionme(int humidity, int temperature, int erosion, double weirdness) { - if (erosion >= 0 && erosion < 3) { - return Biome.STONY_SHORE; - } else if (erosion >= 3 && erosion < 5) { - // Beach Biomes - return getBeachBiome(temperature); - } else if (erosion == 5) { - if (weirdness < 0) { - // Beach Biomes - return getBeachBiome(temperature); - } - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } - } - // Else Beach biomes - return getBeachBiome(temperature); + private @NonNull Biome getLowCoastBiome(int humidity, int temperature, int erosion, double weirdness) { + return switch (erosion) { + case 0, 1, 2 -> Biome.STONY_SHORE; + case 5 -> isWindsweptSavanna(temperature, weirdness) ? Biome.WINDSWEPT_SAVANNA : getBeachBiome(temperature); + default -> getBeachBiome(temperature); + }; } - private @NonNull Biome getHighCoastBionme(int humidity, int temperature, int erosion, double weirdness) { - if (erosion >= 0 && erosion < 5) { - // Middle Biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } else if (erosion == 5) { - if (weirdness < 0 && (temperature == 0 || temperature == 1 || humidity == 4)) { - // Middle biomes - return MiddleBiome.getBiome(humidity, temperature, weirdness); - } - if (weirdness > 0 && (temperature > 2 && temperature <= 4 && humidity >= 0 && humidity <= 4)) { - return Biome.WINDSWEPT_SAVANNA; - } + private @NonNull Biome getHighCoastBiome(int humidity, int temperature, int erosion, double weirdness) { + if (erosion == 5) { + return erosionFiveBiome(humidity, temperature, weirdness, false) + .orElseGet(() -> MiddleBiome.getBiome(humidity, temperature, weirdness)); } - // Middle Biomes return MiddleBiome.getBiome(humidity, temperature, weirdness); } - Biome getBeachBiome(int t) { return switch (t) { case 0 -> Biome.SNOWY_BEACH; @@ -1177,9 +878,8 @@ private Biome getMappedBiome(WorldInfo worldInfo, int x, int y, int z, BiomePara result = getQuadrantBiome(BlockFace.SOUTH_EAST, d); } - if (result == null || result.equals(Biome.CUSTOM)) { + if (result == null) { result = getVanillaBiome(worldInfo, x, y, z, biomeParameterPoint, null); - } // Caves @@ -1194,7 +894,7 @@ private Biome getMappedBiome(WorldInfo worldInfo, int x, int y, int z, BiomePara @Override public List getBiomes(WorldInfo worldInfo) { // Return all of them for now! - return Registry.BIOME.stream().filter(b -> !b.equals(Biome.CUSTOM)).toList(); + return RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).stream().toList(); } /** @@ -1210,26 +910,37 @@ private SortedMap loadQuad(YamlConfiguration config, String secto } for (String ring : config.getStringList(sector)) { String[] split = ring.split(":"); - if (split.length == 2) { - try { - double d = Double.parseDouble(split[0]); - Biome biome = Biome.valueOf(split[1].toUpperCase(Locale.ENGLISH)); - if (biome == null) { - addon.logError(split[1].toUpperCase(Locale.ENGLISH) + " is an unknown biome on this server."); - result.put(d, Biome.CUSTOM); - } else { - // A biome of null means that no alternative biome should be applied - result.put(d, biome); - } - } catch (Exception e) { - addon.logError(sector + ": " + split[0] - + " does not seem to be a double. For integers add a .0 to the end"); - } - } else { + if (split.length != 2) { addon.logError(ring + " must be in the format ratio:biome where ratio is a double."); + continue; + } + try { + double d = Double.parseDouble(split[0]); + // A biome of null means that no alternative biome should be applied + result.put(d, parseBiome(split[1])); + } catch (NumberFormatException e) { + addon.logError(sector + ": " + split[0] + + " does not seem to be a double. For integers add a .0 to the end"); } } return result; } + /** + * Looks up a biome by config name. + * @param name biome name from the config, e.g. PLAINS or minecraft:plains. CUSTOM means "no biome". + * @return the biome, or null if the name is CUSTOM or the biome is unknown on this server + */ + private Biome parseBiome(String name) { + if (CUSTOM_BIOME.equalsIgnoreCase(name)) { + return null; + } + NamespacedKey key = NamespacedKey.fromString(name.toLowerCase(Locale.ENGLISH)); + Biome biome = key == null ? null : RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).get(key); + if (biome == null) { + addon.logError(name.toUpperCase(Locale.ENGLISH) + " is an unknown biome on this server."); + } + return biome; + } + } diff --git a/src/main/java/world/bentobox/boxed/generators/biomes/BoxedBiomeGenerator.java b/src/main/java/world/bentobox/boxed/generators/biomes/BoxedBiomeGenerator.java index b7d664b..87d4c5f 100644 --- a/src/main/java/world/bentobox/boxed/generators/biomes/BoxedBiomeGenerator.java +++ b/src/main/java/world/bentobox/boxed/generators/biomes/BoxedBiomeGenerator.java @@ -1,6 +1,5 @@ package world.bentobox.boxed.generators.biomes; -import org.bukkit.World.Environment; import org.bukkit.block.Biome; import world.bentobox.boxed.Boxed; @@ -13,7 +12,7 @@ public class BoxedBiomeGenerator extends AbstractCopyBiomeProvider { public BoxedBiomeGenerator(Boxed boxed) { - super(boxed, Environment.NORMAL, Biome.OCEAN); + super(boxed, Biome.OCEAN); } } \ No newline at end of file diff --git a/src/main/java/world/bentobox/boxed/generators/biomes/BoxedNetherBiomeGenerator.java b/src/main/java/world/bentobox/boxed/generators/biomes/BoxedNetherBiomeGenerator.java index 504c220..b3cf4df 100644 --- a/src/main/java/world/bentobox/boxed/generators/biomes/BoxedNetherBiomeGenerator.java +++ b/src/main/java/world/bentobox/boxed/generators/biomes/BoxedNetherBiomeGenerator.java @@ -1,6 +1,5 @@ package world.bentobox.boxed.generators.biomes; -import org.bukkit.World.Environment; import org.bukkit.block.Biome; import world.bentobox.boxed.Boxed; @@ -13,7 +12,7 @@ public class BoxedNetherBiomeGenerator extends AbstractCopyBiomeProvider { public BoxedNetherBiomeGenerator(Boxed boxed) { - super(boxed, Environment.NETHER, Biome.BASALT_DELTAS); + super(boxed, Biome.BASALT_DELTAS); } } \ No newline at end of file diff --git a/src/main/java/world/bentobox/boxed/generators/chunks/AbstractBoxedChunkGenerator.java b/src/main/java/world/bentobox/boxed/generators/chunks/AbstractBoxedChunkGenerator.java index 766580a..025c7fd 100644 --- a/src/main/java/world/bentobox/boxed/generators/chunks/AbstractBoxedChunkGenerator.java +++ b/src/main/java/world/bentobox/boxed/generators/chunks/AbstractBoxedChunkGenerator.java @@ -25,7 +25,10 @@ public abstract class AbstractBoxedChunkGenerator extends ChunkGenerator { protected final Boxed addon; - protected static int size; + /** + * Half-width of the repeating seed region in chunks + */ + protected final int size; protected final Map, ChunkStore> chunks = new HashMap<>(); public record ChunkStore(ChunkSnapshot snapshot, List bpEnts, List chests, Map chunkBiomes) {} @@ -33,12 +36,9 @@ public record EntityData(Vector relativeLoc, BlueprintEntity entity) {} public record ChestData(Vector relativeLoc, BlueprintBlock chest) {} - //private final WorldRef wordRefNether; - - public AbstractBoxedChunkGenerator(Boxed addon) { + protected AbstractBoxedChunkGenerator(Boxed addon) { this.addon = addon; size = (int)(addon.getSettings().getIslandDistance() / 16D); // Size is chunks - } /** @@ -51,7 +51,8 @@ public void setChunk(int x, int z, Chunk chunk) { Map chunkBiomes = new HashMap<>(); for (int xx = 0; xx < 16; xx+=4) { for (int zz = 0; zz < 16; zz+=4) { - for (int yy = chunk.getWorld().getMinHeight(); yy < chunk.getWorld().getMaxHeight(); yy+=4) { // TODO: every 4th yy? + // Biomes are stored per 4x4x4 cell, so only sample every 4th block + for (int yy = chunk.getWorld().getMinHeight(); yy < chunk.getWorld().getMaxHeight(); yy+=4) { chunkBiomes.put(new Vector(xx, yy, zz), chunk.getBlock(xx, yy, zz).getBiome()); } } @@ -82,20 +83,22 @@ public boolean canSpawn(World world, int x, int z) /** - * Calculates the repeating value for a given size + * Maps a chunk coordinate back into this generator's repeating seed region + * @param chunkCoord chunk coord + * @return mapped chunk coord in the range [-size, size) + */ + public int repeatCalc(int chunkCoord) { + return repeatCalc(chunkCoord, size); + } + + /** + * Maps a chunk coordinate back into a repeating region of the given half-width * @param chunkCoord chunk coord - * @return mapped chunk coord + * @param size half-width of the region in chunks + * @return mapped chunk coord in the range [-size, size) */ - public static int repeatCalc(int chunkCoord) { + public static int repeatCalc(int chunkCoord, int size) { return Math.floorMod(chunkCoord + size, size*2) - size; - /* - int xx; - if (chunkCoord > 0) { - xx = Math.floorMod(chunkCoord + size, size*2) - size; - } else { - xx = Math.floorMod(chunkCoord - size, -size*2) + size; - } - return xx;*/ } /** @@ -119,19 +122,16 @@ public boolean shouldGenerateSurface() { @Override public boolean shouldGenerateCaves() { return false; - //return this.addon.getSettings().isGenerateCaves(); } @Override public boolean shouldGenerateDecorations() { return false; - //return this.addon.getSettings().isGenerateDecorations(); } @Override public boolean shouldGenerateMobs() { return true; - //return this.addon.getSettings().isGenerateMobs(); } @Override diff --git a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java index 3344f84..c404602 100644 --- a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java +++ b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java @@ -1,6 +1,5 @@ package world.bentobox.boxed.generators.chunks; -import java.util.Map; import java.util.Objects; import java.util.Random; @@ -45,34 +44,38 @@ public BoxedBlockPopulator(Boxed addon) { @Override public void populate(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, LimitedRegion limitedRegion) { - Map, ChunkStore> chunks = addon.getChunkGenerator(worldInfo.getEnvironment()).getChunks(); + AbstractBoxedChunkGenerator gen = addon.getChunkGenerator(worldInfo.getEnvironment()); World world = Bukkit.getWorld(worldInfo.getUID()); - int xx = BoxedChunkGenerator.repeatCalc(chunkX); - int zz = BoxedChunkGenerator.repeatCalc(chunkZ); - Pair coords = new Pair<>(xx, zz); - if (chunks.containsKey(coords)) { - ChunkStore data = chunks.get(coords); - // Paste entities - data.bpEnts().forEach(e -> { - Location l = getLoc(world, e.relativeLoc().clone(), chunkX, chunkZ); - if (limitedRegion.isInRegion(l)) { - Entity ent = limitedRegion.spawnEntity(l, e.entity().getType()); - e.entity().configureEntity(ent); - } - }); - // Fill chests - limitedRegion.getTileEntities().forEach(te -> { - int teX = BoxedChunkGenerator.repeatCalc(te.getX() >> 4); - int teZ = BoxedChunkGenerator.repeatCalc(te.getZ() >> 4); - if (teX == xx && teZ == zz) { - for (ChestData cd : data.chests()) { - Location chestLoc = getLoc(world, cd.relativeLoc().clone(), chunkX, chunkZ); - if (limitedRegion.isInRegion(chestLoc) && te.getLocation().equals(chestLoc)) { - this.setBlockState(te, cd.chest()); - } - } - } - }); + int xx = gen.repeatCalc(chunkX); + int zz = gen.repeatCalc(chunkZ); + ChunkStore data = gen.getChunks().get(new Pair<>(xx, zz)); + if (data == null) { + return; + } + // Paste entities + data.bpEnts().forEach(e -> { + Location l = getLoc(world, e.relativeLoc().clone(), chunkX, chunkZ); + if (limitedRegion.isInRegion(l)) { + Entity ent = limitedRegion.spawnEntity(l, e.entity().getType()); + e.entity().configureEntity(ent); + } + }); + // Fill chests + limitedRegion.getTileEntities().stream() + .filter(te -> gen.repeatCalc(te.getX() >> 4) == xx && gen.repeatCalc(te.getZ() >> 4) == zz) + .forEach(te -> fillChest(world, limitedRegion, data, te, chunkX, chunkZ)); + } + + /** + * Applies the stored chest data that matches this tile entity's location, if any + */ + private void fillChest(World world, LimitedRegion limitedRegion, ChunkStore data, BlockState te, int chunkX, + int chunkZ) { + for (ChestData cd : data.chests()) { + Location chestLoc = getLoc(world, cd.relativeLoc().clone(), chunkX, chunkZ); + if (limitedRegion.isInRegion(chestLoc) && te.getLocation().equals(chestLoc)) { + this.setBlockState(te, cd.chest()); + } } } diff --git a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java index 48547ed..82da84d 100644 --- a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java +++ b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java @@ -87,44 +87,59 @@ protected List getTileEnts(Chunk chunk) { private List setEntities(Collection entities) { List bpEnts = new ArrayList<>(); for (LivingEntity entity: entities) { - BlueprintEntity bpe = new BlueprintEntity(); - bpe.setType(entity.getType()); - bpe.setCustomName(entity.getCustomName()); - if (entity instanceof Villager villager) { - setVillager(villager, bpe); - } - if (entity instanceof Colorable c) { - if (c.getColor() != null) { - bpe.setColor(c.getColor()); - } - } - if (entity instanceof Tameable tameable) { - bpe.setTamed(tameable.isTamed()); - } - if (entity instanceof ChestedHorse chestedHorse) { - bpe.setChest(chestedHorse.isCarryingChest()); - } - // Only set if child. Most animals are adults - if (entity instanceof Ageable ageable && !ageable.isAdult()) { - bpe.setAdult(false); - } - if (entity instanceof AbstractHorse horse) { - bpe.setDomestication(horse.getDomestication()); - bpe.setInventory(new HashMap<>()); - for (int i = 0; i < horse.getInventory().getSize(); i++) { - ItemStack item = horse.getInventory().getItem(i); - if (item != null) { - bpe.getInventory().put(i, item); - } - } - } + bpEnts.add(new EntityData(getLocInChunk(entity.getLocation()), toBlueprintEntity(entity))); + } + return bpEnts; + } + + /** + * Captures the entity's state as a blueprint entity + * @param entity living entity + * @return blueprint entity + */ + private BlueprintEntity toBlueprintEntity(LivingEntity entity) { + BlueprintEntity bpe = new BlueprintEntity(); + bpe.setType(entity.getType()); + bpe.setCustomName(entity.getCustomName()); + if (entity instanceof Villager villager) { + setVillager(villager, bpe); + } + if (entity instanceof Colorable c && c.getColor() != null) { + bpe.setColor(c.getColor()); + } + if (entity instanceof Tameable tameable) { + bpe.setTamed(tameable.isTamed()); + } + if (entity instanceof ChestedHorse chestedHorse) { + bpe.setChest(chestedHorse.isCarryingChest()); + } + // Only set if child. Most animals are adults + if (entity instanceof Ageable ageable && !ageable.isAdult()) { + bpe.setAdult(false); + } + if (entity instanceof AbstractHorse horse) { + setHorse(horse, bpe); + } + if (entity instanceof Horse horse) { + bpe.setStyle(horse.getStyle()); + } + return bpe; + } - if (entity instanceof Horse horse) { - bpe.setStyle(horse.getStyle()); + /** + * Set the horse domestication and inventory + * @param horse - horse + * @param bpe - Blueprint Entity + */ + private void setHorse(AbstractHorse horse, BlueprintEntity bpe) { + bpe.setDomestication(horse.getDomestication()); + bpe.setInventory(new HashMap<>()); + for (int i = 0; i < horse.getInventory().getSize(); i++) { + ItemStack item = horse.getInventory().getItem(i); + if (item != null) { + bpe.getInventory().put(i, item); } - bpEnts.add(new EntityData(getLocInChunk(entity.getLocation()), bpe)); } - return bpEnts; } // Get the location in the chunk @@ -212,8 +227,6 @@ public void generateNoise(WorldInfo worldInfo, Random r, int chunkX, int chunkZ, ChunkStore chunk = this.getChunk(xx,zz); if (chunk == null) { // This should never be needed because islands should abut each other - //cd.setRegion(0, minY, 0, 16, 0, 16, Material.WATER); - //BentoBox.getInstance().logError("No chunks found for " + xx + " " + zz); return; } // Copy the chunk diff --git a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedSeedChunkGenerator.java b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedSeedChunkGenerator.java index 6ba8dab..84d6a18 100644 --- a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedSeedChunkGenerator.java +++ b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedSeedChunkGenerator.java @@ -52,7 +52,6 @@ public boolean shouldGenerateNoise() { @Override public boolean shouldGenerateSurface() { return true; - // return this.addon.getSettings().isGenerateSurface(); } @Override @@ -73,18 +72,17 @@ public boolean shouldGenerateMobs() { @Override public boolean shouldGenerateStructures() { return true; - //return env.equals(Environment.NETHER); // We allow structures in the Nether } @Override protected List getEnts(Chunk chunk) { // These won't be stored - return null; + return List.of(); } @Override protected List getTileEnts(Chunk chunk) { // These won't be stored - return null; + return List.of(); } } diff --git a/src/main/java/world/bentobox/boxed/listeners/AdvancementListener.java b/src/main/java/world/bentobox/boxed/listeners/AdvancementListener.java index a6ac578..9d92889 100644 --- a/src/main/java/world/bentobox/boxed/listeners/AdvancementListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/AdvancementListener.java @@ -10,6 +10,7 @@ import org.bukkit.Bukkit; import org.bukkit.GameMode; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Server; @@ -91,15 +92,16 @@ public void onAdvancement(PlayerAdvancementDoneEvent e) { return; } // Check if player is in the Boxed worlds - if (addon.inWorld(e.getPlayer().getWorld())) { + Player player = e.getPlayer(); + if (addon.inWorld(player.getWorld())) { // Only allow members or higher to get advancements in a box - if (addon.getSettings().isDenyVisitorAdvancements() && !addon.getIslands().getIslandAt(e.getPlayer().getLocation()).map(i -> i.getMemberSet().contains(e.getPlayer().getUniqueId())).orElse(false)) { + if (addon.getSettings().isDenyVisitorAdvancements() && !isMemberAt(player)) { // Remove advancement from player e.getAdvancement().getCriteria().forEach(c -> - e.getPlayer().getAdvancementProgress(e.getAdvancement()).revokeCriteria(c)); - User u = User.getInstance(e.getPlayer()); - if (u != null && addon.getAdvManager().getScore(e.getAdvancement().getKey().getKey()) > 0) { - u.notify("boxed.adv-disallowed", TextVariables.NAME, e.getPlayer().getName(), TextVariables.DESCRIPTION, this.keyToString(u, e.getAdvancement().getKey())); + player.getAdvancementProgress(e.getAdvancement()).revokeCriteria(c)); + User u = User.getInstance(player); + if (addon.getAdvManager().getScore(e.getAdvancement().getKey().getKey()) > 0) { + u.notify("boxed.adv-disallowed", TextVariables.NAME, player.getName(), TextVariables.DESCRIPTION, this.keyToString(u, e.getAdvancement().getKey())); } return; } @@ -113,6 +115,20 @@ public void onAdvancement(PlayerAdvancementDoneEvent e) { } } + /** + * @param player player + * @return true if the player is a member (or higher) of the box they are standing in + */ + private boolean isMemberAt(Player player) { + Location loc = player.getLocation(); + if (loc == null) { + // OfflinePlayer#getLocation() is nullable; an online player always has one + return false; + } + return addon.getIslands().getIslandAt(loc).map(i -> i.getMemberSet().contains(player.getUniqueId())) + .orElse(false); + } + private void tellTeam(User user, NamespacedKey key, int score) { Island island = addon.getIslands().getIsland(addon.getOverWorld(), user); if (island == null) { @@ -253,7 +269,7 @@ public void onPlayerEnterWorld(PlayerChangedWorldEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onTeamJoinTime(TeamJoinedEvent e) { User user = User.getInstance(e.getPlayerUUID()); - if (user != null && addon.getSettings().isOnJoinResetAdvancements() && user.isOnline() + if (addon.getSettings().isOnJoinResetAdvancements() && user.isOnline() && addon.getOverWorld().equals(Util.getWorld(user.getWorld()))) { // Clear and set advancements clearAndSetAdv(user, addon.getSettings().isOnJoinResetAdvancements(), addon.getSettings().getOnJoinGrantAdvancements()); @@ -270,11 +286,10 @@ public void onTeamJoinTime(TeamJoinedEvent e) { public void onTeamLeaveTime(TeamLeaveEvent e) { if (addon.getSettings().isIgnoreAdvancements()) return; User user = User.getInstance(e.getPlayerUUID()); - if (user != null && addon.getSettings().isOnJoinResetAdvancements() && user.isOnline() + if (addon.getSettings().isOnJoinResetAdvancements() && user.isOnline() && addon.getOverWorld().equals(Util.getWorld(user.getWorld()))) { // Clear and set advancements clearAndSetAdv(user, addon.getSettings().isOnLeaveResetAdvancements(), addon.getSettings().getOnLeaveGrantAdvancements()); - } } diff --git a/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java b/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java index bff58c5..bd4eee0 100644 --- a/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java @@ -37,6 +37,8 @@ public class EnderPearlListener implements Listener { /** * @param addon addon */ + private static final String NO_TELEPORT_OUTSIDE = "boxed.general.errors.no-teleport-outside"; + public EnderPearlListener(Boxed addon) { this.addon = addon; } @@ -48,7 +50,7 @@ public void onPlayerTeleport(PlayerTeleportEvent e) { return; // Allow the teleport this one time } if (!addon.inWorld(e.getFrom()) || !e.getPlayer().getGameMode().equals(GameMode.SURVIVAL) - || (e.getTo() != null && !addon.inWorld(e.getTo())) + || !addon.inWorld(e.getTo()) || addon.getIslands().getSpawn(e.getFrom().getWorld()).map(spawn -> spawn.onIsland(e.getTo())).orElse(false) ) { return; @@ -56,16 +58,14 @@ public void onPlayerTeleport(PlayerTeleportEvent e) { User u = User.getInstance(e.getPlayer()); // If the to-location is outside the box, cancel it - if (e.getTo() != null) { - addon.getIslands().getIslandAt(e.getTo()).ifPresent(i -> { - if (!i.onIsland(e.getTo())) { - u.sendMessage("boxed.general.errors.no-teleport-outside"); - addon.logWarning(e.getPlayer().getName() + " tried to teleport outside of their box from " - + e.getFrom() + " to " + e.getTo()); - e.setCancelled(true); - } - }); - } + addon.getIslands().getIslandAt(e.getTo()).ifPresent(i -> { + if (!i.onIsland(e.getTo())) { + u.sendMessage(NO_TELEPORT_OUTSIDE); + addon.logWarning(e.getPlayer().getName() + " tried to teleport outside of their box from " + + e.getFrom() + " to " + e.getTo()); + e.setCancelled(true); + } + }); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -80,46 +80,50 @@ public void onEnderPearlLand(ProjectileHitEvent e) { // Moving box is allowed Location l = e.getHitBlock().getRelative(BlockFace.UP).getLocation(); World w = e.getHitBlock().getWorld(); - if (e.getEntity() instanceof EnderPearl ep && ep.getShooter() instanceof Player player) { - User u = User.getInstance(player); - // Check if enderpearl is inside or outside the box - // Get user's box - Island is = addon.getIslands().getIsland(w, u); - if (is == null) { - return; // Nothing to do - } + if (!(e.getEntity() instanceof EnderPearl ep) || !(ep.getShooter() instanceof Player player)) { + return; + } + User u = User.getInstance(player); + // Check if enderpearl is inside or outside the box + // Get user's box + Island is = addon.getIslands().getIsland(w, u); + if (is == null) { + return; // Nothing to do + } + // Get the box that the player is in and check that it is their box + addon.getIslands().getIslandAt(u.getLocation()) + .filter(fromIsland -> is.getUniqueId().equals(fromIsland.getUniqueId())) + .ifPresent(fromIsland -> handlePearlLanding(e, u, fromIsland, l)); + } - // Get the box that the player is in - addon.getIslands().getIslandAt(u.getLocation()).ifPresent(fromIsland -> { - // Check that it is their box - if (!is.getUniqueId().equals(fromIsland.getUniqueId())) { - return; + /** + * Handles an ender pearl thrown by a player from inside their own box. + * @param e projectile hit event + * @param u the thrower + * @param fromIsland the thrower's box + * @param l where the pearl landed + */ + private void handlePearlLanding(ProjectileHitEvent e, User u, Island fromIsland, Location l) { + // Find where the pearl landed + addon.getIslands().getIslandAt(l).ifPresentOrElse(toIsland -> { + if (fromIsland.getUniqueId().equals(toIsland.getUniqueId())) { + if (!toIsland.onIsland(l)) { + // Moving is allowed + moveBox(u, fromIsland, l); + Util.teleportAsync(u.getPlayer(), l, TeleportCause.ENDER_PEARL); } - // Find where the pearl landed - addon.getIslands().getIslandAt(l).ifPresentOrElse(toIsland -> { - if (fromIsland.getUniqueId().equals(toIsland.getUniqueId())) { - if (!toIsland.onIsland(l)) { - // Moving is allowed - moveBox(u, fromIsland, l); - Util.teleportAsync(player, l, TeleportCause.ENDER_PEARL); - } - } else { - // Different box. This is never allowed. Cancel the throw - e.setCancelled(true); - u.sendMessage("boxed.general.errors.no-teleport-outside"); - addon.logWarning("Enderpearl: " + player.getName() + " tried to teleport between boxes from " - + fromIsland.getCenter() + " to " + toIsland.getCenter()); - } - }, () -> { - // No box. This is never allowed. Cancel the throw - e.setCancelled(true); - u.sendMessage("boxed.general.errors.no-teleport-outside"); - addon.logWarning("Enderpearl: " + player.getName() + " tried to teleport between boxes from " - + fromIsland.getCenter() + " to some place outside"); - }); - - }); - } + } else { + // Different box. This is never allowed. Cancel the throw + cancelThrow(e, u, fromIsland, toIsland.getCenter()); + } + }, () -> cancelThrow(e, u, fromIsland, "some place outside")); // No box. This is never allowed. Cancel the throw + } + + private void cancelThrow(ProjectileHitEvent e, User u, Island fromIsland, Object destination) { + e.setCancelled(true); + u.sendMessage(NO_TELEPORT_OUTSIDE); + addon.logWarning("Enderpearl: " + u.getName() + " tried to teleport between boxes from " + + fromIsland.getCenter() + " to " + destination); } diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java index 18577da..615bdf7 100644 --- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java @@ -30,7 +30,6 @@ import org.bukkit.block.structure.StructureRotation; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -101,14 +100,12 @@ public class NewAreaListener implements Listener { private boolean pasting = true; private static final Gson gson = new Gson(); private static final String TODO = "ToDo"; + private static final String MINECRAFT = "minecraft:"; private static final String COULD_NOT_LOAD = "Could not load "; // Database handler for structure data private final Database handler; private final Database toPlace; - private static String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_'); - private static String pluginPackageName; - /** * Constructor for NewAreaListener. * Initializes structure files, databases, and starts the structure printer. @@ -117,7 +114,6 @@ public class NewAreaListener implements Listener { */ public NewAreaListener(Boxed addon) { this.addon = addon; - pluginPackageName = addon.getClass().getPackage().getName(); // Save the default structures file from the jar addon.saveResource("structures.yml", false); // Load the config @@ -140,13 +136,13 @@ private void runStructurePrinter() { // Run through all the structures in the Jar and register them with the server for (String js : JAR_STRUCTURES) { addon.saveResource("structures/" + js + ".nbt", false); - File structureFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt"); + File nbtFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt"); try { - Structure s = Bukkit.getStructureManager().loadStructure(structureFile); - Bukkit.getStructureManager().registerStructure(NamespacedKey.fromString("minecraft:boxed/" + js), s); + Structure s = Bukkit.getStructureManager().loadStructure(nbtFile); + Bukkit.getStructureManager().registerStructure(NamespacedKey.fromString(MINECRAFT + "boxed/" + js), s); addon.log("Loaded " + js + ".nbt"); } catch (IOException e) { - addon.logError("Error trying to load " + structureFile.getAbsolutePath()); + addon.logError("Error trying to load " + nbtFile.getAbsolutePath()); addon.getPlugin().logStacktrace(e); } } @@ -266,26 +262,27 @@ public void onPlayerMove(PlayerMoveEvent e) { return; } // Check where the player is - addon.getIslands().getIslandAt(e.getTo()).ifPresent(island -> { - // See if island is in cache - final String islandId = island.getUniqueId(); - IslandStructures is = getIslandStructData(islandId); - // Check if player is in any of the structures - Map structures = e.getTo().getWorld().getEnvironment().equals(Environment.NETHER) - ? is.getNetherStructureBoundingBoxMap() - : is.getStructureBoundingBoxMap(); - for (Map.Entry en : structures.entrySet()) { - if (en.getKey().contains(e.getTo().toVector())) { - for (String s : STRUCTURES) { - if (en.getValue().startsWith(s)) { - giveAdvFromCriteria(e.getPlayer(), s); - } - } - // STRUCTURES.stream().filter(en.getValue()::startsWith).forEach(s -> - // giveAdvFromCriteria(e.getPlayer(), s)); - } + addon.getIslands().getIslandAt(e.getTo()).ifPresent(island -> checkStructures(e.getPlayer(), island, e.getTo())); + } + + /** + * Awards structure advancements if the location is inside any of the island's structures. + * @param player player to award + * @param island island the player is on + * @param to location the player has moved to + */ + private void checkStructures(Player player, Island island, Location to) { + // See if island is in cache + IslandStructures is = getIslandStructData(island.getUniqueId()); + // Check if player is in any of the structures + Map structures = to.getWorld().getEnvironment().equals(Environment.NETHER) + ? is.getNetherStructureBoundingBoxMap() + : is.getStructureBoundingBoxMap(); + for (Map.Entry en : structures.entrySet()) { + if (en.getKey().contains(to.toVector())) { + STRUCTURES.stream().filter(en.getValue()::startsWith).forEach(s -> giveAdvFromCriteria(player, s)); } - }); + } } /** @@ -361,11 +358,11 @@ public void onIslandDeleted(IslandDeleteEvent event) { islandStructureCache.remove(deletedIslandId); // Remove from active build queue so we don't paste into a deleted island - itemsToBuild.removeIf(record -> event.getIsland().inIslandSpace(record.location())); + itemsToBuild.removeIf(rec -> event.getIsland().inIslandSpace(rec.location())); // Remove from in-memory pending structures for (List records : pending.values()) { - records.removeIf(record -> event.getIsland().inIslandSpace(record.location())); + records.removeIf(rec -> event.getIsland().inIslandSpace(rec.location())); } pending.values().removeIf(List::isEmpty); @@ -373,7 +370,7 @@ public void onIslandDeleted(IslandDeleteEvent event) { Map, List> readyToBuild = loadToDos().getReadyToBuild(); boolean dbChanged = false; for (List records : readyToBuild.values()) { - if (records.removeIf(record -> event.getIsland().inIslandSpace(record.location()))) { + if (records.removeIf(rec -> event.getIsland().inIslandSpace(rec.location()))) { dbChanged = true; } } @@ -443,7 +440,7 @@ private void place(ConfigurationSection section, Location center, Environment en // Check the structure exists Structure structure = Bukkit.getStructureManager() - .loadStructure(NamespacedKey.fromString("minecraft:" + name)); + .loadStructure(NamespacedKey.fromString(MINECRAFT + name)); if (structure == null) { BentoBox.getInstance().logError(COULD_NOT_LOAD + name); return; @@ -456,17 +453,15 @@ private void place(ConfigurationSection section, Location center, Environment en int z = Integer.parseInt(coords[2].strip()) + center.getBlockZ(); Location location = new Location(world, x, y, z); // Structure will be placed at location - readyToBuild.computeIfAbsent(new Pair<>(x >> 4, z >> 4), k -> new ArrayList<>()) - .add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs, - Collections.emptyMap())); - this.itemsToBuild - .add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs, - Collections.emptyMap())); + StructureRecord sr = new StructureRecord(name, MINECRAFT + name, location, rotation, mirror, noMobs, + Collections.emptyMap()); + readyToBuild.computeIfAbsent(new Pair<>(x >> 4, z >> 4), k -> new ArrayList<>()).add(sr); + this.itemsToBuild.add(sr); } else { addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(coords)); } } - // Load any todo's and add the ones from this new island to the list + // Load the pending structures and add the ones from this new island to the list ToBePlacedStructures tbd = this.loadToDos(); Map, List> mergedMap = tbd.getReadyToBuild(); readyToBuild.forEach((key, value) -> mergedMap.merge(key, value, (list1, list2) -> { @@ -512,26 +507,38 @@ public static BoundingBox removeJigsaw(StructureRecord item) { }; BoundingBox bb = BoundingBox.of(loc, otherCorner); + boolean underwater = key.contains("underwater_ruin"); for (int x = (int) bb.getMinX(); x <= bb.getMaxX(); x++) { for (int y = (int) bb.getMinY(); y <= bb.getMaxY(); y++) { for (int z = (int) bb.getMinZ(); z <= bb.getMaxZ(); z++) { - Block b = loc.getWorld().getBlockAt(x, y, z); - if (b.getType().equals(Material.JIGSAW)) { - // I would like to read the data from the block and do something with it! - processJigsaw(b, structureRotation, !item.noMobs()); - } else if (b.getType().equals(Material.STRUCTURE_BLOCK)) { - processStructureBlock(b); - } - // Set water blocks for underwater ruins - if (key.contains("underwater_ruin") && b.getType().equals(Material.AIR)) { - b.setType(Material.WATER); - } + processBlock(loc.getWorld().getBlockAt(x, y, z), structureRotation, !item.noMobs(), underwater); } } } return bb; } + /** + * Processes a single block of a pasted structure: resolves jigsaw and structure blocks and + * fills air with water for underwater ruins. + * @param b block + * @param structureRotation the structure's rotation + * @param pasteMobs whether mobs should be spawned from jigsaw blocks + * @param underwater whether the structure is an underwater ruin + */ + private static void processBlock(Block b, StructureRotation structureRotation, boolean pasteMobs, + boolean underwater) { + if (b.getType().equals(Material.JIGSAW)) { + processJigsaw(b, structureRotation, pasteMobs); + } else if (b.getType().equals(Material.STRUCTURE_BLOCK)) { + processStructureBlock(b); + } + // Set water blocks for underwater ruins + if (underwater && b.getType().equals(Material.AIR)) { + b.setType(Material.WATER); + } + } + /** * Processes a structure block, possibly spawning entities or filling chests with loot. * @@ -551,13 +558,11 @@ private static void processStructureBlock(Block b) { Block downBlock = b.getRelative(BlockFace.DOWN); if (downBlock.getType().equals(Material.CHEST)) { Chest chest = (Chest) downBlock.getState(); - // TODO: for now just give treasure + // Only buried treasure loot is supported at the moment chest.setLootTable(LootTables.BURIED_TREASURE.getLootTable()); chest.update(); - if (chest.getBlockData() instanceof Waterlogged wl) { - if (wl.isWaterlogged()) { - b.setType(Material.WATER); - } + if (chest.getBlockData() instanceof Waterlogged wl && wl.isWaterlogged()) { + b.setType(Material.WATER); } } } @@ -624,10 +629,7 @@ private static void spawnMob(Block b, BoxedJigsawBlock bjb) { } // Spawn it if (type != null) { - Entity e = b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type); - if (e != null) { - e.setPersistent(true); - } + b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type).setPersistent(true); } } diff --git a/src/main/java/world/bentobox/boxed/objects/BoxedStructureBlock.java b/src/main/java/world/bentobox/boxed/objects/BoxedStructureBlock.java index 0d1a835..65d1905 100644 --- a/src/main/java/world/bentobox/boxed/objects/BoxedStructureBlock.java +++ b/src/main/java/world/bentobox/boxed/objects/BoxedStructureBlock.java @@ -12,8 +12,12 @@ * */ public class BoxedStructureBlock { - //{author:"LadyAgnes",ignoreEntities:1b,integrity:1.0f,metadata:"drowned",mirror:"NONE",mode:"DATA",name:"",posX:0,posY:1,posZ:0,powered:0b,rotation:"NONE",seed:0L,showair:0b - //,showboundingbox:1b,sizeX:0,sizeY:0,sizeZ:0} + /* + * Example NBT payload this class is deserialized from: + * author "LadyAgnes", ignoreEntities 1b, integrity 1.0f, metadata "drowned", mirror "NONE", mode "DATA", + * name "", posX 0, posY 1, posZ 0, powered 0b, rotation "NONE", seed 0L, showair 0b, showboundingbox 1b, + * sizeX 0, sizeY 0, sizeZ 0 + */ @Expose private String author; @Expose diff --git a/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java b/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java index a36eb7a..62ab53e 100644 --- a/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java +++ b/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java @@ -61,11 +61,11 @@ class AdvancementsManagerTest extends CommonTestSetup { private AbstractDatabaseHandler h; @SuppressWarnings("unchecked") - @Override + /** + * Runs after {@link CommonTestSetup#setUp()} (JUnit runs superclass @BeforeEach methods first) + */ @BeforeEach - public void setUp() throws Exception { - super.setUp(); - + void setUpManager() throws Exception { // Database static mock (local — CommonTestSetup does not handle this one) h = mock(AbstractDatabaseHandler.class); mockedDatabaseSetup = Mockito.mockStatic(DatabaseSetup.class); diff --git a/src/test/java/world/bentobox/boxed/CommonTestSetup.java b/src/test/java/world/bentobox/boxed/CommonTestSetup.java index 0962436..26fc7d6 100644 --- a/src/test/java/world/bentobox/boxed/CommonTestSetup.java +++ b/src/test/java/world/bentobox/boxed/CommonTestSetup.java @@ -21,22 +21,16 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.entity.Player.Spigot; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.mockbukkit.mockbukkit.MockBukkit; @@ -131,7 +125,7 @@ public abstract class CommonTestSetup { @BeforeEach - public void setUp() throws Exception { + public void setUp() { // Processes the @Mock annotations and initializes the field closeable = MockitoAnnotations.openMocks(this); server = MockBukkit.mock(); @@ -223,14 +217,13 @@ public void setUp() throws Exception { //Util mockedUtil = Mockito.mockStatic(Util.class, Mockito.CALLS_REAL_METHODS); - mockedUtil.when(() -> Util.getWorld(any())).thenReturn(mock(World.class)); + World utilWorld = mock(World.class); + mockedUtil.when(() -> Util.getWorld(any())).thenReturn(utilWorld); Util.setPlugin(plugin); // Util mockedUtil.when(() -> Util.findFirstMatchingEnum(any(), any())).thenCallRealMethod(); - // Util translate color codes (used in user translate methods) - //mockedUtil.when(() -> translateColorCodes(anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); - + // Server & Scheduler mockedBukkit.when(Bukkit::getScheduler).thenReturn(sch); @@ -287,8 +280,8 @@ public void checkSpigotMessage(String expectedMessage, int expectedOccurrences) List capturedMessages = captor.getAllValues(); // Count the number of occurrences of the expectedMessage in the captured messages - long actualOccurrences = capturedMessages.stream().map(component -> component.toLegacyText()) // Convert each TextComponent to plain text - .filter(messageText -> messageText.contains(expectedMessage)) // Check if the message contains the expected text + long actualOccurrences = capturedMessages.stream() + .filter(component -> component.toLegacyText().contains(expectedMessage)) // Check if the message contains the expected text .count(); // Count how many times the expected message appears // Assert that the number of occurrences matches the expectedOccurrences @@ -296,23 +289,4 @@ public void checkSpigotMessage(String expectedMessage, int expectedOccurrences) actualOccurrences, "Expected message occurrence mismatch: " + expectedMessage); } - /** - * Get the exploded event - * @param entity - * @param l - * @param list - * @return - */ - public EntityExplodeEvent getExplodeEvent(Entity entity, Location l, List list) { - //return new EntityExplodeEvent(entity, l, list, 0, null); - return new EntityExplodeEvent(entity, l, list, 0, null); - } - - public PlayerDeathEvent getPlayerDeathEvent(Player player, List drops, int droppedExp, int newExp, - int newTotalExp, int newLevel, @Nullable String deathMessage) { - //Technically this null is not allowed, but it works right now - return new PlayerDeathEvent(player, null, drops, droppedExp, newExp, - newTotalExp, newLevel, deathMessage); - } - } diff --git a/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java b/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java index 3eb15ec..78e33f6 100644 --- a/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java +++ b/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java @@ -37,7 +37,7 @@ class PlaceholdersManagerTest extends CommonTestSetup { @Override @BeforeEach - public void setUp() throws Exception { + public void setUp() { super.setUp(); uuid = UUID.randomUUID(); diff --git a/src/test/java/world/bentobox/boxed/SettingsTest.java b/src/test/java/world/bentobox/boxed/SettingsTest.java index fb8aca4..200b1b2 100644 --- a/src/test/java/world/bentobox/boxed/SettingsTest.java +++ b/src/test/java/world/bentobox/boxed/SettingsTest.java @@ -26,7 +26,7 @@ class SettingsTest extends CommonTestSetup { @Override @BeforeEach - public void setUp() throws Exception { + public void setUp() { super.setUp(); s = new Settings(); } diff --git a/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java b/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java index 1a64b38..b648dfb 100644 --- a/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java +++ b/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java @@ -38,7 +38,7 @@ class AdminPlaceStructureCommandTest extends CommonTestSetup { private User user; @BeforeEach - public void setUpCommand() { + void setUpCommand() { addon = mock(Boxed.class); CompositeCommand parent = mock(CompositeCommand.class); diff --git a/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java b/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java index 2217520..3eafe0c 100644 --- a/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java +++ b/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java @@ -1,7 +1,7 @@ package world.bentobox.boxed.generators.biomes; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -19,7 +19,6 @@ import world.bentobox.boxed.Boxed; import world.bentobox.boxed.CommonTestSetup; import world.bentobox.boxed.Settings; -import world.bentobox.boxed.WhiteBox; import world.bentobox.boxed.generators.chunks.AbstractBoxedChunkGenerator; import world.bentobox.boxed.generators.chunks.AbstractBoxedChunkGenerator.ChunkStore; @@ -36,7 +35,7 @@ class CopyBiomeProviderTest extends CommonTestSetup { private Boxed addon; @BeforeEach - public void setUpProvider() { + void setUpProvider() { addon = mock(Boxed.class); Settings settings = mock(Settings.class); when(addon.getSettings()).thenReturn(settings); @@ -48,8 +47,9 @@ public void setUpProvider() { worldInfo = mock(WorldInfo.class); when(worldInfo.getEnvironment()).thenReturn(Environment.NORMAL); - // 400 / 16 = 25 chunks half-width. repeatCalc needs size > 0. - WhiteBox.setInternalState(AbstractBoxedChunkGenerator.class, "size", 25); + // 400 / 16 = 25 chunks half-width. + when(chunkGen.repeatCalc(anyInt())) + .thenAnswer(inv -> AbstractBoxedChunkGenerator.repeatCalc(inv.getArgument(0), 25)); gen = new BoxedBiomeGenerator(addon); } @@ -78,7 +78,7 @@ void testReturnsDefaultBiomeAndWarnsWhenChunkMissing() { assertEquals(Biome.OCEAN, gen.getBiome(worldInfo, 3, 64, 5)); // The missing snapshot is logged as a warning - verify(plugin).logWarning(eq("Snapshot at 0 0 is not stored")); + verify(plugin).logWarning("Snapshot at 0 0 is not stored"); } @Test diff --git a/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java b/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java index a665aa7..7962bd9 100644 --- a/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java +++ b/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java @@ -4,18 +4,22 @@ import org.junit.jupiter.api.Test; -import world.bentobox.boxed.WhiteBox; - /** - * Tests {@link AbstractBoxedChunkGenerator#repeatCalc(int)} - the function that + * Tests {@link AbstractBoxedChunkGenerator#repeatCalc(int, int)} - the function that * maps an arbitrary chunk coordinate back into the repeating seed region * {@code [-size, size)}. This is what makes the small captured seed area tile * infinitely across the game world. */ class RepeatCalcTest { + private int size; + private void setSize(int size) { - WhiteBox.setInternalState(AbstractBoxedChunkGenerator.class, "size", size); + this.size = size; + } + + private int repeatCalc(int c) { + return AbstractBoxedChunkGenerator.repeatCalc(c, size); } @Test @@ -23,7 +27,7 @@ void testIdentityWithinRange() { setSize(5); // Coordinates already inside [-size, size) are returned unchanged for (int c = -5; c < 5; c++) { - assertEquals(c, AbstractBoxedChunkGenerator.repeatCalc(c), "coord " + c); + assertEquals(c, repeatCalc(c), "coord " + c); } } @@ -31,26 +35,26 @@ void testIdentityWithinRange() { void testWrapsAboveRange() { setSize(5); // size maps back to -size, and it keeps wrapping with period 2*size - assertEquals(-5, AbstractBoxedChunkGenerator.repeatCalc(5)); - assertEquals(-4, AbstractBoxedChunkGenerator.repeatCalc(6)); - assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(10)); - assertEquals(4, AbstractBoxedChunkGenerator.repeatCalc(14)); - assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(19)); + assertEquals(-5, repeatCalc(5)); + assertEquals(-4, repeatCalc(6)); + assertEquals(0, repeatCalc(10)); + assertEquals(4, repeatCalc(14)); + assertEquals(-1, repeatCalc(19)); } @Test void testWrapsBelowRange() { setSize(5); - assertEquals(-5, AbstractBoxedChunkGenerator.repeatCalc(-5)); - assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(-10)); - assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(-11)); + assertEquals(-5, repeatCalc(-5)); + assertEquals(0, repeatCalc(-10)); + assertEquals(-1, repeatCalc(-11)); } @Test void testResultAlwaysWithinRange() { setSize(8); for (int c = -100; c <= 100; c++) { - int r = AbstractBoxedChunkGenerator.repeatCalc(c); + int r = repeatCalc(c); assertEquals(true, r >= -8 && r < 8, "coord " + c + " mapped out of range to " + r); } } @@ -58,10 +62,10 @@ void testResultAlwaysWithinRange() { @Test void testDifferentSize() { setSize(1); - // With size 1 the region is just {-1, 0} - assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(0)); - assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(-1)); - assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(1)); - assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(2)); + // With size 1 the region only contains the two chunks -1 and 0 + assertEquals(0, repeatCalc(0)); + assertEquals(-1, repeatCalc(-1)); + assertEquals(-1, repeatCalc(1)); + assertEquals(0, repeatCalc(2)); } } diff --git a/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java b/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java index e36dc70..6072283 100644 --- a/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java +++ b/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java @@ -1,11 +1,13 @@ package world.bentobox.boxed.listeners; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -46,6 +48,7 @@ import world.bentobox.boxed.Boxed; import world.bentobox.boxed.CommonTestSetup; import world.bentobox.boxed.Settings; +import world.bentobox.boxed.objects.IslandAdvancements; /** * @author tastybento @@ -76,7 +79,7 @@ class AdvancementListenerTest extends CommonTestSetup { @Override @BeforeEach - public void setUp() throws Exception { + public void setUp() { super.setUp(); // Local User mock (parent only cached mockPlayer, not our local player mock) @@ -219,26 +222,34 @@ private PlayerPortalEvent portalEvent(TeleportCause cause) { @Test void testOnPortalNetherNoException() { - // With null netherAdvancement fields, giveAdv short-circuits — we just assert no throw. - listener.onPortal(portalEvent(TeleportCause.NETHER_PORTAL)); + // With null netherAdvancement fields, giveAdv short-circuits — assert no throw and that the cause was inspected. + PlayerPortalEvent e = portalEvent(TeleportCause.NETHER_PORTAL); + assertDoesNotThrow(() -> listener.onPortal(e)); + verify(e).getCause(); } @Test void testOnPortalEndNoException() { - listener.onPortal(portalEvent(TeleportCause.END_PORTAL)); + PlayerPortalEvent e = portalEvent(TeleportCause.END_PORTAL); + assertDoesNotThrow(() -> listener.onPortal(e)); + verify(e, atLeastOnce()).getCause(); } @Test void testOnPortalNotSurvival() { when(player.getGameMode()).thenReturn(GameMode.CREATIVE); - // should early return — no NPE even though we don't stub the cause - listener.onPortal(portalEvent(TeleportCause.NETHER_PORTAL)); + // should early return before the cause is ever inspected + PlayerPortalEvent e = portalEvent(TeleportCause.NETHER_PORTAL); + listener.onPortal(e); + verify(e, never()).getCause(); } @Test void testOnPortalNotInWorld() { when(addon.inWorld(world)).thenReturn(false); - listener.onPortal(portalEvent(TeleportCause.NETHER_PORTAL)); + PlayerPortalEvent e = portalEvent(TeleportCause.NETHER_PORTAL); + listener.onPortal(e); + verify(e, never()).getCause(); } // ---------- syncAdvancements ---------- @@ -261,8 +272,8 @@ void testSyncAdvancementsNoIsland() { void testSyncAdvancementsSizeIncreased() { when(advManager.checkIslandSize(island)).thenReturn(3); // Return a non-null IslandAdvancements stub for grantAdv's iteration - when(advManager.getIsland(island)) - .thenReturn(mock(world.bentobox.boxed.objects.IslandAdvancements.class)); + IslandAdvancements islandAdvancements = mock(IslandAdvancements.class); + when(advManager.getIsland(island)).thenReturn(islandAdvancements); listener.syncAdvancements(user); verify(user).sendMessage(eq("boxed.size-changed"), anyString(), eq("3")); verify(player).playSound(eq(playerLocation), eq(Sound.ENTITY_PLAYER_LEVELUP), org.mockito.ArgumentMatchers.anyFloat(), org.mockito.ArgumentMatchers.anyFloat()); @@ -271,8 +282,8 @@ void testSyncAdvancementsSizeIncreased() { @Test void testSyncAdvancementsSizeDecreased() { when(advManager.checkIslandSize(island)).thenReturn(-2); - when(advManager.getIsland(island)) - .thenReturn(mock(world.bentobox.boxed.objects.IslandAdvancements.class)); + IslandAdvancements islandAdvancements = mock(IslandAdvancements.class); + when(advManager.getIsland(island)).thenReturn(islandAdvancements); listener.syncAdvancements(user); verify(user).sendMessage(eq("boxed.size-decreased"), anyString(), eq("2")); } diff --git a/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java b/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java index 90ce454..735fdee 100644 --- a/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java +++ b/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java @@ -81,7 +81,7 @@ class EnderPearlListenerTest extends CommonTestSetup { @Override @BeforeEach - public void setUp() throws Exception { + public void setUp() { super.setUp(); // Local static mock for User (parent already wired User.setPlugin + one cached mockPlayer) @@ -167,7 +167,7 @@ void testEnderPearlListener() { */ @Test void testOnPlayerTeleportNotAllowed() { - PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CONSUMABLE_EFFECT); epl.onPlayerTeleport(e); assertTrue(e.isCancelled()); verify(user).sendMessage("boxed.general.errors.no-teleport-outside"); @@ -179,7 +179,7 @@ void testOnPlayerTeleportNotAllowed() { @Test void testOnPlayerTeleportNotSurvival() { when(player.getGameMode()).thenReturn(GameMode.CREATIVE); - PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CONSUMABLE_EFFECT); epl.onPlayerTeleport(e); assertFalse(e.isCancelled()); verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); @@ -191,7 +191,7 @@ void testOnPlayerTeleportNotSurvival() { @Test void testOnPlayerTeleportNullTo() { when(player.getGameMode()).thenReturn(GameMode.CREATIVE); - PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, null, TeleportCause.CHORUS_FRUIT); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, null, TeleportCause.CONSUMABLE_EFFECT); epl.onPlayerTeleport(e); assertFalse(e.isCancelled()); verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); @@ -203,7 +203,7 @@ void testOnPlayerTeleportNullTo() { @Test void testOnPlayerTeleportToSpawn() { when(spawn.onIsland(any())).thenReturn(true); - PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CONSUMABLE_EFFECT); epl.onPlayerTeleport(e); assertFalse(e.isCancelled()); verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); @@ -216,7 +216,7 @@ void testOnPlayerTeleportToSpawn() { void testOnPlayerTeleportNotInWorldAllowed() { when(addon.inWorld(any(World.class))).thenReturn(false); when(addon.inWorld(any(Location.class))).thenReturn(false); - PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CONSUMABLE_EFFECT); epl.onPlayerTeleport(e); assertFalse(e.isCancelled()); verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside");