From e5cae042dcdbcbc2c6606853c8305b86694863a8 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Fri, 17 May 2024 20:42:47 +0200 Subject: [PATCH 01/21] Try with custom biomes. --- README.md | 6 +- Underilla-Core/build.gradle | 2 +- Underilla-Spigot/build.gradle | 34 ------- Underilla-Spigot/build.gradle.kts | 53 +++++++++++ .../mc/underilla/spigot/Underilla.java | 4 +- .../generation/NMSChunkGeneratorDelegate.java | 89 +++++++++++++++++++ .../generation/UnderillaChunkGenerator.java | 8 +- .../spigot/generation/WorldInitListener.java | 30 +++++++ .../spigot/impl/BukkitChunkReader.java | 20 ++++- .../src/main/resources/config.yml | 2 + 10 files changed, 205 insertions(+), 43 deletions(-) delete mode 100644 Underilla-Spigot/build.gradle create mode 100644 Underilla-Spigot/build.gradle.kts create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java diff --git a/README.md b/README.md index 428fb37..b0d67d4 100644 --- a/README.md +++ b/README.md @@ -91,5 +91,7 @@ If you're going to plug your custom WorldPainter world into Underilla, consider Create a working jar with `./gradlew buildDependents` ## TODO -Build-in pre-generation system. -Allow to generate the 2nd world on the fly. +- Build-in pre-generation system. +- Allow to generate the 2nd world on the fly. +- Feature generation filter. +- Transfer custom biomes. \ No newline at end of file diff --git a/Underilla-Core/build.gradle b/Underilla-Core/build.gradle index 1f03f30..6276cce 100644 --- a/Underilla-Core/build.gradle +++ b/Underilla-Core/build.gradle @@ -3,7 +3,7 @@ plugins { } group = 'com.Jkantrell.mc' -version = '1.4.0' +version = '1.4.3' java.sourceCompatibility = JavaVersion.VERSION_17 repositories { diff --git a/Underilla-Spigot/build.gradle b/Underilla-Spigot/build.gradle deleted file mode 100644 index 74e6d73..0000000 --- a/Underilla-Spigot/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - id 'java-library' - id "com.github.johnrengelman.shadow" version "8.1.1" - // id "io.papermc.paperweight.userdev" version "1.5.9" // paperweight -} - -repositories { - mavenLocal() - mavenCentral() - maven { url = uri("https://repo.papermc.io/repository/maven-public/") } - maven { url = uri('https://hub.spigotmc.org/nexus/content/repositories/snapshots/') } - maven { url = uri('https://oss.sonatype.org/content/groups/public/') } - maven { url = uri('https://jitpack.io') } -} - -group = 'com.Jkantrell.mc' -version = '1.4.2' -java.sourceCompatibility = JavaVersion.VERSION_17 - -dependencies { - compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT") // without paperweight - // paperweight.paperDevBundle("1.20.2-R0.1-SNAPSHOT") // paperweight - implementation 'com.jkantrell:Yamlizer:main-SNAPSHOT' - implementation project(':Underilla-Core') -} - -shadowJar { - minimize() - archiveFileName.set("${project.name}-${project.version}.jar") - relocate("jakarta.annotation","${project.group}.jakarta.annotation") -} - -// tasks.build.dependsOn tasks.reobfJar // paperweight -tasks.build.dependsOn tasks.shadowJar // without paperweight \ No newline at end of file diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts new file mode 100644 index 0000000..ad8f0b3 --- /dev/null +++ b/Underilla-Spigot/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + `java-library` + id("io.github.goooler.shadow") version "8.1.7" + id("io.papermc.paperweight.userdev") version "1.7.0" // paperweight // Check for new versions at https://plugins.gradle.org/plugin/io.papermc.paperweight.userdev +} + +group = "com.Jkantrell.mc" +version = "1.4.3" + +repositories { + mavenLocal() + mavenCentral() + maven ("https://repo.papermc.io/repository/maven-public/") + maven ("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + maven ("https://oss.sonatype.org/content/groups/public/") + maven ("https://jitpack.io") +} + + +dependencies { + // compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") // without paperweight + paperweight.paperDevBundle("1.20.4-R0.1-SNAPSHOT") // paperweight + implementation("com.jkantrell:Yamlizer:main-SNAPSHOT") + implementation(project(":Underilla-Core")) +} + +// tasks.build.dependsOn tasks.reobfJar // paperweight +// tasks.build.dependsOn tasks.shadowJar // without paperweight + +tasks { + shadowJar { + minimize() + val prefix = "${project.group}.lib" + sequenceOf( + "co.aikar", + "org.bstats", + "jakarta.annotation", + ).forEach { pkg -> + relocate(pkg, "$prefix.$pkg") + } + } + + assemble { + // dependsOn(shadowJar) // Not needed, probably because reobfJar depends on shadowJar + dependsOn(reobfJar) + } + + compileJava { + // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. + // See https://openjdk.java.net/jeps/247 for more information. + options.release.set(17) + } +} \ No newline at end of file diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java index 726ef1b..da9a7e6 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java @@ -14,7 +14,6 @@ public final class Underilla extends JavaPlugin { - private static Underilla plugin; public static final Config CONFIG = new Config(""); private BukkitWorldReader worldReader_ = null; private @Nullable BukkitWorldReader worldCavesReader_ = null; @@ -32,7 +31,6 @@ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { @Override public void onEnable() { - plugin = this; // Setting default config this.saveResource("config.yml", false); @@ -82,5 +80,5 @@ public void onDisable() { } } - public static Underilla getPlugin() { return plugin; } + public static Underilla getInstance() { return getPlugin(Underilla.class); } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java new file mode 100644 index 0000000..8f2f84d --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java @@ -0,0 +1,89 @@ +package com.jkantrell.mc.underilla.spigot.generation; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import javax.annotation.Nonnull; +import com.jkantrell.mc.underilla.spigot.Underilla; +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.NoiseColumn; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.GenerationStep.Carving; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.blending.Blender; + + +public class NMSChunkGeneratorDelegate extends ChunkGenerator { + // private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate; + + private final ChunkGenerator vanilla; + + public NMSChunkGeneratorDelegate(ChunkGenerator vanilla) { + super(vanilla.getBiomeSource()); + // this.delegate = pack.getGeneratorProvider().newInstance(pack); + this.vanilla = vanilla; + } + + @Override + protected @Nonnull Codec codec() { return ChunkGenerator.CODEC; } + + @Override + public void applyCarvers(@Nonnull WorldGenRegion chunkRegion, long seed, @Nonnull RandomState noiseConfig, @Nonnull BiomeManager world, + @Nonnull StructureManager structureAccessor, @Nonnull ChunkAccess chunk, @Nonnull Carving carverStep) { + vanilla.applyCarvers(chunkRegion, seed, noiseConfig, world, structureAccessor, chunk, carverStep); + } + + @Override + public void buildSurface(@Nonnull WorldGenRegion region, @Nonnull StructureManager structures, @Nonnull RandomState noiseConfig, + @Nonnull ChunkAccess chunk) { + vanilla.buildSurface(region, structures, noiseConfig, chunk); + } + + @Override + public void applyBiomeDecoration(@Nonnull WorldGenLevel world, @Nonnull ChunkAccess chunk, + @Nonnull StructureManager structureAccessor) { + Underilla.getInstance().getLogger().info("Applying biome decoration"); + vanilla.applyBiomeDecoration(world, chunk, structureAccessor); + } + + @Override + public void spawnOriginalMobs(@Nonnull WorldGenRegion region) { vanilla.spawnOriginalMobs(region); } + + @Override + public int getGenDepth() { return vanilla.getGenDepth(); } + + @Override + public @Nonnull CompletableFuture fillFromNoise(@Nonnull Executor executor, @Nonnull Blender blender, + @Nonnull RandomState noiseConfig, @Nonnull StructureManager structureAccessor, @Nonnull ChunkAccess chunk) { + return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk); + } + + @Override + public int getSeaLevel() { return vanilla.getSeaLevel(); } + + @Override + public int getMinY() { return vanilla.getMinY(); } + + @Override + public int getBaseHeight(int x, int z, @Nonnull Types heightmap, @Nonnull LevelHeightAccessor world, @Nonnull RandomState noiseConfig) { + return vanilla.getBaseHeight(x, z, heightmap, world, noiseConfig); + } + + @Override + public @Nonnull NoiseColumn getBaseColumn(int x, int z, @Nonnull LevelHeightAccessor world, @Nonnull RandomState noiseConfig) { + return vanilla.getBaseColumn(x, z, world, noiseConfig); + } + + @Override + public void addDebugScreenInfo(@Nonnull List text, @Nonnull RandomState noiseConfig, @Nonnull BlockPos pos) { + vanilla.addDebugScreenInfo(text, noiseConfig, pos); + } +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java index 88204a5..ea65a4b 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java @@ -146,7 +146,7 @@ public Populator(WorldReader reader, Generator generator) { } - // OVERWRITES + // OVERRITES @Override public void populate(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, LimitedRegion limitedRegion) { if (!CONFIG.generateCaves) { @@ -166,12 +166,18 @@ private class BiomeProviderFromFile extends BiomeProvider { @Override public @Nonnull Biome getBiome(@Nonnull WorldInfo worldInfo, int x, int y, int z) { + // Read biome from the custom world BukkitBiome biome = (BukkitBiome) worldReader_.biomeAt(x, y, z).orElse(null); + // Read biome from the caves world if (worldCavesReader_ != null) { BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader_.biomeAt(x, y, z).orElse(null); if (cavesBiome != null && CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getBiome())) { return cavesBiome.getBiome(); } + // read biome from the generating world + } else { + // TODO It might be possible to keep default generator on Bukkit.yml then enforce Underilla generator and delegate some + // action to default generator } return biome == null ? Biome.PLAINS : biome.getBiome(); } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java new file mode 100644 index 0000000..a308db8 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -0,0 +1,30 @@ +package com.jkantrell.mc.underilla.spigot.generation; + +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import com.jkantrell.mc.underilla.spigot.Underilla; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; + +public class WorldInitListener implements Listener { + @EventHandler + public void onWorldInit(WorldInitEvent event) { + Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); + CraftWorld craftWorld = (CraftWorld) event.getWorld(); + ServerLevel serverWorld = craftWorld.getHandle(); + + // ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); + + ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); + // NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); + + serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla); + // Underilla.getInstance().getDefaultWorldGenerator(event.getWorld().getName(), "WorldInitListenerID") + // serverWorld.getChunkSource().chunkMap.generator + System.out.println("vanilla generator: " + vanilla); + + Underilla.getInstance().getLogger().info("Successfully injected into world."); + } +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java index fe3144b..e9d0d2a 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java @@ -1,5 +1,7 @@ package com.jkantrell.mc.underilla.spigot.impl; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -12,6 +14,7 @@ import com.jkantrell.nbt.tag.StringTag; public class BukkitChunkReader extends ChunkReader { + private Map customBiomes = new HashMap<>(); // CONSTRUCTORS public BukkitChunkReader(Chunk chunk) { super(chunk); } @@ -47,13 +50,26 @@ public Optional blockFromTag(CompoundTag tag) { public Optional biomeFromTag(StringTag tag) { String[] raw = tag.getValue().split(":"); String name = raw.length > 1 ? raw[1] : raw[0]; + if (customBiomes.containsKey(name)) { + Biome biome = new BukkitBiome(customBiomes.get(name)); + return Optional.of(biome); + } try { org.bukkit.block.Biome nativeBiome = org.bukkit.block.Biome.valueOf(name.toUpperCase()); Biome biome = new BukkitBiome(nativeBiome); return Optional.of(biome); } catch (IllegalArgumentException e) { - Bukkit.getLogger().warning("Could not resolve biome '" + name + "'"); - return Optional.empty(); + Bukkit.getLogger().warning("Could not resolve biome '" + name + "' try to create a custom biome from it"); + org.bukkit.block.Biome customBiome = customBiomes.computeIfAbsent(name, b -> org.bukkit.block.Biome.PLAINS); + // CustomBiome customBiome = customBiomes.computeIfAbsent(name, CustomBiome::new); + Biome biome = new BukkitBiome(customBiome); + return Optional.of(biome); } } + public class CustomBiome implements Biome { + private final String name; + public CustomBiome(String name) { this.name = name; } + @Override + public String getName() { return name; } + } } diff --git a/Underilla-Spigot/src/main/resources/config.yml b/Underilla-Spigot/src/main/resources/config.yml index bdd10fa..d05f19e 100644 --- a/Underilla-Spigot/src/main/resources/config.yml +++ b/Underilla-Spigot/src/main/resources/config.yml @@ -1,3 +1,5 @@ +# # The world to merge the terrain into +# world: "world" # The world to copy the terrain from reference_world: "world_base" From 75e064289242bcf47115e12c4e1bf1ed414e1d96 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Fri, 17 May 2024 21:11:36 +0200 Subject: [PATCH 02/21] 1.20.6 & comment some classes --- Underilla-Spigot/build.gradle.kts | 4 +- .../generation/NMSChunkGeneratorDelegate.java | 179 +++++++++--------- .../spigot/generation/WorldInitListener.java | 48 ++--- .../spigot/impl/NMSRegistryBiomeUtils.java | 13 ++ 4 files changed, 129 insertions(+), 115 deletions(-) create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index ad8f0b3..7fbfe8b 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `java-library` id("io.github.goooler.shadow") version "8.1.7" - id("io.papermc.paperweight.userdev") version "1.7.0" // paperweight // Check for new versions at https://plugins.gradle.org/plugin/io.papermc.paperweight.userdev + id("io.papermc.paperweight.userdev") version "1.7.1" // paperweight // Check for new versions at https://plugins.gradle.org/plugin/io.papermc.paperweight.userdev } group = "com.Jkantrell.mc" @@ -19,7 +19,7 @@ repositories { dependencies { // compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") // without paperweight - paperweight.paperDevBundle("1.20.4-R0.1-SNAPSHOT") // paperweight + paperweight.paperDevBundle("1.20.6-R0.1-SNAPSHOT") // paperweight implementation("com.jkantrell:Yamlizer:main-SNAPSHOT") implementation(project(":Underilla-Core")) } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java index 8f2f84d..9a1b4e6 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java @@ -1,89 +1,90 @@ -package com.jkantrell.mc.underilla.spigot.generation; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import javax.annotation.Nonnull; -import com.jkantrell.mc.underilla.spigot.Underilla; -import com.mojang.serialization.Codec; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.WorldGenRegion; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.biome.BiomeManager; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.levelgen.GenerationStep.Carving; -import net.minecraft.world.level.levelgen.Heightmap.Types; -import net.minecraft.world.level.levelgen.RandomState; -import net.minecraft.world.level.levelgen.blending.Blender; - - -public class NMSChunkGeneratorDelegate extends ChunkGenerator { - // private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate; - - private final ChunkGenerator vanilla; - - public NMSChunkGeneratorDelegate(ChunkGenerator vanilla) { - super(vanilla.getBiomeSource()); - // this.delegate = pack.getGeneratorProvider().newInstance(pack); - this.vanilla = vanilla; - } - - @Override - protected @Nonnull Codec codec() { return ChunkGenerator.CODEC; } - - @Override - public void applyCarvers(@Nonnull WorldGenRegion chunkRegion, long seed, @Nonnull RandomState noiseConfig, @Nonnull BiomeManager world, - @Nonnull StructureManager structureAccessor, @Nonnull ChunkAccess chunk, @Nonnull Carving carverStep) { - vanilla.applyCarvers(chunkRegion, seed, noiseConfig, world, structureAccessor, chunk, carverStep); - } - - @Override - public void buildSurface(@Nonnull WorldGenRegion region, @Nonnull StructureManager structures, @Nonnull RandomState noiseConfig, - @Nonnull ChunkAccess chunk) { - vanilla.buildSurface(region, structures, noiseConfig, chunk); - } - - @Override - public void applyBiomeDecoration(@Nonnull WorldGenLevel world, @Nonnull ChunkAccess chunk, - @Nonnull StructureManager structureAccessor) { - Underilla.getInstance().getLogger().info("Applying biome decoration"); - vanilla.applyBiomeDecoration(world, chunk, structureAccessor); - } - - @Override - public void spawnOriginalMobs(@Nonnull WorldGenRegion region) { vanilla.spawnOriginalMobs(region); } - - @Override - public int getGenDepth() { return vanilla.getGenDepth(); } - - @Override - public @Nonnull CompletableFuture fillFromNoise(@Nonnull Executor executor, @Nonnull Blender blender, - @Nonnull RandomState noiseConfig, @Nonnull StructureManager structureAccessor, @Nonnull ChunkAccess chunk) { - return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk); - } - - @Override - public int getSeaLevel() { return vanilla.getSeaLevel(); } - - @Override - public int getMinY() { return vanilla.getMinY(); } - - @Override - public int getBaseHeight(int x, int z, @Nonnull Types heightmap, @Nonnull LevelHeightAccessor world, @Nonnull RandomState noiseConfig) { - return vanilla.getBaseHeight(x, z, heightmap, world, noiseConfig); - } - - @Override - public @Nonnull NoiseColumn getBaseColumn(int x, int z, @Nonnull LevelHeightAccessor world, @Nonnull RandomState noiseConfig) { - return vanilla.getBaseColumn(x, z, world, noiseConfig); - } - - @Override - public void addDebugScreenInfo(@Nonnull List text, @Nonnull RandomState noiseConfig, @Nonnull BlockPos pos) { - vanilla.addDebugScreenInfo(text, noiseConfig, pos); - } -} +// package com.jkantrell.mc.underilla.spigot.generation; + +// import java.util.List; +// import java.util.concurrent.CompletableFuture; +// import java.util.concurrent.Executor; +// import javax.annotation.Nonnull; +// import com.jkantrell.mc.underilla.spigot.Underilla; +// import com.mojang.serialization.Codec; +// import com.mojang.serialization.MapCodec; +// import net.minecraft.core.BlockPos; +// import net.minecraft.server.level.WorldGenRegion; +// import net.minecraft.world.level.LevelHeightAccessor; +// import net.minecraft.world.level.NoiseColumn; +// import net.minecraft.world.level.StructureManager; +// import net.minecraft.world.level.WorldGenLevel; +// import net.minecraft.world.level.biome.BiomeManager; +// import net.minecraft.world.level.chunk.ChunkAccess; +// import net.minecraft.world.level.chunk.ChunkGenerator; +// import net.minecraft.world.level.levelgen.GenerationStep.Carving; +// import net.minecraft.world.level.levelgen.Heightmap.Types; +// import net.minecraft.world.level.levelgen.RandomState; +// import net.minecraft.world.level.levelgen.blending.Blender; + + +// public class NMSChunkGeneratorDelegate extends ChunkGenerator { +// // private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate; + +// private final ChunkGenerator vanilla; + +// public NMSChunkGeneratorDelegate(ChunkGenerator vanilla) { +// super(vanilla.getBiomeSource()); +// // this.delegate = pack.getGeneratorProvider().newInstance(pack); +// this.vanilla = vanilla; +// } + +// @Override +// protected @Nonnull MapCodec codec() { return ChunkGenerator.CODEC; } + +// @Override +// public void applyCarvers(@Nonnull WorldGenRegion chunkRegion, long seed, @Nonnull RandomState noiseConfig, @Nonnull BiomeManager world, +// @Nonnull StructureManager structureAccessor, @Nonnull ChunkAccess chunk, @Nonnull Carving carverStep) { +// vanilla.applyCarvers(chunkRegion, seed, noiseConfig, world, structureAccessor, chunk, carverStep); +// } + +// @Override +// public void buildSurface(@Nonnull WorldGenRegion region, @Nonnull StructureManager structures, @Nonnull RandomState noiseConfig, +// @Nonnull ChunkAccess chunk) { +// vanilla.buildSurface(region, structures, noiseConfig, chunk); +// } + +// @Override +// public void applyBiomeDecoration(@Nonnull WorldGenLevel world, @Nonnull ChunkAccess chunk, +// @Nonnull StructureManager structureAccessor) { +// Underilla.getInstance().getLogger().info("Applying biome decoration"); +// vanilla.applyBiomeDecoration(world, chunk, structureAccessor); +// } + +// @Override +// public void spawnOriginalMobs(@Nonnull WorldGenRegion region) { vanilla.spawnOriginalMobs(region); } + +// @Override +// public int getGenDepth() { return vanilla.getGenDepth(); } + +// @Override +// public @Nonnull CompletableFuture fillFromNoise(@Nonnull Executor executor, @Nonnull Blender blender, +// @Nonnull RandomState noiseConfig, @Nonnull StructureManager structureAccessor, @Nonnull ChunkAccess chunk) { +// return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk); +// } + +// @Override +// public int getSeaLevel() { return vanilla.getSeaLevel(); } + +// @Override +// public int getMinY() { return vanilla.getMinY(); } + +// @Override +// public int getBaseHeight(int x, int z, @Nonnull Types heightmap, @Nonnull LevelHeightAccessor world, @Nonnull RandomState noiseConfig) { +// return vanilla.getBaseHeight(x, z, heightmap, world, noiseConfig); +// } + +// @Override +// public @Nonnull NoiseColumn getBaseColumn(int x, int z, @Nonnull LevelHeightAccessor world, @Nonnull RandomState noiseConfig) { +// return vanilla.getBaseColumn(x, z, world, noiseConfig); +// } + +// @Override +// public void addDebugScreenInfo(@Nonnull List text, @Nonnull RandomState noiseConfig, @Nonnull BlockPos pos) { +// vanilla.addDebugScreenInfo(text, noiseConfig, pos); +// } +// } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index a308db8..2a6c64a 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -1,30 +1,30 @@ -package com.jkantrell.mc.underilla.spigot.generation; +// package com.jkantrell.mc.underilla.spigot.generation; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldInitEvent; -import com.jkantrell.mc.underilla.spigot.Underilla; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.chunk.ChunkGenerator; +// import org.bukkit.craftbukkit.CraftWorld; +// import org.bukkit.event.EventHandler; +// import org.bukkit.event.Listener; +// import org.bukkit.event.world.WorldInitEvent; +// import com.jkantrell.mc.underilla.spigot.Underilla; +// import net.minecraft.server.level.ServerLevel; +// import net.minecraft.world.level.chunk.ChunkGenerator; -public class WorldInitListener implements Listener { - @EventHandler - public void onWorldInit(WorldInitEvent event) { - Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); - CraftWorld craftWorld = (CraftWorld) event.getWorld(); - ServerLevel serverWorld = craftWorld.getHandle(); +// public class WorldInitListener implements Listener { +// @EventHandler +// public void onWorldInit(WorldInitEvent event) { +// Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); +// CraftWorld craftWorld = (CraftWorld) event.getWorld(); +// ServerLevel serverWorld = craftWorld.getHandle(); - // ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); +// // ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); - ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); - // NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); +// ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); +// // NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); - serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla); - // Underilla.getInstance().getDefaultWorldGenerator(event.getWorld().getName(), "WorldInitListenerID") - // serverWorld.getChunkSource().chunkMap.generator - System.out.println("vanilla generator: " + vanilla); +// serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla); +// // Underilla.getInstance().getDefaultWorldGenerator(event.getWorld().getName(), "WorldInitListenerID") +// // serverWorld.getChunkSource().chunkMap.generator +// System.out.println("vanilla generator: " + vanilla); - Underilla.getInstance().getLogger().info("Successfully injected into world."); - } -} +// Underilla.getInstance().getLogger().info("Successfully injected into world."); +// } +// } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java new file mode 100644 index 0000000..9f1fcf0 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java @@ -0,0 +1,13 @@ +package com.jkantrell.mc.underilla.spigot.impl; + +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.biome.Biome; + +public class NMSRegistryBiomeUtils { + public static Registry getBiomeRegistry() { + return ((CraftServer) Bukkit.getServer()).getServer().registryAccess().registryOrThrow(Registries.BIOME); + } +} From 31a1ff9e28718d230d7d32128eb8aa1d23a5064f Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Sat, 18 May 2024 20:47:51 +0200 Subject: [PATCH 03/21] Biome can be set from a key --- .../mc/underilla/spigot/Underilla.java | 1 + .../generation/BiomeGenerationHandler.java | 19 ++++ .../generation/UnderillaChunkGenerator.java | 53 +++++---- .../spigot/generation/WorldInitListener.java | 57 +++++----- .../mc/underilla/spigot/impl/BiomeHelper.java | 103 ++++++++++++++++++ .../mc/underilla/spigot/impl/BukkitBiome.java | 23 ++-- .../spigot/impl/BukkitChunkData.java | 17 ++- .../spigot/impl/BukkitChunkReader.java | 48 ++++---- .../spigot/impl/BukkitRegionChunkData.java | 5 +- .../spigot/impl/CustomBiomeSource.java | 42 +++++++ .../underilla/spigot/impl/NMSBiomeUtils.java | 56 ++++++++++ .../spigot/impl/NMSRegistryBiomeUtils.java | 13 --- .../mc/underilla/spigot/io/Config.java | 9 +- 13 files changed, 338 insertions(+), 108 deletions(-) create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java delete mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java index da9a7e6..f6a5847 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java @@ -65,6 +65,7 @@ public void onEnable() { if (CONFIG.generateStructures) { this.getServer().getPluginManager().registerEvents(new StructureEventListener(CONFIG.structureBlackList), this); } + this.getServer().getPluginManager().registerEvents(new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(), this); } @Override diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java new file mode 100644 index 0000000..be63081 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java @@ -0,0 +1,19 @@ +package com.jkantrell.mc.underilla.spigot.generation; + +import java.util.Arrays; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; + +public class BiomeGenerationHandler implements Listener { + @EventHandler(priority = EventPriority.LOW) + public void onWorldInit(WorldInitEvent event) { + ServerLevel serverLevel = ((CraftWorld) event.getWorld()).getHandle(); + System.out.println("World init event"); + System.out.println(Arrays.asList(ChunkGenerator.class.getDeclaredFields())); + } +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java index ea65a4b..4c6fb96 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java @@ -4,7 +4,6 @@ import java.util.Map; import java.util.Optional; import java.util.Random; -import org.bukkit.Bukkit; import org.bukkit.HeightMap; import org.bukkit.World; import org.bukkit.block.Biome; @@ -15,11 +14,9 @@ import org.bukkit.generator.WorldInfo; import com.jkantrell.mc.underilla.core.api.HeightMapType; import com.jkantrell.mc.underilla.core.generation.Generator; -import com.jkantrell.mc.underilla.core.generation.MergeStrategy; import com.jkantrell.mc.underilla.core.reader.ChunkReader; import com.jkantrell.mc.underilla.core.reader.WorldReader; import com.jkantrell.mc.underilla.spigot.Underilla; -import com.jkantrell.mc.underilla.spigot.impl.BukkitBiome; import com.jkantrell.mc.underilla.spigot.impl.BukkitChunkData; import com.jkantrell.mc.underilla.spigot.impl.BukkitRegionChunkData; import com.jkantrell.mc.underilla.spigot.impl.BukkitWorldInfo; @@ -118,16 +115,17 @@ public boolean shouldGenerateStructures(WorldInfo worldInfo, Random random, int @Override public BiomeProvider getDefaultBiomeProvider(@Nonnull WorldInfo worldInfo) { - // if biome need to be transfered from the custom world add a custom biome provider - // (For MergeStrategy.RELATIVE, kept underground biomes are transfered in the mergeBiomes method not here) - if (CONFIG.transferBiomes && (!CONFIG.mergeStrategy.equals(MergeStrategy.RELATIVE) || CONFIG.keptUndergroundBiomes.isEmpty())) { - Bukkit.getLogger() - .info("Underilla Use the custom biome provider from file data. Structures will be generate in the right biome."); - return new BiomeProviderFromFile(); - } else { - Bukkit.getLogger().info("Underilla Use the default biome provider. Structures will be generate in bad biomes."); - return null; - } + // // if biome need to be transfered from the custom world add a custom biome provider + // // (For MergeStrategy.RELATIVE, kept underground biomes are transfered in the mergeBiomes method not here) + // if (CONFIG.transferBiomes && (!CONFIG.mergeStrategy.equals(MergeStrategy.RELATIVE) || CONFIG.keptUndergroundBiomes.isEmpty())) { + // Bukkit.getLogger() + // .info("Underilla Use the custom biome provider from file data. Structures will be generate in the right biome."); + // return new BiomeProviderFromFile(); + // } else { + // Bukkit.getLogger().info("Underilla Use the default biome provider. Structures will be generate in bad biomes."); + // return null; + // } + return null; } @@ -166,20 +164,21 @@ private class BiomeProviderFromFile extends BiomeProvider { @Override public @Nonnull Biome getBiome(@Nonnull WorldInfo worldInfo, int x, int y, int z) { - // Read biome from the custom world - BukkitBiome biome = (BukkitBiome) worldReader_.biomeAt(x, y, z).orElse(null); - // Read biome from the caves world - if (worldCavesReader_ != null) { - BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader_.biomeAt(x, y, z).orElse(null); - if (cavesBiome != null && CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getBiome())) { - return cavesBiome.getBiome(); - } - // read biome from the generating world - } else { - // TODO It might be possible to keep default generator on Bukkit.yml then enforce Underilla generator and delegate some - // action to default generator - } - return biome == null ? Biome.PLAINS : biome.getBiome(); + // // Read biome from the custom world + // BukkitBiome biome = (BukkitBiome) worldReader_.biomeAt(x, y, z).orElse(null); + // // Read biome from the caves world + // if (worldCavesReader_ != null) { + // BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader_.biomeAt(x, y, z).orElse(null); + // if (cavesBiome != null && CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getBiome())) { + // return cavesBiome.getBiome(); + // } + // // read biome from the generating world + // } else { + // // TODO It might be possible to keep default generator on Bukkit.yml then enforce Underilla generator and delegate some + // // action to default generator + // } + // return biome == null ? Biome.PLAINS : biome.getBiome(); + return Biome.PLAINS; } @Override diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index 2a6c64a..b6bba13 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -1,30 +1,37 @@ -// package com.jkantrell.mc.underilla.spigot.generation; +package com.jkantrell.mc.underilla.spigot.generation; -// import org.bukkit.craftbukkit.CraftWorld; -// import org.bukkit.event.EventHandler; -// import org.bukkit.event.Listener; -// import org.bukkit.event.world.WorldInitEvent; -// import com.jkantrell.mc.underilla.spigot.Underilla; -// import net.minecraft.server.level.ServerLevel; -// import net.minecraft.world.level.chunk.ChunkGenerator; +import java.lang.reflect.Field; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import com.jkantrell.mc.underilla.spigot.Underilla; +import com.jkantrell.mc.underilla.spigot.impl.CustomBiomeSource; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.ChunkGenerator; -// public class WorldInitListener implements Listener { -// @EventHandler -// public void onWorldInit(WorldInitEvent event) { -// Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); -// CraftWorld craftWorld = (CraftWorld) event.getWorld(); -// ServerLevel serverWorld = craftWorld.getHandle(); +public class WorldInitListener implements Listener { + @EventHandler + public void onWorldInit(WorldInitEvent event) { + Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); + CraftWorld craftWorld = (CraftWorld) event.getWorld(); + ServerLevel serverLevel = craftWorld.getHandle(); -// // ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); + // ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); -// ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); -// // NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); + ChunkGenerator vanilla = serverLevel.getChunkSource().getGenerator(); + BiomeSource vanillaBiomeSource = vanilla.getBiomeSource(); + BiomeSource customBiomeSource = new CustomBiomeSource(vanillaBiomeSource); -// serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla); -// // Underilla.getInstance().getDefaultWorldGenerator(event.getWorld().getName(), "WorldInitListenerID") -// // serverWorld.getChunkSource().chunkMap.generator -// System.out.println("vanilla generator: " + vanilla); - -// Underilla.getInstance().getLogger().info("Successfully injected into world."); -// } -// } + try { + Field biomeSourceField = ChunkGenerator.class.getDeclaredField("biomeSource"); + biomeSourceField.setAccessible(true); + biomeSourceField.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + Underilla.getInstance().getLogger().info("Successfully injected custom biome source."); + } catch (NoSuchFieldException | IllegalAccessException e) { + Underilla.getInstance().getLogger().warning("Failed to inject custom biome source."); + e.printStackTrace(); + } + } +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java new file mode 100644 index 0000000..fbb932c --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java @@ -0,0 +1,103 @@ +package com.jkantrell.mc.underilla.spigot.impl; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; + +// From +// https://github.com/eccentricdevotion/TARDISChunkGenerator/blob/master/src/main/java/me/eccentric_nz/tardischunkgenerator/custombiome/BiomeHelper.java +public class BiomeHelper { + + private static DedicatedServer dedicatedServer = ((CraftServer) Bukkit.getServer()).getServer(); + + /** + * Set a chunk to a custom biome + * + * @param newBiomeName the name of the custom biome to set (such as tardis:skaro_lakes) + * @param chunk the chunk to set the biome for + */ + public static void setCustomBiome(String newBiomeName, Chunk chunk) { + WritableRegistry registryWritable = (WritableRegistry) dedicatedServer.registryAccess().registry(Registries.BIOME) + .get(); + ResourceKey key = ResourceKey.create(Registries.BIOME, new ResourceLocation(newBiomeName.toLowerCase())); + Biome base = registryWritable.get(key); + if (base == null) { + if (newBiomeName.contains(":")) { + ResourceKey newKey = ResourceKey.create(Registries.BIOME, + new ResourceLocation(newBiomeName.split(":")[0].toLowerCase(), newBiomeName.split(":")[1].toLowerCase())); + base = registryWritable.get(newKey); + if (base == null) { + return; + } + } else { + return; + } + } + Holder biomeHolder = Holder.direct(base); + Level w = ((CraftWorld) chunk.getWorld()).getHandle(); + for (int x = 0; x <= 15; x++) { + for (int z = 0; z <= 15; z++) { + for (int y = 0; y <= chunk.getWorld().getMaxHeight(); y++) { + setCustomBiome(chunk.getX() * 16 + x, y, chunk.getZ() * 16 + z, w, biomeHolder); + } + } + } + chunk.getWorld().refreshChunk(chunk.getX(), chunk.getZ()); + } + + /** + * Set a location to a custom biome + * + * @param newBiomeName the name of the custom biome to set (such as tardis:skaro_lakes) + * @param location the location to set the biome for + * @return true if the biome was set + */ + public static boolean setCustomBiome(String newBiomeName, Location location) { + Biome base; + WritableRegistry registrywritable = (WritableRegistry) dedicatedServer.registryAccess().registry(Registries.BIOME) + .get(); + ResourceKey key = ResourceKey.create(Registries.BIOME, new ResourceLocation(newBiomeName.toLowerCase())); + base = registrywritable.get(key); + if (base == null) { + if (newBiomeName.contains(":")) { + ResourceKey newKey = ResourceKey.create(Registries.BIOME, + new ResourceLocation(newBiomeName.split(":")[0].toLowerCase(), newBiomeName.split(":")[1].toLowerCase())); + base = registrywritable.get(newKey); + if (base == null) { + return false; + } + } else { + return false; + } + } + setCustomBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), ((CraftWorld) location.getWorld()).getHandle(), + Holder.direct(base)); + location.getWorld().refreshChunk(location.getChunk().getX(), location.getChunk().getZ()); + return true; + } + public static boolean setCustomBiome(String newBiomeName, int x, int y, int z) { + return setCustomBiome(newBiomeName, new Location(Bukkit.getWorld("world"), x, y, z)); + } + + private static void setCustomBiome(int x, int y, int z, Level w, Holder bb) { + BlockPos pos = new BlockPos(x, 0, z); + if (w.isLoaded(pos)) { + ChunkAccess chunk = w.getChunk(pos); + if (chunk != null) { + chunk.setBiome(x >> 2, y >> 2, z >> 2, bb); + } + } + } +} \ No newline at end of file diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java index 7c36f40..72240ed 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java @@ -5,20 +5,27 @@ public class BukkitBiome implements Biome { // FIELDS - private org.bukkit.block.Biome biome_; + private String name; - // CONSTRUCTORS - public BukkitBiome(org.bukkit.block.Biome biome) { this.biome_ = biome; } + // // CONSTRUCTORS + // public BukkitBiome(org.bukkit.block.Biome biome) { this.name = biome.name(); } + public BukkitBiome(String name) { + name = name.toLowerCase(); + if (!name.contains(":")) { + name = "minecraft:" + name; + } + this.name = name; + } - // GETTERS - public org.bukkit.block.Biome getBiome() { return this.biome_; } + // // // GETTERS + // public org.bukkit.block.Biome getBiome() { return NMSBiomeUtils.getBukkitBiome(this.name); } // IMPLEMENTATIONS @Override - public String getName() { return this.biome_.name(); } + public String getName() { return name; } @Override public boolean equals(Object o) { if (o == null) { @@ -30,9 +37,9 @@ public boolean equals(Object o) { if (!(o instanceof BukkitBiome bukkitBiome)) { return false; } - return this.biome_.equals(bukkitBiome.biome_); + return this.name.equals(bukkitBiome.name); } @Override - public String toString() { return "BukkitBiome{" + "biome_=" + biome_ + '}'; } + public String toString() { return "BukkitBiome{" + "biome_=" + name + '}'; } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java index acb85e3..eef1c73 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java @@ -1,9 +1,13 @@ package com.jkantrell.mc.underilla.spigot.impl; -import org.bukkit.block.Biome; +import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftWorld; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.api.ChunkData; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; public class BukkitChunkData implements ChunkData { @@ -11,7 +15,7 @@ public class BukkitChunkData implements ChunkData { // private CraftChunkData internal_; private org.bukkit.generator.ChunkGenerator.ChunkData chunkData; // private static final Map> biomeCache = new HashMap<>(); - // private static final World world = Bukkit.getWorld("world"); + private static final World world = Bukkit.getWorld("world"); // CONSTRUCTORS @@ -36,8 +40,13 @@ public Block getBlock(int x, int y, int z) { @Override public com.jkantrell.mc.underilla.core.api.Biome getBiome(int x, int y, int z) { - Biome b = this.chunkData.getBiome(x, y, z); - return new BukkitBiome(b); + // Biome b = this.chunkData.getBiome(x, y, z); + // return new BukkitBiome(b); + // get net.minecraft.world.level.biome.Biome from x, y, z + Holder nmsBiome = ((CraftWorld) world).getHandle().getBiome(new BlockPos(x, y, z)); + // System.out.println(nmsBiome); + return new BukkitBiome(nmsBiome.getRegisteredName()); + } @Override diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java index e9d0d2a..9b0f188 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import org.bukkit.Bukkit; import org.bukkit.Material; import com.jkantrell.mc.underilla.core.api.Biome; import com.jkantrell.mc.underilla.core.api.Block; @@ -48,28 +47,29 @@ public Optional blockFromTag(CompoundTag tag) { } @Override public Optional biomeFromTag(StringTag tag) { - String[] raw = tag.getValue().split(":"); - String name = raw.length > 1 ? raw[1] : raw[0]; - if (customBiomes.containsKey(name)) { - Biome biome = new BukkitBiome(customBiomes.get(name)); - return Optional.of(biome); - } - try { - org.bukkit.block.Biome nativeBiome = org.bukkit.block.Biome.valueOf(name.toUpperCase()); - Biome biome = new BukkitBiome(nativeBiome); - return Optional.of(biome); - } catch (IllegalArgumentException e) { - Bukkit.getLogger().warning("Could not resolve biome '" + name + "' try to create a custom biome from it"); - org.bukkit.block.Biome customBiome = customBiomes.computeIfAbsent(name, b -> org.bukkit.block.Biome.PLAINS); - // CustomBiome customBiome = customBiomes.computeIfAbsent(name, CustomBiome::new); - Biome biome = new BukkitBiome(customBiome); - return Optional.of(biome); - } - } - public class CustomBiome implements Biome { - private final String name; - public CustomBiome(String name) { this.name = name; } - @Override - public String getName() { return name; } + return Optional.of(new BukkitBiome(tag.getValue())); + // String[] raw = tag.getValue().split(":"); + // String name = raw.length > 1 ? raw[1] : raw[0]; + // if (customBiomes.containsKey(name)) { + // Biome biome = new BukkitBiome(tag.getValue()); + // return Optional.of(biome); + // } + // try { + // org.bukkit.block.Biome nativeBiome = org.bukkit.block.Biome.valueOf(name.toUpperCase()); + // Biome biome = new BukkitBiome(nativeBiome); + // return Optional.of(biome); + // } catch (IllegalArgumentException e) { + // Bukkit.getLogger().warning("Could not resolve biome '" + name + "' try to create a custom biome from it"); + // org.bukkit.block.Biome customBiome = customBiomes.computeIfAbsent(name, b -> org.bukkit.block.Biome.PLAINS); + // // CustomBiome customBiome = customBiomes.computeIfAbsent(name, CustomBiome::new); + // Biome biome = new BukkitBiome(customBiome); + // return Optional.of(biome); + // } } + // public class CustomBiome implements Biome { + // private final String name; + // public CustomBiome(String name) { this.name = name; } + // @Override + // public String getName() { return name; } + // } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java index 082cf2b..c3139a9 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java @@ -45,7 +45,7 @@ public Block getBlock(int x, int y, int z) { @Override public Biome getBiome(int x, int y, int z) { org.bukkit.block.Biome b = this.region_.getBiome(this.absX_ + x, y, this.absZ_ + z); - return new BukkitBiome(b); + return new BukkitBiome(b.name()); } @Override public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Block block) { @@ -63,6 +63,7 @@ public void setBiome(int x, int y, int z, Biome biome) { if (!(biome instanceof BukkitBiome bukkitBiome)) { return; } - this.region_.setBiome(this.absX_ + x, y, this.absZ_ + z, bukkitBiome.getBiome()); + // this.region_.setBiome(this.absX_ + x, y, this.absZ_ + z, bukkitBiome.getBiome()); + BiomeHelper.setCustomBiome(bukkitBiome.getName(), this.absX_ + x, y, this.absZ_ + z); } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java new file mode 100644 index 0000000..a0d6269 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -0,0 +1,42 @@ +package com.jkantrell.mc.underilla.spigot.impl; + +import java.util.stream.Stream; +import com.jkantrell.mc.underilla.spigot.Underilla; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate.Sampler; + +public class CustomBiomeSource extends BiomeSource { + private final BiomeSource vanillaBiomeSource; + + public CustomBiomeSource(BiomeSource vanillaBiomeSource) { this.vanillaBiomeSource = vanillaBiomeSource; } + + @Override + protected MapCodec codec() { throw new UnsupportedOperationException("Not supported"); } + + @Override + protected Stream> collectPossibleBiomes() { + Registry biomeRegistry = NMSBiomeUtils.getBiomeRegistry(); + Underilla.getInstance().getLogger().info( + "Collecting possible biomes: " + biomeRegistry.stream().map(biome -> biomeRegistry.getKey(biome).toString()).toList()); + return biomeRegistry.stream().map(biome -> biomeRegistry.wrapAsHolder(biome)); + } + + @Override + public Holder getNoiseBiome(int x, int y, int z, Sampler noise) { + Holder vanillaBiome = vanillaBiomeSource.getNoiseBiome(x, y, z, noise); + if (vanillaBiome != null && Underilla.CONFIG.keptUndergroundBiomes.contains(vanillaBiome.getRegisteredName())) { + Underilla.getInstance().getLogger().info("Use vanillaBiome because it's a keptUndergroundBiomes: " + + vanillaBiome.getRegisteredName() + " at " + x + ", " + y + ", " + z); + return vanillaBiome; + } + // TODO get key from custom surface world. + String biomeKey = "mvndi:baltic_coast"; + // Underilla.getInstance().getLogger().info("Getting noise biome: " + x + ", " + y + ", " + z + ": " + biomeKey); + return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome(biomeKey)); + } + +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java new file mode 100644 index 0000000..8f3fb53 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java @@ -0,0 +1,56 @@ +package com.jkantrell.mc.underilla.spigot.impl; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBiome; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.biome.Biome; + +public class NMSBiomeUtils { + public static Registry getBiomeRegistry() { + return ((CraftServer) Bukkit.getServer()).getServer().registryAccess().registryOrThrow(Registries.BIOME); + } + + public static Biome getBiome(String key) { return getBiomeRegistry().get(new ResourceLocation(key)); } + public static Biome getBiome(Location location) { + return getBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getWorld()); + } + public static Biome getBiome(int x, int y, int z, World bukkitWorld) { + if (bukkitWorld == null) + return null; + ServerLevel nmsWorld = ((CraftWorld) bukkitWorld).getHandle(); + return nmsWorld.getNoiseBiome(x >> 2, y >> 2, z >> 2).value(); + } + + public static ResourceLocation getBiomeKey(Location location) { return getBiomeRegistry().getKey(getBiome(location)); } + public static ResourceLocation getBiomeKey(int x, int y, int z, World bukkitWorld) { + return getBiomeRegistry().getKey(getBiome(x, y, z, bukkitWorld)); + } + + // public static org.bukkit.block.Biome getBukkitBiome(String key) { + // try { + // // org.bukkit.block.Biome biome = org.bukkit.block.Biome.valueOf(key.split|.toUpperCase()); + // } catch (Exception e) { + // Underilla.getInstance().getLogger().warning("Failed to get Bukkit biome for key: " + key); + // return org.bukkit.block.Biome.PLAINS; + // } + // } + // Convert between Minecraft and Bukkit biomes + // minecraft to bukkit don't work with custom biomes. + public static org.bukkit.block.Biome minecraftToBukkit(Biome minecraft) { return CraftBiome.minecraftToBukkit(minecraft); } + public static Biome bukkitToMinecraft(org.bukkit.block.Biome bukkit) { return CraftBiome.bukkitToMinecraft(bukkit); } + public static org.bukkit.block.Biome minecraftHolderToBukkit(Holder minecraft) { + return CraftBiome.minecraftToBukkit(minecraft.value()); + } + public static Holder bukkitToMinecraftHolder(org.bukkit.block.Biome bukkit) { + return CraftBiome.bukkitToMinecraftHolder(bukkit); + } + +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java deleted file mode 100644 index 9f1fcf0..0000000 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSRegistryBiomeUtils.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.jkantrell.mc.underilla.spigot.impl; - -import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.CraftServer; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.Registries; -import net.minecraft.world.level.biome.Biome; - -public class NMSRegistryBiomeUtils { - public static Registry getBiomeRegistry() { - return ((CraftServer) Bukkit.getServer()).getServer().registryAccess().registryOrThrow(Registries.BIOME); - } -} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java index 562b261..33ac10e 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java @@ -4,7 +4,6 @@ import java.util.List; import org.bukkit.NamespacedKey; import org.bukkit.Registry; -import org.bukkit.block.Biome; import org.bukkit.generator.structure.Structure; import com.jkantrell.mc.underilla.core.generation.GenerationConfig; import com.jkantrell.mc.underilla.core.generation.MergeStrategy; @@ -45,7 +44,7 @@ public Config(String filePath) { public String cavesWorldName = "caves_world"; @ConfigField(path = "transfered_caves_world_biomes") - public List transferCavesWorldBiomes = List.of(Biome.DEEP_DARK, Biome.DRIPSTONE_CAVES, Biome.LUSH_CAVES); + public List transferCavesWorldBiomes = List.of("minecraft:deep_dark", "minecraft:dripstone_caves", "minecraft:lush_caves"); @ConfigField(path = "strategy") public MergeStrategy mergeStrategy = MergeStrategy.RELATIVE; @@ -60,7 +59,7 @@ public Config(String filePath) { public int mergeDepth = 12; @ConfigField(path = "relative.kept_underground_biomes") - public List keptUndergroundBiomes = List.of(); + public List keptUndergroundBiomes = List.of(); @ConfigField(path = "kept_reference_world_blocks") public List keptReferenceWorldBlocks = List.of(); @@ -72,10 +71,10 @@ public Config(String filePath) { public int mergeBlendRange = 8; @ConfigField(path = "preserve_biomes") - public List preserveBiomes = List.of(); + public List preserveBiomes = List.of(); @ConfigField(path = "ravin_biomes") - public List ravinBiomes = List.of(); + public List ravinBiomes = List.of(); @ConfigField(path = "structures.enabled") public boolean generateStructures = true; From eab0a2e8d28f5526a49d5230409c7950f830483c Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Sun, 19 May 2024 21:43:41 +0200 Subject: [PATCH 04/21] Support custom biomes --- README.md | 11 +- Underilla-Core/build.gradle | 2 +- .../core/generation/AbsoluteMerger.java | 10 -- .../underilla/core/generation/Generator.java | 10 +- .../mc/underilla/core/generation/Merger.java | 1 - .../core/generation/RelativeMerger.java | 39 ----- Underilla-Fabric/build.gradle | 148 +++++++++--------- Underilla-Fabric/settings.gradle | 20 +-- Underilla-Spigot/build.gradle.kts | 6 +- .../mc/underilla/spigot/Underilla.java | 21 ++- .../generation/UnderillaChunkGenerator.java | 45 +----- .../spigot/generation/WorldInitListener.java | 14 +- .../mc/underilla/spigot/impl/BukkitBiome.java | 2 +- .../spigot/impl/BukkitChunkReader.java | 27 +--- .../spigot/impl/CustomBiomeSource.java | 81 ++++++++-- settings.gradle | 2 +- 16 files changed, 202 insertions(+), 237 deletions(-) diff --git a/README.md b/README.md index b0d67d4..e777be9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [ ![discord-shield][] ][discord-invite] # Underilla -Underilla is a Bukkit / Spigot based plugin for Minecraft Servers to 'merge' existing custom Minecraft word surfaces and vanilla undergrounds. It works by allowing the vanilla generation engine create chunks as normal, then intercepting the generator and forcing the surface of the original world, which works as a reference. In oder worlds, Underilla generates a brand-new world with vanilla undergrounds, but cloning the surface of an already existing world. +Underilla is a Paper based plugin for Minecraft Servers to 'merge' existing custom Minecraft word surfaces and vanilla undergrounds. It works by allowing the vanilla generation engine create chunks as normal, then intercepting the generator and forcing the surface of the original world, which works as a reference. In oder worlds, Underilla generates a brand-new world with vanilla undergrounds, but cloning the surface of an already existing world. It's original purpose is adding vanilla caves to custom [WorldPainter](https://www.worldpainter.net/) worlds, but it would perfectly work for any pre-generated world. @@ -28,12 +28,12 @@ It's original purpose is adding vanilla caves to custom [WorldPainter](https://w ## Getting started ### Perquisites -- Java 17. +- Java 21. - A pre-generated world to use as a reference (Such as a WorldPainter world). -- A Spigot / Paper (or forks) Minecraft Server of version [1.19.4 - 1.20.4]. It might work with upper version, but only 1.19.4, 1.20.1, 1.20.2, 1.20.4 have been tested. +- A [Paper](https://papermc.io/software/paper) (or forks) Minecraft Server of version [1.20.5 - 1.20.6]. It might work with upper version, but only 1.20.6 have been tested. Use old release for [1.19 - 1.20.4] compatibility. ### Single player or non-Bukkit -Underilla is currently only implemented as a Spigot plugin, so it runs only on Spigot (or fork) servers. If you have a Vanilla, Forge or non Bukkit-based server; or looking for a single player experience; you may [use a local Spigot server](https://www.spigotmc.org/wiki/spigot-installation/) to pre-generate a fully-merged world and then copy the resulting world folder to your actual `saves` folder. +Underilla is currently only implemented as a Paper plugin, so it runs only on Paper (or fork) servers. If you have a Vanilla, Forge or non Bukkit-based server; or looking for a single player experience; you may [use a local Paper server](https://papermc.io/software/paper) to pre-generate a fully-merged world and then copy the resulting world folder to your actual `saves` folder. ### Installation @@ -58,7 +58,7 @@ Underilla is currently only implemented as a Spigot plugin, so it runs only on S **Important:** Make sure your server's main world is still set to `world`. Aside from this plugin, the server itself doesn't need to "know" about the reference world. ### Pregenerate -Underilla is significantly slower than the vanilla generator, as it doesn't relly on noise generation but on reading the reference world's region `nbt` files and analyzing its patterns to 'clone' its surface to a vanilla world. So, if your world is intended for heavy duty in a big server. It's recommended to pre-generate the whole reference world area with a chunk generator plugin, such as [Chunky](https://www.spigotmc.org/resources/chunky.81534/). I'm planning adding a build-in pre-generation system in the future. +Underilla is significantly slower than the vanilla generator, as it doesn't relly on noise generation but on reading the reference world's region `nbt` files and analyzing its patterns to 'clone' its surface to a vanilla world. So, if your world is intended for heavy duty in a big server. It's recommended to pre-generate the whole reference world area with a chunk generator plugin, such as [Chunky](https://hangar.papermc.io/pop4959/Chunky). I'm planning adding a build-in pre-generation system in the future. ### Performances Huge map generation can takes hours or even days, here is some stats about performance to help you choose your configuration settings. @@ -76,7 +76,6 @@ For a 50000 * 30000 world, it would take 40 hours to generate with Minecraft van - Underilla's generation disables Minecraft's chunk blender, which means there will be sharp old-school chunk borders at the edge of the reference world's chunks. This may be tackled by trimming your custom world chunks around the edges to generate blended chunks ahead of time. - Due to Spigot's generation API, outside the reference world's area, heightmaps are broken, which has an impact on structures. You may work around this by pre-generating the whole reference world area, and then disabling Underilla. - **Relative strategy only:** Little underground lava and water pockets will translate to odd floating blobs in the final world if they overlap with large caves. Avoid such generation patterns. -- **With kept biome & relative strategy only**: As Underilla need to mix biome between the 2 world biome, it didn't edit Minecraft vanilla generator biome, this generator will places structures based on the seed, not the actual biomes. This results in structures sometimes in totally unrelated biomes. Shipwrecks, monuments and other ocean structures are the most noticeable. To work around this, you can get rid of kept biomes or you may blacklist structures as you wish in the config file, and spawn them manually using `/place` or use a plugin to place them as [WorldPopulatorH](https://github.com/HydrolienF/WorldPopulatorH). ## WorldPainter considerations If you're going to plug your custom WorldPainter world into Underilla, consider before exporting: diff --git a/Underilla-Core/build.gradle b/Underilla-Core/build.gradle index 6276cce..21df9ab 100644 --- a/Underilla-Core/build.gradle +++ b/Underilla-Core/build.gradle @@ -4,7 +4,7 @@ plugins { group = 'com.Jkantrell.mc' version = '1.4.3' -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_21 repositories { mavenCentral() diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java index c4d3b25..98bf56a 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java @@ -32,11 +32,6 @@ class AbsoluteMerger implements Merger { // IMPLEMENTATIONS - // @Override - // public void merge(ChunkReader reader, ChunkData chunkData) { - // this.mergeLand(reader, chunkData); - // // this.mergeBiomes(reader, chunkData); // No need to set biome for chunk. It's done by the generator. - // } @Override public void mergeLand(ChunkReader reader, ChunkData chunkData, @Nullable ChunkReader cavesReader) { long startTime = System.currentTimeMillis(); @@ -107,11 +102,6 @@ private int getLowerBlockToRemove(Reader reader, int x, int z, Block defaultBloc return lbtr - mergeDepth_; } - @Override - public void mergeBiomes(ChunkReader reader, ChunkData chunkData) { - // No need to set biome for chunk. It's done by the generator. - } - // private -------------------------------------------------------------------------------------------------------- /** diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java index e2aa4cc..e54ad95 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java @@ -66,11 +66,11 @@ public void generateSurface(@Nonnull ChunkReader reader, @Nonnull ChunkData chun this.merger_.mergeLand(reader, chunkData, cavesReader); // The only configuration where we need to merge biome here is when we want to transfer biomes from the reference world // & keep underground biomes. - if (config_.needToMixBiomes()) { - long time = System.currentTimeMillis(); - this.merger_.mergeBiomes(reader, chunkData); - addTime("mergeBiomes", time); - } + // if (config_.needToMixBiomes()) { + // long time = System.currentTimeMillis(); + // this.merger_.mergeBiomes(reader, chunkData); + // addTime("mergeBiomes", time); + // } } public void reInsertLiquids(ChunkReader reader, ChunkData chunkData) { diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Merger.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Merger.java index 7b6a660..5ab629e 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Merger.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Merger.java @@ -9,5 +9,4 @@ interface Merger { // void merge(ChunkReader reader, ChunkData chunkData); void mergeLand(@Nonnull ChunkReader reader, @Nonnull ChunkData chunkData, @Nullable ChunkReader cavesReader); - void mergeBiomes(@Nonnull ChunkReader reader, @Nonnull ChunkData chunkData); } diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java index 8ea6c68..7e70476 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java @@ -48,11 +48,6 @@ public class RelativeMerger implements Merger { // OVERWRITES - // @Override - // public void merge(ChunkReader reader, ChunkData chunkData) { - // this.mergeLand(reader, chunkData); - // this.mergeBiomes(reader, chunkData); - // } @Override public void mergeLand(ChunkReader reader, ChunkData chunkData, @Nullable ChunkReader cavesReader) { Function, Block> blockGetter = v -> chunkData.getBlock(RelativeMerger.relativeCoordinates(v.clone())); @@ -155,40 +150,6 @@ private boolean isCustomWorldOreOutOfVanillaCaves(Block b, Block vanillaBlock) { return keptReferenceWorldBlocks_.contains(b.getName().toUpperCase()) && (vanillaBlock == null || vanillaBlock.isSolid()); } - @Override - public void mergeBiomes(ChunkReader reader, ChunkData chunkData) { - - // Extracting all non-solid blocks from base chunk, and trunking them into 4x4x4 biome cells. - List> cells = reader.locationsOf(m -> !m.isSolid()).stream().map(LocatedBlock::vector).peek(v -> { - v.setX(v.x() >> 2); - v.setY(v.y() >> 2); - v.setZ(v.z() >> 2); - }).distinct().toList(); - - // Using a spreader to map cells in a 3D grid - Spreader spreader = new Spreader().setContainer(0, chunkData.getMinHeight() >> 2, 0, 3, (chunkData.getMaxHeight() >> 2) - 1, 3) - .setRootVectors(cells).setIterationsAmount(1); - spreader.spread(); - - // Looping through every cell - VectorIterable i = new VectorIterable(0, 4, chunkData.getMinHeight() >> 2, chunkData.getMaxHeight() >> 2, 0, 4); - for (Vector v : i) { - Biome b; - if (!spreader.isPresent(v)) { - int x = v.x() << 2, y = v.y() << 2, z = v.z() << 2; - b = chunkData.getBiome(x, y, z); - if (this.keptBiomes_.contains(b)) { - continue; - } - } - b = reader.biomeAtCell(v).orElse(null); - if (b == null) { - continue; - } - chunkData.setBiome(v, b); - } - } - // PRIVATE UTIL private static Vector absoluteCoordinates(int chunkX, int chunkZ, Vector v) { diff --git a/Underilla-Fabric/build.gradle b/Underilla-Fabric/build.gradle index 60a2ce9..193953c 100644 --- a/Underilla-Fabric/build.gradle +++ b/Underilla-Fabric/build.gradle @@ -1,85 +1,85 @@ -plugins { - id 'fabric-loom' version '1.0-SNAPSHOT' - id 'maven-publish' -} +// plugins { +// id 'fabric-loom' version '1.0-SNAPSHOT' +// id 'maven-publish' +// } -version = project.mod_version -group = project.maven_group +// version = project.mod_version +// group = project.maven_group -repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. -} +// repositories { +// // Add repositories to retrieve artifacts from in here. +// // You should only use this when depending on other mods because +// // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. +// // See https://docs.gradle.org/current/userguide/declaring_repositories.html +// // for more information about repositories. +// } -dependencies { - // To change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" +// dependencies { +// // To change the versions see the gradle.properties file +// minecraft "com.mojang:minecraft:${project.minecraft_version}" +// mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" +// modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" -} +// // Fabric API. This is technically optional, but you probably want it anyway. +// modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" +// } -processResources { - inputs.property "version", project.version - inputs.property "minecraft_version", project.minecraft_version - inputs.property "loader_version", project.loader_version - filteringCharset "UTF-8" +// processResources { +// inputs.property "version", project.version +// inputs.property "minecraft_version", project.minecraft_version +// inputs.property "loader_version", project.loader_version +// filteringCharset "UTF-8" - filesMatching("fabric.mod.json") { - expand "version": project.version, - "minecraft_version": project.minecraft_version, - "loader_version": project.loader_version - } -} +// filesMatching("fabric.mod.json") { +// expand "version": project.version, +// "minecraft_version": project.minecraft_version, +// "loader_version": project.loader_version +// } +// } -def targetJavaVersion = 17 -tasks.withType(JavaCompile).configureEach { - // ensure that the encoding is set to UTF-8, no matter what the system default is - // this fixes some edge cases with special characters not displaying correctly - // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html - // If Javadoc is generated, this must be specified in that task too. - it.options.encoding = "UTF-8" - if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { - it.options.release = targetJavaVersion - } -} +// def targetJavaVersion = 21 +// tasks.withType(JavaCompile).configureEach { +// // ensure that the encoding is set to UTF-8, no matter what the system default is +// // this fixes some edge cases with special characters not displaying correctly +// // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +// // If Javadoc is generated, this must be specified in that task too. +// it.options.encoding = "UTF-8" +// if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { +// it.options.release = targetJavaVersion +// } +// } -java { - def javaVersion = JavaVersion.toVersion(targetJavaVersion) - if (JavaVersion.current() < javaVersion) { - toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) - } - archivesBaseName = project.archives_base_name - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. - withSourcesJar() -} +// java { +// def javaVersion = JavaVersion.toVersion(targetJavaVersion) +// if (JavaVersion.current() < javaVersion) { +// toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) +// } +// archivesBaseName = project.archives_base_name +// // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task +// // if it is present. +// // If you remove this line, sources will not be generated. +// withSourcesJar() +// } -jar { - from("LICENSE") { - rename { "${it}_${project.archivesBaseName}" } - } -} +// jar { +// from("LICENSE") { +// rename { "${it}_${project.archivesBaseName}" } +// } +// } -// configure the maven publication -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - } - } +// // configure the maven publication +// publishing { +// publications { +// mavenJava(MavenPublication) { +// from components.java +// } +// } - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - // Notice: This block does NOT have the same function as the block in the top level. - // The repositories here will be used for publishing your artifact, not for - // retrieving dependencies. - } -} +// // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. +// repositories { +// // Add repositories to publish to here. +// // Notice: This block does NOT have the same function as the block in the top level. +// // The repositories here will be used for publishing your artifact, not for +// // retrieving dependencies. +// } +// } diff --git a/Underilla-Fabric/settings.gradle b/Underilla-Fabric/settings.gradle index 8f44b49..0378999 100644 --- a/Underilla-Fabric/settings.gradle +++ b/Underilla-Fabric/settings.gradle @@ -1,11 +1,11 @@ -pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - } -} +// pluginManagement { +// repositories { +// maven { +// name = 'Fabric' +// url = 'https://maven.fabricmc.net/' +// } +// gradlePluginPortal() +// } +// } -rootProject.name = "Underilla-Fabric" +// rootProject.name = "Underilla-Fabric" diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index 7fbfe8b..70d36d7 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -48,6 +48,8 @@ tasks { compileJava { // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. // See https://openjdk.java.net/jeps/247 for more information. - options.release.set(17) + options.release.set(21) } -} \ No newline at end of file +} + +paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION \ No newline at end of file diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java index f6a5847..3b6a7c2 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java @@ -15,18 +15,19 @@ public final class Underilla extends JavaPlugin { public static final Config CONFIG = new Config(""); - private BukkitWorldReader worldReader_ = null; - private @Nullable BukkitWorldReader worldCavesReader_ = null; + private BukkitWorldReader worldSurfaceReader; + private @Nullable BukkitWorldReader worldCavesReader; + private com.jkantrell.mc.underilla.spigot.generation.WorldInitListener worldInitListener; @Override public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { - if (this.worldReader_ == null) { + if (this.worldSurfaceReader == null) { this.getServer().getLogger().warning("No world with name '" + Underilla.CONFIG.referenceWorldName + "' found"); return super.getDefaultWorldGenerator(worldName, id); } this.getServer().getLogger().info("Using Underilla as world generator!"); - return new UnderillaChunkGenerator(this.worldReader_, this.worldCavesReader_); + return new UnderillaChunkGenerator(this.worldSurfaceReader, this.worldCavesReader); } @Override @@ -44,7 +45,7 @@ public void onEnable() { // Loading reference world try { - this.worldReader_ = new BukkitWorldReader(Underilla.CONFIG.referenceWorldName); + this.worldSurfaceReader = new BukkitWorldReader(Underilla.CONFIG.referenceWorldName); this.getServer().getLogger().info("World + '" + Underilla.CONFIG.referenceWorldName + "' found."); } catch (NoSuchFieldException e) { this.getServer().getLogger().warning("No world with name '" + Underilla.CONFIG.referenceWorldName + "' found"); @@ -54,7 +55,7 @@ public void onEnable() { if (Underilla.CONFIG.transferWorldFromCavesWorld) { try { this.getServer().getLogger().info("Loading caves world"); - this.worldCavesReader_ = new BukkitWorldReader(Underilla.CONFIG.cavesWorldName); + this.worldCavesReader = new BukkitWorldReader(Underilla.CONFIG.cavesWorldName); } catch (NoSuchFieldException e) { this.getServer().getLogger().warning("No world with name '" + Underilla.CONFIG.cavesWorldName + "' found"); e.printStackTrace(); @@ -65,7 +66,8 @@ public void onEnable() { if (CONFIG.generateStructures) { this.getServer().getPluginManager().registerEvents(new StructureEventListener(CONFIG.structureBlackList), this); } - this.getServer().getPluginManager().registerEvents(new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(), this); + worldInitListener = new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(worldSurfaceReader, worldCavesReader); + this.getServer().getPluginManager().registerEvents(worldInitListener, this); } @Override @@ -76,8 +78,11 @@ public void onDisable() { this.getServer().getLogger() .info(entry.getKey() + " took " + entry.getValue() + "ms (" + (entry.getValue() * 100 / totalTime) + "%)"); } + this.getServer().getLogger().info("Map of chunks: " + worldInitListener.getCustomBiomeSource().getBiomesPlaced().entrySet() + .stream().sorted().map(entry -> entry.getKey() + ": " + entry.getValue()).reduce((a, b) -> a + ", " + b).orElse("")); } catch (Exception e) { - this.getServer().getLogger().info("Fail to print times"); + this.getServer().getLogger().info("Fail to print times or biomes placed."); + e.printStackTrace(); } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java index 4c6fb96..d2e2fa4 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java @@ -6,7 +6,6 @@ import java.util.Random; import org.bukkit.HeightMap; import org.bukkit.World; -import org.bukkit.block.Biome; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.ChunkGenerator; @@ -113,20 +112,9 @@ public boolean shouldGenerateStructures(WorldInfo worldInfo, Random random, int return this.delegate_.shouldGenerateStructures(chunkX, chunkZ); } + // To support custom biomes, we can't use bukkit biome provider. So biome merging is done in CustomBiomeSource. @Override - public BiomeProvider getDefaultBiomeProvider(@Nonnull WorldInfo worldInfo) { - // // if biome need to be transfered from the custom world add a custom biome provider - // // (For MergeStrategy.RELATIVE, kept underground biomes are transfered in the mergeBiomes method not here) - // if (CONFIG.transferBiomes && (!CONFIG.mergeStrategy.equals(MergeStrategy.RELATIVE) || CONFIG.keptUndergroundBiomes.isEmpty())) { - // Bukkit.getLogger() - // .info("Underilla Use the custom biome provider from file data. Structures will be generate in the right biome."); - // return new BiomeProviderFromFile(); - // } else { - // Bukkit.getLogger().info("Underilla Use the default biome provider. Structures will be generate in bad biomes."); - // return null; - // } - return null; - } + public BiomeProvider getDefaultBiomeProvider(@Nonnull WorldInfo worldInfo) { return null; } // CLASSES @@ -159,33 +147,4 @@ public void populate(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, this.generator_.reInsertLiquids(reader, chunkData); } } - - private class BiomeProviderFromFile extends BiomeProvider { - - @Override - public @Nonnull Biome getBiome(@Nonnull WorldInfo worldInfo, int x, int y, int z) { - // // Read biome from the custom world - // BukkitBiome biome = (BukkitBiome) worldReader_.biomeAt(x, y, z).orElse(null); - // // Read biome from the caves world - // if (worldCavesReader_ != null) { - // BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader_.biomeAt(x, y, z).orElse(null); - // if (cavesBiome != null && CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getBiome())) { - // return cavesBiome.getBiome(); - // } - // // read biome from the generating world - // } else { - // // TODO It might be possible to keep default generator on Bukkit.yml then enforce Underilla generator and delegate some - // // action to default generator - // } - // return biome == null ? Biome.PLAINS : biome.getBiome(); - return Biome.PLAINS; - } - - @Override - public @Nonnull List getBiomes(@Nonnull WorldInfo worldInfo) { - return List.of(Biome.values()).stream().filter(b -> !b.equals(Biome.CUSTOM)).toList(); - } - - } - } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index b6bba13..23fdd39 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -6,12 +6,24 @@ import org.bukkit.event.Listener; import org.bukkit.event.world.WorldInitEvent; import com.jkantrell.mc.underilla.spigot.Underilla; +import com.jkantrell.mc.underilla.spigot.impl.BukkitWorldReader; import com.jkantrell.mc.underilla.spigot.impl.CustomBiomeSource; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.chunk.ChunkGenerator; public class WorldInitListener implements Listener { + private final BukkitWorldReader worldSurfaceReader; + private final BukkitWorldReader worldCavesReader; + private CustomBiomeSource customBiomeSource; + + public WorldInitListener(BukkitWorldReader worldSurfaceReader, BukkitWorldReader worldCavesReader) { + this.worldSurfaceReader = worldSurfaceReader; + this.worldCavesReader = worldCavesReader; + } + + public CustomBiomeSource getCustomBiomeSource() { return customBiomeSource; } + @EventHandler public void onWorldInit(WorldInitEvent event) { Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); @@ -22,7 +34,7 @@ public void onWorldInit(WorldInitEvent event) { ChunkGenerator vanilla = serverLevel.getChunkSource().getGenerator(); BiomeSource vanillaBiomeSource = vanilla.getBiomeSource(); - BiomeSource customBiomeSource = new CustomBiomeSource(vanillaBiomeSource); + customBiomeSource = new CustomBiomeSource(vanillaBiomeSource, worldSurfaceReader, worldCavesReader); try { Field biomeSourceField = ChunkGenerator.class.getDeclaredField("biomeSource"); diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java index 72240ed..dcd826b 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java @@ -41,5 +41,5 @@ public boolean equals(Object o) { } @Override - public String toString() { return "BukkitBiome{" + "biome_=" + name + '}'; } + public String toString() { return "BukkitBiome{" + name + '}'; } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java index 9b0f188..43b6108 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java @@ -46,30 +46,5 @@ public Optional blockFromTag(CompoundTag tag) { return Optional.of(block); } @Override - public Optional biomeFromTag(StringTag tag) { - return Optional.of(new BukkitBiome(tag.getValue())); - // String[] raw = tag.getValue().split(":"); - // String name = raw.length > 1 ? raw[1] : raw[0]; - // if (customBiomes.containsKey(name)) { - // Biome biome = new BukkitBiome(tag.getValue()); - // return Optional.of(biome); - // } - // try { - // org.bukkit.block.Biome nativeBiome = org.bukkit.block.Biome.valueOf(name.toUpperCase()); - // Biome biome = new BukkitBiome(nativeBiome); - // return Optional.of(biome); - // } catch (IllegalArgumentException e) { - // Bukkit.getLogger().warning("Could not resolve biome '" + name + "' try to create a custom biome from it"); - // org.bukkit.block.Biome customBiome = customBiomes.computeIfAbsent(name, b -> org.bukkit.block.Biome.PLAINS); - // // CustomBiome customBiome = customBiomes.computeIfAbsent(name, CustomBiome::new); - // Biome biome = new BukkitBiome(customBiome); - // return Optional.of(biome); - // } - } - // public class CustomBiome implements Biome { - // private final String name; - // public CustomBiome(String name) { this.name = name; } - // @Override - // public String getName() { return name; } - // } + public Optional biomeFromTag(StringTag tag) { return Optional.of(new BukkitBiome(tag.getValue())); } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java index a0d6269..a24fc2a 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -1,6 +1,9 @@ package com.jkantrell.mc.underilla.spigot.impl; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; +import javax.annotation.Nonnull; import com.jkantrell.mc.underilla.spigot.Underilla; import com.mojang.serialization.MapCodec; import net.minecraft.core.Holder; @@ -11,8 +14,20 @@ public class CustomBiomeSource extends BiomeSource { private final BiomeSource vanillaBiomeSource; + private final BukkitWorldReader worldSurfaceReader; + private final BukkitWorldReader worldCavesReader; + private final Map biomesPlaced; + private long lastInfoPrinted = 0; + private long lastWarnningPrinted = 0; - public CustomBiomeSource(BiomeSource vanillaBiomeSource) { this.vanillaBiomeSource = vanillaBiomeSource; } + public CustomBiomeSource(BiomeSource vanillaBiomeSource, BukkitWorldReader worldSurfaceReader, BukkitWorldReader worldCavesReader) { + this.vanillaBiomeSource = vanillaBiomeSource; + this.worldSurfaceReader = worldSurfaceReader; + this.worldCavesReader = worldCavesReader; + this.biomesPlaced = new ConcurrentHashMap<>(); + } + + public Map getBiomesPlaced() { return biomesPlaced; } @Override protected MapCodec codec() { throw new UnsupportedOperationException("Not supported"); } @@ -26,17 +41,65 @@ protected Stream> collectPossibleBiomes() { } @Override - public Holder getNoiseBiome(int x, int y, int z, Sampler noise) { + public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) { + // Keep biome from vanilla noise biome generation if it's in the list of keptUndergroundBiomes. Holder vanillaBiome = vanillaBiomeSource.getNoiseBiome(x, y, z, noise); - if (vanillaBiome != null && Underilla.CONFIG.keptUndergroundBiomes.contains(vanillaBiome.getRegisteredName())) { - Underilla.getInstance().getLogger().info("Use vanillaBiome because it's a keptUndergroundBiomes: " - + vanillaBiome.getRegisteredName() + " at " + x + ", " + y + ", " + z); + // Edit value to mach actual world coordinates. + x = x << 2; + y = y << 2; + z = z << 2; + if (vanillaBiome != null && (!Underilla.CONFIG.transferBiomes + || Underilla.CONFIG.keptUndergroundBiomes.contains(vanillaBiome.getRegisteredName()))) { + info("Use vanillaBiome because we don't transfer biome or it's a keptUndergroundBiomes: " + vanillaBiome.getRegisteredName() + + " at " + x + ", " + y + ", " + z); + // biomesPlaced.put("noise:" + vanillaBiome.getRegisteredName(), + // biomesPlaced.getOrDefault(vanillaBiome.getRegisteredName(), 0L) + 1); + return vanillaBiome; + } + + // Get biome from cave world if it's in the list of keptUndergroundBiomes. + if (Underilla.CONFIG.transferWorldFromCavesWorld && worldCavesReader != null) { + BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader.biomeAt(x, y, z).orElse(null); + if (cavesBiome != null && Underilla.CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getName())) { + info("Use cavesBiome because it's a transferedCavesWorldBiomes: " + cavesBiome.getName() + " at " + x + ", " + y + ", " + + z); + // biomesPlaced.put("caves:" + cavesBiome.getName(), biomesPlaced.getOrDefault(cavesBiome.getName(), 0L) + 1); + return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome(cavesBiome.getName())); + } + } + + // Get biome from surface world. + BukkitBiome surfaceBiome = (BukkitBiome) worldSurfaceReader.biomeAt(x, y, z).orElse(null); + if (surfaceBiome != null) { + info("Use surfaceBiome: " + surfaceBiome.getName() + " at " + x + ", " + y + ", " + z); + biomesPlaced.put("surface:" + surfaceBiome.getName(), biomesPlaced.getOrDefault(surfaceBiome.getName(), 0L) + 1); + return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome(surfaceBiome.getName())); + } + + // If no other biome found, use plain biome. + warning("Use vanilla because no other biome found at " + x + ", " + y + ", " + z); + if (vanillaBiome != null) { + // biomesPlaced.put("notfound:", biomesPlaced.getOrDefault(vanillaBiome.getRegisteredName(), 0L) + 1); return vanillaBiome; + } else { + // biomesPlaced.put("error:", biomesPlaced.getOrDefault("plains", 0L) + 1); + return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome("minecraft:plains")); + } + } + + private synchronized void info(String message) { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastInfoPrinted > 1000) { + Underilla.getInstance().getLogger().info(message); + lastInfoPrinted = currentTime; + } + } + private synchronized void warning(String message) { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastWarnningPrinted > 1000) { + Underilla.getInstance().getLogger().warning(message); + lastWarnningPrinted = currentTime; } - // TODO get key from custom surface world. - String biomeKey = "mvndi:baltic_coast"; - // Underilla.getInstance().getLogger().info("Getting noise biome: " + x + ", " + y + ", " + z + ": " + biomeKey); - return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome(biomeKey)); } } diff --git a/settings.gradle b/settings.gradle index 348557c..7418778 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,4 +23,4 @@ plugins { } rootProject.name = 'Underilla' -include('Underilla-Core', "Underilla-Fabric", "Underilla-Spigot") \ No newline at end of file +include('Underilla-Core', "Underilla-Spigot") //, "Underilla-Fabric" \ No newline at end of file From d6e4ea2f3b7456709339977b62a3fc03fbd4b862 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Sun, 19 May 2024 21:55:00 +0200 Subject: [PATCH 05/21] minor fix --- Underilla-Spigot/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index 70d36d7..850962d 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -52,4 +52,5 @@ tasks { } } -paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION \ No newline at end of file +// Break Yamlizer. +// paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION \ No newline at end of file From 3e1223094878021c2c8f4eb566e7d84f9edb7cf4 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Mon, 20 May 2024 15:16:23 +0200 Subject: [PATCH 06/21] Fix net.minecraft.ReportedException: Biome decoration --- .../mc/underilla/spigot/Underilla.java | 14 ++- .../generation/NMSExtendedChunkGenerator.java | 89 +++++++++++++++++++ .../spigot/generation/WorldInitListener.java | 12 +-- .../spigot/impl/CustomBiomeSource.java | 14 ++- 4 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java index 3b6a7c2..a7c69b9 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java @@ -66,8 +66,10 @@ public void onEnable() { if (CONFIG.generateStructures) { this.getServer().getPluginManager().registerEvents(new StructureEventListener(CONFIG.structureBlackList), this); } - worldInitListener = new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(worldSurfaceReader, worldCavesReader); - this.getServer().getPluginManager().registerEvents(worldInitListener, this); + if (CONFIG.transferBiomes) { + worldInitListener = new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(worldSurfaceReader, worldCavesReader); + this.getServer().getPluginManager().registerEvents(worldInitListener, this); + } } @Override @@ -78,8 +80,12 @@ public void onDisable() { this.getServer().getLogger() .info(entry.getKey() + " took " + entry.getValue() + "ms (" + (entry.getValue() * 100 / totalTime) + "%)"); } - this.getServer().getLogger().info("Map of chunks: " + worldInitListener.getCustomBiomeSource().getBiomesPlaced().entrySet() - .stream().sorted().map(entry -> entry.getKey() + ": " + entry.getValue()).reduce((a, b) -> a + ", " + b).orElse("")); + if (worldInitListener != null) { + this.getServer().getLogger() + .info("Map of chunks: " + worldInitListener.getCustomBiomeSource().getBiomesPlaced().entrySet().stream() + .sorted((a, b) -> Long.compare(b.getValue(), a.getValue())) + .map(entry -> entry.getKey() + ": " + entry.getValue()).reduce((a, b) -> a + ", " + b).orElse("")); + } } catch (Exception e) { this.getServer().getLogger().info("Fail to print times or biomes placed."); e.printStackTrace(); diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java new file mode 100644 index 0000000..bcee07f --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java @@ -0,0 +1,89 @@ +package com.jkantrell.mc.underilla.spigot.generation; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.NoiseColumn; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.GenerationStep.Carving; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.blending.Blender; + +public class NMSExtendedChunkGenerator extends ChunkGenerator { + + private final ChunkGenerator vanillaChunkGenerator; + + public NMSExtendedChunkGenerator(ChunkGenerator vanillaChunkGenerator, BiomeSource biomeSource) { + super(biomeSource); + this.vanillaChunkGenerator = vanillaChunkGenerator; + } + + @Override + protected MapCodec codec() { + try { + Method method = vanillaChunkGenerator.getClass().getDeclaredMethod("codec"); + method.setAccessible(true); + return (MapCodec) method.invoke(vanillaChunkGenerator); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void applyCarvers(WorldGenRegion chunkRegion, long seed, RandomState noiseConfig, BiomeManager biomeAccess, + StructureManager structureAccessor, ChunkAccess chunk, Carving carverStep) { + vanillaChunkGenerator.applyCarvers(chunkRegion, seed, noiseConfig, biomeAccess, structureAccessor, chunk, carverStep); + } + + @Override + public void buildSurface(WorldGenRegion region, StructureManager structures, RandomState noiseConfig, ChunkAccess chunk) { + vanillaChunkGenerator.buildSurface(region, structures, noiseConfig, chunk); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion region) { vanillaChunkGenerator.spawnOriginalMobs(region); } + + @Override + public int getGenDepth() { return vanillaChunkGenerator.getGenDepth(); } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState noiseConfig, + StructureManager structureAccessor, ChunkAccess chunk) { + + return vanillaChunkGenerator.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk); + } + + @Override + public int getSeaLevel() { return vanillaChunkGenerator.getSeaLevel(); } + + @Override + public int getMinY() { return vanillaChunkGenerator.getMinY(); } + + @Override + public int getBaseHeight(int x, int z, Types heightmap, LevelHeightAccessor world, RandomState noiseConfig) { + return vanillaChunkGenerator.getBaseHeight(x, z, heightmap, world, noiseConfig); + } + + @Override + public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor world, RandomState noiseConfig) { + return vanillaChunkGenerator.getBaseColumn(x, z, world, noiseConfig); + } + + @Override + public void addDebugScreenInfo(List text, RandomState noiseConfig, BlockPos pos) { + vanillaChunkGenerator.addDebugScreenInfo(text, noiseConfig, pos); + } + +} diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index 23fdd39..21fddf0 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -1,6 +1,5 @@ package com.jkantrell.mc.underilla.spigot.generation; -import java.lang.reflect.Field; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -36,14 +35,7 @@ public void onWorldInit(WorldInitEvent event) { BiomeSource vanillaBiomeSource = vanilla.getBiomeSource(); customBiomeSource = new CustomBiomeSource(vanillaBiomeSource, worldSurfaceReader, worldCavesReader); - try { - Field biomeSourceField = ChunkGenerator.class.getDeclaredField("biomeSource"); - biomeSourceField.setAccessible(true); - biomeSourceField.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); - Underilla.getInstance().getLogger().info("Successfully injected custom biome source."); - } catch (NoSuchFieldException | IllegalAccessException e) { - Underilla.getInstance().getLogger().warning("Failed to inject custom biome source."); - e.printStackTrace(); - } + serverLevel.getChunkSource().chunkMap.generator = new NMSExtendedChunkGenerator(vanilla, customBiomeSource); } + } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java index a24fc2a..7736f9b 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -1,5 +1,7 @@ package com.jkantrell.mc.underilla.spigot.impl; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -30,7 +32,17 @@ public CustomBiomeSource(BiomeSource vanillaBiomeSource, BukkitWorldReader world public Map getBiomesPlaced() { return biomesPlaced; } @Override - protected MapCodec codec() { throw new UnsupportedOperationException("Not supported"); } + protected MapCodec codec() { + try { + Method method = BiomeSource.class.getDeclaredMethod("codec"); + method.setAccessible(true); + return (MapCodec) method.invoke(vanillaBiomeSource); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + Underilla.getInstance().getLogger().warning("Failed to get codec field from BiomeSource"); + e.printStackTrace(); + return null; + } + } @Override protected Stream> collectPossibleBiomes() { From db3cc6621bb040be55b6d4427830a61eab139ad5 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Mon, 20 May 2024 16:07:49 +0200 Subject: [PATCH 07/21] fix map print --- .../spigot/impl/CustomBiomeSource.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java index 7736f9b..d3703e5 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -63,9 +63,9 @@ public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) if (vanillaBiome != null && (!Underilla.CONFIG.transferBiomes || Underilla.CONFIG.keptUndergroundBiomes.contains(vanillaBiome.getRegisteredName()))) { info("Use vanillaBiome because we don't transfer biome or it's a keptUndergroundBiomes: " + vanillaBiome.getRegisteredName() - + " at " + x + ", " + y + ", " + z); - // biomesPlaced.put("noise:" + vanillaBiome.getRegisteredName(), - // biomesPlaced.getOrDefault(vanillaBiome.getRegisteredName(), 0L) + 1); + + " at " + x + " " + y + " " + z); + String key = "noise:" + vanillaBiome.getRegisteredName(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); return vanillaBiome; } @@ -73,9 +73,9 @@ public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) if (Underilla.CONFIG.transferWorldFromCavesWorld && worldCavesReader != null) { BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader.biomeAt(x, y, z).orElse(null); if (cavesBiome != null && Underilla.CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getName())) { - info("Use cavesBiome because it's a transferedCavesWorldBiomes: " + cavesBiome.getName() + " at " + x + ", " + y + ", " - + z); - // biomesPlaced.put("caves:" + cavesBiome.getName(), biomesPlaced.getOrDefault(cavesBiome.getName(), 0L) + 1); + info("Use cavesBiome because it's a transferedCavesWorldBiomes: " + cavesBiome.getName() + " at " + x + " " + y + " " + z); + String key = "caves:" + cavesBiome.getName(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome(cavesBiome.getName())); } } @@ -83,18 +83,20 @@ public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) // Get biome from surface world. BukkitBiome surfaceBiome = (BukkitBiome) worldSurfaceReader.biomeAt(x, y, z).orElse(null); if (surfaceBiome != null) { - info("Use surfaceBiome: " + surfaceBiome.getName() + " at " + x + ", " + y + ", " + z); - biomesPlaced.put("surface:" + surfaceBiome.getName(), biomesPlaced.getOrDefault(surfaceBiome.getName(), 0L) + 1); + info("Use surfaceBiome: " + surfaceBiome.getName() + " at " + x + " " + y + " " + z); + biomesPlaced.put("surface:" + surfaceBiome.getName(), biomesPlaced.getOrDefault("surface:" + surfaceBiome.getName(), 0L) + 1); return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome(surfaceBiome.getName())); } // If no other biome found, use plain biome. - warning("Use vanilla because no other biome found at " + x + ", " + y + ", " + z); + warning("Use vanilla because no other biome found at " + x + " " + y + " " + z); if (vanillaBiome != null) { - // biomesPlaced.put("notfound:", biomesPlaced.getOrDefault(vanillaBiome.getRegisteredName(), 0L) + 1); + String key = "notfound:" + vanillaBiome.getRegisteredName(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); return vanillaBiome; } else { - // biomesPlaced.put("error:", biomesPlaced.getOrDefault("plains", 0L) + 1); + String key = "error:plains"; + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); return NMSBiomeUtils.getBiomeRegistry().wrapAsHolder(NMSBiomeUtils.getBiome("minecraft:plains")); } } From 72cc5179bc1cfba104e01f57ae56fc58c44ff98a Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Mon, 20 May 2024 16:08:22 +0200 Subject: [PATCH 08/21] Ingore trees in surface calculation. --- .../mc/underilla/core/api/Block.java | 1 + .../core/generation/AbsoluteMerger.java | 2 +- Underilla-Spigot/build.gradle.kts | 2 +- .../mc/underilla/spigot/impl/BukkitBlock.java | 20 +++++++++++++++++++ .../mc/underilla/spigot/io/Config.java | 3 +++ .../src/main/resources/config.yml | 7 +++++++ 6 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/api/Block.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/api/Block.java index 0dca017..fb78b39 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/api/Block.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/api/Block.java @@ -4,6 +4,7 @@ public interface Block { boolean isAir(); boolean isSolid(); + boolean isSolidAndSurfaceBlock(); boolean isLiquid(); boolean isWaterloggable(); void waterlog(); diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java index 98bf56a..c88f21e 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java @@ -96,7 +96,7 @@ public void mergeLand(ChunkReader reader, ChunkData chunkData, @Nullable ChunkRe /** return the 1st block mergeDepth_ blocks under surface or heigth_ */ private int getLowerBlockToRemove(Reader reader, int x, int z, Block defaultBlock) { int lbtr = this.height_ + mergeDepth_; - while (!reader.blockAt(x, lbtr, z).orElse(defaultBlock).isSolid() && lbtr > -64) { + while (!reader.blockAt(x, lbtr, z).orElse(defaultBlock).isSolidAndSurfaceBlock() && lbtr > -64) { lbtr--; } return lbtr - mergeDepth_; diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index 850962d..3763c0a 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "com.Jkantrell.mc" -version = "1.4.3" +version = "1.5.0" repositories { mavenLocal() diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java index 9fb8083..ecd5609 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java @@ -1,11 +1,17 @@ package com.jkantrell.mc.underilla.spigot.impl; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; import org.bukkit.Material; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Waterlogged; import com.jkantrell.mc.underilla.core.api.Block; +import com.jkantrell.mc.underilla.spigot.Underilla; public class BukkitBlock implements Block { + private static Collection ignoredBlockForSurfaceCalculation; // FIELDS private BlockData blockData_; @@ -17,6 +23,15 @@ public class BukkitBlock implements Block { // GETTERS public BlockData getBlockData() { return this.blockData_; } + private static Collection getIgnoredBlocksForSurfaceCalculation() { + if (ignoredBlockForSurfaceCalculation == null) { + List regexListFromConfig = Underilla.CONFIG.ignoredBlockForSurfaceCalculation; + ignoredBlockForSurfaceCalculation = Arrays.stream(Material.values()) + .filter(material -> regexListFromConfig.stream().anyMatch(s -> material.toString().matches(s))) + .collect(Collectors.toSet()); + } + return ignoredBlockForSurfaceCalculation; + } // IMPLEMENTATIONS @Override @@ -25,6 +40,11 @@ public class BukkitBlock implements Block { @Override public boolean isSolid() { return this.blockData_.getMaterial().isSolid(); } + @Override + public boolean isSolidAndSurfaceBlock() { + return isSolid() && !getIgnoredBlocksForSurfaceCalculation().contains(this.blockData_.getMaterial()); + } + @Override public boolean isLiquid() { Material m = this.blockData_.getMaterial(); diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java index 33ac10e..b62c462 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java @@ -67,6 +67,9 @@ public Config(String filePath) { @ConfigField(path = "surface_and_absolute.limit") public int mergeLimit = 22; + @ConfigField(path = "ignored_block_for_surface_calculation") + public List ignoredBlockForSurfaceCalculation = List.of("LEAVES", "LOGS"); + @ConfigField(path = "blend_range") public int mergeBlendRange = 8; diff --git a/Underilla-Spigot/src/main/resources/config.yml b/Underilla-Spigot/src/main/resources/config.yml index d05f19e..d6e8899 100644 --- a/Underilla-Spigot/src/main/resources/config.yml +++ b/Underilla-Spigot/src/main/resources/config.yml @@ -56,6 +56,13 @@ relative_and_surface: depth: 6 +ignored_block_for_surface_calculation: + - .*LEAVES + - .*LOG + - .*WOOD + - .*PLANKS + - .*FENCE + # The range the transition between vanilla and reference worlds takes, the wider, the smoother. # <= 0 results in a sharp transition. blend_range: 8 From 900037e6f963fd6dfe2403d098738bfa8b310fa6 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Thu, 30 May 2024 15:24:39 +0200 Subject: [PATCH 09/21] Add ice to ignored_block_for_surface_calculation --- Underilla-Spigot/src/main/resources/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Underilla-Spigot/src/main/resources/config.yml b/Underilla-Spigot/src/main/resources/config.yml index d6e8899..a688a54 100644 --- a/Underilla-Spigot/src/main/resources/config.yml +++ b/Underilla-Spigot/src/main/resources/config.yml @@ -62,6 +62,7 @@ ignored_block_for_surface_calculation: - .*WOOD - .*PLANKS - .*FENCE + - ICE # The range the transition between vanilla and reference worlds takes, the wider, the smoother. # <= 0 results in a sharp transition. From 1773c684959450e41cb2e08829ea19af45bdb8d6 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Thu, 30 May 2024 23:58:10 +0200 Subject: [PATCH 10/21] Get spawner data from .mca files. Add processResources for plugin.yml version --- Underilla-Core/build.gradle | 5 +- .../mc/underilla/core/reader/ChunkReader.java | 98 ++++++++++--------- Underilla-Spigot/build.gradle.kts | 40 ++++++-- .../mc/underilla/spigot/impl/BukkitBlock.java | 13 ++- .../spigot/impl/BukkitChunkData.java | 8 ++ .../spigot/impl/BukkitChunkReader.java | 48 ++++++++- .../spigot/impl/BukkitRegionChunkData.java | 10 ++ .../src/main/resources/plugin.yml | 7 +- 8 files changed, 169 insertions(+), 60 deletions(-) diff --git a/Underilla-Core/build.gradle b/Underilla-Core/build.gradle index 21df9ab..4fe24f2 100644 --- a/Underilla-Core/build.gradle +++ b/Underilla-Core/build.gradle @@ -3,7 +3,7 @@ plugins { } group = 'com.Jkantrell.mc' -version = '1.4.3' +version = '1.5.1' java.sourceCompatibility = JavaVersion.VERSION_21 repositories { @@ -12,11 +12,12 @@ repositories { maven { url = uri('https://jitpack.io') } } + dependencies { testImplementation platform("org.junit:junit-bom:5.9.1") testImplementation "org.junit.jupiter:junit-jupiter" implementation 'org.apache.commons:commons-lang3:3.12.0' - api 'com.jkantrell:KntNBT:2.2.1' + api 'com.jkantrell:KntNBT:2.2.2' } tasks.test { diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/reader/ChunkReader.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/reader/ChunkReader.java index a18b9c3..d6eb26d 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/reader/ChunkReader.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/reader/ChunkReader.java @@ -1,5 +1,11 @@ package com.jkantrell.mc.underilla.core.reader; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; import com.jkantrell.mc.underilla.core.api.Biome; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.vector.LocatedBlock; @@ -9,53 +15,50 @@ import com.jkantrell.mca.Section; import com.jkantrell.nbt.tag.CompoundTag; import com.jkantrell.nbt.tag.StringTag; -import java.util.*; -import java.util.function.Predicate; -import java.util.stream.Stream; public abstract class ChunkReader implements Reader { - //ASSETS + // ASSETS + public static final int CHUNK_SIZE = 16; public static final int MAXIMUM_SECTION_HEIGHT = 20, MINIMUM_SECTION_HEIGHT = -4; - public static final int MAXIMUM_HEIGHT = MAXIMUM_SECTION_HEIGHT * 16, MINIMUM_HEIGHT = MINIMUM_SECTION_HEIGHT * 16; + public static final int MAXIMUM_HEIGHT = MAXIMUM_SECTION_HEIGHT * CHUNK_SIZE, MINIMUM_HEIGHT = MINIMUM_SECTION_HEIGHT * CHUNK_SIZE; - //FIELDS + // FIELDS private final Chunk chunk_; private Integer airColumnHeight_ = null; - //CONSTRUCTORS - public ChunkReader(Chunk chunk) { - this.chunk_ = chunk; - } + // CONSTRUCTORS + protected ChunkReader(Chunk chunk) { this.chunk_ = chunk; } - //GETTERS - public int getX() { - return this.chunk_.getX(); - } - public int getZ() { - return this.chunk_.getZ(); - } + // GETTERS + public int getX() { return this.chunk_.getX(); } + public int getZ() { return this.chunk_.getZ(); } + public int getGlobalX(int localX) { return this.chunk_.getX() * CHUNK_SIZE + localX; } + public int getGlobalZ(int localZ) { return this.chunk_.getZ() * CHUNK_SIZE + localZ; } - //UTIL + // UTIL @Override public Optional blockAt(int x, int y, int z) { - if (this.chunk_ == null) { return Optional.empty(); } + if (this.chunk_ == null) { + return Optional.empty(); + } CompoundTag tag = this.chunk_.getBlockStateAt(x, y, z); - return this.blockFromTag(tag); + CompoundTag blockEntity = this.chunk_.getBlockEntity(getGlobalX(x), y, getGlobalZ(z)); + return this.blockFromTag(tag, blockEntity); } @Override public Optional biomeAt(int x, int y, int z) { - //Declaring variables + // Declaring variables Map sectionMap = this.chunk_.getSectionMap(); int height = MCAUtil.blockToChunk(y); Section section = sectionMap.get(height); StringTag biomeTag; - //Retuning biome if section exists + // Returning biome if section exists if (section != null) { biomeTag = section.getBiomeAt(x, Math.floorMod(y, 16), z); if (biomeTag != null) { @@ -63,7 +66,7 @@ public Optional biomeAt(int x, int y, int z) { } } - //If sections doesn't exist trying to pull it from the above section + // If sections doesn't exist trying to pull it from the above section int i = height + 1; while (i < MAXIMUM_SECTION_HEIGHT) { section = sectionMap.get(i); @@ -76,7 +79,7 @@ public Optional biomeAt(int x, int y, int z) { i++; } - //If no above section exists, trying to pull it from the under section + // If no above section exists, trying to pull it from the under section i = height - 1; while (i >= MINIMUM_SECTION_HEIGHT) { section = sectionMap.get(i); @@ -89,7 +92,7 @@ public Optional biomeAt(int x, int y, int z) { i--; } - //Returning empty + // Returning empty return Optional.empty(); } public int airSectionsBottom() { @@ -99,9 +102,13 @@ public int airSectionsBottom() { Predicate
isAir = s -> { PaletteContainer paletteContainer = s.getBlockStatePalette(); - if (paletteContainer.getPalette().size() > 1) { return false; } + if (paletteContainer.getPalette().size() > 1) { + return false; + } CompoundTag b = s.getBlockStatePalette().get(0); - if (b == null) { return true; } + if (b == null) { + return true; + } return this.blockFromTag(b).map(Block::isAir).orElse(true); }; @@ -109,7 +116,11 @@ public int airSectionsBottom() { int lowest = MAXIMUM_HEIGHT - 1; while (lowest > MINIMUM_HEIGHT - 1) { s = this.chunk_.getSection(lowest); - if (s == null || isAir.test(s)) { lowest--; } else { break; } + if (s == null || isAir.test(s)) { + lowest--; + } else { + break; + } } this.airColumnHeight_ = (lowest + 1) * 16; @@ -118,7 +129,9 @@ public int airSectionsBottom() { public List locationsOf(Predicate checker, int under, int above) { Stream
sectionStream = this.chunk_.getSections().stream().filter(s -> { int lower = s.getHeight() * 16, upper = lower + 15; - if (under <= lower) { return false; } + if (under <= lower) { + return false; + } return above < upper; }); Predicate locationCheck = l -> l.y() > above && l.y() < under; @@ -127,31 +140,28 @@ public List locationsOf(Predicate checker, int under, int a public List locationsOf(Predicate checker) { return this.locationsOf(checker, l -> true, this.chunk_.getSections().stream()); } - public List locationsOf(Block block) { - return this.locationsOf(block::equals); - } + public List locationsOf(Block block) { return this.locationsOf(block::equals); } public List locationsOf(Block... blocks) { List materialList = Arrays.asList(blocks); return this.locationsOf(materialList::contains); } - //ABSTRACT + // ABSTRACT public abstract Optional blockFromTag(CompoundTag tag); + public abstract Optional blockFromTag(CompoundTag tag, CompoundTag entityTag); public abstract Optional biomeFromTag(StringTag tag); - //PRIVATE UTIL + // PRIVATE UTIL private List locationsOf(Predicate checker, Predicate secondChecker, Stream
sections) { - return sections - .flatMap(s -> s.getBlockLocations(t -> { - Block b = this.blockFromTag(t).orElse(null); - if (b == null) { return false; } - return checker.test(b); - }).stream() - .map(l -> new LocatedBlock(l.x(), l.y() + (s.getHeight()*16), l.z(), this.blockFromTag(l.tag()).get())) - ) - .filter(secondChecker) - .toList(); + return sections.flatMap(s -> s.getBlockLocations(t -> { + Block b = this.blockFromTag(t).orElse(null); + if (b == null) { + return false; + } + return checker.test(b); + }).stream().map(l -> new LocatedBlock(l.x(), l.y() + (s.getHeight() * 16), l.z(), this.blockFromTag(l.tag()).get()))) + .filter(secondChecker).toList(); } } diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index 3763c0a..3945785 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -2,10 +2,13 @@ plugins { `java-library` id("io.github.goooler.shadow") version "8.1.7" id("io.papermc.paperweight.userdev") version "1.7.1" // paperweight // Check for new versions at https://plugins.gradle.org/plugin/io.papermc.paperweight.userdev + `maven-publish` // Add ./gradlew publishToMavenLocal + id("xyz.jpenilla.run-paper") version "2.3.0" } -group = "com.Jkantrell.mc" -version = "1.5.0" +group = "com.jkantrell.mc.underilla.spigot" +version = "1.5.1" +description="Generate vanilla cave in custom world." repositories { mavenLocal() @@ -27,6 +30,11 @@ dependencies { // tasks.build.dependsOn tasks.reobfJar // paperweight // tasks.build.dependsOn tasks.shadowJar // without paperweight +java { + // Configure the java toolchain. This allows gradle to auto-provision JDK 17 on systems that only have JDK 8 installed for example. + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + tasks { shadowJar { minimize() @@ -45,10 +53,30 @@ tasks { dependsOn(reobfJar) } - compileJava { - // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. - // See https://openjdk.java.net/jeps/247 for more information. - options.release.set(21) + processResources { + val props = mapOf( + "name" to project.name, + "version" to project.version, + "description" to project.description, + "apiVersion" to "1.20", + "group" to project.group + ) + inputs.properties(props) + filesMatching("plugin.yml") { + expand(props) + } + } + runServer { + // Configure the Minecraft version for our task. + // This is the only required configuration besides applying the plugin. + // Your plugin's jar (or shadowJar if present) will be used automatically. + minecraftVersion("1.20.6") + } +} + +publishing { + publications.create("maven") { + from(components["java"]) } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java index ecd5609..90d707f 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java @@ -3,10 +3,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.bukkit.Material; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Waterlogged; +import org.bukkit.entity.EntityType; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.spigot.Underilla; @@ -15,10 +17,14 @@ public class BukkitBlock implements Block { // FIELDS private BlockData blockData_; + private Optional spawnedType; // CONSTRUCTORS - public BukkitBlock(BlockData blockData) { this.blockData_ = blockData; } + public BukkitBlock(BlockData blockData) { + this.blockData_ = blockData; + this.spawnedType = Optional.empty(); + } // GETTERS @@ -33,6 +39,11 @@ private static Collection getIgnoredBlocksForSurfaceCalculation() { return ignoredBlockForSurfaceCalculation; } + public Optional getSpawnedType() { return this.spawnedType; } + public void setSpawnedType(String spawnedType) { + this.spawnedType = Optional.ofNullable(EntityType.valueOf(spawnedType.replace("minecraft:", "").toUpperCase())); + } + // IMPLEMENTATIONS @Override public boolean isAir() { return this.blockData_.getMaterial().isAir(); } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java index eef1c73..94e68ea 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java @@ -2,10 +2,13 @@ import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.block.CreatureSpawner; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.EntityType; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.api.ChunkData; +import com.jkantrell.mc.underilla.spigot.Underilla; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -63,6 +66,11 @@ public void setBlock(int x, int y, int z, Block block) { return; } this.chunkData.setBlock(x, y, z, bukkitBlock.getBlockData()); + if (world.getBlockAt(x, y, z) instanceof CreatureSpawner creatureSpawner) { + creatureSpawner.setSpawnedType(bukkitBlock.getSpawnedType().orElse(EntityType.ZOMBIE)); + creatureSpawner.update(); + Underilla.getInstance().getLogger().info("setBlock: Spawner type set to " + creatureSpawner.getSpawnedType()); + } } @Override diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java index 43b6108..07656dd 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java @@ -1,19 +1,18 @@ package com.jkantrell.mc.underilla.spigot.impl; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import org.bukkit.Material; import com.jkantrell.mc.underilla.core.api.Biome; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.reader.ChunkReader; import com.jkantrell.mc.underilla.core.reader.TagInterpreter; +import com.jkantrell.mc.underilla.spigot.Underilla; import com.jkantrell.mca.Chunk; import com.jkantrell.nbt.tag.CompoundTag; import com.jkantrell.nbt.tag.StringTag; public class BukkitChunkReader extends ChunkReader { - private Map customBiomes = new HashMap<>(); + // private Map customBiomes = new HashMap<>(); // CONSTRUCTORS public BukkitChunkReader(Chunk chunk) { super(chunk); } @@ -38,13 +37,54 @@ public Optional blockFromTag(CompoundTag tag) { // In such case, return plain block with no data try { String dataString = TagInterpreter.COMPOUND.interpretBlockDataString(properties); + // if (dataString != null && dataString.length() > 90) { + // Underilla.getInstance().getLogger().info(dataString); + // } block = new BukkitBlock(m.createBlockData(dataString)); } catch (IllegalArgumentException e) { + Underilla.getInstance().getLogger().warning("Failed to create block data " + m + ": " + e.getMessage()); + e.printStackTrace(); block = new BukkitBlock(m.createBlockData()); } - return Optional.of(block); } + + // [21:29:47 INFO]: [Underilla] [STDOUT] Loaded Block Entities: + // {"type":"ListTag","value":{"type":"CompoundTag","list":[{"MaxNearbyEntities":{"type":"ShortTag","value":6}, + // "RequiredPlayerRange":{"type":"ShortTag","value":16},"SpawnCount":{"type":"ShortTag","value":4}, + // "SpawnData":{"type":"CompoundTag","value":{"entity":{"type":"CompoundTag","value":{"id":{"type":"StringTag","value":"minecraft:cave_spider"}}}}}, + // "MaxSpawnDelay":{"type":"ShortTag","value":800},"Delay":{"type":"ShortTag","value":20},"keepPacked":{"type":"ByteTag","value":0}, + // "x":{"type":"IntTag","value":5332},"y":{"type":"IntTag","value":-27},"z":{"type":"IntTag","value":5988}, + // "id":{"type":"StringTag","value":"minecraft:mob_spawner"},"SpawnRange":{"type":"ShortTag","value":4}, + // "MinSpawnDelay":{"type":"ShortTag","value":200},"SpawnPotentials":{"type":"ListTag","value":{"type":"EndTag","list":[]}}}]}} + @Override + public Optional blockFromTag(CompoundTag tag, CompoundTag blockEntity) { + Optional block = blockFromTag(tag); + if (block.isPresent() && blockEntity != null) { + // System.out.println("Loaded Block Entities: " + blockEntity); + // if it's a spawner or a chest + if (blockEntity.getString("id").equals("minecraft:mob_spawner")) { + Underilla.getInstance().getLogger().info("Interesting Spawner: " + blockEntity); + String spawnedType = blockEntity.getCompoundTag("SpawnData").getCompoundTag("entity").getString("id"); + // System.out.println("Spawner Type: " + spawnedType); + if (block.get() instanceof BukkitBlock bukkitBlock) { + // bukkitBlock.setBlockData(Material.SPAWNER.createBlockData()); + bukkitBlock.setSpawnedType(spawnedType); + Underilla.getInstance().getLogger().info("blockFromTag: " + bukkitBlock.getSpawnedType()); + } + + // } else if (blockEntity.getString("id").equals("minecraft:chest")) { + // System.out.println("Interesting Chest: " + blockEntity); + + } else if (blockEntity.getString("id").equals("minecraft:chest")) { + + } + // System.out.println("Loaded Block Entities: " + blockEntity.getString("id") + ":\n" + blockEntity); + } + return block; + } + + @Override public Optional biomeFromTag(StringTag tag) { return Optional.of(new BukkitBiome(tag.getValue())); } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java index c3139a9..5022b86 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitRegionChunkData.java @@ -6,6 +6,7 @@ import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.api.ChunkData; import com.jkantrell.mc.underilla.core.vector.VectorIterable; +import com.jkantrell.mc.underilla.spigot.Underilla; public class BukkitRegionChunkData implements ChunkData { @@ -57,6 +58,15 @@ public void setBlock(int x, int y, int z, Block block) { return; } this.region_.setBlockData(this.absX_ + x, y, this.absZ_ + z, bukkitBlock.getBlockData()); + // TODO nexts lines are never called + if (bukkitBlock.getSpawnedType().isPresent()) { + if (region_.getWorld().getBlockAt(this.absX_ + x, y, + this.absZ_ + z) instanceof org.bukkit.block.CreatureSpawner creatureSpawner) { + creatureSpawner.setSpawnedType(bukkitBlock.getSpawnedType().get()); + creatureSpawner.update(); + Underilla.getInstance().getLogger().info("\nSet spawner type to " + bukkitBlock.getSpawnedType().get()); + } + } } @Override public void setBiome(int x, int y, int z, Biome biome) { diff --git a/Underilla-Spigot/src/main/resources/plugin.yml b/Underilla-Spigot/src/main/resources/plugin.yml index 9903b97..4ab2ede 100644 --- a/Underilla-Spigot/src/main/resources/plugin.yml +++ b/Underilla-Spigot/src/main/resources/plugin.yml @@ -1,6 +1,7 @@ name: Underilla author: Jeshuakrc, Hydrolien -version: '${project.version}' -main: com.jkantrell.mc.underilla.spigot.Underilla -api-version: '1.19' # to support 1.19.4, it don't break anything in 1.20.x to use 1.19 api version. +version: '$version' +main: ${group}.Underilla +description: $description +api-version: "$apiVersion" load: STARTUP From 43bdfeb3d8b04402be2f3fcdecfc1b0a50298d69 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Fri, 31 May 2024 20:07:38 +0200 Subject: [PATCH 11/21] More tries to set spawner block. --- .../mc/underilla/spigot/impl/BukkitBlock.java | 1 + .../spigot/impl/BukkitChunkData.java | 36 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java index 90d707f..35afcb9 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBlock.java @@ -84,6 +84,7 @@ public void waterlog() { @Override public String getName() { return this.blockData_.getMaterial().toString().toLowerCase(); } + public Material getMaterial() { return this.blockData_.getMaterial(); } @Override public String getNameSpace() { return null; } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java index 94e68ea..2d2293d 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java @@ -5,7 +5,8 @@ import org.bukkit.block.CreatureSpawner; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.entity.EntityType; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.CraftBlockState; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.api.ChunkData; import com.jkantrell.mc.underilla.spigot.Underilla; @@ -65,12 +66,37 @@ public void setBlock(int x, int y, int z, Block block) { if (!(block instanceof BukkitBlock bukkitBlock)) { return; } + + this.chunkData.setBlock(x, y, z, bukkitBlock.getBlockData()); - if (world.getBlockAt(x, y, z) instanceof CreatureSpawner creatureSpawner) { - creatureSpawner.setSpawnedType(bukkitBlock.getSpawnedType().orElse(EntityType.ZOMBIE)); - creatureSpawner.update(); - Underilla.getInstance().getLogger().info("setBlock: Spawner type set to " + creatureSpawner.getSpawnedType()); + + if (bukkitBlock.getMaterial().equals(org.bukkit.Material.SPAWNER)) { + // CraftBlock craftBlock = ((CraftBlock) world.getBlockAt(x, y, z)); + // world.getBlockAt(x, y, z).getState().update(); + org.bukkit.block.Block bblock = world.getBlockAt(x, y, z); + bblock.getState().setType(org.bukkit.Material.SPAWNER); + bblock.getState().update(true, true); + CraftBlock craftBlock = (CraftBlock) bblock; + CraftBlockState blockState = (CraftBlockState) craftBlock.getState(); + + + Underilla.getInstance().getLogger() + .info("setBlock: Spawner block detected at " + x + ", " + y + ", " + z + " with class " + bblock.getClass() + + ", material " + bblock.getType() + ", state " + bblock.getState() + ", blockData " + bblock.getBlockData() + + ", blockData class " + bblock.getBlockData().getClass() + ", blockData material " + + bblock.getBlockData().getMaterial() + ", blockData class " + bblock.getBlockData().getClass()); + // CreatureSpawner creatureSpawner = new CraftCreatureSpawner(world, + // new SpawnerBlockEntity(craftBlock.getPosition(), craftBlock.getState())); + // creatureSpawner. + + // TODO it's not a CreatureSpawner, it's stay at the previous block type (Deepslate, mostly). + if (bblock.getState() instanceof CreatureSpawner creatureSpawner) { + creatureSpawner.setSpawnedType(bukkitBlock.getSpawnedType().orElse(org.bukkit.entity.EntityType.ZOMBIE)); + // creatureSpawner.update(); + Underilla.getInstance().getLogger().info("setBlock: Spawner type set to " + creatureSpawner.getSpawnedType()); + } } + } @Override From 12e4cf4b8c19146f47f04d45b742608a22d4dec0 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Tue, 11 Jun 2024 09:31:59 +0200 Subject: [PATCH 12/21] Update gradle to 8.8 --- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f86..6f7a6eb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From 3016890fa6672734fd9a331e6bb6a8b5a52042a6 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Tue, 11 Jun 2024 09:33:29 +0200 Subject: [PATCH 13/21] comment log info --- .../spigot/impl/BukkitChunkData.java | 48 +++++++++---------- .../spigot/impl/BukkitChunkReader.java | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java index 2d2293d..f74072c 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkData.java @@ -71,30 +71,30 @@ public void setBlock(int x, int y, int z, Block block) { this.chunkData.setBlock(x, y, z, bukkitBlock.getBlockData()); if (bukkitBlock.getMaterial().equals(org.bukkit.Material.SPAWNER)) { - // CraftBlock craftBlock = ((CraftBlock) world.getBlockAt(x, y, z)); - // world.getBlockAt(x, y, z).getState().update(); - org.bukkit.block.Block bblock = world.getBlockAt(x, y, z); - bblock.getState().setType(org.bukkit.Material.SPAWNER); - bblock.getState().update(true, true); - CraftBlock craftBlock = (CraftBlock) bblock; - CraftBlockState blockState = (CraftBlockState) craftBlock.getState(); - - - Underilla.getInstance().getLogger() - .info("setBlock: Spawner block detected at " + x + ", " + y + ", " + z + " with class " + bblock.getClass() - + ", material " + bblock.getType() + ", state " + bblock.getState() + ", blockData " + bblock.getBlockData() - + ", blockData class " + bblock.getBlockData().getClass() + ", blockData material " - + bblock.getBlockData().getMaterial() + ", blockData class " + bblock.getBlockData().getClass()); - // CreatureSpawner creatureSpawner = new CraftCreatureSpawner(world, - // new SpawnerBlockEntity(craftBlock.getPosition(), craftBlock.getState())); - // creatureSpawner. - - // TODO it's not a CreatureSpawner, it's stay at the previous block type (Deepslate, mostly). - if (bblock.getState() instanceof CreatureSpawner creatureSpawner) { - creatureSpawner.setSpawnedType(bukkitBlock.getSpawnedType().orElse(org.bukkit.entity.EntityType.ZOMBIE)); - // creatureSpawner.update(); - Underilla.getInstance().getLogger().info("setBlock: Spawner type set to " + creatureSpawner.getSpawnedType()); - } + // // CraftBlock craftBlock = ((CraftBlock) world.getBlockAt(x, y, z)); + // // world.getBlockAt(x, y, z).getState().update(); + // org.bukkit.block.Block bblock = world.getBlockAt(x, y, z); + // bblock.getState().setType(org.bukkit.Material.SPAWNER); + // bblock.getState().update(true, true); + // CraftBlock craftBlock = (CraftBlock) bblock; + // CraftBlockState blockState = (CraftBlockState) craftBlock.getState(); + + + // Underilla.getInstance().getLogger() + // .info("setBlock: Spawner block detected at " + x + ", " + y + ", " + z + " with class " + bblock.getClass() + // + ", material " + bblock.getType() + ", state " + bblock.getState() + ", blockData " + bblock.getBlockData() + // + ", blockData class " + bblock.getBlockData().getClass() + ", blockData material " + // + bblock.getBlockData().getMaterial() + ", blockData class " + bblock.getBlockData().getClass()); + // // CreatureSpawner creatureSpawner = new CraftCreatureSpawner(world, + // // new SpawnerBlockEntity(craftBlock.getPosition(), craftBlock.getState())); + // // creatureSpawner. + + // // TODO it's not a CreatureSpawner, it's stay at the previous block type (Deepslate, mostly). + // if (bblock.getState() instanceof CreatureSpawner creatureSpawner) { + // creatureSpawner.setSpawnedType(bukkitBlock.getSpawnedType().orElse(org.bukkit.entity.EntityType.ZOMBIE)); + // // creatureSpawner.update(); + // Underilla.getInstance().getLogger().info("setBlock: Spawner type set to " + creatureSpawner.getSpawnedType()); + // } } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java index 07656dd..91afa1f 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitChunkReader.java @@ -64,13 +64,13 @@ public Optional blockFromTag(CompoundTag tag, CompoundTag blockEntity) { // System.out.println("Loaded Block Entities: " + blockEntity); // if it's a spawner or a chest if (blockEntity.getString("id").equals("minecraft:mob_spawner")) { - Underilla.getInstance().getLogger().info("Interesting Spawner: " + blockEntity); + // Underilla.getInstance().getLogger().info("Interesting Spawner: " + blockEntity); String spawnedType = blockEntity.getCompoundTag("SpawnData").getCompoundTag("entity").getString("id"); // System.out.println("Spawner Type: " + spawnedType); if (block.get() instanceof BukkitBlock bukkitBlock) { // bukkitBlock.setBlockData(Material.SPAWNER.createBlockData()); bukkitBlock.setSpawnedType(spawnedType); - Underilla.getInstance().getLogger().info("blockFromTag: " + bukkitBlock.getSpawnedType()); + // Underilla.getInstance().getLogger().info("blockFromTag: " + bukkitBlock.getSpawnedType()); } // } else if (blockEntity.getString("id").equals("minecraft:chest")) { From 4a3ec08e9acb194e9368ba5dbb926ece4e774aa7 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Tue, 11 Jun 2024 09:56:38 +0200 Subject: [PATCH 14/21] Add sonar. --- .github/workflows/build.yml | 37 +++++++++++++++++++++++++++++++++ Underilla-Core/build.gradle | 25 ---------------------- Underilla-Core/build.gradle.kts | 34 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 Underilla-Core/build.gradle create mode 100644 Underilla-Core/build.gradle.kts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a5bc1cf --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +name: SonarCloud +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: 21 + distribution: 'temurin' + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: ./gradlew build sonar --info \ No newline at end of file diff --git a/Underilla-Core/build.gradle b/Underilla-Core/build.gradle deleted file mode 100644 index 4fe24f2..0000000 --- a/Underilla-Core/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id "java-library" -} - -group = 'com.Jkantrell.mc' -version = '1.5.1' -java.sourceCompatibility = JavaVersion.VERSION_21 - -repositories { - mavenCentral() - mavenLocal() - maven { url = uri('https://jitpack.io') } -} - - -dependencies { - testImplementation platform("org.junit:junit-bom:5.9.1") - testImplementation "org.junit.jupiter:junit-jupiter" - implementation 'org.apache.commons:commons-lang3:3.12.0' - api 'com.jkantrell:KntNBT:2.2.2' -} - -tasks.test { - useJUnitPlatform() -} \ No newline at end of file diff --git a/Underilla-Core/build.gradle.kts b/Underilla-Core/build.gradle.kts new file mode 100644 index 0000000..9f3a403 --- /dev/null +++ b/Underilla-Core/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + `java-library` + id("org.sonarqube") version "4.4.1.3373" +} + +group = "com.Jkantrell.mc" +version = "1.5.1" +java.sourceCompatibility = JavaVersion.VERSION_21 + +repositories { + mavenCentral() + mavenLocal() + maven ("https://jitpack.io") +} + + +dependencies { + // testImplementation platform("org.junit:junit-bom:5.9.1") + // testImplementation("org.junit.jupiter:junit-jupiter") + implementation("org.apache.commons:commons-lang3:3.12.0") + api("com.jkantrell:KntNBT:2.2.2") +} + +tasks.test { + useJUnitPlatform() +} + +sonar { + properties { + property("sonar.projectKey", "mvndicraft_underilla") + property("sonar.organization", "mvndicraft") + property("sonar.host.url", "https://sonarcloud.io") + } +} \ No newline at end of file From 5847b209949eac8a7cbe74055a0cfdb3890cba64 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Tue, 11 Jun 2024 10:09:30 +0200 Subject: [PATCH 15/21] Make KntNBT a jitpack dependency instead of a local dependency. --- Underilla-Core/build.gradle.kts | 2 +- Underilla-Spigot/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Underilla-Core/build.gradle.kts b/Underilla-Core/build.gradle.kts index 9f3a403..016c73a 100644 --- a/Underilla-Core/build.gradle.kts +++ b/Underilla-Core/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { // testImplementation platform("org.junit:junit-bom:5.9.1") // testImplementation("org.junit.jupiter:junit-jupiter") implementation("org.apache.commons:commons-lang3:3.12.0") - api("com.jkantrell:KntNBT:2.2.2") + api("com.github.HydrolienF:KntNBT:2.2.2") } tasks.test { diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index 3945785..b0482e1 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { // tasks.build.dependsOn tasks.shadowJar // without paperweight java { - // Configure the java toolchain. This allows gradle to auto-provision JDK 17 on systems that only have JDK 8 installed for example. + // Configure the java toolchain. This allows gradle to auto-provision JDK 21 on systems that only have JDK 8 installed for example. toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } From 17aa3a3dc2fc35199799512b7a94355f242577d3 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Tue, 11 Jun 2024 19:26:08 +0200 Subject: [PATCH 16/21] Advice & new version --- README.md | 1 + Underilla-Core/build.gradle.kts | 2 +- Underilla-Spigot/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e777be9..a488d74 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Underilla is currently only implemented as a Paper plugin, so it runs only on Pa ### Pregenerate Underilla is significantly slower than the vanilla generator, as it doesn't relly on noise generation but on reading the reference world's region `nbt` files and analyzing its patterns to 'clone' its surface to a vanilla world. So, if your world is intended for heavy duty in a big server. It's recommended to pre-generate the whole reference world area with a chunk generator plugin, such as [Chunky](https://hangar.papermc.io/pop4959/Chunky). I'm planning adding a build-in pre-generation system in the future. +To increase generation speed you should edit `worker-threads` in your `config/paper-global.yml` to match your number of CPU cores, else paper won't use all CPU cores aviables. Using your number of core instead of default value usually double speed generation. ### Performances Huge map generation can takes hours or even days, here is some stats about performance to help you choose your configuration settings. diff --git a/Underilla-Core/build.gradle.kts b/Underilla-Core/build.gradle.kts index 016c73a..974810d 100644 --- a/Underilla-Core/build.gradle.kts +++ b/Underilla-Core/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.Jkantrell.mc" -version = "1.5.1" +version = "1.5.2" java.sourceCompatibility = JavaVersion.VERSION_21 repositories { diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index b0482e1..bb37ecf 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "com.jkantrell.mc.underilla.spigot" -version = "1.5.1" +version = "1.5.2" description="Generate vanilla cave in custom world." repositories { From 2100e5e6d7d072a35af037fb4250f4ea73e137aa Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Mon, 8 Jul 2024 07:44:10 +0000 Subject: [PATCH 17/21] Move settings to .kts --- .../{settings.gradle => settings.gradle.kts} | 0 Underilla-Spigot/settings.gradle | 5 ---- Underilla-Spigot/settings.gradle.kts | 1 + settings.gradle => settings.gradle.kts | 24 +++++++++---------- 4 files changed, 13 insertions(+), 17 deletions(-) rename Underilla-Core/{settings.gradle => settings.gradle.kts} (100%) delete mode 100644 Underilla-Spigot/settings.gradle create mode 100644 Underilla-Spigot/settings.gradle.kts rename settings.gradle => settings.gradle.kts (52%) diff --git a/Underilla-Core/settings.gradle b/Underilla-Core/settings.gradle.kts similarity index 100% rename from Underilla-Core/settings.gradle rename to Underilla-Core/settings.gradle.kts diff --git a/Underilla-Spigot/settings.gradle b/Underilla-Spigot/settings.gradle deleted file mode 100644 index 4a49fb7..0000000 --- a/Underilla-Spigot/settings.gradle +++ /dev/null @@ -1,5 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - */ - -rootProject.name = 'Underilla' diff --git a/Underilla-Spigot/settings.gradle.kts b/Underilla-Spigot/settings.gradle.kts new file mode 100644 index 0000000..92ea88a --- /dev/null +++ b/Underilla-Spigot/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "Underilla" diff --git a/settings.gradle b/settings.gradle.kts similarity index 52% rename from settings.gradle rename to settings.gradle.kts index 7418778..4018e8a 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -6,21 +6,21 @@ * Detailed information about configuring a multi-project build in Gradle can be found * in the user manual at https://docs.gradle.org/8.1.1/userguide/multi_project_builds.html */ -pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - } -} +// pluginManagement { +// repositories { +// maven { +// name = "Fabric" +// url = "https://maven.fabricmc.net/" +// } +// gradlePluginPortal() +// } +// } plugins { // Apply the foojay-resolver plugin to allow automatic download of JDKs - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' + id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" } -rootProject.name = 'Underilla' -include('Underilla-Core', "Underilla-Spigot") //, "Underilla-Fabric" \ No newline at end of file +rootProject.name = "Underilla" +include("Underilla-Core", "Underilla-Spigot") //, "Underilla-Fabric" \ No newline at end of file From 3f6ad5404f4ed84fb318f454476b1ee76ae817d6 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Thu, 29 Aug 2024 16:53:08 +0000 Subject: [PATCH 18/21] 1.21.1 1st almost working version. --- Underilla-Core/build.gradle.kts | 2 +- Underilla-Spigot/build.gradle.kts | 9 ++-- .../generation/NMSExtendedChunkGenerator.java | 8 ++-- .../spigot/generation/WorldInitListener.java | 44 ++++++++++++++++++- .../mc/underilla/spigot/impl/BiomeHelper.java | 11 ++--- .../spigot/impl/CustomBiomeSource.java | 36 +++++++++------ .../underilla/spigot/impl/NMSBiomeUtils.java | 7 ++- 7 files changed, 84 insertions(+), 33 deletions(-) diff --git a/Underilla-Core/build.gradle.kts b/Underilla-Core/build.gradle.kts index 974810d..1468a2b 100644 --- a/Underilla-Core/build.gradle.kts +++ b/Underilla-Core/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.Jkantrell.mc" -version = "1.5.2" +version = "1.5.3" java.sourceCompatibility = JavaVersion.VERSION_21 repositories { diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index bb37ecf..2be5a44 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "com.jkantrell.mc.underilla.spigot" -version = "1.5.2" +version = "1.5.3" description="Generate vanilla cave in custom world." repositories { @@ -22,7 +22,7 @@ repositories { dependencies { // compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") // without paperweight - paperweight.paperDevBundle("1.20.6-R0.1-SNAPSHOT") // paperweight + paperweight.paperDevBundle("1.21.1-R0.1-SNAPSHOT") // paperweight implementation("com.jkantrell:Yamlizer:main-SNAPSHOT") implementation(project(":Underilla-Core")) } @@ -35,6 +35,7 @@ java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } + tasks { shadowJar { minimize() @@ -58,7 +59,7 @@ tasks { "name" to project.name, "version" to project.version, "description" to project.description, - "apiVersion" to "1.20", + "apiVersion" to "1.21", "group" to project.group ) inputs.properties(props) @@ -70,7 +71,7 @@ tasks { // Configure the Minecraft version for our task. // This is the only required configuration besides applying the plugin. // Your plugin's jar (or shadowJar if present) will be used automatically. - minecraftVersion("1.20.6") + minecraftVersion("1.21.1") } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java index bcee07f..cbb2c90 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java @@ -4,7 +4,6 @@ import java.lang.reflect.Method; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; import com.mojang.serialization.MapCodec; import net.minecraft.core.BlockPos; import net.minecraft.server.level.WorldGenRegion; @@ -59,10 +58,9 @@ public void buildSurface(WorldGenRegion region, StructureManager structures, Ran public int getGenDepth() { return vanillaChunkGenerator.getGenDepth(); } @Override - public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState noiseConfig, - StructureManager structureAccessor, ChunkAccess chunk) { - - return vanillaChunkGenerator.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk); + public CompletableFuture fillFromNoise(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, + ChunkAccess chunk) { + return vanillaChunkGenerator.fillFromNoise(blender, noiseConfig, structureAccessor, chunk); } @Override diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index 21fddf0..aa9fb96 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -1,5 +1,6 @@ package com.jkantrell.mc.underilla.spigot.generation; +import java.lang.reflect.Field; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -10,6 +11,8 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.status.WorldGenContext; +import sun.misc.Unsafe; public class WorldInitListener implements Listener { private final BukkitWorldReader worldSurfaceReader; @@ -35,7 +38,46 @@ public void onWorldInit(WorldInitEvent event) { BiomeSource vanillaBiomeSource = vanilla.getBiomeSource(); customBiomeSource = new CustomBiomeSource(vanillaBiomeSource, worldSurfaceReader, worldCavesReader); - serverLevel.getChunkSource().chunkMap.generator = new NMSExtendedChunkGenerator(vanilla, customBiomeSource); + // Before 1.21 this was working. + // serverLevel.getChunkSource().chunkMap.generator = new NMSExtendedChunkGenerator(vanilla, customBiomeSource); + + // After 1.21 we need to use reflection to set the custom biome source. + // Next steps is based on : + // https://github.com/VolmitSoftware/Iris/blob/master/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java#L491 + try { + var chunkMap = serverLevel.getChunkSource().chunkMap; + var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); + worldGenContextField.setAccessible(true); + var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + Class clazz = worldGenContext.generator().getClass(); + Field biomeSource = getField(clazz, BiomeSource.class); + biomeSource.setAccessible(true); + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + Unsafe unsafe = (Unsafe) unsafeField.get(null); + unsafe.putObject(biomeSource.get(worldGenContext.generator()), unsafe.objectFieldOffset(biomeSource), customBiomeSource); + biomeSource.set(worldGenContext.generator(), customBiomeSource); + } catch (Exception e) { + Underilla.getInstance().getLogger().warning("Failed to set custom biome source"); + e.printStackTrace(); + } + } + + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getType().equals(fieldType)) + return f; + } + throw new NoSuchFieldException(fieldType.getName()); + } catch (NoSuchFieldException var4) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) { + throw var4; + } else { + return getField(superClass, fieldType); + } + } } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java index fbb932c..ea3b856 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java @@ -10,7 +10,6 @@ import net.minecraft.core.WritableRegistry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; @@ -31,12 +30,11 @@ public class BiomeHelper { public static void setCustomBiome(String newBiomeName, Chunk chunk) { WritableRegistry registryWritable = (WritableRegistry) dedicatedServer.registryAccess().registry(Registries.BIOME) .get(); - ResourceKey key = ResourceKey.create(Registries.BIOME, new ResourceLocation(newBiomeName.toLowerCase())); + ResourceKey key = ResourceKey.create(Registries.BIOME, NMSBiomeUtils.resourceLocation(newBiomeName)); Biome base = registryWritable.get(key); if (base == null) { if (newBiomeName.contains(":")) { - ResourceKey newKey = ResourceKey.create(Registries.BIOME, - new ResourceLocation(newBiomeName.split(":")[0].toLowerCase(), newBiomeName.split(":")[1].toLowerCase())); + ResourceKey newKey = ResourceKey.create(Registries.BIOME, NMSBiomeUtils.resourceLocation(newBiomeName)); base = registryWritable.get(newKey); if (base == null) { return; @@ -68,12 +66,11 @@ public static boolean setCustomBiome(String newBiomeName, Location location) { Biome base; WritableRegistry registrywritable = (WritableRegistry) dedicatedServer.registryAccess().registry(Registries.BIOME) .get(); - ResourceKey key = ResourceKey.create(Registries.BIOME, new ResourceLocation(newBiomeName.toLowerCase())); + ResourceKey key = ResourceKey.create(Registries.BIOME, NMSBiomeUtils.resourceLocation(newBiomeName)); base = registrywritable.get(key); if (base == null) { if (newBiomeName.contains(":")) { - ResourceKey newKey = ResourceKey.create(Registries.BIOME, - new ResourceLocation(newBiomeName.split(":")[0].toLowerCase(), newBiomeName.split(":")[1].toLowerCase())); + ResourceKey newKey = ResourceKey.create(Registries.BIOME, NMSBiomeUtils.resourceLocation(newBiomeName)); base = registrywritable.get(newKey); if (base == null) { return false; diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java index d3703e5..266faaa 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -1,8 +1,7 @@ package com.jkantrell.mc.underilla.spigot.impl; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -14,7 +13,7 @@ import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.Climate.Sampler; -public class CustomBiomeSource extends BiomeSource { +public class CustomBiomeSource extends BiomeSource implements java.util.function.Supplier { private final BiomeSource vanillaBiomeSource; private final BukkitWorldReader worldSurfaceReader; private final BukkitWorldReader worldCavesReader; @@ -31,18 +30,20 @@ public CustomBiomeSource(BiomeSource vanillaBiomeSource, BukkitWorldReader world public Map getBiomesPlaced() { return biomesPlaced; } + // @Override + // protected MapCodec codec() { + // try { + // Method method = BiomeSource.class.getDeclaredMethod("codec"); + // method.setAccessible(true); + // return (MapCodec) method.invoke(vanillaBiomeSource); + // } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + // Underilla.getInstance().getLogger().warning("Failed to get codec field from BiomeSource"); + // e.printStackTrace(); + // return null; + // } + // } @Override - protected MapCodec codec() { - try { - Method method = BiomeSource.class.getDeclaredMethod("codec"); - method.setAccessible(true); - return (MapCodec) method.invoke(vanillaBiomeSource); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - Underilla.getInstance().getLogger().warning("Failed to get codec field from BiomeSource"); - e.printStackTrace(); - return null; - } - } + protected MapCodec codec() { throw new UnsupportedOperationException("Not supported"); } @Override protected Stream> collectPossibleBiomes() { @@ -55,6 +56,7 @@ protected Stream> collectPossibleBiomes() { @Override public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) { // Keep biome from vanilla noise biome generation if it's in the list of keptUndergroundBiomes. + // TODO find a fix for cave biomes that are never kept here. (probably because Minecraft generate them in a different way) Holder vanillaBiome = vanillaBiomeSource.getNoiseBiome(x, y, z, noise); // Edit value to mach actual world coordinates. x = x << 2; @@ -116,4 +118,10 @@ private synchronized void warning(String message) { } } + /** + * I don't understand why BiomeSource need to extend Supplier, but it's needed to work with paper. + */ + @Override + public Set> get() { return collectPossibleBiomes().distinct().collect(java.util.stream.Collectors.toSet()); } + } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java index 8f3fb53..7e2db15 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java @@ -18,7 +18,7 @@ public static Registry getBiomeRegistry() { return ((CraftServer) Bukkit.getServer()).getServer().registryAccess().registryOrThrow(Registries.BIOME); } - public static Biome getBiome(String key) { return getBiomeRegistry().get(new ResourceLocation(key)); } + public static Biome getBiome(String key) { return getBiomeRegistry().get(resourceLocation(key)); } public static Biome getBiome(Location location) { return getBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getWorld()); } @@ -34,6 +34,11 @@ public static ResourceLocation getBiomeKey(int x, int y, int z, World bukkitWorl return getBiomeRegistry().getKey(getBiome(x, y, z, bukkitWorld)); } + public static ResourceLocation resourceLocation(String name) { + String[] t = name.split(":"); + return ResourceLocation.fromNamespaceAndPath(t[0], t[1]); + } + // public static org.bukkit.block.Biome getBukkitBiome(String key) { // try { // // org.bukkit.block.Biome biome = org.bukkit.block.Biome.valueOf(key.split|.toUpperCase()); From 3c7f40947f0aae3be5a75fcb366ef5719d4372fa Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Fri, 30 Aug 2024 18:51:03 +0000 Subject: [PATCH 19/21] Improve custom biome management. --- .../core/generation/AbsoluteMerger.java | 10 ++-- .../core/generation/GenerationConfig.java | 4 +- .../core/generation/RelativeMerger.java | 8 ++- .../generation/UnderillaChunkGenerator.java | 19 +++++- .../spigot/generation/WorldInitListener.java | 58 +++++++++++++++---- .../spigot/impl/CustomBiomeSource.java | 22 +++---- .../underilla/spigot/impl/NMSBiomeUtils.java | 8 --- .../mc/underilla/spigot/io/Config.java | 4 +- .../listener/StructureEventListener.java | 4 ++ .../src/main/resources/config.yml | 5 +- 10 files changed, 97 insertions(+), 45 deletions(-) diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java index c88f21e..79b11ab 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/AbsoluteMerger.java @@ -1,7 +1,6 @@ package com.jkantrell.mc.underilla.core.generation; import java.util.List; -import com.jkantrell.mc.underilla.core.api.Biome; import com.jkantrell.mc.underilla.core.api.Block; import com.jkantrell.mc.underilla.core.api.ChunkData; import com.jkantrell.mc.underilla.core.reader.ChunkReader; @@ -16,12 +15,13 @@ class AbsoluteMerger implements Merger { // FIELDS private final int height_; - private final List preserveBiomes_, ravinBiomes_; + private final List preserveBiomes_; + private final List ravinBiomes_; private final List keptReferenceWorldBlocks_; private final int mergeDepth_; // CONSTRUCTORS - AbsoluteMerger(int height, List preserveBiomes, List ravinBiomes, + AbsoluteMerger(int height, List preserveBiomes, List ravinBiomes, List keptReferenceWorldBlocks, int mergeDepth) { this.height_ = height; this.preserveBiomes_ = preserveBiomes; @@ -116,11 +116,11 @@ private boolean isCustomWorldOreOutOfVanillaCaves(Block customBlock, Block vanil } /** Return true if this biome need to be only custom world */ private boolean isPreservedBiome(ChunkReader reader, Vector v) { - return this.preserveBiomes_.contains(reader.biomeAt(v).orElse(null)); + return this.preserveBiomes_.contains(reader.biomeAt(v).orElseThrow().getName()); } private boolean isRavinBiome(ChunkReader reader, Vector v) { - return this.ravinBiomes_.contains(reader.biomeAt(v).orElse(null)); + return this.ravinBiomes_.contains(reader.biomeAt(v).orElseThrow().getName()); } /** Return true if all the collumn is air. */ diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java index a096bd0..c40f5a6 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java @@ -33,9 +33,9 @@ public class GenerationConfig { public List keptReferenceWorldBlocks = Collections.emptyList(); - public List preserveBiomes = Collections.emptyList(); + public List preserveBiomes = Collections.emptyList(); - public List ravinBiomes = Collections.emptyList(); + public List ravinBiomes = Collections.emptyList(); public int mergeLimit = 22; diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java index 7e70476..ba3fd64 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/RelativeMerger.java @@ -27,14 +27,15 @@ public class RelativeMerger implements Merger { // FIELDS private final WorldReader worldReader_; private final int upperLimit_, lowerLimit_, depth_, blendRange_; - private final List keptBiomes_, preserveBiomes_; + private final List keptBiomes_; + private final List preserveBiomes_; private final boolean keepReferenceWorldBlocks_; private final List keptReferenceWorldBlocks_; // CONSTRUCTORS RelativeMerger(WorldReader worldReader, int upperLimit, int lowerLimit, int depth, int transitionRange, - List keptBiomes, List preservedBiomes, List keptReferenceWorldBlocks) { + List keptBiomes, List preservedBiomes, List keptReferenceWorldBlocks) { this.worldReader_ = worldReader; this.upperLimit_ = upperLimit; this.lowerLimit_ = lowerLimit; @@ -137,7 +138,8 @@ public void mergeLand(ChunkReader reader, ChunkData chunkData, @Nullable ChunkRe } /** Return true if this biome need to be only custom world */ private boolean isPreservedBiome(ChunkReader reader, Vector v) { - return this.preserveBiomes_.contains(reader.biomeAt(relativeCoordinates(v)).orElse(null)); + // return this.preserveBiomes_.contains(reader.biomeAt(relativeCoordinates(v)).orElseThrow().getName()); + return false; } /** * Return true if this block is a block to preserve from the custom world and a solid block in vanilla world (This avoid to have ores diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java index d2e2fa4..3e47398 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java @@ -6,11 +6,13 @@ import java.util.Random; import org.bukkit.HeightMap; import org.bukkit.World; +import org.bukkit.block.Biome; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.LimitedRegion; import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; import com.jkantrell.mc.underilla.core.api.HeightMapType; import com.jkantrell.mc.underilla.core.generation.Generator; import com.jkantrell.mc.underilla.core.reader.ChunkReader; @@ -114,7 +116,22 @@ public boolean shouldGenerateStructures(WorldInfo worldInfo, Random random, int // To support custom biomes, we can't use bukkit biome provider. So biome merging is done in CustomBiomeSource. @Override - public BiomeProvider getDefaultBiomeProvider(@Nonnull WorldInfo worldInfo) { return null; } + public BiomeProvider getDefaultBiomeProvider(@Nonnull WorldInfo worldInfo) { + // TODO if surface map biome is a vanilla biome, use it there. This way most of the user will have structure well placed. + // TODO Mvndi patch, if biome name contains "beach" return beach biome, if biome names contains "ocean" but not "deep" return ocean. + // This way we will have shipwrecks well placed. + return new BiomeProvider() { + @Override + public @NotNull Biome getBiome(@NotNull WorldInfo worldInfo, int x, int y, int z) { + return Biome.DESERT; + } + + @Override + public @NotNull List getBiomes(@NotNull WorldInfo worldInfo) { + return List.of(Biome.DESERT); + } + }; + } // CLASSES diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index aa9fb96..1971132 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -8,11 +8,11 @@ import com.jkantrell.mc.underilla.spigot.Underilla; import com.jkantrell.mc.underilla.spigot.impl.BukkitWorldReader; import com.jkantrell.mc.underilla.spigot.impl.CustomBiomeSource; +import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.status.WorldGenContext; -import sun.misc.Unsafe; public class WorldInitListener implements Listener { private final BukkitWorldReader worldSurfaceReader; @@ -37,6 +37,7 @@ public void onWorldInit(WorldInitEvent event) { ChunkGenerator vanilla = serverLevel.getChunkSource().getGenerator(); BiomeSource vanillaBiomeSource = vanilla.getBiomeSource(); customBiomeSource = new CustomBiomeSource(vanillaBiomeSource, worldSurfaceReader, worldCavesReader); + // ChunkGenerator underillaChunkGenerator = new NMSExtendedChunkGenerator(vanilla, customBiomeSource); // Before 1.21 this was working. // serverLevel.getChunkSource().chunkMap.generator = new NMSExtendedChunkGenerator(vanilla, customBiomeSource); @@ -45,22 +46,57 @@ public void onWorldInit(WorldInitEvent event) { // Next steps is based on : // https://github.com/VolmitSoftware/Iris/blob/master/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java#L491 try { - var chunkMap = serverLevel.getChunkSource().chunkMap; - var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); + // Edit biome source that will be generated at generation chunk step. + ChunkMap chunkMap = serverLevel.getChunkSource().chunkMap; + Field worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); - var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + WorldGenContext worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); Class clazz = worldGenContext.generator().getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe) unsafeField.get(null); - unsafe.putObject(biomeSource.get(worldGenContext.generator()), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(worldGenContext.generator(), customBiomeSource); + Field biomeSourceField = getField(clazz, BiomeSource.class); + biomeSourceField.setAccessible(true); + sun.misc.Unsafe unsafe = getUnsafe(); + unsafe.putObject(biomeSourceField.get(worldGenContext.generator()), unsafe.objectFieldOffset(biomeSourceField), + customBiomeSource); + biomeSourceField.set(worldGenContext.generator(), customBiomeSource); + + // // Edit biome source that is used by StructureCheck (& StructureManager.structureCheck). + // Field structureCheckField = getField(serverLevel.getClass(), StructureCheck.class); + // // set public + // structureCheckField.setAccessible(true); + // StructureCheck structureCheck = (StructureCheck) structureCheckField.get(serverLevel); + // Field biomeSourceStructureCheck = getField(structureCheck.getClass(), BiomeSource.class); + // // public + // biomeSourceStructureCheck.setAccessible(true); + + // // Edit value even if it's final. + // unsafe = getUnsafe(); + // unsafe.putObject(biomeSourceStructureCheck.get(structureCheck), unsafe.objectFieldOffset(biomeSourceStructureCheck), + // customBiomeSource); + // biomeSourceStructureCheck.set(structureCheck, customBiomeSource); + + // Field structureManagerField = getField(serverLevel.getClass(), net.minecraft.world.level.StructureManager.class); + // structureManagerField.setAccessible(true); + // Field structureCheckInStructureManager = getField(structureManagerField.getType(), StructureCheck.class); + // structureCheckInStructureManager.setAccessible(true); + // unsafe = getUnsafe(); + // unsafe.putObject(structureCheckInStructureManager.get(structureManagerField.get(serverLevel)), + // unsafe.objectFieldOffset(structureCheckInStructureManager), structureCheck); + // structureCheckInStructureManager.set(structureManagerField.get(serverLevel), structureCheck); + + } catch (Exception e) { Underilla.getInstance().getLogger().warning("Failed to set custom biome source"); e.printStackTrace(); } + + + } + + // Helper method to get the Unsafe instance + private static sun.misc.Unsafe getUnsafe() throws Exception { + Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + return (sun.misc.Unsafe) unsafeField.get(null); } private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java index 266faaa..c90cd23 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -13,7 +13,7 @@ import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.Climate.Sampler; -public class CustomBiomeSource extends BiomeSource implements java.util.function.Supplier { +public class CustomBiomeSource extends BiomeSource implements java.util.function.Supplier>> { private final BiomeSource vanillaBiomeSource; private final BukkitWorldReader worldSurfaceReader; private final BukkitWorldReader worldCavesReader; @@ -32,18 +32,18 @@ public CustomBiomeSource(BiomeSource vanillaBiomeSource, BukkitWorldReader world // @Override // protected MapCodec codec() { - // try { - // Method method = BiomeSource.class.getDeclaredMethod("codec"); - // method.setAccessible(true); - // return (MapCodec) method.invoke(vanillaBiomeSource); - // } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - // Underilla.getInstance().getLogger().warning("Failed to get codec field from BiomeSource"); - // e.printStackTrace(); - // return null; - // } + // try { + // Method method = BiomeSource.class.getDeclaredMethod("codec"); + // method.setAccessible(true); + // return (MapCodec) method.invoke(vanillaBiomeSource); + // } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + // Underilla.getInstance().getLogger().warning("Failed to get codec field from BiomeSource"); + // e.printStackTrace(); + // return null; + // } // } @Override - protected MapCodec codec() { throw new UnsupportedOperationException("Not supported"); } + protected MapCodec codec() { return null; } @Override protected Stream> collectPossibleBiomes() { diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java index 7e2db15..ccfac72 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java @@ -39,14 +39,6 @@ public static ResourceLocation resourceLocation(String name) { return ResourceLocation.fromNamespaceAndPath(t[0], t[1]); } - // public static org.bukkit.block.Biome getBukkitBiome(String key) { - // try { - // // org.bukkit.block.Biome biome = org.bukkit.block.Biome.valueOf(key.split|.toUpperCase()); - // } catch (Exception e) { - // Underilla.getInstance().getLogger().warning("Failed to get Bukkit biome for key: " + key); - // return org.bukkit.block.Biome.PLAINS; - // } - // } // Convert between Minecraft and Bukkit biomes // minecraft to bukkit don't work with custom biomes. public static org.bukkit.block.Biome minecraftToBukkit(Biome minecraft) { return CraftBiome.minecraftToBukkit(minecraft); } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java index b62c462..dd42140 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java @@ -101,8 +101,8 @@ public GenerationConfig toGenerationConfig() { r.mergeDepth = this.mergeDepth; r.keptUndergroundBiomes = this.keptUndergroundBiomes.stream().map(BukkitBiome::new).toList(); r.keptReferenceWorldBlocks = this.keptReferenceWorldBlocks; - r.preserveBiomes = this.preserveBiomes.stream().map(BukkitBiome::new).toList(); - r.ravinBiomes = this.ravinBiomes.stream().map(BukkitBiome::new).toList(); + r.preserveBiomes = this.preserveBiomes; + r.ravinBiomes = this.ravinBiomes; r.mergeLimit = this.mergeLimit; r.mergeBlendRange = this.mergeBlendRange; r.generateStructures = this.generateStructures; diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/listener/StructureEventListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/listener/StructureEventListener.java index 6f4fa2f..1e828c5 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/listener/StructureEventListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/listener/StructureEventListener.java @@ -14,6 +14,10 @@ public class StructureEventListener implements Listener { @EventHandler public void onStructureSpawn(AsyncStructureSpawnEvent e) { + // Location location = e.getWorld().getBlockAt(e.getChunkX() * 16, 0, e.getChunkZ() * 16).getLocation(); + // String biomeKey = NMSBiomeUtils.getBiomeKey(location).toString(); + // Underilla.getInstance().getLogger().info("Structure spawned ? " + !this.blackList.contains(e.getStructure()) + " : " + // + e.getStructure().getStructureType().getKey().toString() + " on biome " + biomeKey + " at location " + location); if (this.blackList.contains(e.getStructure())) { e.setCancelled(true); } diff --git a/Underilla-Spigot/src/main/resources/config.yml b/Underilla-Spigot/src/main/resources/config.yml index a688a54..11b4e2a 100644 --- a/Underilla-Spigot/src/main/resources/config.yml +++ b/Underilla-Spigot/src/main/resources/config.yml @@ -23,13 +23,14 @@ transfered_caves_world_biomes: - "DRIPSTONE_CAVES" - "DEEP_DARK" +customBiomeEnabled: false # How to merge vanilla and reference worlds. -# - RELATIVE: At a given depth based on terrain height and air pockets at every X and Z coordinates. +# - RELATIVE: At a given depth based on terrain height and air pockets at every X and Z coordinates. @Deprecated # - SURFACE: At a fixed Y height based on custom world terrain height. # - ABSOLUTE: At a fixed Y height. # - NONE: Just... don't merge. Reference world will be re-generated without vanilla underground. -strategy: "RELATIVE" +strategy: "SURFACE" # RELATIVE strategy exclusive settings relative: From bb30192fdd1c7bb41258c43805a6bed0db338c39 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Fri, 30 Aug 2024 19:55:04 +0000 Subject: [PATCH 20/21] If custom biome are disabled then structures spawn in the right biome. --- Underilla-Core/build.gradle.kts | 2 +- .../core/generation/GenerationConfig.java | 7 +- .../underilla/core/generation/Generator.java | 2 +- Underilla-Spigot/build.gradle.kts | 2 +- .../mc/underilla/spigot/Underilla.java | 13 ++-- .../generation/BiomeGenerationHandler.java | 30 ++++----- .../generation/UnderillaChunkGenerator.java | 66 +++++++++++++++---- .../spigot/generation/WorldInitListener.java | 7 +- .../mc/underilla/spigot/impl/BukkitBiome.java | 5 +- .../spigot/impl/CustomBiomeSource.java | 5 +- .../underilla/spigot/impl/NMSBiomeUtils.java | 9 +++ .../mc/underilla/spigot/io/Config.java | 7 +- .../src/main/resources/config.yml | 10 +-- 13 files changed, 104 insertions(+), 61 deletions(-) diff --git a/Underilla-Core/build.gradle.kts b/Underilla-Core/build.gradle.kts index 1468a2b..b2365c4 100644 --- a/Underilla-Core/build.gradle.kts +++ b/Underilla-Core/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.Jkantrell.mc" -version = "1.5.3" +version = "1.6.0" java.sourceCompatibility = JavaVersion.VERSION_21 repositories { diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java index c40f5a6..38cb96a 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java @@ -21,6 +21,8 @@ public class GenerationConfig { public List transferCavesWorldBiomes = Collections.emptyList(); + public boolean customBiomeEnabled = false; + public MergeStrategy mergeStrategy = MergeStrategy.RELATIVE; public int mergeUpperLimit = 320; @@ -29,8 +31,6 @@ public class GenerationConfig { public int mergeDepth = 12; - public List keptUndergroundBiomes = Collections.emptyList(); - public List keptReferenceWorldBlocks = Collections.emptyList(); public List preserveBiomes = Collections.emptyList(); @@ -45,6 +45,7 @@ public class GenerationConfig { public boolean needToMixBiomes() { // true if we transfer biomes and we have kept underground biomes and we are using relative merge strategy - return this.transferBiomes && !this.keptUndergroundBiomes.isEmpty() && MergeStrategy.RELATIVE.equals(this.mergeStrategy); + // return this.transferBiomes && !this.keptUndergroundBiomes.isEmpty() && MergeStrategy.RELATIVE.equals(this.mergeStrategy); + return false; } } diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java index e54ad95..add940c 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/Generator.java @@ -30,7 +30,7 @@ public Generator(WorldReader worldReader, GenerationConfig config) { this.config_ = config; this.merger_ = switch (config_.mergeStrategy) { case RELATIVE -> new RelativeMerger(this.worldReader_, config_.mergeUpperLimit, config_.mergeLowerLimit, config_.mergeDepth, - config_.mergeBlendRange, config_.keptUndergroundBiomes, config_.preserveBiomes, config_.keptReferenceWorldBlocks); + config_.mergeBlendRange, List.of(), config_.preserveBiomes, config_.keptReferenceWorldBlocks); case SURFACE, ABSOLUTE, NONE -> new AbsoluteMerger(config_.mergeStrategy.equals(MergeStrategy.NONE) ? -64 : config_.mergeLimit, config_.preserveBiomes, config.ravinBiomes, config_.keptReferenceWorldBlocks, config_.mergeStrategy.equals(MergeStrategy.SURFACE) ? config_.mergeDepth : 0); diff --git a/Underilla-Spigot/build.gradle.kts b/Underilla-Spigot/build.gradle.kts index 2be5a44..0f5fb35 100644 --- a/Underilla-Spigot/build.gradle.kts +++ b/Underilla-Spigot/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "com.jkantrell.mc.underilla.spigot" -version = "1.5.3" +version = "1.6.0" description="Generate vanilla cave in custom world." repositories { diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java index a7c69b9..ef21c59 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/Underilla.java @@ -66,7 +66,7 @@ public void onEnable() { if (CONFIG.generateStructures) { this.getServer().getPluginManager().registerEvents(new StructureEventListener(CONFIG.structureBlackList), this); } - if (CONFIG.transferBiomes) { + if (CONFIG.transferBiomes && CONFIG.customBiomeEnabled) { worldInitListener = new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(worldSurfaceReader, worldCavesReader); this.getServer().getPluginManager().registerEvents(worldInitListener, this); } @@ -80,12 +80,11 @@ public void onDisable() { this.getServer().getLogger() .info(entry.getKey() + " took " + entry.getValue() + "ms (" + (entry.getValue() * 100 / totalTime) + "%)"); } - if (worldInitListener != null) { - this.getServer().getLogger() - .info("Map of chunks: " + worldInitListener.getCustomBiomeSource().getBiomesPlaced().entrySet().stream() - .sorted((a, b) -> Long.compare(b.getValue(), a.getValue())) - .map(entry -> entry.getKey() + ": " + entry.getValue()).reduce((a, b) -> a + ", " + b).orElse("")); - } + Map biomesPlaced = worldInitListener != null ? worldInitListener.getCustomBiomeSource().getBiomesPlaced() + : UnderillaChunkGenerator.getBiomesPlaced(); + this.getServer().getLogger() + .info("Map of chunks: " + biomesPlaced.entrySet().stream().sorted((a, b) -> Long.compare(b.getValue(), a.getValue())) + .map(entry -> entry.getKey() + ": " + entry.getValue()).reduce((a, b) -> a + ", " + b).orElse("")); } catch (Exception e) { this.getServer().getLogger().info("Fail to print times or biomes placed."); e.printStackTrace(); diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java index be63081..60262a4 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java @@ -1,19 +1,15 @@ -package com.jkantrell.mc.underilla.spigot.generation; +// package com.jkantrell.mc.underilla.spigot.generation; -import java.util.Arrays; -import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldInitEvent; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.chunk.ChunkGenerator; +// import org.bukkit.craftbukkit.CraftWorld; +// import org.bukkit.event.EventHandler; +// import org.bukkit.event.EventPriority; +// import org.bukkit.event.Listener; +// import org.bukkit.event.world.WorldInitEvent; +// import net.minecraft.server.level.ServerLevel; -public class BiomeGenerationHandler implements Listener { - @EventHandler(priority = EventPriority.LOW) - public void onWorldInit(WorldInitEvent event) { - ServerLevel serverLevel = ((CraftWorld) event.getWorld()).getHandle(); - System.out.println("World init event"); - System.out.println(Arrays.asList(ChunkGenerator.class.getDeclaredFields())); - } -} +// public class BiomeGenerationHandler implements Listener { +// @EventHandler(priority = EventPriority.LOW) +// public void onWorldInit(WorldInitEvent event) { +// ServerLevel serverLevel = ((CraftWorld) event.getWorld()).getHandle(); +// } +// } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java index 3e47398..6c41294 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java @@ -1,9 +1,11 @@ package com.jkantrell.mc.underilla.spigot.generation; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; +import org.bukkit.Bukkit; import org.bukkit.HeightMap; import org.bukkit.World; import org.bukkit.block.Biome; @@ -12,12 +14,12 @@ import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.LimitedRegion; import org.bukkit.generator.WorldInfo; -import org.jetbrains.annotations.NotNull; import com.jkantrell.mc.underilla.core.api.HeightMapType; import com.jkantrell.mc.underilla.core.generation.Generator; import com.jkantrell.mc.underilla.core.reader.ChunkReader; import com.jkantrell.mc.underilla.core.reader.WorldReader; import com.jkantrell.mc.underilla.spigot.Underilla; +import com.jkantrell.mc.underilla.spigot.impl.BukkitBiome; import com.jkantrell.mc.underilla.spigot.impl.BukkitChunkData; import com.jkantrell.mc.underilla.spigot.impl.BukkitRegionChunkData; import com.jkantrell.mc.underilla.spigot.impl.BukkitWorldInfo; @@ -41,6 +43,7 @@ public class UnderillaChunkGenerator extends ChunkGenerator { private final Generator delegate_; private final @Nonnull com.jkantrell.mc.underilla.core.reader.WorldReader worldReader_; private final @Nullable com.jkantrell.mc.underilla.core.reader.WorldReader worldCavesReader_; + private static Map biomesPlaced = new HashMap<>(); // CONSTRUCTORS @@ -117,22 +120,24 @@ public boolean shouldGenerateStructures(WorldInfo worldInfo, Random random, int // To support custom biomes, we can't use bukkit biome provider. So biome merging is done in CustomBiomeSource. @Override public BiomeProvider getDefaultBiomeProvider(@Nonnull WorldInfo worldInfo) { - // TODO if surface map biome is a vanilla biome, use it there. This way most of the user will have structure well placed. // TODO Mvndi patch, if biome name contains "beach" return beach biome, if biome names contains "ocean" but not "deep" return ocean. // This way we will have shipwrecks well placed. - return new BiomeProvider() { - @Override - public @NotNull Biome getBiome(@NotNull WorldInfo worldInfo, int x, int y, int z) { - return Biome.DESERT; - } - - @Override - public @NotNull List getBiomes(@NotNull WorldInfo worldInfo) { - return List.of(Biome.DESERT); - } - }; + if (Underilla.CONFIG.customBiomeEnabled) { + return null; // biomes will be set later in CustomBiomeSource. This won't place structures in the right biome. But features will + // be in the right biome. + } else if (!Underilla.CONFIG.transferBiomes) { + Bukkit.getLogger().info( + "Biome aren't transfered from the reference world. This will generate the world with the default biome provider."); + return null; + } else { + Bukkit.getLogger() + .info("Underilla Use the custom biome provider from file data. Structures will be generate in the right biome."); + return new BiomeProviderFromFile(); + } } + public static Map getBiomesPlaced() { return biomesPlaced; } + // CLASSES private static class Populator extends BlockPopulator { @@ -164,4 +169,39 @@ public void populate(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, this.generator_.reInsertLiquids(reader, chunkData); } } + + private class BiomeProviderFromFile extends BiomeProvider { + + @Override + public @Nonnull Biome getBiome(@Nonnull WorldInfo worldInfo, int x, int y, int z) { + // If the cave world is enabled and the biome is in the list of transfered biomes, return the biome from the cave world. + if (worldCavesReader_ != null) { + BukkitBiome cavesWorldBiome = (BukkitBiome) worldCavesReader_.biomeAt(x, y, z).orElse(null); + if (cavesWorldBiome != null && CONFIG.transferCavesWorldBiomes.contains(cavesWorldBiome.getName())) { + Biome biome = cavesWorldBiome.getBiome(); + String key = "caves:" + cavesWorldBiome.getName(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); + return biome; + } + } + // If there is a surface world biome, return it, else return plains. + BukkitBiome surfaceWorldBiome = (BukkitBiome) worldReader_.biomeAt(x, y, z).orElse(null); + if (surfaceWorldBiome != null) { + Biome biome = surfaceWorldBiome.getBiome(); + String key = "surface:" + surfaceWorldBiome.getName(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); + return biome; + } else { + String key = "notfound:minecraft:plains"; + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); + return Biome.PLAINS; + } + } + + @Override + public @Nonnull List getBiomes(@Nonnull WorldInfo worldInfo) { + return List.of(Biome.values()).stream().filter(b -> !b.equals(Biome.CUSTOM)).toList(); + } + + } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java index 1971132..4640f3b 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -28,7 +28,12 @@ public WorldInitListener(BukkitWorldReader worldSurfaceReader, BukkitWorldReader @EventHandler public void onWorldInit(WorldInitEvent event) { - Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()); + if (!Underilla.CONFIG.customBiomeEnabled){ + Underilla.getInstance().getLogger().info("Custom biome is disabled, no need to take over the world: " + event.getWorld().getName()); + return; + } + + Underilla.getInstance().getLogger().info("Preparing to take over the world: " + event.getWorld().getName()+" to use custom biome source"); CraftWorld craftWorld = (CraftWorld) event.getWorld(); ServerLevel serverLevel = craftWorld.getHandle(); diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java index dcd826b..ccd2383 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BukkitBiome.java @@ -9,7 +9,6 @@ public class BukkitBiome implements Biome { // // CONSTRUCTORS - // public BukkitBiome(org.bukkit.block.Biome biome) { this.name = biome.name(); } public BukkitBiome(String name) { name = name.toLowerCase(); if (!name.contains(":")) { @@ -19,8 +18,8 @@ public BukkitBiome(String name) { } - // // // GETTERS - // public org.bukkit.block.Biome getBiome() { return NMSBiomeUtils.getBukkitBiome(this.name); } + // GETTERS + public org.bukkit.block.Biome getBiome() { return NMSBiomeUtils.getBukkitBiome(this.name); } // IMPLEMENTATIONS diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java index c90cd23..a2ef75b 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -62,8 +62,7 @@ public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) x = x << 2; y = y << 2; z = z << 2; - if (vanillaBiome != null && (!Underilla.CONFIG.transferBiomes - || Underilla.CONFIG.keptUndergroundBiomes.contains(vanillaBiome.getRegisteredName()))) { + if (vanillaBiome != null && (!Underilla.CONFIG.transferBiomes)) { info("Use vanillaBiome because we don't transfer biome or it's a keptUndergroundBiomes: " + vanillaBiome.getRegisteredName() + " at " + x + " " + y + " " + z); String key = "noise:" + vanillaBiome.getRegisteredName(); @@ -71,7 +70,7 @@ public Holder getNoiseBiome(int x, int y, int z, @Nonnull Sampler noise) return vanillaBiome; } - // Get biome from cave world if it's in the list of keptUndergroundBiomes. + // Get biome from cave world if it's in the list of transferWorldFromCavesWorld. if (Underilla.CONFIG.transferWorldFromCavesWorld && worldCavesReader != null) { BukkitBiome cavesBiome = (BukkitBiome) worldCavesReader.biomeAt(x, y, z).orElse(null); if (cavesBiome != null && Underilla.CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getName())) { diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java index ccfac72..450d387 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java @@ -6,6 +6,7 @@ import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBiome; +import com.jkantrell.mc.underilla.spigot.Underilla; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; @@ -50,4 +51,12 @@ public static Holder bukkitToMinecraftHolder(org.bukkit.block.Biome bukki return CraftBiome.bukkitToMinecraftHolder(bukkit); } + public static org.bukkit.block.Biome getBukkitBiome(String name) { + try { + return org.bukkit.block.Biome.valueOf(name.split(":")[1].toUpperCase()); + }catch (IllegalArgumentException e) { + Underilla.getInstance().getLogger().warning("Failed to get Bukkit biome from " + name); + return org.bukkit.block.Biome.PLAINS; + } + } } diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java index dd42140..2943034 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java @@ -46,6 +46,9 @@ public Config(String filePath) { @ConfigField(path = "transfered_caves_world_biomes") public List transferCavesWorldBiomes = List.of("minecraft:deep_dark", "minecraft:dripstone_caves", "minecraft:lush_caves"); + @ConfigField(path = "custom_biome_enabled") + public boolean customBiomeEnabled = false; + @ConfigField(path = "strategy") public MergeStrategy mergeStrategy = MergeStrategy.RELATIVE; @@ -58,9 +61,6 @@ public Config(String filePath) { @ConfigField(path = "relative_and_surface.depth") public int mergeDepth = 12; - @ConfigField(path = "relative.kept_underground_biomes") - public List keptUndergroundBiomes = List.of(); - @ConfigField(path = "kept_reference_world_blocks") public List keptReferenceWorldBlocks = List.of(); @@ -99,7 +99,6 @@ public GenerationConfig toGenerationConfig() { r.mergeUpperLimit = this.mergeUpperLimit; r.mergeLowerLimit = this.mergeLowerLimit; r.mergeDepth = this.mergeDepth; - r.keptUndergroundBiomes = this.keptUndergroundBiomes.stream().map(BukkitBiome::new).toList(); r.keptReferenceWorldBlocks = this.keptReferenceWorldBlocks; r.preserveBiomes = this.preserveBiomes; r.ravinBiomes = this.ravinBiomes; diff --git a/Underilla-Spigot/src/main/resources/config.yml b/Underilla-Spigot/src/main/resources/config.yml index 11b4e2a..ce96934 100644 --- a/Underilla-Spigot/src/main/resources/config.yml +++ b/Underilla-Spigot/src/main/resources/config.yml @@ -23,7 +23,8 @@ transfered_caves_world_biomes: - "DRIPSTONE_CAVES" - "DEEP_DARK" -customBiomeEnabled: false +custom_biome_enabled: false + # How to merge vanilla and reference worlds. # - RELATIVE: At a given depth based on terrain height and air pockets at every X and Z coordinates. @Deprecated @@ -40,12 +41,7 @@ relative: # Below this height the RELATIVE strategy will be ignored, and only vanilla world terrain will be placed. # No effect if <= -64 lower_limit: -64 - # Which biomes keep in vanilla underground while overwriting reference world biomes. - # Any biome not it this list will be replaced during generation. - # Biomes must be defined as spigot Biome enum constants. https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/Biome.html - # This won't work with biomes that Minecraft add at population time as "LUSH_CAVES", "DRIPSTONE_CAVES" and "DEEP_DARK. Use transfer_world_from_caves_world instead. - # This feature might be removed in future versions as transfered_caves_world_biomes do a better job. - kept_underground_biomes: [] + # Mix strategy exclusive settings surface_and_absolute: From 0c8ad753164b30c13887cdda14415e3f91443774 Mon Sep 17 00:00:00 2001 From: HydrolienF Date: Fri, 30 Aug 2024 20:22:54 +0000 Subject: [PATCH 21/21] Update README. --- README.md | 15 ++++++++++----- .../core/generation/GenerationConfig.java | 2 +- .../jkantrell/mc/underilla/spigot/io/Config.java | 2 +- Underilla-Spigot/src/main/resources/config.yml | 6 ++++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a488d74..c3c0378 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ It's original purpose is adding vanilla caves to custom [WorldPainter](https://w - **None:** No actual vanilla underground noise is generated. `generate_noodle_caves` setting can still be on, to generate noodle caves. - **Absolute:** A Y coordinate value divides the original world surface and vanilla underground. - **Surface:** Mix the original world surface and vanilla underground at a variable y that depends of original & vanilla world surface. It have the best racio generated world quality & performance. - - **Relative:** This is the cool one. The reference world's surface will be dynamically carved into vanilla underground; which means there's no actual height-based division. + - **Relative:** **This strategie still need improvement, for now you should use Surface**. The reference world's surface will be dynamically carved into vanilla underground; which means there's no actual height-based division. - Custom caves also supported. If using Relative merge strategy, every non-solid block and surroundings will be preserved, thus, if the reference world has itself an underground system, it'll be transferred over to the merged world. - Heightmap fixed. Underilla re-calculates heightmaps when merging chunks, getting rid of floating and buried structures. Vanilla villagers and other structures are placed at the right height. - Biome overwrite. Biomes from the reference world will be transferred and overwrite biomes from de vanilla seed being used. Cave biomes underground will be preserved. @@ -30,7 +30,7 @@ It's original purpose is adding vanilla caves to custom [WorldPainter](https://w - Java 21. - A pre-generated world to use as a reference (Such as a WorldPainter world). -- A [Paper](https://papermc.io/software/paper) (or forks) Minecraft Server of version [1.20.5 - 1.20.6]. It might work with upper version, but only 1.20.6 have been tested. Use old release for [1.19 - 1.20.4] compatibility. +- A [Paper](https://papermc.io/software/paper) (or forks) Minecraft Server of version [1.21 - 1.21.1]. It might work with upper version, but only 1.21.1 have been tested. Use old release for [1.19 - 1.20.6] compatibility. ### Single player or non-Bukkit Underilla is currently only implemented as a Paper plugin, so it runs only on Paper (or fork) servers. If you have a Vanilla, Forge or non Bukkit-based server; or looking for a single player experience; you may [use a local Paper server](https://papermc.io/software/paper) to pre-generate a fully-merged world and then copy the resulting world folder to your actual `saves` folder. @@ -86,12 +86,17 @@ If you're going to plug your custom WorldPainter world into Underilla, consider - The Populate layer has no effect. Weather all or none of the terrain will be populated based on the above point. - If you have custom cave/tunnels layers and want to preserve them during the merge, you'd want to use the Relative merge strategy +## Custom biome +Cave generation on custom biomes is now working. Features (ores, flowers etc) will be placed according to the custom surface world biome but structures won't. +If you have a custom world with custom biomes, you should enable custom biome it in the config. + +## Feature fiter +If you want to remove some of the game features, for example the `monster_room` you can create a datapack where you have customine witch feature can spawn in each biome. Underilla will generate feature according to your cusomized biome. +It can also be used to add feature to some biome. For example a quartz_ore feature if your nether is disabled you you still want your builder to have quartz. ## Build Create a working jar with `./gradlew buildDependents` ## TODO - Build-in pre-generation system. -- Allow to generate the 2nd world on the fly. -- Feature generation filter. -- Transfer custom biomes. \ No newline at end of file +- Allow to generate the 2nd world on the fly. \ No newline at end of file diff --git a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java index 38cb96a..3659bc9 100644 --- a/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java +++ b/Underilla-Core/src/main/java/com/jkantrell/mc/underilla/core/generation/GenerationConfig.java @@ -23,7 +23,7 @@ public class GenerationConfig { public boolean customBiomeEnabled = false; - public MergeStrategy mergeStrategy = MergeStrategy.RELATIVE; + public MergeStrategy mergeStrategy = MergeStrategy.SURFACE; public int mergeUpperLimit = 320; diff --git a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java index 2943034..fa95a39 100644 --- a/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/io/Config.java @@ -50,7 +50,7 @@ public Config(String filePath) { public boolean customBiomeEnabled = false; @ConfigField(path = "strategy") - public MergeStrategy mergeStrategy = MergeStrategy.RELATIVE; + public MergeStrategy mergeStrategy = MergeStrategy.SURFACE; @ConfigField(path = "relative.upper_limit") public int mergeUpperLimit = 320; diff --git a/Underilla-Spigot/src/main/resources/config.yml b/Underilla-Spigot/src/main/resources/config.yml index ce96934..ab72a4b 100644 --- a/Underilla-Spigot/src/main/resources/config.yml +++ b/Underilla-Spigot/src/main/resources/config.yml @@ -27,8 +27,9 @@ custom_biome_enabled: false # How to merge vanilla and reference worlds. -# - RELATIVE: At a given depth based on terrain height and air pockets at every X and Z coordinates. @Deprecated -# - SURFACE: At a fixed Y height based on custom world terrain height. +# - SURFACE: Generation will be based on the custom word surface. It have a top limits with surface_and_absolute.limit and always generate world at least x blocks below the custom world surface (see relative_and_surface.depth). This is the best choice for most cases. +# - RELATIVE: At a given depth based on terrain height and air pockets at every X and Z coordinates. This strategy sometimes results in stone clusters at random places. Use only if you absolutely need to keep your custom world underground caves. + # - ABSOLUTE: At a fixed Y height. # - NONE: Just... don't merge. Reference world will be re-generated without vanilla underground. strategy: "SURFACE" @@ -46,6 +47,7 @@ relative: # Mix strategy exclusive settings surface_and_absolute: # The Y height dividing reference terrain above and vanilla worlds below. + # Increasing this value way above 60 won't have much effect as the caves are only generated in vanilla world. limit: 60 # How deep reference world terrain should dig into vanilla word.