Compare commits

..

3 Commits

Author SHA1 Message Date
justaureus 8d01059833 Merge pull request #458 from OakLoaf/dev/1.20.6/bukkit
Updated Bukkit to 1.20.6
2024-06-23 18:08:41 -05:00
Oak 500cf3c0b5 Fixed incorrect version formatting 2024-06-19 14:29:03 +01:00
Oak 0507978c87 Revert "Updated bukkit platform to 1.21" 2024-06-18 11:30:02 +01:00
873 changed files with 9873 additions and 11599 deletions
+3 -4
View File
@@ -1,14 +1,13 @@
# Global owners, automatically request review when pull request is submitted
* @dfsek @solonovamax @duplexsystem @astrsh @justaureus
* @dfsek @solonovamax @duplexsystem @Astrashh @justaureus
# Platforms
/platforms/ @dfsek @solonovamax @duplexsystem @justaureus
/platforms/bukkit @dfsek @solonovamax @duplexsystem @justaureus @OakLoaf
# Common
/common/ @dfsek @solonovamax @duplexsystem @astrsh
/common/ @dfsek @solonovamax @duplexsystem @Astrashh
# Gradle Stuff
/buildSrc/ @dfsek @solonovamax @duplexsystem
*.gradle.kts @dfsek @solonovamax @duplexsystem
/gradle/ @dfsek @solonovamax @duplexsystem
/gradle/ @dfsek @solonovamax @duplexsystem
+1 -2
View File
@@ -44,7 +44,6 @@ You must put an x in all the boxes that it applies to. (Like this: [x])
<!-- There is an included `.editorconfig` file in the base of the repo. Please use a plugin for your IDE of choice that follows those settings. -->
- [ ] I have read the [`CONTRIBUTING.md`](https://github.com/PolyhedralDev/Terra/blob/master/CONTRIBUTING.md)
document in the root of the git repository.
- [ ] LLM-based tools were not used to create this PR. (ChatGPT, Claude, etc)
#### Types of changes
@@ -99,7 +98,7 @@ You must put an x in all the boxes that it applies to. (Like this: [x])
- [ ] I am not the original author of this code, but it is in public domain or
released under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) or a compatible license.
<!--
Please provide reliable evidence of this. LLM-generated code does not satisfy this requirement.
Please provide reliable evidence of this.
NOTE: for compatible licenses, you must make sure to add the included license somewhere in the program, if so required.
(And even if it's not required, it's still nice to do it. Also add attribution somewhere.)
-->
+4 -4
View File
@@ -17,16 +17,16 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Set up JDK 21
uses: actions/setup-java@v4.7.1
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93
with:
java-version: '21'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- uses: burrunan/gradle-cache-action@v3.0.1
- uses: burrunan/gradle-cache-action@03c71a8ba93d670980695505f48f49daf43704a6
name: Build Terra
with:
# Specifies arguments for Gradle execution
@@ -44,4 +44,4 @@ jobs:
# Properties are passed as -Pname=value
properties: |
kotlin.js.compiler=ir
kotlin.parallel.tasks.in.project=true
kotlin.parallel.tasks.in.project=true
+2 -5
View File
@@ -258,9 +258,6 @@ as [GitHub Pull Requests](https://guides.github.com/activities/forking/#making-a
see instead** and why.
- **Explain why this enhancement would be useful** to most Terra users and isn't
something that can or should be implemented as an addon.
- **Do not use LLM/"AI" tools to create your pull request.** Your pr should be written
by you. Using an LLM to automate small, tedious tasks (regex and other fiddly things like it)
is acceptable, but submitting a low-effort, completely LLM-generated PR will result in a ban.
## Styleguides
@@ -384,7 +381,7 @@ compatibilities are welcome and encouraged, in the form of addons.**
### Platform-Agnostic Design
Terra must, at all times, remain platform-agnostic. This means it must be able
Terra must, at all times, remain platform agnostic. This means it must be able
to run on theoretically any voxel based platform. Including non-minecraft games
like Terasology.
@@ -394,7 +391,7 @@ it'll be running on.
Examples:
- Don't assume the world height is 256.
- Don't assume that a specific block, item, or entity exists. (E.g. don't assume
- Don't assume that a specific block, item, or entity exists. (Eg. don't assume
there exists a block called `minecraft:grass_block`)
### Data-Driven
Vendored
-149
View File
@@ -1,149 +0,0 @@
pipeline {
agent any
tools {
jdk "Temurin Java 21"
}
triggers {
githubPush()
}
environment {
DISCORD_WEBHOOK_URL = credentials('polydev-discord-webhook-url')
}
stages {
stage('Checkout') {
steps {
scmSkip(deleteBuild: true)
}
}
stage('Setup Gradle') {
steps {
sh 'chmod +x gradlew'
}
}
stage('Build') {
steps {
withGradle {
sh './gradlew build --rerun-tasks -x check'
sh './gradlew javadoc'
}
}
post {
success {
archiveArtifacts artifacts: 'platforms/fabric/build/libs/Terra-fabric*.jar,platforms/bukkit/build/libs/Terra-bukkit*-shaded.jar,platforms/allay/build/libs/Terra-allay*.jar,platforms/minestom/build/libs/Terra-minestom*.jar', fingerprint: true, onlyIfSuccessful: true
javadoc javadocDir: 'common/api/build/docs/javadoc', keepAll: true
}
}
}
stage('Tests') {
steps {
withGradle {
sh './gradlew test --rerun-tasks'
}
}
}
// stage('Deploy to snapshots repositories') {
// when {
// allOf {
// not { buildingTag() }
// not { expression { env.TAG_NAME != null && env.TAG_NAME.matches('v\\d+\\.\\d+\\.\\d+') } }
// }
// }
//
// steps {
// withCredentials([
// string(credentialsId: 'maven-signing-key', variable: 'ORG_GRADLE_PROJECT_signingKey'),
// string(credentialsId: 'maven-signing-key-password', variable: 'ORG_GRADLE_PROJECT_signingPassword'),
// usernamePassword(
// credentialsId: 'solo-studios-maven',
// passwordVariable: 'ORG_GRADLE_PROJECT_SoloStudiosSnapshotsPassword',
// usernameVariable: 'ORG_GRADLE_PROJECT_SoloStudiosSnapshotsUsername'
// )
// ]) {
// withGradle {
// sh './gradlew publishAllPublicationsToSoloStudiosSnapshotsRepository'
// }
// }
// }
// }
stage('Deploy to releases repositories') {
// when {
// allOf {
// buildingTag()
// expression { env.TAG_NAME != null && env.TAG_NAME.matches('v\\d+\\.\\d+\\.\\d+') }
// }
// }
steps {
withCredentials([
string(credentialsId: 'maven-signing-key', variable: 'ORG_GRADLE_PROJECT_signingKey'),
string(credentialsId: 'maven-signing-key-password', variable: 'ORG_GRADLE_PROJECT_signingPassword'),
usernamePassword(
credentialsId: 'solo-studios-maven',
passwordVariable: 'ORG_GRADLE_PROJECT_SoloStudiosReleasesPassword',
usernameVariable: 'ORG_GRADLE_PROJECT_SoloStudiosReleasesUsername'
),
// TODO: does not yet exist (uncomment once added)
// usernamePassword(
// credentialsId: 'sonatype-maven-credentials',
// passwordVariable: 'ORG_GRADLE_PROJECT_SonatypePassword',
// usernameVariable: 'ORG_GRADLE_PROJECT_SonatypeUsername'
// ),
// usernamePassword(
// credentialsId: 'codemc-maven-credentials',
// passwordVariable: 'ORG_GRADLE_PROJECT_CodeMCPassword',
// usernameVariable: 'ORG_GRADLE_PROJECT_CodeMCUsername'
// )
]) {
withGradle {
sh './gradlew publish'
//sh './gradlew publishAllPublicationsToSoloStudiosReleasesRepository'
// sh './gradlew publishAllPublicationsToSonatypeRepository'
// sh './gradlew publishAllPublicationsToCodeMCRepository'
}
}
}
}
}
post {
always {
discoverReferenceBuild()
// junit testResults: '**/build/test-results/*/TEST-*.xml'
recordIssues(
aggregatingResults: true,
enabledForFailure: true,
minimumSeverity: 'ERROR',
sourceCodeEncoding: 'UTF-8',
checksAnnotationScope: 'ALL',
sourceCodeRetention: 'LAST_BUILD',
tools: [java(), javaDoc()]
)
discordSend(
title: env.JOB_NAME + ' ' + env.BUILD_DISPLAY_NAME,
showChangeset: true,
enableArtifactsList: true,
link: env.BUILD_URL,
result: currentBuild.currentResult,
customAvatarUrl: 'https://github.com/PolyhedralDev.png',
customUsername: 'Solo Studios Jenkins',
webhookURL: env.DISCORD_WEBHOOK_URL,
)
cleanWs()
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2025 Polyhedral Development
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
+4 -6
View File
@@ -1,8 +1,8 @@
preRelease(true)
versionProjects(":common:api", version("7.0.0"))
versionProjects(":common:implementation", version("7.0.0"))
versionProjects(":platforms", version("7.0.0"))
versionProjects(":common:api", version("6.5.0"))
versionProjects(":common:implementation", version("6.5.0"))
versionProjects(":platforms", version("6.5.0"))
allprojects {
@@ -15,7 +15,6 @@ allprojects {
tasks.withType<JavaCompile>().configureEach {
options.isFork = true
options.isIncremental = true
options.release.set(21)
}
tasks.withType<Test>().configureEach {
@@ -44,9 +43,8 @@ afterEvaluate {
configureDistribution()
}
project(":platforms:bukkit:common").configureDistribution()
project(":platforms:minestom:example").configureDistribution()
forSubProjects(":common:addons") {
apply(plugin = "com.gradleup.shadow")
apply(plugin = "com.github.johnrengelman.shadow")
tasks.named("build") {
finalizedBy(tasks.named("shadowJar"))
+6 -12
View File
@@ -6,12 +6,6 @@ plugins {
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.solo-studios.ca/releases") {
name = "Solo Studios"
}
maven("https://maven.solo-studios.ca/snapshots") {
name = "Solo Studios"
}
maven("https://repo.codemc.org/repository/maven-public") {
name = "CodeMC"
}
@@ -22,11 +16,11 @@ repositories {
dependencies {
//TODO Allow pulling from Versions.kt
implementation("com.gradleup.shadow", "shadow-gradle-plugin", "8.3.9")
implementation("com.github.johnrengelman", "shadow", "8.1.1")
implementation("io.papermc.paperweight.userdev", "io.papermc.paperweight.userdev.gradle.plugin", "1.7.1")
implementation("io.papermc.paperweight.userdev", "io.papermc.paperweight.userdev.gradle.plugin", "2.0.0-beta.18")
implementation("org.ow2.asm", "asm", "9.9")
implementation("org.ow2.asm", "asm-tree", "9.9")
implementation("com.dfsek.tectonic", "common", "4.3.1")
implementation("org.yaml", "snakeyaml", "2.5")
implementation("org.ow2.asm", "asm", "9.7")
implementation("org.ow2.asm", "asm-tree", "9.7")
implementation("com.dfsek.tectonic", "common", "4.2.1")
implementation("org.yaml", "snakeyaml", "2.2")
}
@@ -13,7 +13,6 @@ import org.gradle.kotlin.dsl.getByName
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.plugins.ide.idea.model.IdeaModel
fun Project.configureCompilation() {
apply(plugin = "maven-publish")
@@ -22,13 +21,6 @@ fun Project.configureCompilation() {
apply(plugin = "idea")
apply<TectonicDocPlugin>()
configure<IdeaModel> {
module {
isDownloadJavadoc = true
isDownloadSources = true
}
}
configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
+2 -14
View File
@@ -30,12 +30,6 @@ fun Project.configureDependencies() {
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.solo-studios.ca/releases") {
name = "Solo Studios"
}
maven("https://maven.solo-studios.ca/snapshots") {
name = "Solo Studios"
}
maven("https://maven.fabricmc.net/") {
name = "FabricMC"
}
@@ -54,17 +48,11 @@ fun Project.configureDependencies() {
maven("https://jitpack.io") {
name = "JitPack"
}
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
name = "Sonatype Snapshots"
}
maven("https://repo.onarandombox.com/multiverse-releases") {
name = "onarandombox"
}
}
dependencies {
testImplementation("org.junit.jupiter", "junit-jupiter", Versions.Libraries.Internal.junit)
"testRuntimeOnly"("org.junit.platform", "junit-platform-launcher")
testImplementation("org.junit.jupiter", "junit-jupiter-api", Versions.Libraries.Internal.junit)
testImplementation("org.junit.jupiter", "junit-jupiter-engine", Versions.Libraries.Internal.junit)
compileOnly("org.jetbrains", "annotations", Versions.Libraries.Internal.jetBrainsAnnotations)
compileOnly("com.google.guava", "guava", Versions.Libraries.Internal.guava)
+26 -60
View File
@@ -4,11 +4,9 @@ import java.io.File
import java.io.FileWriter
import java.net.URL
import java.nio.file.FileSystems
import java.nio.file.Path
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.extra
@@ -21,49 +19,16 @@ import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.exists
private fun Project.installAddonsInto(dest: Path) {
FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs ->
forSubProjects(":common:addons") {
val jar = getJarTask()
logger.info("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")
val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")
if (!addonPath.exists()) {
addonPath.parent.createDirectories()
addonPath.createFile()
jar.archiveFile.get().asFile.toPath().copyTo(addonPath, overwrite = true)
}
}
}
}
fun Project.configureDistribution() {
apply(plugin = "com.gradleup.shadow")
apply(plugin = "com.github.johnrengelman.shadow")
val downloadDefaultPacks = tasks.create("downloadDefaultPacks") {
group = "terra"
doFirst {
try {
file("${buildDir}/resources/main/packs/").deleteRecursively()
file("${buildDir}/resources/main/metapacks/").deleteRecursively()
val overworldPackUrl =
URL("https://github.com/PolyhedralDev/TerraOverworldConfig/releases/download/" + Versions.Terra.overworldConfig + "/Overworld.zip")
val reimagENDPackUrl =
URL("https://github.com/PolyhedralDev/ReimagEND/releases/download/" + Versions.Terra.reimagENDConfig + "/ReimagEND.zip")
val tartarusPackUrl =
URL("https://github.com/PolyhedralDev/Tartarus/releases/download/" + Versions.Terra.tartarusConfig + "/Tartarus.zip")
val defaultPackUrl =
URL("https://github.com/PolyhedralDev/DefaultMetapack/releases/download/" + Versions.Terra.defaultConfig + "/default.zip")
downloadPack(overworldPackUrl, project)
downloadPack(reimagENDPackUrl, project)
downloadPack(tartarusPackUrl, project)
downloadPack(defaultPackUrl, project, true)
} catch (_: Exception) {
}
file("${buildDir}/resources/main/packs/").deleteRecursively()
val defaultPackUrl = URL("https://github.com/PolyhedralDev/TerraOverworldConfig/releases/download/" + Versions.Terra.overworldConfig + "/default.zip")
downloadPack(defaultPackUrl, project)
}
}
@@ -82,17 +47,25 @@ fun Project.configureDistribution() {
doLast {
// https://github.com/johnrengelman/shadow/issues/111
val dest = tasks.named<ShadowJar>("shadowJar").get().archiveFile.get().path
installAddonsInto(dest)
}
}
tasks.create("installAddonsIntoDefaultJar") {
group = "terra"
dependsOn(compileAddons)
doLast {
val dest = tasks.named<Jar>("jar").get().archiveFile.get().path
installAddonsInto(dest)
FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs ->
forSubProjects(":common:addons") {
val jar = getJarTask()
logger.info("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")
val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")
if (!addonPath.exists()) {
addonPath.parent.createDirectories()
addonPath.createFile()
jar.archiveFile.get().asFile.toPath().copyTo(addonPath, overwrite = true)
}
}
}
}
}
@@ -107,13 +80,6 @@ fun Project.configureDistribution() {
resources.computeIfAbsent("packs") { ArrayList() }.add(it.name)
}
val metaPacksDir = File("${project.buildDir}/resources/main/metapacks/")
metaPacksDir.walkTopDown().forEach {
if (it.isDirectory || !it.name.endsWith(".zip")) return@forEach
resources.computeIfAbsent("metapacks") { ArrayList() }.add(it.name)
}
val langDir = File("${project(":common:implementation").buildDir}/resources/main/lang/")
langDir.walkTopDown().forEach {
@@ -166,6 +132,7 @@ fun Project.configureDistribution() {
version = project.version
relocate("org.apache.commons", "com.dfsek.terra.lib.commons")
relocate("org.objectweb.asm", "com.dfsek.terra.lib.asm")
relocate("com.dfsek.paralithic", "com.dfsek.terra.lib.paralithic")
relocate("org.json", "com.dfsek.terra.lib.json")
relocate("org.yaml", "com.dfsek.terra.lib.yaml")
@@ -181,10 +148,9 @@ fun Project.configureDistribution() {
}
}
fun downloadPack(packUrl: URL, project: Project, metapack: Boolean = false) {
fun downloadPack(packUrl: URL, project: Project) {
val fileName = packUrl.file.substring(packUrl.file.lastIndexOf("/"))
val resourceType = if (metapack) "metapacks" else "packs"
val file = File("${project.buildDir}/resources/main/${resourceType}/${fileName}")
val file = File("${project.buildDir}/resources/main/packs/${fileName}")
file.parentFile.mkdirs()
file.outputStream().write(packUrl.readBytes())
}
+6 -7
View File
@@ -16,17 +16,16 @@ fun Project.configurePublishing() {
}
repositories {
val mavenUrl = "https://maven.solo-studios.ca/releases/"
val mavenUrl = "https://repo.codemc.io/repository/maven-releases/"
//val mavenSnapshotUrl = "https://repo.codemc.io/repository/maven-snapshots/"
maven(mavenUrl) {
val SoloStudiosReleasesUsername: String? by project
val SoloStudiosReleasesPassword: String? by project
if (SoloStudiosReleasesUsername != null && SoloStudiosReleasesPassword != null) {
val mavenUsername: String? by project
val mavenPassword: String? by project
if (mavenUsername != null && mavenPassword != null) {
credentials {
username = SoloStudiosReleasesUsername
password = SoloStudiosReleasesPassword
username = mavenUsername
password = mavenPassword
}
}
}
+30 -55
View File
@@ -1,39 +1,33 @@
object Versions {
object Terra {
const val overworldConfig = "latest"
const val reimagENDConfig = "latest"
const val tartarusConfig = "latest"
const val defaultConfig = "latest"
const val overworldConfig = "v1.3.4"
}
object Libraries {
const val tectonic = "4.3.1"
const val paralithic = "2.0.1"
const val tectonic = "4.2.1"
const val paralithic = "0.7.1"
const val strata = "1.3.2"
const val seismic = "2.5.7"
const val cloud = "2.0.0"
const val cloud = "1.8.4"
const val caffeine = "3.2.2"
const val caffeine = "3.1.8"
const val slf4j = "2.0.17"
const val slf4j = "2.0.13"
object Internal {
const val shadow = "8.3.9"
const val apacheText = "1.14.0"
const val apacheIO = "2.20.0"
const val guava = "33.5.0-jre"
const val asm = "9.9"
const val snakeYml = "2.5"
const val jetBrainsAnnotations = "26.0.2-1"
const val junit = "6.0.0"
const val nbt = "6.1"
const val shadow = "8.1.1"
const val apacheText = "1.12.0"
const val apacheIO = "2.16.1"
const val guava = "33.1.0-jre"
const val asm = "9.7"
const val snakeYml = "2.2"
const val jetBrainsAnnotations = "24.1.0"
const val junit = "5.10.2"
}
}
object Fabric {
const val fabricAPI = "0.134.1+${Mod.minecraft}"
const val cloud = "2.0.0-beta.13"
const val fabricAPI = "0.97.8+${Mod.minecraft}"
}
//
// object Quilt {
@@ -42,16 +36,14 @@ object Versions {
// }
object Mod {
const val mixin = "0.16.4+mixin.0.8.7"
const val mixinExtras = "0.5.0"
const val mixin = "0.12.5+mixin.0.8.5"
const val minecraft = "1.21.10"
const val minecraft = "1.20.6"
const val yarn = "$minecraft+build.1"
const val fabricLoader = "0.18.2"
const val fabricLoader = "0.15.10"
const val architecuryLoom = "1.11.451"
const val architecturyPlugin = "3.4.162"
const val architecuryLoom = "1.6.395"
const val architecturyPlugin = "3.4.155"
}
//
// object Forge {
@@ -60,20 +52,17 @@ object Versions {
// }
object Bukkit {
const val minecraft = "1.21.10"
const val nms = "$minecraft-R0.1"
const val paperBuild = "$nms-20251012.013929-7"
const val minecraft = "1.20.6"
const val paperBuild = "$minecraft-R0.1-20240602.222958-107"
const val paper = paperBuild
const val paperLib = "1.0.8"
const val reflectionRemapper = "0.1.3"
const val paperDevBundle = paperBuild
const val runPaper = "2.3.1"
const val paperWeight = "2.0.0-beta.19"
const val cloud = "2.0.0-beta.12"
const val multiverse = "5.3.0"
const val reflectionRemapper = "0.1.1"
const val paperDevBundle = "$minecraft-R0.1-20240601.143523-105"
const val runPaper = "2.3.0"
const val paperWeight = "1.7.1"
}
//
//
// object Sponge {
// const val sponge = "9.0.0-SNAPSHOT"
// const val mixin = "0.8.2"
@@ -81,21 +70,7 @@ object Versions {
// }
//
object CLI {
const val logback = "1.5.19"
const val picocli = "4.7.7"
const val nbt = "6.1"
const val logback = "1.4.14"
}
object Allay {
const val api = "0.13.0"
const val gson = "2.13.2"
const val mappings = "15398c1"
const val mappingsGenerator = "8fa6058"
const val mcmeta = "e85a17c"
}
object Minestom {
const val minestom = "2025.10.04-1.21.8"
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2025 Polyhedral Development
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.
@@ -46,6 +46,6 @@ class BaseBiomeColumn implements Column<Biome> {
@Override
public Biome get(int y) {
return biomeProvider.pipeline.extrude(base, x, y, z, seed);
return biomeProvider.extrude(base, x, y, z, seed);
}
}
@@ -6,33 +6,37 @@ import java.util.Set;
import java.util.stream.Collectors;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.utils.ExtrusionPipeline;
import com.dfsek.terra.addons.biome.extrusion.utils.ExtrusionPipelineFactory;
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 BiomeExtrusionProvider implements BiomeProvider {
public final ExtrusionPipeline pipeline;
private final BiomeProvider delegate;
private final Set<Biome> biomes;
private final List<Extrusion> extrusions;
private final int resolution;
public BiomeExtrusionProvider(BiomeProvider delegate, List<Extrusion> extrusions, int resolution) {
this.delegate = delegate;
this.biomes = delegate.stream().collect(Collectors.toSet());
extrusions.forEach(e -> biomes.addAll(e.getBiomes()));
this.pipeline = ExtrusionPipelineFactory.create(extrusions);
this.extrusions = extrusions;
this.resolution = resolution;
}
@Override
public Biome getBiome(int x, int y, int z, long seed) {
Biome delegated = delegate.getBiome(x, y, z, seed);
return pipeline.extrude(delegated, x, y, z, seed);
return extrude(delegated, x, y, z, seed);
}
public Biome extrude(Biome original, int x, int y, int z, long seed) {
for(Extrusion extrusion : extrusions) {
original = extrusion.extrude(original, x, y, z, seed);
}
return original;
}
@Override
@@ -60,4 +64,4 @@ public class BiomeExtrusionProvider implements BiomeProvider {
public BiomeProvider getDelegate() {
return delegate;
}
}
}
@@ -1,17 +1,17 @@
package com.dfsek.terra.addons.biome.extrusion.config.extrusions;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.range.Range;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.Range;
public abstract class SamplerExtrusionTemplate implements ObjectTemplate<Extrusion> {
@Value("sampler")
protected @Meta Sampler sampler;
protected @Meta NoiseSampler sampler;
@Value("range")
protected @Meta Range range;
@@ -1,7 +1,5 @@
package com.dfsek.terra.addons.biome.extrusion.extrusions;
import com.dfsek.seismic.type.sampler.Sampler;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -9,9 +7,9 @@ import java.util.stream.Collectors;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.api.ReplaceableBiome;
import com.dfsek.terra.addons.biome.query.api.BiomeQueries;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.Range;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
import com.dfsek.terra.api.util.collection.TriStateIntCache;
import com.dfsek.terra.api.util.range.Range;
import com.dfsek.terra.api.world.biome.Biome;
@@ -19,42 +17,26 @@ import com.dfsek.terra.api.world.biome.Biome;
* Sets biomes at locations based on a sampler.
*/
public class ReplaceExtrusion implements Extrusion {
private final Sampler sampler;
private final Range range;
private final ProbabilityCollection<ReplaceableBiome> biomes;
private final Predicate<Biome> hasTag;
private final TriStateIntCache cache;
private final NoiseSampler sampler;
public ReplaceExtrusion(Sampler sampler, Range range, ProbabilityCollection<ReplaceableBiome> biomes, String tag) {
private final Range range;
private final ProbabilityCollection<ReplaceableBiome> biomes;
private final Predicate<Biome> hasTag;
public ReplaceExtrusion(NoiseSampler sampler, Range range, ProbabilityCollection<ReplaceableBiome> biomes, String tag) {
this.sampler = sampler;
this.range = range;
this.biomes = biomes;
this.hasTag = BiomeQueries.has(tag);
this.cache = new TriStateIntCache(Biome.INT_ID_COUNTER.get());
}
@Override
public Biome extrude(Biome original, int x, int y, int z, long seed) {
int id = original.getIntID();
long state = cache.get(id);
boolean passes;
if(state == TriStateIntCache.STATE_UNSET) {
// Only run the test if unset in cache
passes = hasTag.test(original);
cache.set(id, passes);
} else {
// Read the primitive long directly
passes = (state == TriStateIntCache.STATE_TRUE);
if(hasTag.test(original)) {
return range.ifInRange(y, () -> biomes.get(sampler, x, y, z, seed).get(original), original);
}
if(passes) {
if(range.isInRange(y)) {
return biomes.get(sampler, x, y, z, seed).get(original);
}
}
return original;
}
@@ -1,15 +1,14 @@
package com.dfsek.terra.addons.biome.extrusion.extrusions;
import com.dfsek.seismic.type.sampler.Sampler;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.api.ReplaceableBiome;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.Range;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
import com.dfsek.terra.api.util.range.Range;
import com.dfsek.terra.api.world.biome.Biome;
@@ -17,13 +16,13 @@ import com.dfsek.terra.api.world.biome.Biome;
* Sets biomes at locations based on a sampler.
*/
public class SetExtrusion implements Extrusion {
private final Sampler sampler;
private final NoiseSampler sampler;
private final Range range;
private final ProbabilityCollection<ReplaceableBiome> biomes;
public SetExtrusion(Sampler sampler, Range range, ProbabilityCollection<ReplaceableBiome> biomes) {
public SetExtrusion(NoiseSampler sampler, Range range, ProbabilityCollection<ReplaceableBiome> biomes) {
this.sampler = sampler;
this.range = range;
this.biomes = biomes;
@@ -1,8 +0,0 @@
package com.dfsek.terra.addons.biome.extrusion.utils;
import com.dfsek.terra.api.world.biome.Biome;
public interface ExtrusionPipeline {
Biome extrude(Biome original, int x, int y, int z, long seed);
}
@@ -1,158 +0,0 @@
package com.dfsek.terra.addons.biome.extrusion.utils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.api.world.biome.Biome;
import static org.objectweb.asm.Opcodes.AALOAD;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.SIPUSH;
import static org.objectweb.asm.Opcodes.SWAP;
import static org.objectweb.asm.Opcodes.V1_8;
public class ExtrusionPipelineFactory {
private static final AtomicInteger ID_COUNTER = new AtomicInteger(0);
// Type Descriptors
private static final String EXTRUSION_TYPE = Type.getInternalName(Extrusion.class);
private static final String EXTRUSION_DESC = Type.getDescriptor(Extrusion.class);
private static final String BIOME_DESC = Type.getDescriptor(Biome.class);
private static final String PIPELINE_INTERFACE = Type.getInternalName(ExtrusionPipeline.class);
// Method Signature: (Biome, int, int, int, long) -> Biome
private static final String EXTRUDE_SIG = "(" + BIOME_DESC + "IIIJ)" + BIOME_DESC;
public static ExtrusionPipeline create(List<Extrusion> extrusions) {
// Optimization: If empty, return identity
if(extrusions.isEmpty()) {
return (original, x, y, z, seed) -> original;
}
String className = "com/dfsek/terra/addons/biome/extrusion/GeneratedExtrusionPipeline_" + ID_COUNTER.getAndIncrement();
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 1. Define Class
cw.visit(V1_8, ACC_PUBLIC | ACC_FINAL, className, null, "java/lang/Object", new String[]{ PIPELINE_INTERFACE });
// 2. Define Fields (e0, e1, e2...)
for(int i = 0; i < extrusions.size(); i++) {
FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_FINAL, "e" + i, EXTRUSION_DESC, null, null);
fv.visitEnd();
}
// 3. Generate Constructor(Extrusion[])
generateConstructor(cw, className, extrusions.size());
// 4. Generate extrude() method
generateExtrudeMethod(cw, className, extrusions.size());
cw.visitEnd();
// 5. Load and Instantiate
byte[] bytecode = cw.toByteArray();
try {
Class<?> generatedClass = new PipelineClassLoader(ExtrusionPipelineFactory.class.getClassLoader())
.defineClass(className.replace('/', '.'), bytecode);
return (ExtrusionPipeline) generatedClass.getConstructor(Extrusion[].class)
.newInstance((Object) extrusions.toArray(new Extrusion[0]));
} catch(Exception e) {
throw new RuntimeException("Failed to generate ExtrusionPipeline", e);
}
}
private static void generateConstructor(ClassWriter cw, String className, int count) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "([L" + EXTRUSION_TYPE + ";)V", null, null);
mv.visitCode();
// super()
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
// Assign array elements to fields
for(int i = 0; i < count; i++) {
mv.visitVarInsn(ALOAD, 0); // Load this
mv.visitVarInsn(ALOAD, 1); // Load array argument
mv.visitIntInsn(SIPUSH, i); // Load index
mv.visitInsn(AALOAD); // Load array[i]
mv.visitFieldInsn(PUTFIELD, className, "e" + i, EXTRUSION_DESC);
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0); // Computed automatically
mv.visitEnd();
}
private static void generateExtrudeMethod(ClassWriter cw, String className, int count) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "extrude", EXTRUDE_SIG, null, null);
mv.visitCode();
// Helper var indices:
// 0: this
// 1: Biome original (We will update this or chain it on stack)
// 2: int x
// 3: int y
// 4: int z
// 5: long seed
mv.visitVarInsn(ALOAD, 1); // Load 'original' Biome onto stack initially
for(int i = 0; i < count; i++) {
// Stack contains: [CurrentBiome]
mv.visitVarInsn(ALOAD, 0); // Load 'this'
mv.visitFieldInsn(GETFIELD, className, "e" + i, EXTRUSION_DESC); // Load Extrusion field
// Stack: [CurrentBiome, Extrusion]
// We need: [Extrusion, CurrentBiome, x, y, z, seed]
mv.visitInsn(SWAP); // Swap to get [Extrusion, CurrentBiome]
mv.visitVarInsn(ILOAD, 2); // x
mv.visitVarInsn(ILOAD, 3); // y
mv.visitVarInsn(ILOAD, 4); // z
mv.visitVarInsn(LLOAD, 5); // seed
// Invoke Extrusion.extrude(Biome, x, y, z, seed)
mv.visitMethodInsn(INVOKEINTERFACE, EXTRUSION_TYPE, "extrude", EXTRUDE_SIG, true);
// Stack now contains: [NewBiome]
// Loop continues using this result as input for the next one
}
mv.visitInsn(ARETURN); // Return the final Biome
mv.visitMaxs(0, 0);
mv.visitEnd();
}
// Custom ClassLoader to inject the bytes
private static class PipelineClassLoader extends ClassLoader {
public PipelineClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
}
@@ -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.
@@ -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.
@@ -0,0 +1,8 @@
version = version("1.0.1")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
compileOnlyApi(project(":common:addons:library-image"))
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2020-2023 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.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.converter.ColorConverter;
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<Biome> colorConverter;
private final ColorSampler colorSampler;
public ImageBiomeProvider(ColorConverter<Biome> 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<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z));
}
@Override
public Iterable<Biome> getBiomes() {
return colorConverter.getEntries();
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2020-2023 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<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorConverter<Biome>>>> BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorMapping<Biome>>>> 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<Supplier<ObjectTemplate<BiomeProvider>>> providerRegistry = event.getPack().getOrCreateRegistry(
PROVIDER_REGISTRY_KEY);
providerRegistry.register(addon.key("IMAGE"), ImageProviderTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<ColorConverter<Biome>>>> 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<Supplier<ObjectTemplate<ColorMapping<Biome>>>> 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();
}
}
@@ -1,18 +1,18 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config;
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.ImageBiomeProvider;
import com.dfsek.terra.addons.biome.image.v2.ImageBiomeProvider;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.api.world.biome.Biome;
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.image.config.converter;
package com.dfsek.terra.addons.biome.image.v2.config.converter;
import com.dfsek.tectonic.api.config.template.annotations.Value;
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.image.config.converter;
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;
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.image.config.converter.mapping;
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;
@@ -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.+"
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2025 Polyhedral Development
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
+5 -4
View File
@@ -1,5 +1,6 @@
# biome-provider-image-v2
# biome-provider-image
Implements and registers the `IMAGE` biome provider, which
utilizes various config types provided by the `library-image` addon to
distribute biomes based on images.
Implements and registers the `IMAGE` biome provider, a biome provider which
generates biomes from an image, using the `color` attribute of biomes.
This addon registers the provider type, and all associated config options.
@@ -1,8 +1,7 @@
version = version("2.0.0")
version = version("1.0.1")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
compileOnlyApi(project(":common:addons:library-image"))
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.
@@ -7,25 +7,32 @@
package com.dfsek.terra.addons.biome.image;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class ImageBiomeProvider implements BiomeProvider {
private final Map<Color, Biome> colorBiomeMap = new HashMap<>();
private final BufferedImage image;
private final int resolution;
private final Align align;
private final ColorConverter<Biome> colorConverter;
private final ColorSampler colorSampler;
public ImageBiomeProvider(ColorConverter<Biome> colorConverter, ColorSampler colorSampler, int resolution) {
public ImageBiomeProvider(Set<Biome> registry, BufferedImage image, int resolution, Align align) {
this.image = image;
this.resolution = resolution;
this.colorConverter = colorConverter;
this.colorSampler = colorSampler;
this.align = align;
registry.forEach(biome -> colorBiomeMap.put(new Color(biome.getColor()), biome));
}
private static int distance(Color a, Color b) {
return Math.abs(a.getRed() - b.getRed()) + Math.abs(a.getGreen() - b.getGreen()) + Math.abs(a.getBlue() - b.getBlue());
}
@Override
@@ -36,7 +43,15 @@ public class ImageBiomeProvider implements BiomeProvider {
public Biome getBiome(int x, int z) {
x /= resolution;
z /= resolution;
return colorConverter.apply(colorSampler.apply(x, z));
Color color = align.getColor(image, x, z);
return colorBiomeMap.get(colorBiomeMap.keySet()
.stream()
.reduce(colorBiomeMap.keySet().stream().findAny().orElseThrow(IllegalStateException::new),
(running, element) -> {
int d1 = distance(color, running);
int d2 = distance(color, element);
return d1 < d2 ? running : element;
}));
}
@Override
@@ -46,6 +61,24 @@ public class ImageBiomeProvider implements BiomeProvider {
@Override
public Iterable<Biome> getBiomes() {
return colorConverter.getEntries();
return colorBiomeMap.values();
}
public enum Align {
CENTER {
@Override
public Color getColor(BufferedImage image, int x, int z) {
return new Color(image.getRGB(Math.floorMod(x - image.getWidth() / 2, image.getWidth()),
Math.floorMod(z - image.getHeight() / 2, image.getHeight())));
}
},
NONE {
@Override
public Color getColor(BufferedImage image, int x, int z) {
return new Color(image.getRGB(Math.floorMod(x, image.getWidth()), Math.floorMod(z, image.getHeight())));
}
};
public abstract Color getColor(BufferedImage image, int x, int z);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.
@@ -8,16 +8,11 @@
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;
import com.dfsek.terra.addons.biome.image.config.ImageProviderTemplate;
import com.dfsek.terra.addons.biome.image.config.converter.ClosestBiomeColorConverterTemplate;
import com.dfsek.terra.addons.biome.image.config.converter.ExactBiomeColorConverterTemplate;
import com.dfsek.terra.addons.biome.image.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;
@@ -31,15 +26,10 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class ImageBiomeProviderAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorConverter<Biome>>>> BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorMapping<Biome>>>> BIOME_COLOR_MAPPING_REGISTRY_KEY = new TypeKey<>() {
};
private static final Logger logger = LoggerFactory.getLogger(ImageBiomeProviderAddon.class);
@Inject
private Platform platform;
@@ -51,28 +41,16 @@ public class ImageBiomeProviderAddon implements AddonInitializer {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.priority(501)
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<BiomeProvider>>> providerRegistry = event.getPack().getOrCreateRegistry(
PROVIDER_REGISTRY_KEY);
providerRegistry.register(addon.key("IMAGE"), ImageProviderTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<ColorConverter<Biome>>>> 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<Supplier<ObjectTemplate<ColorMapping<Biome>>>> 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);
providerRegistry.register(addon.key("IMAGE"),
() -> new ImageProviderTemplate(event.getPack().getRegistry(Biome.class)));
})
.failThrough();
if(platform.getTerraConfig().isDebugLog())
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.");
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2020-2023 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;
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.awt.image.BufferedImage;
import java.util.HashSet;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@SuppressWarnings("FieldMayBeFinal")
public class ImageProviderTemplate implements ObjectTemplate<BiomeProvider> {
private final Registry<Biome> biomes;
@Value("resolution")
@Default
@Description("Sets the resolution at which to sample the image.")
private int resolution = 1;
@Value("image.name")
@Description("Sets the location of the image on the filesystem, relative to the pack root.")
private BufferedImage image;
@Value("image.align")
@Description("Sets the alignment style to use for the image.")
private ImageBiomeProvider.Align align;
public ImageProviderTemplate(Registry<Biome> set) {
this.biomes = set;
}
@Override
public BiomeProvider get() {
return new ImageBiomeProvider(new HashSet<>(biomes.entries()), image, resolution, align);
}
}
@@ -9,6 +9,4 @@ 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.+"
license: MIT 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.
@@ -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.
@@ -1,5 +1,5 @@
version = version("1.0.0")
version = version("1.0.1")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
}
}
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2020-2023 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.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.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.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<Supplier<ObjectTemplate<Source>>> SOURCE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<Stage>>> STAGE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> 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<Supplier<ObjectTemplate<BiomeProvider>>> providerRegistry = event.getPack().getOrCreateRegistry(
PROVIDER_REGISTRY_KEY);
providerRegistry.register(addon.key("PIPELINE"), BiomePipelineTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<Source>>> sourceRegistry = event.getPack().getOrCreateRegistry(
SOURCE_REGISTRY_KEY);
sourceRegistry.register(addon.key("SAMPLER"), SamplerSourceTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<Stage>>> 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<Biome> biomeRegistry = event.getPack().getRegistry(Biome.class);
event.getPack().applyLoader(PipelineBiome.class, new PipelineBiomeLoader(biomeRegistry));
});
}
}
@@ -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<Biome> {
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<Biome> consumer) {
consumer.accept(min, max, biome);
}
@Override
public void forEach(Consumer<Biome> consumer) {
for(int y = min; y < max; y++) {
consumer.accept(biome);
}
}
@Override
public void forEach(IntObjConsumer<Biome> consumer) {
for(int y = min; y < max; y++) {
consumer.accept(y, biome);
}
}
}
@@ -1,13 +1,5 @@
/*
* Copyright (c) 2020-2025 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;
package com.dfsek.terra.addons.biome.pipeline;
import com.dfsek.seismic.type.sampler.Sampler;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
@@ -17,27 +9,28 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.StreamSupport;
import com.dfsek.terra.addons.biome.pipeline.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.api.Pipeline;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
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.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.util.cache.SeededVector2Key;
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<SeededVector2Key, BiomeChunk> biomeChunkCache;
private final LoadingCache<SeededVector, BiomeChunk> biomeChunkCache;
private final int chunkSize;
private final int resolution;
private final Sampler mutator;
private final NoiseSampler mutator;
private final double noiseAmp;
private final Set<Biome> biomes;
public PipelineBiomeProvider(Pipeline pipeline, int resolution, Sampler mutator, double noiseAmp) {
public PipelineBiomeProvider(Pipeline pipeline, int resolution, NoiseSampler mutator, double noiseAmp) {
this.resolution = resolution;
this.mutator = mutator;
this.noiseAmp = noiseAmp;
@@ -82,8 +75,8 @@ public class PipelineBiomeProvider implements BiomeProvider {
public Biome getBiome(int x, int z, long seed) {
x += (int) (mutator.getSample(seed + 1, x, z) * noiseAmp);
z += (int) (mutator.getSample(seed + 2, x, z) * noiseAmp);
x += mutator.noise(seed + 1, x, z) * noiseAmp;
z += mutator.noise(seed + 2, x, z) * noiseAmp;
x /= resolution;
z /= resolution;
@@ -97,7 +90,7 @@ public class PipelineBiomeProvider implements BiomeProvider {
int xInChunk = x - chunkWorldX;
int zInChunk = z - chunkWorldZ;
return biomeChunkCache.get(new SeededVector2Key(chunkWorldX, chunkWorldZ, seed)).get(xInChunk, zInChunk).getBiome();
return biomeChunkCache.get(new SeededVector(seed, chunkWorldX, chunkWorldZ)).get(xInChunk, zInChunk).getBiome();
}
@Override
@@ -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);
}
@@ -1,7 +1,7 @@
package com.dfsek.terra.addons.biome.pipeline.api;
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint;
/**
@@ -10,7 +10,7 @@ import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
*/
public interface Expander extends Stage {
PipelineBiome fillBiome(BiomeChunkImpl.ViewPoint viewPoint);
PipelineBiome fillBiome(ViewPoint viewPoint);
@Override
default int maxRelativeReadDistance() {
@@ -18,7 +18,7 @@ public interface Expander extends Stage {
}
@Override
default PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) {
default PipelineBiome apply(ViewPoint viewPoint) {
PipelineBiome currentBiome = viewPoint.getBiome();
if(currentBiome == null) {
return fillBiome(viewPoint);
@@ -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<Stage> getStages();
}
@@ -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)));
}
}
@@ -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<PipelineBiome> getBiomes();
}
@@ -0,0 +1,15 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint;
public interface Stage {
PipelineBiome apply(ViewPoint viewPoint);
int maxRelativeReadDistance();
default Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
return biomes;
}
}
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.pipeline.api.biome;
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.Set;
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.pipeline.api.biome;
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.Set;
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.pipeline.api.biome;
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.HashSet;
import java.util.Set;
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.pipeline.api.biome;
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.Collections;
import java.util.Set;
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2020-2023 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.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.PipelineImpl;
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<BiomeProvider> {
@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("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;
@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;
@Override
public BiomeProvider get() {
return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 128), resolution, blendSampler, blendAmplitude);
}
}
@@ -1,4 +1,4 @@
package com.dfsek.terra.addons.biome.pipeline.config;
package com.dfsek.terra.addons.biome.pipeline.v2.config;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
@@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.AnnotatedType;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
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;
@@ -1,27 +1,27 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config.source;
package com.dfsek.terra.addons.biome.pipeline.v2.config.source;
import com.dfsek.seismic.type.sampler.Sampler;
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.api.Source;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.source.SamplerSource;
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 Sampler noise;
private @Meta NoiseSampler noise;
@Value("biomes")
@Description("The biomes to be distributed.")
@@ -1,15 +1,15 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config.source;
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.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
public abstract class SourceTemplate implements ObjectTemplate<Source> {
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2020-2023 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<Stage> {
@Value("sampler")
@Description("Sampler to use for stage distribution.")
protected @Meta NoiseSampler noise;
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020-2023 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.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.expander.FractalExpander;
public class ExpanderStageTemplate extends StageTemplate {
@Override
public Expander get() {
return new FractalExpander(noise);
}
}
@@ -1,20 +1,20 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config.stage.mutator;
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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.stage.mutators.BorderListStage;
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.config.stage.StageTemplate;
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;
@@ -1,18 +1,18 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config.stage.mutator;
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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.stage.mutators.BorderStage;
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.config.stage.StageTemplate;
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;
@@ -1,20 +1,20 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config.stage.mutator;
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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.stage.mutators.ReplaceListStage;
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.config.stage.StageTemplate;
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;
@@ -1,18 +1,18 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.config.stage.mutator;
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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.stage.mutators.ReplaceStage;
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.config.stage.StageTemplate;
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;
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020-2023 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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.SmoothStage;
public class SmoothStageTemplate extends StageTemplate {
@Override
public Stage get() {
return new SmoothStage(noise);
}
}
@@ -1,25 +1,23 @@
package com.dfsek.terra.addons.biome.pipeline.pipeline;
import com.dfsek.seismic.math.floatingpoint.FloatingPointFunctions;
package com.dfsek.terra.addons.biome.pipeline.v2.pipeline;
import java.util.List;
import com.dfsek.terra.addons.biome.pipeline.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.api.util.cache.SeededVector2Key;
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;
public class BiomeChunkImpl implements BiomeChunk {
private final SeededVector2Key worldOrigin;
private final SeededVector worldOrigin;
private final int chunkOriginArrayIndex;
private final int worldCoordinateScale;
private final int size;
private PipelineBiome[] biomes;
public BiomeChunkImpl(SeededVector2Key worldOrigin, PipelineImpl pipeline) {
public BiomeChunkImpl(SeededVector worldOrigin, PipelineImpl pipeline) {
this.worldOrigin = worldOrigin;
this.chunkOriginArrayIndex = pipeline.getChunkOriginArrayIndex();
@@ -46,7 +44,7 @@ public class BiomeChunkImpl implements BiomeChunk {
for(int gridZ = 0; gridZ < gridSize; gridZ++) {
int xIndex = gridOrigin + gridX * gridInterval;
int zIndex = gridOrigin + gridZ * gridInterval;
biomes[(xIndex * size) + zIndex] = pipeline.getSource().get(worldOrigin.seed, xIndexToWorldCoordinate(xIndex),
biomes[(xIndex * size) + zIndex] = pipeline.getSource().get(worldOrigin.seed(), xIndexToWorldCoordinate(xIndex),
zIndexToWorldCoordinate(zIndex));
}
}
@@ -73,13 +71,12 @@ public class BiomeChunkImpl implements BiomeChunk {
lookupArray = tempArray;
// Apply stage to working grid
ViewPoint viewPoint = new ViewPoint(this, gridInterval, lookupArray, size);
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;
viewPoint.set(gridX, gridZ, xIndex, zIndex);
biomes[(xIndex * size) + zIndex] = stage.apply(viewPoint);
biomes[(xIndex * size) + zIndex] = stage.apply(
new ViewPoint(this, gridInterval, gridX, gridZ, xIndex, zIndex, lookupArray, size));
}
}
}
@@ -101,7 +98,7 @@ public class BiomeChunkImpl implements BiomeChunk {
// 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 FloatingPointFunctions.ceil((double) finalGridOrigin / initialGridInterval) * initialGridInterval;
return (int) Math.ceil((double) finalGridOrigin / initialGridInterval) * initialGridInterval;
}
private static int calculateFinalGridOrigin(int totalExpanderCount, List<Stage> stages) {
@@ -142,14 +139,14 @@ public class BiomeChunkImpl implements BiomeChunk {
}
private int xIndexToWorldCoordinate(int xIndex) {
return (worldOrigin.x + xIndex - chunkOriginArrayIndex) * worldCoordinateScale;
return (worldOrigin.x() + xIndex - chunkOriginArrayIndex) * worldCoordinateScale;
}
private int zIndexToWorldCoordinate(int zIndex) {
return (worldOrigin.z + zIndex - chunkOriginArrayIndex) * worldCoordinateScale;
return (worldOrigin.z() + zIndex - chunkOriginArrayIndex) * worldCoordinateScale;
}
private SeededVector2Key getOrigin() {
private SeededVector getOrigin() {
return worldOrigin;
}
@@ -158,32 +155,25 @@ public class BiomeChunkImpl implements BiomeChunk {
*/
public static class ViewPoint {
private final BiomeChunkImpl chunk;
private PipelineBiome biome;
private final PipelineBiome biome;
private final int gridInterval;
private int gridX;
private int gridZ;
private int xIndex;
private int zIndex;
private final int gridX;
private final int gridZ;
private final int xIndex;
private final int zIndex;
private final PipelineBiome[] lookupArray;
private final int size;
private ViewPoint(BiomeChunkImpl chunk, int gridInterval,
private ViewPoint(BiomeChunkImpl chunk, int gridInterval, int gridX, int gridZ, int xIndex, int zIndex,
PipelineBiome[] lookupArray, int size) {
this.chunk = chunk;
this.gridInterval = gridInterval;
this.gridX = 0;
this.gridZ = 0;
this.xIndex = 0;
this.zIndex = 0;
this.lookupArray = lookupArray;
this.size = size;
}
public void set(int gridX, int gridZ, int xIndex, int zIndex) {
this.gridX = gridX;
this.gridZ = gridZ;
this.xIndex = xIndex;
this.zIndex = zIndex;
this.lookupArray = lookupArray;
this.size = size;
this.biome = lookupArray[(this.xIndex * this.size) + this.zIndex];
}
@@ -226,7 +216,7 @@ public class BiomeChunkImpl implements BiomeChunk {
}
public long worldSeed() {
return chunk.getOrigin().seed;
return chunk.getOrigin().seed();
}
}
}
@@ -1,16 +1,16 @@
package com.dfsek.terra.addons.biome.pipeline.pipeline;
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.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.api.Pipeline;
import com.dfsek.terra.addons.biome.pipeline.api.Source;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.api.util.cache.SeededVector2Key;
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 {
@@ -55,7 +55,7 @@ public class PipelineImpl implements Pipeline {
}
@Override
public BiomeChunk generateChunk(SeededVector2Key worldCoordinates) {
public BiomeChunk generateChunk(SeededVector worldCoordinates) {
return new BiomeChunkImpl(worldCoordinates, this);
}
@@ -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<PipelineBiome> biomes;
private final NoiseSampler sampler;
public SamplerSource(ProbabilityCollection<PipelineBiome> 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<PipelineBiome> getBiomes() {
return biomes.getContents();
}
}
@@ -1,10 +1,10 @@
package com.dfsek.terra.addons.biome.pipeline.source;
package com.dfsek.terra.addons.biome.pipeline.v2.source;
import java.util.Collections;
import java.util.Set;
import com.dfsek.terra.addons.biome.pipeline.api.Source;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
public class SingleSource implements Source {
@@ -1,17 +1,16 @@
package com.dfsek.terra.addons.biome.pipeline.stage.expander;
package com.dfsek.terra.addons.biome.pipeline.v2.stage.expander;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.terra.addons.biome.pipeline.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
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 Sampler sampler;
private final NoiseSampler sampler;
public FractalExpander(Sampler sampler) {
public FractalExpander(NoiseSampler sampler) {
this.sampler = sampler;
}
@@ -21,7 +20,7 @@ public class FractalExpander implements Expander {
int xMod2 = viewPoint.gridX() % 2;
int zMod2 = viewPoint.gridZ() % 2;
double roll = sampler.getSample(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ());
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);
@@ -1,14 +1,11 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.stage.mutators;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.seismic.type.vector.Vector2Int;
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.ArrayList;
import java.util.HashSet;
@@ -17,15 +14,17 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
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 Sampler Sampler;
private final NoiseSampler noiseSampler;
private final ProbabilityCollection<PipelineBiome> replaceDefault;
private final String defaultReplace;
private final Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace;
@@ -33,9 +32,9 @@ public class BorderListStage implements Stage {
private final Vector2Int[] borderPoints;
public BorderListStage(Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace, String border, String defaultReplace,
Sampler Sampler, ProbabilityCollection<PipelineBiome> replaceDefault) {
NoiseSampler noiseSampler, ProbabilityCollection<PipelineBiome> replaceDefault) {
this.border = border;
this.Sampler = Sampler;
this.noiseSampler = noiseSampler;
this.replaceDefault = replaceDefault;
this.defaultReplace = defaultReplace;
this.replace = replace;
@@ -68,11 +67,11 @@ public class BorderListStage implements Stage {
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(Sampler, viewPoint.worldX(), viewPoint.worldZ(),
PipelineBiome replacement = replace.get(center).get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(),
viewPoint.worldSeed());
return replacement.isSelf() ? center : replacement;
}
PipelineBiome replacement = replaceDefault.get(Sampler, viewPoint.worldX(), viewPoint.worldZ(),
PipelineBiome replacement = replaceDefault.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(),
viewPoint.worldSeed());
return replacement.isSelf() ? center : replacement;
}
@@ -1,14 +1,11 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.stage.mutators;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.seismic.type.vector.Vector2Int;
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.ArrayList;
import java.util.HashSet;
@@ -16,22 +13,24 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
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 Sampler Sampler;
private final NoiseSampler noiseSampler;
private final ProbabilityCollection<PipelineBiome> replace;
private final String replaceTag;
private final Vector2Int[] borderPoints;
public BorderStage(String border, String replaceTag, Sampler Sampler, ProbabilityCollection<PipelineBiome> replace) {
public BorderStage(String border, String replaceTag, NoiseSampler noiseSampler, ProbabilityCollection<PipelineBiome> replace) {
this.border = border;
this.Sampler = Sampler;
this.noiseSampler = noiseSampler;
this.replace = replace;
this.replaceTag = replaceTag;
List<Vector2Int> points = new ArrayList<>();
@@ -51,7 +50,7 @@ public class BorderStage implements Stage {
for(Vector2Int point : borderPoints) {
PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ());
if(current != null && current.getTags().contains(border)) {
PipelineBiome replacement = replace.get(Sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
PipelineBiome replacement = replace.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
return replacement.isSelf() ? center : replacement;
}
}
@@ -1,33 +1,32 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.stage.mutators;
import com.dfsek.seismic.type.sampler.Sampler;
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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
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<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace;
private final Sampler sampler;
private final NoiseSampler sampler;
private final ProbabilityCollection<PipelineBiome> replaceDefault;
private final String defaultTag;
public ReplaceListStage(Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace, String defaultTag,
ProbabilityCollection<PipelineBiome> replaceDefault, Sampler sampler) {
ProbabilityCollection<PipelineBiome> replaceDefault, NoiseSampler sampler) {
this.replace = replace;
this.sampler = sampler;
this.defaultTag = defaultTag;
@@ -1,30 +1,29 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.stage.mutators;
import com.dfsek.seismic.type.sampler.Sampler;
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.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
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<PipelineBiome> replace;
private final Sampler sampler;
private final NoiseSampler sampler;
public ReplaceStage(String replaceable, ProbabilityCollection<PipelineBiome> replace, Sampler sampler) {
public ReplaceStage(String replaceable, ProbabilityCollection<PipelineBiome> replace, NoiseSampler sampler) {
this.replaceableTag = replaceable;
this.replace = replace;
this.sampler = sampler;
@@ -1,26 +1,25 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.stage.mutators;
import com.dfsek.seismic.type.sampler.Sampler;
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.Objects;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
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 Sampler sampler;
private final NoiseSampler sampler;
public SmoothStage(Sampler sampler) {
public SmoothStage(NoiseSampler sampler) {
this.sampler = sampler;
}
@@ -31,7 +30,7 @@ public class SmoothStage implements Stage {
PipelineBiome left = viewPoint.getRelativeBiome(0, 1);
PipelineBiome right = viewPoint.getRelativeBiome(0, -1);
double roll = sampler.getSample(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ());
double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ());
boolean vert = Objects.equals(top, bottom);
boolean horiz = Objects.equals(left, right);
@@ -1,10 +1,10 @@
schema-version: 1
contributors:
- Terra contributors
id: command-locate
id: biome-provider-pipeline-v2
version: @VERSION@
entrypoints:
- "com.dfsek.terra.addons.commands.locate.LocateCommandAddon"
- "com.dfsek.terra.addons.biome.pipeline.v2.BiomePipelineAddon"
website:
issues: https://github.com/PolyhedralDev/Terra/issues
source: https://github.com/PolyhedralDev/Terra
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2025 Polyhedral Development
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
@@ -1,12 +1,7 @@
# biome-provider-pipeline-2
# biome-provider-pipeline
The second version of the Biome Pipeline, a procedural biome provider that uses a series
Implements 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.
@@ -1,5 +1,5 @@
version = version("2.0.0")
version = version("1.0.2")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
}
}
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2020-2023 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;
import com.dfsek.terra.addons.biome.pipeline.api.BiomeHolder;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
import com.dfsek.terra.addons.biome.pipeline.api.stage.type.BiomeExpander;
import com.dfsek.terra.addons.biome.pipeline.api.stage.type.BiomeMutator;
import com.dfsek.terra.addons.biome.pipeline.source.BiomeSource;
import com.dfsek.terra.api.util.vector.Vector2;
public class BiomeHolderImpl implements BiomeHolder {
private final Vector2.Mutable origin;
private final int width;
private final int offset;
private BiomeDelegate[][] biomes;
public BiomeHolderImpl(int width, Vector2.Mutable origin) {
width += 4;
this.width = width;
biomes = new BiomeDelegate[width][width];
this.origin = origin;
this.offset = 2;
}
private BiomeHolderImpl(BiomeDelegate[][] biomes, Vector2.Mutable origin, int width, int offset) {
this.biomes = biomes;
this.origin = origin;
this.width = width;
this.offset = 2 * offset;
}
@Override
public BiomeHolder expand(BiomeExpander expander, long seed) {
BiomeDelegate[][] old = biomes;
int newWidth = width * 2 - 1;
biomes = new BiomeDelegate[newWidth][newWidth];
for(int x = 0; x < width; x++) {
for(int z = 0; z < width; z++) {
biomes[x * 2][z * 2] = old[x][z];
if(z != width - 1)
biomes[x * 2][z * 2 + 1] = expander.getBetween(x + origin.getX(), z + 1 + origin.getZ(), seed, old[x][z],
old[x][z + 1]);
if(x != width - 1)
biomes[x * 2 + 1][z * 2] = expander.getBetween(x + 1 + origin.getX(), z + origin.getZ(), seed, old[x][z],
old[x + 1][z]);
if(x != width - 1 && z != width - 1)
biomes[x * 2 + 1][z * 2 + 1] = expander.getBetween(x + 1 + origin.getX(), z + 1 + origin.getZ(), seed, old[x][z],
old[x + 1][z + 1], old[x][z + 1], old[x + 1][z]);
}
}
return new BiomeHolderImpl(biomes, origin.setX(origin.getX() * 2 - 1).setZ(origin.getZ() * 2 - 1), newWidth, offset);
}
@Override
public void mutate(BiomeMutator mutator, long seed) {
for(int x = 0; x < width; x++) {
for(int z = 0; z < width; z++) {
BiomeMutator.ViewPoint viewPoint = new BiomeMutator.ViewPoint(this, x, z);
biomes[x][z] = mutator.mutate(viewPoint, x + origin.getX(), z + origin.getZ(), seed);
}
}
}
@Override
public void fill(BiomeSource source, long seed) {
for(int x = 0; x < width; x++) {
for(int z = 0; z < width; z++) {
biomes[x][z] = source.getBiome(origin.getX() + x, origin.getZ() + z, seed);
}
}
}
@Override
public BiomeDelegate getBiome(int x, int z) {
x += offset;
z += offset;
return getBiomeRaw(x, z);
}
@Override
public BiomeDelegate getBiomeRaw(int x, int z) {
if(x >= width || z >= width || x < 0 || z < 0) return null;
return biomes[x][z];
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2020-2023 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.dfsek.terra.addons.biome.pipeline.api.BiomeHolder;
import com.dfsek.terra.addons.biome.pipeline.api.stage.Stage;
import com.dfsek.terra.addons.biome.pipeline.source.BiomeSource;
import com.dfsek.terra.api.util.vector.Vector2;
public class BiomePipeline {
private final BiomeSource source;
private final List<Stage> stages;
private final int size;
private final int init;
private BiomePipeline(BiomeSource source, List<Stage> stages, int size, int init) {
this.source = source;
this.stages = stages;
this.size = size;
this.init = init;
}
/**
* Get biomes in a chunk
*
* @param x Chunk X coord
* @param z Chunk Z coord
*
* @return BiomeHolder containing biomes.
*/
public BiomeHolder getBiomes(int x, int z, long seed) {
BiomeHolder holder = new BiomeHolderImpl(init, Vector2.of(x * (init - 1), z * (init - 1)).mutable());
holder.fill(source, seed);
for(Stage stage : stages) holder = stage.apply(holder, seed);
return holder;
}
public BiomeSource getSource() {
return source;
}
public List<Stage> getStages() {
return Collections.unmodifiableList(stages);
}
public int getSize() {
return size;
}
public static final class BiomePipelineBuilder {
private final int init;
private final List<Stage> stages = new ArrayList<>();
private int expand;
public BiomePipelineBuilder(int init) {
this.init = init;
expand = init;
}
public BiomePipeline build(BiomeSource source) {
for(Stage stage : stages) {
if(stage.isExpansion()) expand = expand * 2 - 1;
}
return new BiomePipeline(source, stages, expand, init);
}
public BiomePipelineBuilder addStage(Stage stage) {
stages.add(stage);
return this;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.
@@ -8,21 +8,23 @@
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;
import com.dfsek.terra.addons.biome.pipeline.api.Source;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
import com.dfsek.terra.addons.biome.pipeline.api.stage.Stage;
import com.dfsek.terra.addons.biome.pipeline.config.BiomeDelegateLoader;
import com.dfsek.terra.addons.biome.pipeline.config.BiomePipelineTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.PipelineBiomeLoader;
import com.dfsek.terra.addons.biome.pipeline.config.source.SamplerSourceTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.SamplerSourceTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.expander.ExpanderStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.BorderListStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.BorderStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.ReplaceListStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.ReplaceStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.SmoothStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.BorderListMutatorTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.BorderMutatorTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.ReplaceListMutatorTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.ReplaceMutatorTemplate;
import com.dfsek.terra.addons.biome.pipeline.config.stage.mutator.SmoothMutatorTemplate;
import com.dfsek.terra.addons.biome.pipeline.source.BiomeSource;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
@@ -39,13 +41,13 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomePipelineAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<Source>>> SOURCE_REGISTRY_KEY = new TypeKey<>() {
public static final TypeKey<Supplier<ObjectTemplate<BiomeSource>>> SOURCE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<Stage>>> STAGE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
};
private static final Logger logger = LoggerFactory.getLogger(BiomePipelineAddon.class);
@Inject
private Platform platform;
@@ -63,7 +65,7 @@ public class BiomePipelineAddon implements AddonInitializer {
providerRegistry.register(addon.key("PIPELINE"), BiomePipelineTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<Source>>> sourceRegistry = event.getPack().getOrCreateRegistry(
CheckedRegistry<Supplier<ObjectTemplate<BiomeSource>>> sourceRegistry = event.getPack().getOrCreateRegistry(
SOURCE_REGISTRY_KEY);
sourceRegistry.register(addon.key("SAMPLER"), SamplerSourceTemplate::new);
})
@@ -71,11 +73,11 @@ public class BiomePipelineAddon implements AddonInitializer {
CheckedRegistry<Supplier<ObjectTemplate<Stage>>> 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);
stageRegistry.register(addon.key("SMOOTH"), SmoothMutatorTemplate::new);
stageRegistry.register(addon.key("REPLACE"), ReplaceMutatorTemplate::new);
stageRegistry.register(addon.key("REPLACE_LIST"), ReplaceListMutatorTemplate::new);
stageRegistry.register(addon.key("BORDER"), BorderMutatorTemplate::new);
stageRegistry.register(addon.key("BORDER_LIST"), BorderListMutatorTemplate::new);
})
.failThrough();
platform.getEventManager()
@@ -83,7 +85,12 @@ public class BiomePipelineAddon implements AddonInitializer {
.register(addon, ConfigPackPostLoadEvent.class)
.then(event -> {
Registry<Biome> biomeRegistry = event.getPack().getRegistry(Biome.class);
event.getPack().applyLoader(PipelineBiome.class, new PipelineBiomeLoader(biomeRegistry));
event.getPack().applyLoader(BiomeDelegate.class, new BiomeDelegateLoader(biomeRegistry));
});
if(platform.getTerraConfig().isDebugLog())
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.");
}
}
@@ -9,7 +9,7 @@ import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomePipelineColumn implements Column<Biome> {
class BiomePipelineColumn implements Column<Biome> {
private final int min;
private final int max;
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2020-2023 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;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
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.addons.biome.pipeline.api.BiomeHolder;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
import com.dfsek.terra.addons.biome.pipeline.api.stage.Stage;
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 BiomePipelineProvider implements BiomeProvider {
private final LoadingCache<SeededVector, BiomeHolder> holderCache;
private final BiomePipeline pipeline;
private final int resolution;
private final NoiseSampler mutator;
private final double noiseAmp;
private final Set<Biome> biomes;
public BiomePipelineProvider(BiomePipeline pipeline, int resolution, NoiseSampler mutator, double noiseAmp) {
this.resolution = resolution;
this.mutator = mutator;
this.noiseAmp = noiseAmp;
holderCache = Caffeine.newBuilder()
.maximumSize(1024)
.build(key -> pipeline.getBiomes(key.x, key.z, key.seed));
this.pipeline = pipeline;
Set<BiomeDelegate> biomeSet = new HashSet<>();
pipeline.getSource().getBiomes().forEach(biomeSet::add);
Iterable<BiomeDelegate> result = biomeSet;
for(Stage stage : pipeline.getStages()) {
result = stage.getBiomes(result); // pass through all stages
}
this.biomes = new HashSet<>();
Iterable<BiomeDelegate> finalResult = result;
result.forEach(biomeDelegate -> {
if(biomeDelegate.isEphemeral()) {
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 ephemeral biome \"" + biomeDelegate.getID() +
"\". Ensure there is a stage to guarantee replacement of the ephemeral biome. Biomes: " +
biomeList);
}
this.biomes.add(biomeDelegate.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 fdX = Math.floorDiv(x, pipeline.getSize());
int fdZ = Math.floorDiv(z, pipeline.getSize());
return holderCache.get(new SeededVector(fdX, fdZ, seed)).getBiome(x - fdX * pipeline.getSize(),
z - fdZ * pipeline.getSize()).getBiome();
}
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z, seed));
}
@Override
public Iterable<Biome> getBiomes() {
return biomes;
}
@Override
public Column<Biome> 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;
}
private record SeededVector(int x, int z, long seed) {
@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)));
}
}
}
@@ -1,10 +0,0 @@
package com.dfsek.terra.addons.biome.pipeline.api;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
public interface BiomeChunk {
PipelineBiome get(int xInChunk, int zInChunk);
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2020-2023 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.api;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
import com.dfsek.terra.addons.biome.pipeline.api.stage.type.BiomeExpander;
import com.dfsek.terra.addons.biome.pipeline.api.stage.type.BiomeMutator;
import com.dfsek.terra.addons.biome.pipeline.source.BiomeSource;
public interface BiomeHolder {
BiomeHolder expand(BiomeExpander expander, long seed);
void mutate(BiomeMutator mutator, long seed);
void fill(BiomeSource source, long seed);
BiomeDelegate getBiome(int x, int z);
BiomeDelegate getBiomeRaw(int x, int z);
}
@@ -1,16 +0,0 @@
package com.dfsek.terra.addons.biome.pipeline.api;
import java.util.List;
import com.dfsek.terra.api.util.cache.SeededVector2Key;
public interface Pipeline {
BiomeChunk generateChunk(SeededVector2Key worldCoordinates);
int getChunkSize();
Source getSource();
List<Stage> getStages();
}
@@ -1,11 +0,0 @@
package com.dfsek.terra.addons.biome.pipeline.api;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
public interface Source {
PipelineBiome get(long seed, int x, int z);
Iterable<PipelineBiome> getBiomes();
}
@@ -1,15 +0,0 @@
package com.dfsek.terra.addons.biome.pipeline.api;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.pipeline.BiomeChunkImpl;
public interface Stage {
PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint);
int maxRelativeReadDistance();
default Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
return biomes;
}
}
@@ -0,0 +1,35 @@
package com.dfsek.terra.addons.biome.pipeline.api.delegate;
import java.util.Set;
import com.dfsek.terra.api.registry.key.StringIdentifiable;
import com.dfsek.terra.api.world.biome.Biome;
public interface BiomeDelegate extends StringIdentifiable {
static BiomeDelegate ephemeral(String id) {
return new EphemeralBiomeDelegate(id);
}
static BiomeDelegate from(Biome biome) {
return new DelegatedBiome(biome);
}
static BiomeDelegate self() {
return SelfDelegate.INSTANCE;
}
Biome getBiome();
Set<String> getTags();
default boolean isEphemeral() {
return false;
}
default boolean isSelf() {
return false;
}
}
@@ -0,0 +1,40 @@
package com.dfsek.terra.addons.biome.pipeline.api.delegate;
import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome;
final class DelegatedBiome implements BiomeDelegate {
private final Biome biome;
public DelegatedBiome(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 DelegatedBiome that)) return false;
return that.biome.equals(this.biome);
}
@Override
public Set<String> getTags() {
return biome.getTags();
}
@Override
public String getID() {
return biome.getID();
}
}
@@ -0,0 +1,51 @@
package com.dfsek.terra.addons.biome.pipeline.api.delegate;
import java.util.HashSet;
import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome;
final class EphemeralBiomeDelegate implements BiomeDelegate {
private final Set<String> tags;
private final String id;
public EphemeralBiomeDelegate(String id) {
this.id = id;
tags = new HashSet<>();
tags.add(id);
tags.add("ALL");
}
@Override
public Biome getBiome() {
throw new UnsupportedOperationException("Cannot get biome from ephemeral delegate");
}
@Override
public Set<String> getTags() {
return tags;
}
@Override
public String getID() {
return id;
}
@Override
public boolean isEphemeral() {
return true;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof EphemeralBiomeDelegate that)) return false;
return this.id.equals(that.id);
}
}
@@ -0,0 +1,40 @@
package com.dfsek.terra.addons.biome.pipeline.api.delegate;
import java.util.Collections;
import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome;
final class SelfDelegate implements BiomeDelegate {
public static final SelfDelegate INSTANCE = new SelfDelegate();
private SelfDelegate() {
}
@Override
public Biome getBiome() {
throw new UnsupportedOperationException("Cannot get biome from self delegate");
}
@Override
public boolean isSelf() {
return true;
}
@Override
public boolean isEphemeral() {
return true;
}
@Override
public Set<String> getTags() {
return Collections.emptySet();
}
@Override
public String getID() {
return "SELF";
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020-2023 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.api.stage;
import com.dfsek.terra.addons.biome.pipeline.api.BiomeHolder;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
public interface Stage {
BiomeHolder apply(BiomeHolder in, long seed);
boolean isExpansion();
Iterable<BiomeDelegate> getBiomes(Iterable<BiomeDelegate> biomes);
}
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2020-2023 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.api.stage.type;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
public interface BiomeExpander {
BiomeDelegate getBetween(double x, double z, long seed, BiomeDelegate... others);
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2023 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.api.stage.type;
import com.dfsek.terra.addons.biome.pipeline.api.BiomeHolder;
import com.dfsek.terra.addons.biome.pipeline.api.delegate.BiomeDelegate;
public interface BiomeMutator {
BiomeDelegate mutate(ViewPoint viewPoint, double x, double z, long seed);
default Iterable<BiomeDelegate> getBiomes(Iterable<BiomeDelegate> biomes) {
return biomes;
}
class ViewPoint {
private final BiomeHolder biomes;
private final int offX;
private final int offZ;
public ViewPoint(BiomeHolder biomes, int offX, int offZ) {
this.biomes = biomes;
this.offX = offX;
this.offZ = offZ;
}
public BiomeDelegate getBiome(int x, int z) {
return biomes.getBiomeRaw(x + offX, z + offZ);
}
}
}
@@ -0,0 +1,32 @@
package com.dfsek.terra.addons.biome.pipeline.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.api.delegate.BiomeDelegate;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.world.biome.Biome;
public class BiomeDelegateLoader implements TypeLoader<BiomeDelegate> {
private final Registry<Biome> biomeRegistry;
public BiomeDelegateLoader(Registry<Biome> biomeRegistry) {
this.biomeRegistry = biomeRegistry;
}
@Override
public BiomeDelegate load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker)
throws LoadException {
if(c.equals("SELF")) return BiomeDelegate.self();
return biomeRegistry
.getByID((String) c)
.map(BiomeDelegate::from)
.orElseGet(() -> BiomeDelegate.ephemeral((String) c));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2025 Polyhedral Development
* Copyright (c) 2020-2023 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.
@@ -7,49 +7,48 @@
package com.dfsek.terra.addons.biome.pipeline.config;
import com.dfsek.seismic.type.sampler.Sampler;
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.PipelineBiomeProvider;
import com.dfsek.terra.addons.biome.pipeline.api.Source;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.pipeline.PipelineImpl;
import com.dfsek.terra.addons.biome.pipeline.BiomePipeline;
import com.dfsek.terra.addons.biome.pipeline.BiomePipelineProvider;
import com.dfsek.terra.addons.biome.pipeline.api.stage.Stage;
import com.dfsek.terra.addons.biome.pipeline.source.BiomeSource;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@SuppressWarnings({ "FieldMayBeFinal", "unused" })
public class BiomePipelineTemplate implements ObjectTemplate<BiomeProvider> {
@Value("resolution")
public class BiomePipelineTemplate extends BiomeProviderTemplate {
@Value("pipeline.initial-size")
@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("blend.sampler")
@Default
@Description("A sampler to use for blending the edges of biomes via domain warping.")
protected @Meta Sampler blendSampler = Sampler.zero();
@Value("blend.amplitude")
@Default
@Description("The amplitude at which to perform blending.")
protected @Meta double blendAmplitude = 0d;
The initial size of biome chunks. This value must be at least 2.
<b>This is not the final size of biome chunks. Final chunks will be much larger</b>.
It is recommended to keep biome chunks' final size in the range of [50, 300]
to prevent performance issues. To calculate the size of biome chunks, simply
take initial-size and for each expand stage, multiply the running value by 2
and subtract 1. (The size is also printed to the server console if you
have debug mode enabled)""")
private @Meta int initialSize = 2;
@Value("pipeline.source")
@Description("The Biome Source to use for initial population of biomes.")
private @Meta Source source;
private @Meta BiomeSource source;
@Value("pipeline.stages")
@Description("A list of pipeline stages to apply to the result of #source")
private @Meta List<@Meta Stage> stages;
@Override
public BiomeProvider get() {
return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 128), resolution, blendSampler, blendAmplitude);
BiomePipeline.BiomePipelineBuilder biomePipelineBuilder = new BiomePipeline.BiomePipelineBuilder(initialSize);
stages.forEach(biomePipelineBuilder::addStage);
BiomePipeline pipeline = biomePipelineBuilder.build(source);
return new BiomePipelineProvider(pipeline, resolution, blend, blendAmp);
}
}

Some files were not shown because too many files have changed in this diff Show More