Compare commits

...

37 Commits

Author SHA1 Message Date
dfsek 2e3ce78521 add ai guidelines 2026-01-09 21:41:33 -07:00
Zoe Gidiere c6eb2f49f3 Revert "Merge pull request #538 from ryzech/fix/caffeine-relocate"
This reverts commit 6929de7b61, reversing
changes made to a159debe3e.
2025-12-17 01:20:33 -07:00
Zoë Gidiere 6929de7b61 Merge pull request #538 from ryzech/fix/caffeine-relocate
Relocate caffeine to fix conflicts with other mods/plugins
2025-12-14 16:11:16 +00:00
Zoe Gidiere a159debe3e update publishing config 2025-12-13 17:55:17 -07:00
Zoe Gidiere 99e2907b2d add java doc build 2025-12-13 17:45:47 -07:00
Zoe Gidiere ac71b3ec0c Fix java doc dir 2025-12-13 17:41:21 -07:00
Zoe Gidiere e184937743 WIP Jenkinsfile 2025-12-13 17:34:13 -07:00
Zoe Gidiere 8c155c78eb step arg as min res 2025-12-13 11:35:39 -07:00
Zoe Gidiere fc779e1120 Auto-mode 2025-12-13 11:32:29 -07:00
Zoe Gidiere be964da4fa paper async 2025-12-13 10:48:18 -07:00
Zoe Gidiere 6ca401413b Terra Search Command 2025-12-13 10:34:01 -07:00
Zoe Gidiere e556e2bca1 Update Seismic 2025-12-13 10:33:18 -07:00
Zoe Gidiere 1fc97a480a Small biomechunk allocation opt 2025-12-11 14:44:32 -07:00
Zoe Gidiere bba55f2669 Align TriStateIntCache 2025-12-11 01:56:20 -07:00
Zoe Gidiere 97b4ea6d94 Improve PaddedGridDistributor Performance 2025-12-10 23:17:06 -07:00
Zoe Gidiere cf4f7822e2 Download JavaDocs and Sources in Idea 2025-12-10 23:16:39 -07:00
Zoe Gidiere d6285a5901 Improve TriStateintCache with Unsafe 2025-12-10 22:33:49 -07:00
Zoe Gidiere 8b933b0d5c Merge remote-tracking branch 'origin/master' 2025-12-10 19:28:43 -07:00
Zoe Gidiere f9a5dfbfce Update seismic 2025-12-10 19:28:39 -07:00
Zoë Gidiere 24bca3ed98 Merge pull request #542 from everbuild-org/fix/minestom-nbt-additions
Minestom Nbt Support
2025-12-10 18:57:19 -07:00
Zoe Gidiere f8f6b0b4bc Reformat 2025-12-10 18:52:00 -07:00
Zoe Gidiere dd2f0365b0 More Extrusion Opts 2025-12-10 18:49:03 -07:00
Christian Bergschneider ae2d801be0 perf: reduce object churn by swapping to long keys in GeneratedChunkCache 2025-12-11 02:33:40 +01:00
Christian Bergschneider 8578bba7b9 perf: reduce object churn by using minestom block states 2025-12-11 02:00:13 +01:00
Christian Bergschneider d262831107 feat: add basic minestom NBT additions and improve block state parser performance 2025-12-11 01:30:40 +01:00
Zoe Gidiere ddc8cc7db5 Reformat 2025-12-10 02:55:26 -07:00
Zoe Gidiere 12f1b3f8fc Use ASM to to compile extrusion pipelines 2025-12-10 02:54:44 -07:00
Zoe Gidiere dc7c57d1a3 Update seismic 2 2025-12-10 02:18:10 -07:00
Zoe Gidiere 309fb5af96 Update Seismic 2025-12-08 13:29:10 -07:00
RyzechDev 9d747aed71 Relocate caffeine to fix conflicts with other mods/plugins 2025-11-19 10:14:22 -06:00
Zoë Gidiere 3cf11a9ad4 Merge pull request #534 from AllayMC/feat/meta-pack
feat: add support for meta pack in allay platform
2025-10-14 13:34:24 -06:00
Zoë Gidiere 9f766b0647 Merge pull request #535 from PolyhedralDev/dev/1.21.10
Cleaned up Bukkit NMS bindings and marked 1.21.10 as supported
2025-10-14 13:33:51 -06:00
daoge_cmd 8fa3978dc8 feat: update resource files 2025-10-14 22:10:35 +08:00
daoge_cmd 9d0fa0a7c4 Merge branch 'master' into feat/meta-pack 2025-10-10 10:04:15 +08:00
daoge_cmd 8c532ede8e fix: ignore the block when out of bounds 2025-10-10 10:03:01 +08:00
daoge_cmd 0144200ec9 doc: update comment 2025-10-10 00:46:55 +08:00
daoge_cmd d1ad3d04e1 feat: add support for meta pack 2025-10-10 00:30:38 +08:00
56 changed files with 1214 additions and 217 deletions
+2 -1
View File
@@ -44,6 +44,7 @@ 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
@@ -98,7 +99,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.
Please provide reliable evidence of this. LLM-generated code does not satisfy this requirement.
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.)
-->
+5 -2
View File
@@ -258,6 +258,9 @@ 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
@@ -381,7 +384,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.
@@ -391,7 +394,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. (Eg. don't assume
- Don't assume that a specific block, item, or entity exists. (E.g. don't assume
there exists a block called `minecraft:grass_block`)
### Data-Driven
Vendored
+149
View File
@@ -0,0 +1,149 @@
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()
}
}
}
@@ -13,6 +13,7 @@ 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")
@@ -21,6 +22,13 @@ 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
+7 -6
View File
@@ -16,16 +16,17 @@ fun Project.configurePublishing() {
}
repositories {
val mavenUrl = "https://repo.codemc.io/repository/maven-releases/"
val mavenUrl = "https://maven.solo-studios.ca/releases/"
//val mavenSnapshotUrl = "https://repo.codemc.io/repository/maven-snapshots/"
maven(mavenUrl) {
val mavenUsername: String? by project
val mavenPassword: String? by project
if (mavenUsername != null && mavenPassword != null) {
val SoloStudiosReleasesUsername: String? by project
val SoloStudiosReleasesPassword: String? by project
if (SoloStudiosReleasesUsername != null && SoloStudiosReleasesPassword != null) {
credentials {
username = mavenUsername
password = mavenPassword
username = SoloStudiosReleasesUsername
password = SoloStudiosReleasesPassword
}
}
}
+6 -6
View File
@@ -10,7 +10,7 @@ object Versions {
const val tectonic = "4.3.1"
const val paralithic = "2.0.1"
const val strata = "1.3.2"
const val seismic = "2.1.1"
const val seismic = "2.5.7"
const val cloud = "2.0.0"
@@ -47,7 +47,7 @@ object Versions {
const val minecraft = "1.21.10"
const val yarn = "$minecraft+build.1"
const val fabricLoader = "0.17.2"
const val fabricLoader = "0.18.2"
const val architecuryLoom = "1.11.451"
const val architecturyPlugin = "3.4.162"
@@ -86,13 +86,13 @@ object Versions {
}
object Allay {
const val api = "0.12.0"
const val api = "0.13.0"
const val gson = "2.13.2"
const val mappings = "8002ed6"
const val mappingsGenerator = "fd83f41"
const val mappings = "15398c1"
const val mappingsGenerator = "8fa6058"
const val mcmeta = "b758592"
const val mcmeta = "e85a17c"
}
object Minestom {
@@ -46,6 +46,6 @@ class BaseBiomeColumn implements Column<Biome> {
@Override
public Biome get(int y) {
return biomeProvider.extrude(base, x, y, z, seed);
return biomeProvider.pipeline.extrude(base, x, y, z, seed);
}
}
@@ -6,37 +6,33 @@ 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 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.extrusions = extrusions.toArray(new Extrusion[0]);
this.pipeline = ExtrusionPipelineFactory.create(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 extrude(delegated, x, y, z, seed);
}
public Biome extrude(Biome original, int x, int y, int z, long seed) {
for(int i = 0; i < extrusions.length; i++) {
original = extrusions[i].extrude(original, x, y, z, seed);
}
return original;
return pipeline.extrude(delegated, x, y, z, seed);
}
@Override
@@ -64,4 +60,4 @@ public class BiomeExtrusionProvider implements BiomeProvider {
public BiomeProvider getDelegate() {
return delegate;
}
}
}
@@ -10,6 +10,7 @@ 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.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,25 +20,41 @@ import com.dfsek.terra.api.world.biome.Biome;
*/
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;
public ReplaceExtrusion(Sampler 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) {
if(hasTag.test(original)) {
return range.ifInRange(y, () -> biomes.get(sampler, x, y, z, seed).get(original), original);
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(passes) {
if(range.isInRange(y)) {
return biomes.get(sampler, x, y, z, seed).get(original);
}
}
return original;
}
@@ -0,0 +1,8 @@
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);
}
@@ -0,0 +1,158 @@
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);
}
}
}
@@ -73,12 +73,13 @@ 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;
biomes[(xIndex * size) + zIndex] = stage.apply(
new ViewPoint(this, gridInterval, gridX, gridZ, xIndex, zIndex, lookupArray, size));
viewPoint.set(gridX, gridZ, xIndex, zIndex);
biomes[(xIndex * size) + zIndex] = stage.apply(viewPoint);
}
}
}
@@ -157,25 +158,32 @@ public class BiomeChunkImpl implements BiomeChunk {
*/
public static class ViewPoint {
private final BiomeChunkImpl chunk;
private final PipelineBiome biome;
private PipelineBiome biome;
private final int gridInterval;
private final int gridX;
private final int gridZ;
private final int xIndex;
private final int zIndex;
private int gridX;
private int gridZ;
private int xIndex;
private int zIndex;
private final PipelineBiome[] lookupArray;
private final int size;
private ViewPoint(BiomeChunkImpl chunk, int gridInterval, int gridX, int gridZ, int xIndex, int zIndex,
private ViewPoint(BiomeChunkImpl chunk, int gridInterval,
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];
}
@@ -0,0 +1,5 @@
version = version("1.0.0")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
}
@@ -0,0 +1,131 @@
package com.dfsek.terra.addons.commands.locate;
import com.dfsek.seismic.type.vector.Vector2Int;
import com.dfsek.seismic.type.vector.Vector3Int;
import com.dfsek.terra.api.util.generic.either.Either;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class BiomeLocator {
/**
* Locates the nearest biome matching the given predicate using a parallelized square spiral search.
*
* @param provider The BiomeProvider to search in.
* @param properties The world properties (needed for seed and height bounds).
* @param originX Starting X coordinate.
* @param originZ Starting Z coordinate.
* @param radius The maximum radius (in blocks) to search.
* @param step The search step/increment. Higher values are faster but less accurate.
* @param filter The condition to match the biome.
* @param search3D If true, searches the entire vertical column at each step. If false, only checks originY.
* @return An Optional containing the location of the found biome, or empty if not found.
*/
public static Optional<Either<Vector3Int, Vector2Int>> search(
@NotNull BiomeProvider provider,
@NotNull WorldProperties properties,
int originX,
int originZ,
int radius,
int step,
@NotNull Predicate<Biome> filter,
boolean search3D
) {
long seed = properties.getSeed();
int minHeight = properties.getMinHeight();
int maxHeight = properties.getMaxHeight();
// 1. Check the exact center first
Optional<Either<Vector3Int, Vector2Int>> centerResult = check(provider, seed, originX, originZ, step, filter, search3D, minHeight, maxHeight);
if (centerResult.isPresent()) {
return centerResult;
}
// 2. Begin Parallel Square Spiral Search
// We iterate rings sequentially to guarantee finding the *nearest* result.
// However, we process all points within a specific ring in parallel.
for (int r = step; r <= radius; r += step) {
final int currentRadius = r;
final int minX = -currentRadius;
final int maxX = currentRadius;
final int minZ = -currentRadius;
final int maxZ = currentRadius;
Stream<int[]> northSide = IntStream.iterate(minX, n -> n < maxX, n -> n + step)
.mapToObj(x -> new int[]{x, minZ}); // Fixed Z (min), varying X
Stream<int[]> eastSide = IntStream.iterate(minZ, n -> n < maxZ, n -> n + step)
.mapToObj(z -> new int[]{maxX, z}); // Fixed X (max), varying Z
Stream<int[]> southSide = IntStream.iterate(maxX, n -> n > minX, n -> n - step)
.mapToObj(x -> new int[]{x, maxZ}); // Fixed Z (max), varying X
Stream<int[]> westSide = IntStream.iterate(maxZ, n -> n > minZ, n -> n - step)
.mapToObj(z -> new int[]{minX, z}); // Fixed X (min), varying Z
Optional<Either<Vector3Int, Vector2Int>> ringResult = Stream.of(northSide, eastSide, southSide, westSide)
.flatMap(Function.identity())
.parallel()
.map(coords -> check(
provider,
seed,
originX + coords[0],
originZ + coords[1],
step,
filter,
search3D,
minHeight,
maxHeight
))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst(); // findFirst() respects encounter order (North -> East -> South -> West)
if (ringResult.isPresent()) {
return ringResult;
}
}
return Optional.empty();
}
/**
* Helper to check a specific coordinate column or point.
* This logic is executed inside the worker threads.
*/
private static Optional<Either<Vector3Int, Vector2Int>> check(
BiomeProvider provider,
long seed,
int x,
int z,
int step,
Predicate<Biome> filter,
boolean search3D,
int minHeight,
int maxHeight
) {
if (search3D) {
// Iterate from bottom to top of the world using the step
for (int y = minHeight; y < maxHeight; y += step) {
if (filter.test(provider.getBiome(x, y, z, seed))) {
return Optional.of(Either.left(Vector3Int.of(x, y, z)));
}
}
return Optional.empty();
} else {
// 2D Mode: Check only the base biome
// We use a flatMap approach here to be safe with Optionals inside the stream
return provider.getBaseBiome(x, z, seed)
.filter(filter)
.map(b -> Either.right(Vector2Int.of(x, z)));
}
}
}
@@ -0,0 +1,142 @@
package com.dfsek.terra.addons.commands.locate;
import com.dfsek.seismic.type.vector.Vector2Int;
import com.dfsek.seismic.type.vector.Vector3Int;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.component.DefaultValue;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.description.Description;
import org.incendo.cloud.parser.standard.IntegerParser;
import java.util.Optional;
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.command.CommandSender;
import com.dfsek.terra.api.command.arguments.RegistryArgument;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.event.events.platform.CommandRegistrationEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.util.generic.either.Either;
import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.World;
import com.dfsek.terra.api.world.biome.Biome;
public class LocateCommandAddon implements AddonInitializer {
@Inject
private Platform platform;
@Inject
private BaseAddon addon;
private static Registry<Biome> getBiomeRegistry(CommandContext<CommandSender> sender) {
return sender.sender().getEntity().orElseThrow().world().getPack().getRegistry(Biome.class);
}
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, CommandRegistrationEvent.class)
.then(event -> {
CommandManager<CommandSender> manager = event.getCommandManager();
manager.command(
manager.commandBuilder("search", Description.of("Locate things in the world"))
.literal("biome")
// Argument 1: The Biome to search for
.argument(RegistryArgument.builder("biome",
LocateCommandAddon::getBiomeRegistry,
TypeKey.of(Biome.class)))
// Argument 2: Radius (Optional, default 5000)
.optional("radius", IntegerParser.integerParser(100), DefaultValue.constant(5000))
// Argument 3: Step/Resolution (Optional, default 16)
.optional("step", IntegerParser.integerParser(1), DefaultValue.constant(16))
// Flag: Toggle 3D search (e.g., --3d or -3)
.flag(manager.flagBuilder("3d").withAliases("3").build())
// Flag: Auto resolution mode (e.g., --auto or -a)
.flag(manager.flagBuilder("auto").withAliases("a").build())
.handler(context -> {
// 1. Gather Context & Arguments
Biome targetBiome = context.get("biome");
Entity sender = context.sender().getEntity().orElseThrow(
() -> new Error("Only entities can run this command."));
World world = sender.world();
// Fetch properties needed for the locator
int radius = context.get("radius");
boolean search3D = context.flags().hasFlag("3d");
boolean autoMode = context.flags().hasFlag("auto");
// 2. Determine Initial Step
// If Auto: Start at radius / 2 (very coarse check).
// If Manual: Use provided step.
int stepArg = context.get("step");
int currentStep = autoMode ? Integer.highestOneBit(radius - 1) : stepArg;
// Notify player
String modeMsg = autoMode ? " (Auto Mode)" : " (Step: " + currentStep + ")";
context.sender().sendMessage(
"Searching for " + targetBiome.getID() + " within " + radius + " blocks" + modeMsg + "...");
Optional<Either<Vector3Int, Vector2Int>> result;
// 3. Execute Search Loop
while(true) {
result = BiomeLocator.search(
world.getBiomeProvider(),
world,
sender.position().getFloorX(),
sender.position().getFloorZ(),
radius,
currentStep,
found -> found.equals(targetBiome), // Match specific biome instance
search3D
);
// Exit Conditions:
// 1. Found a result
if(result.isPresent()) {
break;
}
// 2. Not in auto mode (only run once)
if(!autoMode) {
break;
}
// 3. We just ran a search at step arg and failed (lowest resolution)
if(currentStep <= stepArg) {
break;
}
// Reduce step for next iteration (Adaptive Search)
currentStep /= 2;
context.sender().sendMessage("No result found, refining search (Step: " + currentStep + ")...");
}
// 4. Handle Result
if(result.isPresent()) {
Either<Vector3Int, Vector2Int> location = result.get();
String coords;
if(location.hasLeft()) { // 3D Result
Vector3Int vec = location.getLeft().get();
coords = String.format("%d, %d, %d", vec.getX(), vec.getY(), vec.getZ());
} else { // 2D Result
Vector2Int vec = location.getRight().get();
coords = String.format("%d, ~, %d", vec.getX(), vec.getZ());
}
context.sender().sendMessage("Found " + targetBiome.getID() + " at [" + coords + "]");
} else {
context.sender().sendMessage("Could not find " + targetBiome.getID() + " within " + radius + " blocks.");
}
})
.permission("terra.locate.biome")
);
});
}
}
@@ -0,0 +1,12 @@
schema-version: 1
contributors:
- Terra contributors
id: command-locate
version: @VERSION@
entrypoints:
- "com.dfsek.terra.addons.commands.locate.LocateCommandAddon"
website:
issues: https://github.com/PolyhedralDev/Terra/issues
source: https://github.com/PolyhedralDev/Terra
docs: https://terra.polydev.org
license: MIT License
@@ -8,6 +8,7 @@
package com.dfsek.terra.addons.biome;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.dfsek.terra.api.properties.Context;
import com.dfsek.terra.api.world.biome.Biome;
@@ -25,6 +26,7 @@ public class UserDefinedBiome implements Biome {
private final Set<String> tags;
private final Context context = new Context();
private final int intID;
public UserDefinedBiome(PlatformBiome vanilla, BiomeTemplate config) {
this.vanilla = vanilla;
@@ -32,6 +34,7 @@ public class UserDefinedBiome implements Biome {
this.config = config;
this.color = config.getColor();
this.tags = config.getTags();
this.intID = INT_ID_COUNTER.getAndIncrement();
tags.add("BIOME:" + id);
tags.add("ALL");
}
@@ -61,6 +64,11 @@ public class UserDefinedBiome implements Biome {
return tags;
}
@Override
public int getIntID() {
return intID;
}
@Override
public String getID() {
return id;
@@ -2,18 +2,11 @@ package com.dfsek.terra.addons.feature.distributor.distributors;
import com.dfsek.seismic.algorithms.hashing.HashingFunctions;
import com.dfsek.seismic.math.integer.IntegerFunctions;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import com.dfsek.terra.api.structure.feature.Distributor;
public class PaddedGridDistributor implements Distributor {
private final int width;
private final int cellWidth;
private final int salt;
public PaddedGridDistributor(int width, int padding, int salt) {
@@ -27,12 +20,26 @@ public class PaddedGridDistributor implements Distributor {
int cellX = Math.floorDiv(x, cellWidth);
int cellZ = Math.floorDiv(z, cellWidth);
RandomGenerator random = RandomGeneratorFactory.<RandomGenerator.SplittableGenerator>of("Xoroshiro128PlusPlus").create(
(HashingFunctions.murmur64(IntegerFunctions.squash(cellX, cellZ)) ^ seed) + salt);
int localX = x - (cellX * cellWidth);
int localZ = z - (cellZ * cellWidth);
int pointX = random.nextInt(width) + cellX * cellWidth;
int pointZ = random.nextInt(width) + cellZ * cellWidth;
if (localX >= width || localZ >= width) {
return false;
}
return x == pointX && z == pointZ;
long hash = HashingFunctions.murmur64(IntegerFunctions.squash(cellX, cellZ)) ^ seed;
hash += salt;
hash = HashingFunctions.splitMix64(hash);
int targetX = (int) ((hash & 0x7FFFFFFFFFFFFFFFL) % width);
if (localX != targetX) {
return false;
}
hash = HashingFunctions.splitMix64(hash);
int targetZ = (int) ((hash & 0x7FFFFFFFFFFFFFFFL) % width);
return localZ == targetZ;
}
}
@@ -7,11 +7,11 @@
package com.dfsek.terra.addons.terrascript.parser.lang.functions;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import java.util.List;
public interface FunctionBuilder<T extends Function<?>> {
T build(List<Returnable<?>> argumentList, Position position);
@@ -0,0 +1,67 @@
package com.dfsek.terra.api.util.collection;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import com.dfsek.seismic.util.UnsafeUtils;
public class TriStateIntCache {
public static final long STATE_UNSET = 0L;
public static final long STATE_FALSE = 1L;
public static final long STATE_TRUE = 2L;
private static final long BIT_MASK = 3L;
private final long[] data;
private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(long[].class);
private static int getOptimalMaxKeys(int requestedKeys) {
// 192 keys fill the first cache line exactly (along with the 16-byte header)
if (requestedKeys <= 192) {
return 192;
}
// For every additional line, we fit 256 keys (64 bytes * 4 keys/byte)
// We calculate the overflow beyond 192, round up to the nearest 256, and add it back.
int overflow = requestedKeys - 192;
int chunks = (overflow + 255) >>> 8; // Fast ceil division by 256
return 192 + (chunks << 8); // chunks * 256
}
public TriStateIntCache(int maxKeySize) {
this.data = new long[(getOptimalMaxKeys(maxKeySize) + 31) >>> 5];
}
/**
* Checks the cache state without any allocation.
*
* @return STATE_UNSET (0), STATE_FALSE (1), or STATE_TRUE (2)
*/
public long get(int key) {
long offset = UnsafeUtils.LONG_ARRAY_BASE + ((long)(key >>> 5) << UnsafeUtils.LONG_ARRAY_SHIFT);
long currentWord = UnsafeUtils.UNSAFE.getLong(data, offset);
return (currentWord >>> ((key << 1) & 63)) & BIT_MASK;
}
/**
* Sets the value safely. Handles race conditions internally.
*/
public void set(int key, boolean value) {
int index = key >>> 5;
int shift = (key << 1) & 63;
long targetWord = (value ? STATE_TRUE : STATE_FALSE) << shift;
long current;
do {
current = (long) ARRAY_HANDLE.getVolatile(data, index);
if (((current >>> shift) & BIT_MASK) != STATE_UNSET) {
return;
}
} while (!ARRAY_HANDLE.compareAndSet(data, index, current, current | targetWord));
}
}
@@ -9,6 +9,7 @@ package com.dfsek.terra.api.world.biome;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.dfsek.terra.api.properties.PropertyHolder;
import com.dfsek.terra.api.registry.key.StringIdentifiable;
@@ -18,6 +19,7 @@ import com.dfsek.terra.api.registry.key.StringIdentifiable;
* Represents a Terra biome
*/
public interface Biome extends PropertyHolder, StringIdentifiable {
AtomicInteger INT_ID_COUNTER = new AtomicInteger(0);
/**
* Gets the platform biome this custom biome delegates to.
@@ -39,4 +41,12 @@ public interface Biome extends PropertyHolder, StringIdentifiable {
* @return A {@link Set} of String tags this biome holds.
*/
Set<String> getTags();
/**
* Get the numeric ID of this biome, generated at registration time
*
* @return The numeric ID.
*/
int getIntID();
}
@@ -52,7 +52,8 @@ import com.dfsek.terra.api.util.reflection.TypeKey;
public class OpenRegistryImpl<T> implements OpenRegistry<T> {
private static final Entry<?> NULL = new Entry<>(null);
private final Map<RegistryKey, Entry<T>> objects;
private final ListMultimap<String, Pair<RegistryKey, Entry<T>>> objectIDs = Multimaps.newListMultimap(new ConcurrentHashMap<>(), ArrayList::new);
private final ListMultimap<String, Pair<RegistryKey, Entry<T>>> objectIDs = Multimaps.newListMultimap(new ConcurrentHashMap<>(),
ArrayList::new);
private final TypeKey<T> typeKey;
public OpenRegistryImpl(TypeKey<T> typeKey) {
@@ -21,7 +21,6 @@ import java.io.IOException;
import java.io.Serial;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Stream;
+2 -2
View File
@@ -2,8 +2,8 @@
## Resource files
Current mapping version: je 1.21.7 to be 1.21.93
Current mapping version: je 1.21.9 to be 1.21.111
- `mapping/biomes.json` and `mapping/items.json` are obtained from [GeyserMC/mappings](https://github.com/GeyserMC/mappings).
- `mapping/blocks.json` is obtained from [GeyserMC/mappings-generator](https://github.com/GeyserMC/mappings-generator) (path: `https://github.com/GeyserMC/mappings-generator/blob/master/new_generator_blocks.json`).
- `mapping/blocks.json` is obtained from [GeyserMC/mappings-generator](https://github.com/GeyserMC/mappings-generator) (path: `https://github.com/GeyserMC/mappings-generator/blob/master/generator_blocks.json`).
- `je_blocks.json` is obtained from [misode/mcmeta](https://github.com/misode/mcmeta) (path: `https://github.com/misode/mcmeta/blob/<version>-summary/blocks/data.json`).
+2 -2
View File
@@ -28,7 +28,7 @@ dependencies {
geyserMappings("GeyserMC.mappings", "items", Versions.Allay.mappings, ext = "json")
geyserMappings("GeyserMC.mappings", "biomes", Versions.Allay.mappings, ext = "json")
geyserMappings("GeyserMC.mappings-generator", "new_generator_blocks", Versions.Allay.mappingsGenerator, ext = "json")
geyserMappings("GeyserMC.mappings-generator", "generator_blocks", Versions.Allay.mappingsGenerator, ext = "json")
mcmeta("misode.mcmeta", "blocks/data", Versions.Allay.mcmeta, ext = "json")
}
@@ -38,7 +38,7 @@ tasks.processResources {
into("mapping")
// rather jank, but whatever
rename("(?:new_generator_)?([^-]+)-(.*)\\.json", "$1.json")
rename("(?:generator_)?([^-]+)-(.*)\\.json", "$1.json")
}
from(mcmeta) {
rename("data-(.*)\\.json", "je_blocks.json")
@@ -45,7 +45,7 @@ public class AllayPlatform extends AbstractPlatform {
getConfigRegistry().get(wrapper.getConfigPack().getRegistryKey()).ifPresent(pack -> {
wrapper.setConfigPack(pack);
var dimension = wrapper.getAllayWorldGenerator().getDimension();
TerraAllayPlugin.INSTANCE.getPluginLogger().info(
TerraAllayPlugin.instance.getPluginLogger().info(
"Replaced pack in chunk generator for world {}",
dimension.getWorld().getWorldData().getDisplayName() + ":" + dimension.getDimensionInfo().dimensionId()
);
@@ -72,7 +72,7 @@ public class AllayPlatform extends AbstractPlatform {
@Override
public @NotNull File getDataFolder() {
return TerraAllayPlugin.INSTANCE.getPluginContainer().dataFolder().toFile();
return TerraAllayPlugin.instance.getPluginContainer().dataFolder().toFile();
}
@Override
@@ -10,12 +10,25 @@ import java.util.TreeMap;
* @author daoge_cmd
*/
public class JeBlockState {
protected final String identifier;
protected final TreeMap<String, String> properties;
protected int hash = Integer.MAX_VALUE;
private JeBlockState(String data) {
String[] strings = data.replace("[", ",").replace("]", ",").replace(" ", "").split(",");
// TODO: support block state with nbt (identifier[properties]{nbt}), for now we just ignore it
int braceIndex = data.indexOf('{');
if(braceIndex != -1) {
data = data.substring(0, braceIndex);
}
String[] strings = data
.replace("[", ",")
.replace("]", ",")
.replace(" ", "")
.split(",");
this.identifier = strings[0];
this.properties = new TreeMap<>();
if(strings.length > 1) {
@@ -57,7 +57,7 @@ public final class Mapping {
public static BlockState blockStateJeToBe(JeBlockState jeBlockState) {
BlockState result = JE_BLOCK_STATE_HASH_TO_BE.get(jeBlockState.getHash());
if(result == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().warn("Failed to find be block state for {}", jeBlockState);
TerraAllayPlugin.instance.getPluginLogger().warn("Failed to find be block state for {}", jeBlockState);
return BE_AIR_STATE;
}
return result;
@@ -81,10 +81,19 @@ public final class Mapping {
return JE_BIOME_ID_TO_BE.get(jeBiomeId);
}
public static String dimensionIdBeToJe(String beDimensionId) {
return switch(beDimensionId) {
case "overworld" -> "minecraft:overworld";
case "nether" -> "minecraft:the_nether";
case "the_end" -> "minecraft:the_end";
default -> beDimensionId;
};
}
public static Map<String, String> getJeBlockDefaultProperties(String jeBlockIdentifier) {
var defaultProperties = JE_BLOCK_DEFAULT_PROPERTIES.get(jeBlockIdentifier);
if(defaultProperties == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().warn("Failed to find default properties for {}", jeBlockIdentifier);
TerraAllayPlugin.instance.getPluginLogger().warn("Failed to find default properties for {}", jeBlockIdentifier);
return Map.of();
}
@@ -98,7 +107,7 @@ public final class Mapping {
private static boolean initBiomeMapping() {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/biomes.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("biomes mapping not found");
TerraAllayPlugin.instance.getPluginLogger().error("biomes mapping not found");
return false;
}
@@ -106,7 +115,7 @@ public final class Mapping {
});
mappings.forEach((javaId, mapping) -> JE_BIOME_ID_TO_BE.put(javaId, mapping.bedrockId()));
} catch(IOException e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Failed to load biomes mapping", e);
TerraAllayPlugin.instance.getPluginLogger().error("Failed to load biomes mapping", e);
return false;
}
return true;
@@ -115,7 +124,7 @@ public final class Mapping {
private static boolean initItemMapping() {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/items.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("items mapping not found");
TerraAllayPlugin.instance.getPluginLogger().error("items mapping not found");
return false;
}
@@ -129,7 +138,7 @@ public final class Mapping {
JE_ITEM_ID_TO_BE.put(javaId, itemType);
});
} catch(IOException e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Failed to load items mapping", e);
TerraAllayPlugin.instance.getPluginLogger().error("Failed to load items mapping", e);
return false;
}
return true;
@@ -138,7 +147,7 @@ public final class Mapping {
private static boolean initBlockStateMapping() {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/blocks.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("blocks mapping not found");
TerraAllayPlugin.instance.getPluginLogger().error("blocks mapping not found");
return false;
}
@@ -152,7 +161,7 @@ public final class Mapping {
JE_BLOCK_STATE_HASH_TO_BE.put(jeState.getHash(), beState);
});
} catch(IOException e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Failed to load blocks mapping", e);
TerraAllayPlugin.instance.getPluginLogger().error("Failed to load blocks mapping", e);
return false;
}
return true;
@@ -162,7 +171,7 @@ public final class Mapping {
private static boolean initJeBlockDefaultProperties() {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("je_blocks.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("je_block_default_states.json not found");
TerraAllayPlugin.instance.getPluginLogger().error("je_block_default_states.json not found");
return false;
}
@@ -15,38 +15,38 @@ import com.dfsek.terra.api.event.events.platform.PlatformInitializationEvent;
*/
public class TerraAllayPlugin extends Plugin {
public static TerraAllayPlugin INSTANCE;
public static AllayPlatform PLATFORM;
public static TerraAllayPlugin instance;
public static AllayPlatform platform;
{
INSTANCE = this;
TerraAllayPlugin.instance = this;
}
@Override
public void onLoad() {
pluginLogger.info("Starting Terra...");
this.pluginLogger.info("Starting Terra...");
pluginLogger.info("Loading mapping...");
this.pluginLogger.info("Loading mapping...");
Mapping.init();
pluginLogger.info("Initializing allay platform...");
PLATFORM = new AllayPlatform();
PLATFORM.getEventManager().callEvent(new PlatformInitializationEvent());
this.pluginLogger.info("Initializing allay platform...");
TerraAllayPlugin.platform = new AllayPlatform();
TerraAllayPlugin.platform.getEventManager().callEvent(new PlatformInitializationEvent());
// TODO: adapt command manager
pluginLogger.info("Registering generator...");
this.pluginLogger.info("Registering generator...");
Registries.WORLD_GENERATOR_FACTORIES.register("TERRA", preset -> {
try {
AllayGeneratorWrapper wrapper = new AllayGeneratorWrapper(preset);
AllayPlatform.GENERATOR_WRAPPERS.add(wrapper);
return wrapper.getAllayWorldGenerator();
} catch(IllegalArgumentException e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Fail to create world generator with preset: {}", preset, e);
TerraAllayPlugin.instance.getPluginLogger().error("Fail to create world generator with preset: {}", preset, e);
return Registries.WORLD_GENERATOR_FACTORIES.get("FLAT").apply("");
}
});
pluginLogger.info("Terra started");
this.pluginLogger.info("Terra started");
}
@Override
@@ -61,10 +61,10 @@ public class TerraAllayPlugin extends Plugin {
@Override
public void reload() {
if(PLATFORM.reload()) {
pluginLogger.info("Terra reloaded successfully.");
if(TerraAllayPlugin.platform.reload()) {
this.pluginLogger.info("Terra reloaded successfully.");
} else {
pluginLogger.error("Terra failed to reload.");
this.pluginLogger.error("Terra failed to reload.");
}
}
@@ -0,0 +1,51 @@
package com.dfsek.terra.allay.delegate;
import com.dfsek.seismic.type.vector.Vector3;
import org.allaymc.api.blockentity.BlockEntity;
import com.dfsek.terra.allay.Mapping;
import com.dfsek.terra.api.block.state.BlockState;
/**
* @author daoge_cmd
*/
public record AllayBlockEntity(BlockEntity allayBlockEntity) implements com.dfsek.terra.api.block.entity.BlockEntity {
@Override
public boolean update(boolean applyPhysics) {
return false;
}
@Override
public Vector3 getPosition() {
var pos = this.allayBlockEntity.getPosition();
return Vector3.of(pos.x(), pos.y(), pos.z());
}
@Override
public int getX() {
return this.allayBlockEntity.getPosition().x();
}
@Override
public int getY() {
return this.allayBlockEntity.getPosition().y();
}
@Override
public int getZ() {
return this.allayBlockEntity.getPosition().z();
}
@Override
public BlockState getBlockState() {
var allayBlockState = this.allayBlockEntity.getBlockState();
return new AllayBlockState(allayBlockState, Mapping.blockStateBeToJe(this.allayBlockEntity.getBlockState()));
}
@Override
public Object getHandle() {
return this.allayBlockEntity;
}
}
@@ -21,6 +21,13 @@ public record AllayChunk(ServerWorld world, Chunk allayChunk) implements com.dfs
@Override
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
var dimensionInfo = allayChunk.getDimensionInfo();
if(x < 0 || x > 15 ||
z < 0 || z > 15 ||
y < dimensionInfo.minHeight() || y > dimensionInfo.maxHeight()) {
return;
}
AllayBlockState allayBlockState = (AllayBlockState) data;
allayChunk.setBlockState(x, y, z, allayBlockState.allayBlockState());
if(allayBlockState.containsWater() || allayChunk.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER)) {
@@ -7,7 +7,7 @@ import com.dfsek.terra.api.world.ServerWorld;
/**
* NOTICE: Entity is not supported currently, and this is a fake implementation.
* TODO: Entity is not supported currently, and this is a fake implementation.
*
* @author daoge_cmd
*/
@@ -1,7 +1,7 @@
package com.dfsek.terra.allay.delegate;
import org.allaymc.api.block.property.type.BlockPropertyTypes;
import org.allaymc.api.block.data.BlockTags;
import org.allaymc.api.block.property.type.BlockPropertyTypes;
import org.allaymc.api.block.type.BlockTypes;
import org.allaymc.api.world.chunk.UnsafeChunk;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +26,13 @@ public record AllayProtoChunk(UnsafeChunk allayChunk) implements ProtoChunk {
@Override
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
var dimensionInfo = allayChunk.getDimensionInfo();
if(x < 0 || x > 15 ||
z < 0 || z > 15 ||
y < dimensionInfo.minHeight() || y > dimensionInfo.maxHeight()) {
return;
}
AllayBlockState allayBlockState = (AllayBlockState) blockState;
allayChunk.setBlockState(x, y, z, allayBlockState.allayBlockState());
if(allayBlockState.containsWater() || allayChunk.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER)) {
@@ -1,8 +1,8 @@
package com.dfsek.terra.allay.delegate;
import com.dfsek.seismic.type.vector.Vector3;
import org.allaymc.api.block.property.type.BlockPropertyTypes;
import org.allaymc.api.block.data.BlockTags;
import org.allaymc.api.block.property.type.BlockPropertyTypes;
import org.allaymc.api.block.type.BlockTypes;
import org.allaymc.api.world.generator.context.OtherChunkAccessibleContext;
@@ -26,6 +26,23 @@ public record AllayProtoWorld(AllayServerWorld allayServerWorld, OtherChunkAcces
private static final org.allaymc.api.block.type.BlockState WATER = BlockTypes.WATER.ofState(
BlockPropertyTypes.LIQUID_DEPTH.createValue(0));
// TODO: use method in OtherChunkAccessibleContext directly after bumped allay-api version to 0.14.0
private static org.allaymc.api.blockentity.BlockEntity getBlockEntity(OtherChunkAccessibleContext context, int x, int y, int z) {
var currentChunk = context.getCurrentChunk();
var currentChunkX = currentChunk.getX();
var currentChunkZ = currentChunk.getZ();
var dimInfo = currentChunk.getDimensionInfo();
if(x >= currentChunkX * 16 && x < currentChunkX * 16 + 16 &&
z >= currentChunkZ * 16 && z < currentChunkZ * 16 + 16 &&
y >= dimInfo.minHeight() && y <= dimInfo.maxHeight()) {
return currentChunk.getBlockEntity(x & 15, y, z & 15);
} else {
var chunk = context.getChunkSource().getChunk(x >> 4, z >> 4);
return chunk == null ? null : chunk.getBlockEntity(x & 15, y, z & 15);
}
}
@Override
public int centerChunkX() {
return context.getCurrentChunk().getX();
@@ -43,6 +60,11 @@ public record AllayProtoWorld(AllayServerWorld allayServerWorld, OtherChunkAcces
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
var dimensionInfo = allayServerWorld.allayDimension().getDimensionInfo();
if(y < dimensionInfo.minHeight() || y > dimensionInfo.maxHeight()) {
return;
}
AllayBlockState allayBlockState = (AllayBlockState) data;
context.setBlockState(x, y, z, allayBlockState.allayBlockState());
if(allayBlockState.containsWater() || context.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER)) {
@@ -63,7 +85,7 @@ public record AllayProtoWorld(AllayServerWorld allayServerWorld, OtherChunkAcces
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return null;
return new AllayBlockEntity(getBlockEntity(context, x, y, z));
}
@Override
@@ -27,6 +27,11 @@ public record AllayServerWorld(AllayGeneratorWrapper allayGeneratorWrapper, Dime
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
var dimensionInfo = allayDimension.getDimensionInfo();
if(y < dimensionInfo.minHeight() || y > dimensionInfo.maxHeight()) {
return;
}
// In dimension#setBlockState() method, Water will be moved to layer 1 if it is placed at layer 0
allayDimension.setBlockState(x, y, z, ((AllayBlockState) data).allayBlockState());
}
@@ -44,7 +49,7 @@ public record AllayServerWorld(AllayGeneratorWrapper allayGeneratorWrapper, Dime
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return null;
return new AllayBlockEntity(allayDimension.getBlockEntity(x, y, z));
}
@Override
@@ -0,0 +1,42 @@
package com.dfsek.terra.allay.delegate;
import org.allaymc.api.world.data.DimensionInfo;
import com.dfsek.terra.api.world.info.WorldProperties;
/**
* @author daoge_cmd
*/
public class AllayWorldProperties implements WorldProperties {
private final Object fakeHandle;
private final long seed;
private final DimensionInfo dimensionInfo;
public AllayWorldProperties(long seed, DimensionInfo dimensionInfo) {
this.fakeHandle = new Object();
this.seed = seed;
this.dimensionInfo = dimensionInfo;
}
@Override
public long getSeed() {
return this.seed;
}
@Override
public int getMaxHeight() {
return dimensionInfo.maxHeight();
}
@Override
public int getMinHeight() {
return dimensionInfo.minHeight();
}
@Override
public Object getHandle() {
return fakeHandle;
}
}
@@ -1,22 +1,22 @@
package com.dfsek.terra.allay.generator;
import com.google.common.base.Preconditions;
import org.allaymc.api.utils.AllayStringUtils;
import org.allaymc.api.world.biome.BiomeType;
import org.allaymc.api.world.chunk.UnsafeChunk;
import org.allaymc.api.world.data.DimensionInfo;
import org.allaymc.api.world.generator.WorldGenerator;
import org.allaymc.api.world.generator.context.NoiseContext;
import org.allaymc.api.world.generator.context.PopulateContext;
import org.allaymc.api.world.generator.function.Noiser;
import org.allaymc.api.world.generator.function.Populator;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import com.dfsek.terra.allay.Mapping;
import com.dfsek.terra.allay.TerraAllayPlugin;
import com.dfsek.terra.allay.delegate.AllayProtoChunk;
import com.dfsek.terra.allay.delegate.AllayProtoWorld;
import com.dfsek.terra.allay.delegate.AllayServerWorld;
import com.dfsek.terra.allay.delegate.AllayWorldProperties;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
@@ -30,27 +30,23 @@ import com.dfsek.terra.api.world.info.WorldProperties;
*/
public class AllayGeneratorWrapper implements GeneratorWrapper {
protected static final String OPTION_META_PACK_NAME = "meta-pack";
protected static final String OPTION_PACK_NAME = "pack";
protected static final String OPTION_SEED = "seed";
protected final BiomeProvider biomeProvider;
protected final long seed;
protected final WorldGenerator allayWorldGenerator;
protected ChunkGenerator chunkGenerator;
protected ConfigPack configPack;
protected ChunkGenerator chunkGenerator;
protected BiomeProvider biomeProvider;
protected WorldProperties worldProperties;
protected AllayServerWorld allayServerWorld;
public AllayGeneratorWrapper(String preset) {
Map<String, String> options = AllayStringUtils.parseOptions(preset);
String packName = options.get(OPTION_PACK_NAME);
if(packName == null) {
throw new IllegalArgumentException("Missing config pack name");
}
this.seed = Long.parseLong(options.getOrDefault(OPTION_SEED, "0"));
this.configPack = getConfigPack(packName);
this.chunkGenerator = createGenerator(this.configPack);
this.biomeProvider = this.configPack.getBiomeProvider();
var options = AllayStringUtils.parseOptions(preset);
this.seed = parseSeed(options.get(OPTION_SEED));
this.allayWorldGenerator = WorldGenerator
.builder()
.name("TERRA")
@@ -59,40 +55,52 @@ public class AllayGeneratorWrapper implements GeneratorWrapper {
.populators(new AllayPopulator())
.onDimensionSet(dimension -> {
this.allayServerWorld = new AllayServerWorld(this, dimension);
this.worldProperties = new WorldProperties() {
this.worldProperties = new AllayWorldProperties(this.seed, dimension.getDimensionInfo());
private final Object fakeHandle = new Object();
var metaPackName = options.get(OPTION_META_PACK_NAME);
if(metaPackName != null) {
setConfigPack(getConfigPackByMeta(metaPackName, dimension.getDimensionInfo()));
return;
}
@Override
public long getSeed() {
return seed;
}
var packName = options.get(OPTION_PACK_NAME);
if(packName != null) {
setConfigPack(getConfigPackById(packName));
return;
}
@Override
public int getMaxHeight() {
return dimension.getDimensionInfo().maxHeight();
}
@Override
public int getMinHeight() {
return dimension.getDimensionInfo().minHeight();
}
@Override
public Object getHandle() {
return fakeHandle;
}
};
throw new IllegalArgumentException("Either 'pack' or 'meta-pack' option should be specified in the generator preset!");
})
.build();
}
protected static ConfigPack getConfigPack(String packName) {
Optional<ConfigPack> byId = TerraAllayPlugin.PLATFORM.getConfigRegistry().getByID(packName);
return byId.orElseGet(
() -> TerraAllayPlugin.PLATFORM.getConfigRegistry().getByID(packName.toUpperCase(Locale.ENGLISH))
.orElseThrow(() -> new IllegalArgumentException("Cant find terra config pack named " + packName))
);
protected static long parseSeed(String str) {
if(str == null) {
return 0;
}
try {
return Long.parseLong(str);
} catch(NumberFormatException e) {
// Return the hashcode of the string if it cannot be parsed to a long value directly
return str.hashCode();
}
}
protected static ConfigPack getConfigPackById(String packId) {
return TerraAllayPlugin.platform
.getConfigRegistry()
.getByID(packId)
.orElseThrow(() -> new IllegalArgumentException("Cant find terra config pack named " + packId));
}
protected static ConfigPack getConfigPackByMeta(String metaPackId, DimensionInfo dimensionInfo) {
return TerraAllayPlugin.platform
.getMetaConfigRegistry()
.getByID(metaPackId)
.orElseThrow(() -> new IllegalArgumentException("Cant find terra meta pack named " + metaPackId))
.packs()
.get(Mapping.dimensionIdBeToJe(dimensionInfo.toString()));
}
protected static ChunkGenerator createGenerator(ConfigPack configPack) {
@@ -101,7 +109,7 @@ public class AllayGeneratorWrapper implements GeneratorWrapper {
@Override
public ChunkGenerator getHandle() {
return chunkGenerator;
return this.chunkGenerator;
}
public BiomeProvider getBiomeProvider() {
@@ -113,8 +121,10 @@ public class AllayGeneratorWrapper implements GeneratorWrapper {
}
public void setConfigPack(ConfigPack configPack) {
Preconditions.checkNotNull(configPack, "Config pack cannot be null!");
this.configPack = configPack;
this.chunkGenerator = createGenerator(this.configPack);
this.biomeProvider = this.configPack.getBiomeProvider();
}
public long getSeed() {
@@ -165,7 +175,7 @@ public class AllayGeneratorWrapper implements GeneratorWrapper {
generationStage.populate(tmp);
}
} catch(Exception e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Error while populating chunk", e);
TerraAllayPlugin.instance.getPluginLogger().error("Error while populating chunk", e);
}
return true;
}
@@ -1,6 +1,7 @@
package com.dfsek.terra.allay.handle;
import org.allaymc.api.registry.Registries;
import org.allaymc.api.utils.identifier.Identifier;
import java.util.Set;
import java.util.stream.Collectors;
@@ -12,8 +13,6 @@ import com.dfsek.terra.api.handle.ItemHandle;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import org.allaymc.api.utils.identifier.Identifier;
/**
* @author daoge_cmd
@@ -3,10 +3,10 @@ package com.dfsek.terra.bukkit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.dfsek.terra.bukkit.util.VersionUtil;
import java.util.List;
import com.dfsek.terra.bukkit.util.VersionUtil;
public interface NMSInitializer {
List<String> SUPPORTED_VERSIONS = List.of("v1.21.9", "v1.21.10");
@@ -16,8 +16,9 @@ public interface NMSInitializer {
static PlatformImpl init(TerraBukkitPlugin plugin) {
Logger logger = LoggerFactory.getLogger(NMSInitializer.class);
if (!SUPPORTED_VERSIONS.contains(MINECRAFT_VERSION)) {
logger.error("You are running your server on Minecraft version {} which is not supported by this version of Terra.", MINECRAFT_VERSION);
if(!SUPPORTED_VERSIONS.contains(MINECRAFT_VERSION)) {
logger.error("You are running your server on Minecraft version {} which is not supported by this version of Terra.",
MINECRAFT_VERSION);
String bypassKey = "IKnowThereAreNoNMSBindingsFor" + MINECRAFT_VERSION.replace(".", "_") + "ButIWillProceedAnyway";
if(System.getProperty(bypassKey) == null) {
@@ -93,7 +93,7 @@ public class TerraBukkitPlugin extends JavaPlugin {
BukkitAdapter::adapt,
BukkitAdapter::adapt
))
.executionCoordinator(ExecutionCoordinator.simpleCoordinator())
.executionCoordinator(ExecutionCoordinator.asyncCoordinator())
.buildOnEnable(this);
commandManager.brigadierManager().setNativeNumberSuggestions(false);
@@ -17,12 +17,12 @@
package com.dfsek.terra.bukkit.util;
import com.dfsek.terra.bukkit.TerraBukkitPlugin;
import io.papermc.lib.PaperLib;
import java.util.concurrent.TimeUnit;
import com.dfsek.terra.bukkit.TerraBukkitPlugin;
import static io.papermc.lib.PaperLib.suggestPaper;
@@ -27,6 +27,8 @@ public class TerraMinestomExample {
private TerraMinestomWorld world;
public static void main(String[] args) {
System.setProperty("minestom.registry.unsafe-ops", "true");
TerraMinestomExample example = new TerraMinestomExample();
example.createNewInstance();
example.attachTerra();
@@ -2,12 +2,6 @@ package com.dfsek.terra.minestom;
import com.dfsek.tectonic.api.TypeRegistry;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.terra.minestom.api.BiomeFactory;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomeFactory;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomePool;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.util.RGBLike;
import net.minestom.server.MinecraftServer;
@@ -32,8 +26,11 @@ import com.dfsek.terra.api.handle.ItemHandle;
import com.dfsek.terra.api.handle.WorldHandle;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import com.dfsek.terra.minestom.addon.MinestomAddon;
import com.dfsek.terra.minestom.api.BiomeFactory;
import com.dfsek.terra.minestom.api.TerraMinestomWorldBuilder;
import com.dfsek.terra.minestom.biome.MinestomBiomeLoader;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomeFactory;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomePool;
import com.dfsek.terra.minestom.config.BiomeAdditionsSoundTemplate;
import com.dfsek.terra.minestom.config.BiomeMoodSoundTemplate;
import com.dfsek.terra.minestom.config.BiomeParticleConfigTemplate;
@@ -70,6 +67,10 @@ public final class TerraMinestomPlatform extends AbstractPlatform {
this(new MinestomWorldHandle(), new MinestomItemHandle(), new MinestomBiomeLoader(), new MinestomUserDefinedBiomeFactory());
}
public static Builder builder() {
return new Builder();
}
@Override
public void register(TypeRegistry registry) {
super.register(registry);
@@ -146,16 +147,13 @@ public final class TerraMinestomPlatform extends AbstractPlatform {
return worldBuilder(MinecraftServer.getInstanceManager().createInstanceContainer());
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private final List<BaseAddon> platformAddons = new ArrayList<>();
private @Nullable WorldHandle worldHandle;
private @Nullable ItemHandle itemHandle;
private @Nullable TypeLoader<PlatformBiome> biomeTypeLoader;
private @Nullable BiomeFactory biomeFactory;
private final List<BaseAddon> platformAddons = new ArrayList<>();
public Builder worldHandle(@Nullable WorldHandle worldHandle) {
this.worldHandle = worldHandle;
@@ -1,6 +1,7 @@
package com.dfsek.terra.minestom.api;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
@@ -9,5 +10,22 @@ import net.minestom.server.entity.EntityType;
* Allows adding AI to generated entities using custom entity types
*/
public interface EntityFactory {
/**
* Creates a new entity of the specified type.
*
* @param type the type of the entity to be created
* @return the created entity instance
*/
Entity createEntity(EntityType type);
/**
* Creates a new entity of the specified type with additional data.
*
* @param type the type of the entity to be created
* @param data the additional data for the entity, represented as a CompoundBinaryTag
* @return the created entity instance
*/
default Entity createEntity(EntityType type, CompoundBinaryTag data) {
return createEntity(type);
}
}
@@ -1,8 +1,9 @@
package com.dfsek.terra.minestom.api;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomePool;
import net.minestom.server.instance.Instance;
import net.minestom.server.registry.RegistryKey;
import net.minestom.server.world.DimensionType;
import org.jspecify.annotations.NonNull;
import java.util.Random;
import java.util.function.Function;
@@ -10,14 +11,11 @@ import java.util.function.Function;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.minestom.TerraMinestomPlatform;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomePool;
import com.dfsek.terra.minestom.block.DefaultBlockEntityFactory;
import com.dfsek.terra.minestom.entity.DefaultEntityFactory;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import net.minestom.server.registry.RegistryKey;
import net.minestom.server.world.DimensionType;
import org.jspecify.annotations.NonNull;
public class TerraMinestomWorldBuilder {
private final TerraMinestomPlatform platform;
@@ -4,9 +4,6 @@ import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.tectonic.api.loader.ConfigLoader;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import net.kyori.adventure.key.Key;
import net.minestom.server.registry.RegistryKey;
import org.intellij.lang.annotations.Subst;
@@ -14,6 +11,8 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.AnnotatedType;
import com.dfsek.terra.api.world.biome.PlatformBiome;
public class MinestomBiomeLoader implements TypeLoader<PlatformBiome> {
@Override
@@ -1,7 +1,10 @@
package com.dfsek.terra.minestom.block;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.minestom.server.instance.block.Block;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -10,38 +13,84 @@ import com.dfsek.terra.api.block.BlockType;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.block.state.properties.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MinestomBlockState implements BlockState {
private final Block block;
public record MinestomBlockState(Block block) implements BlockState {
private static final Logger LOGGER = LoggerFactory.getLogger(MinestomBlockState.class);
public static final MinestomBlockState AIR = new MinestomBlockState(Block.AIR);
private static final TagStringIO tagStringIO = TagStringIO.tagStringIO();
public MinestomBlockState(Block block) {
if(block == null) {
this.block = Block.AIR;
} else {
this.block = block;
}
public MinestomBlockState {
block = Objects.requireNonNullElse(block, Block.AIR);
}
public MinestomBlockState(String data) {
if(!data.contains("[")) {
block = Block.fromKey(data);
return;
public static MinestomBlockState fromStateId(String data) {
CompoundBinaryTag nbt = CompoundBinaryTag.empty();
int splitIndex = data.indexOf('{');
if(splitIndex != -1) {
String fullId = data;
data = data.substring(0, splitIndex);
String dataString = fullId.substring(splitIndex);
try {
nbt = tagStringIO.asCompound(dataString);
} catch(IOException exception) {
LOGGER.warn("Invalid entity data, will be ignored: {}", dataString);
}
}
String[] split = data.split("\\[");
String namespaceId = split[0];
String properties = split[1].substring(0, split[1].length() - 1);
int openBracketIndex = data.indexOf('[');
int closeBracketIndex = data.indexOf(']');
if(openBracketIndex == -1 || closeBracketIndex == -1 || closeBracketIndex < openBracketIndex) {
// no or invalid properties
Block block = Block.fromKey(data);
if(block != null && !nbt.isEmpty()) {
block = block.withNbt(nbt);
}
return new MinestomBlockState(block);
}
String namespaceId = data.substring(0, openBracketIndex);
String propertiesContent = data.substring(openBracketIndex + 1, closeBracketIndex);
Block block = Block.fromKey(namespaceId);
HashMap<String, String> propertiesMap = new HashMap<>();
for(String property : properties.split(",")) {
String[] kv = property.split("=");
propertiesMap.put(kv[0].strip(), kv[1].strip());
if (block == null) {
LOGGER.error("Invalid block ID found during parsing: {}", namespaceId);
return new MinestomBlockState(Block.AIR);
}
assert block != null;
this.block = block.withProperties(propertiesMap);
HashMap<String, String> propertiesMap = new HashMap<>();
int current = 0;
while (current < propertiesContent.length()) {
int nextComma = propertiesContent.indexOf(',', current);
String property;
if (nextComma == -1) {
property = propertiesContent.substring(current);
current = propertiesContent.length();
} else {
property = propertiesContent.substring(current, nextComma);
current = nextComma + 1;
}
int equalsIndex = property.indexOf('=');
if (equalsIndex == -1) {
LOGGER.warn("Invalid block property syntax (missing '=') in string: {}", property);
continue;
}
String key = property.substring(0, equalsIndex).strip();
String value = property.substring(equalsIndex + 1).strip();
propertiesMap.put(key, value);
}
if(!nbt.isEmpty()) {
block = block.withNbt(nbt);
}
return new MinestomBlockState(block.withProperties(propertiesMap));
}
@Override
@@ -15,26 +15,27 @@ import com.dfsek.terra.minestom.block.MinestomBlockState;
public class CachedChunk implements ProtoChunk {
private final int minHeight;
private final int maxHeight;
private final Block[] blocks;
private final MinestomBlockState[] blocks;
public CachedChunk(int minHeight, int maxHeight) {
this.minHeight = minHeight;
this.maxHeight = maxHeight;
this.blocks = new Block[16 * (maxHeight - minHeight + 1) * 16];
Arrays.fill(blocks, Block.AIR);
this.blocks = new MinestomBlockState[16 * (maxHeight - minHeight + 1) * 16];
Arrays.fill(blocks, MinestomBlockState.AIR);
}
public void writeRelative(UnitModifier modifier) {
modifier.setAllRelative((x, y, z) -> blocks[getIndex(x, y + minHeight, z)]);
modifier.setAllRelative((x, y, z) -> blocks[getIndex(x, y + minHeight, z)].block());
}
@Override
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
Block block = (Block) blockState.getHandle();
MinestomBlockState minestomBlockState = (MinestomBlockState) blockState;
Block block = minestomBlockState.block();
if(block == null) return;
int index = getIndex(x, y, z);
if (index > blocks.length || index < 0) return;
blocks[index] = block;
if(index > blocks.length || index < 0) return;
blocks[index] = minestomBlockState;
}
private int getIndex(int x, int y, int z) {
@@ -45,8 +46,8 @@ public class CachedChunk implements ProtoChunk {
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
int index = getIndex(x, y, z);
if (index > blocks.length || index < 0) return MinestomBlockState.AIR;
return new MinestomBlockState(blocks[index]);
if(index > blocks.length || index < 0) return MinestomBlockState.AIR;
return blocks[index];
}
@Override
@@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,7 +16,7 @@ import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
public class GeneratedChunkCache {
private static final Logger log = LoggerFactory.getLogger(GeneratedChunkCache.class);
private final LoadingCache<Pair<Integer, Integer>, CachedChunk> cache;
private final LoadingCache<@NotNull Long, CachedChunk> cache;
private final DimensionType dimensionType;
private final ChunkGenerator generator;
private final ServerWorld world;
@@ -29,7 +30,7 @@ public class GeneratedChunkCache {
this.cache = Caffeine.newBuilder()
.maximumSize(128)
.recordStats()
.build((Pair<Integer, Integer> key) -> generateChunk(key.getLeft(), key.getRight()));
.build((Long key) -> generateChunk(unpackX(key), unpackZ(key)));
}
private CachedChunk generateChunk(int x, int z) {
@@ -50,6 +51,18 @@ public class GeneratedChunkCache {
}
public CachedChunk at(int x, int z) {
return cache.get(Pair.of(x, z));
return cache.get(pack(x, z));
}
private long pack(final int x, final int z) {
return ((long) x) << 32 | z & 0xFFFFFFFFL;
}
private int unpackX(long key) {
return (int) (key >>> 32);
}
private int unpackZ(long key) {
return (int) key;
}
}
@@ -3,19 +3,10 @@ package com.dfsek.terra.minestom.config;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentParticle;
import net.minestom.server.particle.Particle;
import net.minestom.server.world.biome.BiomeEffects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class BiomeParticleConfigTemplate implements ObjectTemplate<BiomeEffects.Particle> {
@Value("particle")
@@ -34,8 +25,9 @@ public class BiomeParticleConfigTemplate implements ObjectTemplate<BiomeEffects.
String[] parts = particle.split("\\{");
Particle parsedParticle = Particle.fromKey(parts[0]);
if (parts.length > 1) {
LoggerFactory.getLogger(BiomeParticleConfigTemplate.class).warn("Particle {} has additional data, particle will be ignored.", particle);
if(parts.length > 1) {
LoggerFactory.getLogger(BiomeParticleConfigTemplate.class).warn("Particle {} has additional data, particle will be ignored.",
particle);
return null;
}
@@ -1,10 +1,10 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.minestom.api.EntityFactory;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import com.dfsek.terra.minestom.api.EntityFactory;
public class DefaultEntityFactory implements EntityFactory {
@Override
@@ -22,7 +22,8 @@ public class MinestomEntity implements com.dfsek.terra.api.entity.Entity {
public static MinestomEntity spawn(double x, double y, double z, EntityType type, TerraMinestomWorld world) {
Instance instance = world.getHandle();
Entity entity = world.getEntityFactory().createEntity(((MinestomEntityType) type).getHandle());
MinestomEntityType entityType = (MinestomEntityType) type;
Entity entity = world.getEntityFactory().createEntity(entityType.getHandle(), entityType.getData());
entity.setInstance(instance, new Pos(x, y, z));
return new MinestomEntity(entity, world);
}
@@ -1,12 +1,38 @@
package com.dfsek.terra.minestom.entity;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.minestom.server.entity.EntityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType {
private static final Logger LOGGER = LoggerFactory.getLogger(MinestomEntityType.class);
private static final TagStringIO tagStringIO = TagStringIO.tagStringIO();
private final EntityType delegate;
private final CompoundBinaryTag data;
public MinestomEntityType(String id) {
int splitIndex = id.indexOf('{');
if(splitIndex != -1) {
String fullId = id;
id = id.substring(0, splitIndex);
String dataString = fullId.substring(splitIndex);
CompoundBinaryTag data;
try {
data = tagStringIO.asCompound(dataString);
} catch(IOException exception) {
LOGGER.warn("Invalid entity data, will be ignored: {}", dataString);
data = CompoundBinaryTag.empty();
}
this.data = data;
} else {
this.data = CompoundBinaryTag.empty();
}
delegate = EntityType.fromKey(id);
}
@@ -14,4 +40,8 @@ public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType
public EntityType getHandle() {
return delegate;
}
public CompoundBinaryTag getData() {
return data;
}
}
@@ -15,7 +15,7 @@ public class MinestomWorldHandle implements WorldHandle {
@Override
public @NotNull BlockState createBlockState(@NotNull String data) {
return new MinestomBlockState(data);
return MinestomBlockState.fromStateId(data);
}
@Override
@@ -20,7 +20,6 @@ import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.info.WorldProperties;
import com.dfsek.terra.minestom.TerraMinestomPlatform;
import com.dfsek.terra.minestom.api.BiomeFactory;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import com.dfsek.terra.minestom.api.EntityFactory;
import com.dfsek.terra.minestom.biome.MinestomUserDefinedBiomePool;
@@ -18,7 +18,7 @@ public final class LifecycleEntryPoint {
logger.info("Initializing Terra {} mod...", modName);
FabricServerCommandManager<CommandSender> manager = new FabricServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
ExecutionCoordinator.asyncCoordinator(),
SenderMapper.create(
serverCommandSource -> (CommandSender) serverCommandSource,
commandSender -> (ServerCommandSource) commandSender)