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/README.md b/README.md index 428fb37..c3c0378 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. @@ -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. @@ -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.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 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,8 @@ 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. +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. @@ -76,7 +77,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: @@ -86,10 +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. +- Build-in pre-generation system. +- Allow to generate the 2nd world on the fly. \ No newline at end of file diff --git a/Underilla-Core/build.gradle b/Underilla-Core/build.gradle deleted file mode 100644 index 1f03f30..0000000 --- a/Underilla-Core/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - id "java-library" -} - -group = 'com.Jkantrell.mc' -version = '1.4.0' -java.sourceCompatibility = JavaVersion.VERSION_17 - -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.1' -} - -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..b2365c4 --- /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.6.0" +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.github.HydrolienF: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 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-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 c4d3b25..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; @@ -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(); @@ -101,17 +96,12 @@ 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_; } - @Override - public void mergeBiomes(ChunkReader reader, ChunkData chunkData) { - // No need to set biome for chunk. It's done by the generator. - } - // private -------------------------------------------------------------------------------------------------------- /** @@ -126,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..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 @@ -21,7 +21,9 @@ public class GenerationConfig { public List transferCavesWorldBiomes = Collections.emptyList(); - public MergeStrategy mergeStrategy = MergeStrategy.RELATIVE; + public boolean customBiomeEnabled = false; + + public MergeStrategy mergeStrategy = MergeStrategy.SURFACE; public int mergeUpperLimit = 320; @@ -29,13 +31,11 @@ public class GenerationConfig { public int mergeDepth = 12; - public List keptUndergroundBiomes = Collections.emptyList(); - 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; @@ -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 e2aa4cc..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); @@ -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..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; @@ -48,11 +49,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())); @@ -142,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 @@ -155,40 +152,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-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-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 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..0f5fb35 --- /dev/null +++ b/Underilla-Spigot/build.gradle.kts @@ -0,0 +1,85 @@ +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.underilla.spigot" +version = "1.6.0" +description="Generate vanilla cave in custom world." + +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.21.1-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 + +java { + // 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)) +} + + +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) + } + + processResources { + val props = mapOf( + "name" to project.name, + "version" to project.version, + "description" to project.description, + "apiVersion" to "1.21", + "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.21.1") + } +} + +publishing { + publications.create("maven") { + from(components["java"]) + } +} + +// Break Yamlizer. +// paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION \ No newline at end of file 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/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..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 @@ -14,25 +14,24 @@ 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; + 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 public void onEnable() { - plugin = this; // Setting default config this.saveResource("config.yml", false); @@ -46,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"); @@ -56,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(); @@ -67,6 +66,10 @@ public void onEnable() { if (CONFIG.generateStructures) { this.getServer().getPluginManager().registerEvents(new StructureEventListener(CONFIG.structureBlackList), this); } + if (CONFIG.transferBiomes && CONFIG.customBiomeEnabled) { + worldInitListener = new com.jkantrell.mc.underilla.spigot.generation.WorldInitListener(worldSurfaceReader, worldCavesReader); + this.getServer().getPluginManager().registerEvents(worldInitListener, this); + } } @Override @@ -77,10 +80,16 @@ public void onDisable() { this.getServer().getLogger() .info(entry.getKey() + " took " + entry.getValue() + "ms (" + (entry.getValue() * 100 / totalTime) + "%)"); } + 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"); + this.getServer().getLogger().info("Fail to print times or biomes placed."); + e.printStackTrace(); } } - 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/BiomeGenerationHandler.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java new file mode 100644 index 0000000..60262a4 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/BiomeGenerationHandler.java @@ -0,0 +1,15 @@ +// package com.jkantrell.mc.underilla.spigot.generation; + +// 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(); +// } +// } 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..9a1b4e6 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSChunkGeneratorDelegate.java @@ -0,0 +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 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/NMSExtendedChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java new file mode 100644 index 0000000..cbb2c90 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/NMSExtendedChunkGenerator.java @@ -0,0 +1,87 @@ +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 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(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, + ChunkAccess chunk) { + return vanillaChunkGenerator.fillFromNoise(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/UnderillaChunkGenerator.java b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/UnderillaChunkGenerator.java index 88204a5..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,5 +1,6 @@ package com.jkantrell.mc.underilla.spigot.generation; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -15,7 +16,6 @@ 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; @@ -43,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 @@ -116,20 +117,27 @@ 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())) { + // 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. + 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(); - } else { - Bukkit.getLogger().info("Underilla Use the default biome provider. Structures will be generate in bad biomes."); - return null; } } + public static Map getBiomesPlaced() { return biomesPlaced; } + // CLASSES private static class Populator extends BlockPopulator { @@ -146,7 +154,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,14 +174,28 @@ private class BiomeProviderFromFile extends BiomeProvider { @Override public @Nonnull Biome getBiome(@Nonnull WorldInfo worldInfo, int x, int y, int z) { - BukkitBiome biome = (BukkitBiome) worldReader_.biomeAt(x, y, z).orElse(null); + // 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 cavesBiome = (BukkitBiome) worldCavesReader_.biomeAt(x, y, z).orElse(null); - if (cavesBiome != null && CONFIG.transferCavesWorldBiomes.contains(cavesBiome.getBiome())) { - return cavesBiome.getBiome(); + 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; } } - return biome == null ? Biome.PLAINS : biome.getBiome(); + // 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 @@ -182,5 +204,4 @@ private class BiomeProviderFromFile extends BiomeProvider { } } - } 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..4640f3b --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/generation/WorldInitListener.java @@ -0,0 +1,124 @@ +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; +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.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; + +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) { + 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(); + + // ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); + + 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); + + // 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 { + // 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); + WorldGenContext worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + Class clazz = worldGenContext.generator().getClass(); + 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 { + 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 new file mode 100644 index 0000000..ea3b856 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/BiomeHelper.java @@ -0,0 +1,100 @@ +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.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, NMSBiomeUtils.resourceLocation(newBiomeName)); + Biome base = registryWritable.get(key); + if (base == null) { + if (newBiomeName.contains(":")) { + ResourceKey newKey = ResourceKey.create(Registries.BIOME, NMSBiomeUtils.resourceLocation(newBiomeName)); + 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, NMSBiomeUtils.resourceLocation(newBiomeName)); + base = registrywritable.get(key); + if (base == null) { + if (newBiomeName.contains(":")) { + ResourceKey newKey = ResourceKey.create(Registries.BIOME, NMSBiomeUtils.resourceLocation(newBiomeName)); + 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..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 @@ -5,20 +5,26 @@ 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(String name) { + name = name.toLowerCase(); + if (!name.contains(":")) { + name = "minecraft:" + name; + } + this.name = name; + } // GETTERS - public org.bukkit.block.Biome getBiome() { return this.biome_; } + 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 +36,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{" + name + '}'; } } 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..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 @@ -1,22 +1,48 @@ package com.jkantrell.mc.underilla.spigot.impl; +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; public class BukkitBlock implements Block { + private static Collection ignoredBlockForSurfaceCalculation; // 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 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; + } + + public Optional getSpawnedType() { return this.spawnedType; } + public void setSpawnedType(String spawnedType) { + this.spawnedType = Optional.ofNullable(EntityType.valueOf(spawnedType.replace("minecraft:", "").toUpperCase())); + } // IMPLEMENTATIONS @Override @@ -25,6 +51,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(); @@ -53,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 acb85e3..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 @@ -1,9 +1,17 @@ package com.jkantrell.mc.underilla.spigot.impl; -import org.bukkit.block.Biome; +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.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; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; public class BukkitChunkData implements ChunkData { @@ -11,7 +19,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 +44,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 @@ -53,7 +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 (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 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..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 @@ -1,17 +1,18 @@ package com.jkantrell.mc.underilla.spigot.impl; 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; 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<>(); // CONSTRUCTORS public BukkitChunkReader(Chunk chunk) { super(chunk); } @@ -36,24 +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 biomeFromTag(StringTag tag) { - String[] raw = tag.getValue().split(":"); - String name = raw.length > 1 ? raw[1] : raw[0]; - 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(); + 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 082cf2b..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 { @@ -45,7 +46,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) { @@ -57,12 +58,22 @@ 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) { 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..a2ef75b --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/CustomBiomeSource.java @@ -0,0 +1,126 @@ +package com.jkantrell.mc.underilla.spigot.impl; + +import java.util.Map; +import java.util.Set; +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; +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 implements java.util.function.Supplier>> { + 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, 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() { + // 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() { return null; } + + @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, @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; + y = y << 2; + z = z << 2; + 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(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); + return vanillaBiome; + } + + // 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())) { + 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())); + } + } + + // 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("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); + if (vanillaBiome != null) { + String key = "notfound:" + vanillaBiome.getRegisteredName(); + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 0L) + 1); + return vanillaBiome; + } else { + String key = "error:plains"; + biomesPlaced.put(key, biomesPlaced.getOrDefault(key, 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; + } + } + + /** + * 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 new file mode 100644 index 0000000..450d387 --- /dev/null +++ b/Underilla-Spigot/src/main/java/com/jkantrell/mc/underilla/spigot/impl/NMSBiomeUtils.java @@ -0,0 +1,62 @@ +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 com.jkantrell.mc.underilla.spigot.Underilla; +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(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 ResourceLocation resourceLocation(String name) { + String[] t = name.split(":"); + return ResourceLocation.fromNamespaceAndPath(t[0], t[1]); + } + + // 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); + } + + 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 562b261..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 @@ -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,10 +44,13 @@ 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 = "custom_biome_enabled") + 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; @@ -59,23 +61,23 @@ 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(); @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; @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; @@ -97,10 +99,9 @@ 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.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 bdd10fa..ab72a4b 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" @@ -21,13 +23,16 @@ transfered_caves_world_biomes: - "DRIPSTONE_CAVES" - "DEEP_DARK" +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. -# - 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: "RELATIVE" +strategy: "SURFACE" # RELATIVE strategy exclusive settings relative: @@ -37,16 +42,12 @@ 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: # 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. @@ -54,6 +55,14 @@ relative_and_surface: depth: 6 +ignored_block_for_surface_calculation: + - .*LEAVES + - .*LOG + - .*WOOD + - .*PLANKS + - .*FENCE + - ICE + # The range the transition between vanilla and reference worlds takes, the wider, the smoother. # <= 0 results in a sharp transition. blend_range: 8 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 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ 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 diff --git a/settings.gradle b/settings.gradle.kts similarity index 52% rename from settings.gradle rename to settings.gradle.kts index 348557c..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-Fabric", "Underilla-Spigot") \ No newline at end of file +rootProject.name = "Underilla" +include("Underilla-Core", "Underilla-Spigot") //, "Underilla-Fabric" \ No newline at end of file