diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md index d074cff47..e5c4b0d4a 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -43,7 +43,7 @@ assignees: "" is Terra that is causing the issue. - [ ] I have checked that this is an issue with Terra and not an issue with the pack I am using. - + - [ ] I have attached a copy of the `latest.log` file - [ ] I have filled out and provided all the appropriate information. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d0f7e32c0..98ac590d7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -315,19 +315,15 @@ Terra has a global moderation team which is currently comprised of the following members: - solonovamax - - - discord: [@solonovamax#6983](https://discord.com/channels/@me/566146322273402881)* + - discord: [@solonovamax#6983](https://discord.com/channels/@me/566146322273402881)* - github: [@solonovamax](https://github.com/solonovamax) - - - email: [solonovamax@12oclockpoint.com](mailto:solonovamax@12oclockpoint.com) + - email: [solonovamax@12oclockpoint.com](mailto:solonovamax@12oclockpoint.com) - dfsek - - - discord: [@dfsek#4208](https://discord.com/channels/@me/378350362236682240)* + - discord: [@dfsek#4208](https://discord.com/channels/@me/378350362236682240)* - github: [@dfsek](https://github.com/dfsek) - email: [dfsek@protonmail.com](mailto:dfsek@protonmail.com) - duplex (duplexsystem) - - - discord: [@Duplex#0797](https://discord.com/channels/@me/356822848641171456)* + - discord: [@Duplex#0797](https://discord.com/channels/@me/356822848641171456)* - github: [@duplexsystem](https://github.com/duplexsystem) - email: [duplexsys@protonmail.com](mailto:duplexsys@protonmail.com) @@ -401,32 +397,17 @@ issue on this github repo, or contact a member of the global moderation team. ## License and Attribution This set of guidelines is distributed under a -[Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/3.0/) -. +[Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/3.0/). -These guidelines have been adapted from -[Mozilla's Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/) -, which were adapted from: +These guidelines have been adapted with modifications from the following sources: -- Mozilla's original Community Participation Guidelines +- [Mozilla's Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/) - The [Ubuntu Code of Conduct](https://ubuntu.com/community/code-of-conduct) -- - -Mozilla's [View Source Conference Code of Conduct](https://viewsourceconf.org/berlin-2016/code-of-conduct/) - -- And - the [Rust Language Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) - -which in turn were based -on [Stumptown Syndicate's Citizen Code of Conduct](http://citizencodeofconduct.org/) -, along with some adapted text from -the [LGBTQ in Technology Code of Conduct](https://lgbtq.technology/coc.html) and -the [WisCon code of conduct](http://wiscon.net/policies/anti-harassment/code-of-conduct/) -. - -It was then modified by solonovamax with various inclusions from -the [LGBTQ in Technology Code of Conduct](https://lgbtq.technology/coc.html) and -a few other sources. +- Mozilla's [View Source Conference Code of Conduct](https://viewsourceconf.org/berlin-2016/code-of-conduct/) +- The [Rust Language Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) +- The [Stumptown Syndicate's Citizen Code of Conduct](http://citizencodeofconduct.org/) +- The [LGBTQ in Technology Code of Conduct](https://lgbtq.technology/coc.html) +- The [WisCon code of conduct](http://wiscon.net/policies/anti-harassment/code-of-conduct/) ## Notes @@ -443,7 +424,6 @@ people are not comfortable using [neopronouns](https://www.mypronouns.org/neopronouns). But if someone refuses to use your more common pronouns, you should report them to us. Additionally, you may not ask people to use unreasonable pronouns, such as ' -acab/acabself', 'that/bitch', 'ur/mom', or -'dream/dreamself' (pronouns related to real people, eg. the minecraft youtuber ' -dreamwastaken'). Doing so will be considered mockery of individuals who use +acab/acabself', 'that/bitch', 'ur/mom', or anything else that may be considered +disrectful. Doing so will be considered mockery of individuals who use non-standard pronouns and is very disrespectful. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index de1b02560..0d71e84fc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ preRelease(true) -versionProjects(":common:api", version("6.2.0")) -versionProjects(":common:implementation", version("6.2.0")) -versionProjects(":platforms", version("6.2.0")) +versionProjects(":common:api", version("6.4.0")) +versionProjects(":common:implementation", version("6.4.0")) +versionProjects(":platforms", version("6.4.0")) allprojects { @@ -42,6 +42,7 @@ afterEvaluate { forImmediateSubProjects(":platforms") { configureDistribution() } + project(":platforms:bukkit:common").configureDistribution() forSubProjects(":common:addons") { apply(plugin = "com.github.johnrengelman.shadow") diff --git a/buildSrc/src/main/kotlin/DistributionConfig.kt b/buildSrc/src/main/kotlin/DistributionConfig.kt index 6c743e9e8..24750233f 100644 --- a/buildSrc/src/main/kotlin/DistributionConfig.kt +++ b/buildSrc/src/main/kotlin/DistributionConfig.kt @@ -1,6 +1,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import java.io.File import java.io.FileWriter +import java.net.URI import java.net.URL import java.nio.file.FileSystems import java.nio.file.Files @@ -44,7 +45,7 @@ fun Project.configureDistribution() { doLast { // https://github.com/johnrengelman/shadow/issues/111 - val dest = tasks.named("shadowJar").get().archiveFile.get().asFile.toPath() + val dest = URI.create("jar:" + tasks.named("shadowJar").get().archiveFile.get().asFile.toURI()) FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs -> forSubProjects(":common:addons") { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6d0a1e2e0..6c9e8a888 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -4,7 +4,7 @@ object Versions { const val paralithic = "0.7.0" const val strata = "1.1.1" - const val cloud = "1.7.0" + const val cloud = "1.8.0" const val slf4j = "1.7.36" const val log4j_slf4j_impl = "2.14.1" @@ -19,39 +19,38 @@ object Versions { object Fabric { const val fabricLoader = "0.14.8" - const val fabricAPI = "0.57.0+1.19" + const val fabricAPI = "0.83.1+1.20.1" } object Quilt { const val quiltLoader = "0.17.0" - const val fabricApi = "2.0.0-beta.4+0.57.0-1.19" + const val fabricApi = "6.0.0-beta.3+0.76.0-1.19.4" } object Mod { const val mixin = "0.11.2+mixin.0.8.5" - const val minecraft = "1.19" - const val yarn = "$minecraft+build.1" - const val fabricLoader = "0.14.2" + const val minecraft = "1.20.1" + const val yarn = "$minecraft+build.2" + const val fabricLoader = "0.14.21" const val architecuryLoom = "0.12.0.290" const val architecturyPlugin = "3.4-SNAPSHOT" const val loomQuiltflower = "1.7.1" - - const val lazyDfu = "0.1.2" } object Forge { - const val forge = "${Mod.minecraft}-41.0.63" + const val forge = "${Mod.minecraft}-47.0.3" const val burningwave = "12.53.0" } object Bukkit { const val paper = "1.18.2-R0.1-SNAPSHOT" const val paperLib = "1.0.5" - const val minecraft = "1.19" + const val minecraft = "1.20.1" const val reflectionRemapper = "0.1.0-SNAPSHOT" + const val paperDevBundle = "1.20.1-R0.1-SNAPSHOT" } object Sponge { diff --git a/common/addons/biome-provider-image-v2/LICENSE b/common/addons/biome-provider-image-v2/LICENSE new file mode 100644 index 000000000..64c1cd516 --- /dev/null +++ b/common/addons/biome-provider-image-v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2021 Polyhedral Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/common/addons/biome-provider-image-v2/README.md b/common/addons/biome-provider-image-v2/README.md new file mode 100644 index 000000000..573eabee7 --- /dev/null +++ b/common/addons/biome-provider-image-v2/README.md @@ -0,0 +1,5 @@ +# biome-provider-image-v2 + +Implements and registers the `IMAGE` biome provider, which +utilizes various config types provided by the `library-image` addon to +distribute biomes based on images. diff --git a/common/addons/biome-provider-image-v2/build.gradle.kts b/common/addons/biome-provider-image-v2/build.gradle.kts new file mode 100644 index 000000000..3819ba485 --- /dev/null +++ b/common/addons/biome-provider-image-v2/build.gradle.kts @@ -0,0 +1,12 @@ +version = version("1.0.0") + +dependencies { + compileOnlyApi(project(":common:addons:manifest-addon-loader")) + compileOnlyApi(project(":common:addons:library-image")) + implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) + testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) +} + +tasks.named("shadowJar") { + relocate("net.jafama", "com.dfsek.terra.addons.biome.image.lib.jafama") +} \ No newline at end of file diff --git a/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/ImageBiomeProvider.java b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/ImageBiomeProvider.java new file mode 100644 index 000000000..7a37664a1 --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/ImageBiomeProvider.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.image.v2; + +import java.util.Optional; + +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class ImageBiomeProvider implements BiomeProvider { + private final int resolution; + + private final ColorConverter colorConverter; + + private final ColorSampler colorSampler; + + public ImageBiomeProvider(ColorConverter colorConverter, ColorSampler colorSampler, int resolution) { + this.resolution = resolution; + this.colorConverter = colorConverter; + this.colorSampler = colorSampler; + } + + @Override + public Biome getBiome(int x, int y, int z, long seed) { + return getBiome(x, z); + } + + public Biome getBiome(int x, int z) { + x /= resolution; + z /= resolution; + return colorConverter.apply(colorSampler.apply(x, z)); + } + + @Override + public Optional getBaseBiome(int x, int z, long seed) { + return Optional.of(getBiome(x, z)); + } + + @Override + public Iterable getBiomes() { + return colorConverter.getEntries(); + } +} diff --git a/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/ImageBiomeProviderAddon.java b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/ImageBiomeProviderAddon.java new file mode 100644 index 000000000..c719f86be --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/ImageBiomeProviderAddon.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.image.v2; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.function.Supplier; + +import com.dfsek.terra.addons.biome.image.v2.config.ImageProviderTemplate; +import com.dfsek.terra.addons.biome.image.v2.config.converter.ClosestBiomeColorConverterTemplate; +import com.dfsek.terra.addons.biome.image.v2.config.converter.ExactBiomeColorConverterTemplate; +import com.dfsek.terra.addons.biome.image.v2.config.converter.mapping.DefinedBiomeColorMappingTemplate; +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.converter.mapping.BiomeDefinedColorMapping; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; +import com.dfsek.terra.addons.manifest.api.AddonInitializer; +import com.dfsek.terra.api.Platform; +import com.dfsek.terra.api.addon.BaseAddon; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; +import com.dfsek.terra.api.event.functional.FunctionalEventHandler; +import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.registry.CheckedRegistry; +import com.dfsek.terra.api.util.reflection.TypeKey; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class ImageBiomeProviderAddon implements AddonInitializer { + public static final TypeKey>> PROVIDER_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>>> BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>>> BIOME_COLOR_MAPPING_REGISTRY_KEY = new TypeKey<>() { + }; + + @Inject + private Platform platform; + + @Inject + private BaseAddon addon; + + @Override + public void initialize() { + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPreLoadEvent.class) + .priority(501) + .then(event -> { + CheckedRegistry>> providerRegistry = event.getPack().getOrCreateRegistry( + PROVIDER_REGISTRY_KEY); + providerRegistry.register(addon.key("IMAGE"), ImageProviderTemplate::new); + }) + .then(event -> { + CheckedRegistry>>> biomeColorConverterRegistry = event.getPack().getOrCreateRegistry( + BIOME_COLOR_CONVERTER_REGISTRY_KEY); + biomeColorConverterRegistry.register(addon.key("EXACT"), ExactBiomeColorConverterTemplate::new); + biomeColorConverterRegistry.register(addon.key("CLOSEST"), ClosestBiomeColorConverterTemplate::new); + }) + .then(event -> { + CheckedRegistry>>> biomeColorMappingRegistry = event.getPack().getOrCreateRegistry( + BIOME_COLOR_MAPPING_REGISTRY_KEY); + biomeColorMappingRegistry.register(addon.key("USE_BIOME_COLORS"), () -> () -> new BiomeDefinedColorMapping<>(event.getPack().getRegistry(Biome.class), b -> b)); + biomeColorMappingRegistry.register(addon.key("MAP"), DefinedBiomeColorMappingTemplate::new); + }) + .failThrough(); + } +} diff --git a/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/ImageProviderTemplate.java b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/ImageProviderTemplate.java new file mode 100644 index 000000000..8d9955ad4 --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/ImageProviderTemplate.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.image.v2.config; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.biome.image.v2.ImageBiomeProvider; +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +@SuppressWarnings("FieldMayBeFinal") +public class ImageProviderTemplate implements ObjectTemplate { + + @Value("resolution") + @Default + @Description("Sets the resolution at which to sample the image.") + private int resolution = 1; + + @Value("color-sampler") + private ColorSampler colorSampler; + + @Value("color-conversion") + private ColorConverter colorConverter; + + @Override + public BiomeProvider get() { + return new ImageBiomeProvider(colorConverter, colorSampler, resolution); + } +} diff --git a/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/ClosestBiomeColorConverterTemplate.java b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/ClosestBiomeColorConverterTemplate.java new file mode 100644 index 000000000..9c9e15869 --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/ClosestBiomeColorConverterTemplate.java @@ -0,0 +1,19 @@ +package com.dfsek.terra.addons.biome.image.v2.config.converter; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.image.config.converter.ClosestColorConverterTemplate; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; +import com.dfsek.terra.api.world.biome.Biome; + + +public class ClosestBiomeColorConverterTemplate extends ClosestColorConverterTemplate { + + @Value("match") + private ColorMapping match; + + @Override + protected ColorMapping getMapping() { + return match; + } +} diff --git a/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/ExactBiomeColorConverterTemplate.java b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/ExactBiomeColorConverterTemplate.java new file mode 100644 index 000000000..3be64aa77 --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/ExactBiomeColorConverterTemplate.java @@ -0,0 +1,37 @@ +package com.dfsek.terra.addons.biome.image.v2.config.converter; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.image.config.converter.ExactColorConverterTemplate; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; +import com.dfsek.terra.api.world.biome.Biome; + + +public class ExactBiomeColorConverterTemplate extends ExactColorConverterTemplate { + + @Value("match") + private ColorMapping match; + + @Value("else") + private Biome fallback; + + @Value("ignore-alpha") + @Default + private boolean ignoreAlpha = true; + + @Override + protected ColorMapping getMapping() { + return match; + } + + @Override + protected Biome getFallback() { + return fallback; + } + + @Override + protected boolean ignoreAlpha() { + return ignoreAlpha; + } +} diff --git a/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/mapping/DefinedBiomeColorMappingTemplate.java b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/mapping/DefinedBiomeColorMappingTemplate.java new file mode 100644 index 000000000..ea650d424 --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/java/com/dfsek/terra/addons/biome/image/v2/config/converter/mapping/DefinedBiomeColorMappingTemplate.java @@ -0,0 +1,24 @@ +package com.dfsek.terra.addons.biome.image.v2.config.converter.mapping; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.Map; + +import com.dfsek.terra.addons.image.config.ColorLoader.ColorString; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; +import com.dfsek.terra.addons.image.util.MapUtil; +import com.dfsek.terra.api.world.biome.Biome; + + +public class DefinedBiomeColorMappingTemplate implements ObjectTemplate> { + + @Value("map") + Map map; + + @Override + public ColorMapping get() { + var map = MapUtil.mapKeys(this.map, ColorString::getColor); + return () -> map; + } +} diff --git a/common/addons/biome-provider-image-v2/src/main/resources/terra.addon.yml b/common/addons/biome-provider-image-v2/src/main/resources/terra.addon.yml new file mode 100644 index 000000000..be3dd2af4 --- /dev/null +++ b/common/addons/biome-provider-image-v2/src/main/resources/terra.addon.yml @@ -0,0 +1,14 @@ +schema-version: 1 +contributors: + - Terra contributors +id: biome-provider-image-v2 +version: @VERSION@ +entrypoints: + - "com.dfsek.terra.addons.biome.image.v2.ImageBiomeProviderAddon" +website: + issues: https://github.com/PolyhedralDev/Terra/issues + source: https://github.com/PolyhedralDev/Terra + docs: https://terra.polydev.org +license: MIT License +depends: + library-image: "1.+" diff --git a/common/addons/biome-provider-image/src/main/java/com/dfsek/terra/addons/biome/image/ImageBiomeProviderAddon.java b/common/addons/biome-provider-image/src/main/java/com/dfsek/terra/addons/biome/image/ImageBiomeProviderAddon.java index 5db8dd51f..ecef73f5d 100644 --- a/common/addons/biome-provider-image/src/main/java/com/dfsek/terra/addons/biome/image/ImageBiomeProviderAddon.java +++ b/common/addons/biome-provider-image/src/main/java/com/dfsek/terra/addons/biome/image/ImageBiomeProviderAddon.java @@ -8,6 +8,8 @@ package com.dfsek.terra.addons.biome.image; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.function.Supplier; @@ -24,6 +26,9 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider; public class ImageBiomeProviderAddon implements AddonInitializer { + + private static final Logger logger = LoggerFactory.getLogger(ImageBiomeProviderAddon.class); + public static final TypeKey>> PROVIDER_REGISTRY_KEY = new TypeKey<>() { }; @@ -45,5 +50,6 @@ public class ImageBiomeProviderAddon implements AddonInitializer { () -> new ImageProviderTemplate(event.getPack().getRegistry(Biome.class))); }) .failThrough(); + logger.warn("The biome-provider-image addon is deprecated and scheduled for removal in Terra 7.0. It is recommended to use the biome-provider-image-v2 addon for future pack development instead."); } } diff --git a/common/addons/biome-provider-pipeline-v2/LICENSE b/common/addons/biome-provider-pipeline-v2/LICENSE new file mode 100644 index 000000000..64c1cd516 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2021 Polyhedral Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/README.md b/common/addons/biome-provider-pipeline-v2/README.md new file mode 100644 index 000000000..d1d216dfd --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/README.md @@ -0,0 +1,12 @@ +# biome-provider-pipeline-2 + +The second version of the Biome Pipeline, a procedural biome provider that uses a series +of "stages" to apply "mutations" to a 2D grid of biomes. + +Version 2 is a re-implementation of the original addon with the primary goal of providing +consistent scaling for noise relative to the world +(See https://github.com/PolyhedralDev/Terra/issues/264 for more details), and has been +included as a separate addon to maintain parity with packs utilizing the first version. + +This addon registers the `PIPELINE` biome provider type, and all associated +configurations. \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/build.gradle.kts b/common/addons/biome-provider-pipeline-v2/build.gradle.kts new file mode 100644 index 000000000..8ca2e3b81 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/build.gradle.kts @@ -0,0 +1,12 @@ +version = version("1.0.0") + +dependencies { + compileOnlyApi(project(":common:addons:manifest-addon-loader")) + + implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) + testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) +} + +tasks.named("shadowJar") { + relocate("net.jafama", "com.dfsek.terra.addons.biome.pipeline.lib.jafama") +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineAddon.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineAddon.java new file mode 100644 index 000000000..66c1a7908 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineAddon.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.function.Supplier; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.BiomePipelineTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.PipelineBiomeLoader; +import com.dfsek.terra.addons.biome.pipeline.v2.config.source.SamplerSourceTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.expander.ExpanderStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.BorderListStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.BorderStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.ReplaceListStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.ReplaceStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.SmoothStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.manifest.api.AddonInitializer; +import com.dfsek.terra.api.Platform; +import com.dfsek.terra.api.addon.BaseAddon; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPostLoadEvent; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; +import com.dfsek.terra.api.event.functional.FunctionalEventHandler; +import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.registry.CheckedRegistry; +import com.dfsek.terra.api.registry.Registry; +import com.dfsek.terra.api.util.reflection.TypeKey; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class BiomePipelineAddon implements AddonInitializer { + + public static final TypeKey>> SOURCE_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>> STAGE_REGISTRY_KEY = new TypeKey<>() { + }; + public static final TypeKey>> PROVIDER_REGISTRY_KEY = new TypeKey<>() { + }; + @Inject + private Platform platform; + + @Inject + private BaseAddon addon; + + @Override + public void initialize() { + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPreLoadEvent.class) + .then(event -> { + CheckedRegistry>> providerRegistry = event.getPack().getOrCreateRegistry( + PROVIDER_REGISTRY_KEY); + providerRegistry.register(addon.key("PIPELINE"), BiomePipelineTemplate::new); + }) + .then(event -> { + CheckedRegistry>> sourceRegistry = event.getPack().getOrCreateRegistry( + SOURCE_REGISTRY_KEY); + sourceRegistry.register(addon.key("SAMPLER"), SamplerSourceTemplate::new); + }) + .then(event -> { + CheckedRegistry>> stageRegistry = event.getPack().getOrCreateRegistry( + STAGE_REGISTRY_KEY); + stageRegistry.register(addon.key("FRACTAL_EXPAND"), ExpanderStageTemplate::new); + stageRegistry.register(addon.key("SMOOTH"), SmoothStageTemplate::new); + stageRegistry.register(addon.key("REPLACE"), ReplaceStageTemplate::new); + stageRegistry.register(addon.key("REPLACE_LIST"), ReplaceListStageTemplate::new); + stageRegistry.register(addon.key("BORDER"), BorderStageTemplate::new); + stageRegistry.register(addon.key("BORDER_LIST"), BorderListStageTemplate::new); + }) + .failThrough(); + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPostLoadEvent.class) + .then(event -> { + Registry biomeRegistry = event.getPack().getRegistry(Biome.class); + event.getPack().applyLoader(PipelineBiome.class, new PipelineBiomeLoader(biomeRegistry)); + }); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineColumn.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineColumn.java new file mode 100644 index 000000000..9b3e1391a --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineColumn.java @@ -0,0 +1,71 @@ +package com.dfsek.terra.addons.biome.pipeline.v2; + +import java.util.function.Consumer; + +import com.dfsek.terra.api.util.Column; +import com.dfsek.terra.api.util.function.IntIntObjConsumer; +import com.dfsek.terra.api.util.function.IntObjConsumer; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class BiomePipelineColumn implements Column { + private final int min; + private final int max; + + private final int x; + private final int z; + private final Biome biome; + + protected BiomePipelineColumn(BiomeProvider biomeProvider, int min, int max, int x, int z, long seed) { + this.min = min; + this.max = max; + this.x = x; + this.z = z; + this.biome = biomeProvider.getBiome(x, 0, z, seed); + } + + @Override + public int getMinY() { + return min; + } + + @Override + public int getMaxY() { + return max; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getZ() { + return z; + } + + @Override + public Biome get(int y) { + return biome; + } + + @Override + public void forRanges(int resolution, IntIntObjConsumer consumer) { + consumer.accept(min, max, biome); + } + + @Override + public void forEach(Consumer consumer) { + for(int y = min; y < max; y++) { + consumer.accept(biome); + } + } + + @Override + public void forEach(IntObjConsumer consumer) { + for(int y = min; y < max; y++) { + consumer.accept(y, biome); + } + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/PipelineBiomeProvider.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/PipelineBiomeProvider.java new file mode 100644 index 000000000..1a34d06e0 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/PipelineBiomeProvider.java @@ -0,0 +1,115 @@ +package com.dfsek.terra.addons.biome.pipeline.v2; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Pipeline; +import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import net.jafama.FastMath; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.StreamSupport; + +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.registry.key.StringIdentifiable; +import com.dfsek.terra.api.util.Column; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class PipelineBiomeProvider implements BiomeProvider { + + private final LoadingCache biomeChunkCache; + private final int chunkSize; + private final int resolution; + private final NoiseSampler mutator; + private final double noiseAmp; + private final Set biomes; + + public PipelineBiomeProvider(Pipeline pipeline, int resolution, NoiseSampler mutator, double noiseAmp) { + this.resolution = resolution; + this.mutator = mutator; + this.noiseAmp = noiseAmp; + this.chunkSize = pipeline.getChunkSize(); + this.biomeChunkCache = Caffeine.newBuilder() + .maximumSize(64) + .build(pipeline::generateChunk); + + Set biomeSet = new HashSet<>(); + pipeline.getSource().getBiomes().forEach(biomeSet::add); + Iterable result = biomeSet; + for(Stage stage : pipeline.getStages()) { + result = stage.getBiomes(result); + } + this.biomes = new HashSet<>(); + Iterable finalResult = result; + result.forEach(pipelineBiome -> { + if(pipelineBiome.isPlaceholder()) { + + StringBuilder biomeList = new StringBuilder("\n"); + StreamSupport.stream(finalResult.spliterator(), false) + .sorted(Comparator.comparing(StringIdentifiable::getID)) + .forEach(delegate -> biomeList + .append(" - ") + .append(delegate.getID()) + .append(':') + .append(delegate.getClass().getCanonicalName()) + .append('\n')); + throw new IllegalArgumentException("Biome Pipeline leaks placeholder biome \"" + pipelineBiome.getID() + + "\". Ensure there is a stage to guarantee replacement of the placeholder biome. Biomes: " + + biomeList); + } + this.biomes.add(pipelineBiome.getBiome()); + }); + } + + @Override + public Biome getBiome(int x, int y, int z, long seed) { + return getBiome(x, z, seed); + } + + public Biome getBiome(int x, int z, long seed) { + + x += mutator.noise(seed + 1, x, z) * noiseAmp; + z += mutator.noise(seed + 2, x, z) * noiseAmp; + + x /= resolution; + z /= resolution; + + int chunkX = FastMath.floorDiv(x, chunkSize); + int chunkZ = FastMath.floorDiv(z, chunkSize); + + int chunkWorldX = chunkX * chunkSize; + int chunkWorldZ = chunkZ * chunkSize; + + int xInChunk = x - chunkWorldX; + int zInChunk = z - chunkWorldZ; + + return biomeChunkCache.get(new SeededVector(seed, chunkWorldX, chunkWorldZ)).get(xInChunk, zInChunk).getBiome(); + } + + @Override + public Iterable getBiomes() { + return biomes; + } + + @Override + public Optional getBaseBiome(int x, int z, long seed) { + return Optional.of(getBiome(x, z, seed)); + } + + @Override + public Column getColumn(int x, int z, long seed, int min, int max) { + return new BiomePipelineColumn(this, min, max, x, z, seed); + } + + @Override + public int resolution() { + return resolution; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/BiomeChunk.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/BiomeChunk.java new file mode 100644 index 000000000..818652f59 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/BiomeChunk.java @@ -0,0 +1,10 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +public interface BiomeChunk { + + PipelineBiome get(int xInChunk, int zInChunk); +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Expander.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Expander.java new file mode 100644 index 000000000..177e74694 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Expander.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +/** + * Resizes the internal grid of a BiomeChunk when applied, serves the purpose of + * filling in null biomes as a result of this resizing. + */ +public interface Expander extends Stage { + + PipelineBiome fillBiome(ViewPoint viewPoint); + + @Override + default int maxRelativeReadDistance() { + return 0; + } + + @Override + default PipelineBiome apply(ViewPoint viewPoint) { + PipelineBiome currentBiome = viewPoint.getBiome(); + if(currentBiome == null) { + return fillBiome(viewPoint); + } else { + return currentBiome; + } + } +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Pipeline.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Pipeline.java new file mode 100644 index 000000000..cfb9ee9a8 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Pipeline.java @@ -0,0 +1,14 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +import java.util.List; + + +public interface Pipeline { + BiomeChunk generateChunk(SeededVector worldCoordinates); + + int getChunkSize(); + + Source getSource(); + + List getStages(); +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/SeededVector.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/SeededVector.java new file mode 100644 index 000000000..6581ca50c --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/SeededVector.java @@ -0,0 +1,19 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +public record SeededVector(long seed, int x, int z) { + + @Override + public boolean equals(Object obj) { + if(obj instanceof SeededVector that) { + return this.z == that.z && this.x == that.x && this.seed == that.seed; + } + return false; + } + + @Override + public int hashCode() { + int code = x; + code = 31 * code + z; + return 31 * code + ((int) (seed ^ (seed >>> 32))); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Source.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Source.java new file mode 100644 index 000000000..bfb135653 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Source.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +public interface Source { + PipelineBiome get(long seed, int x, int z); + + Iterable getBiomes(); +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Stage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Stage.java new file mode 100644 index 000000000..ee2c75e89 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Stage.java @@ -0,0 +1,15 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +public interface Stage { + PipelineBiome apply(ViewPoint viewPoint); + + int maxRelativeReadDistance(); + + default Iterable getBiomes(Iterable biomes) { + return biomes; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/DelegatedPipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/DelegatedPipelineBiome.java new file mode 100644 index 000000000..978e68b06 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/DelegatedPipelineBiome.java @@ -0,0 +1,40 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.Set; + +import com.dfsek.terra.api.world.biome.Biome; + + +public final class DelegatedPipelineBiome implements PipelineBiome { + private final Biome biome; + + public DelegatedPipelineBiome(Biome biome) { + this.biome = biome; + } + + @Override + public Biome getBiome() { + return biome; + } + + @Override + public int hashCode() { + return biome.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof DelegatedPipelineBiome that)) return false; + return that.biome.equals(this.biome); + } + + @Override + public Set getTags() { + return biome.getTags(); + } + + @Override + public String getID() { + return biome.getID(); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PipelineBiome.java new file mode 100644 index 000000000..ccde84022 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PipelineBiome.java @@ -0,0 +1,35 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.Set; + +import com.dfsek.terra.api.registry.key.StringIdentifiable; +import com.dfsek.terra.api.world.biome.Biome; + + +public interface PipelineBiome extends StringIdentifiable { + Biome getBiome(); + + static PipelineBiome placeholder(String id) { + return new PlaceholderPipelineBiome(id); + } + + static PipelineBiome from(Biome biome) { + return new DelegatedPipelineBiome(biome); + } + + static PipelineBiome self() { + return SelfPipelineBiome.INSTANCE; + } + + Set getTags(); + + default boolean isPlaceholder() { + return false; + } + + default boolean isSelf() { + return false; + } + + +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PlaceholderPipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PlaceholderPipelineBiome.java new file mode 100644 index 000000000..87bd67120 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PlaceholderPipelineBiome.java @@ -0,0 +1,51 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.HashSet; +import java.util.Set; + +import com.dfsek.terra.api.world.biome.Biome; + + +final class PlaceholderPipelineBiome implements PipelineBiome { + private final Set tags; + private final String id; + + public PlaceholderPipelineBiome(String id) { + this.id = id; + tags = new HashSet<>(); + tags.add(id); + tags.add("ALL"); + } + + @Override + public Biome getBiome() { + throw new UnsupportedOperationException("Cannot get raw biome from placeholder pipeline biome"); + } + + @Override + public Set getTags() { + return tags; + } + + @Override + public String getID() { + return id; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof PlaceholderPipelineBiome that)) return false; + + return this.id.equals(that.id); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/SelfPipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/SelfPipelineBiome.java new file mode 100644 index 000000000..80583c1cb --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/SelfPipelineBiome.java @@ -0,0 +1,40 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.Collections; +import java.util.Set; + +import com.dfsek.terra.api.world.biome.Biome; + + +final class SelfPipelineBiome implements PipelineBiome { + public static final SelfPipelineBiome INSTANCE = new SelfPipelineBiome(); + + private SelfPipelineBiome() { + + } + + @Override + public Biome getBiome() { + throw new UnsupportedOperationException("Cannot get biome from self delegate"); + } + + @Override + public boolean isSelf() { + return true; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public Set getTags() { + return Collections.emptySet(); + } + + @Override + public String getID() { + return "SELF"; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/BiomePipelineTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/BiomePipelineTemplate.java new file mode 100644 index 000000000..776d5cd04 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/BiomePipelineTemplate.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.List; + +import com.dfsek.terra.addons.biome.pipeline.v2.PipelineBiomeProvider; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.PipelineImpl; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +@SuppressWarnings({ "FieldMayBeFinal", "unused" }) +public class BiomePipelineTemplate implements ObjectTemplate { + @Value("resolution") + @Default + @Description(""" + The resolution at which to sample biomes. + + Larger values are quadratically faster, but produce lower quality results. + For example, a value of 3 would sample every 3 blocks.""") + protected @Meta int resolution = 1; + + @Value("pipeline.source") + @Description("The Biome Source to use for initial population of biomes.") + private @Meta Source source; + + @Value("pipeline.stages") + @Description("A list of pipeline stages to apply to the result of #source") + private @Meta List<@Meta Stage> stages; + + @Value("blend.sampler") + @Default + @Description("A sampler to use for blending the edges of biomes via domain warping.") + protected @Meta NoiseSampler blendSampler = NoiseSampler.zero(); + + @Value("blend.amplitude") + @Default + @Description("The amplitude at which to perform blending.") + protected @Meta double blendAmplitude = 0d; + + @Override + public BiomeProvider get() { + return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 128), resolution, blendSampler, blendAmplitude); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/PipelineBiomeLoader.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/PipelineBiomeLoader.java new file mode 100644 index 000000000..5e7a08578 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/PipelineBiomeLoader.java @@ -0,0 +1,32 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.config; + +import com.dfsek.tectonic.api.depth.DepthTracker; +import com.dfsek.tectonic.api.exception.LoadException; +import com.dfsek.tectonic.api.loader.ConfigLoader; +import com.dfsek.tectonic.api.loader.type.TypeLoader; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.AnnotatedType; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.api.registry.Registry; +import com.dfsek.terra.api.world.biome.Biome; + + +public class PipelineBiomeLoader implements TypeLoader { + private final Registry biomeRegistry; + + public PipelineBiomeLoader(Registry biomeRegistry) { + this.biomeRegistry = biomeRegistry; + } + + @Override + public PipelineBiome load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker) + throws LoadException { + if(c.equals("SELF")) return PipelineBiome.self(); + return biomeRegistry + .getByID((String) c) + .map(PipelineBiome::from) + .orElseGet(() -> PipelineBiome.placeholder((String) c)); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SamplerSourceTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SamplerSourceTemplate.java new file mode 100644 index 000000000..dbfb1ef31 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SamplerSourceTemplate.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.source; + +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.source.SamplerSource; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class SamplerSourceTemplate extends SourceTemplate { + @Value("sampler") + @Description("The sampler used to distribute biomes.") + private @Meta NoiseSampler noise; + + @Value("biomes") + @Description("The biomes to be distributed.") + private @Meta ProbabilityCollection<@Meta PipelineBiome> biomes; + + @Override + public Source get() { + return new SamplerSource(biomes, noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SourceTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SourceTemplate.java new file mode 100644 index 000000000..ba61f8940 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SourceTemplate.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.source; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; + + +public abstract class SourceTemplate implements ObjectTemplate { + +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/StageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/StageTemplate.java new file mode 100644 index 000000000..c1cea771e --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/StageTemplate.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage; + +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public abstract class StageTemplate implements ObjectTemplate { + @Value("sampler") + @Description("Sampler to use for stage distribution.") + protected @Meta NoiseSampler noise; +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/expander/ExpanderStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/expander/ExpanderStageTemplate.java new file mode 100644 index 000000000..312880d3b --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/expander/ExpanderStageTemplate.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.expander; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.expander.FractalExpander; + + +public class ExpanderStageTemplate extends StageTemplate { + @Override + public Expander get() { + return new FractalExpander(noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderListStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderListStageTemplate.java new file mode 100644 index 000000000..57d5c7deb --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderListStageTemplate.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import java.util.Map; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.BorderListStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class BorderListStageTemplate extends StageTemplate { + @Value("from") + private @Meta String from; + + @Value("default-replace") + private @Meta String defaultReplace; + + @Value("default-to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> defaultTo; + + @Value("replace") + private @Meta Map<@Meta PipelineBiome, @Meta ProbabilityCollection<@Meta PipelineBiome>> replace; + + + @Override + public Stage get() { + return new BorderListStage(replace, from, defaultReplace, noise, defaultTo); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderStageTemplate.java new file mode 100644 index 000000000..605eeb9f4 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderStageTemplate.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.BorderStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class BorderStageTemplate extends StageTemplate { + @Value("from") + private @Meta String from; + + @Value("replace") + private @Meta String replace; + + @Value("to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> to; + + @Override + public Stage get() { + return new BorderStage(from, replace, noise, to); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceListStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceListStageTemplate.java new file mode 100644 index 000000000..be9ade61c --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceListStageTemplate.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import java.util.Map; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.ReplaceListStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class ReplaceListStageTemplate extends StageTemplate { + @Value("default-from") + private @Meta String defaultFrom; + + @Value("default-to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> defaultTo; + + @Value("to") + private @Meta Map<@Meta PipelineBiome, @Meta ProbabilityCollection<@Meta PipelineBiome>> replace; + + @Override + public Stage get() { + return new ReplaceListStage(replace, defaultFrom, defaultTo, noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceStageTemplate.java new file mode 100644 index 000000000..6f273a294 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceStageTemplate.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.ReplaceStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class ReplaceStageTemplate extends StageTemplate { + @Value("from") + private @Meta String from; + + @Value("to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> to; + + @Override + public Stage get() { + return new ReplaceStage(from, to, noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/SmoothStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/SmoothStageTemplate.java new file mode 100644 index 000000000..a13e441af --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/SmoothStageTemplate.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.SmoothStage; + + +public class SmoothStageTemplate extends StageTemplate { + @Override + public Stage get() { + return new SmoothStage(noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/BiomeChunkImpl.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/BiomeChunkImpl.java new file mode 100644 index 000000000..cf739fa77 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/BiomeChunkImpl.java @@ -0,0 +1,217 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.pipeline; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import net.jafama.FastMath; + +import java.util.List; + + +public class BiomeChunkImpl implements BiomeChunk { + + private PipelineBiome[][] biomes; + private final SeededVector worldOrigin; + private final int chunkOriginArrayIndex; + private final int worldCoordinateScale; + + public BiomeChunkImpl(SeededVector worldOrigin, PipelineImpl pipeline) { + + this.worldOrigin = worldOrigin; + this.chunkOriginArrayIndex = pipeline.getChunkOriginArrayIndex(); + this.worldCoordinateScale = pipeline.getResolution(); + + int size = pipeline.getArraySize(); + + int expanderCount = pipeline.getExpanderCount(); + int expansionsApplied = 0; + + // Allocate working arrays + this.biomes = new PipelineBiome[size][size]; + PipelineBiome[][] lookupArray = new PipelineBiome[size][size]; + // A second lookup array is required such that stage application doesn't affect lookups, otherwise application may cascade + + // Construct working grid + int gridOrigin = 0; + int gridInterval = calculateGridInterval(expanderCount, expansionsApplied); + int gridSize = (size / gridInterval); + gridSize += expanderCount > 0 ? 1 : 0; // Add an extra border if expansion occurs + + // Fill working grid with initial cells + for(int gridX = 0; gridX < gridSize; gridX++) { + for(int gridZ = 0; gridZ < gridSize; gridZ++) { + int xIndex = gridOrigin + gridX * gridInterval; + int zIndex = gridOrigin + gridZ * gridInterval; + biomes[xIndex][zIndex] = pipeline.getSource().get(worldOrigin.seed(), xIndexToWorldCoordinate(xIndex), zIndexToWorldCoordinate(zIndex)); + } + } + + for(Stage stage : pipeline.getStages()) { + if(stage instanceof Expander) { + // Shrink working grid size, the expander will fill in null cells (as a result of shrinking the grid) during mutation + expansionsApplied++; + gridInterval = calculateGridInterval(expanderCount, expansionsApplied); + gridSize = expandSize(gridSize); + } + + int stageReadDistance = stage.maxRelativeReadDistance(); + if(stageReadDistance > 0) { + // Discard edges such that adjacent lookups are only ran on valid cells + gridSize = contractBordersFromSize(gridSize, stageReadDistance); + gridOrigin += stageReadDistance * gridInterval; + } + + // Cycle arrays, the previously populated array is swapped to be used for lookups, and the result of the stage application + // overwrites the previous lookup array. This saves having to allocate a new array copy each time + PipelineBiome[][] tempArray = biomes; + biomes = lookupArray; + lookupArray = tempArray; + + // Apply stage to working grid + for(int gridZ = 0; gridZ < gridSize; gridZ = gridZ + 1) { + for(int gridX = 0; gridX < gridSize; gridX = gridX + 1) { + int xIndex = gridOrigin + gridX * gridInterval; + int zIndex = gridOrigin + gridZ * gridInterval; + biomes[xIndex][zIndex] = stage.apply(new ViewPoint(this, gridInterval, gridX, gridZ, xIndex, zIndex, lookupArray)); + } + } + } + } + + @Override + public PipelineBiome get(int xInChunk, int zInChunk) { + int xIndex = xInChunk + chunkOriginArrayIndex; + int zIndex = zInChunk + chunkOriginArrayIndex; + return biomes[xIndex][zIndex]; + } + + private int xIndexToWorldCoordinate(int xIndex) { + return (worldOrigin.x() + xIndex - chunkOriginArrayIndex) * worldCoordinateScale; + } + + private int zIndexToWorldCoordinate(int zIndex) { + return (worldOrigin.z() + zIndex - chunkOriginArrayIndex) * worldCoordinateScale; + } + + protected static int initialSizeToArraySize(int expanderCount, int initialSize) { + int size = initialSize; + for(int i = 0; i < expanderCount; i++) { + size = expandSize(size); + } + return size; + } + + protected static int calculateChunkOriginArrayIndex(int totalExpanderCount, List stages) { + int finalGridOrigin = calculateFinalGridOrigin(totalExpanderCount, stages); + int initialGridInterval = calculateGridInterval(totalExpanderCount, 0); + + // Round the final grid origin up to the nearest multiple of initialGridInterval, such that each + // chunk samples points on the same overall grid. + // Without this, shared chunk borders (required because of adjacent cell reads) will not be identical + // because points would be sampled on grids at different offsets, resulting in artifacts at borders. + return FastMath.ceilToInt((double) finalGridOrigin / initialGridInterval) * initialGridInterval; + } + + private static int calculateFinalGridOrigin(int totalExpanderCount, List stages) { + int gridOrigin = 0; + int expansionsApplied = 0; + int gridInterval = calculateGridInterval(totalExpanderCount, expansionsApplied); + for (Stage stage : stages) { + if (stage instanceof Expander) { + expansionsApplied++; + gridInterval = calculateGridInterval(totalExpanderCount, expansionsApplied); + } + gridOrigin += stage.maxRelativeReadDistance() * gridInterval; + } + return gridOrigin; + } + + protected static int calculateChunkSize(int arraySize, int chunkOriginArrayIndex, int totalExpanderCount) { + return contractBordersFromSize(arraySize, chunkOriginArrayIndex) - (totalExpanderCount > 0 ? 1 : 0); + } + + private static int expandSize(int size) { + return size * 2 - 1; + } + + private static int contractBordersFromSize(int size, int border) { + return size - border * 2; + } + + private static int calculateGridInterval(int totalExpansions, int expansionsApplied) { + return 1 << (totalExpansions - expansionsApplied); + } + + private SeededVector getOrigin() { + return worldOrigin; + } + + /** + * Represents a point on the operating grid within the biomes array + */ + public static class ViewPoint { + private final BiomeChunkImpl chunk; + private final PipelineBiome biome; + private final int gridInterval; + private final int gridX; + private final int gridZ; + private final int xIndex; + private final int zIndex; + private final PipelineBiome[][] lookupArray; + + private ViewPoint(BiomeChunkImpl chunk, int gridInterval, int gridX, int gridZ, int xIndex, int zIndex, PipelineBiome[][] lookupArray) { + this.chunk = chunk; + this.gridInterval = gridInterval; + this.gridX = gridX; + this.gridZ = gridZ; + this.xIndex = xIndex; + this.zIndex = zIndex; + this.lookupArray = lookupArray; + this.biome = lookupArray[xIndex][zIndex]; + } + + public PipelineBiome getRelativeBiome(int x, int z) { + int lookupXIndex = this.xIndex + x * gridInterval; + int lookupZIndex = this.zIndex + z * gridInterval; + return lookupArray[lookupXIndex][lookupZIndex]; + } + + public PipelineBiome getBiome() { + return biome; + } + + /** + * @return X position of the point relative to the operating grid + */ + public int gridX() { + return gridX; + } + + /** + * @return Z position of the point relative to the operating grid + */ + public int gridZ() { + return gridZ; + } + + /** + * @return X position of the point in the world + */ + public int worldX() { + return chunk.xIndexToWorldCoordinate(xIndex); + } + + /** + * @return Z position of the point in the world + */ + public int worldZ() { + return chunk.zIndexToWorldCoordinate(zIndex); + } + + public long worldSeed() { + return chunk.getOrigin().seed(); + } + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/PipelineImpl.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/PipelineImpl.java new file mode 100644 index 000000000..a01091d2e --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/PipelineImpl.java @@ -0,0 +1,92 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Pipeline; +import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; + + +public class PipelineImpl implements Pipeline { + + private static final Logger logger = LoggerFactory.getLogger(PipelineImpl.class); + + private final Source source; + private final List stages; + private final int chunkSize; + private final int expanderCount; + private final int arraySize; + private final int chunkOriginArrayIndex; + private final int resolution; + + public PipelineImpl(Source source, List stages, int resolution, int idealChunkArraySize) { + this.source = source; + this.stages = stages; + this.resolution = resolution; + this.expanderCount = (int) stages.stream().filter(s -> s instanceof Expander).count(); + + // Optimize for the ideal array size + int arraySize; + int chunkOriginArrayIndex; + int chunkSize; + int initialSize = 1; + while (true) { + arraySize = BiomeChunkImpl.initialSizeToArraySize(expanderCount, initialSize); + chunkOriginArrayIndex = BiomeChunkImpl.calculateChunkOriginArrayIndex(expanderCount, stages); + chunkSize = BiomeChunkImpl.calculateChunkSize(arraySize, chunkOriginArrayIndex, expanderCount); + if (chunkSize > 1 && arraySize >= idealChunkArraySize) break; + initialSize++; + } + + this.arraySize = arraySize; + this.chunkOriginArrayIndex = chunkOriginArrayIndex; + this.chunkSize = chunkSize; + + logger.debug("Initialized a new biome pipeline:"); + logger.debug("Array size: {} (Target: {})", arraySize, idealChunkArraySize); + logger.debug("Internal array origin: {}", chunkOriginArrayIndex); + logger.debug("Chunk size: {}", chunkSize); + } + + @Override + public BiomeChunk generateChunk(SeededVector worldCoordinates) { + return new BiomeChunkImpl(worldCoordinates, this); + } + + @Override + public int getChunkSize() { + return chunkSize; + } + + @Override + public Source getSource() { + return source; + } + + @Override + public List getStages() { + return stages; + } + + protected int getExpanderCount() { + return expanderCount; + } + + protected int getArraySize() { + return arraySize; + } + + protected int getChunkOriginArrayIndex() { + return chunkOriginArrayIndex; + } + + protected int getResolution() { + return resolution; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SamplerSource.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SamplerSource.java new file mode 100644 index 000000000..f6d66b9e2 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SamplerSource.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.source; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class SamplerSource implements Source { + private final ProbabilityCollection biomes; + private final NoiseSampler sampler; + + public SamplerSource(ProbabilityCollection biomes, NoiseSampler sampler) { + this.biomes = biomes; + this.sampler = sampler; + } + + @Override + public PipelineBiome get(long seed, int x, int z) { + return biomes.get(sampler, x, z, seed); + } + + @Override + public Iterable getBiomes() { + return biomes.getContents(); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SingleSource.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SingleSource.java new file mode 100644 index 000000000..b2c0ee8db --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SingleSource.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.source; + +import java.util.Collections; +import java.util.Set; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; + + +public class SingleSource implements Source { + + private final PipelineBiome biome; + + public SingleSource(PipelineBiome biome) { + this.biome = biome; + } + + @Override + public PipelineBiome get(long seed, int x, int z) { + return biome; + } + + @Override + public Set getBiomes() { + return Collections.singleton(biome); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/expander/FractalExpander.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/expander/FractalExpander.java new file mode 100644 index 000000000..c82743ba4 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/expander/FractalExpander.java @@ -0,0 +1,37 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.stage.expander; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class FractalExpander implements Expander { + + private final NoiseSampler sampler; + + public FractalExpander(NoiseSampler sampler) { + this.sampler = sampler; + } + + @Override + public PipelineBiome fillBiome(BiomeChunkImpl.ViewPoint viewPoint) { + + int xMod2 = viewPoint.gridX() % 2; + int zMod2 = viewPoint.gridZ() % 2; + + double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ()); + + if (xMod2 == 1 && zMod2 == 0) { // Pick one of 2 neighbors on X axis randomly + return roll > 0 ? viewPoint.getRelativeBiome(-1, 0) : viewPoint.getRelativeBiome(1, 0); + + } else if (xMod2 == 0 && zMod2 == 1) { // Pick one of 2 neighbors on Z axis randomly + return roll > 0 ? viewPoint.getRelativeBiome(0, -1) : viewPoint.getRelativeBiome(0, 1); + + } else { // Pick one of 4 corners randomly + return roll > 0 ? + roll > 0.25 ? viewPoint.getRelativeBiome(-1, 1) : viewPoint.getRelativeBiome(1, 1) : + roll > -0.25 ? viewPoint.getRelativeBiome(-1, -1) : viewPoint.getRelativeBiome(1, -1); + } + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderListStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderListStage.java new file mode 100644 index 000000000..2587bc792 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderListStage.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; +import com.dfsek.terra.api.util.vector.Vector2Int; + + +public class BorderListStage implements Stage { + private final String border; + private final NoiseSampler noiseSampler; + private final ProbabilityCollection replaceDefault; + private final String defaultReplace; + private final Map> replace; + + private final Vector2Int[] borderPoints; + + public BorderListStage(Map> replace, String border, String defaultReplace, + NoiseSampler noiseSampler, ProbabilityCollection replaceDefault) { + this.border = border; + this.noiseSampler = noiseSampler; + this.replaceDefault = replaceDefault; + this.defaultReplace = defaultReplace; + this.replace = replace; + + List points = new ArrayList<>(); + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + if(x == 0 && z == 0) continue; + points.add(Vector2Int.of(x, z)); + } + } + this.borderPoints = points.toArray(new Vector2Int[0]); + + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + biomes.forEach(biomeSet::add); + biomeSet.addAll(replaceDefault.getContents().stream().filter(Predicate.not(PipelineBiome::isSelf)).toList()); + replace.forEach((biome, collection) -> biomeSet.addAll(collection.getContents())); + return biomeSet; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome center = viewPoint.getBiome(); + if(center.getTags().contains(defaultReplace)) { + for(Vector2Int point : borderPoints) { + PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ()); + if(current != null && current.getTags().contains(border)) { + if(replace.containsKey(center)) { + PipelineBiome replacement = replace.get(center).get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), + viewPoint.worldSeed()); + return replacement.isSelf() ? center : replacement; + } + PipelineBiome replacement = replaceDefault.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return replacement.isSelf() ? center : replacement; + } + } + } + return center; + } + + @Override + public int maxRelativeReadDistance() { + return 1; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderStage.java new file mode 100644 index 000000000..274ef1ff9 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderStage.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; +import com.dfsek.terra.api.util.vector.Vector2Int; + + +public class BorderStage implements Stage { + private final String border; + private final NoiseSampler noiseSampler; + private final ProbabilityCollection replace; + private final String replaceTag; + private final Vector2Int[] borderPoints; + + public BorderStage(String border, String replaceTag, NoiseSampler noiseSampler, ProbabilityCollection replace) { + this.border = border; + this.noiseSampler = noiseSampler; + this.replace = replace; + this.replaceTag = replaceTag; + List points = new ArrayList<>(); + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + if(x == 0 && z == 0) continue; + points.add(Vector2Int.of(x, z)); + } + } + this.borderPoints = points.toArray(new Vector2Int[0]); + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome center = viewPoint.getBiome(); + if(center.getTags().contains(replaceTag)) { + for(Vector2Int point : borderPoints) { + PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ()); + if(current != null && current.getTags().contains(border)) { + PipelineBiome replacement = replace.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return replacement.isSelf() ? center : replacement; + } + } + } + return center; + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + biomes.forEach(biomeSet::add); + biomeSet.addAll( + replace + .getContents() + .stream() + .filter( + Predicate.not(PipelineBiome::isSelf) + ) + .toList() + ); + return biomeSet; + } + + @Override + public int maxRelativeReadDistance() { + return 1; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceListStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceListStage.java new file mode 100644 index 000000000..372127546 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceListStage.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class ReplaceListStage implements Stage { + private final Map> replace; + private final NoiseSampler sampler; + private final ProbabilityCollection replaceDefault; + private final String defaultTag; + + public ReplaceListStage(Map> replace, String defaultTag, + ProbabilityCollection replaceDefault, NoiseSampler sampler) { + this.replace = replace; + this.sampler = sampler; + this.defaultTag = defaultTag; + this.replaceDefault = replaceDefault; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome center = viewPoint.getBiome(); + if(replace.containsKey(center)) { + PipelineBiome biome = replace.get(center).get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return biome.isSelf() ? viewPoint.getBiome() : biome; + } + if(viewPoint.getBiome().getTags().contains(defaultTag)) { + PipelineBiome biome = replaceDefault.get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return biome.isSelf() ? viewPoint.getBiome() : biome; + } + return center; + } + + @Override + public int maxRelativeReadDistance() { + return 0; + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + + Set reject = new HashSet<>(); + + biomes.forEach(biome -> { + if(!biome.getTags().contains(defaultTag) && !replace.containsKey(biome)) { + biomeSet.add(biome); + } else { + reject.add(biome); + } + }); + biomeSet.addAll(replaceDefault.getContents().stream().flatMap(terraBiome -> { + if(terraBiome.isSelf()) return reject.stream(); + return Stream.of(terraBiome); + }).toList()); + replace.forEach((biome, collection) -> biomeSet.addAll(collection.getContents().stream().map(terraBiome -> { + if(terraBiome.isSelf()) return biome; + return terraBiome; + }).toList())); + return biomeSet; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceStage.java new file mode 100644 index 000000000..b7a2fd684 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceStage.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class ReplaceStage implements Stage { + private final String replaceableTag; + private final ProbabilityCollection replace; + private final NoiseSampler sampler; + + public ReplaceStage(String replaceable, ProbabilityCollection replace, NoiseSampler sampler) { + this.replaceableTag = replaceable; + this.replace = replace; + this.sampler = sampler; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + if(viewPoint.getBiome().getTags().contains(replaceableTag)) { + PipelineBiome biome = replace.get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return biome.isSelf() ? viewPoint.getBiome() : biome; + } + return viewPoint.getBiome(); + } + + @Override + public int maxRelativeReadDistance() { + return 0; + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + Set reject = new HashSet<>(); + biomes.forEach(biome -> { + if(!biome.getTags().contains(replaceableTag)) { + biomeSet.add(biome); + } else { + reject.add(biome); + } + }); + biomeSet.addAll(replace.getContents().stream().flatMap(terraBiome -> { + if(terraBiome.isSelf()) return reject.stream(); + return Stream.of(terraBiome); + }).toList()); + return biomeSet; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/SmoothStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/SmoothStage.java new file mode 100644 index 000000000..92bbee433 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/SmoothStage.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.Objects; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class SmoothStage implements Stage { + + private final NoiseSampler sampler; + + public SmoothStage(NoiseSampler sampler) { + this.sampler = sampler; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome top = viewPoint.getRelativeBiome(1, 0); + PipelineBiome bottom = viewPoint.getRelativeBiome(-1, 0); + PipelineBiome left = viewPoint.getRelativeBiome(0, 1); + PipelineBiome right = viewPoint.getRelativeBiome(0, -1); + + double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ()); + + boolean vert = Objects.equals(top, bottom); + boolean horiz = Objects.equals(left, right); + + if(vert && horiz) { + return roll > 0 ? + roll > 0.25 ? left : right : + roll > -0.25 ? top : bottom; + } + if(vert) { + return roll > 0 ? top : bottom; + } + if(horiz) { + return roll > 0 ? left : right; + } + return viewPoint.getBiome(); + } + + @Override + public int maxRelativeReadDistance() { + return 1; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/resources/terra.addon.yml b/common/addons/biome-provider-pipeline-v2/src/main/resources/terra.addon.yml new file mode 100644 index 000000000..f800574ca --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/resources/terra.addon.yml @@ -0,0 +1,12 @@ +schema-version: 1 +contributors: + - Terra contributors +id: biome-provider-pipeline-v2 +version: @VERSION@ +entrypoints: + - "com.dfsek.terra.addons.biome.pipeline.v2.BiomePipelineAddon" +website: + issues: https://github.com/PolyhedralDev/Terra/issues + source: https://github.com/PolyhedralDev/Terra + docs: https://terra.polydev.org +license: MIT License \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java b/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java index 42aa95b0e..6765ab3c3 100644 --- a/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java +++ b/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java @@ -8,6 +8,8 @@ package com.dfsek.terra.addons.biome.pipeline; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.function.Supplier; @@ -39,6 +41,8 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider; public class BiomePipelineAddon implements AddonInitializer { + private static final Logger logger = LoggerFactory.getLogger(BiomePipelineAddon.class); + public static final TypeKey>> SOURCE_REGISTRY_KEY = new TypeKey<>() { }; @@ -85,5 +89,7 @@ public class BiomePipelineAddon implements AddonInitializer { Registry biomeRegistry = event.getPack().getRegistry(Biome.class); event.getPack().applyLoader(BiomeDelegate.class, new BiomeDelegateLoader(biomeRegistry)); }); + + logger.warn("The biome-provider-pipeline addon is deprecated and scheduled for removal in Terra 7.0. It is recommended to use the biome-provider-pipeline-v2 addon for future pack development instead."); } } diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/NoiseChunkGenerator3DAddon.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/NoiseChunkGenerator3DAddon.java index 06450b613..ce1ec9650 100644 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/NoiseChunkGenerator3DAddon.java +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/NoiseChunkGenerator3DAddon.java @@ -11,9 +11,10 @@ import com.dfsek.terra.addons.chunkgenerator.config.NoiseChunkGeneratorPackConfi import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseConfigTemplate; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties; import com.dfsek.terra.addons.chunkgenerator.config.palette.BiomePaletteTemplate; -import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo; -import com.dfsek.terra.addons.chunkgenerator.config.palette.SlantLayer; +import com.dfsek.terra.addons.chunkgenerator.config.palette.slant.SlantLayerTemplate; import com.dfsek.terra.addons.chunkgenerator.generation.NoiseChunkGenerator3D; +import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo; +import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder; import com.dfsek.terra.addons.manifest.api.AddonInitializer; import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.addon.BaseAddon; @@ -36,15 +37,20 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer { @Override public void initialize() { - PropertyKey paletteInfoPropertyKey = Context.create(PaletteInfo.class); + PropertyKey paletteInfoPropertyKey = Context.create(BiomePaletteInfo.class); PropertyKey noisePropertiesPropertyKey = Context.create(BiomeNoiseProperties.class); platform.getEventManager() .getHandler(FunctionalEventHandler.class) .register(addon, ConfigPackPreLoadEvent.class) .priority(1000) .then(event -> { + + event.getPack().applyLoader(SlantHolder.CalculationMethod.class, + (type, o, loader, depthTracker) -> SlantHolder.CalculationMethod.valueOf((String) o)); + NoiseChunkGeneratorPackConfigTemplate config = event.loadTemplate(new NoiseChunkGeneratorPackConfigTemplate()); - + event.getPack().getContext().put(config); + event.getPack() .getOrCreateRegistry(ChunkGeneratorProvider.class) .register(addon.key("NOISE_3D"), @@ -53,7 +59,7 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer { config.getVerticalRes(), noisePropertiesPropertyKey, paletteInfoPropertyKey)); event.getPack() - .applyLoader(SlantLayer.class, SlantLayer::new); + .applyLoader(SlantHolder.Layer.class, SlantLayerTemplate::new); }) .failThrough(); @@ -62,8 +68,10 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer { .register(addon, ConfigurationLoadEvent.class) .then(event -> { if(event.is(Biome.class)) { + NoiseChunkGeneratorPackConfigTemplate config = event.getPack().getContext().get(NoiseChunkGeneratorPackConfigTemplate.class); + event.getLoadedObject(Biome.class).getContext().put(paletteInfoPropertyKey, - event.load(new BiomePaletteTemplate(platform)).get()); + event.load(new BiomePaletteTemplate(platform, config.getSlantCalculationMethod())).get()); event.getLoadedObject(Biome.class).getContext().put(noisePropertiesPropertyKey, event.load(new BiomeNoiseConfigTemplate()).get()); } diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/NoiseChunkGeneratorPackConfigTemplate.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/NoiseChunkGeneratorPackConfigTemplate.java index e636ae400..db9922980 100644 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/NoiseChunkGeneratorPackConfigTemplate.java +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/NoiseChunkGeneratorPackConfigTemplate.java @@ -4,10 +4,12 @@ import com.dfsek.tectonic.api.config.template.ConfigTemplate; import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder; import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.properties.Properties; -public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate { +public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate, Properties { @Value("blend.terrain.elevation") @Default private @Meta int elevationBlend = 4; @@ -20,6 +22,10 @@ public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate { @Default private @Meta int verticalRes = 2; + @Value("slant.calculation-method") + @Default + private SlantHolder.@Meta CalculationMethod slantCalculationMethod = SlantHolder.CalculationMethod.Derivative; + public int getElevationBlend() { return elevationBlend; } @@ -31,4 +37,8 @@ public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate { public int getVerticalRes() { return verticalRes; } + + public SlantHolder.CalculationMethod getSlantCalculationMethod() { + return slantCalculationMethod; + } } diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/BiomePaletteTemplate.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/BiomePaletteTemplate.java index f1f0f804a..f872794c9 100644 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/BiomePaletteTemplate.java +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/BiomePaletteTemplate.java @@ -15,25 +15,23 @@ import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; -import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolderBuilder; -import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder; +import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo; +import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder; import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.config.meta.Meta; import com.dfsek.terra.api.world.chunk.generation.util.Palette; -public class BiomePaletteTemplate implements ObjectTemplate { +public class BiomePaletteTemplate implements ObjectTemplate { private final Platform platform; @Value("slant") @Default @Description("The slant palettes to use in this biome.") - private @Meta List<@Meta SlantLayer> slant = Collections.emptyList(); + private @Meta List slantLayers = Collections.emptyList(); @Value("slant-depth") @Default @@ -63,27 +61,16 @@ public class BiomePaletteTemplate implements ObjectTemplate { @Default private @Meta boolean updatePalette = false; - public BiomePaletteTemplate(Platform platform) { this.platform = platform; } + private final SlantHolder.CalculationMethod slantCalculationMethod; + + public BiomePaletteTemplate(Platform platform, SlantHolder.CalculationMethod slantCalculationMethod) { + this.platform = platform; + this.slantCalculationMethod = slantCalculationMethod; + } @Override - public PaletteInfo get() { - PaletteHolderBuilder builder = new PaletteHolderBuilder(); - for(Map layer : palettes) { - for(Entry entry : layer.entrySet()) { - builder.add(entry.getValue(), entry.getKey()); - } - } - - TreeMap slantLayers = new TreeMap<>(); - double minThreshold = Double.MAX_VALUE; - - for(SlantLayer layer : slant) { - double threshold = layer.getThreshold(); - if(threshold < minThreshold) minThreshold = threshold; - slantLayers.put(threshold, layer.getPalette()); - } - - return new PaletteInfo(builder.build(), SlantHolder.of(slantLayers, minThreshold), oceanPalette, seaLevel, slantDepth, - updatePalette); + public BiomePaletteInfo get() { + return new BiomePaletteInfo(PaletteHolder.of(palettes), SlantHolder.of(slantLayers, slantDepth, slantCalculationMethod), + oceanPalette, seaLevel, updatePalette); } } diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/PaletteInfo.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/PaletteInfo.java deleted file mode 100644 index caf3c4802..000000000 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/PaletteInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2020-2021 Polyhedral Development - * - * The Terra Core Addons are licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in this module's root directory. - */ - -package com.dfsek.terra.addons.chunkgenerator.config.palette; - -import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; -import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder; -import com.dfsek.terra.api.properties.Properties; -import com.dfsek.terra.api.world.chunk.generation.util.Palette; - - -public record PaletteInfo(PaletteHolder paletteHolder, - SlantHolder slantHolder, - Palette ocean, - int seaLevel, - int maxSlantDepth, - boolean updatePaletteWhenCarving) implements Properties { -} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/SlantLayer.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/SlantLayer.java deleted file mode 100644 index d63cbbb46..000000000 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/SlantLayer.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.dfsek.terra.addons.chunkgenerator.config.palette; - -import com.dfsek.tectonic.api.config.template.annotations.Value; -import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; - -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; -import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolderBuilder; -import com.dfsek.terra.api.config.meta.Meta; -import com.dfsek.terra.api.world.chunk.generation.util.Palette; - - -public class SlantLayer implements ObjectTemplate { - @Value("threshold") - private @Meta double threshold; - - @Value("palette") - private @Meta List<@Meta Map<@Meta Palette, @Meta Integer>> palettes; - - @Override - public SlantLayer get() { - return this; - } - - public double getThreshold() { - return threshold; - } - - public PaletteHolder getPalette() { - PaletteHolderBuilder builder = new PaletteHolderBuilder(); - for(Map layer : palettes) { - for(Entry entry : layer.entrySet()) { - builder.add(entry.getValue(), entry.getKey()); - } - } - - return builder.build(); - } -} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/slant/SlantLayerTemplate.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/slant/SlantLayerTemplate.java new file mode 100644 index 000000000..5a4fd646d --- /dev/null +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/config/palette/slant/SlantLayerTemplate.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.chunkgenerator.config.palette.slant; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.List; +import java.util.Map; + +import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; +import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.world.chunk.generation.util.Palette; + + +public class SlantLayerTemplate implements ObjectTemplate { + + @Value("threshold") + private @Meta double threshold; + + @Value("palette") + private @Meta List<@Meta Map<@Meta Palette, @Meta Integer>> palettes; + + @Override + public SlantHolder.Layer get() { + return new SlantHolder.Layer(PaletteHolder.of(palettes), threshold); + } +} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/NoiseChunkGenerator3D.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/NoiseChunkGenerator3D.java index fcb8d3fe2..f0f6de8e3 100644 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/NoiseChunkGenerator3D.java +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/NoiseChunkGenerator3D.java @@ -12,7 +12,7 @@ import net.jafama.FastMath; import org.jetbrains.annotations.NotNull; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties; -import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo; +import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo; import com.dfsek.terra.addons.chunkgenerator.generation.math.PaletteUtil; import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.LazilyEvaluatedInterpolator; import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D; @@ -40,13 +40,13 @@ public class NoiseChunkGenerator3D implements ChunkGenerator { private final int carverHorizontalResolution; private final int carverVerticalResolution; - private final PropertyKey paletteInfoPropertyKey; + private final PropertyKey paletteInfoPropertyKey; private final PropertyKey noisePropertiesKey; public NoiseChunkGenerator3D(ConfigPack pack, Platform platform, int elevationBlend, int carverHorizontalResolution, int carverVerticalResolution, PropertyKey noisePropertiesKey, - PropertyKey paletteInfoPropertyKey) { + PropertyKey paletteInfoPropertyKey) { this.platform = platform; this.air = platform.getWorldHandle().air(); this.carverHorizontalResolution = carverHorizontalResolution; @@ -97,7 +97,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator { for(int y = world.getMaxHeight() - 1; y >= world.getMinHeight(); y--) { Biome biome = biomeColumn.get(y); - PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey); + BiomePaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey); int sea = paletteInfo.seaLevel(); Palette seaPalette = paletteInfo.ocean(); @@ -131,7 +131,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator { Biome biome = biomeProvider.getBiome(x, y, z, world.getSeed()); Sampler3D sampler = samplerCache.get(x, z, world, biomeProvider); - PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey); + BiomePaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey); int fdX = FastMath.floorMod(x, 16); int fdZ = FastMath.floorMod(z, 16); diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/math/PaletteUtil.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/math/PaletteUtil.java index d649cf4f2..ebeabaf23 100644 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/math/PaletteUtil.java +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/generation/math/PaletteUtil.java @@ -7,40 +7,23 @@ package com.dfsek.terra.addons.chunkgenerator.generation.math; -import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo; +import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo; import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D; -import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder; +import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder; import com.dfsek.terra.api.world.chunk.generation.util.Palette; public final class PaletteUtil { - /** - * Derivative constant. - */ - private static final double DERIVATIVE_DIST = 0.55; - public static Palette getPalette(int x, int y, int z, Sampler3D sampler, PaletteInfo paletteInfo, int depth) { - SlantHolder slant = paletteInfo.slantHolder(); - if(!slant.isEmpty() && depth <= paletteInfo.maxSlantDepth()) { - double slope = derivative(sampler, x, y, z); - if(slope > slant.getMinSlope()) { - return slant.getPalette(slope).getPalette(y); + public static Palette getPalette(int x, int y, int z, Sampler3D sampler, BiomePaletteInfo paletteInfo, int depth) { + SlantHolder slantHolder = paletteInfo.slantHolder(); + if(slantHolder.isAboveDepth(depth)) { + double slant = slantHolder.calculateSlant(sampler, x, y, z); + if(slantHolder.isInSlantThreshold(slant)) { + return slantHolder.getPalette(slant).getPalette(y); } } return paletteInfo.paletteHolder().getPalette(y); } - - public static double derivative(Sampler3D sampler, double x, double y, double z) { - double baseSample = sampler.sample(x, y, z); - - double xVal1 = (sampler.sample(x + DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST; - double xVal2 = (sampler.sample(x - DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST; - double zVal1 = (sampler.sample(x, y, z + DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST; - double zVal2 = (sampler.sample(x, y, z - DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST; - double yVal1 = (sampler.sample(x, y + DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST; - double yVal2 = (sampler.sample(x, y - DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST; - - return Math.sqrt(((xVal2 - xVal1) * (xVal2 - xVal1)) + ((zVal2 - zVal1) * (zVal2 - zVal1)) + ((yVal2 - yVal1) * (yVal2 - yVal1))); - } } \ No newline at end of file diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/BiomePaletteInfo.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/BiomePaletteInfo.java new file mode 100644 index 000000000..a75eeef5f --- /dev/null +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/BiomePaletteInfo.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.chunkgenerator.palette; + +import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder; +import com.dfsek.terra.api.properties.Properties; +import com.dfsek.terra.api.world.chunk.generation.util.Palette; + + +public record BiomePaletteInfo(PaletteHolder paletteHolder, + SlantHolder slantHolder, + Palette ocean, + int seaLevel, + boolean updatePaletteWhenCarving) implements Properties { +} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolder.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolder.java index 2451d063c..fcd48f4a8 100644 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolder.java +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolder.java @@ -7,6 +7,13 @@ package com.dfsek.terra.addons.chunkgenerator.palette; +import net.jafama.FastMath; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + import com.dfsek.terra.api.world.chunk.generation.util.Palette; @@ -27,4 +34,43 @@ public class PaletteHolder { : palettes[palettes.length - 1] : palettes[0]; } + + public static PaletteHolder of(List> palettes) { + PaletteHolderBuilder builder = new PaletteHolderBuilder(); + for(Map layer : palettes) { + for(Entry entry : layer.entrySet()) { + builder.add(entry.getValue(), entry.getKey()); + } + } + return builder.build(); + } + + private static class PaletteHolderBuilder { + private final TreeMap paletteMap = new TreeMap<>(); + + public PaletteHolderBuilder add(int y, Palette palette) { + paletteMap.put(y, palette); + return this; + } + + public PaletteHolder build() { + + int min = FastMath.min(paletteMap.keySet().stream().min(Integer::compareTo).orElse(0), 0); + int max = FastMath.max(paletteMap.keySet().stream().max(Integer::compareTo).orElse(255), 255); + + Palette[] palettes = new Palette[paletteMap.lastKey() + 1 - min]; + for(int y = min; y <= FastMath.max(paletteMap.lastKey(), max); y++) { + Palette d = null; + for(Entry e : paletteMap.entrySet()) { + if(e.getKey() >= y) { + d = e.getValue(); + break; + } + } + if(d == null) throw new IllegalArgumentException("No palette for Y=" + y); + palettes[y - min] = d; + } + return new PaletteHolder(palettes, -min); + } + } } diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolderBuilder.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolderBuilder.java deleted file mode 100644 index 693939c75..000000000 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/PaletteHolderBuilder.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2020-2021 Polyhedral Development - * - * The Terra Core Addons are licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in this module's root directory. - */ - -package com.dfsek.terra.addons.chunkgenerator.palette; - -import net.jafama.FastMath; - -import java.util.Map; -import java.util.TreeMap; - -import com.dfsek.terra.api.world.chunk.generation.util.Palette; - - -public class PaletteHolderBuilder { - private final TreeMap paletteMap = new TreeMap<>(); - - public PaletteHolderBuilder add(int y, Palette palette) { - paletteMap.put(y, palette); - return this; - } - - public PaletteHolder build() { - - int min = FastMath.min(paletteMap.keySet().stream().min(Integer::compareTo).orElse(0), 0); - int max = FastMath.max(paletteMap.keySet().stream().max(Integer::compareTo).orElse(255), 255); - - Palette[] palettes = new Palette[paletteMap.lastKey() + 1 - min]; - for(int y = min; y <= FastMath.max(paletteMap.lastKey(), max); y++) { - Palette d = null; - for(Map.Entry e : paletteMap.entrySet()) { - if(e.getKey() >= y) { - d = e.getValue(); - break; - } - } - if(d == null) throw new IllegalArgumentException("No palette for Y=" + y); - palettes[y - min] = d; - } - return new PaletteHolder(palettes, -min); - } -} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/SlantHolder.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/SlantHolder.java deleted file mode 100644 index 6ceacee24..000000000 --- a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/SlantHolder.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2020-2021 Polyhedral Development - * - * The Terra Core Addons are licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in this module's root directory. - */ - -package com.dfsek.terra.addons.chunkgenerator.palette; - - -import java.util.Map.Entry; -import java.util.TreeMap; - - -public class SlantHolder { - private final TreeMap layers; - private final double minSlope; - - private SlantHolder(TreeMap layers, double minSlope) { - this.layers = layers; - this.minSlope = minSlope; - } - - public static SlantHolder of(TreeMap layers, double minSlope) { - if(layers.size() == 1) { - Entry firstEntry = layers.firstEntry(); - return new Single(firstEntry.getValue(), minSlope); - } - return new SlantHolder(layers, minSlope); - } - - public boolean isEmpty() { - return layers.isEmpty(); - } - - public PaletteHolder getPalette(double slope) { - return layers.floorEntry(slope).getValue(); - } - - public double getMinSlope() { - return minSlope; - } - - private static final class Single extends SlantHolder { - - private final PaletteHolder layers; - - public Single(PaletteHolder layers, double minSlope) { - super(of(minSlope, layers), minSlope); - this.layers = layers; - } - - private static TreeMap of(double v, PaletteHolder layer) { - TreeMap map = new TreeMap<>(); - map.put(v, layer); - return map; - } - - @Override - public PaletteHolder getPalette(double slope) { - return layers; - } - } -} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/MultipleSlantHolder.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/MultipleSlantHolder.java new file mode 100644 index 000000000..928edb2c2 --- /dev/null +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/MultipleSlantHolder.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.chunkgenerator.palette.slant; + + +import java.util.List; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; + + +public class MultipleSlantHolder extends SlantHolderImpl { + private final NavigableMap layers; + private final double slantThreshold; + + MultipleSlantHolder(List slant, int slantDepth, CalculationMethod calculationMethod) { + super(slantDepth, calculationMethod); + NavigableMap layers = new TreeMap<>(slant.stream().collect(Collectors.toMap(SlantHolder.Layer::threshold, SlantHolder.Layer::palette))); + Stream thresholds = layers.keySet().stream(); + double slantThreshold = floorToThreshold ? + thresholds.min(Double::compare).orElseThrow() : + thresholds.max(Double::compare).orElseThrow(); + this.layers = layers; + this.slantThreshold = slantThreshold; + } + + @Override + protected double getSlantThreshold() { + return slantThreshold; + } + + @Override + public PaletteHolder getPalette(double slant) { + return (floorToThreshold ? + layers.floorEntry(slant) : + layers.ceilingEntry(slant) + ).getValue(); + } +} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SingleSlantHolder.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SingleSlantHolder.java new file mode 100644 index 000000000..fdc9b27cb --- /dev/null +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SingleSlantHolder.java @@ -0,0 +1,24 @@ +package com.dfsek.terra.addons.chunkgenerator.palette.slant; + +import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; + + +final class SingleSlantHolder extends SlantHolderImpl { + + private final SlantHolder.Layer layer; + + public SingleSlantHolder(SlantHolder.Layer layer, int slantDepth, CalculationMethod calculationMethod) { + super(slantDepth, calculationMethod); + this.layer = layer; + } + + @Override + public PaletteHolder getPalette(double slant) { + return layer.palette(); + } + + @Override + protected double getSlantThreshold() { + return layer.threshold(); + } +} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SlantHolder.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SlantHolder.java new file mode 100644 index 000000000..e70670977 --- /dev/null +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SlantHolder.java @@ -0,0 +1,116 @@ +package com.dfsek.terra.addons.chunkgenerator.palette.slant; + +import java.util.List; + +import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D; +import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder; +import com.dfsek.terra.api.util.vector.Vector3; + + +public interface SlantHolder { + + static SlantHolder of(List layers, int slantDepth, CalculationMethod calculationMethod) { + if(layers.isEmpty()) { + return EMPTY; + } else if(layers.size() == 1) { + return new SingleSlantHolder(layers.get(0), slantDepth, calculationMethod); + } + return new MultipleSlantHolder(layers, slantDepth, calculationMethod); + } + + double calculateSlant(Sampler3D sampler, double x, double y, double z); + + boolean isAboveDepth(int depth); + + boolean isInSlantThreshold(double slant); + + PaletteHolder getPalette(double slant); + + record Layer(PaletteHolder palette, double threshold) { + } + + enum CalculationMethod { + DotProduct { + private static final Vector3 DOT_PRODUCT_DIRECTION = Vector3.of(0, 1, 0); + + private static final Vector3[] DOT_PRODUCT_SAMPLE_POINTS = { + Vector3.of(0, 0, -DERIVATIVE_DIST), + Vector3.of(0, 0, DERIVATIVE_DIST), + Vector3.of(0, -DERIVATIVE_DIST, 0), + Vector3.of(0, DERIVATIVE_DIST, 0), + Vector3.of(-DERIVATIVE_DIST, 0, 0), + Vector3.of(DERIVATIVE_DIST, 0, 0) + }; + + @Override + public double slant(Sampler3D sampler, double x, double y, double z) { + Vector3.Mutable normalApproximation = Vector3.Mutable.of(0, 0, 0); + for(Vector3 point : DOT_PRODUCT_SAMPLE_POINTS) { + var scalar = -sampler.sample(x+point.getX(), y+point.getY(), z+point.getZ()); + normalApproximation.add(point.mutable().multiply(scalar)); + } + return DOT_PRODUCT_DIRECTION.dot(normalApproximation.normalize()); + } + + @Override + public boolean floorToThreshold() { + return false; + } + }, + + Derivative { + @Override + public double slant(Sampler3D sampler, double x, double y, double z) { + double baseSample = sampler.sample(x, y, z); + + double xVal1 = (sampler.sample(x + DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST; + double xVal2 = (sampler.sample(x - DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST; + double zVal1 = (sampler.sample(x, y, z + DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST; + double zVal2 = (sampler.sample(x, y, z - DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST; + double yVal1 = (sampler.sample(x, y + DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST; + double yVal2 = (sampler.sample(x, y - DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST; + + return Math.sqrt(((xVal2 - xVal1) * (xVal2 - xVal1)) + ((zVal2 - zVal1) * (zVal2 - zVal1)) + ((yVal2 - yVal1) * (yVal2 - yVal1))); + } + + @Override + public boolean floorToThreshold() { + return true; + } + }; + + private static final double DERIVATIVE_DIST = 0.55; + + public abstract double slant(Sampler3D sampler, double x, double y, double z); + + /* + * Controls whether palettes should be applied before or after their respective thresholds. + * + * If true, slant values will map to the palette of the next floor threshold, otherwise they + * will map to the ceiling. + */ + public abstract boolean floorToThreshold(); + } + + SlantHolder EMPTY = new SlantHolder() { + @Override + public double calculateSlant(Sampler3D sampler, double x, double y, double z) { + throw new UnsupportedOperationException("Empty holder should not calculate slant"); + } + + @Override + public boolean isAboveDepth(int depth) { + return false; + } + + @Override + public boolean isInSlantThreshold(double slant) { + return false; + } + + @Override + public PaletteHolder getPalette(double slant) { + throw new UnsupportedOperationException("Empty holder cannot return a palette"); + } + }; +} diff --git a/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SlantHolderImpl.java b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SlantHolderImpl.java new file mode 100644 index 000000000..e7261b88e --- /dev/null +++ b/common/addons/chunk-generator-noise-3d/src/main/java/com/dfsek/terra/addons/chunkgenerator/palette/slant/SlantHolderImpl.java @@ -0,0 +1,38 @@ +package com.dfsek.terra.addons.chunkgenerator.palette.slant; + +import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D; + + +public abstract class SlantHolderImpl implements SlantHolder { + private final SlantHolder.CalculationMethod calculationMethod; + + private final int slantDepth; + + protected final boolean floorToThreshold; + + protected SlantHolderImpl(int slantDepth, CalculationMethod calculationMethod) { + this.floorToThreshold = calculationMethod.floorToThreshold(); + this.calculationMethod = calculationMethod; + this.slantDepth = slantDepth; + } + + protected abstract double getSlantThreshold(); + + @Override + public final double calculateSlant(Sampler3D sampler, double x, double y, double z) { + return calculationMethod.slant(sampler, x, y, z); + } + + @Override + public final boolean isAboveDepth(int depth) { + return depth <= slantDepth; + } + + @Override + public final boolean isInSlantThreshold(double slant) { + return (floorToThreshold ? + slant > getSlantThreshold() : + slant < getSlantThreshold() + ); + } +} diff --git a/common/addons/config-noise-function/build.gradle.kts b/common/addons/config-noise-function/build.gradle.kts index f51d5e878..02054f08b 100644 --- a/common/addons/config-noise-function/build.gradle.kts +++ b/common/addons/config-noise-function/build.gradle.kts @@ -1,6 +1,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -version = version("1.0.0") +version = version("1.1.0") dependencies { compileOnlyApi(project(":common:addons:manifest-addon-loader")) diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java index 06bf082b9..b002f29a4 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java @@ -21,8 +21,10 @@ import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate; import com.dfsek.terra.addons.noise.config.templates.ImageSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.KernelTemplate; import com.dfsek.terra.addons.noise.config.templates.LinearHeightmapSamplerTemplate; +import com.dfsek.terra.addons.noise.config.templates.TranslateSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.CellularNoiseTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.ConstantNoiseTemplate; +import com.dfsek.terra.addons.noise.config.templates.noise.DistanceSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.ExpressionFunctionTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.GaborNoiseTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.SimpleNoiseTemplate; @@ -30,6 +32,7 @@ import com.dfsek.terra.addons.noise.config.templates.noise.fractal.BrownianMotio import com.dfsek.terra.addons.noise.config.templates.noise.fractal.PingPongTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.fractal.RidgedFractalTemplate; import com.dfsek.terra.addons.noise.config.templates.normalizer.ClampNormalizerTemplate; +import com.dfsek.terra.addons.noise.config.templates.normalizer.ExpressionNormalizerTemplate; import com.dfsek.terra.addons.noise.config.templates.normalizer.LinearNormalizerTemplate; import com.dfsek.terra.addons.noise.config.templates.normalizer.NormalNormalizerTemplate; import com.dfsek.terra.addons.noise.config.templates.normalizer.PosterizationNormalizerTemplate; @@ -42,6 +45,7 @@ import com.dfsek.terra.addons.noise.samplers.arithmetic.MinSampler; import com.dfsek.terra.addons.noise.samplers.arithmetic.MultiplicationSampler; import com.dfsek.terra.addons.noise.samplers.arithmetic.SubtractionSampler; import com.dfsek.terra.addons.noise.samplers.noise.CellularSampler; +import com.dfsek.terra.addons.noise.samplers.noise.DistanceSampler; import com.dfsek.terra.addons.noise.samplers.noise.random.GaussianNoiseSampler; import com.dfsek.terra.addons.noise.samplers.noise.random.PositiveWhiteNoiseSampler; import com.dfsek.terra.addons.noise.samplers.noise.random.WhiteNoiseSampler; @@ -83,6 +87,8 @@ public class NoiseAddon implements AddonInitializer { (type, o, loader, depthTracker) -> CellularSampler.DistanceFunction.valueOf((String) o)) .applyLoader(CellularSampler.ReturnType.class, (type, o, loader, depthTracker) -> CellularSampler.ReturnType.valueOf((String) o)) + .applyLoader(DistanceSampler.DistanceFunction.class, + (type, o, loader, depthTracker) -> DistanceSampler.DistanceFunction.valueOf((String) o)) .applyLoader(DimensionApplicableNoiseSampler.class, DimensionApplicableNoiseSampler::new) .applyLoader(FunctionTemplate.class, FunctionTemplate::new); @@ -92,9 +98,9 @@ public class NoiseAddon implements AddonInitializer { noiseRegistry.register(addon.key("PROBABILITY"), ProbabilityNormalizerTemplate::new); noiseRegistry.register(addon.key("SCALE"), ScaleNormalizerTemplate::new); noiseRegistry.register(addon.key("POSTERIZATION"), PosterizationNormalizerTemplate::new); - + noiseRegistry.register(addon.key("IMAGE"), ImageSamplerTemplate::new); - + noiseRegistry.register(addon.key("DOMAIN_WARP"), DomainWarpTemplate::new); noiseRegistry.register(addon.key("FBM"), BrownianMotionTemplate::new); @@ -116,12 +122,15 @@ public class NoiseAddon implements AddonInitializer { noiseRegistry.register(addon.key("WHITE_NOISE"), () -> new SimpleNoiseTemplate(WhiteNoiseSampler::new)); noiseRegistry.register(addon.key("POSITIVE_WHITE_NOISE"), () -> new SimpleNoiseTemplate(PositiveWhiteNoiseSampler::new)); noiseRegistry.register(addon.key("GAUSSIAN"), () -> new SimpleNoiseTemplate(GaussianNoiseSampler::new)); + + noiseRegistry.register(addon.key("DISTANCE"), DistanceSamplerTemplate::new); noiseRegistry.register(addon.key("CONSTANT"), ConstantNoiseTemplate::new); noiseRegistry.register(addon.key("KERNEL"), KernelTemplate::new); noiseRegistry.register(addon.key("LINEAR_HEIGHTMAP"), LinearHeightmapSamplerTemplate::new); + noiseRegistry.register(addon.key("TRANSLATE"), TranslateSamplerTemplate::new); noiseRegistry.register(addon.key("ADD"), () -> new BinaryArithmeticTemplate<>(AdditionSampler::new)); noiseRegistry.register(addon.key("SUB"), () -> new BinaryArithmeticTemplate<>(SubtractionSampler::new)); @@ -134,7 +143,7 @@ public class NoiseAddon implements AddonInitializer { Map packSamplers = new LinkedHashMap<>(); Map packFunctions = new LinkedHashMap<>(); noiseRegistry.register(addon.key("EXPRESSION"), () -> new ExpressionFunctionTemplate(packSamplers, packFunctions)); - + noiseRegistry.register(addon.key("EXPRESSION_NORMALIZER"), () -> new ExpressionNormalizerTemplate(packSamplers, packFunctions)); NoiseConfigPackTemplate template = event.loadTemplate(new NoiseConfigPackTemplate()); packSamplers.putAll(template.getSamplers()); diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/ImageSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/ImageSamplerTemplate.java index 4aaf2d511..bbccae810 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/ImageSamplerTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/ImageSamplerTemplate.java @@ -8,6 +8,8 @@ package com.dfsek.terra.addons.noise.config.templates; import com.dfsek.tectonic.api.config.template.annotations.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.awt.image.BufferedImage; @@ -19,6 +21,10 @@ import com.dfsek.terra.api.noise.NoiseSampler; @SuppressWarnings({ "unused", "FieldMayBeFinal" }) public class ImageSamplerTemplate extends SamplerTemplate { + private static final Logger logger = LoggerFactory.getLogger(ImageSamplerTemplate.class); + + private static boolean used = false; + @Value("image") private @Meta BufferedImage image; @@ -30,6 +36,12 @@ public class ImageSamplerTemplate extends SamplerTemplate { @Override public NoiseSampler get() { + if(!used) { + logger.warn("The IMAGE NoiseSampler implemented by the config-noise-function addon is deprecated. " + + "It is recommended to use the IMAGE NoiseSampler implemented by the config-noise-image " + + "addon instead."); + used = true; + } return new ImageSampler(image, channel, frequency); } } diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/LinearHeightmapSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/LinearHeightmapSamplerTemplate.java index 3185ea9c3..d4339968e 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/LinearHeightmapSamplerTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/LinearHeightmapSamplerTemplate.java @@ -4,6 +4,7 @@ import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; import com.dfsek.terra.addons.noise.samplers.LinearHeightmapSampler; +import com.dfsek.terra.api.config.meta.Meta; import com.dfsek.terra.api.noise.NoiseSampler; @@ -11,14 +12,14 @@ import com.dfsek.terra.api.noise.NoiseSampler; public class LinearHeightmapSamplerTemplate extends SamplerTemplate { @Value("sampler") @Default - private NoiseSampler sampler = NoiseSampler.zero(); + private @Meta NoiseSampler sampler = NoiseSampler.zero(); @Value("base") - private double base; + private @Meta double base; @Value("scale") @Default - private double scale = 1; + private @Meta double scale = 1; @Override public NoiseSampler get() { diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/TranslateSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/TranslateSamplerTemplate.java new file mode 100644 index 000000000..c08a8253f --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/TranslateSamplerTemplate.java @@ -0,0 +1,32 @@ +package com.dfsek.terra.addons.noise.config.templates; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.noise.samplers.TranslateSampler; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class TranslateSamplerTemplate extends SamplerTemplate { + + @Value("sampler") + private NoiseSampler sampler; + + @Value("x") + @Default + private @Meta double x = 0; + + @Value("y") + @Default + private @Meta double y = 0; + + @Value("z") + @Default + private @Meta double z = 0; + + @Override + public NoiseSampler get() { + return new TranslateSampler(sampler, x, y ,z); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DistanceSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DistanceSamplerTemplate.java new file mode 100644 index 000000000..1bd6fb2dc --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DistanceSamplerTemplate.java @@ -0,0 +1,42 @@ +package com.dfsek.terra.addons.noise.config.templates.noise; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.noise.config.templates.SamplerTemplate; +import com.dfsek.terra.addons.noise.samplers.noise.DistanceSampler; +import com.dfsek.terra.addons.noise.samplers.noise.DistanceSampler.DistanceFunction; +import com.dfsek.terra.api.config.meta.Meta; + + +public class DistanceSamplerTemplate extends SamplerTemplate { + + @Value("distance-function") + @Default + private DistanceSampler.@Meta DistanceFunction distanceFunction = DistanceFunction.Euclidean; + + @Value("point.x") + @Default + private @Meta double x = 0; + + @Value("point.y") + @Default + private @Meta double y = 0; + + @Value("point.z") + @Default + private @Meta double z = 0; + + @Value("normalize") + @Default + private @Meta boolean normalize = false; + + @Value("radius") + @Default + private @Meta double normalizeRadius = 100; + + @Override + public DistanceSampler get() { + return new DistanceSampler(distanceFunction, x, y, z, normalize, normalizeRadius); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/ExpressionFunctionTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/ExpressionFunctionTemplate.java index a15cc8e61..e401aa0cf 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/ExpressionFunctionTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/ExpressionFunctionTemplate.java @@ -8,7 +8,6 @@ package com.dfsek.terra.addons.noise.config.templates.noise; import com.dfsek.paralithic.eval.tokenizer.ParseException; -import com.dfsek.paralithic.functions.Function; import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; @@ -19,17 +18,16 @@ import java.util.Map; import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler; import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate; import com.dfsek.terra.addons.noise.config.templates.SamplerTemplate; -import com.dfsek.terra.addons.noise.paralithic.defined.UserDefinedFunction; -import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction2; -import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction3; import com.dfsek.terra.addons.noise.samplers.noise.ExpressionFunction; import com.dfsek.terra.api.config.meta.Meta; import com.dfsek.terra.api.noise.NoiseSampler; +import static com.dfsek.terra.addons.noise.paralithic.FunctionUtil.convertFunctionsAndSamplers; + @SuppressWarnings({ "FieldMayBeFinal", "unused" }) public class ExpressionFunctionTemplate extends SamplerTemplate { - private final Map otherFunctions; + private final Map globalSamplers; private final Map globalFunctions; @Value("variables") @Default @@ -43,44 +41,19 @@ public class ExpressionFunctionTemplate extends SamplerTemplate functions = new LinkedHashMap<>(); - public ExpressionFunctionTemplate(Map otherFunctions, Map samplers) { - this.otherFunctions = otherFunctions; - this.globalFunctions = samplers; + public ExpressionFunctionTemplate(Map globalSamplers, Map globalFunctions) { + this.globalSamplers = globalSamplers; + this.globalFunctions = globalFunctions; } @Override public NoiseSampler get() { + var mergedFunctions = new HashMap<>(globalFunctions); mergedFunctions.putAll(functions); + var mergedSamplers = new HashMap<>(globalSamplers); mergedSamplers.putAll(samplers); try { - Map noiseFunctionMap = generateFunctions(); - return new ExpressionFunction(noiseFunctionMap, expression, vars); + return new ExpressionFunction(convertFunctionsAndSamplers(mergedFunctions, mergedSamplers), expression, vars); } catch(ParseException e) { throw new RuntimeException("Failed to parse expression.", e); } } - - private Map generateFunctions() throws ParseException { - Map noiseFunctionMap = new HashMap<>(); - - for(Map.Entry entry : globalFunctions.entrySet()) { - noiseFunctionMap.put(entry.getKey(), UserDefinedFunction.newInstance(entry.getValue())); - } - - for(Map.Entry entry : functions.entrySet()) { - noiseFunctionMap.put(entry.getKey(), UserDefinedFunction.newInstance(entry.getValue())); - } - - otherFunctions.forEach((id, function) -> { - if(function.getDimensions() == 2) { - noiseFunctionMap.put(id, new NoiseFunction2(function.getSampler())); - } else noiseFunctionMap.put(id, new NoiseFunction3(function.getSampler())); - }); - - samplers.forEach((id, function) -> { - if(function.getDimensions() == 2) { - noiseFunctionMap.put(id, new NoiseFunction2(function.getSampler())); - } else noiseFunctionMap.put(id, new NoiseFunction3(function.getSampler())); - }); - - return noiseFunctionMap; - } } diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/normalizer/ExpressionNormalizerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/normalizer/ExpressionNormalizerTemplate.java new file mode 100644 index 000000000..97318d388 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/normalizer/ExpressionNormalizerTemplate.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.noise.config.templates.normalizer; + +import com.dfsek.paralithic.eval.tokenizer.ParseException; +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler; +import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate; +import com.dfsek.terra.addons.noise.normalizer.ExpressionNormalizer; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.dfsek.terra.addons.noise.paralithic.FunctionUtil.convertFunctionsAndSamplers; + + +@SuppressWarnings({ "unused", "FieldMayBeFinal" }) +public class ExpressionNormalizerTemplate extends NormalizerTemplate { + + private final Map globalSamplers; + private final Map globalFunctions; + + @Value("expression") + private @Meta String expression; + + @Value("variables") + @Default + private @Meta Map vars = new HashMap<>(); + + @Value("samplers") + @Default + private @Meta LinkedHashMap samplers = new LinkedHashMap<>(); + + @Value("functions") + @Default + private @Meta LinkedHashMap functions = new LinkedHashMap<>(); + + public ExpressionNormalizerTemplate(Map globalSamplers, Map globalFunctions) { + this.globalSamplers = globalSamplers; + this.globalFunctions = globalFunctions; + } + + @Override + public NoiseSampler get() { + var mergedFunctions = new HashMap<>(globalFunctions); mergedFunctions.putAll(functions); + var mergedSamplers = new HashMap<>(globalSamplers); mergedSamplers.putAll(samplers); + try { + return new ExpressionNormalizer(function, convertFunctionsAndSamplers(mergedFunctions, mergedSamplers), expression, vars); + } catch(ParseException e) { + throw new RuntimeException("Failed to parse expression.", e); + } + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/normalizer/ExpressionNormalizer.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/normalizer/ExpressionNormalizer.java new file mode 100644 index 000000000..95c607f76 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/normalizer/ExpressionNormalizer.java @@ -0,0 +1,33 @@ +package com.dfsek.terra.addons.noise.normalizer; + +import com.dfsek.paralithic.Expression; +import com.dfsek.paralithic.eval.parser.Parser; +import com.dfsek.paralithic.eval.parser.Scope; +import com.dfsek.paralithic.eval.tokenizer.ParseException; +import com.dfsek.paralithic.functions.Function; + +import com.dfsek.terra.api.noise.NoiseSampler; + +import java.util.Map; + + +public class ExpressionNormalizer extends Normalizer { + + private final Expression expression; + + public ExpressionNormalizer(NoiseSampler sampler, Map functions, String eq, Map vars) + throws ParseException { + super(sampler); + Parser p = new Parser(); + Scope scope = new Scope(); + scope.addInvocationVariable("in"); + vars.forEach(scope::create); + functions.forEach(p::registerFunction); + expression = p.parse(eq, scope); + } + + @Override + public double normalize(double in) { + return expression.evaluate(in); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/paralithic/FunctionUtil.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/paralithic/FunctionUtil.java new file mode 100644 index 000000000..f968f5bad --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/paralithic/FunctionUtil.java @@ -0,0 +1,31 @@ +package com.dfsek.terra.addons.noise.paralithic; + +import com.dfsek.paralithic.eval.tokenizer.ParseException; +import com.dfsek.paralithic.functions.Function; + +import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler; +import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate; +import com.dfsek.terra.addons.noise.paralithic.defined.UserDefinedFunction; +import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction2; +import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction3; + +import java.util.HashMap; +import java.util.Map; + + +public class FunctionUtil { + private FunctionUtil() {} + + public static Map convertFunctionsAndSamplers(Map functions, + Map samplers) throws ParseException { + Map functionMap = new HashMap<>(); + for(Map.Entry entry : functions.entrySet()) { + functionMap.put(entry.getKey(), UserDefinedFunction.newInstance(entry.getValue())); + } + samplers.forEach((id, sampler) -> functionMap.put(id, + sampler.getDimensions() == 2 ? + new NoiseFunction2(sampler.getSampler()) : + new NoiseFunction3(sampler.getSampler()))); + return functionMap; + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/TranslateSampler.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/TranslateSampler.java new file mode 100644 index 000000000..b634da5dc --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/TranslateSampler.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.noise.samplers; + +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class TranslateSampler implements NoiseSampler { + + private final NoiseSampler sampler; + private final double dx, dy, dz; + + public TranslateSampler(NoiseSampler sampler, double dx, double dy, double dz) { + this.sampler = sampler; + this.dx = dx; + this.dy = dy; + this.dz = dz; + } + + @Override + public double noise(long seed, double x, double y) { + return sampler.noise(seed, x - dx, y - dz); + } + + @Override + public double noise(long seed, double x, double y, double z) { + return sampler.noise(seed, x - dx, y - dy, z - dz); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java index f31f81a3f..cdd40990d 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java @@ -240,99 +240,37 @@ public class CellularSampler extends NoiseFunction { double centerX = x; double centerY = y; - switch(distanceFunction) { - default: - case Euclidean: - case EuclideanSq: - for(int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; - - for(int yi = yr - 1; yi <= yr + 1; yi++) { - int hash = hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - double vecX = (xi - x) + RAND_VECS_2D[idx] * cellularJitter; - double vecY = (yi - y) + RAND_VECS_2D[idx | 1] * cellularJitter; - - double newDistance = vecX * vecX + vecY * vecY; - - distance1 = fastMax(fastMin(distance1, newDistance), distance0); - if(newDistance < distance0) { - distance0 = newDistance; - closestHash = hash; - centerX = ((xi + RAND_VECS_2D[idx] * cellularJitter) / frequency); - centerY = ((yi + RAND_VECS_2D[idx | 1] * cellularJitter) / frequency); - } else if(newDistance < distance1) { - distance2 = distance1; - distance1 = newDistance; - } else if(newDistance < distance2) { - distance2 = newDistance; - } - yPrimed += PRIME_Y; - } - xPrimed += PRIME_X; + for(int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for(int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + double vecX = (xi - x) + RAND_VECS_2D[idx] * cellularJitter; + double vecY = (yi - y) + RAND_VECS_2D[idx | 1] * cellularJitter; + + double newDistance = switch(distanceFunction) { + case Manhattan -> fastAbs(vecX) + fastAbs(vecY); + case Hybrid -> (fastAbs(vecX) + fastAbs(vecY)) + (vecX * vecX + vecY * vecY); + default -> vecX * vecX + vecY * vecY; + }; + + distance1 = fastMax(fastMin(distance1, newDistance), distance0); + if(newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + centerX = ((xi + RAND_VECS_2D[idx] * cellularJitter) / frequency); + centerY = ((yi + RAND_VECS_2D[idx | 1] * cellularJitter) / frequency); + } else if(newDistance < distance1) { + distance2 = distance1; + distance1 = newDistance; + } else if(newDistance < distance2) { + distance2 = newDistance; } - break; - case Manhattan: - for(int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; - - for(int yi = yr - 1; yi <= yr + 1; yi++) { - int hash = hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - double vecX = (xi - x) + RAND_VECS_2D[idx] * cellularJitter; - double vecY = (yi - y) + RAND_VECS_2D[idx | 1] * cellularJitter; - - double newDistance = fastAbs(vecX) + fastAbs(vecY); - - distance1 = fastMax(fastMin(distance1, newDistance), distance0); - if(newDistance < distance0) { - distance0 = newDistance; - closestHash = hash; - centerX = ((xi + RAND_VECS_2D[idx] * cellularJitter) / frequency); - centerY = ((yi + RAND_VECS_2D[idx | 1] * cellularJitter) / frequency); - } else if(newDistance < distance1) { - distance2 = distance1; - distance1 = newDistance; - } else if(newDistance < distance2) { - distance2 = newDistance; - } - yPrimed += PRIME_Y; - } - xPrimed += PRIME_X; - } - break; - case Hybrid: - for(int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; - - for(int yi = yr - 1; yi <= yr + 1; yi++) { - int hash = hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - double vecX = (xi - x) + RAND_VECS_2D[idx] * cellularJitter; - double vecY = (yi - y) + RAND_VECS_2D[idx | 1] * cellularJitter; - - double newDistance = (fastAbs(vecX) + fastAbs(vecY)) + (vecX * vecX + vecY * vecY); - - distance1 = fastMax(fastMin(distance1, newDistance), distance0); - if(newDistance < distance0) { - distance0 = newDistance; - closestHash = hash; - centerX = ((xi + RAND_VECS_2D[idx] * cellularJitter) / frequency); - centerY = ((yi + RAND_VECS_2D[idx | 1] * cellularJitter) / frequency); - } else if(newDistance < distance1) { - distance2 = distance1; - distance1 = newDistance; - } else if(newDistance < distance2) { - distance2 = newDistance; - } - yPrimed += PRIME_Y; - } - xPrimed += PRIME_X; - } - break; + yPrimed += PRIME_Y; + } + xPrimed += PRIME_X; } if(distanceFunction == DistanceFunction.Euclidean && returnType != ReturnType.CellValue) { @@ -351,6 +289,7 @@ public class CellularSampler extends NoiseFunction { case Distance2Mul -> distance1 * distance0 * 0.5 - 1; case Distance2Div -> distance0 / distance1 - 1; case NoiseLookup -> noiseLookup.noise(sl, centerX, centerY); + case LocalNoiseLookup -> noiseLookup.noise(sl, x / frequency - centerX, y / frequency - centerY); case Distance3 -> distance2 - 1; case Distance3Add -> (distance2 + distance0) * 0.5 - 1; case Distance3Sub -> distance2 - distance0 - 1; @@ -382,120 +321,47 @@ public class CellularSampler extends NoiseFunction { double centerY = y; double centerZ = z; - switch(distanceFunction) { - case Euclidean: - case EuclideanSq: - for(int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; + for(int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for(int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for(int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); - for(int yi = yr - 1; yi <= yr + 1; yi++) { - int zPrimed = zPrimedBase; - - for(int zi = zr - 1; zi <= zr + 1; zi++) { - int hash = hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - double vecX = (xi - x) + RAND_VECS_3D[idx] * cellularJitter; - double vecY = (yi - y) + RAND_VECS_3D[idx | 1] * cellularJitter; - double vecZ = (zi - z) + RAND_VECS_3D[idx | 2] * cellularJitter; - - double newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; - - if(newDistance < distance0) { - distance0 = newDistance; - closestHash = hash; - centerX = ((xi + RAND_VECS_3D[idx] * cellularJitter) / frequency); - centerY = ((yi + RAND_VECS_3D[idx | 1] * cellularJitter) / frequency); - centerZ = ((zi + RAND_VECS_3D[idx | 2] * cellularJitter) / frequency); - } else if(newDistance < distance1) { - distance2 = distance1; - distance1 = newDistance; - } else if(newDistance < distance2) { - distance2 = newDistance; - } - zPrimed += PRIME_Z; - } - yPrimed += PRIME_Y; - } - xPrimed += PRIME_X; - } - break; - case Manhattan: - for(int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; + double vecX = (xi - x) + RAND_VECS_3D[idx] * cellularJitter; + double vecY = (yi - y) + RAND_VECS_3D[idx | 1] * cellularJitter; + double vecZ = (zi - z) + RAND_VECS_3D[idx | 2] * cellularJitter; - for(int yi = yr - 1; yi <= yr + 1; yi++) { - int zPrimed = zPrimedBase; - - for(int zi = zr - 1; zi <= zr + 1; zi++) { - int hash = hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - double vecX = (xi - x) + RAND_VECS_3D[idx] * cellularJitter; - double vecY = (yi - y) + RAND_VECS_3D[idx | 1] * cellularJitter; - double vecZ = (zi - z) + RAND_VECS_3D[idx | 2] * cellularJitter; - - double newDistance = fastAbs(vecX) + fastAbs(vecY) + fastAbs(vecZ); - - if(newDistance < distance0) { - distance0 = newDistance; - closestHash = hash; - centerX = ((xi + RAND_VECS_3D[idx] * cellularJitter) / frequency); - centerY = ((yi + RAND_VECS_3D[idx | 1] * cellularJitter) / frequency); - centerZ = ((zi + RAND_VECS_3D[idx | 2] * cellularJitter) / frequency); - } else if(newDistance < distance1) { - distance2 = distance1; - distance1 = newDistance; - } else if(newDistance < distance2) { - distance2 = newDistance; - } - zPrimed += PRIME_Z; - } - yPrimed += PRIME_Y; - } - xPrimed += PRIME_X; - } - break; - case Hybrid: - for(int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; - - for(int yi = yr - 1; yi <= yr + 1; yi++) { - int zPrimed = zPrimedBase; - - for(int zi = zr - 1; zi <= zr + 1; zi++) { - int hash = hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - double vecX = (xi - x) + RAND_VECS_3D[idx] * cellularJitter; - double vecY = (yi - y) + RAND_VECS_3D[idx | 1] * cellularJitter; - double vecZ = (zi - z) + RAND_VECS_3D[idx | 2] * cellularJitter; - - double newDistance = (fastAbs(vecX) + fastAbs(vecY) + fastAbs(vecZ)) + - (vecX * vecX + vecY * vecY + vecZ * vecZ); - + double newDistance = 0; + switch(distanceFunction) { + case Euclidean, EuclideanSq -> newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; + case Manhattan -> newDistance = fastAbs(vecX) + fastAbs(vecY) + fastAbs(vecZ); + case Hybrid -> { + newDistance = (fastAbs(vecX) + fastAbs(vecY) + fastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); distance1 = fastMax(fastMin(distance1, newDistance), distance0); - if(newDistance < distance0) { - distance0 = newDistance; - closestHash = hash; - centerX = ((xi + RAND_VECS_3D[idx] * cellularJitter) / frequency); - centerY = ((yi + RAND_VECS_3D[idx | 1] * cellularJitter) / frequency); - centerZ = ((zi + RAND_VECS_3D[idx | 2] * cellularJitter) / frequency); - } else if(newDistance < distance1) { - distance2 = distance1; - distance1 = newDistance; - } else if(newDistance < distance2) { - distance2 = newDistance; - } - zPrimed += PRIME_Z; } - yPrimed += PRIME_Y; } - xPrimed += PRIME_X; + + if(newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + centerX = ((xi + RAND_VECS_3D[idx] * cellularJitter) / frequency); + centerY = ((yi + RAND_VECS_3D[idx | 1] * cellularJitter) / frequency); + centerZ = ((zi + RAND_VECS_3D[idx | 2] * cellularJitter) / frequency); + } else if(newDistance < distance1) { + distance2 = distance1; + distance1 = newDistance; + } else if(newDistance < distance2) { + distance2 = newDistance; + } + zPrimed += PRIME_Z; } - break; - default: - break; + yPrimed += PRIME_Y; + } + xPrimed += PRIME_X; } if(distanceFunction == DistanceFunction.Euclidean && returnType != ReturnType.CellValue) { @@ -514,6 +380,7 @@ public class CellularSampler extends NoiseFunction { case Distance2Mul -> distance1 * distance0 * 0.5 - 1; case Distance2Div -> distance0 / distance1 - 1; case NoiseLookup -> noiseLookup.noise(sl, centerX, centerY, centerZ); + case LocalNoiseLookup -> noiseLookup.noise(sl, x / frequency - centerX, y / frequency - centerY, z / frequency - centerZ); case Distance3 -> distance2 - 1; case Distance3Add -> (distance2 + distance0) * 0.5 - 1; case Distance3Sub -> distance2 - distance0 - 1; @@ -540,6 +407,7 @@ public class CellularSampler extends NoiseFunction { Distance2Mul, Distance2Div, NoiseLookup, + LocalNoiseLookup, Distance3, Distance3Add, Distance3Sub, diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/DistanceSampler.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/DistanceSampler.java new file mode 100644 index 000000000..1e083fb86 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/DistanceSampler.java @@ -0,0 +1,66 @@ +package com.dfsek.terra.addons.noise.samplers.noise; + + +public class DistanceSampler extends NoiseFunction { + + private final DistanceFunction distanceFunction; + private final double ox, oy, oz; + private final boolean normalize; + private final double radius; + + private final double distanceAtRadius; + + public DistanceSampler(DistanceFunction distanceFunction, double ox, double oy, double oz, boolean normalize, double radius) { + frequency = 1; + this.distanceFunction = distanceFunction; + this.ox = ox; + this.oy = oy; + this.oz = oz; + this.normalize = normalize; + this.radius = radius; + this.distanceAtRadius = distance2d(distanceFunction, radius, 0); // distance2d and distance3d should return the same value + } + + @Override + public double getNoiseRaw(long seed, double x, double y) { + double dx = x - ox; + double dy = y - oz; + if (normalize && (fastAbs(dx) > radius || fastAbs(dy) > radius)) return 1; + double dist = distance2d(distanceFunction, dx, dy); + if (normalize) return fastMin(((2*dist)/distanceAtRadius)-1, 1); + return dist; + } + + @Override + public double getNoiseRaw(long seed, double x, double y, double z) { + double dx = x - ox; + double dy = y - oy; + double dz = z - oz; + if(normalize && (fastAbs(dx) > radius || fastAbs(dy) > radius || fastAbs(dz) > radius)) return 1; + double dist = distance3d(distanceFunction, dx, dy, dz); + if (normalize) return fastMin(((2*dist)/distanceAtRadius)-1, 1); + return dist; + } + + private static double distance2d(DistanceFunction distanceFunction, double x, double z) { + return switch(distanceFunction) { + case Euclidean -> fastSqrt(x*x + z*z); + case EuclideanSq -> x*x + z*z; + case Manhattan -> fastAbs(x) + fastAbs(z); + }; + } + + private static double distance3d(DistanceFunction distanceFunction, double x, double y, double z) { + return switch(distanceFunction) { + case Euclidean -> fastSqrt(x*x + y*y + z*z); + case EuclideanSq -> x*x + y*y + z*z; + case Manhattan -> fastAbs(x) + fastAbs(y) + fastAbs(z); + }; + } + + public enum DistanceFunction { + Euclidean, + EuclideanSq, + Manhattan + } +} diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts new file mode 100644 index 000000000..6f5d40074 --- /dev/null +++ b/common/addons/library-image/build.gradle.kts @@ -0,0 +1,8 @@ +version = version("1.0.0") + +dependencies { + compileOnlyApi(project(":common:addons:manifest-addon-loader")) + + implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) + testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java new file mode 100644 index 000000000..fdcbc701d --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java @@ -0,0 +1,84 @@ +package com.dfsek.terra.addons.image; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.function.Supplier; + +import com.dfsek.terra.addons.image.config.ColorLoader; +import com.dfsek.terra.addons.image.config.ColorLoader.ColorString; +import com.dfsek.terra.addons.image.config.noisesampler.ChannelNoiseSamplerTemplate; +import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate; +import com.dfsek.terra.addons.image.config.image.ImageTemplate; +import com.dfsek.terra.addons.image.config.image.StitchedImageTemplate; +import com.dfsek.terra.addons.image.config.colorsampler.ConstantColorSamplerTemplate; +import com.dfsek.terra.addons.image.config.colorsampler.image.SingleImageColorSamplerTemplate; +import com.dfsek.terra.addons.image.config.colorsampler.image.TileImageColorSamplerTemplate; +import com.dfsek.terra.addons.image.config.colorsampler.mutate.RotateColorSamplerTemplate; +import com.dfsek.terra.addons.image.config.colorsampler.mutate.TranslateColorSamplerTemplate; +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.operator.DistanceTransform; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.manifest.api.AddonInitializer; +import com.dfsek.terra.api.Platform; +import com.dfsek.terra.api.addon.BaseAddon; +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; +import com.dfsek.terra.api.event.functional.FunctionalEventHandler; +import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.registry.CheckedRegistry; +import com.dfsek.terra.api.util.reflection.TypeKey; + + +public class ImageLibraryAddon implements AddonInitializer { + + public static final TypeKey>> IMAGE_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>> COLOR_PICKER_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>> NOISE_SAMPLER_TOKEN = new TypeKey<>() { + }; + @Inject + private Platform platform; + + @Inject + private BaseAddon addon; + + @Override + public void initialize() { + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPreLoadEvent.class) + .priority(10) + .then(event -> { + ConfigPack pack = event.getPack(); + CheckedRegistry>> imageRegistry = pack.getOrCreateRegistry(IMAGE_REGISTRY_KEY); + imageRegistry.register(addon.key("BITMAP"), () -> new ImageTemplate(pack.getLoader(), pack)); + imageRegistry.register(addon.key("STITCHED_BITMAP"), () -> new StitchedImageTemplate(pack.getLoader(), pack)); + }) + .then(event -> { + event.getPack() + .applyLoader(DistanceTransform.CostFunction.class, + (type, o, loader, depthTracker) -> DistanceTransform.CostFunction.valueOf((String) o)) + .applyLoader(DistanceTransform.Normalization.class, + (type, o, loader, depthTracker) -> DistanceTransform.Normalization.valueOf((String) o)) + .applyLoader(ColorString.class, new ColorLoader()); + + CheckedRegistry>> noiseRegistry = event.getPack().getOrCreateRegistry( + NOISE_SAMPLER_TOKEN); + noiseRegistry.register(addon.key("DISTANCE_TRANSFORM"), DistanceTransformNoiseSamplerTemplate::new); + noiseRegistry.register(addon.key("CHANNEL"), ChannelNoiseSamplerTemplate::new); + }) + .then(event -> { + CheckedRegistry>> colorSamplerRegistry = event.getPack().getOrCreateRegistry( + COLOR_PICKER_REGISTRY_KEY); + colorSamplerRegistry.register(addon.key("SINGLE_IMAGE"), SingleImageColorSamplerTemplate::new); + colorSamplerRegistry.register(addon.key("TILED_IMAGE"), TileImageColorSamplerTemplate::new); + colorSamplerRegistry.register(addon.key("COLOR"), ConstantColorSamplerTemplate::new); + colorSamplerRegistry.register(addon.key("ROTATE"), RotateColorSamplerTemplate::new); + colorSamplerRegistry.register(addon.key("TRANSLATE"), TranslateColorSamplerTemplate::new); + }); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/ColorSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/ColorSampler.java new file mode 100644 index 000000000..9cf226302 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/ColorSampler.java @@ -0,0 +1,12 @@ +package com.dfsek.terra.addons.image.colorsampler; + +@FunctionalInterface +public interface ColorSampler { + + /** + * @param x World x coordinate + * @param z World z coordinate + * @return Integer representing a web color + */ + int apply(int x, int z); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/SingleImageColorSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/SingleImageColorSampler.java new file mode 100644 index 000000000..420b0f62f --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/SingleImageColorSampler.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.addons.image.colorsampler.image; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.image.transform.ImageTransformation; + + +public class SingleImageColorSampler implements ColorSampler { + + private final Image image; + + private final ColorSampler fallback; + + private final ImageTransformation transformation; + + public SingleImageColorSampler(Image image, ColorSampler fallback, ImageTransformation transformation) { + this.image = image; + this.fallback = fallback; + this.transformation = transformation; + } + + @Override + public int apply(int x, int z) { + var nx = transformation.transformX(image, x); + var nz = transformation.transformZ(image, z); + if(nx < 0 || nz < 0 || nx >= image.getWidth() || nz >= image.getHeight()) return fallback.apply(x, z); + return image.getRGB(nx, nz); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/TileImageColorSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/TileImageColorSampler.java new file mode 100644 index 000000000..ec03c4941 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/TileImageColorSampler.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.image.colorsampler.image; + +import net.jafama.FastMath; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.image.transform.ImageTransformation; + + +public class TileImageColorSampler implements ColorSampler { + + private final Image image; + + private final ImageTransformation transformation; + + public TileImageColorSampler(Image image, ImageTransformation transformation) { + this.image = image; + this.transformation = transformation; + } + + @Override + public int apply(int x, int z) { + x = transformation.transformX(image, x); + z = transformation.transformZ(image, z); + return image.getRGB(FastMath.floorMod(x, image.getWidth()), FastMath.floorMod(z, image.getHeight())); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/transform/Alignment.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/transform/Alignment.java new file mode 100644 index 000000000..834428c5c --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/transform/Alignment.java @@ -0,0 +1,30 @@ +package com.dfsek.terra.addons.image.colorsampler.image.transform; + +import com.dfsek.terra.addons.image.image.Image; + + +public enum Alignment implements ImageTransformation { + + NONE() { + @Override + public int transformX(Image image, int x) { + return x; + } + + @Override + public int transformZ(Image image, int z) { + return z; + } + }, + CENTER { + @Override + public int transformX(Image image, int x) { + return x + image.getWidth() / 2; + } + + @Override + public int transformZ(Image image, int z) { + return z + image.getHeight() / 2; + } + }; +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/transform/ImageTransformation.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/transform/ImageTransformation.java new file mode 100644 index 000000000..cc5b63e7f --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/image/transform/ImageTransformation.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.addons.image.colorsampler.image.transform; + +import com.dfsek.terra.addons.image.image.Image; + + +public interface ImageTransformation { + + int transformX(Image image, int x); + + int transformZ(Image image, int z); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/mutate/RotateColorSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/mutate/RotateColorSampler.java new file mode 100644 index 000000000..46a449773 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/mutate/RotateColorSampler.java @@ -0,0 +1,61 @@ +package com.dfsek.terra.addons.image.colorsampler.mutate; + +import net.jafama.FastMath; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; + +public class RotateColorSampler implements ColorSampler { + + private final ColorSampler sampler; + + private final double radians; + + private final RotationMethod rotationMethod; + + public RotateColorSampler(ColorSampler sampler, double degrees) { + this.sampler = sampler; + + double normalizedDegrees = degrees % 360.0; + if (normalizedDegrees < 0) normalizedDegrees += 360.0; + + if (normalizedDegrees == 0.0) + rotationMethod = RotationMethod.DEG_0; + else if (normalizedDegrees == 90.0) + rotationMethod = RotationMethod.DEG_90; + else if (normalizedDegrees == 180.0) + rotationMethod = RotationMethod.DEG_180; + else if (normalizedDegrees == 270.0) + rotationMethod = RotationMethod.DEG_270; + else + rotationMethod = RotationMethod.RAD_ANY; + + this.radians = FastMath.toRadians(degrees); + } + + @Override + public int apply(int x, int z) { + int rx = switch(rotationMethod) { + case DEG_0 -> x; + case DEG_90 -> -z; + case DEG_180 -> -x; + case DEG_270 -> z; + case RAD_ANY -> (int) (x * FastMath.cos(radians) - z * FastMath.sin(radians)); + }; + int rz = switch(rotationMethod) { + case DEG_0 -> z; + case DEG_90 -> x; + case DEG_180 -> -z; + case DEG_270 -> -x; + case RAD_ANY -> (int) (z * FastMath.cos(radians) + x * FastMath.sin(radians)); + }; + return sampler.apply(rx, rz); + } + + private enum RotationMethod { + DEG_0, + DEG_90, + DEG_180, + DEG_270, + RAD_ANY, + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/mutate/TranslateColorSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/mutate/TranslateColorSampler.java new file mode 100644 index 000000000..16d3cb71c --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/colorsampler/mutate/TranslateColorSampler.java @@ -0,0 +1,21 @@ +package com.dfsek.terra.addons.image.colorsampler.mutate; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; + + +public class TranslateColorSampler implements ColorSampler { + + private final ColorSampler sampler; + private final int translateX, translateZ; + + public TranslateColorSampler(ColorSampler sampler, int translateX, int translateZ) { + this.sampler = sampler; + this.translateX = translateX; + this.translateZ = translateZ; + } + + @Override + public int apply(int x, int z) { + return sampler.apply(x - translateX, z - translateZ); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/ColorLoader.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/ColorLoader.java new file mode 100644 index 000000000..03316d87b --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/ColorLoader.java @@ -0,0 +1,92 @@ +package com.dfsek.terra.addons.image.config; + +import com.dfsek.tectonic.api.depth.DepthTracker; +import com.dfsek.tectonic.api.exception.LoadException; +import com.dfsek.tectonic.api.loader.ConfigLoader; +import com.dfsek.tectonic.api.loader.type.TypeLoader; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.AnnotatedType; + +import com.dfsek.terra.addons.image.config.ColorLoader.ColorString; +import com.dfsek.terra.addons.image.util.ColorUtil; + + +public class ColorLoader implements TypeLoader { + + @Override + public ColorString load(@NotNull AnnotatedType annotatedType, @NotNull Object o, @NotNull ConfigLoader configLoader, + DepthTracker depthTracker) throws LoadException { + return new ColorString((String) o); + } + + public static class ColorString { + + private final int argb; + + public ColorString(String string) throws IllegalArgumentException { + this.argb = parse(string); + } + + public int getColor() { + return argb; + } + + private static int parse(String string) throws IllegalArgumentException { + if (string.length() == 0) + throw new IllegalArgumentException("Empty string cannot be parsed as a valid color"); + + String[] split = string.split(","); + + if (split.length == 1) + return parseHex(string); + else if (split.length == 3) + return parseChannels("255", split[0], split[1], split[2]); + else if (split.length == 4) + return parseChannels(split[0], split[1], split[2], split[3]); + else + throw new IllegalArgumentException("Invalid channels provided, required format RED,GREEN,BLUE or ALPHA,RED,GREEN,BLUE"); + } + + private static int parseHex(String hex) throws IllegalArgumentException { + if (hex.startsWith("#")) + hex = hex.substring(1); + + int alpha = 255; + int red = 0; + int green = 0; + int blue = 0; + + try { + if(hex.length() == 8) { + alpha = Integer.parseInt(hex.substring(0, 2), 16); + hex = hex.substring(2); + } + + if(hex.length() != 6) + throw new IllegalArgumentException("Invalid color channels, required format AARRGGBB or RRGGBB"); + + red = Integer.parseInt(hex.substring(0, 2), 16); + green = Integer.parseInt(hex.substring(2, 4), 16); + blue = Integer.parseInt(hex.substring(4, 6), 16); + + return ColorUtil.argbValidated(alpha, red, green, blue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Failed to parse hex color", e); + } + } + + private static int parseChannels(String alpha, String red, String green, String blue) throws IllegalArgumentException { + try { + int a = Integer.decode(alpha); + int r = Integer.decode(red); + int g = Integer.decode(green); + int b = Integer.decode(blue); + + return ColorUtil.argbValidated(a, r, g, b); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid channel value", e); + } + } + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/ConstantColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/ConstantColorSamplerTemplate.java new file mode 100644 index 000000000..c2d940480 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/ConstantColorSamplerTemplate.java @@ -0,0 +1,19 @@ +package com.dfsek.terra.addons.image.config.colorsampler; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.config.ColorLoader.ColorString; + + +public class ConstantColorSamplerTemplate implements ObjectTemplate { + + @Value("color") + private ColorString color; + + @Override + public ColorSampler get() { + return ((x, z) -> color.getColor()); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/ImageColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/ImageColorSamplerTemplate.java new file mode 100644 index 000000000..3b9e6fe37 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/ImageColorSamplerTemplate.java @@ -0,0 +1,21 @@ +package com.dfsek.terra.addons.image.config.colorsampler.image; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.image.transform.Alignment; + + +public abstract class ImageColorSamplerTemplate implements ObjectTemplate { + + @Value("image") + protected Image image; + + @Value("align") + @Default + protected Alignment alignment = Alignment.NONE; + +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/SingleImageColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/SingleImageColorSamplerTemplate.java new file mode 100644 index 000000000..dd31a3d67 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/SingleImageColorSamplerTemplate.java @@ -0,0 +1,17 @@ +package com.dfsek.terra.addons.image.config.colorsampler.image; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.image.SingleImageColorSampler; + + +public class SingleImageColorSamplerTemplate extends ImageColorSamplerTemplate { + @Value("outside-sampler") + private ColorSampler fallback; + + @Override + public ColorSampler get() { + return new SingleImageColorSampler(image, fallback, alignment); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/TileImageColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/TileImageColorSamplerTemplate.java new file mode 100644 index 000000000..72421cb58 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/image/TileImageColorSamplerTemplate.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.addons.image.config.colorsampler.image; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.image.TileImageColorSampler; + + +public class TileImageColorSamplerTemplate extends ImageColorSamplerTemplate { + + @Override + public ColorSampler get() { + return new TileImageColorSampler(image, alignment); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/MutateColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/MutateColorSamplerTemplate.java new file mode 100644 index 000000000..532963837 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/MutateColorSamplerTemplate.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.addons.image.config.colorsampler.mutate; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; + + +public abstract class MutateColorSamplerTemplate implements ObjectTemplate { + + @Value("color-sampler") + protected ColorSampler sampler; +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/RotateColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/RotateColorSamplerTemplate.java new file mode 100644 index 000000000..662735a54 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/RotateColorSamplerTemplate.java @@ -0,0 +1,18 @@ +package com.dfsek.terra.addons.image.config.colorsampler.mutate; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.mutate.RotateColorSampler; + + +public class RotateColorSamplerTemplate extends MutateColorSamplerTemplate { + + @Value("angle") + private double degrees; + + @Override + public ColorSampler get() { + return new RotateColorSampler(sampler, degrees); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/TranslateColorSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/TranslateColorSamplerTemplate.java new file mode 100644 index 000000000..e8e18c447 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/colorsampler/mutate/TranslateColorSamplerTemplate.java @@ -0,0 +1,21 @@ +package com.dfsek.terra.addons.image.config.colorsampler.mutate; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.colorsampler.mutate.TranslateColorSampler; + + +public class TranslateColorSamplerTemplate extends MutateColorSamplerTemplate { + + @Value("x") + private int translateX; + + @Value("z") + private int translateZ; + + @Override + public ColorSampler get() { + return new TranslateColorSampler(sampler, translateX, translateZ); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ClosestColorConverterTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ClosestColorConverterTemplate.java new file mode 100644 index 000000000..61aca4b79 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ClosestColorConverterTemplate.java @@ -0,0 +1,16 @@ +package com.dfsek.terra.addons.image.config.converter; + +import com.dfsek.terra.addons.image.converter.ClosestMatchColorConverter; +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; + + +public abstract class ClosestColorConverterTemplate implements ColorConverterTemplate { + + protected abstract ColorMapping getMapping(); + + @Override + public ColorConverter get() { + return new ClosestMatchColorConverter(getMapping().get()); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ColorConverterTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ColorConverterTemplate.java new file mode 100644 index 000000000..c0cf7a7d5 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ColorConverterTemplate.java @@ -0,0 +1,9 @@ +package com.dfsek.terra.addons.image.config.converter; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.converter.ColorConverter; + + +public interface ColorConverterTemplate extends ObjectTemplate> { +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ExactColorConverterTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ExactColorConverterTemplate.java new file mode 100644 index 000000000..17d2fd37d --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ExactColorConverterTemplate.java @@ -0,0 +1,20 @@ +package com.dfsek.terra.addons.image.config.converter; + +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.converter.ExactColorConverter; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; + + +public abstract class ExactColorConverterTemplate implements ColorConverterTemplate { + + protected abstract ColorMapping getMapping(); + + protected abstract T getFallback(); + + protected abstract boolean ignoreAlpha(); + + @Override + public ColorConverter get() { + return new ExactColorConverter(getMapping().get(), getFallback(), ignoreAlpha()); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/ImageCache.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/ImageCache.java new file mode 100644 index 000000000..5f96ea390 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/ImageCache.java @@ -0,0 +1,46 @@ +package com.dfsek.terra.addons.image.config.image; + +import javax.imageio.ImageIO; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import com.dfsek.terra.addons.image.image.BufferedImageWrapper; +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.config.Loader; +import com.dfsek.terra.api.properties.Properties; + + +/* + * Cache prevents configs from loading the same image multiple times into memory + */ +record ImageCache(ConcurrentHashMap map) implements Properties { + public static Image load(String path, ConfigPack pack, Loader files) throws IOException { + ImageCache cache; + if(!pack.getContext().has(ImageCache.class)) { + cache = new ImageCache(new ConcurrentHashMap<>()); + pack.getContext().put(cache); + } else { + cache = pack.getContext().get(ImageCache.class); + } + + if(cache.map.containsKey(path)) { + return cache.map.get(path); + } else { + try { + BufferedImageWrapper image = new BufferedImageWrapper(ImageIO.read(files.get(path))); + cache.map.put(path, image); + return image; + } catch(IllegalArgumentException e) { + throw new IllegalArgumentException("Unable to load image (image might be too large?)", e); + } catch(IOException e) { + if(e instanceof FileNotFoundException) { + // Rethrow using nicer message + throw new IOException("Unable to load image: No such file or directory: " + path, e); + } + throw new IOException("Unable to load image", e); + } + } + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/ImageTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/ImageTemplate.java new file mode 100644 index 000000000..7590ca1b4 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/ImageTemplate.java @@ -0,0 +1,35 @@ +package com.dfsek.terra.addons.image.config.image; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.io.IOException; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.config.Loader; + + +public class ImageTemplate implements ObjectTemplate { + + @Value("path") + private String path; + + private final Loader files; + + private final ConfigPack pack; + + public ImageTemplate(Loader files, ConfigPack pack) { + this.files = files; + this.pack = pack; + } + + @Override + public Image get() { + try { + return ImageCache.load(path, pack, files); + } catch(IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/StitchedImageTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/StitchedImageTemplate.java new file mode 100644 index 000000000..509bbeb22 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/image/StitchedImageTemplate.java @@ -0,0 +1,76 @@ +package com.dfsek.terra.addons.image.config.image; + +import com.dfsek.tectonic.api.config.template.ValidatedConfigTemplate; +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import com.dfsek.tectonic.api.exception.ValidationException; + +import java.io.IOException; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.image.StitchedImage; +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.config.Loader; + + +public class StitchedImageTemplate implements ObjectTemplate, ValidatedConfigTemplate { + + @Value("path-format") + private String path; + + @Value("rows") + private int rows; + + @Value("columns") + private int cols; + + @Value("zero-indexed") + @Default + private boolean zeroIndexed = false; + + private final Loader files; + + private final ConfigPack pack; + + public StitchedImageTemplate(Loader files, ConfigPack pack) { + this.files = files; + this.pack = pack; + } + + @Override + public Image get() { + Image[][] grid = new Image[rows][cols]; + for(int i = 0; i < rows; i++) { + for(int j = 0; j < cols; j++) { + try { + grid[i][j] = ImageCache.load(getFormattedPath(i, j), pack, files); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + } + return new StitchedImage(grid, zeroIndexed); + } + + private String getFormattedPath(int row, int column) { + if (!zeroIndexed) { + row++; + column++; + } + return path.replaceFirst("\\{row}", String.valueOf(row)).replaceFirst("\\{column}", String.valueOf(column)); + } + + @Override + public boolean validate() throws ValidationException { + if(!path.contains("{row}")) + throw new ValidationException("Path format does not contain sequence '{row}'"); + if(!path.contains("{column}")) + throw new ValidationException("Path format does not contain sequence '{column}'"); + if(rows < 1) + throw new ValidationException("Must have at least one row"); + if(cols < 1) + throw new ValidationException("Must have at least one column"); + return true; + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/ChannelNoiseSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/ChannelNoiseSamplerTemplate.java new file mode 100644 index 000000000..4aac073d4 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/ChannelNoiseSamplerTemplate.java @@ -0,0 +1,41 @@ +package com.dfsek.terra.addons.image.config.noisesampler; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.noisesampler.ChannelNoiseSampler; +import com.dfsek.terra.addons.image.util.ColorUtil.Channel; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class ChannelNoiseSamplerTemplate implements ObjectTemplate { + + @Value("color-sampler") + private ColorSampler colorSampler; + + @Value("channel") + private Channel channel; + + /* + * If the channel should be normalized to range [-1, 1] or not + */ + @Value("normalize") + @Default + private boolean normalize = true; + + /* + * Whether to multiply color channels by the alpha channel or not. If users + * are expecting pixel transparency to reduce the output value then this should + * be set to true. + */ + @Value("premultiply") + @Default + private boolean premultiply = false; + + @Override + public NoiseSampler get() { + return new ChannelNoiseSampler(colorSampler, channel, normalize, premultiply); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/DistanceTransformNoiseSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/DistanceTransformNoiseSamplerTemplate.java new file mode 100644 index 000000000..32c523d3c --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/DistanceTransformNoiseSamplerTemplate.java @@ -0,0 +1,74 @@ +package com.dfsek.terra.addons.image.config.noisesampler; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.operator.DistanceTransform; +import com.dfsek.terra.addons.image.operator.DistanceTransform.CostFunction; +import com.dfsek.terra.addons.image.operator.DistanceTransform.Normalization; +import com.dfsek.terra.addons.image.util.ColorUtil.Channel; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class DistanceTransformNoiseSamplerTemplate implements ObjectTemplate { + + @Value("image") + private Image image; + + /** + * The threshold value applied to the channel specified in the 'channel' parameter that splits + * the image into a binary image. This parameter is only used for cost functions that utilize + * a binary image. + */ + @Value("threshold") + @Default + private int threshold = 127; + + /** + * If set to true, distances calculated will be clamped to stay above the largest + * distance calculated on the edges of the image. This ensures output values do not + * appear to be cut off at the image boundaries. It is recommended to leave padding + * around the image if this is in use, such that larger evaluated distances do not + * get cut out by smaller evaluated distances close to borders. Doing so will yield + * better results. + */ + @Value("clamp-to-max-edge") + @Default + private boolean clampToEdge = false; + + /** + * The target channel to run distance calculations on. + */ + @Value("channel") + @Default + private Channel channel = Channel.GRAYSCALE; + + /** + * The method of image processing applied to the specified image prior to calculating + * distances. + */ + @Value("cost-function") + @Default + private CostFunction costFunction = CostFunction.Channel; + + /** + * Inverts the resulting binary image that may be used as a cost function. + */ + @Value("invert-threshold") + @Default + private boolean invertThreshold = false; + + /** + * How the final distance calculation should be redistributed. + */ + @Value("normalization") + @Default + private Normalization normalization = Normalization.None; + + @Override + public NoiseSampler get() { + return new DistanceTransform.Noise(new DistanceTransform(image, channel, threshold, clampToEdge, costFunction, invertThreshold), normalization); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ClosestMatchColorConverter.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ClosestMatchColorConverter.java new file mode 100644 index 000000000..b2d44f94d --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ClosestMatchColorConverter.java @@ -0,0 +1,40 @@ +package com.dfsek.terra.addons.image.converter; + +import java.util.Map; + +import com.dfsek.terra.addons.image.util.ColorUtil; + +public class ClosestMatchColorConverter implements ColorConverter { + + private final Map map; + + private final Integer[] colors; + + public ClosestMatchColorConverter(Map map) { + this.map = map; + this.colors = map.keySet().toArray(new Integer[0]); + } + + @Override + public T apply(int color) { + int closest = 0; + int smallestDistance = Integer.MAX_VALUE; + for(int compare : colors) { + if(color == compare) { + closest = compare; + break; + } + int distance = ColorUtil.distance(color, compare); + if(distance < smallestDistance) { + smallestDistance = distance; + closest = compare; + } + } + return map.get(closest); + } + + @Override + public Iterable getEntries() { + return map.values(); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ColorConverter.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ColorConverter.java new file mode 100644 index 000000000..5f76291d8 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ColorConverter.java @@ -0,0 +1,8 @@ +package com.dfsek.terra.addons.image.converter; + +public interface ColorConverter { + + T apply(int color); + + Iterable getEntries(); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ExactColorConverter.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ExactColorConverter.java new file mode 100644 index 000000000..8f6f55d4c --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ExactColorConverter.java @@ -0,0 +1,42 @@ +package com.dfsek.terra.addons.image.converter; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.dfsek.terra.addons.image.util.ColorUtil; +import com.dfsek.terra.addons.image.util.MapUtil; + + +public class ExactColorConverter implements ColorConverter { + private final Map map; + + private final T fallback; + + private final boolean ignoreAlpha; + + public ExactColorConverter(Map map, T fallback, boolean ignoreAlpha) { + if (ignoreAlpha) { + map = MapUtil.mapKeys(map, ColorUtil::zeroAlpha); + } + this.map = map; + this.fallback = fallback; + this.ignoreAlpha = ignoreAlpha; + } + + @Override + public T apply(int color) { + if (ignoreAlpha) { + color = ColorUtil.zeroAlpha(color); + } + T lookup = map.get(color); + return lookup != null ? lookup : fallback; + } + + @Override + public Iterable getEntries() { + Set entries = new HashSet<>(map.values()); + entries.add(fallback); + return entries; + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/BiomeDefinedColorMapping.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/BiomeDefinedColorMapping.java new file mode 100644 index 000000000..469596577 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/BiomeDefinedColorMapping.java @@ -0,0 +1,38 @@ +package com.dfsek.terra.addons.image.converter.mapping; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.dfsek.terra.api.registry.Registry; +import com.dfsek.terra.api.world.biome.Biome; + + +public class BiomeDefinedColorMapping implements ColorMapping { + + Registry biomeRegistry; + + Function converter; + + public BiomeDefinedColorMapping(Registry biomeRegistry, Function converter) { + this.biomeRegistry = biomeRegistry; + this.converter = converter; + } + + @Override + public Map get() { + Map colorMap = new HashSet<>(biomeRegistry.entries()).stream().collect(Collectors.toMap(b -> b, Biome::getColor)); + Map output = new HashMap<>(); + colorMap.forEach(((biome, color) -> { + if(!output.containsKey(color)) { + output.put(color, biome); + } else { + throw new IllegalArgumentException(String.format("Biome %s has same color as %s: %x", biome.getID(), output.get(color).getID(), color)); + } + })); + return output.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> converter.apply(e.getValue()))); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/ColorMapping.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/ColorMapping.java new file mode 100644 index 000000000..6fc586a84 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/ColorMapping.java @@ -0,0 +1,8 @@ +package com.dfsek.terra.addons.image.converter.mapping; + +import java.util.Map; +import java.util.function.Supplier; + + +public interface ColorMapping extends Supplier> { +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/BufferedImageWrapper.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/BufferedImageWrapper.java new file mode 100644 index 000000000..16df31a74 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/BufferedImageWrapper.java @@ -0,0 +1,28 @@ +package com.dfsek.terra.addons.image.image; + +import java.awt.image.BufferedImage; + + +public class BufferedImageWrapper implements Image { + + private final BufferedImage image; + + public BufferedImageWrapper(BufferedImage image) { + this.image = image; + } + + @Override + public int getRGB(int x, int y) { + return image.getRGB(x, y); + } + + @Override + public int getWidth() { + return image.getWidth(); + } + + @Override + public int getHeight() { + return image.getHeight(); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/Image.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/Image.java new file mode 100644 index 000000000..f8eb83793 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/Image.java @@ -0,0 +1,9 @@ +package com.dfsek.terra.addons.image.image; + +public interface Image { + int getRGB(int x, int y); + + int getWidth(); + + int getHeight(); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/StitchedImage.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/StitchedImage.java new file mode 100644 index 000000000..c2d131b46 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/image/StitchedImage.java @@ -0,0 +1,74 @@ +package com.dfsek.terra.addons.image.image; + +public class StitchedImage implements Image { + + private final Image[][] images; + + private final int[] rowOffsets, columnOffsets; + + private final int width, height; + + public StitchedImage(Image[][] images, boolean zeroIndexed) throws IllegalArgumentException { + int width = 0; + int height = 0; + int rows = images.length; + int columns = images[0].length; + this.rowOffsets = new int[rows]; + this.columnOffsets = new int[columns]; + for(int i = 0; i < rows; i++) { + int rowHeight = images[i][0].getHeight(); + rowOffsets[i] = height; + height += rowHeight; + for(int j = 1; j < columns; j++) { + if(images[i][j].getHeight() != rowHeight) + throw new IllegalArgumentException("Image heights in row " + (i + (zeroIndexed ? 0 : 1)) + " do not match"); + } + } + for(int i = 0; i < columns; i++) { + int columnWidth = images[0][i].getWidth(); + columnOffsets[i] = width; + width += columnWidth; + for(int j = 1; j < rows; j++) { + if(images[i][j].getWidth() != columnWidth) + throw new IllegalArgumentException("Image widths in column " + (i + (zeroIndexed ? 0 : 1)) + " do not match"); + } + } + + this.width = width; + this.height = height; + this.images = images; + } + + private int getColumn(int x) { + for(int i = columnOffsets.length-1; i > 0; i--) { + if(x >= columnOffsets[i]) + return i; + } + return 0; + } + + private int getRow(int y) { + for(int i = rowOffsets.length-1; i > 0; i--) { + if(y >= rowOffsets[i]) + return i; + } + return 0; + } + + @Override + public int getRGB(int x, int y) { + int row = getRow(y); + int column = getColumn(x); + return images[row][column].getRGB(x-columnOffsets[column], y-rowOffsets[row]); + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/ChannelNoiseSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/ChannelNoiseSampler.java new file mode 100644 index 000000000..52d977591 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/ChannelNoiseSampler.java @@ -0,0 +1,40 @@ +package com.dfsek.terra.addons.image.noisesampler; + +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.util.ColorUtil; +import com.dfsek.terra.addons.image.util.ColorUtil.Channel; +import com.dfsek.terra.api.noise.NoiseSampler; + +import static com.dfsek.terra.addons.image.util.MathUtil.lerp; + + +public class ChannelNoiseSampler implements NoiseSampler { + + private final ColorSampler colorSampler; + + private final Channel channel; + + private final boolean normalize; + + private final boolean premultiply; + + public ChannelNoiseSampler(ColorSampler colorSampler, Channel channel, boolean normalize, boolean premultiply) { + this.colorSampler = colorSampler; + this.channel = channel; + this.normalize = normalize; + this.premultiply = premultiply; + } + + @Override + public double noise(long seed, double x, double y) { + int sample = colorSampler.apply((int) x, (int) y); + int premultiplied = premultiply ? ColorUtil.premultiply(sample) : sample; + double channelValue = channel.from(premultiplied); + return normalize ? lerp(channelValue, 0, -1, 255, 1) : channelValue; + } + + @Override + public double noise(long seed, double x, double y, double z) { + return noise(seed, x, z); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/operator/DistanceTransform.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/operator/DistanceTransform.java new file mode 100644 index 000000000..626380720 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/operator/DistanceTransform.java @@ -0,0 +1,242 @@ +package com.dfsek.terra.addons.image.operator; + +import net.jafama.FastMath; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.util.ColorUtil; +import com.dfsek.terra.addons.image.util.ColorUtil.Channel; +import com.dfsek.terra.api.noise.NoiseSampler; + +import static com.dfsek.terra.addons.image.util.MathUtil.lerp; + + +/** + * Computes a 2D distance transform of a given image and stores the result in a 2D array of distances. + * Implementation based on the algorithm described in the paper + * Distance Transforms of Sampled Functions + * by Pedro F. Felzenszwalb and Daniel P. Huttenlocher. + */ +public class DistanceTransform { + + private final double[][] distances; + + /** + * Size bounds matching the provided image. + */ + private final int width, height; + + /** + * Min and max distances of the distance computation. These may change after {@link #normalize(Normalization)} calls. + */ + private double minDistance, maxDistance; + + private static final double MAX_DISTANCE_CAP = 10_000_000; // Arbitrarily large value, doubtful someone would + // ever use an image large enough to exceed this. + public DistanceTransform(Image image, Channel channel, int threshold, boolean clampToMaxEdgeDistance, CostFunction costFunction, boolean invertThreshold) { + // Construct binary image based on threshold value + boolean[][] binaryImage = new boolean[image.getWidth()][image.getHeight()]; + for(int x = 0; x < image.getWidth(); x++) { + for(int y = 0; y < image.getHeight(); y++) { + binaryImage[x][y] = ColorUtil.getChannel(image.getRGB(x, y), channel) > threshold ^ invertThreshold; + } + } + + // Get edges of binary image + boolean[][] binaryImageEdge = new boolean[image.getWidth()][image.getHeight()]; + for(int x = 0; x < image.getWidth(); x++) { + for(int y = 0; y < image.getHeight(); y++) { + if(!binaryImage[x][y]) + binaryImageEdge[x][y] = false; + else + // If cell borders any false cell + binaryImageEdge[x][y] = x > 0 && !binaryImage[x-1][y] || + y > 0 && !binaryImage[x][y-1] || + x < image.getWidth ()-1 && !binaryImage[x+1][y] || + y < image.getHeight()-1 && !binaryImage[x][y+1]; + } + } + + double[][] function = new double[image.getWidth()][image.getHeight()]; + for(int x = 0; x < image.getWidth(); x++) { + for(int y = 0; y < image.getHeight(); y++) { + function[x][y] = switch (costFunction) { + case Channel -> ColorUtil.getChannel(image.getRGB(x, y), channel); + case Threshold -> binaryImage[x][y] ? MAX_DISTANCE_CAP : 0; + case ThresholdEdge, ThresholdEdgeSigned -> binaryImageEdge[x][y] ? 0 : MAX_DISTANCE_CAP; + }; + } + } + + distances = calculateDistance2D(function); + + if(costFunction == CostFunction.ThresholdEdgeSigned) { + for(int x = 0; x < image.getWidth(); x++) { + for(int y = 0; y < image.getHeight(); y++) { + distances[x][y] *= binaryImage[x][y] ? 1 : -1; + } + } + } + + if(clampToMaxEdgeDistance) { + // Find largest value on the edge of the image + double max = Double.NEGATIVE_INFINITY; + for(int x = 0; x < image.getWidth(); x++) { + max = Math.max(max, distances[x][0]); + max = Math.max(max, distances[x][image.getHeight()-1]); + } + for(int y = 0; y < image.getHeight(); y++) { + max = Math.max(max, distances[0][y]); + max = Math.max(max, distances[image.getWidth()-1][y]); + } + // Clamp to that largest value + for(int x = 0; x < image.getWidth(); x++) { + for(int y = 0; y < image.getHeight(); y++) { + distances[x][y] = Math.max(max, distances[x][y]); + } + } + } + + this.width = image.getWidth(); + this.height = image.getHeight(); + + setOutputRange(); + } + + private double[][] calculateDistance2D(double[][] f) { + double[][] d = new double[f.length][f[0].length]; + // Distance pass for each column + for(int x = 0; x < f.length; x++) { + d[x] = calculateDistance1D(f[x]); + } + // Distance pass for each row + double[] row = new double[f.length]; + for(int y = 0; y < f[0].length; y++) { + for(int x = 0; x < f[0].length; x++) + row[x] = d[x][y]; + row = calculateDistance1D(row); + for(int x = 0; x < f[0].length; x++) { + d[x][y] = FastMath.sqrt(row[x]); + } + } + return d; + } + + private double[] calculateDistance1D(double[] f) { + double[] d = new double[f.length]; + int[] v = new int[f.length]; + double[] z = new double[f.length+1]; + int k = 0; + v[0] = 0; + z[0] = Integer.MIN_VALUE; + z[1] = Integer.MAX_VALUE; + for(int q = 1; q <= f.length-1; q++) { + double s = ((f[q]+FastMath.pow2(q))-(f[v[k]]+FastMath.pow2(v[k])))/(2*q-2*v[k]); + while (s <= z[k]) { + k--; + s = ((f[q]+FastMath.pow2(q))-(f[v[k]]+FastMath.pow2(v[k])))/(2*q-2*v[k]); + } + k++; + v[k] = q; + z[k] = s; + z[k+1] = Integer.MAX_VALUE; + } + + k = 0; + for(int q = 0; q <= f.length-1; q++) { + while(z[k+1] < q) + k++; + d[q] = FastMath.pow2(q-v[k]) + f[v[k]]; + } + return d; + } + + /** + * Redistributes the stored distance computation according to the provided {@link Normalization} method. + */ + private void normalize(Normalization normalization) { + for(int x = 0; x < width; x++) { + for(int y = 0; y < height; y++) { + double d = distances[x][y]; + distances[x][y] = switch(normalization) { + case None -> distances[x][y]; + case Linear -> lerp(d, minDistance, -1, maxDistance, 1); + case SmoothPreserveZero -> { + if(minDistance > 0 || maxDistance < 0) { + // Can't preserve zero if it is not contained in range so just lerp + yield lerp(distances[x][y], minDistance, -1, maxDistance, 1); + } else { + if(d > 0) { + yield FastMath.pow2(d/maxDistance); + } else if(d < 0) { + yield -FastMath.pow2(d/minDistance); + } else { + yield 0; + } + } + } + }; + } + } + setOutputRange(); + } + + private void setOutputRange() { + double minDistance = Double.POSITIVE_INFINITY; + double maxDistance = Double.NEGATIVE_INFINITY; + for(int x = 0; x < width; x++) { + for(int y = 0; y < height; y++) { + minDistance = Math.min(minDistance, distances[x][y]); + maxDistance = Math.max(maxDistance, distances[x][y]); + } + } + this.minDistance = minDistance; + this.maxDistance = maxDistance; + } + + public enum CostFunction { + Channel, + Threshold, + ThresholdEdge, + ThresholdEdgeSigned, + } + + public enum Normalization { + /** + * Return the raw calculated distances. + */ + None, + + /** + * Redistribute the output values to fit in the range [-1, 1] + */ + Linear, + + /** + * Redistributes smoothly to the range [-1, 1], such that areas where distance = 0 stay 0. + * This is only really applicable to signed distance calculations, and will fall back to linear + * redistribution if the input range does not contain both positive and negative values. + */ + SmoothPreserveZero, + } + + public static class Noise implements NoiseSampler { + + private final DistanceTransform transform; + + public Noise(DistanceTransform transform, Normalization normalization) { + this.transform = transform; + transform.normalize(normalization); + } + + @Override + public double noise(long seed, double x, double y) { + if(x<0 || y<0 || x>=transform.width || y>=transform.height) return transform.minDistance; + return transform.distances[FastMath.floorToInt(x)][FastMath.floorToInt(y)]; + } + + @Override + public double noise(long seed, double x, double y, double z) { + return noise(seed, x, z); + } + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/ColorUtil.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/ColorUtil.java new file mode 100644 index 000000000..0c52ea5a6 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/ColorUtil.java @@ -0,0 +1,322 @@ +package com.dfsek.terra.addons.image.util; + +import net.jafama.FastMath; + + +/** + * Utility class for manipulating 8 bit ARGB colors + */ +public class ColorUtil { + + private ColorUtil() {} + + public static int distance(int a, int b) { + return FastMath.abs(getRed(a) - getRed(b)) + + FastMath.abs(getGreen(a) - getGreen(b)) + + FastMath.abs(getBlue(a) - getBlue(b)); + } + + /** + * Returns the red channel value of a given ARGB color value. + * + * @param argb the ARGB color value to extract the red channel value from + * @return the red channel value of the given ARGB color value, in the range 0-255 + */ + public static int getRed(int argb) { + return argb >> 16 & 255; + } + + /** + * Returns the green channel value of a given ARGB color value. + * + * @param argb the ARGB color value to extract the green channel value from + * @return the green channel value of the given ARGB color value, in the range 0-255 + */ + public static int getGreen(int argb) { + return argb >> 8 & 255; + } + + /** + * Returns the blue channel value of a given ARGB color value. + * + * @param argb the ARGB color value to extract the blue channel value from + * @return the blue channel value of the given ARGB color value, in the range 0-255 + */ + public static int getBlue(int argb) { + return argb & 255; + } + + /** + * Returns the alpha channel value of a given ARGB color value. + * + * @param argb the ARGB color value to extract the blue channel value from + * @return the alpha channel value of the given ARGB color value, in the range 0-255 + */ + public static int getAlpha(int argb) { + return argb >> 24 & 255; + } + + /** + * Returns the grayscale value of a given ARGB color value. + * + * @param argb the ARGB color value to convert to grayscale + * @return the grayscale value of the given ARGB color value, in the range 0-255 + */ + public static int getGrayscale(int argb) { + return (getRed(argb) + getGreen(argb) + getBlue(argb)) / 3; + } + + /** + * Returns the value of the specified channel for a given ARGB color value. + * + * @param argb the ARGB color value to extract the channel value from + * @param channel the channel to extract the value from + * @return the value of the specified channel for the given ARGB color value, in the range 0-255 + */ + public static int getChannel(int argb, Channel channel) { + return channel.from(argb); + } + + /** + * Sets the red channel value of a given ARGB color value to zero. + * + * @param argb the ARGB color value to zero the red channel of + * @return the resulting ARGB color value with the red channel set to zero + */ + public static int zeroRed(int argb) { + return argb & ~0x00FF0000; + } + + /** + * Sets the green channel value of a given ARGB color value to zero. + * + * @param argb the ARGB color value to zero the green channel of + * @return the resulting ARGB color value with the green channel set to zero + */ + public static int zeroGreen(int argb) { + return argb & ~0x0000FF00; + } + + /** + * Sets the blue channel value of a given ARGB color value to zero. + * + * @param argb the ARGB color value to zero the blue channel of + * @return the resulting ARGB color value with the blue channel set to zero + */ + public static int zeroBlue(int argb) { + return argb & ~0x000000FF; + } + + /** + * Sets the alpha channel value of a given ARGB color value to zero. + * This is the same as setting the color to fully transparent. + * + * @param argb the ARGB color value to zero the alpha channel of + * @return the resulting ARGB color value with the alpha channel set to zero + */ + public static int zeroAlpha(int argb) { + return argb & ~0xFF000000; + } + + /** + * Sets the color channels of a given ARGB color value to zero. + * This is the same as setting the color to black, while preserving the alpha. + * + * @param argb the ARGB color value to zero the color channel of + * @return the resulting ARGB color value with the color channels set to zero + */ + public static int zeroGrayscale(int argb) { + return argb & ~0x00FFFFFF; + } + + /** + * Sets the specified channel value of a given ARGB color value to zero. + * + * @param argb the ARGB color value to zero the specified channel of + * @param channel the channel to zero the value of + * @return the resulting ARGB color value with the specified channel value set to zero + */ + public static int zeroChannel(int argb, Channel channel) { + return channel.zero(argb); + } + + /** + * Multiply the RGB channels of a given ARGB color value by its alpha channel value. + * + * @param argb the ARGB color value to premultiply the RGB channels of + * @return the resulting premultiplied ARGB color value + */ + public static int premultiply(int argb) { + int alpha = getAlpha(argb); + int red = (getRed(argb) * alpha + 127) / 255; + int green = (getGreen(argb) * alpha + 127) / 255; + int blue = (getBlue(argb) * alpha + 127) / 255; + return argb(alpha, red, green, blue); + } + + /** + * Returns an ARGB color value with the specified values for alpha, red, green, and blue channels. + * + * @param alpha the alpha value, between 0 and 255, to set in the ARGB color value + * @param red the red value, between 0 and 255, to set in the ARGB color value + * @param green the green value, between 0 and 255, to set in the ARGB color value + * @param blue the blue value, between 0 and 255, to set in the ARGB color value + * @return the resulting ARGB color value with the specified values for alpha, red, green, and blue channels + */ + public static int argb(int alpha, int red, int green, int blue) { + return argbAlpha(alpha) | argbRed(red) | argbGreen(green) | argbBlue(blue); + } + + /** + * Returns an ARGB color value with the specified values for alpha, red, green, and blue channels, + * after validating that each channel value is in the range 0-255. + * + * @param alpha the alpha value, between 0 and 255, to set in the ARGB color value + * @param red the red value, between 0 and 255, to set in the ARGB color value + * @param green the green value, between 0 and 255, to set in the ARGB color value + * @param blue the blue value, between 0 and 255, to set in the ARGB color value + * @return the resulting ARGB color value with the specified values for alpha, red, green, and blue channels + * @throws IllegalArgumentException if any channel value is outside the range 0-255 + */ + public static int argbValidated(int alpha, int red, int green, int blue) throws IllegalArgumentException { + if (alpha < 0 || alpha > 255 || + red < 0 || red > 255 || + green < 0 || green > 255 || + blue < 0 || blue > 255 + ) throw new IllegalArgumentException("Channel values must be in range 0-255"); + return argb(alpha, red, green, blue); + } + + /** + * Returns the ARGB color value with the specified alpha channel value and zero + * for the red, green, and blue channels. + * + * @param alpha the alpha channel value to set in the ARGB color value + * @return the resulting ARGB color value + */ + public static int argbAlpha(int alpha) { return alpha << 24; } + + /** + * Returns the ARGB color value with the specified red channel value and zero + * for the alpha, green, and blue channels. + * + * @param red the red channel value to set in the ARGB color value + * @return the resulting ARGB color value + */ + public static int argbRed(int red) { return red << 16; } + + /** + * Returns the ARGB color value with the specified red channel value and zero + * for the alpha, red, and blue channels. + * + * @param green the green channel value to set in the ARGB color value + * @return the resulting ARGB color value + */ + public static int argbGreen(int green) { return green << 8; } + + /** + * Returns the ARGB color value with the specified blue channel value and zero + * for the alpha, red, and green channels. + * + * @param blue the blue channel value to set in the ARGB color value + * @return the resulting ARGB color value + */ + public static int argbBlue(int blue) { return blue; } + + /** + * Returns an ARGB color value with the specified grayscale value for all four channels. + * + * @param value the grayscale value to set in all four channels of the ARGB color value + * @return the resulting ARGB color value with the specified grayscale value for all four channels + */ + public static int argbGrayscale(int value) { return argb(value, value, value, value); } + + public enum Channel { + RED { + @Override + public int from(int argb) { + return getRed(argb); + } + + @Override + public int zero(int argb) { + return zeroRed(argb); + } + + @Override + public int argb(int value) { + return argbRed(value); + } + }, + GREEN { + @Override + public int from(int argb) { + return getGreen(argb); + } + + @Override + public int zero(int argb) { + return zeroGreen(argb); + } + + @Override + public int argb(int value) { + return argbGreen(value); + } + }, + BLUE { + @Override + public int from(int argb) { + return getBlue(argb); + } + + @Override + public int zero(int argb) { + return zeroBlue(argb); + } + + @Override + public int argb(int value) { + return argbBlue(value); + } + }, + GRAYSCALE { + @Override + public int from(int argb) { + return getGrayscale(argb); + } + + @Override + public int zero(int argb) { + return zeroGrayscale(argb); + } + + @Override + public int argb(int value) { + return argbAlpha(value); + } + }, + ALPHA { + @Override + public int from(int argb) { + return getAlpha(argb); + } + + @Override + public int zero(int argb) { + return zeroAlpha(argb); + } + + @Override + public int argb(int value) { + return argbAlpha(value); + } + }; + + public abstract int from(int argb); + + public abstract int zero(int argb); + + public abstract int argb(int value); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MapUtil.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MapUtil.java new file mode 100644 index 000000000..7fa1040ce --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MapUtil.java @@ -0,0 +1,25 @@ +package com.dfsek.terra.addons.image.util; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; + + +public class MapUtil { + + private MapUtil() {} + + /** + * Utility method for applying transformations on a map's keys. + */ + public static Map mapKeys(Map map, Function mappingFunction) { + return map + .entrySet() + .stream() + .collect(Collectors.toMap( + e -> mappingFunction.apply(e.getKey()), + Entry::getValue + )); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MathUtil.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MathUtil.java new file mode 100644 index 000000000..40e52e5d1 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MathUtil.java @@ -0,0 +1,9 @@ +package com.dfsek.terra.addons.image.util; + +public class MathUtil { + private MathUtil() {} + + public static double lerp(double x, double x1, double y1, double x2, double y2) { + return (((y1-y2)*(x-x1))/(x1-x2))+y1; + } +} diff --git a/common/addons/library-image/src/main/resources/terra.addon.yml b/common/addons/library-image/src/main/resources/terra.addon.yml new file mode 100644 index 000000000..c664c3df4 --- /dev/null +++ b/common/addons/library-image/src/main/resources/terra.addon.yml @@ -0,0 +1,12 @@ +schema-version: 1 +contributors: + - Terra contributors +id: library-image +version: @VERSION@ +entrypoints: + - "com.dfsek.terra.addons.image.ImageLibraryAddon" +website: + issues: https://github.com/PolyhedralDev/Terra/issues + source: https://github.com/PolyhedralDev/Terra + docs: https://terra.polydev.org +license: MIT License diff --git a/common/addons/pipeline-image/LICENSE b/common/addons/pipeline-image/LICENSE new file mode 100644 index 000000000..22b8e06e5 --- /dev/null +++ b/common/addons/pipeline-image/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2023 Polyhedral Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/common/addons/pipeline-image/build.gradle.kts b/common/addons/pipeline-image/build.gradle.kts new file mode 100644 index 000000000..1befb4b44 --- /dev/null +++ b/common/addons/pipeline-image/build.gradle.kts @@ -0,0 +1,7 @@ +version = version("1.0.0") + +dependencies { + compileOnlyApi(project(":common:addons:manifest-addon-loader")) + compileOnlyApi(project(":common:addons:biome-provider-pipeline-v2")) + compileOnlyApi(project(":common:addons:library-image")) +} diff --git a/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/ImageSource.java b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/ImageSource.java new file mode 100644 index 000000000..857e0de34 --- /dev/null +++ b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/ImageSource.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.addons.biome.pipeline.image; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.converter.ColorConverter; + + +public class ImageSource implements Source { + + private final ColorSampler colorSampler; + + private final ColorConverter colorConverter; + + public ImageSource(ColorSampler colorSampler, ColorConverter colorConverter) { + this.colorSampler = colorSampler; + this.colorConverter = colorConverter; + } + + @Override + public PipelineBiome get(long seed, int x, int z) { + return colorConverter.apply(colorSampler.apply(x, z)); + } + + @Override + public Iterable getBiomes() { + return colorConverter.getEntries(); + } +} diff --git a/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/PipelineImageAddon.java b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/PipelineImageAddon.java new file mode 100644 index 000000000..eb62a79b2 --- /dev/null +++ b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/PipelineImageAddon.java @@ -0,0 +1,68 @@ +package com.dfsek.terra.addons.biome.pipeline.image; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.function.Supplier; + +import com.dfsek.terra.addons.biome.pipeline.image.config.ImageSourceTemplate; +import com.dfsek.terra.addons.biome.pipeline.image.config.converter.ClosestPipelineBiomeColorConverterTemplate; +import com.dfsek.terra.addons.biome.pipeline.image.config.converter.ExactPipelineBiomeColorConverterTemplate; +import com.dfsek.terra.addons.biome.pipeline.image.config.converter.mapping.DefinedPipelineBiomeColorMappingTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.BiomePipelineAddon; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.DelegatedPipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.converter.mapping.BiomeDefinedColorMapping; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; +import com.dfsek.terra.addons.manifest.api.AddonInitializer; +import com.dfsek.terra.api.Platform; +import com.dfsek.terra.api.addon.BaseAddon; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; +import com.dfsek.terra.api.event.functional.FunctionalEventHandler; +import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.registry.CheckedRegistry; +import com.dfsek.terra.api.util.reflection.TypeKey; +import com.dfsek.terra.api.world.biome.Biome; + + +public class PipelineImageAddon implements AddonInitializer { + + public static final TypeKey>>> PIPELINE_BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>>> PIPELINE_BIOME_COLOR_MAPPING_REGISTRY_KEY = new TypeKey<>() { + }; + + @Inject + private Platform platform; + + @Inject + private BaseAddon addon; + + @Override + public void initialize() { + + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPreLoadEvent.class) + .priority(500) + .then(event -> { + CheckedRegistry>>> biomeColorConverterRegistry = event.getPack().getOrCreateRegistry( + PIPELINE_BIOME_COLOR_CONVERTER_REGISTRY_KEY); + biomeColorConverterRegistry.register(addon.key("EXACT"), ExactPipelineBiomeColorConverterTemplate::new); + biomeColorConverterRegistry.register(addon.key("CLOSEST"), ClosestPipelineBiomeColorConverterTemplate::new); + }) + .then(event -> { + CheckedRegistry>> sourceRegistry = event.getPack().getOrCreateRegistry(BiomePipelineAddon.SOURCE_REGISTRY_KEY); + sourceRegistry.register(addon.key("IMAGE"), ImageSourceTemplate::new); + }) + .then(event -> { + CheckedRegistry>>> biomeColorMappingRegistry = event.getPack().getOrCreateRegistry( + PIPELINE_BIOME_COLOR_MAPPING_REGISTRY_KEY); + biomeColorMappingRegistry.register(addon.key("USE_BIOME_COLORS"), () -> () -> new BiomeDefinedColorMapping<>(event.getPack().getRegistry(Biome.class), DelegatedPipelineBiome::new)); + biomeColorMappingRegistry.register(addon.key("MAP"), DefinedPipelineBiomeColorMappingTemplate::new); + }) + .failThrough(); + } +} diff --git a/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/ImageSourceTemplate.java b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/ImageSourceTemplate.java new file mode 100644 index 000000000..40d2910c8 --- /dev/null +++ b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/ImageSourceTemplate.java @@ -0,0 +1,25 @@ +package com.dfsek.terra.addons.biome.pipeline.image.config; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.biome.pipeline.image.ImageSource; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.image.colorsampler.ColorSampler; +import com.dfsek.terra.addons.image.converter.ColorConverter; + + +public class ImageSourceTemplate implements ObjectTemplate { + + @Value("color-sampler") + private ColorSampler colorSampler; + + @Value("color-conversion") + private ColorConverter colorConverter; + + @Override + public Source get() { + return new ImageSource(colorSampler, colorConverter); + } +} diff --git a/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/ClosestPipelineBiomeColorConverterTemplate.java b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/ClosestPipelineBiomeColorConverterTemplate.java new file mode 100644 index 000000000..a695398dc --- /dev/null +++ b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/ClosestPipelineBiomeColorConverterTemplate.java @@ -0,0 +1,19 @@ +package com.dfsek.terra.addons.biome.pipeline.image.config.converter; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.image.config.converter.ClosestColorConverterTemplate; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; + + +public class ClosestPipelineBiomeColorConverterTemplate extends ClosestColorConverterTemplate { + + @Value("match") + private ColorMapping match; + + @Override + protected ColorMapping getMapping() { + return match; + } +} diff --git a/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/ExactPipelineBiomeColorConverterTemplate.java b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/ExactPipelineBiomeColorConverterTemplate.java new file mode 100644 index 000000000..305a03e31 --- /dev/null +++ b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/ExactPipelineBiomeColorConverterTemplate.java @@ -0,0 +1,37 @@ +package com.dfsek.terra.addons.biome.pipeline.image.config.converter; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.image.config.converter.ExactColorConverterTemplate; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; + + +public class ExactPipelineBiomeColorConverterTemplate extends ExactColorConverterTemplate { + + @Value("match") + private ColorMapping match; + + @Value("else") + private PipelineBiome fallback; + + @Value("ignore-alpha") + @Default + private boolean ignoreAlpha = true; + + @Override + protected ColorMapping getMapping() { + return match; + } + + @Override + protected PipelineBiome getFallback() { + return fallback; + } + + @Override + protected boolean ignoreAlpha() { + return ignoreAlpha; + } +} diff --git a/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/mapping/DefinedPipelineBiomeColorMappingTemplate.java b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/mapping/DefinedPipelineBiomeColorMappingTemplate.java new file mode 100644 index 000000000..b1d4115f2 --- /dev/null +++ b/common/addons/pipeline-image/src/main/java/com/dfsek/terra/addons/biome/pipeline/image/config/converter/mapping/DefinedPipelineBiomeColorMappingTemplate.java @@ -0,0 +1,24 @@ +package com.dfsek.terra.addons.biome.pipeline.image.config.converter.mapping; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.Map; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.image.config.ColorLoader.ColorString; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; +import com.dfsek.terra.addons.image.util.MapUtil; + + +public class DefinedPipelineBiomeColorMappingTemplate implements ObjectTemplate> { + + @Value("map") + Map map; + + @Override + public ColorMapping get() { + var map = MapUtil.mapKeys(this.map, ColorString::getColor); + return () -> map; + } +} diff --git a/common/addons/pipeline-image/src/main/resources/terra.addon.yml b/common/addons/pipeline-image/src/main/resources/terra.addon.yml new file mode 100644 index 000000000..09adeb933 --- /dev/null +++ b/common/addons/pipeline-image/src/main/resources/terra.addon.yml @@ -0,0 +1,15 @@ +schema-version: 1 +contributors: + - Terra contributors +id: pipeline-image +version: @VERSION@ +entrypoints: + - "com.dfsek.terra.addons.biome.pipeline.image.PipelineImageAddon" +website: + issues: https://github.com/PolyhedralDev/Terra/issues + source: https://github.com/PolyhedralDev/Terra + docs: https://terra.polydev.org +license: MIT License +depends: + library-image: "1.+" + biome-provider-pipeline-v2: "1.+" diff --git a/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeSchematicAddon.java b/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeSchematicAddon.java index caf7f47b5..bd46330ac 100644 --- a/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeSchematicAddon.java +++ b/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeSchematicAddon.java @@ -30,6 +30,7 @@ import com.dfsek.terra.api.inject.annotations.Inject; import com.dfsek.terra.api.registry.CheckedRegistry; import com.dfsek.terra.api.structure.Structure; import com.dfsek.terra.api.util.StringUtil; +import com.dfsek.terra.api.util.vector.Vector3Int; public class SpongeSchematicAddon implements AddonInitializer { @@ -71,9 +72,36 @@ public class SpongeSchematicAddon implements AddonInitializer { public SpongeStructure convert(InputStream in, String id) { try { CompoundTag baseTag = (CompoundTag) new NBTDeserializer(false).fromStream(detectDecompression(in)).getTag(); + int ver = baseTag.getInt("Version"); int wid = baseTag.getShort("Width"); int len = baseTag.getShort("Length"); int hei = baseTag.getShort("Height"); + + CompoundTag metadata = baseTag.getCompoundTag("Metadata"); + + Vector3Int offset = switch(ver) { + case 2 -> { + // Use WorldEdit defined legacy relative offset if it exists in schematic metadata + IntTag worldEditOffsetX = metadata.getIntTag("WEOffsetX"); + IntTag worldEditOffsetY = metadata.getIntTag("WEOffsetY"); + IntTag worldEditOffsetZ = metadata.getIntTag("WEOffsetZ"); + if(worldEditOffsetX != null || worldEditOffsetY != null || worldEditOffsetZ != null) { + if(worldEditOffsetX == null || worldEditOffsetY == null || worldEditOffsetZ == null) { + throw new IllegalArgumentException("Failed to parse Sponge schematic: Malformed WorldEdit offset"); + } + yield Vector3Int.of(worldEditOffsetX.asInt(), worldEditOffsetY.asInt(), worldEditOffsetZ.asInt()); + } else { + // Relative offset handling via 'Offset' field is ambiguous in spec 2 so just apply no offset + yield Vector3Int.zero(); + } + } + case 3 -> { + // Relative offset is more concretely defined in spec 3 to use 'Offset' field + int[] offsetArray = baseTag.getIntArray("Offset"); + yield Vector3Int.of(offsetArray[0], offsetArray[1], offsetArray[2]); + } + default -> throw new IllegalArgumentException("Failed to parse Sponge schematic: Unsupported format version: " + ver); + }; ByteArrayTag blocks = baseTag.getByteArrayTag("BlockData"); @@ -97,7 +125,7 @@ public class SpongeSchematicAddon implements AddonInitializer { } } - return new SpongeStructure(states, addon.key(id)); + return new SpongeStructure(states, offset, addon.key(id)); } catch(IOException e) { throw new IllegalArgumentException("Failed to parse Sponge schematic: ", e); } diff --git a/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeStructure.java b/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeStructure.java index e08c51146..49bde23fb 100644 --- a/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeStructure.java +++ b/common/addons/structure-sponge-loader/src/main/java/com/dfsek/terra/addons/sponge/SpongeStructure.java @@ -23,10 +23,15 @@ public class SpongeStructure implements Structure, Keyed { private final BlockState[][][] blocks; + private final int offsetX, offsetY, offsetZ; + private final RegistryKey id; - public SpongeStructure(BlockState[][][] blocks, RegistryKey id) { + public SpongeStructure(BlockState[][][] blocks, Vector3Int offset, RegistryKey id) { this.blocks = blocks; + this.offsetX = offset.getX(); + this.offsetY = offset.getY(); + this.offsetZ = offset.getZ(); this.id = id; } @@ -37,13 +42,15 @@ public class SpongeStructure implements Structure, Keyed { int bZ = location.getZ(); for(int x = 0; x < blocks.length; x++) { for(int z = 0; z < blocks[x].length; z++) { - Vector2Int r = Vector2Int.of(x, z).rotate(rotation); + int oX = x + offsetX; + int oZ = z + offsetZ; + Vector2Int r = Vector2Int.of(oX, oZ).rotate(rotation); int rX = r.getX(); int rZ = r.getZ(); for(int y = 0; y < blocks[x][z].length; y++) { BlockState state = blocks[x][z][y]; if(state == null) continue; - world.setBlockState(bX + rX, bY + y, bZ + rZ, state); + world.setBlockState(bX + rX, bY + y + offsetY, bZ + rZ, state); } } } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/RangeLoader.java b/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/RangeLoader.java index b4e55f935..ca40d6b90 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/RangeLoader.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/RangeLoader.java @@ -17,10 +17,17 @@ package com.dfsek.terra.config.loaders; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; import com.dfsek.tectonic.api.depth.DepthTracker; import com.dfsek.tectonic.api.exception.LoadException; import com.dfsek.tectonic.api.loader.ConfigLoader; import com.dfsek.tectonic.api.loader.type.TypeLoader; + +import com.dfsek.tectonic.impl.MapConfiguration; + +import com.dfsek.terra.api.config.meta.Meta; + import org.jetbrains.annotations.NotNull; import java.lang.reflect.AnnotatedType; @@ -36,11 +43,26 @@ public class RangeLoader implements TypeLoader { public Range load(@NotNull AnnotatedType type, @NotNull Object o, @NotNull ConfigLoader configLoader, DepthTracker depthTracker) throws LoadException { if(o instanceof Map) { - Map map = (Map) o; - return new ConstantRange(map.get("min"), map.get("max")); + return configLoader.load(new RangeMapTemplate(), new MapConfiguration((Map) o), depthTracker).get(); } else { int h = configLoader.loadType(Integer.class, o, depthTracker); return new ConstantRange(h, h + 1); } } + + /* + * Template needed so keys can be meta annotated, otherwise the loader could just grab keys directly from the object + */ + public static class RangeMapTemplate implements ObjectTemplate { + @Value("min") + private @Meta int min; + + @Value("max") + private @Meta int max; + + @Override + public Range get() { + return new ConstantRange(min, max); + } + } } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java b/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java index 98f24842c..b6f50dd78 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java @@ -27,24 +27,43 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; import java.lang.reflect.AnnotatedType; +import java.util.concurrent.ConcurrentHashMap; +import com.dfsek.terra.api.config.ConfigPack; import com.dfsek.terra.api.config.Loader; +import com.dfsek.terra.api.properties.Properties; - +/* + * @deprecated Use the Image and ImageLoader class provided by the library-image addon instead. This is subject to removal in v7. + */ +@Deprecated public class BufferedImageLoader implements TypeLoader { private final Loader files; - public BufferedImageLoader(Loader files) { + private final ConfigPack pack; + + public BufferedImageLoader(Loader files, ConfigPack pack) { this.files = files; + this.pack = pack; + if (!pack.getContext().has(ImageCache.class)) + pack.getContext().put(new ImageCache(new ConcurrentHashMap<>())); } @Override public BufferedImage load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker) throws LoadException { - try { - return ImageIO.read(files.get((String) c)); - } catch(IOException e) { - throw new LoadException("Unable to load image", e, depthTracker); - } + return pack.getContext().get(ImageCache.class).map.computeIfAbsent((String) c, s -> { + try { + return ImageIO.read(files.get(s)); + } catch(IOException e) { + throw new LoadException("Unable to load image", e, depthTracker); + } + }); + } + + /* + * Cache prevents configs from loading the same image multiple times into memory + */ + private record ImageCache(ConcurrentHashMap map) implements Properties { } } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java b/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java index 84f2fb382..611539c41 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java @@ -283,7 +283,7 @@ public class ConfigPackImpl implements ConfigPack { @Override public void register(TypeRegistry registry) { registry.registerLoader(ConfigType.class, configTypeRegistry) - .registerLoader(BufferedImage.class, new BufferedImageLoader(loader)); + .registerLoader(BufferedImage.class, new BufferedImageLoader(loader, this)); registryMap.forEach(registry::registerLoader); shortcuts.forEach(registry::registerLoader); // overwrite with delegated shortcuts if present } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/preprocessor/MetaListLikePreprocessor.java b/common/implementation/base/src/main/java/com/dfsek/terra/config/preprocessor/MetaListLikePreprocessor.java index 38f754cf7..59e335ab4 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/preprocessor/MetaListLikePreprocessor.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/config/preprocessor/MetaListLikePreprocessor.java @@ -68,7 +68,7 @@ public class MetaListLikePreprocessor extends MetaPreprocessor { if(!(metaValue instanceof List)) { throw new LoadException( - "MetaList/Set injection candidate must be list, is type " + metaValue.getClass().getCanonicalName(), + "Meta list / set injection (via <<) must point to a list. '" + meta + "' points to type " + metaValue.getClass().getCanonicalName(), depthTracker); } diff --git a/platforms/bukkit/build.gradle.kts b/platforms/bukkit/build.gradle.kts index ee85af57a..dbc2d4125 100644 --- a/platforms/bukkit/build.gradle.kts +++ b/platforms/bukkit/build.gradle.kts @@ -10,12 +10,14 @@ dependencies { shaded(project(":platforms:bukkit:common")) shaded(project(":platforms:bukkit:nms:v1_18_R2", configuration = "reobf")) shaded(project(":platforms:bukkit:nms:v1_19_R1", configuration = "reobf")) + shaded(project(":platforms:bukkit:nms:v1_19_R2", configuration = "reobf")) + shaded(project(":platforms:bukkit:nms:v1_19_R3", configuration = "reobf")) + shaded(project(":platforms:bukkit:nms:v1_20_R1", configuration = "reobf")) shaded("xyz.jpenilla", "reflection-remapper", Versions.Bukkit.reflectionRemapper) } tasks { shadowJar { - relocate("org.bstats.bukkit", "com.dfsek.terra.lib.bstats") relocate("io.papermc.lib", "com.dfsek.terra.lib.paperlib") relocate("com.google.common", "com.dfsek.terra.lib.google.common") relocate("org.apache.logging.slf4j", "com.dfsek.terra.lib.slf4j-over-log4j") @@ -29,11 +31,11 @@ tasks { } runServer { - minecraftVersion("1.19") + minecraftVersion(Versions.Bukkit.minecraft) dependsOn(shadowJar) pluginJars(shadowJar.get().archiveFile) } } -addonDir(project.file("./target/server/paper/plugins/Terra/addons"), tasks.named("runServer").get()) +addonDir(project.file("./run/plugins/Terra/addons"), tasks.named("runServer").get()) diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java index ac215b83e..ee6249eb5 100644 --- a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java +++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java @@ -5,8 +5,10 @@ import ca.solostudios.strata.version.Version; import com.dfsek.terra.api.addon.BaseAddon; import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; import com.dfsek.terra.api.event.functional.FunctionalEventHandler; import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions; import com.dfsek.terra.bukkit.config.VanillaBiomeProperties; @@ -21,6 +23,12 @@ public class BukkitAddon implements BaseAddon { @Override public void initialize() { + terraBukkitPlugin.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(this, ConfigPackPreLoadEvent.class) + .then(event -> event.getPack().getContext().put(event.loadTemplate(new PreLoadCompatibilityOptions()))) + .global(); + terraBukkitPlugin.getEventManager() .getHandler(FunctionalEventHandler.class) .register(this, ConfigurationLoadEvent.class) diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/config/PreLoadCompatibilityOptions.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/config/PreLoadCompatibilityOptions.java new file mode 100644 index 000000000..c9c4de3f4 --- /dev/null +++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/config/PreLoadCompatibilityOptions.java @@ -0,0 +1,60 @@ +/* + * This file is part of Terra. + * + * Terra is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Terra is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Terra. If not, see . + */ + +package com.dfsek.terra.bukkit.config; + +import com.dfsek.tectonic.api.config.template.ConfigTemplate; +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.api.properties.Properties; + + +@SuppressWarnings("FieldMayBeFinal") +public class PreLoadCompatibilityOptions implements ConfigTemplate, Properties { + @Value("minecraft.use-vanilla-biomes") + @Default + private boolean vanillaBiomes = false; + + @Value("minecraft.beard.enable") + @Default + private boolean beard = true; + + @Value("minecraft.beard.threshold") + @Default + private double beardThreshold = 0.5; + + @Value("minecraft.beard.air-threshold") + @Default + private double airThreshold = -0.5; + + public boolean useVanillaBiomes() { + return vanillaBiomes; + } + + public boolean isBeard() { + return beard; + } + + public double getBeardThreshold() { + return beardThreshold; + } + + public double getAirThreshold() { + return airThreshold; + } +} diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitBlockPopulator.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitBlockPopulator.java new file mode 100644 index 000000000..1a1cb3b13 --- /dev/null +++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitBlockPopulator.java @@ -0,0 +1,33 @@ +package com.dfsek.terra.bukkit.generator; + +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.LimitedRegion; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; + +import java.util.Random; + +import com.dfsek.terra.api.block.state.BlockState; +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.world.BukkitProtoWorld; + + +public class BukkitBlockPopulator extends BlockPopulator { + private ConfigPack pack; + private final BlockState air; + + public BukkitBlockPopulator(ConfigPack pack, BlockState air) { + this.pack = pack; + this.air = air; + } + + public void setPack(ConfigPack pack) { + this.pack = pack; + } + + @Override + public void populate(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, + @NotNull LimitedRegion limitedRegion) { + pack.getStages().forEach(generationStage -> generationStage.populate(new BukkitProtoWorld(limitedRegion, air, pack.getBiomeProvider()))); + } +} diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java index 8d7302fa7..8a6b17bdd 100644 --- a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java +++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java @@ -45,12 +45,14 @@ public class BukkitChunkGeneratorWrapper extends org.bukkit.generator.ChunkGener private final BlockState air; private ChunkGenerator delegate; private ConfigPack pack; + private final BukkitBlockPopulator blockPopulator; public BukkitChunkGeneratorWrapper(ChunkGenerator delegate, ConfigPack pack, BlockState air) { this.delegate = delegate; this.pack = pack; this.air = air; + this.blockPopulator = new BukkitBlockPopulator(pack, air); } public void setDelegate(ChunkGenerator delegate) { @@ -70,16 +72,7 @@ public class BukkitChunkGeneratorWrapper extends org.bukkit.generator.ChunkGener @Override public @NotNull List getDefaultPopulators(@NotNull World world) { - return pack.getStages() - .stream() - .map(generationStage -> new BlockPopulator() { - @Override - public void populate(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, - @NotNull LimitedRegion limitedRegion) { - generationStage.populate(new BukkitProtoWorld(limitedRegion, air, pack.getBiomeProvider())); - } - }) - .collect(Collectors.toList()); + return List.of(blockPopulator); } @Override diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/handles/BukkitWorldHandle.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/handles/BukkitWorldHandle.java index 33e2f6708..563a3b66e 100644 --- a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/handles/BukkitWorldHandle.java +++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/handles/BukkitWorldHandle.java @@ -52,7 +52,12 @@ public class BukkitWorldHandle implements WorldHandle { @Override public @NotNull EntityType getEntity(@NotNull String id) { if(!id.startsWith("minecraft:")) throw new IllegalArgumentException("Invalid entity identifier " + id); - return new BukkitEntityType(org.bukkit.entity.EntityType.valueOf(id.toUpperCase(Locale.ROOT).substring(10))); + String entityID = id.toUpperCase(Locale.ROOT).substring(10); + + return new BukkitEntityType(switch(entityID) { + case "END_CRYSTAL" -> org.bukkit.entity.EntityType.ENDER_CRYSTAL; + case "ENDER_CRYSTAL" -> throw new IllegalArgumentException("Invalid entity identifier " + id); // make sure this issue can't happen the other way around. + default -> org.bukkit.entity.EntityType.valueOf(entityID); + }); } - } diff --git a/platforms/bukkit/nms/v1_19_R1/build.gradle.kts b/platforms/bukkit/nms/v1_19_R1/build.gradle.kts index e40d548dc..27f3b157e 100644 --- a/platforms/bukkit/nms/v1_19_R1/build.gradle.kts +++ b/platforms/bukkit/nms/v1_19_R1/build.gradle.kts @@ -14,4 +14,4 @@ tasks { assemble { dependsOn("reobfJar") } -} \ No newline at end of file +} diff --git a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java index 81933be0f..c35bae020 100644 --- a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java +++ b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java @@ -1,5 +1,11 @@ package com.dfsek.terra.bukkit.nms.v1_19_R1; +import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions; + +import com.dfsek.terra.bukkit.world.BukkitWorldProperties; + +import com.dfsek.terra.bukkit.world.block.data.BukkitBlockState; + import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; @@ -10,14 +16,21 @@ import net.minecraft.core.SectionPos; import net.minecraft.server.level.WorldGenRegion; import net.minecraft.util.RandomSource; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; 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.Biome; import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.Beardifier; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunction.FunctionContext; +import net.minecraft.world.level.levelgen.DensityFunction.SinglePointContext; import net.minecraft.world.level.levelgen.GenerationStep.Carving; import net.minecraft.world.level.levelgen.Heightmap.Types; import net.minecraft.world.level.levelgen.RandomState; @@ -103,7 +116,43 @@ public class NMSChunkGeneratorDelegate extends ChunkGenerator { public @NotNull CompletableFuture fillFromNoise(@NotNull Executor executor, @NotNull Blender blender, @NotNull RandomState noiseConfig, @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk) { - return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk); + return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk) + .thenApply(c -> { + LevelAccessor level = Reflection.STRUCTURE_MANAGER.getLevel(structureAccessor); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + PreLoadCompatibilityOptions compatibilityOptions = pack.getContext().get(PreLoadCompatibilityOptions.class); + if(compatibilityOptions.isBeard()) { + beard(structureAccessor, chunk, new BukkitWorldProperties(level.getMinecraftWorld().getWorld()), biomeProvider, compatibilityOptions); + } + return c; + }); + } + + private void beard(StructureManager structureAccessor, ChunkAccess chunk, WorldProperties world, BiomeProvider biomeProvider, + PreLoadCompatibilityOptions compatibilityOptions) { + Beardifier structureWeightSampler = Beardifier.forStructuresInChunk(structureAccessor, chunk.getPos()); + double threshold = compatibilityOptions.getBeardThreshold(); + double airThreshold = compatibilityOptions.getAirThreshold(); + int xi = chunk.getPos().x << 4; + int zi = chunk.getPos().z << 4; + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + int depth = 0; + for(int y = world.getMaxHeight(); y >= world.getMinHeight(); y--) { + double noise = structureWeightSampler.compute(new SinglePointContext(x + xi, y, z + zi)); + if(noise > threshold) { + chunk.setBlockState(new BlockPos(x, y, z), ((CraftBlockData) ((BukkitBlockState) delegate + .getPalette(x + xi, y, z + zi, world, biomeProvider) + .get(depth, x + xi, y, z + zi, world.getSeed())).getHandle()).getState(), false); + depth++; + } else if(noise < airThreshold) { + chunk.setBlockState(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false); + } else { + depth = 0; + } + } + } + } } @Override diff --git a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java index 8fa9a0b01..b704952d5 100644 --- a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java +++ b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java @@ -1,14 +1,18 @@ package com.dfsek.terra.bukkit.nms.v1_19_R1; import net.minecraft.core.MappedRegistry; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.StructureManager; import xyz.jpenilla.reflectionremapper.ReflectionRemapper; import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter; import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter; import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies; public class Reflection { public static final MappedRegistryProxy MAPPED_REGISTRY; + public static final StructureManagerProxy STRUCTURE_MANAGER; static { ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar(); @@ -16,6 +20,7 @@ public class Reflection { Reflection.class.getClassLoader()); MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class); + STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class); } @@ -24,4 +29,10 @@ public class Reflection { @FieldSetter("frozen") void setFrozen(MappedRegistry instance, boolean frozen); } + + @Proxies(StructureManager.class) + public interface StructureManagerProxy { + @FieldGetter("level") + LevelAccessor getLevel(StructureManager instance); + } } diff --git a/platforms/bukkit/nms/v1_19_R2/build.gradle.kts b/platforms/bukkit/nms/v1_19_R2/build.gradle.kts new file mode 100644 index 000000000..687d651b6 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/build.gradle.kts @@ -0,0 +1,17 @@ +apply(plugin = "io.papermc.paperweight.userdev") + +repositories { + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} + +dependencies { + api(project(":platforms:bukkit:common")) + paperDevBundle("1.19.3-R0.1-SNAPSHOT") + implementation("xyz.jpenilla", "reflection-remapper", "0.1.0-SNAPSHOT") +} + +tasks { + assemble { + dependsOn("reobfJar") + } +} \ No newline at end of file diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/AwfulBukkitHacks.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/AwfulBukkitHacks.java new file mode 100644 index 000000000..f7da9c45d --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/AwfulBukkitHacks.java @@ -0,0 +1,100 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import com.google.common.collect.ImmutableMap; +import com.mojang.serialization.Lifecycle; +import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; +import org.bukkit.NamespacedKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.dfsek.terra.bukkit.world.BukkitPlatformBiome; +import com.dfsek.terra.registry.master.ConfigRegistry; + + +public class AwfulBukkitHacks { + private static final Logger LOGGER = LoggerFactory.getLogger(AwfulBukkitHacks.class); + + private static final Map> terraBiomeMap = new HashMap<>(); + + public static void registerBiomes(ConfigRegistry configRegistry) { + try { + LOGGER.info("Hacking biome registry..."); + WritableRegistry biomeRegistry = (WritableRegistry) RegistryFetcher.biomeRegistry(); + + Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry) biomeRegistry, false); + + configRegistry.forEach(pack -> pack.getRegistry(com.dfsek.terra.api.world.biome.Biome.class).forEach((key, biome) -> { + try { + BukkitPlatformBiome platformBiome = (BukkitPlatformBiome) biome.getPlatformBiome(); + NamespacedKey vanillaBukkitKey = platformBiome.getHandle().getKey(); + ResourceLocation vanillaMinecraftKey = new ResourceLocation(vanillaBukkitKey.getNamespace(), vanillaBukkitKey.getKey()); + Biome platform = NMSBiomeInjector.createBiome(biome, Objects.requireNonNull(biomeRegistry.get(vanillaMinecraftKey))); + + ResourceKey delegateKey = ResourceKey.create( + Registries.BIOME, + new ResourceLocation("terra", NMSBiomeInjector.createBiomeID(pack, key)) + ); + + Reference holder = biomeRegistry.register(delegateKey, platform, Lifecycle.stable()); + Reflection.REFERENCE.invokeBindValue(holder, platform); // IMPORTANT: bind holder. + + platformBiome.getContext().put(new NMSBiomeInfo(delegateKey)); + + terraBiomeMap.computeIfAbsent(vanillaMinecraftKey, i -> new ArrayList<>()).add(delegateKey.location()); + + LOGGER.debug("Registered biome: " + delegateKey); + } catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + })); + + Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry) biomeRegistry, true); // freeze registry again :) + + LOGGER.info("Doing tag garbage...."); + Map, List>> collect = biomeRegistry + .getTags() // streamKeysAndEntries + .collect(HashMap::new, + (map, pair) -> + map.put(pair.getFirst(), new ArrayList<>(pair.getSecond().stream().toList())), + HashMap::putAll); + + terraBiomeMap + .forEach((vb, terraBiomes) -> + NMSBiomeInjector.getEntry(biomeRegistry, vb).ifPresentOrElse( + vanilla -> terraBiomes.forEach( + tb -> NMSBiomeInjector.getEntry(biomeRegistry, tb).ifPresentOrElse( + terra -> { + LOGGER.debug("{} (vanilla for {}): {}", + vanilla.unwrapKey().orElseThrow().location(), + terra.unwrapKey().orElseThrow().location(), + vanilla.tags().toList()); + vanilla.tags() + .forEach(tag -> collect + .computeIfAbsent(tag, t -> new ArrayList<>()) + .add(terra)); + }, + () -> LOGGER.error("No such biome: {}", tb))), + () -> LOGGER.error("No vanilla biome: {}", vb))); + + biomeRegistry.resetTags(); + biomeRegistry.bindTags(ImmutableMap.copyOf(collect)); + + } catch(SecurityException | IllegalArgumentException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeInfo.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeInfo.java new file mode 100644 index 000000000..639c9eaed --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeInfo.java @@ -0,0 +1,10 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; + +import com.dfsek.terra.api.properties.Properties; + + +public record NMSBiomeInfo(ResourceKey biomeKey) implements Properties { +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeInjector.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeInjector.java new file mode 100644 index 000000000..20a426949 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeInjector.java @@ -0,0 +1,81 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; + +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.config.VanillaBiomeProperties; + + +public class NMSBiomeInjector { + + public static Optional> getEntry(Registry registry, ResourceLocation identifier) { + return registry.getOptional(identifier) + .flatMap(registry::getResourceKey) + .flatMap(registry::getHolder); + } + + public static Biome createBiome(com.dfsek.terra.api.world.biome.Biome biome, Biome vanilla) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Biome.BiomeBuilder builder = new Biome.BiomeBuilder(); + + builder + .precipitation(vanilla.getPrecipitation()) + .downfall(vanilla.getDownfall()) + .temperature(vanilla.getBaseTemperature()) + .mobSpawnSettings(vanilla.getMobSettings()) + .generationSettings(vanilla.getGenerationSettings()); + + + BiomeSpecialEffects.Builder effects = new BiomeSpecialEffects.Builder(); + + effects.grassColorModifier(vanilla.getSpecialEffects().getGrassColorModifier()); + + VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class); + + effects.fogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getFogColor(), vanilla.getFogColor())) + + .waterColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterColor(), vanilla.getWaterColor())) + + .waterFogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterFogColor(), vanilla.getWaterFogColor())) + + .skyColor(Objects.requireNonNullElse(vanillaBiomeProperties.getSkyColor(), vanilla.getSkyColor())); + + if(vanillaBiomeProperties.getFoliageColor() == null) { + vanilla.getSpecialEffects().getFoliageColorOverride().ifPresent(effects::foliageColorOverride); + } else { + effects.foliageColorOverride(vanillaBiomeProperties.getFoliageColor()); + } + + if(vanillaBiomeProperties.getGrassColor() == null) { + vanilla.getSpecialEffects().getGrassColorOverride().ifPresent(effects::grassColorOverride); + } else { + // grass + effects.grassColorOverride(vanillaBiomeProperties.getGrassColor()); + } + + vanilla.getAmbientLoop().ifPresent(effects::ambientLoopSound); + vanilla.getAmbientAdditions().ifPresent(effects::ambientAdditionsSound); + vanilla.getAmbientMood().ifPresent(effects::ambientMoodSound); + vanilla.getBackgroundMusic().ifPresent(effects::backgroundMusic); + vanilla.getAmbientParticle().ifPresent(effects::ambientParticle); + + builder.specialEffects(effects.build()); + + return builder.build(); + } + + public static String createBiomeID(ConfigPack pack, com.dfsek.terra.api.registry.key.RegistryKey biomeID) { + return pack.getID() + .toLowerCase() + "/" + biomeID.getNamespace().toLowerCase(Locale.ROOT) + "/" + biomeID.getID().toLowerCase(Locale.ROOT); + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeProvider.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeProvider.java new file mode 100644 index 000000000..e7b09426d --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSBiomeProvider.java @@ -0,0 +1,42 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import com.mojang.serialization.Codec; +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; +import org.jetbrains.annotations.NotNull; + +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; +import com.dfsek.terra.bukkit.world.BukkitPlatformBiome; + + +public class NMSBiomeProvider extends BiomeSource { + private final BiomeProvider delegate; + private final long seed; + private final Registry biomeRegistry = RegistryFetcher.biomeRegistry(); + + public NMSBiomeProvider(BiomeProvider delegate, long seed) { + super(delegate.stream() + .map(biome -> RegistryFetcher.biomeRegistry() + .getHolderOrThrow(((BukkitPlatformBiome) biome.getPlatformBiome()).getContext() + .get(NMSBiomeInfo.class) + .biomeKey()))); + this.delegate = delegate; + this.seed = seed; + } + + @Override + protected @NotNull Codec codec() { + return BiomeSource.CODEC; + } + + @Override + public @NotNull Holder getNoiseBiome(int x, int y, int z, @NotNull Sampler sampler) { + return biomeRegistry.getHolderOrThrow(((BukkitPlatformBiome) delegate.getBiome(x << 2, y << 2, z << 2, seed) + .getPlatformBiome()).getContext() + .get(NMSBiomeInfo.class) + .biomeKey()); + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSChunkGeneratorDelegate.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSChunkGeneratorDelegate.java new file mode 100644 index 000000000..5faa4ba4b --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSChunkGeneratorDelegate.java @@ -0,0 +1,173 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.LevelAccessor; +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.block.Blocks; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.Beardifier; +import net.minecraft.world.level.levelgen.DensityFunction.SinglePointContext; +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; +import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; +import com.dfsek.terra.api.world.info.WorldProperties; +import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions; +import com.dfsek.terra.bukkit.world.BukkitWorldProperties; +import com.dfsek.terra.bukkit.world.block.data.BukkitBlockState; + + +public class NMSChunkGeneratorDelegate extends ChunkGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(NMSChunkGeneratorDelegate.class); + private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate; + + private final ChunkGenerator vanilla; + private final ConfigPack pack; + + private final long seed; + + public NMSChunkGeneratorDelegate(ChunkGenerator vanilla, ConfigPack pack, NMSBiomeProvider biomeProvider, long seed) { + super(biomeProvider); + this.delegate = pack.getGeneratorProvider().newInstance(pack); + this.vanilla = vanilla; + this.pack = pack; + this.seed = seed; + } + + @Override + protected @NotNull Codec codec() { + return ChunkGenerator.CODEC; + } + + @Override + public void applyCarvers(@NotNull WorldGenRegion chunkRegion, long seed, @NotNull RandomState noiseConfig, @NotNull BiomeManager world, + @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk, @NotNull Carving carverStep) { + // no-op + } + + @Override + public void buildSurface(@NotNull WorldGenRegion region, @NotNull StructureManager structures, @NotNull RandomState noiseConfig, + @NotNull ChunkAccess chunk) { + // no-op + } + + @Override + public void applyBiomeDecoration(@NotNull WorldGenLevel world, @NotNull ChunkAccess chunk, + @NotNull StructureManager structureAccessor) { + vanilla.applyBiomeDecoration(world, chunk, structureAccessor); + } + + @Override + public void spawnOriginalMobs(@NotNull WorldGenRegion region) { + vanilla.spawnOriginalMobs(region); + } + + @Override + public int getGenDepth() { + return vanilla.getGenDepth(); + } + + @Override + public @NotNull CompletableFuture fillFromNoise(@NotNull Executor executor, @NotNull Blender blender, + @NotNull RandomState noiseConfig, + @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk) { + return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk) + .thenApply(c -> { + LevelAccessor level = Reflection.STRUCTURE_MANAGER.getLevel(structureAccessor); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + PreLoadCompatibilityOptions compatibilityOptions = pack.getContext().get(PreLoadCompatibilityOptions.class); + if(compatibilityOptions.isBeard()) { + beard(structureAccessor, chunk, new BukkitWorldProperties(level.getMinecraftWorld().getWorld()), biomeProvider, compatibilityOptions); + } + return c; + }); + } + + private void beard(StructureManager structureAccessor, ChunkAccess chunk, WorldProperties world, BiomeProvider biomeProvider, + PreLoadCompatibilityOptions compatibilityOptions) { + Beardifier structureWeightSampler = Beardifier.forStructuresInChunk(structureAccessor, chunk.getPos()); + double threshold = compatibilityOptions.getBeardThreshold(); + double airThreshold = compatibilityOptions.getAirThreshold(); + int xi = chunk.getPos().x << 4; + int zi = chunk.getPos().z << 4; + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + int depth = 0; + for(int y = world.getMaxHeight(); y >= world.getMinHeight(); y--) { + double noise = structureWeightSampler.compute(new SinglePointContext(x + xi, y, z + zi)); + if(noise > threshold) { + chunk.setBlockState(new BlockPos(x, y, z), ((CraftBlockData) ((BukkitBlockState) delegate + .getPalette(x + xi, y, z + zi, world, biomeProvider) + .get(depth, x + xi, y, z + zi, world.getSeed())).getHandle()).getState(), false); + depth++; + } else if(noise < airThreshold) { + chunk.setBlockState(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false); + } else { + depth = 0; + } + } + } + } + } + + @Override + public int getSeaLevel() { + return vanilla.getSeaLevel(); + } + + @Override + public int getMinY() { + return vanilla.getMinY(); + } + + @Override + public int getBaseHeight(int x, int z, @NotNull Types heightmap, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) { + WorldProperties properties = new NMSWorldProperties(seed, world); + int y = properties.getMaxHeight(); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + while(y >= getMinY() && !heightmap.isOpaque().test( + ((CraftBlockData) delegate.getBlock(properties, x, y - 1, z, biomeProvider).getHandle()).getState())) { + y--; + } + return y; + } + + @Override + public @NotNull NoiseColumn getBaseColumn(int x, int z, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) { + /* + BlockState[] array = new BlockState[world.getHeight()]; + WorldProperties properties = new NMSWorldProperties(seed, world); + BiomeProvider biomeProvider = pack.getBiomeProvider().caching(properties); + for(int y = properties.getMaxHeight() - 1; y >= properties.getMinHeight(); y--) { + array[y - properties.getMinHeight()] = ((CraftBlockData) delegate.getBlock(properties, x, y, z, biomeProvider) + .getHandle()).getState(); + } + return new NoiseColumn(getMinY(), array); + + */ + return vanilla.getBaseColumn(x, z, world, noiseConfig); + } + + @Override + public void addDebugScreenInfo(@NotNull List text, @NotNull RandomState noiseConfig, @NotNull BlockPos pos) { + + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSInitializer.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSInitializer.java new file mode 100644 index 000000000..05814cfd4 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSInitializer.java @@ -0,0 +1,15 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import org.bukkit.Bukkit; + +import com.dfsek.terra.bukkit.PlatformImpl; +import com.dfsek.terra.bukkit.nms.Initializer; + + +public class NMSInitializer implements Initializer { + @Override + public void initialize(PlatformImpl platform) { + AwfulBukkitHacks.registerBiomes(platform.getRawConfigRegistry()); + Bukkit.getPluginManager().registerEvents(new NMSInjectListener(), platform.getPlugin()); + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSInjectListener.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSInjectListener.java new file mode 100644 index 000000000..015de732e --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSInjectListener.java @@ -0,0 +1,48 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R2.CraftWorld; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.generator.BukkitChunkGeneratorWrapper; + + +public class NMSInjectListener implements Listener { + private static final Logger LOGGER = LoggerFactory.getLogger(NMSInjectListener.class); + private static final Set INJECTED = new HashSet<>(); + private static final ReentrantLock INJECT_LOCK = new ReentrantLock(); + + @EventHandler + public void onWorldInit(WorldInitEvent event) { + if(!INJECTED.contains(event.getWorld()) && + event.getWorld().getGenerator() instanceof BukkitChunkGeneratorWrapper bukkitChunkGeneratorWrapper) { + INJECT_LOCK.lock(); + INJECTED.add(event.getWorld()); + LOGGER.info("Preparing to take over the world: {}", event.getWorld().getName()); + CraftWorld craftWorld = (CraftWorld) event.getWorld(); + ServerLevel serverWorld = craftWorld.getHandle(); + + ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); + + ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); + NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); + + serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla, pack, provider, craftWorld.getSeed()); + + LOGGER.info("Successfully injected into world."); + + INJECT_LOCK.unlock(); + } + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSWorldProperties.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSWorldProperties.java new file mode 100644 index 000000000..cee2284d0 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/NMSWorldProperties.java @@ -0,0 +1,36 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import net.minecraft.world.level.LevelHeightAccessor; + +import com.dfsek.terra.api.world.info.WorldProperties; + + +public class NMSWorldProperties implements WorldProperties { + private final long seed; + private final LevelHeightAccessor height; + + public NMSWorldProperties(long seed, LevelHeightAccessor height) { + this.seed = seed; + this.height = height; + } + + @Override + public Object getHandle() { + return height; + } + + @Override + public long getSeed() { + return seed; + } + + @Override + public int getMaxHeight() { + return height.getMaxBuildHeight(); + } + + @Override + public int getMinHeight() { + return height.getMinBuildHeight(); + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/Reflection.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/Reflection.java new file mode 100644 index 000000000..c65df8ca1 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/Reflection.java @@ -0,0 +1,52 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.MappedRegistry; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.StructureManager; +import xyz.jpenilla.reflectionremapper.ReflectionRemapper; +import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter; +import xyz.jpenilla.reflectionremapper.proxy.annotation.MethodName; +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies; + +import java.util.Objects; + + +public class Reflection { + public static final MappedRegistryProxy MAPPED_REGISTRY; + public static final StructureManagerProxy STRUCTURE_MANAGER; + + public static final ReferenceProxy REFERENCE; + + static { + ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar(); + ReflectionProxyFactory reflectionProxyFactory = ReflectionProxyFactory.create(reflectionRemapper, + Reflection.class.getClassLoader()); + + MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class); + STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class); + REFERENCE = reflectionProxyFactory.reflectionProxy(ReferenceProxy.class); + } + + + @Proxies(MappedRegistry.class) + public interface MappedRegistryProxy { + @FieldSetter("frozen") + void setFrozen(MappedRegistry instance, boolean frozen); + } + + @Proxies(StructureManager.class) + public interface StructureManagerProxy { + @FieldGetter("level") + LevelAccessor getLevel(StructureManager instance); + } + + @Proxies(Holder.Reference.class) + public interface ReferenceProxy { + @MethodName("bindValue") + void invokeBindValue(Reference instance, T value); + } +} diff --git a/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/RegistryFetcher.java b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/RegistryFetcher.java new file mode 100644 index 000000000..f0c776f48 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R2/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R2/RegistryFetcher.java @@ -0,0 +1,25 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R2; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R2.CraftServer; + + +public class RegistryFetcher { + private static Registry getRegistry(ResourceKey> key) { + CraftServer craftserver = (CraftServer) Bukkit.getServer(); + DedicatedServer dedicatedserver = craftserver.getServer(); + return dedicatedserver + .registryAccess() + .registryOrThrow(key); + } + + public static Registry biomeRegistry() { + return getRegistry(Registries.BIOME); + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/build.gradle.kts b/platforms/bukkit/nms/v1_19_R3/build.gradle.kts new file mode 100644 index 000000000..5b92e7ab2 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/build.gradle.kts @@ -0,0 +1,17 @@ +apply(plugin = "io.papermc.paperweight.userdev") + +repositories { + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} + +dependencies { + api(project(":platforms:bukkit:common")) + paperDevBundle("1.19.4-R0.1-SNAPSHOT") + implementation("xyz.jpenilla", "reflection-remapper", "0.1.0-SNAPSHOT") +} + +tasks { + assemble { + dependsOn("reobfJar") + } +} \ No newline at end of file diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/AwfulBukkitHacks.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/AwfulBukkitHacks.java new file mode 100644 index 000000000..6ce0cc9a5 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/AwfulBukkitHacks.java @@ -0,0 +1,101 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import com.google.common.collect.ImmutableMap; +import com.mojang.serialization.Lifecycle; +import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; +import org.bukkit.NamespacedKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.dfsek.terra.bukkit.world.BukkitPlatformBiome; +import com.dfsek.terra.registry.master.ConfigRegistry; + + +public class AwfulBukkitHacks { + private static final Logger LOGGER = LoggerFactory.getLogger(AwfulBukkitHacks.class); + + private static final Map> terraBiomeMap = new HashMap<>(); + + public static void registerBiomes(ConfigRegistry configRegistry) { + try { + LOGGER.info("Hacking biome registry..."); + WritableRegistry biomeRegistry = (WritableRegistry) RegistryFetcher.biomeRegistry(); + + Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry) biomeRegistry, false); + + configRegistry.forEach(pack -> pack.getRegistry(com.dfsek.terra.api.world.biome.Biome.class).forEach((key, biome) -> { + try { + BukkitPlatformBiome platformBiome = (BukkitPlatformBiome) biome.getPlatformBiome(); + NamespacedKey vanillaBukkitKey = platformBiome.getHandle().getKey(); + ResourceLocation vanillaMinecraftKey = new ResourceLocation(vanillaBukkitKey.getNamespace(), vanillaBukkitKey.getKey()); + Biome platform = NMSBiomeInjector.createBiome(biome, Objects.requireNonNull(biomeRegistry.get(vanillaMinecraftKey))); + + ResourceKey delegateKey = ResourceKey.create( + Registries.BIOME, + new ResourceLocation("terra", NMSBiomeInjector.createBiomeID(pack, key)) + ); + + Reference holder = biomeRegistry.register(delegateKey, platform, Lifecycle.stable()); + Reflection.REFERENCE.invokeBindValue(holder, platform); // IMPORTANT: bind holder. + + platformBiome.getContext().put(new NMSBiomeInfo(delegateKey)); + + terraBiomeMap.computeIfAbsent(vanillaMinecraftKey, i -> new ArrayList<>()).add(delegateKey.location()); + + LOGGER.debug("Registered biome: " + delegateKey); + } catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + })); + + Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry) biomeRegistry, true); // freeze registry again :) + + LOGGER.info("Doing tag garbage...."); + Map, List>> collect = biomeRegistry + .getTags() // streamKeysAndEntries + .collect(HashMap::new, + (map, pair) -> + map.put(pair.getFirst(), new ArrayList<>(pair.getSecond().stream().toList())), + HashMap::putAll); + + terraBiomeMap + .forEach((vb, terraBiomes) -> + NMSBiomeInjector.getEntry(biomeRegistry, vb).ifPresentOrElse( + vanilla -> terraBiomes.forEach( + tb -> NMSBiomeInjector.getEntry(biomeRegistry, tb).ifPresentOrElse( + terra -> { + LOGGER.debug("{} (vanilla for {}): {}", + vanilla.unwrapKey().orElseThrow().location(), + terra.unwrapKey().orElseThrow().location(), + vanilla.tags().toList()); + vanilla.tags() + .forEach(tag -> collect + .computeIfAbsent(tag, t -> new ArrayList<>()) + .add(terra)); + }, + () -> LOGGER.error("No such biome: {}", tb))), + () -> LOGGER.error("No vanilla biome: {}", vb))); + + biomeRegistry.resetTags(); + biomeRegistry.bindTags(ImmutableMap.copyOf(collect)); + + } catch(SecurityException | IllegalArgumentException exception) { + throw new RuntimeException(exception); + } + } +} + diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeInfo.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeInfo.java new file mode 100644 index 000000000..816d96068 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeInfo.java @@ -0,0 +1,10 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; + +import com.dfsek.terra.api.properties.Properties; + + +public record NMSBiomeInfo(ResourceKey biomeKey) implements Properties { +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeInjector.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeInjector.java new file mode 100644 index 000000000..f7bedfd0f --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeInjector.java @@ -0,0 +1,83 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import net.minecraft.core.Holder; +import java.util.stream.Stream; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; + +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.config.VanillaBiomeProperties; + +import static net.minecraft.world.level.biome.Biome.ClimateSettings; +import static net.minecraft.world.level.biome.Biome.Precipitation; + +public class NMSBiomeInjector { + + public static Optional> getEntry(Registry registry, ResourceLocation identifier) { + return registry.getOptional(identifier) + .flatMap(registry::getResourceKey) + .flatMap(registry::getHolder); + } + + public static Biome createBiome(com.dfsek.terra.api.world.biome.Biome biome, Biome vanilla) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Biome.BiomeBuilder builder = new Biome.BiomeBuilder(); + + builder + .downfall(vanilla.climateSettings.downfall()) + .temperature(vanilla.getBaseTemperature()) + .mobSpawnSettings(vanilla.getMobSettings()) + .generationSettings(vanilla.getGenerationSettings()); + + + BiomeSpecialEffects.Builder effects = new BiomeSpecialEffects.Builder(); + + effects.grassColorModifier(vanilla.getSpecialEffects().getGrassColorModifier()); + + VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class); + + effects.fogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getFogColor(), vanilla.getFogColor())) + + .waterColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterColor(), vanilla.getWaterColor())) + + .waterFogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterFogColor(), vanilla.getWaterFogColor())) + + .skyColor(Objects.requireNonNullElse(vanillaBiomeProperties.getSkyColor(), vanilla.getSkyColor())); + + if(vanillaBiomeProperties.getFoliageColor() == null) { + vanilla.getSpecialEffects().getFoliageColorOverride().ifPresent(effects::foliageColorOverride); + } else { + effects.foliageColorOverride(vanillaBiomeProperties.getFoliageColor()); + } + + if(vanillaBiomeProperties.getGrassColor() == null) { + vanilla.getSpecialEffects().getGrassColorOverride().ifPresent(effects::grassColorOverride); + } else { + // grass + effects.grassColorOverride(vanillaBiomeProperties.getGrassColor()); + } + + vanilla.getAmbientLoop().ifPresent(effects::ambientLoopSound); + vanilla.getAmbientAdditions().ifPresent(effects::ambientAdditionsSound); + vanilla.getAmbientMood().ifPresent(effects::ambientMoodSound); + vanilla.getBackgroundMusic().ifPresent(effects::backgroundMusic); + vanilla.getAmbientParticle().ifPresent(effects::ambientParticle); + + builder.specialEffects(effects.build()); + + return builder.build(); + } + + public static String createBiomeID(ConfigPack pack, com.dfsek.terra.api.registry.key.RegistryKey biomeID) { + return pack.getID() + .toLowerCase() + "/" + biomeID.getNamespace().toLowerCase(Locale.ROOT) + "/" + biomeID.getID().toLowerCase(Locale.ROOT); + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeProvider.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeProvider.java new file mode 100644 index 000000000..52711f0ec --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSBiomeProvider.java @@ -0,0 +1,47 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Holder; +import java.util.stream.Stream; +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; +import org.jetbrains.annotations.NotNull; + +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; +import com.dfsek.terra.bukkit.world.BukkitPlatformBiome; + + +public class NMSBiomeProvider extends BiomeSource { + private final BiomeProvider delegate; + private final long seed; + private final Registry biomeRegistry = RegistryFetcher.biomeRegistry(); + + public NMSBiomeProvider(BiomeProvider delegate, long seed) { + super(); + this.delegate = delegate; + this.seed = seed; + } + @Override + protected Stream> collectPossibleBiomes() { + return delegate.stream() + .map(biome -> RegistryFetcher.biomeRegistry() + .getHolderOrThrow(((BukkitPlatformBiome) biome.getPlatformBiome()).getContext() + .get(NMSBiomeInfo.class) + .biomeKey())); + } + + @Override + protected @NotNull Codec codec() { + return BiomeSource.CODEC; + } + + @Override + public @NotNull Holder getNoiseBiome(int x, int y, int z, @NotNull Sampler sampler) { + return biomeRegistry.getHolderOrThrow(((BukkitPlatformBiome) delegate.getBiome(x << 2, y << 2, z << 2, seed) + .getPlatformBiome()).getContext() + .get(NMSBiomeInfo.class) + .biomeKey()); + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSChunkGeneratorDelegate.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSChunkGeneratorDelegate.java new file mode 100644 index 000000000..e977a2855 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSChunkGeneratorDelegate.java @@ -0,0 +1,170 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.LevelAccessor; +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.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.Beardifier; +import net.minecraft.world.level.levelgen.DensityFunction.SinglePointContext; +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; +import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; +import com.dfsek.terra.api.world.info.WorldProperties; +import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions; +import com.dfsek.terra.bukkit.world.BukkitWorldProperties; +import com.dfsek.terra.bukkit.world.block.data.BukkitBlockState; + + +public class NMSChunkGeneratorDelegate extends ChunkGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(NMSChunkGeneratorDelegate.class); + private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate; + + private final ChunkGenerator vanilla; + private final ConfigPack pack; + + private final long seed; + + public NMSChunkGeneratorDelegate(ChunkGenerator vanilla, ConfigPack pack, NMSBiomeProvider biomeProvider, long seed) { + super(biomeProvider); + this.delegate = pack.getGeneratorProvider().newInstance(pack); + this.vanilla = vanilla; + this.pack = pack; + this.seed = seed; + } + + @Override + protected @NotNull Codec codec() { + return ChunkGenerator.CODEC; + } + + @Override + public void applyCarvers(@NotNull WorldGenRegion chunkRegion, long seed, @NotNull RandomState noiseConfig, @NotNull BiomeManager world, + @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk, @NotNull Carving carverStep) { + // no-op + } + + @Override + public void buildSurface(@NotNull WorldGenRegion region, @NotNull StructureManager structures, @NotNull RandomState noiseConfig, + @NotNull ChunkAccess chunk) { + // no-op + } + + @Override + public void applyBiomeDecoration(@NotNull WorldGenLevel world, @NotNull ChunkAccess chunk, + @NotNull StructureManager structureAccessor) { + vanilla.applyBiomeDecoration(world, chunk, structureAccessor); + } + + @Override + public void spawnOriginalMobs(@NotNull WorldGenRegion region) { + vanilla.spawnOriginalMobs(region); + } + + @Override + public int getGenDepth() { + return vanilla.getGenDepth(); + } + + @Override + public @NotNull CompletableFuture fillFromNoise(@NotNull Executor executor, @NotNull Blender blender, + @NotNull RandomState noiseConfig, + @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk) { + return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk) + .thenApply(c -> { + LevelAccessor level = Reflection.STRUCTURE_MANAGER.getLevel(structureAccessor); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + PreLoadCompatibilityOptions compatibilityOptions = pack.getContext().get(PreLoadCompatibilityOptions.class); + if(compatibilityOptions.isBeard()) { + beard(structureAccessor, chunk, new BukkitWorldProperties(level.getMinecraftWorld().getWorld()), biomeProvider, compatibilityOptions); + } + return c; + }); + } + + private void beard(StructureManager structureAccessor, ChunkAccess chunk, WorldProperties world, BiomeProvider biomeProvider, + PreLoadCompatibilityOptions compatibilityOptions) { + Beardifier structureWeightSampler = Beardifier.forStructuresInChunk(structureAccessor, chunk.getPos()); + double threshold = compatibilityOptions.getBeardThreshold(); + double airThreshold = compatibilityOptions.getAirThreshold(); + int xi = chunk.getPos().x << 4; + int zi = chunk.getPos().z << 4; + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + int depth = 0; + for(int y = world.getMaxHeight(); y >= world.getMinHeight(); y--) { + double noise = structureWeightSampler.compute(new SinglePointContext(x + xi, y, z + zi)); + if(noise > threshold) { + chunk.setBlockState(new BlockPos(x, y, z), ((CraftBlockData) ((BukkitBlockState) delegate + .getPalette(x + xi, y, z + zi, world, biomeProvider) + .get(depth, x + xi, y, z + zi, world.getSeed())).getHandle()).getState(), false); + depth++; + } else if(noise < airThreshold) { + chunk.setBlockState(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false); + } else { + depth = 0; + } + } + } + } + } + + @Override + public int getSeaLevel() { + return vanilla.getSeaLevel(); + } + + @Override + public int getMinY() { + return vanilla.getMinY(); + } + + @Override + public int getBaseHeight(int x, int z, @NotNull Types heightmap, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) { + WorldProperties properties = new NMSWorldProperties(seed, world); + int y = properties.getMaxHeight(); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + while(y >= getMinY() && !heightmap.isOpaque().test( + ((CraftBlockData) delegate.getBlock(properties, x, y - 1, z, biomeProvider).getHandle()).getState())) { + y--; + } + return y; + } + + @Override + public @NotNull NoiseColumn getBaseColumn(int x, int z, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) { + BlockState[] array = new BlockState[world.getHeight()]; + WorldProperties properties = new NMSWorldProperties(seed, world); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + for(int y = properties.getMaxHeight() - 1; y >= properties.getMinHeight(); y--) { + array[y - properties.getMinHeight()] = ((CraftBlockData) delegate.getBlock(properties, x, y, z, biomeProvider) + .getHandle()).getState(); + } + return new NoiseColumn(getMinY(), array); + } + + @Override + public void addDebugScreenInfo(@NotNull List text, @NotNull RandomState noiseConfig, @NotNull BlockPos pos) { + + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSInitializer.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSInitializer.java new file mode 100644 index 000000000..99797e8ab --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSInitializer.java @@ -0,0 +1,15 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import org.bukkit.Bukkit; + +import com.dfsek.terra.bukkit.PlatformImpl; +import com.dfsek.terra.bukkit.nms.Initializer; + + +public class NMSInitializer implements Initializer { + @Override + public void initialize(PlatformImpl platform) { + AwfulBukkitHacks.registerBiomes(platform.getRawConfigRegistry()); + Bukkit.getPluginManager().registerEvents(new NMSInjectListener(), platform.getPlugin()); + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSInjectListener.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSInjectListener.java new file mode 100644 index 000000000..46b479860 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSInjectListener.java @@ -0,0 +1,48 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.generator.BukkitChunkGeneratorWrapper; + + +public class NMSInjectListener implements Listener { + private static final Logger LOGGER = LoggerFactory.getLogger(NMSInjectListener.class); + private static final Set INJECTED = new HashSet<>(); + private static final ReentrantLock INJECT_LOCK = new ReentrantLock(); + + @EventHandler + public void onWorldInit(WorldInitEvent event) { + if(!INJECTED.contains(event.getWorld()) && + event.getWorld().getGenerator() instanceof BukkitChunkGeneratorWrapper bukkitChunkGeneratorWrapper) { + INJECT_LOCK.lock(); + INJECTED.add(event.getWorld()); + LOGGER.info("Preparing to take over the world: {}", event.getWorld().getName()); + CraftWorld craftWorld = (CraftWorld) event.getWorld(); + ServerLevel serverWorld = craftWorld.getHandle(); + + ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); + + ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); + NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); + + serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla, pack, provider, craftWorld.getSeed()); + + LOGGER.info("Successfully injected into world."); + + INJECT_LOCK.unlock(); + } + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSWorldProperties.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSWorldProperties.java new file mode 100644 index 000000000..0c88eec33 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/NMSWorldProperties.java @@ -0,0 +1,36 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import net.minecraft.world.level.LevelHeightAccessor; + +import com.dfsek.terra.api.world.info.WorldProperties; + + +public class NMSWorldProperties implements WorldProperties { + private final long seed; + private final LevelHeightAccessor height; + + public NMSWorldProperties(long seed, LevelHeightAccessor height) { + this.seed = seed; + this.height = height; + } + + @Override + public Object getHandle() { + return height; + } + + @Override + public long getSeed() { + return seed; + } + + @Override + public int getMaxHeight() { + return height.getMaxBuildHeight(); + } + + @Override + public int getMinHeight() { + return height.getMinBuildHeight(); + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/Reflection.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/Reflection.java new file mode 100644 index 000000000..a10837e11 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/Reflection.java @@ -0,0 +1,52 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.MappedRegistry; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.StructureManager; +import xyz.jpenilla.reflectionremapper.ReflectionRemapper; +import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter; +import xyz.jpenilla.reflectionremapper.proxy.annotation.MethodName; +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies; + +import java.util.Objects; + + +public class Reflection { + public static final MappedRegistryProxy MAPPED_REGISTRY; + public static final StructureManagerProxy STRUCTURE_MANAGER; + + public static final ReferenceProxy REFERENCE; + + static { + ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar(); + ReflectionProxyFactory reflectionProxyFactory = ReflectionProxyFactory.create(reflectionRemapper, + Reflection.class.getClassLoader()); + + MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class); + STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class); + REFERENCE = reflectionProxyFactory.reflectionProxy(ReferenceProxy.class); + } + + + @Proxies(MappedRegistry.class) + public interface MappedRegistryProxy { + @FieldSetter("frozen") + void setFrozen(MappedRegistry instance, boolean frozen); + } + + @Proxies(StructureManager.class) + public interface StructureManagerProxy { + @FieldGetter("level") + LevelAccessor getLevel(StructureManager instance); + } + + @Proxies(Holder.Reference.class) + public interface ReferenceProxy { + @MethodName("bindValue") + void invokeBindValue(Reference instance, T value); + } +} diff --git a/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/RegistryFetcher.java b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/RegistryFetcher.java new file mode 100644 index 000000000..4c5c704b1 --- /dev/null +++ b/platforms/bukkit/nms/v1_19_R3/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R3/RegistryFetcher.java @@ -0,0 +1,25 @@ +package com.dfsek.terra.bukkit.nms.v1_19_R3; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R3.CraftServer; + + +public class RegistryFetcher { + private static Registry getRegistry(ResourceKey> key) { + CraftServer craftserver = (CraftServer) Bukkit.getServer(); + DedicatedServer dedicatedserver = craftserver.getServer(); + return dedicatedserver + .registryAccess() + .registryOrThrow(key); + } + + public static Registry biomeRegistry() { + return getRegistry(Registries.BIOME); + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/build.gradle.kts b/platforms/bukkit/nms/v1_20_R1/build.gradle.kts new file mode 100644 index 000000000..96f9038d8 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/build.gradle.kts @@ -0,0 +1,17 @@ +apply(plugin = "io.papermc.paperweight.userdev") + +repositories { + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} + +dependencies { + api(project(":platforms:bukkit:common")) + paperDevBundle(Versions.Bukkit.paperDevBundle) + implementation("xyz.jpenilla", "reflection-remapper", "0.1.0-SNAPSHOT") +} + +tasks { + assemble { + dependsOn("reobfJar") + } +} \ No newline at end of file diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/AwfulBukkitHacks.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/AwfulBukkitHacks.java new file mode 100644 index 000000000..c30882958 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/AwfulBukkitHacks.java @@ -0,0 +1,101 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import com.google.common.collect.ImmutableMap; +import com.mojang.serialization.Lifecycle; +import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; +import org.bukkit.NamespacedKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.dfsek.terra.bukkit.world.BukkitPlatformBiome; +import com.dfsek.terra.registry.master.ConfigRegistry; + + +public class AwfulBukkitHacks { + private static final Logger LOGGER = LoggerFactory.getLogger(AwfulBukkitHacks.class); + + private static final Map> terraBiomeMap = new HashMap<>(); + + public static void registerBiomes(ConfigRegistry configRegistry) { + try { + LOGGER.info("Hacking biome registry..."); + WritableRegistry biomeRegistry = (WritableRegistry) RegistryFetcher.biomeRegistry(); + + Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry) biomeRegistry, false); + + configRegistry.forEach(pack -> pack.getRegistry(com.dfsek.terra.api.world.biome.Biome.class).forEach((key, biome) -> { + try { + BukkitPlatformBiome platformBiome = (BukkitPlatformBiome) biome.getPlatformBiome(); + NamespacedKey vanillaBukkitKey = platformBiome.getHandle().getKey(); + ResourceLocation vanillaMinecraftKey = new ResourceLocation(vanillaBukkitKey.getNamespace(), vanillaBukkitKey.getKey()); + Biome platform = NMSBiomeInjector.createBiome(biome, Objects.requireNonNull(biomeRegistry.get(vanillaMinecraftKey))); + + ResourceKey delegateKey = ResourceKey.create( + Registries.BIOME, + new ResourceLocation("terra", NMSBiomeInjector.createBiomeID(pack, key)) + ); + + Reference holder = biomeRegistry.register(delegateKey, platform, Lifecycle.stable()); + Reflection.REFERENCE.invokeBindValue(holder, platform); // IMPORTANT: bind holder. + + platformBiome.getContext().put(new NMSBiomeInfo(delegateKey)); + + terraBiomeMap.computeIfAbsent(vanillaMinecraftKey, i -> new ArrayList<>()).add(delegateKey.location()); + + LOGGER.debug("Registered biome: " + delegateKey); + } catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + })); + + Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry) biomeRegistry, true); // freeze registry again :) + + LOGGER.info("Doing tag garbage...."); + Map, List>> collect = biomeRegistry + .getTags() // streamKeysAndEntries + .collect(HashMap::new, + (map, pair) -> + map.put(pair.getFirst(), new ArrayList<>(pair.getSecond().stream().toList())), + HashMap::putAll); + + terraBiomeMap + .forEach((vb, terraBiomes) -> + NMSBiomeInjector.getEntry(biomeRegistry, vb).ifPresentOrElse( + vanilla -> terraBiomes.forEach( + tb -> NMSBiomeInjector.getEntry(biomeRegistry, tb).ifPresentOrElse( + terra -> { + LOGGER.debug("{} (vanilla for {}): {}", + vanilla.unwrapKey().orElseThrow().location(), + terra.unwrapKey().orElseThrow().location(), + vanilla.tags().toList()); + vanilla.tags() + .forEach(tag -> collect + .computeIfAbsent(tag, t -> new ArrayList<>()) + .add(terra)); + }, + () -> LOGGER.error("No such biome: {}", tb))), + () -> LOGGER.error("No vanilla biome: {}", vb))); + + biomeRegistry.resetTags(); + biomeRegistry.bindTags(ImmutableMap.copyOf(collect)); + + } catch(SecurityException | IllegalArgumentException exception) { + throw new RuntimeException(exception); + } + } +} + diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeInfo.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeInfo.java new file mode 100644 index 000000000..7d8203ce3 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeInfo.java @@ -0,0 +1,10 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; + +import com.dfsek.terra.api.properties.Properties; + + +public record NMSBiomeInfo(ResourceKey biomeKey) implements Properties { +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeInjector.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeInjector.java new file mode 100644 index 000000000..d0c32c7d5 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeInjector.java @@ -0,0 +1,83 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import net.minecraft.core.Holder; +import java.util.stream.Stream; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; + +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.config.VanillaBiomeProperties; + +import static net.minecraft.world.level.biome.Biome.ClimateSettings; +import static net.minecraft.world.level.biome.Biome.Precipitation; + +public class NMSBiomeInjector { + + public static Optional> getEntry(Registry registry, ResourceLocation identifier) { + return registry.getOptional(identifier) + .flatMap(registry::getResourceKey) + .flatMap(registry::getHolder); + } + + public static Biome createBiome(com.dfsek.terra.api.world.biome.Biome biome, Biome vanilla) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Biome.BiomeBuilder builder = new Biome.BiomeBuilder(); + + builder + .downfall(vanilla.climateSettings.downfall()) + .temperature(vanilla.getBaseTemperature()) + .mobSpawnSettings(vanilla.getMobSettings()) + .generationSettings(vanilla.getGenerationSettings()); + + + BiomeSpecialEffects.Builder effects = new BiomeSpecialEffects.Builder(); + + effects.grassColorModifier(vanilla.getSpecialEffects().getGrassColorModifier()); + + VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class); + + effects.fogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getFogColor(), vanilla.getFogColor())) + + .waterColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterColor(), vanilla.getWaterColor())) + + .waterFogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterFogColor(), vanilla.getWaterFogColor())) + + .skyColor(Objects.requireNonNullElse(vanillaBiomeProperties.getSkyColor(), vanilla.getSkyColor())); + + if(vanillaBiomeProperties.getFoliageColor() == null) { + vanilla.getSpecialEffects().getFoliageColorOverride().ifPresent(effects::foliageColorOverride); + } else { + effects.foliageColorOverride(vanillaBiomeProperties.getFoliageColor()); + } + + if(vanillaBiomeProperties.getGrassColor() == null) { + vanilla.getSpecialEffects().getGrassColorOverride().ifPresent(effects::grassColorOverride); + } else { + // grass + effects.grassColorOverride(vanillaBiomeProperties.getGrassColor()); + } + + vanilla.getAmbientLoop().ifPresent(effects::ambientLoopSound); + vanilla.getAmbientAdditions().ifPresent(effects::ambientAdditionsSound); + vanilla.getAmbientMood().ifPresent(effects::ambientMoodSound); + vanilla.getBackgroundMusic().ifPresent(effects::backgroundMusic); + vanilla.getAmbientParticle().ifPresent(effects::ambientParticle); + + builder.specialEffects(effects.build()); + + return builder.build(); + } + + public static String createBiomeID(ConfigPack pack, com.dfsek.terra.api.registry.key.RegistryKey biomeID) { + return pack.getID() + .toLowerCase() + "/" + biomeID.getNamespace().toLowerCase(Locale.ROOT) + "/" + biomeID.getID().toLowerCase(Locale.ROOT); + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeProvider.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeProvider.java new file mode 100644 index 000000000..2f29ac3ef --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSBiomeProvider.java @@ -0,0 +1,47 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Holder; +import java.util.stream.Stream; +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; +import org.jetbrains.annotations.NotNull; + +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; +import com.dfsek.terra.bukkit.world.BukkitPlatformBiome; + + +public class NMSBiomeProvider extends BiomeSource { + private final BiomeProvider delegate; + private final long seed; + private final Registry biomeRegistry = RegistryFetcher.biomeRegistry(); + + public NMSBiomeProvider(BiomeProvider delegate, long seed) { + super(); + this.delegate = delegate; + this.seed = seed; + } + @Override + protected Stream> collectPossibleBiomes() { + return delegate.stream() + .map(biome -> RegistryFetcher.biomeRegistry() + .getHolderOrThrow(((BukkitPlatformBiome) biome.getPlatformBiome()).getContext() + .get(NMSBiomeInfo.class) + .biomeKey())); + } + + @Override + protected @NotNull Codec codec() { + return BiomeSource.CODEC; + } + + @Override + public @NotNull Holder getNoiseBiome(int x, int y, int z, @NotNull Sampler sampler) { + return biomeRegistry.getHolderOrThrow(((BukkitPlatformBiome) delegate.getBiome(x << 2, y << 2, z << 2, seed) + .getPlatformBiome()).getContext() + .get(NMSBiomeInfo.class) + .biomeKey()); + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSChunkGeneratorDelegate.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSChunkGeneratorDelegate.java new file mode 100644 index 000000000..f0e929a5b --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSChunkGeneratorDelegate.java @@ -0,0 +1,170 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.LevelAccessor; +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.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.Beardifier; +import net.minecraft.world.level.levelgen.DensityFunction.SinglePointContext; +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; +import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; +import com.dfsek.terra.api.world.info.WorldProperties; +import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions; +import com.dfsek.terra.bukkit.world.BukkitWorldProperties; +import com.dfsek.terra.bukkit.world.block.data.BukkitBlockState; + + +public class NMSChunkGeneratorDelegate extends ChunkGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(NMSChunkGeneratorDelegate.class); + private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate; + + private final ChunkGenerator vanilla; + private final ConfigPack pack; + + private final long seed; + + public NMSChunkGeneratorDelegate(ChunkGenerator vanilla, ConfigPack pack, NMSBiomeProvider biomeProvider, long seed) { + super(biomeProvider); + this.delegate = pack.getGeneratorProvider().newInstance(pack); + this.vanilla = vanilla; + this.pack = pack; + this.seed = seed; + } + + @Override + protected @NotNull Codec codec() { + return ChunkGenerator.CODEC; + } + + @Override + public void applyCarvers(@NotNull WorldGenRegion chunkRegion, long seed, @NotNull RandomState noiseConfig, @NotNull BiomeManager world, + @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk, @NotNull Carving carverStep) { + // no-op + } + + @Override + public void buildSurface(@NotNull WorldGenRegion region, @NotNull StructureManager structures, @NotNull RandomState noiseConfig, + @NotNull ChunkAccess chunk) { + // no-op + } + + @Override + public void applyBiomeDecoration(@NotNull WorldGenLevel world, @NotNull ChunkAccess chunk, + @NotNull StructureManager structureAccessor) { + vanilla.applyBiomeDecoration(world, chunk, structureAccessor); + } + + @Override + public void spawnOriginalMobs(@NotNull WorldGenRegion region) { + vanilla.spawnOriginalMobs(region); + } + + @Override + public int getGenDepth() { + return vanilla.getGenDepth(); + } + + @Override + public @NotNull CompletableFuture fillFromNoise(@NotNull Executor executor, @NotNull Blender blender, + @NotNull RandomState noiseConfig, + @NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk) { + return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk) + .thenApply(c -> { + LevelAccessor level = Reflection.STRUCTURE_MANAGER.getLevel(structureAccessor); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + PreLoadCompatibilityOptions compatibilityOptions = pack.getContext().get(PreLoadCompatibilityOptions.class); + if(compatibilityOptions.isBeard()) { + beard(structureAccessor, chunk, new BukkitWorldProperties(level.getMinecraftWorld().getWorld()), biomeProvider, compatibilityOptions); + } + return c; + }); + } + + private void beard(StructureManager structureAccessor, ChunkAccess chunk, WorldProperties world, BiomeProvider biomeProvider, + PreLoadCompatibilityOptions compatibilityOptions) { + Beardifier structureWeightSampler = Beardifier.forStructuresInChunk(structureAccessor, chunk.getPos()); + double threshold = compatibilityOptions.getBeardThreshold(); + double airThreshold = compatibilityOptions.getAirThreshold(); + int xi = chunk.getPos().x << 4; + int zi = chunk.getPos().z << 4; + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + int depth = 0; + for(int y = world.getMaxHeight(); y >= world.getMinHeight(); y--) { + double noise = structureWeightSampler.compute(new SinglePointContext(x + xi, y, z + zi)); + if(noise > threshold) { + chunk.setBlockState(new BlockPos(x, y, z), ((CraftBlockData) ((BukkitBlockState) delegate + .getPalette(x + xi, y, z + zi, world, biomeProvider) + .get(depth, x + xi, y, z + zi, world.getSeed())).getHandle()).getState(), false); + depth++; + } else if(noise < airThreshold) { + chunk.setBlockState(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false); + } else { + depth = 0; + } + } + } + } + } + + @Override + public int getSeaLevel() { + return vanilla.getSeaLevel(); + } + + @Override + public int getMinY() { + return vanilla.getMinY(); + } + + @Override + public int getBaseHeight(int x, int z, @NotNull Types heightmap, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) { + WorldProperties properties = new NMSWorldProperties(seed, world); + int y = properties.getMaxHeight(); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + while(y >= getMinY() && !heightmap.isOpaque().test( + ((CraftBlockData) delegate.getBlock(properties, x, y - 1, z, biomeProvider).getHandle()).getState())) { + y--; + } + return y; + } + + @Override + public @NotNull NoiseColumn getBaseColumn(int x, int z, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) { + BlockState[] array = new BlockState[world.getHeight()]; + WorldProperties properties = new NMSWorldProperties(seed, world); + BiomeProvider biomeProvider = pack.getBiomeProvider(); + for(int y = properties.getMaxHeight() - 1; y >= properties.getMinHeight(); y--) { + array[y - properties.getMinHeight()] = ((CraftBlockData) delegate.getBlock(properties, x, y, z, biomeProvider) + .getHandle()).getState(); + } + return new NoiseColumn(getMinY(), array); + } + + @Override + public void addDebugScreenInfo(@NotNull List text, @NotNull RandomState noiseConfig, @NotNull BlockPos pos) { + + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSInitializer.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSInitializer.java new file mode 100644 index 000000000..6fb689ee7 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSInitializer.java @@ -0,0 +1,15 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import org.bukkit.Bukkit; + +import com.dfsek.terra.bukkit.PlatformImpl; +import com.dfsek.terra.bukkit.nms.Initializer; + + +public class NMSInitializer implements Initializer { + @Override + public void initialize(PlatformImpl platform) { + AwfulBukkitHacks.registerBiomes(platform.getRawConfigRegistry()); + Bukkit.getPluginManager().registerEvents(new NMSInjectListener(), platform.getPlugin()); + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSInjectListener.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSInjectListener.java new file mode 100644 index 000000000..3b2fbb0f8 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSInjectListener.java @@ -0,0 +1,48 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import com.dfsek.terra.api.config.ConfigPack; +import com.dfsek.terra.bukkit.generator.BukkitChunkGeneratorWrapper; + + +public class NMSInjectListener implements Listener { + private static final Logger LOGGER = LoggerFactory.getLogger(NMSInjectListener.class); + private static final Set INJECTED = new HashSet<>(); + private static final ReentrantLock INJECT_LOCK = new ReentrantLock(); + + @EventHandler + public void onWorldInit(WorldInitEvent event) { + if(!INJECTED.contains(event.getWorld()) && + event.getWorld().getGenerator() instanceof BukkitChunkGeneratorWrapper bukkitChunkGeneratorWrapper) { + INJECT_LOCK.lock(); + INJECTED.add(event.getWorld()); + LOGGER.info("Preparing to take over the world: {}", event.getWorld().getName()); + CraftWorld craftWorld = (CraftWorld) event.getWorld(); + ServerLevel serverWorld = craftWorld.getHandle(); + + ConfigPack pack = bukkitChunkGeneratorWrapper.getPack(); + + ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator(); + NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed()); + + serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla, pack, provider, craftWorld.getSeed()); + + LOGGER.info("Successfully injected into world."); + + INJECT_LOCK.unlock(); + } + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSWorldProperties.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSWorldProperties.java new file mode 100644 index 000000000..9754c7627 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/NMSWorldProperties.java @@ -0,0 +1,36 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import net.minecraft.world.level.LevelHeightAccessor; + +import com.dfsek.terra.api.world.info.WorldProperties; + + +public class NMSWorldProperties implements WorldProperties { + private final long seed; + private final LevelHeightAccessor height; + + public NMSWorldProperties(long seed, LevelHeightAccessor height) { + this.seed = seed; + this.height = height; + } + + @Override + public Object getHandle() { + return height; + } + + @Override + public long getSeed() { + return seed; + } + + @Override + public int getMaxHeight() { + return height.getMaxBuildHeight(); + } + + @Override + public int getMinHeight() { + return height.getMinBuildHeight(); + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/Reflection.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/Reflection.java new file mode 100644 index 000000000..6197e6e01 --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/Reflection.java @@ -0,0 +1,50 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.MappedRegistry; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.StructureManager; +import xyz.jpenilla.reflectionremapper.ReflectionRemapper; +import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter; +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter; +import xyz.jpenilla.reflectionremapper.proxy.annotation.MethodName; +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies; + + +public class Reflection { + public static final MappedRegistryProxy MAPPED_REGISTRY; + public static final StructureManagerProxy STRUCTURE_MANAGER; + + public static final ReferenceProxy REFERENCE; + + static { + ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar(); + ReflectionProxyFactory reflectionProxyFactory = ReflectionProxyFactory.create(reflectionRemapper, + Reflection.class.getClassLoader()); + + MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class); + STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class); + REFERENCE = reflectionProxyFactory.reflectionProxy(ReferenceProxy.class); + } + + + @Proxies(MappedRegistry.class) + public interface MappedRegistryProxy { + @FieldSetter("frozen") + void setFrozen(MappedRegistry instance, boolean frozen); + } + + @Proxies(StructureManager.class) + public interface StructureManagerProxy { + @FieldGetter("level") + LevelAccessor getLevel(StructureManager instance); + } + + @Proxies(Holder.Reference.class) + public interface ReferenceProxy { + @MethodName("bindValue") + void invokeBindValue(Reference instance, T value); + } +} diff --git a/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/RegistryFetcher.java b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/RegistryFetcher.java new file mode 100644 index 000000000..9c7de5d8a --- /dev/null +++ b/platforms/bukkit/nms/v1_20_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_20_R1/RegistryFetcher.java @@ -0,0 +1,24 @@ +package com.dfsek.terra.bukkit.nms.v1_20_R1; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.level.biome.Biome; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R1.CraftServer; + + +public class RegistryFetcher { + private static Registry getRegistry(ResourceKey> key) { + CraftServer craftserver = (CraftServer) Bukkit.getServer(); + DedicatedServer dedicatedserver = craftserver.getServer(); + return dedicatedserver + .registryAccess() + .registryOrThrow(key); + } + + public static Registry biomeRegistry() { + return getRegistry(Registries.BIOME); + } +} diff --git a/platforms/fabric/build.gradle.kts b/platforms/fabric/build.gradle.kts index b457594d8..72abe20f9 100644 --- a/platforms/fabric/build.gradle.kts +++ b/platforms/fabric/build.gradle.kts @@ -39,8 +39,6 @@ dependencies { modImplementation("cloud.commandframework", "cloud-fabric", Versions.Libraries.cloud) include("cloud.commandframework", "cloud-fabric", Versions.Libraries.cloud) - - modLocalRuntime("com.github.astei:lazydfu:${Versions.Mod.lazyDfu}") } loom { diff --git a/platforms/fabric/src/main/resources/fabric.mod.json b/platforms/fabric/src/main/resources/fabric.mod.json index e71bcfeac..cbb32e4cc 100644 --- a/platforms/fabric/src/main/resources/fabric.mod.json +++ b/platforms/fabric/src/main/resources/fabric.mod.json @@ -28,6 +28,6 @@ "depends": { "fabricloader": ">=0.14.2", "java": ">=17", - "minecraft": "1.19.x" + "minecraft": "1.20.x" } } \ No newline at end of file diff --git a/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgeEntryPoint.java b/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgeEntryPoint.java index b342e5a11..36446a08d 100644 --- a/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgeEntryPoint.java +++ b/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgeEntryPoint.java @@ -17,8 +17,9 @@ package com.dfsek.terra.forge; +import net.minecraft.registry.RegistryKeys; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import net.minecraft.world.biome.Biome; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.IEventBus; @@ -71,12 +72,12 @@ public class ForgeEntryPoint { public void registerBiomes(RegisterEvent event) { event.register(Keys.BLOCKS, helper -> sanityCheck.progress(RegistryStep.BLOCK, () -> logger.debug("Block registration detected."))); event.register(Keys.BIOMES, helper -> sanityCheck.progress(RegistryStep.BIOME, () -> initialize(helper))); - event.register(Registry.WORLD_PRESET_KEY, + event.register(RegistryKeys.WORLD_PRESET, helper -> sanityCheck.progress(RegistryStep.WORLD_TYPE, () -> TERRA_PLUGIN.registerWorldTypes(helper::register))); - event.register(Registry.CHUNK_GENERATOR_KEY, + event.register(RegistryKeys.CHUNK_GENERATOR, helper -> helper.register(new Identifier("terra:terra"), Codecs.MINECRAFT_CHUNK_GENERATOR_WRAPPER)); - event.register(Registry.BIOME_SOURCE_KEY, helper -> helper.register(new Identifier("terra:terra"), Codecs.TERRA_BIOME_SOURCE)); + event.register(RegistryKeys.BIOME_SOURCE, helper -> helper.register(new Identifier("terra:terra"), Codecs.TERRA_BIOME_SOURCE)); } } diff --git a/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgePlatform.java b/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgePlatform.java index 2d8b51c9a..5f854740e 100644 --- a/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgePlatform.java +++ b/platforms/forge/src/main/java/com/dfsek/terra/forge/ForgePlatform.java @@ -21,8 +21,16 @@ import ca.solostudios.strata.Versions; import ca.solostudios.strata.parser.tokenizer.ParseException; import ca.solostudios.strata.version.Version; import net.minecraft.MinecraftVersion; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; import net.minecraft.server.MinecraftServer; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterList; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.server.ServerLifecycleHooks; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -86,7 +94,7 @@ public class ForgePlatform extends ModPlatform { super.platformAddon().forEach(addons::add); - String mcVersion = MinecraftVersion.CURRENT.getReleaseTarget(); + String mcVersion = MinecraftVersion.CURRENT.getName(); try { addons.add(new EphemeralAddon(Versions.parseVersion(mcVersion), "minecraft")); } catch(ParseException e) { @@ -122,4 +130,24 @@ public class ForgePlatform extends ModPlatform { public BaseAddon getPlatformAddon() { return new ForgeAddon(this); } + + @Override + public Registry dimensionTypeRegistry() { + return null; + } + + @Override + public Registry biomeRegistry() { + return null; + } + + @Override + public Registry chunkGeneratorSettingsRegistry() { + return null; + } + + @Override + public Registry multiNoiseBiomeSourceParameterListRegistry() { + return null; + } } diff --git a/platforms/forge/src/main/java/com/dfsek/terra/forge/mixin/lifecycle/NoiseConfigMixin.java b/platforms/forge/src/main/java/com/dfsek/terra/forge/mixin/lifecycle/NoiseConfigMixin.java index 1bdffca2e..8762d51ed 100644 --- a/platforms/forge/src/main/java/com/dfsek/terra/forge/mixin/lifecycle/NoiseConfigMixin.java +++ b/platforms/forge/src/main/java/com/dfsek/terra/forge/mixin/lifecycle/NoiseConfigMixin.java @@ -1,19 +1,26 @@ package com.dfsek.terra.forge.mixin.lifecycle; +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.util.math.noise.DoublePerlinNoiseSampler; +import net.minecraft.util.math.noise.DoublePerlinNoiseSampler.NoiseParameters; import net.minecraft.world.biome.source.util.MultiNoiseUtil.MultiNoiseSampler; import net.minecraft.world.biome.source.util.MultiNoiseUtil.NoiseHypercube; +import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; import net.minecraft.world.gen.densityfunction.DensityFunction; import net.minecraft.world.gen.noise.NoiseConfig; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import java.util.List; import com.dfsek.terra.mod.util.SeedHack; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + /** * Hack to map noise sampler to seeds @@ -22,23 +29,12 @@ import com.dfsek.terra.mod.util.SeedHack; public class NoiseConfigMixin { @Shadow @Final - private long legacyWorldSeed; + private MultiNoiseSampler multiNoiseSampler; - @Redirect(method = "(Lnet/minecraft/world/gen/chunk/ChunkGeneratorSettings;Lnet/minecraft/util/registry/Registry;J)V", - at = @At(value = "NEW", - target = "(Lnet/minecraft/world/gen/densityfunction/DensityFunction;" + - "Lnet/minecraft/world/gen/densityfunction/DensityFunction;" + - "Lnet/minecraft/world/gen/densityfunction/DensityFunction;" + - "Lnet/minecraft/world/gen/densityfunction/DensityFunction;" + - "Lnet/minecraft/world/gen/densityfunction/DensityFunction;" + - "Lnet/minecraft/world/gen/densityfunction/DensityFunction;Ljava/util/List;)" + - "Lnet/minecraft/world/biome/source/util/MultiNoiseUtil$MultiNoiseSampler;")) - private MultiNoiseSampler t(DensityFunction densityFunction, DensityFunction densityFunction2, DensityFunction densityFunction3, - DensityFunction densityFunction4, DensityFunction densityFunction5, DensityFunction densityFunction6, - List list) { - MultiNoiseSampler sampler = new MultiNoiseSampler(densityFunction, densityFunction2, densityFunction3, densityFunction4, - densityFunction5, densityFunction6, list); - SeedHack.register(sampler, legacyWorldSeed); - return sampler; + @Inject(method = "(Lnet/minecraft/world/gen/chunk/ChunkGeneratorSettings;Lnet/minecraft/registry/RegistryEntryLookup;J)V", + at = @At("TAIL")) + private void mapMultiNoise(ChunkGeneratorSettings chunkGeneratorSettings, RegistryEntryLookup noiseParametersLookup, long seed, + CallbackInfo ci) { + SeedHack.register(multiNoiseSampler, seed); } } diff --git a/platforms/forge/src/main/java/com/dfsek/terra/forge/util/BiomeUtil.java b/platforms/forge/src/main/java/com/dfsek/terra/forge/util/BiomeUtil.java index 177a63f8e..9c14f7d1a 100644 --- a/platforms/forge/src/main/java/com/dfsek/terra/forge/util/BiomeUtil.java +++ b/platforms/forge/src/main/java/com/dfsek/terra/forge/util/BiomeUtil.java @@ -1,9 +1,9 @@ package com.dfsek.terra.forge.util; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.BuiltinRegistries; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; import net.minecraft.village.VillagerType; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegisterEvent.RegisterHelper; @@ -39,7 +39,6 @@ public final class BiomeUtil { pack.getCheckedRegistry(Biome.class) .forEach((id, biome) -> registerBiome(biome, pack, id, helper)); }); - MinecraftUtil.registerFlora(BuiltinRegistries.BIOME); logger.info("Terra biomes registered."); } @@ -52,7 +51,8 @@ public final class BiomeUtil { private static void registerBiome(Biome biome, ConfigPack pack, com.dfsek.terra.api.registry.key.RegistryKey id, RegisterHelper helper) { - RegistryKey vanilla = ((ProtoPlatformBiome) biome.getPlatformBiome()).get(BuiltinRegistries.BIOME); + RegistryEntry + vanilla = ForgeRegistries.BIOMES.getHolder(((ProtoPlatformBiome) biome.getPlatformBiome()).getHandle()).orElseThrow(); if(pack.getContext().get(PreLoadCompatibilityOptions.class).useVanillaBiomes()) { @@ -61,7 +61,7 @@ public final class BiomeUtil { VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class); net.minecraft.world.biome.Biome minecraftBiome = MinecraftUtil.createBiome(biome, - ForgeRegistries.BIOMES.getDelegateOrThrow(vanilla) + ForgeRegistries.BIOMES.getDelegateOrThrow(vanilla.getKey().orElseThrow()) .value(), vanillaBiomeProperties); @@ -69,24 +69,20 @@ public final class BiomeUtil { if(ForgeRegistries.BIOMES.containsKey(identifier)) { ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(ForgeRegistries.BIOMES.getHolder(identifier) - .orElseThrow() - .getKey() .orElseThrow()); } else { helper.register(MinecraftUtil.registerKey(identifier).getValue(), minecraftBiome); ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(ForgeRegistries.BIOMES.getHolder(identifier) - .orElseThrow() - .getKey() .orElseThrow()); } - Map villagerMap = VillagerTypeAccessor.getBiomeTypeToIdMap(); + Map, VillagerType> villagerMap = VillagerTypeAccessor.getBiomeTypeToIdMap(); - villagerMap.put(RegistryKey.of(Registry.BIOME_KEY, identifier), + villagerMap.put(RegistryKey.of(RegistryKeys.BIOME, identifier), Objects.requireNonNullElse(vanillaBiomeProperties.getVillagerType(), - villagerMap.getOrDefault(vanilla, VillagerType.PLAINS))); + villagerMap.getOrDefault(vanilla.getKey().orElseThrow(), VillagerType.PLAINS))); - MinecraftUtil.TERRA_BIOME_MAP.computeIfAbsent(vanilla.getValue(), i -> new ArrayList<>()).add(identifier); + MinecraftUtil.TERRA_BIOME_MAP.computeIfAbsent(vanilla.getKey().orElseThrow().getValue(), i -> new ArrayList<>()).add(identifier); } } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/ModPlatform.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/ModPlatform.java index 4a6efa188..161bdf33c 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/ModPlatform.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/ModPlatform.java @@ -5,21 +5,31 @@ import com.dfsek.tectonic.api.depth.DepthTracker; import com.dfsek.tectonic.api.exception.LoadException; import net.minecraft.entity.EntityType; import net.minecraft.entity.SpawnGroup; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; import net.minecraft.server.MinecraftServer; import net.minecraft.sound.BiomeAdditionsSound; import net.minecraft.sound.BiomeMoodSound; import net.minecraft.sound.MusicSound; import net.minecraft.sound.SoundEvent; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.BuiltinRegistries; import net.minecraft.village.VillagerType; +import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome.Precipitation; import net.minecraft.world.biome.Biome.TemperatureModifier; import net.minecraft.world.biome.BiomeEffects.GrassColorModifier; import net.minecraft.world.biome.BiomeParticleConfig; import net.minecraft.world.biome.SpawnSettings; import net.minecraft.world.biome.SpawnSettings.SpawnEntry; +import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterList; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.dimension.DimensionType; import net.minecraft.world.gen.WorldPreset; +import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -57,7 +67,7 @@ public abstract class ModPlatform extends AbstractPlatform { public void registerWorldTypes(BiConsumer registerFunction) { getRawConfigRegistry() - .forEach(pack -> PresetUtil.createDefault(pack).apply(registerFunction)); + .forEach(pack -> PresetUtil.createDefault(pack, this).apply(registerFunction)); } @Override @@ -94,7 +104,7 @@ public abstract class ModPlatform extends AbstractPlatform { private ProtoPlatformBiome parseBiome(String id, DepthTracker tracker) throws LoadException { Identifier identifier = Identifier.tryParse(id); - if(BuiltinRegistries.BIOME.get(identifier) == null) throw new LoadException("Invalid Biome ID: " + identifier, tracker); // failure. + if(!biomeRegistry().containsId(identifier)) throw new LoadException("Invalid Biome ID: " + identifier, tracker); // failure. return new ProtoPlatformBiome(identifier); } @@ -105,6 +115,11 @@ public abstract class ModPlatform extends AbstractPlatform { protected abstract BaseAddon getPlatformAddon(); + public abstract Registry dimensionTypeRegistry(); + public abstract Registry biomeRegistry(); + public abstract Registry chunkGeneratorSettingsRegistry(); + public abstract Registry multiNoiseBiomeSourceParameterListRegistry(); + @Override public @NotNull WorldHandle getWorldHandle() { return worldHandle; diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeAdditionsSoundTemplate.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeAdditionsSoundTemplate.java index e9d4a2038..4a66cf6e5 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeAdditionsSoundTemplate.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeAdditionsSoundTemplate.java @@ -3,6 +3,7 @@ package com.dfsek.terra.mod.config; import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import net.minecraft.registry.Registries; import net.minecraft.sound.BiomeAdditionsSound; import net.minecraft.sound.SoundEvent; @@ -21,7 +22,7 @@ public class BiomeAdditionsSoundTemplate implements ObjectTemplate { if(sound == null || soundCultivationTicks == null || soundSpawnRange == null || soundExtraDistance == null) { return null; } else { - return new BiomeMoodSound(sound, soundCultivationTicks, soundSpawnRange, soundExtraDistance); + return new BiomeMoodSound(Registries.SOUND_EVENT.getEntry(sound), soundCultivationTicks, soundSpawnRange, soundExtraDistance); } } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeParticleConfigTemplate.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeParticleConfigTemplate.java index 538a80870..a48ec5c54 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeParticleConfigTemplate.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/BiomeParticleConfigTemplate.java @@ -6,6 +6,7 @@ import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.command.argument.ParticleEffectArgumentType; +import net.minecraft.registry.Registries; import net.minecraft.world.biome.BiomeParticleConfig; @@ -25,7 +26,7 @@ public class BiomeParticleConfigTemplate implements ObjectTemplate> { @@ -15,6 +16,6 @@ public class EntityTypeTemplate implements ObjectTemplate> { @Override public EntityType get() { - return Registry.ENTITY_TYPE.get(id); + return Registries.ENTITY_TYPE.get(id); } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/MusicSoundTemplate.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/MusicSoundTemplate.java index 17745a9af..b9f563621 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/MusicSoundTemplate.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/MusicSoundTemplate.java @@ -3,6 +3,7 @@ package com.dfsek.terra.mod.config; import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import net.minecraft.registry.Registries; import net.minecraft.sound.MusicSound; import net.minecraft.sound.SoundEvent; @@ -29,7 +30,7 @@ public class MusicSoundTemplate implements ObjectTemplate { if(sound == null || minDelay == null || maxDelay == null || replaceCurrentMusic == null) { return null; } else { - return new MusicSound(sound, minDelay, maxDelay, replaceCurrentMusic); + return new MusicSound(Registries.SOUND_EVENT.getEntry(sound), minDelay, maxDelay, replaceCurrentMusic); } } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/ProtoPlatformBiome.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/ProtoPlatformBiome.java index f9524ef58..11bbae507 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/ProtoPlatformBiome.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/ProtoPlatformBiome.java @@ -17,9 +17,10 @@ package com.dfsek.terra.mod.config; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; +import net.minecraft.registry.Registry; import net.minecraft.world.biome.Biome; import java.util.Objects; @@ -31,7 +32,7 @@ import com.dfsek.terra.mod.util.MinecraftUtil; public class ProtoPlatformBiome implements PlatformBiome { private final Identifier identifier; - private RegistryKey delegate; + private RegistryEntry delegate; public ProtoPlatformBiome(Identifier identifier) { this.identifier = identifier; @@ -42,15 +43,15 @@ public class ProtoPlatformBiome implements PlatformBiome { } @Override - public Object getHandle() { + public Identifier getHandle() { return identifier; } - public RegistryKey getDelegate() { + public RegistryEntry getDelegate() { return delegate; } - public void setDelegate(RegistryKey delegate) { + public void setDelegate(RegistryEntry delegate) { this.delegate = Objects.requireNonNull(delegate); } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/SoundEventTemplate.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/SoundEventTemplate.java index 5e87dc21a..b5b7fe079 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/SoundEventTemplate.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/SoundEventTemplate.java @@ -21,9 +21,9 @@ public class SoundEventTemplate implements ObjectTemplate { if(id == null) { return null; } else if(distanceToTravel == null) { - return new SoundEvent(id); + return SoundEvent.of(id); } else { - return new SoundEvent(id, distanceToTravel); + return SoundEvent.of(id, distanceToTravel); } } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VanillaBiomeProperties.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VanillaBiomeProperties.java index b5b88a6b9..feb3680d2 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VanillaBiomeProperties.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VanillaBiomeProperties.java @@ -52,7 +52,7 @@ public class VanillaBiomeProperties implements ConfigTemplate, Properties { @Value("climate.precipitation") @Default - private Precipitation precipitation = null; + private Boolean precipitation = true; @Value("climate.temperature") @Default @@ -122,7 +122,7 @@ public class VanillaBiomeProperties implements ConfigTemplate, Properties { return particleConfig; } - public Precipitation getPrecipitation() { + public Boolean getPrecipitation() { return precipitation; } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VillagerTypeTemplate.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VillagerTypeTemplate.java index f8cce4832..9aaf3362b 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VillagerTypeTemplate.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/config/VillagerTypeTemplate.java @@ -3,8 +3,9 @@ package com.dfsek.terra.mod.config; import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import net.minecraft.registry.Registries; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import net.minecraft.village.VillagerType; @@ -15,6 +16,6 @@ public class VillagerTypeTemplate implements ObjectTemplate { @Override public VillagerType get() { - return Registry.VILLAGER_TYPE.get(id); + return Registries.VILLAGER_TYPE.get(id); } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/data/Codecs.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/data/Codecs.java index 5f5dd615a..890e3e2b5 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/data/Codecs.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/data/Codecs.java @@ -2,8 +2,8 @@ package com.dfsek.terra.mod.data; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.util.dynamic.RegistryOps; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryOps; import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; import com.dfsek.terra.api.config.ConfigPack; @@ -35,10 +35,7 @@ public final class Codecs { id))))); public static final Codec TERRA_BIOME_SOURCE = RecordCodecBuilder - .create(instance -> instance.group(RegistryOps.createRegistryCodec(Registry.BIOME_KEY) - .fieldOf("biome_registry") - .stable() - .forGetter(TerraBiomeSource::getBiomeRegistry), + .create(instance -> instance.group( CONFIG_PACK.fieldOf("pack") .stable() .forGetter(TerraBiomeSource::getPack)) @@ -47,10 +44,6 @@ public final class Codecs { public static final Codec MINECRAFT_CHUNK_GENERATOR_WRAPPER = RecordCodecBuilder .create( instance -> instance.group( - RegistryOps.createRegistryCodec(Registry.STRUCTURE_SET_KEY) - .fieldOf("structure_registry") - .stable() - .forGetter(MinecraftChunkGeneratorWrapper::getNoiseRegistry), TERRA_BIOME_SOURCE.fieldOf("biome_source") .stable() .forGetter(MinecraftChunkGeneratorWrapper::getBiomeSource), @@ -60,6 +53,7 @@ public final class Codecs { ChunkGeneratorSettings.REGISTRY_CODEC.fieldOf("settings") .stable() .forGetter(MinecraftChunkGeneratorWrapper::getSettings) - ).apply(instance, instance.stable(MinecraftChunkGeneratorWrapper::new)) + ).apply(instance, instance.stable( + MinecraftChunkGeneratorWrapper::new)) ); } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/MinecraftChunkGeneratorWrapper.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/MinecraftChunkGeneratorWrapper.java index 5742120d6..75fd4f498 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/MinecraftChunkGeneratorWrapper.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/MinecraftChunkGeneratorWrapper.java @@ -17,9 +17,12 @@ package com.dfsek.terra.mod.generation; +import com.dfsek.terra.mod.util.SeedHack; + import com.mojang.serialization.Codec; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.structure.StructureSet; import net.minecraft.util.Util; import net.minecraft.util.math.BlockPos; @@ -27,8 +30,7 @@ import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.random.CheckedRandom; import net.minecraft.util.math.random.ChunkRandom; import net.minecraft.util.math.random.RandomSeed; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryEntry; +import net.minecraft.registry.Registry; import net.minecraft.world.ChunkRegion; import net.minecraft.world.HeightLimitView; import net.minecraft.world.Heightmap.Type; @@ -71,15 +73,13 @@ public class MinecraftChunkGeneratorWrapper extends net.minecraft.world.gen.chun private static final Logger logger = LoggerFactory.getLogger(MinecraftChunkGeneratorWrapper.class); private final TerraBiomeSource biomeSource; - private final Registry noiseRegistry; private final RegistryEntry settings; private ChunkGenerator delegate; private ConfigPack pack; - public MinecraftChunkGeneratorWrapper(Registry noiseRegistry, TerraBiomeSource biomeSource, ConfigPack configPack, + public MinecraftChunkGeneratorWrapper(TerraBiomeSource biomeSource, ConfigPack configPack, RegistryEntry settingsSupplier) { - super(noiseRegistry, Optional.empty(), biomeSource); - this.noiseRegistry = noiseRegistry; + super(biomeSource); this.pack = configPack; this.settings = settingsSupplier; @@ -88,10 +88,6 @@ public class MinecraftChunkGeneratorWrapper extends net.minecraft.world.gen.chun this.biomeSource = biomeSource; } - public Registry getNoiseRegistry() { - return noiseRegistry; - } - @Override protected Codec getCodec() { return Codecs.MINECRAFT_CHUNK_GENERATOR_WRAPPER; @@ -137,7 +133,7 @@ public class MinecraftChunkGeneratorWrapper extends net.minecraft.world.gen.chun private void beard(StructureAccessor structureAccessor, Chunk chunk, WorldProperties world, BiomeProvider biomeProvider, PreLoadCompatibilityOptions compatibilityOptions) { - StructureWeightSampler structureWeightSampler = StructureWeightSampler.method_42695(structureAccessor, chunk.getPos()); + StructureWeightSampler structureWeightSampler = StructureWeightSampler.createStructureWeightSampler(structureAccessor, chunk.getPos()); double threshold = compatibilityOptions.getBeardThreshold(); double airThreshold = compatibilityOptions.getAirThreshold(); int xi = chunk.getPos().x << 4; @@ -185,7 +181,7 @@ public class MinecraftChunkGeneratorWrapper extends net.minecraft.world.gen.chun @Override public int getHeight(int x, int z, Type heightmap, HeightLimitView height, NoiseConfig noiseConfig) { - WorldProperties properties = MinecraftAdapter.adapt(height, noiseConfig.getLegacyWorldSeed()); + WorldProperties properties = MinecraftAdapter.adapt(height, SeedHack.getSeed(noiseConfig.getMultiNoiseSampler())); BiomeProvider biomeProvider = pack.getBiomeProvider(); int min = height.getBottomY(); for(int y = height.getTopY() - 1; y >= min; y--) { @@ -199,7 +195,7 @@ public class MinecraftChunkGeneratorWrapper extends net.minecraft.world.gen.chun @Override public VerticalBlockSample getColumnSample(int x, int z, HeightLimitView height, NoiseConfig noiseConfig) { BlockState[] array = new BlockState[height.getHeight()]; - WorldProperties properties = MinecraftAdapter.adapt(height, noiseConfig.getLegacyWorldSeed()); + WorldProperties properties = MinecraftAdapter.adapt(height, SeedHack.getSeed(noiseConfig.getMultiNoiseSampler())); BiomeProvider biomeProvider = pack.getBiomeProvider(); for(int y = height.getTopY() - 1; y >= height.getBottomY(); y--) { array[y - height.getBottomY()] = (BlockState) delegate.getBlock(properties, x, y, z, biomeProvider); diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/TerraBiomeSource.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/TerraBiomeSource.java index 5730cee80..d4a285edd 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/TerraBiomeSource.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/TerraBiomeSource.java @@ -18,13 +18,15 @@ package com.dfsek.terra.mod.generation; import com.mojang.serialization.Codec; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryEntry; +import net.minecraft.registry.Registry; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.source.BiomeSource; import net.minecraft.world.biome.source.util.MultiNoiseUtil.MultiNoiseSampler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.dfsek.terra.api.config.ConfigPack; @@ -37,16 +39,9 @@ import com.dfsek.terra.mod.util.SeedHack; public class TerraBiomeSource extends BiomeSource { private static final Logger LOGGER = LoggerFactory.getLogger(TerraBiomeSource.class); - private final Registry biomeRegistry; private ConfigPack pack; - public TerraBiomeSource(Registry biomes, ConfigPack pack) { - super(StreamSupport - .stream(pack.getBiomeProvider() - .getBiomes() - .spliterator(), false) - .map(b -> biomes.getOrCreateEntry(((ProtoPlatformBiome) b.getPlatformBiome()).getDelegate()))); - this.biomeRegistry = biomes; + public TerraBiomeSource(ConfigPack pack) { this.pack = pack; LOGGER.debug("Biomes: " + getBiomes()); @@ -58,23 +53,26 @@ public class TerraBiomeSource extends BiomeSource { } @Override - public RegistryEntry getBiome(int biomeX, int biomeY, int biomeZ, MultiNoiseSampler noiseSampler) { - return biomeRegistry - .entryOf(((ProtoPlatformBiome) pack + protected Stream> biomeStream() { + return StreamSupport + .stream(pack.getBiomeProvider() + .getBiomes() + .spliterator(), false) + .map(b -> ((ProtoPlatformBiome) b.getPlatformBiome()).getDelegate()); + } + + @Override + public RegistryEntry getBiome(int biomeX, int biomeY, int biomeZ, MultiNoiseSampler noiseSampler) { + return ((ProtoPlatformBiome) pack .getBiomeProvider() .getBiome(biomeX << 2, biomeY << 2, biomeZ << 2, SeedHack.getSeed(noiseSampler)) - .getPlatformBiome()).getDelegate() - ); + .getPlatformBiome()).getDelegate(); } public BiomeProvider getProvider() { return pack.getBiomeProvider(); } - public Registry getBiomeRegistry() { - return biomeRegistry; - } - public ConfigPack getPack() { return pack; } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftItemHandle.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftItemHandle.java index 5d26b6c43..66582857a 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftItemHandle.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftItemHandle.java @@ -21,8 +21,11 @@ import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.argument.ItemStackArgumentType; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryWrapper; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import java.util.Set; import java.util.stream.Collectors; @@ -38,8 +41,12 @@ public class MinecraftItemHandle implements ItemHandle { @Override public Item createItem(String data) { try { - return (Item) new ItemStackArgumentType(new CommandRegistryAccess( - CommonPlatform.get().getServer().getRegistryManager())).parse(new StringReader(data)).getItem(); + return (Item) new ItemStackArgumentType(new CommandRegistryAccess() { + @Override + public RegistryWrapper createWrapper(RegistryKey> registryRef) { + return CommonPlatform.get().getServer().getRegistryManager().getWrapperOrThrow(registryRef); + } + }).parse(new StringReader(data)).getItem(); } catch(CommandSyntaxException e) { throw new IllegalArgumentException("Invalid item data \"" + data + "\"", e); } @@ -47,11 +54,11 @@ public class MinecraftItemHandle implements ItemHandle { @Override public Enchantment getEnchantment(String id) { - return (Enchantment) (Registry.ENCHANTMENT.get(Identifier.tryParse(id))); + return (Enchantment) (Registries.ENCHANTMENT.get(Identifier.tryParse(id))); } @Override public Set getEnchantments() { - return Registry.ENCHANTMENT.stream().map(enchantment -> (Enchantment) enchantment).collect(Collectors.toSet()); + return Registries.ENCHANTMENT.stream().map(enchantment -> (Enchantment) enchantment).collect(Collectors.toSet()); } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftWorldHandle.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftWorldHandle.java index 13eef4b2d..aabc38471 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftWorldHandle.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/handle/MinecraftWorldHandle.java @@ -20,8 +20,9 @@ package com.dfsek.terra.mod.handle; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.Blocks; import net.minecraft.command.argument.BlockArgumentParser; +import net.minecraft.registry.Registries; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import org.jetbrains.annotations.NotNull; import com.dfsek.terra.api.block.state.BlockState; @@ -36,7 +37,7 @@ public class MinecraftWorldHandle implements WorldHandle { @Override public @NotNull BlockState createBlockState(@NotNull String data) { try { - net.minecraft.block.BlockState state = BlockArgumentParser.block(Registry.BLOCK, data, true).blockState(); + net.minecraft.block.BlockState state = BlockArgumentParser.block(Registries.BLOCK.getReadOnlyWrapper(), data, true).blockState(); if(state == null) throw new IllegalArgumentException("Invalid data: " + data); return (BlockState) state; } catch(CommandSyntaxException e) { @@ -53,6 +54,6 @@ public class MinecraftWorldHandle implements WorldHandle { public @NotNull EntityType getEntity(@NotNull String id) { Identifier identifier = Identifier.tryParse(id); if(identifier == null) identifier = Identifier.tryParse(id); - return (EntityType) Registry.ENTITY_TYPE.get(identifier); + return (EntityType) Registries.ENTITY_TYPE.get(identifier); } } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/access/VillagerTypeAccessor.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/access/VillagerTypeAccessor.java index 477c0647b..68ae33e35 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/access/VillagerTypeAccessor.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/access/VillagerTypeAccessor.java @@ -1,6 +1,6 @@ package com.dfsek.terra.mod.mixin.access; -import net.minecraft.util.registry.RegistryKey; +import net.minecraft.registry.RegistryKey; import net.minecraft.village.VillagerType; import net.minecraft.world.biome.Biome; import org.spongepowered.asm.mixin.Mixin; diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/MobSpawnerBlockEntityMixin.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/MobSpawnerBlockEntityMixin.java index f28c27a62..95fe9154c 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/MobSpawnerBlockEntityMixin.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/MobSpawnerBlockEntityMixin.java @@ -21,9 +21,11 @@ import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.MobSpawnerBlockEntity; +import net.minecraft.registry.Registries; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; +import net.minecraft.util.math.random.Random; import net.minecraft.world.MobSpawnerLogic; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Implements; @@ -48,13 +50,16 @@ public abstract class MobSpawnerBlockEntityMixin extends BlockEntity { @Shadow public abstract MobSpawnerLogic getLogic(); + @Shadow + public abstract void setEntityType(net.minecraft.entity.EntityType entityType, Random random); + public EntityType terra$getSpawnedType() { - return (EntityType) Registry.ENTITY_TYPE.get( + return (EntityType) Registries.ENTITY_TYPE.get( Identifier.tryParse(((MobSpawnerLogicAccessor) getLogic()).getSpawnEntry().getNbt().getString("id"))); } public void terra$setSpawnedType(@NotNull EntityType creatureType) { - getLogic().setEntityId((net.minecraft.entity.EntityType) creatureType); + setEntityType((net.minecraft.entity.EntityType) creatureType, world.getRandom()); } public int terra$getDelay() { diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/SignBlockEntityMixin.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/SignBlockEntityMixin.java index e32d7e053..fe8d2cbab 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/SignBlockEntityMixin.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/entity/SignBlockEntityMixin.java @@ -18,6 +18,7 @@ package com.dfsek.terra.mod.mixin.implementations.terra.block.entity; import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.block.entity.SignText; import net.minecraft.text.Text; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Final; @@ -34,17 +35,17 @@ import com.dfsek.terra.api.block.entity.Sign; @Implements(@Interface(iface = Sign.class, prefix = "terra$")) public abstract class SignBlockEntityMixin { @Shadow - @Final - private Text[] texts; + private SignText frontText; @Shadow - public abstract void setTextOnRow(int row, Text text); + public abstract boolean setText(SignText text, boolean front); public void terra$setLine(int index, @NotNull String line) throws IndexOutOfBoundsException { - setTextOnRow(index, Text.literal(line)); + setText(frontText.withMessage(index, Text.literal(line)), true); } public @NotNull String[] terra$getLines() { + Text[] texts = frontText.getMessages(false); String[] lines = new String[texts.length]; for(int i = 0; i < texts.length; i++) { lines[i] = texts[i].getString(); @@ -53,7 +54,7 @@ public abstract class SignBlockEntityMixin { } public @NotNull String terra$getLine(int index) throws IndexOutOfBoundsException { - return texts[index].getString(); + return frontText.getMessage(index, false).getString(); } public void terra$applyState(String state) { diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/state/BlockStateMixin.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/state/BlockStateMixin.java index 636a3f721..2ec3cf649 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/state/BlockStateMixin.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/implementations/terra/block/state/BlockStateMixin.java @@ -5,8 +5,9 @@ import com.google.common.collect.ImmutableMap; import com.mojang.serialization.MapCodec; import net.minecraft.block.AbstractBlock.AbstractBlockState; import net.minecraft.block.Block; +import net.minecraft.registry.Registries; import net.minecraft.state.State; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import org.spongepowered.asm.mixin.Implements; import org.spongepowered.asm.mixin.Interface; import org.spongepowered.asm.mixin.Intrinsic; @@ -66,7 +67,7 @@ public abstract class BlockStateMixin extends State { NbtCompound eTag = (NbtCompound) enchantment; - map.put((Enchantment) Registry.ENCHANTMENT.get(eTag.getInt("id")), eTag.getInt("lvl")); + map.put((Enchantment) Registries.ENCHANTMENT.get(eTag.getInt("id")), eTag.getInt("lvl")); }); return map; } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/lifecycle/DataPackContentsMixin.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/lifecycle/DataPackContentsMixin.java index a9ecb9354..d4a27e750 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/lifecycle/DataPackContentsMixin.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/mixin/lifecycle/DataPackContentsMixin.java @@ -1,8 +1,10 @@ package com.dfsek.terra.mod.mixin.lifecycle; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKeys; import net.minecraft.server.DataPackContents; -import net.minecraft.util.registry.DynamicRegistryManager; -import net.minecraft.util.registry.Registry; import net.minecraft.world.biome.Biome; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -18,11 +20,11 @@ public class DataPackContentsMixin { /* * #refresh populates all tags in the registries */ - @Inject(method = "refresh(Lnet/minecraft/util/registry/DynamicRegistryManager;)V", at = @At("RETURN")) + @Inject(method = "refresh(Lnet/minecraft/registry/DynamicRegistryManager;)V", at = @At("RETURN")) private void injectReload(DynamicRegistryManager dynamicRegistryManager, CallbackInfo ci) { - TagUtil.registerWorldPresetTags(dynamicRegistryManager.get(Registry.WORLD_PRESET_KEY)); + TagUtil.registerWorldPresetTags(dynamicRegistryManager.get(RegistryKeys.WORLD_PRESET)); - Registry biomeRegistry = dynamicRegistryManager.get(Registry.BIOME_KEY); + Registry biomeRegistry = dynamicRegistryManager.get(RegistryKeys.BIOME); TagUtil.registerBiomeTags(biomeRegistry); MinecraftUtil.registerFlora(biomeRegistry); } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/MinecraftUtil.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/MinecraftUtil.java index 69f91e6f6..803b78b0b 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/MinecraftUtil.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/MinecraftUtil.java @@ -3,11 +3,13 @@ package com.dfsek.terra.mod.util; import net.minecraft.block.entity.LootableContainerBlockEntity; import net.minecraft.block.entity.MobSpawnerBlockEntity; import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryEntry; -import net.minecraft.util.registry.RegistryKey; +import net.minecraft.registry.Registry; import net.minecraft.world.WorldAccess; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome.Builder; @@ -23,6 +25,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import com.dfsek.terra.api.block.entity.BlockEntity; import com.dfsek.terra.api.block.entity.Container; @@ -46,7 +49,7 @@ public final class MinecraftUtil { public static Optional> getEntry(Registry registry, Identifier identifier) { return registry.getOrEmpty(identifier) .flatMap(registry::getKey) - .map(registry::getOrCreateEntry); + .flatMap(registry::getEntry); } public static BlockEntity createState(WorldAccess worldAccess, BlockPos pos) { @@ -91,7 +94,7 @@ public final class MinecraftUtil { } public static RegistryKey registerKey(Identifier identifier) { - return RegistryKey.of(Registry.BIOME_KEY, identifier); + return RegistryKey.of(RegistryKeys.BIOME, identifier); } public static Biome createBiome(com.dfsek.terra.api.world.biome.Biome biome, Biome vanilla, @@ -131,7 +134,7 @@ public final class MinecraftUtil { if(vanillaBiomeProperties.getLoopSound() == null) { vanilla.getEffects().getLoopSound().ifPresent(effects::loopSound); } else { - effects.loopSound(vanillaBiomeProperties.getLoopSound()); + effects.loopSound(Registries.SOUND_EVENT.getEntry(vanillaBiomeProperties.getLoopSound())); } if(vanillaBiomeProperties.getMoodSound() == null) { @@ -152,11 +155,11 @@ public final class MinecraftUtil { effects.music(vanillaBiomeProperties.getMusic()); } - builder.precipitation(Objects.requireNonNullElse(vanillaBiomeProperties.getPrecipitation(), vanilla.getPrecipitation())); + builder.precipitation(Objects.requireNonNullElse(vanillaBiomeProperties.getPrecipitation(), vanilla.hasPrecipitation())); builder.temperature(Objects.requireNonNullElse(vanillaBiomeProperties.getTemperature(), vanilla.getTemperature())); - builder.downfall(Objects.requireNonNullElse(vanillaBiomeProperties.getDownfall(), vanilla.getDownfall())); + builder.downfall(Objects.requireNonNullElse(vanillaBiomeProperties.getDownfall(), ((BiomeAccessor) ((Object) vanilla)).getWeather().downfall())); builder.temperatureModifier(Objects.requireNonNullElse(vanillaBiomeProperties.getTemperatureModifier(), ((BiomeAccessor) ((Object) vanilla)).getWeather().temperatureModifier())); diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/PresetUtil.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/PresetUtil.java index f4fd942c8..fb061b67d 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/PresetUtil.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/PresetUtil.java @@ -1,13 +1,11 @@ package com.dfsek.terra.mod.util; -import net.minecraft.structure.StructureSet; +import net.minecraft.registry.Registry; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.util.Identifier; -import net.minecraft.util.math.noise.DoublePerlinNoiseSampler.NoiseParameters; -import net.minecraft.util.registry.BuiltinRegistries; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryEntry; -import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.source.MultiNoiseBiomeSource; +import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterList; +import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterLists; import net.minecraft.world.biome.source.TheEndBiomeSource; import net.minecraft.world.dimension.DimensionOptions; import net.minecraft.world.dimension.DimensionType; @@ -26,6 +24,7 @@ import java.util.Map; import com.dfsek.terra.api.config.ConfigPack; import com.dfsek.terra.api.util.generic.pair.Pair; +import com.dfsek.terra.mod.ModPlatform; import com.dfsek.terra.mod.generation.MinecraftChunkGeneratorWrapper; import com.dfsek.terra.mod.generation.TerraBiomeSource; @@ -34,54 +33,48 @@ public class PresetUtil { private static final Logger LOGGER = LoggerFactory.getLogger(PresetUtil.class); private static final List PRESETS = new ArrayList<>(); - public static Pair createDefault(ConfigPack pack) { - Registry dimensionTypeRegistry = BuiltinRegistries.DIMENSION_TYPE; - Registry chunkGeneratorSettingsRegistry = BuiltinRegistries.CHUNK_GENERATOR_SETTINGS; - Registry structureSetRegistry = BuiltinRegistries.STRUCTURE_SET; - Registry noiseParametersRegistry = BuiltinRegistries.NOISE_PARAMETERS; - Registry biomeRegistry = BuiltinRegistries.BIOME; + public static Pair createDefault(ConfigPack pack, ModPlatform platform) { + Registry dimensionTypeRegistry = platform.dimensionTypeRegistry(); + Registry chunkGeneratorSettingsRegistry = platform.chunkGeneratorSettingsRegistry(); + Registry multiNoiseBiomeSourceParameterLists = + platform.multiNoiseBiomeSourceParameterListRegistry(); - RegistryEntry theNetherDimensionType = dimensionTypeRegistry.getOrCreateEntry(DimensionTypes.THE_NETHER); - RegistryEntry - netherChunkGeneratorSettings = chunkGeneratorSettingsRegistry.getOrCreateEntry(ChunkGeneratorSettings.NETHER); - DimensionOptions netherDimensionOptions = new DimensionOptions(theNetherDimensionType, - new NoiseChunkGenerator(structureSetRegistry, - noiseParametersRegistry, - MultiNoiseBiomeSource.Preset.NETHER.getBiomeSource( - biomeRegistry), - netherChunkGeneratorSettings)); - RegistryEntry theEndDimensionType = dimensionTypeRegistry.getOrCreateEntry(DimensionTypes.THE_END); - RegistryEntry endChunkGeneratorSettings = chunkGeneratorSettingsRegistry.getOrCreateEntry( - ChunkGeneratorSettings.END); - DimensionOptions endDimensionOptions = new DimensionOptions(theEndDimensionType, - new NoiseChunkGenerator(structureSetRegistry, noiseParametersRegistry, - new TheEndBiomeSource(biomeRegistry), - endChunkGeneratorSettings)); - RegistryEntry overworldDimensionType = dimensionTypeRegistry.getOrCreateEntry(DimensionTypes.OVERWORLD); + RegistryEntry overworldDimensionType = dimensionTypeRegistry.getEntry(DimensionTypes.OVERWORLD).orElseThrow(); + RegistryEntry overworld = chunkGeneratorSettingsRegistry.getEntry(ChunkGeneratorSettings.OVERWORLD) + .orElseThrow(); - RegistryEntry overworld = chunkGeneratorSettingsRegistry.getOrCreateEntry(ChunkGeneratorSettings.OVERWORLD); Identifier generatorID = Identifier.of("terra", pack.getID().toLowerCase(Locale.ROOT) + "/" + pack.getNamespace().toLowerCase( Locale.ROOT)); PRESETS.add(generatorID); - TerraBiomeSource biomeSource = new TerraBiomeSource(biomeRegistry, pack); - ChunkGenerator generator = new MinecraftChunkGeneratorWrapper(structureSetRegistry, biomeSource, pack, overworld); + RegistryEntry registryEntry = dimensionTypeRegistry.getEntry(DimensionTypes.THE_NETHER).orElseThrow(); + RegistryEntry.Reference reference = multiNoiseBiomeSourceParameterLists.getEntry(MultiNoiseBiomeSourceParameterLists.NETHER).orElseThrow(); + RegistryEntry registryEntry2 = chunkGeneratorSettingsRegistry.getEntry(ChunkGeneratorSettings.NETHER).orElseThrow(); + + RegistryEntry registryEntry3 = dimensionTypeRegistry.getEntry(DimensionTypes.THE_END).orElseThrow(); + RegistryEntry registryEntry4 = chunkGeneratorSettingsRegistry.getEntry(ChunkGeneratorSettings.END).orElseThrow(); + + TerraBiomeSource biomeSource = new TerraBiomeSource(pack); + ChunkGenerator generator = new MinecraftChunkGeneratorWrapper(biomeSource, pack, overworld); DimensionOptions dimensionOptions = new DimensionOptions(overworldDimensionType, generator); - WorldPreset preset = new WorldPreset( - Map.of( - DimensionOptions.OVERWORLD, dimensionOptions, - DimensionOptions.NETHER, netherDimensionOptions, - DimensionOptions.END, endDimensionOptions - ) - ); + DimensionOptions netherDimensionOptions = new DimensionOptions(registryEntry, new NoiseChunkGenerator(MultiNoiseBiomeSource.create(reference), registryEntry2)); + DimensionOptions endDimensionOptions = new DimensionOptions(registryEntry3, new NoiseChunkGenerator(TheEndBiomeSource.createVanilla(platform.biomeRegistry().getReadOnlyWrapper()), registryEntry4)); + + WorldPreset preset = createPreset(dimensionOptions, netherDimensionOptions, endDimensionOptions); LOGGER.info("Created world type \"{}\"", generatorID); return Pair.of(generatorID, preset); } + private static WorldPreset createPreset(DimensionOptions dimensionOptions, DimensionOptions netherDimensionOptions, DimensionOptions endDimensionOptions) { + return new WorldPreset( + Map.of(DimensionOptions.OVERWORLD, dimensionOptions, DimensionOptions.NETHER, netherDimensionOptions, DimensionOptions.END, endDimensionOptions) + ); + } + public static List getPresets() { return PRESETS; } diff --git a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/TagUtil.java b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/TagUtil.java index 8edf50afc..81289fdfb 100644 --- a/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/TagUtil.java +++ b/platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/TagUtil.java @@ -1,10 +1,10 @@ package com.dfsek.terra.mod.util; import com.google.common.collect.ImmutableMap; -import net.minecraft.tag.TagKey; -import net.minecraft.tag.WorldPresetTags; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.Registry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.registry.tag.WorldPresetTags; import net.minecraft.world.biome.Biome; import net.minecraft.world.gen.WorldPreset; import org.slf4j.Logger; diff --git a/platforms/mixin-common/src/main/resources/terra.accesswidener b/platforms/mixin-common/src/main/resources/terra.accesswidener index 4f59bb962..d638bbf45 100644 --- a/platforms/mixin-common/src/main/resources/terra.accesswidener +++ b/platforms/mixin-common/src/main/resources/terra.accesswidener @@ -1,2 +1,3 @@ accessWidener v1 named -accessible class net/minecraft/world/biome/Biome$Weather \ No newline at end of file +accessible class net/minecraft/world/biome/Biome$Weather +accessible class net/minecraft/world/gen/WorldPresets$Registrar diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java index f218380d7..81ebcae8a 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java @@ -2,18 +2,28 @@ package com.dfsek.terra.lifecycle; import ca.solostudios.strata.Versions; import ca.solostudios.strata.parser.tokenizer.ParseException; + +import com.dfsek.terra.lifecycle.util.BiomeUtil; + import net.minecraft.MinecraftVersion; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKeys; import net.minecraft.server.MinecraftServer; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterList; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import com.dfsek.terra.addon.EphemeralAddon; import com.dfsek.terra.api.addon.BaseAddon; -import com.dfsek.terra.lifecycle.util.BiomeUtil; import com.dfsek.terra.mod.CommonPlatform; import com.dfsek.terra.mod.ModPlatform; import com.dfsek.terra.mod.generation.MinecraftChunkGeneratorWrapper; @@ -23,6 +33,11 @@ public abstract class LifecyclePlatform extends ModPlatform { private static final Logger LOGGER = LoggerFactory.getLogger(LifecyclePlatform.class); private static MinecraftServer server; + private static final AtomicReference> BIOMES = new AtomicReference<>(); + private static final AtomicReference> DIMENSIONS = new AtomicReference<>(); + private static final AtomicReference> SETTINGS = new AtomicReference<>(); + private static final AtomicReference> NOISE = new AtomicReference<>(); + public LifecyclePlatform() { CommonPlatform.initialize(this); load(); @@ -45,11 +60,11 @@ public abstract class LifecyclePlatform extends ModPlatform { if(server != null) { + BiomeUtil.registerBiomes(server.getRegistryManager().get(RegistryKeys.BIOME)); server.reloadResources(server.getDataPackManager().getNames()).exceptionally(throwable -> { LOGGER.warn("Failed to execute reload", throwable); return null; }).join(); - BiomeUtil.registerBiomes(); server.getWorlds().forEach(world -> { if(world.getChunkManager().getChunkGenerator() instanceof MinecraftChunkGeneratorWrapper chunkGeneratorWrapper) { getConfigRegistry().get(chunkGeneratorWrapper.getPack().getRegistryKey()).ifPresent(pack -> { @@ -62,13 +77,23 @@ public abstract class LifecyclePlatform extends ModPlatform { return succeed; } + public static void setRegistries(Registry biomeRegistry, + Registry dimensionTypeRegistry, + Registry chunkGeneratorSettingsRegistry, + Registry multiNoiseBiomeSourceParameterListRegistry) { + BIOMES.set(biomeRegistry); + DIMENSIONS.set(dimensionTypeRegistry); + SETTINGS.set(chunkGeneratorSettingsRegistry); + NOISE.set(multiNoiseBiomeSourceParameterListRegistry); + } + @Override protected Iterable platformAddon() { List addons = new ArrayList<>(); super.platformAddon().forEach(addons::add); - String mcVersion = MinecraftVersion.CURRENT.getReleaseTarget(); + String mcVersion = MinecraftVersion.CURRENT.getName(); try { addons.add(new EphemeralAddon(Versions.parseVersion(mcVersion), "minecraft")); } catch(ParseException e) { @@ -84,5 +109,25 @@ public abstract class LifecyclePlatform extends ModPlatform { return addons; } + @Override + public Registry dimensionTypeRegistry() { + return DIMENSIONS.get(); + } + + @Override + public Registry biomeRegistry() { + return BIOMES.get(); + } + + @Override + public Registry chunkGeneratorSettingsRegistry() { + return SETTINGS.get(); + } + + @Override + public Registry multiNoiseBiomeSourceParameterListRegistry() { + return NOISE.get(); + } + protected abstract Collection getPlatformMods(); } diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/NoiseConfigMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/NoiseConfigMixin.java index 2793aa500..e8091826a 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/NoiseConfigMixin.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/NoiseConfigMixin.java @@ -1,7 +1,8 @@ package com.dfsek.terra.lifecycle.mixin; +import net.minecraft.registry.RegistryEntryLookup; import net.minecraft.util.math.noise.DoublePerlinNoiseSampler; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import net.minecraft.world.biome.source.util.MultiNoiseUtil.MultiNoiseSampler; import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; import net.minecraft.world.gen.noise.NoiseConfig; @@ -24,10 +25,10 @@ public class NoiseConfigMixin { @Final private MultiNoiseSampler multiNoiseSampler; - @Inject(method = "(Lnet/minecraft/world/gen/chunk/ChunkGeneratorSettings;Lnet/minecraft/util/registry/Registry;J)V", + @Inject(method = "(Lnet/minecraft/world/gen/chunk/ChunkGeneratorSettings;Lnet/minecraft/registry/RegistryEntryLookup;J)V", at = @At("TAIL")) - private void mapMultiNoise(ChunkGeneratorSettings chunkGeneratorSettings, - Registry noiseRegistry, long seed, CallbackInfo ci) { + private void mapMultiNoise(ChunkGeneratorSettings chunkGeneratorSettings, RegistryEntryLookup noiseParametersLookup, long seed, + CallbackInfo ci) { SeedHack.register(multiNoiseSampler, seed); } } diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryEntryReferenceInvoker.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryEntryReferenceInvoker.java new file mode 100644 index 000000000..d84b643cb --- /dev/null +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryEntryReferenceInvoker.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.lifecycle.mixin; + + +import net.minecraft.registry.entry.RegistryEntry.Reference; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + + +@Mixin(Reference.class) +public interface RegistryEntryReferenceInvoker { + @Invoker("setValue") + void invokeSetValue(T value); +} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryMixin.java index 8e2c12c70..a2d516931 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryMixin.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/RegistryMixin.java @@ -1,6 +1,7 @@ package com.dfsek.terra.lifecycle.mixin; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -9,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.dfsek.terra.lifecycle.util.RegistryUtil; -@Mixin(Registry.class) +@Mixin(Registries.class) public class RegistryMixin { @Inject(method = "", at = @At("RETURN")) private static void registerTerraGenerators(CallbackInfo ci) { diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/SimpleRegistryMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/SimpleRegistryMixin.java new file mode 100644 index 000000000..2da9f317d --- /dev/null +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/SimpleRegistryMixin.java @@ -0,0 +1,28 @@ +package com.dfsek.terra.lifecycle.mixin; + +import com.dfsek.terra.lifecycle.util.RegistryHack; + +import net.minecraft.registry.SimpleRegistry; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntry.Reference; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Map; + + +@Mixin(SimpleRegistry.class) +public class SimpleRegistryMixin implements RegistryHack { + @Shadow + @Final + private Map> valueToEntry; + + @Override + public void terra_bind() { + valueToEntry.forEach((value, entry) -> { + //noinspection unchecked + ((RegistryEntryReferenceInvoker) entry).invokeSetValue(value); + }); + } +} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/RegistryLoaderMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/RegistryLoaderMixin.java new file mode 100644 index 000000000..8193a3224 --- /dev/null +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/RegistryLoaderMixin.java @@ -0,0 +1,80 @@ +package com.dfsek.terra.lifecycle.mixin.lifecycle; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.registry.MutableRegistry; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryLoader; +import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterList; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.WorldPreset; +import net.minecraft.world.gen.chunk.ChunkGeneratorSettings; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import com.dfsek.terra.lifecycle.LifecyclePlatform; +import com.dfsek.terra.lifecycle.util.LifecycleUtil; +import com.dfsek.terra.lifecycle.util.RegistryHack; + + +@Mixin(RegistryLoader.class) +public class RegistryLoaderMixin { + @Shadow + @Final + private static Logger LOGGER; + + @Redirect( + method = "load(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/registry/DynamicRegistryManager;Ljava/util/List;)" + + "Lnet/minecraft/registry/DynamicRegistryManager$Immutable;", + at = @At( + value = "INVOKE", + target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V", + ordinal = 1 // we want right after the first forEach + ) + ) + private static void grabManager(List, Object>> instance, Consumer, Object>> consumer) { + instance.forEach(mutableRegistryObjectPair -> LOGGER.debug("{}: {} entries", + mutableRegistryObjectPair.getFirst().toString(), + mutableRegistryObjectPair.getFirst().size()) + ); + extractRegistry(instance, RegistryKeys.BIOME).ifPresent( + biomes -> { // this redirect triggers twice, second time only with dimension registry. don't try extraction second time + MutableRegistry dimensionTypes = extractRegistry(instance, RegistryKeys.DIMENSION_TYPE).orElseThrow(); + MutableRegistry worldPresets = extractRegistry(instance, RegistryKeys.WORLD_PRESET).orElseThrow(); + MutableRegistry chunkGeneratorSettings = extractRegistry(instance, + RegistryKeys.CHUNK_GENERATOR_SETTINGS).orElseThrow(); + MutableRegistry multiNoiseBiomeSourceParameterLists = extractRegistry(instance, RegistryKeys.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST).orElseThrow(); + + LifecyclePlatform.setRegistries(biomes, dimensionTypes, chunkGeneratorSettings, multiNoiseBiomeSourceParameterLists); + LifecycleUtil.initialize(biomes, worldPresets); + }); + instance.forEach(consumer); + } + + @SuppressWarnings("unchecked") + private static Optional> extractRegistry(List, Object>> instance, + RegistryKey> key) { + List> matches = instance + .stream() + .map(Pair::getFirst) + .filter(r -> r.getKey().equals(key)) + .toList(); + if(matches.size() > 1) { + throw new IllegalStateException("Illegal number of registries returned: " + matches); + } else if(matches.isEmpty()) { + return Optional.empty(); + } + MutableRegistry registry = (MutableRegistry) matches.get(0); + ((RegistryHack) registry).terra_bind(); + return Optional.of(registry); + } +} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/SaveLoadingMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/SaveLoadingMixin.java new file mode 100644 index 000000000..260d3c4a2 --- /dev/null +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/SaveLoadingMixin.java @@ -0,0 +1,32 @@ +package com.dfsek.terra.lifecycle.mixin.lifecycle; + +import com.dfsek.terra.lifecycle.util.LifecycleUtil; + +import com.dfsek.terra.mod.util.MinecraftUtil; + +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.server.SaveLoading; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + + +@Mixin(SaveLoading.class) +public class SaveLoadingMixin { + @ModifyArg( + method = "method_42097(Lnet/minecraft/registry/DynamicRegistryManager$Immutable;" + + "Lnet/minecraft/server/SaveLoading$SaveApplierFactory;Lnet/minecraft/resource/LifecycledResourceManager;" + + "Lnet/minecraft/registry/CombinedDynamicRegistries;Lnet/minecraft/server/SaveLoading$LoadContext;" + + "Lnet/minecraft/server/DataPackContents;)Ljava/lang/Object;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/DataPackContents;refresh(Lnet/minecraft/registry/DynamicRegistryManager;)V" + ), + index = 0 + ) + private static DynamicRegistryManager grabManager(DynamicRegistryManager in) { + MinecraftUtil.registerFlora(in.get(RegistryKeys.BIOME)); + return in; + } +} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/client/MinecraftClientMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/client/MinecraftClientMixin.java deleted file mode 100644 index 30f092786..000000000 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/client/MinecraftClientMixin.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is part of Terra. - * - * Terra is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Terra is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Terra. If not, see . - */ - -package com.dfsek.terra.lifecycle.mixin.lifecycle.client; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.RunArgs; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import com.dfsek.terra.lifecycle.util.LifecycleUtil; - - -@Mixin(MinecraftClient.class) -public class MinecraftClientMixin { - @Inject(method = "", at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/util/WindowProvider;createWindow" + - "(Lnet/minecraft/client/WindowSettings;Ljava/lang/String;Ljava/lang/String;)" + - "Lnet/minecraft/client/util/Window;", - // sorta arbitrary position, after mod init, before window opens - shift = At.Shift.BEFORE)) - public void injectConstructor(RunArgs args, CallbackInfo callbackInfo) { - LifecycleUtil.initialize(); - } -} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/server/ServerMainMixin.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/server/ServerMainMixin.java deleted file mode 100644 index e2e52bd2a..000000000 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/server/ServerMainMixin.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of Terra. - * - * Terra is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Terra is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Terra. If not, see . - */ - -package com.dfsek.terra.lifecycle.mixin.lifecycle.server; - -import net.minecraft.server.Main; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import com.dfsek.terra.lifecycle.util.LifecycleUtil; - - -@Mixin(Main.class) -public class ServerMainMixin { - @Inject(method = "main([Ljava/lang/String;)V", - at = @At(value = "INVOKE", - target = "Lnet/minecraft/resource/ResourcePackManager;(Lnet/minecraft/resource/ResourceType;" + - "[Lnet/minecraft/resource/ResourcePackProvider;)V") - // after registry manager creation - ) - private static void injectConstructor(String[] args, CallbackInfo ci) { - LifecycleUtil.initialize(); - } -} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/BiomeUtil.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/BiomeUtil.java index f0d3e06e6..0eadea7f4 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/BiomeUtil.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/BiomeUtil.java @@ -1,9 +1,10 @@ package com.dfsek.terra.lifecycle.util; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.BuiltinRegistries; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; +import net.minecraft.registry.Registry; import net.minecraft.village.VillagerType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,13 +30,12 @@ public final class BiomeUtil { } - public static void registerBiomes() { + public static void registerBiomes(Registry biomeRegistry) { logger.info("Registering biomes..."); CommonPlatform.get().getConfigRegistry().forEach(pack -> { // Register all Terra biomes. pack.getCheckedRegistry(Biome.class) - .forEach((id, biome) -> registerBiome(biome, pack, id)); + .forEach((id, biome) -> registerBiome(biome, pack, id, biomeRegistry)); }); - MinecraftUtil.registerFlora(BuiltinRegistries.BIOME); logger.info("Terra biomes registered."); } @@ -46,36 +46,34 @@ public final class BiomeUtil { * @param pack The ConfigPack this biome belongs to. */ private static void registerBiome(Biome biome, ConfigPack pack, - com.dfsek.terra.api.registry.key.RegistryKey id) { - Registry registry = BuiltinRegistries.BIOME; + com.dfsek.terra.api.registry.key.RegistryKey id, + Registry registry) { RegistryKey vanilla = ((ProtoPlatformBiome) biome.getPlatformBiome()).get(registry); if(pack.getContext().get(PreLoadCompatibilityOptions.class).useVanillaBiomes()) { - ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(vanilla); + ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(registry.getEntry(vanilla).orElseThrow()); } else { VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class); - net.minecraft.world.biome.Biome minecraftBiome = MinecraftUtil.createBiome(biome, registry.get(vanilla), + net.minecraft.world.biome.Biome minecraftBiome = MinecraftUtil.createBiome(biome, Objects.requireNonNull(registry.get(vanilla)), vanillaBiomeProperties); Identifier identifier = new Identifier("terra", MinecraftUtil.createBiomeID(pack, id)); if(registry.containsId(identifier)) { ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(MinecraftUtil.getEntry(registry, identifier) - .orElseThrow() - .getKey() .orElseThrow()); } else { - ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(BuiltinRegistries.add(registry, + ((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(Registry.registerReference(registry, MinecraftUtil.registerKey(identifier) .getValue(), - minecraftBiome).getKey().orElseThrow()); + minecraftBiome)); } - Map villagerMap = VillagerTypeAccessor.getBiomeTypeToIdMap(); + Map, VillagerType> villagerMap = VillagerTypeAccessor.getBiomeTypeToIdMap(); - villagerMap.put(RegistryKey.of(Registry.BIOME_KEY, identifier), + villagerMap.put(RegistryKey.of(RegistryKeys.BIOME, identifier), Objects.requireNonNullElse(vanillaBiomeProperties.getVillagerType(), villagerMap.getOrDefault(vanilla, VillagerType.PLAINS))); diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/LifecycleUtil.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/LifecycleUtil.java index 498de58aa..6b4516c5f 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/LifecycleUtil.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/LifecycleUtil.java @@ -1,6 +1,11 @@ package com.dfsek.terra.lifecycle.util; -import net.minecraft.util.registry.BuiltinRegistries; +import net.minecraft.registry.MutableRegistry; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.gen.WorldPreset; import com.dfsek.terra.api.event.events.platform.PlatformInitializationEvent; import com.dfsek.terra.mod.CommonPlatform; @@ -11,9 +16,9 @@ public final class LifecycleUtil { } - public static void initialize() { + public static void initialize(MutableRegistry biomeMutableRegistry, MutableRegistry worldPresetMutableRegistry) { CommonPlatform.get().getEventManager().callEvent(new PlatformInitializationEvent()); - BiomeUtil.registerBiomes(); - CommonPlatform.get().registerWorldTypes((id, preset) -> BuiltinRegistries.add(BuiltinRegistries.WORLD_PRESET, id, preset)); + BiomeUtil.registerBiomes(biomeMutableRegistry); + CommonPlatform.get().registerWorldTypes((id, preset) -> Registry.register(worldPresetMutableRegistry, RegistryKey.of(RegistryKeys.WORLD_PRESET, id), preset)); } } diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryHack.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryHack.java new file mode 100644 index 000000000..347a13bfa --- /dev/null +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryHack.java @@ -0,0 +1,5 @@ +package com.dfsek.terra.lifecycle.util; + +public interface RegistryHack { + void terra_bind(); +} diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryUtil.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryUtil.java index 9f17b89e3..6c6ee4ad6 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryUtil.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/RegistryUtil.java @@ -1,7 +1,8 @@ package com.dfsek.terra.lifecycle.util; +import net.minecraft.registry.Registries; import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; +import net.minecraft.registry.Registry; import com.dfsek.terra.mod.data.Codecs; @@ -12,7 +13,7 @@ public final class RegistryUtil { } public static void register() { - Registry.register(Registry.CHUNK_GENERATOR, new Identifier("terra:terra"), Codecs.MINECRAFT_CHUNK_GENERATOR_WRAPPER); - Registry.register(Registry.BIOME_SOURCE, new Identifier("terra:terra"), Codecs.TERRA_BIOME_SOURCE); + Registry.register(Registries.CHUNK_GENERATOR, new Identifier("terra:terra"), Codecs.MINECRAFT_CHUNK_GENERATOR_WRAPPER); + Registry.register(Registries.BIOME_SOURCE, new Identifier("terra:terra"), Codecs.TERRA_BIOME_SOURCE); } } diff --git a/platforms/mixin-lifecycle/src/main/resources/terra.lifecycle.mixins.json b/platforms/mixin-lifecycle/src/main/resources/terra.lifecycle.mixins.json index 7ec5e4d95..bf42757a4 100644 --- a/platforms/mixin-lifecycle/src/main/resources/terra.lifecycle.mixins.json +++ b/platforms/mixin-lifecycle/src/main/resources/terra.lifecycle.mixins.json @@ -1,21 +1,23 @@ { - "required": true, - "minVersion": "0.8", - "package": "com.dfsek.terra.lifecycle.mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [ - "NoiseConfigMixin", - "RegistryMixin", - "lifecycle.MinecraftServerMixin" - ], - "client": [ - "lifecycle.client.MinecraftClientMixin" - ], - "server": [ - "lifecycle.server.ServerMainMixin" - ], - "injectors": { - "defaultRequire": 1 - }, - "refmap": "terra.lifecycle.refmap.json" + "required": true, + "minVersion": "0.8", + "package": "com.dfsek.terra.lifecycle.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "NoiseConfigMixin", + "RegistryEntryReferenceInvoker", + "RegistryMixin", + "SimpleRegistryMixin", + "lifecycle.MinecraftServerMixin", + "lifecycle.RegistryLoaderMixin", + "lifecycle.SaveLoadingMixin" + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + }, + "refmap": "terra.lifecycle.refmap.json" } \ No newline at end of file diff --git a/platforms/quilt/build.gradle.kts b/platforms/quilt/build.gradle.kts index 940045d47..4cff5a427 100644 --- a/platforms/quilt/build.gradle.kts +++ b/platforms/quilt/build.gradle.kts @@ -39,11 +39,6 @@ dependencies { exclude("net.fabricmc") exclude("net.fabricmc.fabric-api") } - - modLocalRuntime("com.github.astei:lazydfu:${Versions.Mod.lazyDfu}") { - exclude("net.fabricmc") - exclude("net.fabricmc.fabric-api") - } } loom {