Merge pull request #329 from PolyhedralDev/ver/6.2.0

Minor version 6.2.0
This commit is contained in:
dfsek
2022-07-06 19:43:00 -07:00
committed by GitHub
285 changed files with 5678 additions and 2448 deletions
+4 -1
View File
@@ -245,4 +245,7 @@ nbdist/
/run/ /run/
**/testDir/ **/testDir/
platforms/**/run/**
+5 -2
View File
@@ -63,7 +63,8 @@ to [Terra global moderation team](CODE_OF_CONDUCT.md#Reporting).
## I don't want to read this whole thing I just have a question!!! ## I don't want to read this whole thing I just have a question!!!
> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below. > **Note:** Please don't file an issue to ask a question. You'll get faster
> results by using the resources below.
We have an official discord server where you can request help from various users We have an official discord server where you can request help from various users
@@ -103,7 +104,9 @@ you don't need to create one. When you are creating a bug report,
please [include as many details as possible](#how-do-i-submit-a-good-bug-report) please [include as many details as possible](#how-do-i-submit-a-good-bug-report)
. .
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. > **Note:** If you find a **Closed** issue that seems like it is the same thing
> that you're experiencing, open a new issue and include a link to the original
> issue in the body of your new one.
#### Before Submitting A Bug Report #### Before Submitting A Bug Report
+3 -3
View File
@@ -1,8 +1,8 @@
preRelease(true) preRelease(true)
versionProjects(":common:api", version("6.1.2")) versionProjects(":common:api", version("6.2.0"))
versionProjects(":common:implementation", version("6.1.2")) versionProjects(":common:implementation", version("6.2.0"))
versionProjects(":platforms", version("6.1.2")) versionProjects(":platforms", version("6.2.0"))
allprojects { allprojects {
+3 -1
View File
@@ -15,7 +15,9 @@ buildscript {
repositories { repositories {
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
maven { url = uri("https://repo.codemc.org/repository/maven-public") } maven("https://repo.codemc.org/repository/maven-public") {
name = "CodeMC"
}
maven("https://papermc.io/repo/repository/maven-public/") { maven("https://papermc.io/repo/repository/maven-public/") {
name = "PaperMC" name = "PaperMC"
} }
+22 -22
View File
@@ -12,30 +12,30 @@ import kotlin.streams.asStream
*/ */
fun Project.addonDir(dir: File, task: Task) { fun Project.addonDir(dir: File, task: Task) {
val moveAddons = tasks.register("moveAddons" + task.name) { val moveAddons = tasks.register("moveAddons" + task.name) {
dependsOn("compileAddons") dependsOn("compileAddons")
doLast { doLast {
dir.parentFile.mkdirs() dir.parentFile.mkdirs()
matchingAddons(dir) { matchingAddons(dir) {
it.name.startsWith("Terra-") // Assume everything that starts with Terra- is a core addon. it.name.startsWith("Terra-") // Assume everything that starts with Terra- is a core addon.
}.forEach { }.forEach {
println("Deleting old addon: " + it.absolutePath) println("Deleting old addon: " + it.absolutePath)
it.delete() it.delete()
} }
forSubProjects(":common:addons") { forSubProjects(":common:addons") {
val jar = tasks.named("shadowJar").get() as ShadowJar val jar = tasks.named("shadowJar").get() as ShadowJar
val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else "" val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val target = File(dir, boot + jar.archiveFileName.get()) val target = File(dir, boot + jar.archiveFileName.get())
val base = "${jar.archiveBaseName.get()}-${version}" val base = "${jar.archiveBaseName.get()}-${version}"
println("Copying addon ${jar.archiveFileName.get()} to ${target.absolutePath}. Base name: $base") println("Copying addon ${jar.archiveFileName.get()} to ${target.absolutePath}. Base name: $base")
jar.archiveFile.orNull?.asFile?.copyTo(target) jar.archiveFile.orNull?.asFile?.copyTo(target)
}
} }
} }
}
task.dependsOn(moveAddons) task.dependsOn(moveAddons)
} }
+18 -4
View File
@@ -30,10 +30,24 @@ fun Project.configureDependencies() {
repositories { repositories {
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
maven("https://maven.fabricmc.net/") maven("https://maven.fabricmc.net/") {
maven("https://repo.codemc.org/repository/maven-public") name = "FabricMC"
maven("https://repo.codemc.io/repository/nms/") }
maven("https://papermc.io/repo/repository/maven-public/") maven("https://repo.codemc.org/repository/maven-public") {
name = "CodeMC"
}
maven("https://papermc.io/repo/repository/maven-public/") {
name = "PaperMC"
}
maven("https://files.minecraftforge.net/maven/") {
name = "Forge"
}
maven("https://maven.quiltmc.org/repository/release/") {
name = "Quilt"
}
maven("https://jitpack.io") {
name = "JitPack"
}
} }
dependencies { dependencies {
@@ -7,12 +7,9 @@ import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.BasePluginExtension import org.gradle.api.plugins.BasePluginExtension
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.creating
import org.gradle.kotlin.dsl.extra import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.named
@@ -55,7 +52,7 @@ fun Project.configureDistribution() {
println("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB") println("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 boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}"); val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")
if (!Files.exists(addonPath)) { if (!Files.exists(addonPath)) {
Files.createDirectories(addonPath.parent) Files.createDirectories(addonPath.parent)
+36 -7
View File
@@ -4,7 +4,7 @@ object Versions {
const val paralithic = "0.7.0" const val paralithic = "0.7.0"
const val strata = "1.1.1" const val strata = "1.1.1"
const val cloud = "1.7.0-SNAPSHOT" const val cloud = "1.7.0"
const val slf4j = "1.7.36" const val slf4j = "1.7.36"
const val log4j_slf4j_impl = "2.14.1" const val log4j_slf4j_impl = "2.14.1"
@@ -18,18 +18,40 @@ object Versions {
} }
object Fabric { object Fabric {
const val fabricLoader = "0.14.2" const val fabricLoader = "0.14.8"
const val fabricAPI = "0.55.1+1.19" const val fabricAPI = "0.57.0+1.19"
}
object Quilt {
const val quiltLoader = "0.17.0"
const val fabricApi = "2.0.0-beta.4+0.57.0-1.19"
}
object Mod {
const val mixin = "0.11.2+mixin.0.8.5"
const val minecraft = "1.19" const val minecraft = "1.19"
const val yarn = "$minecraft+build.1" const val yarn = "$minecraft+build.1"
const val permissionsAPI = "0.1-SNAPSHOT" const val fabricLoader = "0.14.2"
const val mixin = "0.11.2+mixin.0.8.5"
const val loom = "0.11-SNAPSHOT" const val architecuryLoom = "0.12.0-SNAPSHOT"
const val architecturyPlugin = "3.4-SNAPSHOT"
const val loomQuiltflower = "1.7.1"
const val lazyDfu = "0.1.2"
}
object Forge {
const val forge = "${Mod.minecraft}-41.0.63"
const val burningwave = "12.53.0"
} }
object Bukkit { object Bukkit {
const val paper = "1.18-R0.1-SNAPSHOT" const val paper = "1.18.2-R0.1-SNAPSHOT"
const val paperLib = "1.0.5" const val paperLib = "1.0.5"
const val minecraft = "1.19"
const val reflectionRemapper = "0.1.0-SNAPSHOT"
} }
object Sponge { object Sponge {
@@ -37,4 +59,11 @@ object Versions {
const val mixin = "0.8.2" const val mixin = "0.8.2"
const val minecraft = "1.17.1" const val minecraft = "1.17.1"
} }
object CLI {
const val nbt = "6.1"
const val logback = "1.2.9"
const val commonsIO = "2.7"
const val guava = "31.0.1-jre"
}
} }
@@ -0,0 +1,6 @@
version = version("1.0.0")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
compileOnlyApi(project(":common:addons:biome-query-api"))
}
@@ -0,0 +1,51 @@
package com.dfsek.terra.addons.biome.extrusion;
import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.world.biome.Biome;
class BaseBiomeColumn implements Column<Biome> {
private final BiomeExtrusionProvider biomeProvider;
private final Biome base;
private final int min;
private final int max;
private final int x;
private final int z;
private final long seed;
protected BaseBiomeColumn(BiomeExtrusionProvider biomeProvider, Biome base, int min, int max, int x, int z, long seed) {
this.biomeProvider = biomeProvider;
this.base = base;
this.min = min;
this.max = max;
this.x = x;
this.z = z;
this.seed = 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 biomeProvider.extrude(base, x, y, z, seed);
}
}
@@ -0,0 +1,67 @@
package com.dfsek.terra.addons.biome.extrusion;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.function.Supplier;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.api.ReplaceableBiome;
import com.dfsek.terra.addons.biome.extrusion.config.BiomeExtrusionTemplate;
import com.dfsek.terra.addons.biome.extrusion.config.ReplaceableBiomeLoader;
import com.dfsek.terra.addons.biome.extrusion.config.extrusions.ReplaceExtrusionTemplate;
import com.dfsek.terra.addons.biome.extrusion.config.extrusions.SetExtrusionTemplate;
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 BiomeExtrusionAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<Extrusion>>> EXTRUSION_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("EXTRUSION"), BiomeExtrusionTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<Extrusion>>> extrusionRegistry = event.getPack().getOrCreateRegistry(
EXTRUSION_REGISTRY_KEY);
extrusionRegistry.register(addon.key("SET"), SetExtrusionTemplate::new);
extrusionRegistry.register(addon.key("REPLACE"), ReplaceExtrusionTemplate::new);
})
.failThrough();
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPostLoadEvent.class)
.then(event -> {
Registry<Biome> biomeRegistry = event.getPack().getRegistry(Biome.class);
event.getPack().applyLoader(ReplaceableBiome.class, new ReplaceableBiomeLoader(biomeRegistry));
});
}
}
@@ -0,0 +1,67 @@
package com.dfsek.terra.addons.biome.extrusion;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
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 {
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.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 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
public Column<Biome> getColumn(int x, int z, long seed, int min, int max) {
return delegate.getBaseBiome(x, z, seed)
.map(base -> (Column<Biome>) new BaseBiomeColumn(this, base, min, max, x, z, seed))
.orElseGet(() -> BiomeProvider.super.getColumn(x, z, seed, min, max));
}
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return delegate.getBaseBiome(x, z, seed);
}
@Override
public Iterable<Biome> getBiomes() {
return biomes;
}
@Override
public int resolution() {
return resolution;
}
public BiomeProvider getDelegate() {
return delegate;
}
}
@@ -0,0 +1,12 @@
package com.dfsek.terra.addons.biome.extrusion.api;
import java.util.Collection;
import com.dfsek.terra.api.world.biome.Biome;
public interface Extrusion {
Biome extrude(Biome original, int x, int y, int z, long seed);
Collection<Biome> getBiomes();
}
@@ -0,0 +1,23 @@
package com.dfsek.terra.addons.biome.extrusion.api;
import com.dfsek.terra.api.world.biome.Biome;
final class PresentBiome implements ReplaceableBiome {
private final Biome biome;
PresentBiome(Biome biome) {
this.biome = biome;
}
@Override
public Biome get(Biome existing) {
return biome;
}
@Override
public boolean isSelf() {
return false;
}
}
@@ -0,0 +1,31 @@
package com.dfsek.terra.addons.biome.extrusion.api;
import java.util.Optional;
import com.dfsek.terra.api.world.biome.Biome;
/**
* Basically just a specialised implementation of {@link Optional} for biomes where a biome may be a "self" reference.
*/
public sealed interface ReplaceableBiome permits PresentBiome, SelfBiome {
static ReplaceableBiome of(Biome biome) {
return new PresentBiome(biome);
}
static ReplaceableBiome self() {
return SelfBiome.INSTANCE;
}
Biome get(Biome existing);
default Biome get() {
if(isSelf()) {
throw new IllegalStateException("Cannot get() self biome!");
}
return get(null);
}
boolean isSelf();
}
@@ -0,0 +1,21 @@
package com.dfsek.terra.addons.biome.extrusion.api;
import java.util.Objects;
import com.dfsek.terra.api.world.biome.Biome;
final class SelfBiome implements ReplaceableBiome {
public static final SelfBiome INSTANCE = new SelfBiome();
@Override
public Biome get(Biome existing) {
return Objects.requireNonNull(existing);
}
@Override
public boolean isSelf() {
return true;
}
}
@@ -0,0 +1,30 @@
package com.dfsek.terra.addons.biome.extrusion.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 java.util.List;
import com.dfsek.terra.addons.biome.extrusion.BiomeExtrusionProvider;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomeExtrusionTemplate implements ObjectTemplate<BiomeProvider> {
@Value("provider")
private @Meta BiomeProvider provider;
@Value("resolution")
@Default
private @Meta int resolution = 4;
@Value("extrusions")
private @Meta List<@Meta Extrusion> extrusions;
@Override
public BiomeProvider get() {
return new BiomeExtrusionProvider(provider, extrusions, resolution);
}
}
@@ -0,0 +1,32 @@
package com.dfsek.terra.addons.biome.extrusion.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.extrusion.api.ReplaceableBiome;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.world.biome.Biome;
public class ReplaceableBiomeLoader implements TypeLoader<ReplaceableBiome> {
private final Registry<Biome> biomeRegistry;
public ReplaceableBiomeLoader(Registry<Biome> biomeRegistry) {
this.biomeRegistry = biomeRegistry;
}
@Override
public ReplaceableBiome load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker)
throws LoadException {
if(c.equals("SELF")) return ReplaceableBiome.self();
return biomeRegistry
.getByID((String) c)
.map(ReplaceableBiome::of)
.orElseThrow(() -> new LoadException("No such biome: " + c, depthTracker));
}
}
@@ -0,0 +1,23 @@
package com.dfsek.terra.addons.biome.extrusion.config.extrusions;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.api.ReplaceableBiome;
import com.dfsek.terra.addons.biome.extrusion.extrusions.ReplaceExtrusion;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class ReplaceExtrusionTemplate extends SamplerExtrusionTemplate {
@Value("to")
private @Meta ProbabilityCollection<@Meta ReplaceableBiome> biomes;
@Value("from")
private @Meta String fromTag;
@Override
public Extrusion get() {
return new ReplaceExtrusion(sampler, range, biomes, fromTag);
}
}
@@ -0,0 +1,18 @@
package com.dfsek.terra.addons.biome.extrusion.config.extrusions;
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.noise.NoiseSampler;
import com.dfsek.terra.api.util.Range;
public abstract class SamplerExtrusionTemplate implements ObjectTemplate<Extrusion> {
@Value("sampler")
protected @Meta NoiseSampler sampler;
@Value("range")
protected @Meta Range range;
}
@@ -0,0 +1,20 @@
package com.dfsek.terra.addons.biome.extrusion.config.extrusions;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.api.ReplaceableBiome;
import com.dfsek.terra.addons.biome.extrusion.extrusions.SetExtrusion;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class SetExtrusionTemplate extends SamplerExtrusionTemplate {
@Value("to")
private @Meta ProbabilityCollection<@Meta ReplaceableBiome> biomes;
@Override
public Extrusion get() {
return new SetExtrusion(sampler, range, biomes);
}
}
@@ -0,0 +1,52 @@
package com.dfsek.terra.addons.biome.extrusion.extrusions;
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.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.world.biome.Biome;
/**
* Sets biomes at locations based on a sampler.
*/
public class ReplaceExtrusion implements Extrusion {
private final NoiseSampler sampler;
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);
}
@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);
}
return original;
}
@Override
public Collection<Biome> getBiomes() {
return biomes
.getContents()
.stream()
.filter(Predicate.not(ReplaceableBiome::isSelf))
.map(ReplaceableBiome::get)
.collect(Collectors.toSet());
}
}
@@ -0,0 +1,45 @@
package com.dfsek.terra.addons.biome.extrusion.extrusions;
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.world.biome.Biome;
/**
* Sets biomes at locations based on a sampler.
*/
public class SetExtrusion implements Extrusion {
private final NoiseSampler sampler;
private final Range range;
private final ProbabilityCollection<ReplaceableBiome> biomes;
public SetExtrusion(NoiseSampler sampler, Range range, ProbabilityCollection<ReplaceableBiome> biomes) {
this.sampler = sampler;
this.range = range;
this.biomes = biomes;
}
@Override
public Biome extrude(Biome original, int x, int y, int z, long seed) {
return range.ifInRange(y, () -> biomes.get(sampler, x, y, z, seed).get(original), original);
}
@Override
public Collection<Biome> getBiomes() {
return biomes
.getContents()
.stream()
.filter(Predicate.not(ReplaceableBiome::isSelf))
.map(ReplaceableBiome::get)
.collect(Collectors.toSet());
}
}
@@ -0,0 +1,14 @@
schema-version: 1
contributors:
- Terra contributors
id: biome-provider-extrusion
version: @VERSION@
entrypoints:
- "com.dfsek.terra.addons.biome.extrusion.BiomeExtrusionAddon"
website:
issues: https://github.com/PolyhedralDev/Terra/issues
source: https://github.com/PolyhedralDev/Terra
docs: https://terra.polydev.org
license: MIT License
depends:
biome-query-api: "1.+"
@@ -13,6 +13,7 @@ import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
@@ -38,6 +39,10 @@ public class ImageBiomeProvider implements BiomeProvider {
@Override @Override
public Biome getBiome(int x, int y, int z, long seed) { public Biome getBiome(int x, int y, int z, long seed) {
return getBiome(x, z);
}
public Biome getBiome(int x, int z) {
x /= resolution; x /= resolution;
z /= resolution; z /= resolution;
Color color = align.getColor(image, x, z); Color color = align.getColor(image, x, z);
@@ -51,6 +56,11 @@ public class ImageBiomeProvider implements BiomeProvider {
})); }));
} }
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z));
}
@Override @Override
public Iterable<Biome> getBiomes() { public Iterable<Biome> getBiomes() {
return colorBiomeMap.values(); return colorBiomeMap.values();
@@ -1,14 +1,12 @@
version = version("1.0.0") version = version("1.0.1")
dependencies { dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader")) compileOnlyApi(project(":common:addons:manifest-addon-loader"))
implementation("com.github.ben-manes.caffeine:caffeine:3.1.0")
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
} }
tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") { tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
relocate("com.github.benmanes.caffeine", "com.dfsek.terra.addons.biome.pipeline.lib.caffeine")
relocate("net.jafama", "com.dfsek.terra.addons.biome.pipeline.lib.jafama") relocate("net.jafama", "com.dfsek.terra.addons.biome.pipeline.lib.jafama")
} }
@@ -0,0 +1,71 @@
package com.dfsek.terra.addons.biome.pipeline;
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;
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);
}
}
}
@@ -13,6 +13,7 @@ import net.jafama.FastMath;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@@ -21,6 +22,7 @@ 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.api.stage.Stage;
import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.registry.key.StringIdentifiable; 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.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@@ -73,18 +75,26 @@ public class BiomePipelineProvider implements BiomeProvider {
@Override @Override
public Biome getBiome(int x, int y, int z, long seed) { 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; x += mutator.noise(seed + 1, x, z) * noiseAmp;
z += mutator.noise(seed + 2, x, z) * noiseAmp; z += mutator.noise(seed + 2, x, z) * noiseAmp;
x = FastMath.floorToInt(FastMath.floorDiv(x, resolution)); x /= resolution;
z /= resolution;
z = FastMath.floorToInt(FastMath.floorDiv(z, resolution));
int fdX = FastMath.floorDiv(x, pipeline.getSize()); int fdX = FastMath.floorDiv(x, pipeline.getSize());
int fdZ = FastMath.floorDiv(z, pipeline.getSize()); int fdZ = FastMath.floorDiv(z, pipeline.getSize());
return holderCache.get(new SeededVector(fdX, fdZ, seed)).getBiome(x - fdX * pipeline.getSize(), return holderCache.get(new SeededVector(fdX, fdZ, seed)).getBiome(x - fdX * pipeline.getSize(),
z - fdZ * pipeline.getSize()).getBiome(); z - fdZ * pipeline.getSize()).getBiome();
}
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z, seed));
} }
@Override @Override
@@ -92,6 +102,30 @@ public class BiomePipelineProvider implements BiomeProvider {
return biomes; 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) { 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)));
}
} }
} }
@@ -22,7 +22,7 @@ public abstract class BiomeProviderTemplate implements ObjectTemplate<BiomeProvi
@Default @Default
@Description(""" @Description("""
The resolution at which to sample biomes. The resolution at which to sample biomes.
Larger values are quadratically faster, but produce lower quality results. Larger values are quadratically faster, but produce lower quality results.
For example, a value of 3 would sample every 3 blocks.""") For example, a value of 3 would sample every 3 blocks.""")
protected @Meta int resolution = 1; protected @Meta int resolution = 1;
@@ -8,6 +8,7 @@
package com.dfsek.terra.addons.biome.single; package com.dfsek.terra.addons.biome.single;
import java.util.Collections; import java.util.Collections;
import java.util.Optional;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@@ -25,6 +26,11 @@ public class SingleBiomeProvider implements BiomeProvider {
return biome; return biome;
} }
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(biome);
}
@Override @Override
public Iterable<Biome> getBiomes() { public Iterable<Biome> getBiomes() {
return Collections.singleton(biome); return Collections.singleton(biome);
+4
View File
@@ -0,0 +1,4 @@
# Biome Query API
This addon contains an API to allow other addons to quickly query
Biome data, by baking queries and using Contexts on biomes.
@@ -0,0 +1,5 @@
version = version("1.0.0")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
}
@@ -0,0 +1,46 @@
package com.dfsek.terra.addons.biome.query;
import java.util.Collection;
import com.dfsek.terra.addons.biome.query.impl.BiomeTagFlattener;
import com.dfsek.terra.addons.biome.query.impl.BiomeTagHolder;
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.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.properties.Context;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.biome.Biome;
public class BiomeQueryAPIAddon implements AddonInitializer {
public static PropertyKey<BiomeTagHolder> BIOME_TAG_KEY = Context.create(BiomeTagHolder.class);
@Inject
private Platform platform;
@Inject
private BaseAddon addon;
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPostLoadEvent.class)
.then(event -> {
Collection<Biome> biomes = event
.getPack()
.getRegistry(Biome.class)
.entries();
BiomeTagFlattener flattener = new BiomeTagFlattener(biomes
.stream()
.flatMap(biome -> biome.getTags().stream())
.toList());
biomes.forEach(biome -> biome.getContext().put(BIOME_TAG_KEY, new BiomeTagHolder(biome, flattener)));
})
.global();
}
}
@@ -0,0 +1,17 @@
package com.dfsek.terra.addons.biome.query.api;
import java.util.function.Predicate;
import com.dfsek.terra.addons.biome.query.impl.SingleTagQuery;
import com.dfsek.terra.api.world.biome.Biome;
public final class BiomeQueries {
private BiomeQueries() {
}
public static Predicate<Biome> has(String tag) {
return new SingleTagQuery(tag);
}
}
@@ -0,0 +1,20 @@
package com.dfsek.terra.addons.biome.query.impl;
import java.util.List;
public class BiomeTagFlattener {
private final List<String> tags;
public BiomeTagFlattener(List<String> tags) {
this.tags = tags;
}
public int index(String tag) {
return tags.indexOf(tag);
}
public int size() {
return tags.size();
}
}
@@ -0,0 +1,26 @@
package com.dfsek.terra.addons.biome.query.impl;
import com.dfsek.terra.api.properties.Properties;
import com.dfsek.terra.api.world.biome.Biome;
public class BiomeTagHolder implements Properties {
private final boolean[] tags;
private final BiomeTagFlattener flattener;
public BiomeTagHolder(Biome biome, BiomeTagFlattener flattener) {
this.tags = new boolean[flattener.size()];
this.flattener = flattener;
for(String tag : biome.getTags()) {
tags[flattener.index(tag)] = true;
}
}
boolean get(int index) {
return tags[index];
}
public BiomeTagFlattener getFlattener() {
return flattener;
}
}
@@ -0,0 +1,31 @@
package com.dfsek.terra.addons.biome.query.impl;
import java.util.function.Predicate;
import com.dfsek.terra.addons.biome.query.BiomeQueryAPIAddon;
import com.dfsek.terra.api.world.biome.Biome;
public class SingleTagQuery implements Predicate<Biome> {
private final String tag;
private int tagIndex = -1;
public SingleTagQuery(String tag) {
this.tag = tag;
}
@Override
public boolean test(Biome biome) {
if(tagIndex < 0) {
tagIndex = biome
.getContext()
.get(BiomeQueryAPIAddon.BIOME_TAG_KEY)
.getFlattener()
.index(tag);
}
return biome
.getContext()
.get(BiomeQueryAPIAddon.BIOME_TAG_KEY)
.get(tagIndex);
}
}
@@ -0,0 +1,12 @@
schema-version: 1
contributors:
- Terra contributors
id: biome-query-api
version: @VERSION@
entrypoints:
- "com.dfsek.terra.addons.biome.query.BiomeQueryAPIAddon"
website:
issues: https://github.com/PolyhedralDev/Terra/issues
source: https://github.com/PolyhedralDev/Terra
docs: https://terra.polydev.org
license: MIT License
@@ -1,4 +1,4 @@
version = version("1.0.0") version = version("1.1.0")
dependencies { dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader")) compileOnlyApi(project(":common:addons:manifest-addon-loader"))
@@ -9,7 +9,9 @@ package com.dfsek.terra.addons.chunkgenerator;
import com.dfsek.terra.addons.chunkgenerator.config.NoiseChunkGeneratorPackConfigTemplate; import com.dfsek.terra.addons.chunkgenerator.config.NoiseChunkGeneratorPackConfigTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseConfigTemplate; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseConfigTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.addons.chunkgenerator.config.palette.BiomePaletteTemplate; import com.dfsek.terra.addons.chunkgenerator.config.palette.BiomePaletteTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.config.palette.SlantLayer; import com.dfsek.terra.addons.chunkgenerator.config.palette.SlantLayer;
import com.dfsek.terra.addons.chunkgenerator.generation.NoiseChunkGenerator3D; import com.dfsek.terra.addons.chunkgenerator.generation.NoiseChunkGenerator3D;
import com.dfsek.terra.addons.manifest.api.AddonInitializer; import com.dfsek.terra.addons.manifest.api.AddonInitializer;
@@ -19,6 +21,8 @@ import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler; import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject; import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.properties.Context;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.chunk.generation.util.provider.ChunkGeneratorProvider; import com.dfsek.terra.api.world.chunk.generation.util.provider.ChunkGeneratorProvider;
@@ -32,17 +36,22 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
@Override @Override
public void initialize() { public void initialize() {
PropertyKey<PaletteInfo> paletteInfoPropertyKey = Context.create(PaletteInfo.class);
PropertyKey<BiomeNoiseProperties> noisePropertiesPropertyKey = Context.create(BiomeNoiseProperties.class);
platform.getEventManager() platform.getEventManager()
.getHandler(FunctionalEventHandler.class) .getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class) .register(addon, ConfigPackPreLoadEvent.class)
.priority(1000)
.then(event -> { .then(event -> {
NoiseChunkGeneratorPackConfigTemplate config = event.loadTemplate(new NoiseChunkGeneratorPackConfigTemplate()); NoiseChunkGeneratorPackConfigTemplate config = event.loadTemplate(new NoiseChunkGeneratorPackConfigTemplate());
event.getPack() event.getPack()
.getOrCreateRegistry(ChunkGeneratorProvider.class) .getOrCreateRegistry(ChunkGeneratorProvider.class)
.register(addon.key("NOISE_3D"), .register(addon.key("NOISE_3D"),
pack -> new NoiseChunkGenerator3D(platform, config.getElevationBlend(), config.getHorizontalRes(), pack -> new NoiseChunkGenerator3D(pack, platform, config.getElevationBlend(),
config.getVerticalRes())); config.getHorizontalRes(),
config.getVerticalRes(), noisePropertiesPropertyKey,
paletteInfoPropertyKey));
event.getPack() event.getPack()
.applyLoader(SlantLayer.class, SlantLayer::new); .applyLoader(SlantLayer.class, SlantLayer::new);
}) })
@@ -53,8 +62,10 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
.register(addon, ConfigurationLoadEvent.class) .register(addon, ConfigurationLoadEvent.class)
.then(event -> { .then(event -> {
if(event.is(Biome.class)) { if(event.is(Biome.class)) {
event.getLoadedObject(Biome.class).getContext().put(event.load(new BiomePaletteTemplate(platform)).get()); event.getLoadedObject(Biome.class).getContext().put(paletteInfoPropertyKey,
event.getLoadedObject(Biome.class).getContext().put(event.load(new BiomeNoiseConfigTemplate()).get()); event.load(new BiomePaletteTemplate(platform)).get());
event.getLoadedObject(Biome.class).getContext().put(noisePropertiesPropertyKey,
event.load(new BiomeNoiseConfigTemplate()).get());
} }
}) })
.failThrough(); .failThrough();
@@ -39,6 +39,6 @@ public class BiomeNoiseConfigTemplate implements ObjectTemplate<BiomeNoiseProper
@Override @Override
public BiomeNoiseProperties get() { public BiomeNoiseProperties get() {
return new BiomeNoiseProperties(baseSampler, elevationSampler, carvingSampler, blendDistance, blendStep, blendWeight, return new BiomeNoiseProperties(baseSampler, elevationSampler, carvingSampler, blendDistance, blendStep, blendWeight,
elevationWeight); elevationWeight, new ThreadLocalNoiseHolder());
} }
} }
@@ -10,6 +10,6 @@ public record BiomeNoiseProperties(NoiseSampler base,
int blendDistance, int blendDistance,
int blendStep, int blendStep,
double blendWeight, double blendWeight,
double elevationWeight) implements Properties { double elevationWeight,
ThreadLocalNoiseHolder noiseHolder) implements Properties {
} }
@@ -0,0 +1,32 @@
package com.dfsek.terra.addons.chunkgenerator.config.noise;
import com.dfsek.terra.api.noise.NoiseSampler;
public class ThreadLocalNoiseHolder {
private final ThreadLocal<Holder> holder = ThreadLocal.withInitial(Holder::new);
public double getNoise(NoiseSampler sampler, int x, int y, int z, long seed) {
Holder holder = this.holder.get();
if(holder.init && holder.y == y && holder.z == z && holder.x == x && holder.seed == seed) {
return holder.noise;
}
double noise = sampler.noise(seed, x, y, z);
holder.noise = noise;
holder.x = x;
holder.y = y;
holder.z = z;
holder.seed = seed;
holder.init = true;
return noise;
}
private static final class Holder {
int x, y, z;
boolean init = false;
long seed;
double noise;
}
}
@@ -59,6 +59,10 @@ public class BiomePaletteTemplate implements ObjectTemplate<PaletteInfo> {
} }
}; };
@Value("carving.update-palette")
@Default
private @Meta boolean updatePalette = false;
public BiomePaletteTemplate(Platform platform) { this.platform = platform; } public BiomePaletteTemplate(Platform platform) { this.platform = platform; }
@Override @Override
@@ -79,6 +83,7 @@ public class BiomePaletteTemplate implements ObjectTemplate<PaletteInfo> {
slantLayers.put(threshold, layer.getPalette()); slantLayers.put(threshold, layer.getPalette());
} }
return new PaletteInfo(builder.build(), SlantHolder.of(slantLayers, minThreshold), oceanPalette, seaLevel, slantDepth); return new PaletteInfo(builder.build(), SlantHolder.of(slantLayers, minThreshold), oceanPalette, seaLevel, slantDepth,
updatePalette);
} }
} }
@@ -17,5 +17,6 @@ public record PaletteInfo(PaletteHolder paletteHolder,
SlantHolder slantHolder, SlantHolder slantHolder,
Palette ocean, Palette ocean,
int seaLevel, int seaLevel,
int maxSlantDepth) implements Properties { int maxSlantDepth,
boolean updatePaletteWhenCarving) implements Properties {
} }
@@ -11,6 +11,7 @@ package com.dfsek.terra.addons.chunkgenerator.generation;
import net.jafama.FastMath; import net.jafama.FastMath;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo; import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.generation.math.PaletteUtil; import com.dfsek.terra.addons.chunkgenerator.generation.math.PaletteUtil;
import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.LazilyEvaluatedInterpolator; import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.LazilyEvaluatedInterpolator;
@@ -18,6 +19,9 @@ import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.SamplerProvider; import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.SamplerProvider;
import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator; import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
@@ -36,13 +40,28 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
private final int carverHorizontalResolution; private final int carverHorizontalResolution;
private final int carverVerticalResolution; private final int carverVerticalResolution;
public NoiseChunkGenerator3D(Platform platform, int elevationBlend, int carverHorizontalResolution, private final PropertyKey<PaletteInfo> paletteInfoPropertyKey;
int carverVerticalResolution) { private final PropertyKey<BiomeNoiseProperties> noisePropertiesKey;
public NoiseChunkGenerator3D(ConfigPack pack, Platform platform, int elevationBlend, int carverHorizontalResolution,
int carverVerticalResolution,
PropertyKey<BiomeNoiseProperties> noisePropertiesKey,
PropertyKey<PaletteInfo> paletteInfoPropertyKey) {
this.platform = platform; this.platform = platform;
this.air = platform.getWorldHandle().air(); this.air = platform.getWorldHandle().air();
this.carverHorizontalResolution = carverHorizontalResolution; this.carverHorizontalResolution = carverHorizontalResolution;
this.carverVerticalResolution = carverVerticalResolution; this.carverVerticalResolution = carverVerticalResolution;
this.samplerCache = new SamplerProvider(platform, elevationBlend); this.paletteInfoPropertyKey = paletteInfoPropertyKey;
this.noisePropertiesKey = noisePropertiesKey;
int maxBlend = pack
.getBiomeProvider()
.stream()
.map(biome -> biome.getContext().get(noisePropertiesKey))
.mapToInt(properties -> properties.blendDistance() * properties.blendStep())
.max()
.orElse(0);
this.samplerCache = new SamplerProvider(platform, elevationBlend, noisePropertiesKey, maxBlend);
} }
@Override @Override
@@ -62,7 +81,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
chunkX, chunkX,
chunkZ, chunkZ,
world.getMaxHeight(), world.getMaxHeight(),
world.getMinHeight(), noisePropertiesKey, world.getMinHeight(),
carverHorizontalResolution, carverHorizontalResolution,
carverVerticalResolution, carverVerticalResolution,
seed); seed);
@@ -73,23 +92,28 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
int cx = xOrig + x; int cx = xOrig + x;
int cz = zOrig + z; int cz = zOrig + z;
Biome biome = biomeProvider.getBiome(cx, 0, cz, seed);
PaletteInfo paletteInfo = biome.getContext().get(PaletteInfo.class);
int sea = paletteInfo.seaLevel();
Palette seaPalette = paletteInfo.ocean();
BlockState data; BlockState data;
Column<Biome> biomeColumn = biomeProvider.getColumn(cx, cz, world);
for(int y = world.getMaxHeight() - 1; y >= world.getMinHeight(); y--) { for(int y = world.getMaxHeight() - 1; y >= world.getMinHeight(); y--) {
Biome biome = biomeColumn.get(y);
PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
int sea = paletteInfo.seaLevel();
Palette seaPalette = paletteInfo.ocean();
if(sampler.sample(x, y, z) > 0) { if(sampler.sample(x, y, z) > 0) {
if(carver.sample(x, y, z) <= 0) { if(carver.sample(x, y, z) <= 0) {
data = PaletteUtil.getPalette(x, y, z, sampler, paletteInfo, paletteLevel).get(paletteLevel, cx, y, cz, data = PaletteUtil
seed); .getPalette(x, y, z, sampler, paletteInfo, paletteLevel)
.get(paletteLevel, cx, y, cz, seed);
chunk.setBlock(x, y, z, data); chunk.setBlock(x, y, z, data);
paletteLevel++;
} else if(paletteInfo.updatePaletteWhenCarving()) {
paletteLevel = 0;
} else {
paletteLevel++;
} }
paletteLevel++;
} else if(y <= sea) { } else if(y <= sea) {
chunk.setBlock(x, y, z, seaPalette.get(sea - y, x + xOrig, y, z + zOrig, seed)); chunk.setBlock(x, y, z, seaPalette.get(sea - y, x + xOrig, y, z + zOrig, seed));
paletteLevel = 0; paletteLevel = 0;
@@ -107,7 +131,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
Biome biome = biomeProvider.getBiome(x, y, z, world.getSeed()); Biome biome = biomeProvider.getBiome(x, y, z, world.getSeed());
Sampler3D sampler = samplerCache.get(x, z, world, biomeProvider); Sampler3D sampler = samplerCache.get(x, z, world, biomeProvider);
PaletteInfo paletteInfo = biome.getContext().get(PaletteInfo.class); PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
int fdX = FastMath.floorMod(x, 16); int fdX = FastMath.floorMod(x, 16);
int fdZ = FastMath.floorMod(z, 16); int fdZ = FastMath.floorMod(z, 16);
@@ -128,7 +152,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
@Override @Override
public Palette getPalette(int x, int y, int z, WorldProperties world, BiomeProvider biomeProvider) { public Palette getPalette(int x, int y, int z, WorldProperties world, BiomeProvider biomeProvider) {
return biomeProvider.getBiome(x, y, z, world.getSeed()).getContext().get(PaletteInfo.class).paletteHolder().getPalette(y); return biomeProvider.getBiome(x, y, z, world.getSeed()).getContext().get(paletteInfoPropertyKey).paletteHolder().getPalette(y);
} }
public SamplerProvider samplerProvider() { public SamplerProvider samplerProvider() {
@@ -9,11 +9,10 @@ package com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation;
import net.jafama.FastMath; import net.jafama.FastMath;
import java.util.HashMap;
import java.util.Map;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.api.util.mutable.MutableInteger; import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@@ -23,7 +22,6 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
*/ */
public class ChunkInterpolator { public class ChunkInterpolator {
private final Interpolator3[][][] interpGrid; private final Interpolator3[][][] interpGrid;
private final long seed;
private final int min; private final int min;
private final int max; private final int max;
@@ -37,10 +35,10 @@ public class ChunkInterpolator {
* @param min * @param min
* @param max * @param max
*/ */
public ChunkInterpolator(long seed, int chunkX, int chunkZ, BiomeProvider provider, int min, int max) { public ChunkInterpolator(long seed, int chunkX, int chunkZ, BiomeProvider provider, int min, int max,
PropertyKey<BiomeNoiseProperties> noisePropertiesKey, int maxBlend) {
this.min = min; this.min = min;
this.max = max; this.max = max;
this.seed = seed;
int xOrigin = chunkX << 4; int xOrigin = chunkX << 4;
int zOrigin = chunkZ << 4; int zOrigin = chunkZ << 4;
@@ -53,28 +51,67 @@ public class ChunkInterpolator {
double[][][] noiseStorage = new double[5][5][size + 1]; double[][][] noiseStorage = new double[5][5][size + 1];
int maxBlendAndChunk = 17 + 2 * maxBlend;
@SuppressWarnings("unchecked")
Column<Biome>[] columns = new Column[maxBlendAndChunk * maxBlendAndChunk];
for(int x = 0; x < 5; x++) { for(int x = 0; x < 5; x++) {
int scaledX = x << 2;
int absoluteX = xOrigin + scaledX;
for(int z = 0; z < 5; z++) { for(int z = 0; z < 5; z++) {
BiomeNoiseProperties generationSettings = provider.getBiome(xOrigin + (x << 2), 0, zOrigin + (z << 2), seed) int scaledZ = z << 2;
.getContext() int absoluteZ = zOrigin + scaledZ;
.get(BiomeNoiseProperties.class);
Map<BiomeNoiseProperties, MutableInteger> genMap = new HashMap<>();
int step = generationSettings.blendStep(); int index = (scaledX + maxBlend) + maxBlendAndChunk * (scaledZ + maxBlend);
int blend = generationSettings.blendDistance(); Column<Biome> biomeColumn = columns[index];
for(int xi = -blend; xi <= blend; xi++) { if(biomeColumn == null) {
for(int zi = -blend; zi <= blend; zi++) { biomeColumn = provider.getColumn(absoluteX, absoluteZ, seed, min, max);
genMap.computeIfAbsent( columns[index] = biomeColumn;
provider.getBiome(xOrigin + (x << 2) + (xi * step), 0, zOrigin + (z << 2) + (zi * step), seed)
.getContext()
.get(BiomeNoiseProperties.class),
g -> new MutableInteger(0)).increment(); // Increment by 1
}
} }
for(int y = 0; y < size + 1; y++) { for(int y = 0; y < size; y++) {
noiseStorage[x][z][y] = computeNoise(genMap, (x << 2) + xOrigin, (y << 2) + this.min, (z << 2) + zOrigin); int scaledY = (y << 2) + min;
BiomeNoiseProperties generationSettings = biomeColumn.get(scaledY)
.getContext()
.get(noisePropertiesKey);
int step = generationSettings.blendStep();
int blend = generationSettings.blendDistance();
double runningNoise = 0;
double runningDiv = 0;
for(int xi = -blend; xi <= blend; xi++) {
for(int zi = -blend; zi <= blend; zi++) {
int blendX = (xi * step);
int blendZ = (zi * step);
int localIndex = (scaledX + maxBlend + blendX) + maxBlendAndChunk * (scaledZ + maxBlend + blendZ);
Column<Biome> column = columns[localIndex];
if(column == null) {
column = provider.getColumn(absoluteX + blendX, absoluteZ + blendZ, seed, min, max);
columns[localIndex] = column;
}
BiomeNoiseProperties properties = column
.get(scaledY)
.getContext()
.get(noisePropertiesKey);
double sample = properties.noiseHolder().getNoise(properties.base(), absoluteX, scaledY, absoluteZ, seed);
runningNoise += sample * properties.blendWeight();
runningDiv += properties.blendWeight();
}
}
double noise = runningNoise / runningDiv;
noiseStorage[x][z][y] = noise;
if(y == size - 1) {
noiseStorage[x][z][size] = noise;
}
} }
} }
} }
@@ -100,24 +137,6 @@ public class ChunkInterpolator {
return FastMath.max(FastMath.min(value, high), 0); return FastMath.max(FastMath.min(value, high), 0);
} }
public double computeNoise(BiomeNoiseProperties generationSettings, double x, double y, double z) {
return generationSettings.base().noise(seed, x, y, z);
}
public double computeNoise(Map<BiomeNoiseProperties, MutableInteger> gens, double x, double y, double z) {
double n = 0;
double div = 0;
for(Map.Entry<BiomeNoiseProperties, MutableInteger> entry : gens.entrySet()) {
BiomeNoiseProperties gen = entry.getKey();
int weight = entry.getValue().get();
double noise = computeNoise(gen, x, y, z);
n += noise * weight;
div += gen.blendWeight() * weight;
}
return n / div;
}
/** /**
* Gets the noise at a pair of internal chunk coordinates. * Gets the noise at a pair of internal chunk coordinates.
* *
@@ -8,13 +8,15 @@
package com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation; package com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class ElevationInterpolator { public class ElevationInterpolator {
private final double[][] values = new double[18][18]; private final double[][] values = new double[18][18];
public ElevationInterpolator(long seed, int chunkX, int chunkZ, BiomeProvider provider, int smooth) { public ElevationInterpolator(long seed, int chunkX, int chunkZ, BiomeProvider provider, int smooth,
PropertyKey<BiomeNoiseProperties> noisePropertiesKey) {
int xOrigin = chunkX << 4; int xOrigin = chunkX << 4;
int zOrigin = chunkZ << 4; int zOrigin = chunkZ << 4;
@@ -23,8 +25,14 @@ public class ElevationInterpolator {
// Precompute generators. // Precompute generators.
for(int x = -1 - smooth; x <= 16 + smooth; x++) { for(int x = -1 - smooth; x <= 16 + smooth; x++) {
for(int z = -1 - smooth; z <= 16 + smooth; z++) { for(int z = -1 - smooth; z <= 16 + smooth; z++) {
gens[x + 1 + smooth][z + 1 + smooth] = provider.getBiome(xOrigin + x, 0, zOrigin + z, seed).getContext().get( int bx = xOrigin + x;
BiomeNoiseProperties.class); int bz = zOrigin + z;
gens[x + 1 + smooth][z + 1 + smooth] =
provider
.getBaseBiome(bx, bz, seed)
.orElseGet(() -> provider.getBiome(bx, 0, bz, seed)) // kind of a hack
.getContext()
.get(noisePropertiesKey);
} }
} }
@@ -3,16 +3,14 @@ package com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation;
import net.jafama.FastMath; import net.jafama.FastMath;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import static com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.Interpolator.lerp; import static com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.Interpolator.lerp;
public class LazilyEvaluatedInterpolator { public class LazilyEvaluatedInterpolator {
private final Double[][][] samples; private final Double[] samples; //
private final NoiseSampler[][] samplers;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
@@ -21,16 +19,22 @@ public class LazilyEvaluatedInterpolator {
private final int verticalRes; private final int verticalRes;
private final BiomeProvider biomeProvider; private final BiomeProvider biomeProvider;
private final PropertyKey<BiomeNoiseProperties> noisePropertiesKey;
private final long seed; private final long seed;
private final int min; private final int min, max;
public LazilyEvaluatedInterpolator(BiomeProvider biomeProvider, int cx, int cz, int max, int min, int horizontalRes, int verticalRes, private final int zMul, yMul;
public LazilyEvaluatedInterpolator(BiomeProvider biomeProvider, int cx, int cz, int max,
PropertyKey<BiomeNoiseProperties> noisePropertiesKey, int min, int horizontalRes, int verticalRes,
long seed) { long seed) {
this.noisePropertiesKey = noisePropertiesKey;
int hSamples = FastMath.ceilToInt(16.0 / horizontalRes); int hSamples = FastMath.ceilToInt(16.0 / horizontalRes);
int vSamples = FastMath.ceilToInt((double) (max - min) / verticalRes); int vSamples = FastMath.ceilToInt((double) (max - min) / verticalRes);
samples = new Double[hSamples + 1][vSamples + 1][hSamples + 1]; this.zMul = (hSamples + 1);
samplers = new NoiseSampler[hSamples + 1][hSamples + 1]; this.yMul = zMul * zMul;
samples = new Double[yMul * (vSamples + 1)];
this.chunkX = cx << 4; this.chunkX = cx << 4;
this.chunkZ = cz << 4; this.chunkZ = cz << 4;
this.horizontalRes = horizontalRes; this.horizontalRes = horizontalRes;
@@ -38,22 +42,25 @@ public class LazilyEvaluatedInterpolator {
this.biomeProvider = biomeProvider; this.biomeProvider = biomeProvider;
this.seed = seed; this.seed = seed;
this.min = min; this.min = min;
this.max = max - 1;
} }
private double sample(int x, int y, int z, int ox, int oy, int oz) { private double sample(int xIndex, int yIndex, int zIndex, int ox, int oy, int oz) {
Double sample = samples[x][y][z]; int index = xIndex + (zIndex * zMul) + (yIndex * yMul);
Double sample = samples[index];
if(sample == null) { if(sample == null) {
int xi = ox + chunkX; int xi = ox + chunkX;
int zi = oz + chunkZ; int zi = oz + chunkZ;
NoiseSampler sampler = samplers[x][z]; int y = FastMath.min(max, oy);
if(sampler == null) {
sampler = biomeProvider.getBiome(xi, y, zi, seed).getContext().get(BiomeNoiseProperties.class).carving();
samplers[x][z] = sampler;
}
sample = sampler.noise(seed, xi, oy, zi); sample = biomeProvider
samples[x][y][z] = sample; .getBiome(xi, y, zi, seed)
.getContext()
.get(noisePropertiesKey)
.carving()
.noise(seed, xi, y, zi);
samples[index] = sample;
} }
return sample; return sample;
} }
@@ -9,8 +9,10 @@ package com.dfsek.terra.addons.chunkgenerator.generation.math.samplers;
import net.jafama.FastMath; import net.jafama.FastMath;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.ChunkInterpolator; import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.ChunkInterpolator;
import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.ElevationInterpolator; import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.ElevationInterpolator;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@@ -18,10 +20,11 @@ public class Sampler3D {
private final ChunkInterpolator interpolator; private final ChunkInterpolator interpolator;
private final ElevationInterpolator elevationInterpolator; private final ElevationInterpolator elevationInterpolator;
public Sampler3D(int x, int z, long seed, int minHeight, int maxHeight, BiomeProvider provider, int elevationSmooth) { public Sampler3D(int x, int z, long seed, int minHeight, int maxHeight, BiomeProvider provider, int elevationSmooth,
PropertyKey<BiomeNoiseProperties> noisePropertiesKey, int maxBlend) {
this.interpolator = new ChunkInterpolator(seed, x, z, provider, this.interpolator = new ChunkInterpolator(seed, x, z, provider,
minHeight, maxHeight); minHeight, maxHeight, noisePropertiesKey, maxBlend);
this.elevationInterpolator = new ElevationInterpolator(seed, x, z, provider, elevationSmooth); this.elevationInterpolator = new ElevationInterpolator(seed, x, z, provider, elevationSmooth, noisePropertiesKey);
} }
public double sample(double x, double y, double z) { public double sample(double x, double y, double z) {
@@ -17,13 +17,13 @@
package com.dfsek.terra.addons.chunkgenerator.generation.math.samplers; package com.dfsek.terra.addons.chunkgenerator.generation.math.samplers;
import com.google.common.cache.Cache; import com.github.benmanes.caffeine.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.github.benmanes.caffeine.cache.Caffeine;
import net.jafama.FastMath; import net.jafama.FastMath;
import java.util.concurrent.ExecutionException; import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties; import com.dfsek.terra.api.world.info.WorldProperties;
@@ -31,10 +31,17 @@ import com.dfsek.terra.api.world.info.WorldProperties;
public class SamplerProvider { public class SamplerProvider {
private final Cache<WorldContext, Sampler3D> cache; private final Cache<WorldContext, Sampler3D> cache;
private final int elevationSmooth; private final int elevationSmooth;
private final PropertyKey<BiomeNoiseProperties> noisePropertiesKey;
private final int maxBlend;
public SamplerProvider(Platform platform, int elevationSmooth) { public SamplerProvider(Platform platform, int elevationSmooth, PropertyKey<BiomeNoiseProperties> noisePropertiesKey, int maxBlend) {
cache = Caffeine
.newBuilder()
.maximumSize(platform.getTerraConfig().getSamplerCache())
.build();
this.elevationSmooth = elevationSmooth; this.elevationSmooth = elevationSmooth;
cache = CacheBuilder.newBuilder().maximumSize(platform.getTerraConfig().getSamplerCache()).build(); this.noisePropertiesKey = noisePropertiesKey;
this.maxBlend = maxBlend;
} }
public Sampler3D get(int x, int z, WorldProperties world, BiomeProvider provider) { public Sampler3D get(int x, int z, WorldProperties world, BiomeProvider provider) {
@@ -45,13 +52,8 @@ public class SamplerProvider {
public Sampler3D getChunk(int cx, int cz, WorldProperties world, BiomeProvider provider) { public Sampler3D getChunk(int cx, int cz, WorldProperties world, BiomeProvider provider) {
WorldContext context = new WorldContext(cx, cz, world.getSeed(), world.getMinHeight(), world.getMaxHeight()); WorldContext context = new WorldContext(cx, cz, world.getSeed(), world.getMinHeight(), world.getMaxHeight());
try { return cache.get(context, c -> new Sampler3D(c.cx, c.cz, c.seed, c.minHeight, c.maxHeight, provider,
return cache.get(context, elevationSmooth, noisePropertiesKey, maxBlend));
() -> new Sampler3D(context.cx, context.cz, context.seed, context.minHeight, context.maxHeight, provider,
elevationSmooth));
} catch(ExecutionException e) {
throw new RuntimeException(e);
}
} }
private record WorldContext(int cx, int cz, long seed, int minHeight, int maxHeight) { private record WorldContext(int cx, int cz, long seed, int minHeight, int maxHeight) {
@@ -1,4 +1,4 @@
version = version("1.0.0") version = version("1.1.0")
dependencies { dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader")) compileOnlyApi(project(":common:addons:manifest-addon-loader"))
@@ -11,6 +11,7 @@ import com.dfsek.terra.addons.feature.locator.patterns.Pattern;
import com.dfsek.terra.api.structure.feature.BinaryColumn; import com.dfsek.terra.api.structure.feature.BinaryColumn;
import com.dfsek.terra.api.structure.feature.Locator; import com.dfsek.terra.api.structure.feature.Locator;
import com.dfsek.terra.api.util.Range; import com.dfsek.terra.api.util.Range;
import com.dfsek.terra.api.world.WritableWorld;
import com.dfsek.terra.api.world.chunk.generation.util.Column; import com.dfsek.terra.api.world.chunk.generation.util.Column;
@@ -31,16 +32,19 @@ public class AdjacentPatternLocator implements Locator {
} }
private boolean isValid(int y, Column<?> column) { private boolean isValid(int y, Column<?> column) {
WritableWorld world = column.getWorld();
int x = column.getX();
int z = column.getZ();
if(matchAll) { if(matchAll) {
return pattern.matches(y, column.adjacent(0, -1)) && return pattern.matches(world, x, y, z - 1) &&
pattern.matches(y, column.adjacent(0, 1)) && pattern.matches(world, x, y, z + 1) &&
pattern.matches(y, column.adjacent(-1, 0)) && pattern.matches(world, x - 1, y, z) &&
pattern.matches(y, column.adjacent(1, 0)); pattern.matches(world, x + 1, y, z);
} else { } else {
return pattern.matches(y, column.adjacent(0, -1)) || return pattern.matches(world, x, y, z - 1) ||
pattern.matches(y, column.adjacent(0, 1)) || pattern.matches(world, x, y, z + 1) ||
pattern.matches(y, column.adjacent(-1, 0)) || pattern.matches(world, x - 1, y, z) ||
pattern.matches(y, column.adjacent(1, 0)); pattern.matches(world, x + 1, y, z);
} }
} }
} }
@@ -7,6 +7,8 @@
package com.dfsek.terra.addons.feature.locator.locators; package com.dfsek.terra.addons.feature.locator.locators;
import net.jafama.FastMath;
import com.dfsek.terra.addons.feature.locator.patterns.Pattern; import com.dfsek.terra.addons.feature.locator.patterns.Pattern;
import com.dfsek.terra.api.structure.feature.BinaryColumn; import com.dfsek.terra.api.structure.feature.BinaryColumn;
import com.dfsek.terra.api.structure.feature.Locator; import com.dfsek.terra.api.structure.feature.Locator;
@@ -25,6 +27,9 @@ public class PatternLocator implements Locator {
@Override @Override
public BinaryColumn getSuitableCoordinates(Column<?> column) { public BinaryColumn getSuitableCoordinates(Column<?> column) {
return new BinaryColumn(search, y -> pattern.matches(y, column)); int min = FastMath.max(column.getMinY(), search.getMin());
int max = FastMath.min(column.getMaxY(), search.getMax());
if(min >= max) return BinaryColumn.getNull();
return new BinaryColumn(min, max, y -> pattern.matches(y, column));
} }
} }
@@ -23,6 +23,19 @@ public class SamplerLocator implements Locator {
this.samplers = samplers; this.samplers = samplers;
} }
private static int floorToInt(double value) {
int valueInt = (int) value;
if(value < 0.0) {
if(value == (double) valueInt) {
return valueInt;
} else {
return valueInt == Integer.MIN_VALUE ? valueInt : valueInt - 1;
}
} else {
return valueInt;
}
}
@Override @Override
public BinaryColumn getSuitableCoordinates(Column<?> column) { public BinaryColumn getSuitableCoordinates(Column<?> column) {
BinaryColumnBuilder results = column.newBinaryColumn(); BinaryColumnBuilder results = column.newBinaryColumn();
@@ -36,17 +49,4 @@ public class SamplerLocator implements Locator {
return results.build(); return results.build();
} }
private static int floorToInt(double value) {
int valueInt = (int)value;
if (value < 0.0) {
if (value == (double)valueInt) {
return valueInt;
} else {
return valueInt == Integer.MIN_VALUE ? valueInt : valueInt - 1;
}
} else {
return valueInt;
}
}
} }
@@ -7,6 +7,8 @@
package com.dfsek.terra.addons.feature.locator.locators; package com.dfsek.terra.addons.feature.locator.locators;
import net.jafama.FastMath;
import com.dfsek.terra.api.structure.feature.BinaryColumn; import com.dfsek.terra.api.structure.feature.BinaryColumn;
import com.dfsek.terra.api.structure.feature.Locator; import com.dfsek.terra.api.structure.feature.Locator;
import com.dfsek.terra.api.util.Range; import com.dfsek.terra.api.util.Range;
@@ -24,7 +26,10 @@ public class SurfaceLocator implements Locator {
@Override @Override
public BinaryColumn getSuitableCoordinates(Column<?> column) { public BinaryColumn getSuitableCoordinates(Column<?> column) {
BinaryColumnBuilder builder = column.newBinaryColumn(); BinaryColumnBuilder builder = column.newBinaryColumn();
for(int y : search) { int max = FastMath.min(search.getMax(), column.getMaxY());
int min = FastMath.max(search.getMin(), column.getMinY());
if(min >= max) return builder.build();
for(int y = min; y < max; y++) {
if(column.getBlock(y).isAir() && !column.getBlock(y - 1).isAir()) { if(column.getBlock(y).isAir() && !column.getBlock(y - 1).isAir()) {
builder.set(y); builder.set(y);
} }
@@ -7,10 +7,13 @@
package com.dfsek.terra.addons.feature.locator.patterns; package com.dfsek.terra.addons.feature.locator.patterns;
import net.jafama.FastMath;
import java.util.function.Predicate; import java.util.function.Predicate;
import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.util.Range; import com.dfsek.terra.api.util.Range;
import com.dfsek.terra.api.world.WritableWorld;
import com.dfsek.terra.api.world.chunk.generation.util.Column; import com.dfsek.terra.api.world.chunk.generation.util.Column;
@@ -25,8 +28,22 @@ public class MatchPattern implements Pattern {
@Override @Override
public boolean matches(int y, Column<?> column) { public boolean matches(int y, Column<?> column) {
for(int i : range) { int min = FastMath.max(column.getMinY(), range.getMin() + y);
if(!matches.test(column.getBlock(y + i))) return false; int max = FastMath.min(column.getMaxY(), range.getMax() + y);
if(max <= min) return false;
for(int i = min; i < max; i++) {
if(!matches.test(column.getBlock(i))) return false;
}
return true;
}
@Override
public boolean matches(WritableWorld world, int x, int y, int z) {
int min = FastMath.max(world.getMinHeight(), range.getMin() + y);
int max = FastMath.min(world.getMaxHeight(), range.getMax() + y);
if(max <= min) return false;
for(int i = min; i < max; i++) {
if(!matches.test(world.getBlockState(x, i, z))) return false;
} }
return true; return true;
} }
@@ -7,12 +7,18 @@
package com.dfsek.terra.addons.feature.locator.patterns; package com.dfsek.terra.addons.feature.locator.patterns;
import com.dfsek.terra.api.world.WritableWorld;
import com.dfsek.terra.api.world.chunk.generation.util.Column; import com.dfsek.terra.api.world.chunk.generation.util.Column;
public interface Pattern { public interface Pattern {
boolean matches(int y, Column<?> column); boolean matches(int y, Column<?> column);
default boolean matches(WritableWorld world, int x, int y, int z) {
return matches(y, world.column(x, z));
}
default Pattern and(Pattern that) { default Pattern and(Pattern that) {
return (y, column) -> this.matches(y, column) && that.matches(y, column); return (y, column) -> this.matches(y, column) && that.matches(y, column);
} }
@@ -23,11 +23,10 @@ import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate;
public class UserDefinedFunction implements DynamicFunction { public class UserDefinedFunction implements DynamicFunction {
private static final Map<FunctionTemplate, UserDefinedFunction> CACHE = new HashMap<>();
private final Expression expression; private final Expression expression;
private final int args; private final int args;
private static final Map<FunctionTemplate, UserDefinedFunction> CACHE = new HashMap<>();
protected UserDefinedFunction(Expression expression, int args) { protected UserDefinedFunction(Expression expression, int args) {
this.expression = expression; this.expression = expression;
this.args = args; this.args = args;
@@ -38,17 +37,17 @@ public class UserDefinedFunction implements DynamicFunction {
if(function == null) { if(function == null) {
Parser parser = new Parser(); Parser parser = new Parser();
Scope parent = new Scope(); Scope parent = new Scope();
Scope functionScope = new Scope().withParent(parent); Scope functionScope = new Scope().withParent(parent);
template.getArgs().forEach(functionScope::addInvocationVariable); template.getArgs().forEach(functionScope::addInvocationVariable);
for(Entry<String, FunctionTemplate> entry : template.getFunctions().entrySet()) { for(Entry<String, FunctionTemplate> entry : template.getFunctions().entrySet()) {
String id = entry.getKey(); String id = entry.getKey();
FunctionTemplate nest = entry.getValue(); FunctionTemplate nest = entry.getValue();
parser.registerFunction(id, newInstance(nest)); parser.registerFunction(id, newInstance(nest));
} }
function = new UserDefinedFunction(parser.parse(template.getFunction(), functionScope), template.getArgs().size()); function = new UserDefinedFunction(parser.parse(template.getFunction(), functionScope), template.getArgs().size());
CACHE.put(template, function); CACHE.put(template, function);
} }
@@ -12,20 +12,21 @@ import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection; import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class PaletteLayerLoader implements ObjectTemplate<PaletteLayerHolder> { public class PaletteLayerLoader implements ObjectTemplate<PaletteLayerHolder> {
@Value("materials") @Value("materials")
private ProbabilityCollection<BlockState> collection; private @Meta ProbabilityCollection<@Meta BlockState> collection;
@Value("sampler") @Value("sampler")
@Default @Default
private NoiseSampler sampler = null; private @Meta NoiseSampler sampler = null;
@Value("layers") @Value("layers")
private int layers; private @Meta int layers;
@Override @Override
public PaletteLayerHolder get() { public PaletteLayerHolder get() {
@@ -1,4 +1,4 @@
version = version("1.0.0") version = version("1.1.0")
dependencies { dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader")) compileOnlyApi(project(":common:addons:manifest-addon-loader"))
@@ -28,6 +28,8 @@ import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler; import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject; import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.properties.Context;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.structure.feature.Feature; import com.dfsek.terra.api.structure.feature.Feature;
import com.dfsek.terra.api.util.reflection.TypeKey; import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
@@ -49,12 +51,13 @@ public class FeatureGenerationAddon implements AddonInitializer {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public void initialize() { public void initialize() {
PropertyKey<BiomeFeatures> biomeFeaturesKey = Context.create(BiomeFeatures.class);
platform.getEventManager() platform.getEventManager()
.getHandler(FunctionalEventHandler.class) .getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class) .register(addon, ConfigPackPreLoadEvent.class)
.then(event -> event.getPack() .then(event -> event.getPack()
.getOrCreateRegistry(STAGE_TYPE_KEY) .getOrCreateRegistry(STAGE_TYPE_KEY)
.register(addon.key("FEATURE"), () -> new FeatureStageTemplate(platform))) .register(addon.key("FEATURE"), () -> new FeatureStageTemplate(platform, biomeFeaturesKey)))
.failThrough(); .failThrough();
platform.getEventManager() platform.getEventManager()
@@ -84,7 +87,7 @@ public class FeatureGenerationAddon implements AddonInitializer {
featureGenerationStages.forEach(stage -> features.put(stage, template.get(stage.getID(), List.class))); featureGenerationStages.forEach(stage -> features.put(stage, template.get(stage.getID(), List.class)));
event.getLoadedObject(Biome.class).getContext().put(new BiomeFeatures(features)); event.getLoadedObject(Biome.class).getContext().put(biomeFeaturesKey, new BiomeFeatures(features));
} }
}) })
.failThrough(); .failThrough();
@@ -12,6 +12,7 @@ import java.util.Random;
import com.dfsek.terra.addons.generation.feature.config.BiomeFeatures; import com.dfsek.terra.addons.generation.feature.config.BiomeFeatures;
import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.registry.key.StringIdentifiable; import com.dfsek.terra.api.registry.key.StringIdentifiable;
import com.dfsek.terra.api.util.Rotation; import com.dfsek.terra.api.util.Rotation;
import com.dfsek.terra.api.util.vector.Vector3Int; import com.dfsek.terra.api.util.vector.Vector3Int;
@@ -28,10 +29,15 @@ public class FeatureGenerationStage implements GenerationStage, StringIdentifiab
private final String profile; private final String profile;
public FeatureGenerationStage(Platform platform, String id) { private final int resolution;
private final PropertyKey<BiomeFeatures> biomeFeaturesKey;
public FeatureGenerationStage(Platform platform, String id, int resolution, PropertyKey<BiomeFeatures> biomeFeaturesKey) {
this.platform = platform; this.platform = platform;
this.id = id; this.id = id;
this.profile = "feature_stage:" + id; this.profile = "feature_stage:" + id;
this.resolution = resolution;
this.biomeFeaturesKey = biomeFeaturesKey;
} }
@Override @Override
@@ -41,33 +47,39 @@ public class FeatureGenerationStage implements GenerationStage, StringIdentifiab
int cx = world.centerChunkX() << 4; int cx = world.centerChunkX() << 4;
int cz = world.centerChunkZ() << 4; int cz = world.centerChunkZ() << 4;
long seed = world.getSeed(); long seed = world.getSeed();
for(int x = 0; x < 16; x++) { for(int chunkX = 0; chunkX < 16; chunkX += resolution) {
for(int z = 0; z < 16; z++) { for(int chunkZ = 0; chunkZ < 16; chunkZ += resolution) {
int tx = cx + x; int tx = cx + chunkX;
int tz = cz + z; int tz = cz + chunkZ;
Column<WritableWorld> column = world.column(tx, tz);
long coordinateSeed = (seed * 31 + tx) * 31 + tz;
world.getBiomeProvider() world.getBiomeProvider()
.getBiome(tx, 0, tz, seed) .getColumn(tx, tz, world)
.getContext() .forRanges(resolution, (min, max, biome) -> {
.get(BiomeFeatures.class) for(int subChunkX = 0; subChunkX < resolution; subChunkX++) {
.getFeatures() for(int subChunkZ = 0; subChunkZ < resolution; subChunkZ++) {
.getOrDefault(this, Collections.emptyList()) int x = subChunkX + tx;
.forEach(feature -> { int z = subChunkZ + tz;
platform.getProfiler().push(feature.getID()); long coordinateSeed = (seed * 31 + x) * 31 + z;
if(feature.getDistributor().matches(tx, tz, seed)) { Column<WritableWorld> column = world.column(x, z);
feature.getLocator() biome.getContext()
.getSuitableCoordinates(column) .get(biomeFeaturesKey)
.forEach(y -> .getFeatures()
feature.getStructure(world, tx, y, tz) .getOrDefault(this, Collections.emptyList())
.generate(Vector3Int.of(tx, y, tz), .forEach(feature -> {
world, platform.getProfiler().push(feature.getID());
new Random(coordinateSeed * 31 + y), if(feature.getDistributor().matches(x, z, seed)) {
Rotation.NONE) feature.getLocator()
); .getSuitableCoordinates(column.clamp(min, max))
.forEach(y -> feature.getStructure(world, x, y, z)
.generate(Vector3Int.of(x, y, z),
world,
new Random(coordinateSeed * 31 + y),
Rotation.NONE)
);
}
platform.getProfiler().pop(feature.getID());
});
}
} }
platform.getProfiler().pop(feature.getID());
}); });
} }
} }
@@ -1,23 +1,46 @@
package com.dfsek.terra.addons.generation.feature.config; package com.dfsek.terra.addons.generation.feature.config;
import com.dfsek.tectonic.api.config.template.ValidatedConfigTemplate;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value; import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.tectonic.api.exception.ValidationException;
import com.dfsek.terra.addons.generation.feature.FeatureGenerationStage; import com.dfsek.terra.addons.generation.feature.FeatureGenerationStage;
import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.properties.PropertyKey;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage; import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
public class FeatureStageTemplate implements ObjectTemplate<GenerationStage> { public class FeatureStageTemplate implements ObjectTemplate<GenerationStage>, ValidatedConfigTemplate {
private final Platform platform; private final Platform platform;
private final PropertyKey<BiomeFeatures> biomeFeaturesKey;
@Value("id") @Value("id")
private String id; private String id;
public FeatureStageTemplate(Platform platform) { this.platform = platform; } @Value("resolution")
@Default
private int resolution = 4;
public FeatureStageTemplate(Platform platform, PropertyKey<BiomeFeatures> biomeFeaturesKey) {
this.platform = platform;
this.biomeFeaturesKey = biomeFeaturesKey;
}
@Override @Override
public FeatureGenerationStage get() { public FeatureGenerationStage get() {
return new FeatureGenerationStage(platform, id); return new FeatureGenerationStage(platform, id, resolution, biomeFeaturesKey);
}
@Override
public boolean validate() throws ValidationException {
if(!(resolution == 1
|| resolution == 2
|| resolution == 4
|| resolution == 8
|| resolution == 16)) throw new ValidationException(
"Resolution must be power of 2 less than or equal to 16 (1, 2, 4, 8, 16), got: " + resolution);
return true;
} }
} }
@@ -2,10 +2,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
version = version("1.0.0") version = version("1.0.0")
repositories {
maven { url = uri("https://jitpack.io/") }
}
dependencies { dependencies {
api("commons-io:commons-io:2.7") api("commons-io:commons-io:2.7")
api("com.github.Querz:NBT:6.1") api("com.github.Querz:NBT:6.1")
@@ -1,6 +1,6 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
version = version("1.0.1") version = version("1.1.0")
dependencies { dependencies {
api("commons-io:commons-io:2.7") api("commons-io:commons-io:2.7")
@@ -15,9 +15,12 @@ import java.util.Map;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException; import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.Block; import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.Executable;
import com.dfsek.terra.addons.terrascript.parser.lang.Item; import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword; import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable.ReturnType;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope.ScopeBuilder;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.BooleanConstant; import com.dfsek.terra.addons.terrascript.parser.lang.constants.BooleanConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.ConstantExpression; import com.dfsek.terra.addons.terrascript.parser.lang.constants.ConstantExpression;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.NumericConstant; import com.dfsek.terra.addons.terrascript.parser.lang.constants.NumericConstant;
@@ -48,12 +51,17 @@ import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.Grea
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanOrEqualsStatement; import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanOrEqualsStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanStatement; import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.NotEqualsStatement; import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.NotEqualsStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.VariableAssignmentNode; import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.BoolAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.VariableDeclarationNode; import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.NumAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.VariableReferenceNode; import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.StrAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.VariableAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.BoolVariableReferenceNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.NumVariableReferenceNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.StrVariableReferenceNode;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.addons.terrascript.tokenizer.Token; import com.dfsek.terra.addons.terrascript.tokenizer.Token;
import com.dfsek.terra.addons.terrascript.tokenizer.Tokenizer; import com.dfsek.terra.addons.terrascript.tokenizer.Tokenizer;
import com.dfsek.terra.api.util.generic.pair.Pair;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -83,11 +91,12 @@ public class Parser {
* *
* @throws ParseException If parsing fails. * @throws ParseException If parsing fails.
*/ */
public Block parse() { public Executable parse() {
return parseBlock(new Tokenizer(data), new HashMap<>(), false); ScopeBuilder scopeBuilder = new ScopeBuilder();
return new Executable(parseBlock(new Tokenizer(data), false, scopeBuilder), scopeBuilder);
} }
private Keyword<?> parseLoopLike(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, boolean loop) throws ParseException { private Keyword<?> parseLoopLike(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) throws ParseException {
Token identifier = tokens.consume(); Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP); ParserUtil.checkType(identifier, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP);
@@ -95,43 +104,43 @@ public class Parser {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
return switch(identifier.getType()) { return switch(identifier.getType()) {
case FOR_LOOP -> parseForLoop(tokens, variableMap, identifier.getPosition()); case FOR_LOOP -> parseForLoop(tokens, identifier.getPosition(), scopeBuilder);
case IF_STATEMENT -> parseIfStatement(tokens, variableMap, identifier.getPosition(), loop); case IF_STATEMENT -> parseIfStatement(tokens, identifier.getPosition(), loop, scopeBuilder);
case WHILE_LOOP -> parseWhileLoop(tokens, variableMap, identifier.getPosition()); case WHILE_LOOP -> parseWhileLoop(tokens, identifier.getPosition(), scopeBuilder);
default -> throw new UnsupportedOperationException( default -> throw new UnsupportedOperationException(
"Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition()); "Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition());
}; };
} }
private WhileKeyword parseWhileLoop(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, Position start) { private WhileKeyword parseWhileLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
Returnable<?> first = parseExpression(tokens, true, variableMap); Returnable<?> first = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN); ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
return new WhileKeyword(parseStatementBlock(tokens, variableMap, true), (Returnable<Boolean>) first, start); // While loop return new WhileKeyword(parseStatementBlock(tokens, true, scopeBuilder), (Returnable<Boolean>) first, start); // While loop
} }
private IfKeyword parseIfStatement(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, Position start, boolean loop) { private IfKeyword parseIfStatement(Tokenizer tokens, Position start, boolean loop, ScopeBuilder scopeBuilder) {
Returnable<?> condition = parseExpression(tokens, true, variableMap); Returnable<?> condition = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(condition, Returnable.ReturnType.BOOLEAN); ParserUtil.checkReturnType(condition, Returnable.ReturnType.BOOLEAN);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
Block elseBlock = null; Block elseBlock = null;
Block statement = parseStatementBlock(tokens, variableMap, loop); Block statement = parseStatementBlock(tokens, loop, scopeBuilder);
List<IfKeyword.Pair<Returnable<Boolean>, Block>> elseIf = new ArrayList<>(); List<Pair<Returnable<Boolean>, Block>> elseIf = new ArrayList<>();
while(tokens.hasNext() && tokens.get().getType().equals(Token.Type.ELSE)) { while(tokens.hasNext() && tokens.get().getType().equals(Token.Type.ELSE)) {
tokens.consume(); // Consume else. tokens.consume(); // Consume else.
if(tokens.get().getType().equals(Token.Type.IF_STATEMENT)) { if(tokens.get().getType().equals(Token.Type.IF_STATEMENT)) {
tokens.consume(); // Consume if. tokens.consume(); // Consume if.
Returnable<?> elseCondition = parseExpression(tokens, true, variableMap); Returnable<?> elseCondition = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(elseCondition, Returnable.ReturnType.BOOLEAN); ParserUtil.checkReturnType(elseCondition, Returnable.ReturnType.BOOLEAN);
elseIf.add(new IfKeyword.Pair<>((Returnable<Boolean>) elseCondition, parseStatementBlock(tokens, variableMap, loop))); elseIf.add(Pair.of((Returnable<Boolean>) elseCondition, parseStatementBlock(tokens, loop, scopeBuilder)));
} else { } else {
elseBlock = parseStatementBlock(tokens, variableMap, loop); elseBlock = parseStatementBlock(tokens, loop, scopeBuilder);
break; // Else must be last. break; // Else must be last.
} }
} }
@@ -139,51 +148,51 @@ public class Parser {
return new IfKeyword(statement, (Returnable<Boolean>) condition, elseIf, elseBlock, start); // If statement return new IfKeyword(statement, (Returnable<Boolean>) condition, elseIf, elseBlock, start); // If statement
} }
private Block parseStatementBlock(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, boolean loop) { private Block parseStatementBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
if(tokens.get().getType().equals(Token.Type.BLOCK_BEGIN)) { if(tokens.get().getType().equals(Token.Type.BLOCK_BEGIN)) {
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN); ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN);
Block block = parseBlock(tokens, variableMap, loop); Block block = parseBlock(tokens, loop, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END); ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END);
return block; return block;
} else { } else {
Position position = tokens.get().getPosition(); Position position = tokens.get().getPosition();
Block block = new Block(Collections.singletonList(parseItem(tokens, variableMap, loop)), position); Block block = new Block(Collections.singletonList(parseItem(tokens, loop, scopeBuilder)), position);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
return block; return block;
} }
} }
private ForKeyword parseForLoop(Tokenizer tokens, Map<String, Returnable.ReturnType> old, Position start) { private ForKeyword parseForLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
Map<String, Returnable.ReturnType> variableMap = new HashMap<>(old); // New scope scopeBuilder = scopeBuilder.sub(); // new scope
Token f = tokens.get(); Token f = tokens.get();
ParserUtil.checkType(f, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.IDENTIFIER); ParserUtil.checkType(f, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.IDENTIFIER);
Item<?> initializer; Item<?> initializer;
if(f.isVariableDeclaration()) { if(f.isVariableDeclaration()) {
VariableDeclarationNode<?> forVar = parseVariableDeclaration(tokens, variableMap); VariableAssignmentNode<?> forVar = parseVariableDeclaration(tokens, scopeBuilder);
Token name = tokens.get(); Token name = tokens.get();
if(functions.containsKey(name.getContent()) || variableMap.containsKey(name.getContent())) if(functions.containsKey(name.getContent()) || scopeBuilder.contains(name.getContent()))
throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition()); throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition());
initializer = forVar; initializer = forVar;
} else initializer = parseExpression(tokens, true, variableMap); } else initializer = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
Returnable<?> conditional = parseExpression(tokens, true, variableMap); Returnable<?> conditional = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN); ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
Item<?> incrementer; Item<?> incrementer;
Token token = tokens.get(); Token token = tokens.get();
if(variableMap.containsKey(token.getContent())) { // Assume variable assignment if(scopeBuilder.contains(token.getContent())) { // Assume variable assignment
incrementer = parseAssignment(tokens, variableMap); incrementer = parseAssignment(tokens, scopeBuilder);
} else incrementer = parseFunction(tokens, true, variableMap); } else incrementer = parseFunction(tokens, true, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
return new ForKeyword(parseStatementBlock(tokens, variableMap, true), initializer, (Returnable<Boolean>) conditional, incrementer, return new ForKeyword(parseStatementBlock(tokens, true, scopeBuilder), initializer, (Returnable<Boolean>) conditional, incrementer,
start); start);
} }
private Returnable<?> parseExpression(Tokenizer tokens, boolean full, Map<String, Returnable.ReturnType> variableMap) { private Returnable<?> parseExpression(Tokenizer tokens, boolean full, ScopeBuilder scopeBuilder) {
boolean booleanInverted = false; // Check for boolean not operator boolean booleanInverted = false; // Check for boolean not operator
boolean negate = false; boolean negate = false;
if(tokens.get().getType().equals(Token.Type.BOOLEAN_NOT)) { if(tokens.get().getType().equals(Token.Type.BOOLEAN_NOT)) {
@@ -202,13 +211,21 @@ public class Parser {
if(id.isConstant()) { if(id.isConstant()) {
expression = parseConstantExpression(tokens); expression = parseConstantExpression(tokens);
} else if(id.getType().equals(Token.Type.GROUP_BEGIN)) { // Parse grouped expression } else if(id.getType().equals(Token.Type.GROUP_BEGIN)) { // Parse grouped expression
expression = parseGroup(tokens, variableMap); expression = parseGroup(tokens, scopeBuilder);
} else { } else {
if(functions.containsKey(id.getContent())) if(functions.containsKey(id.getContent()))
expression = parseFunction(tokens, false, variableMap); expression = parseFunction(tokens, false, scopeBuilder);
else if(variableMap.containsKey(id.getContent())) { else if(scopeBuilder.contains(id.getContent())) {
ParserUtil.checkType(tokens.consume(), Token.Type.IDENTIFIER); ParserUtil.checkType(tokens.consume(), Token.Type.IDENTIFIER);
expression = new VariableReferenceNode(id.getContent(), id.getPosition(), variableMap.get(id.getContent())); String varId = id.getContent();
ReturnType varType = scopeBuilder.getType(varId);
expression = switch(varType) {
case NUMBER -> new NumVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
case STRING -> new StrVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
case BOOLEAN -> new BoolVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
default -> throw new ParseException("Illegal type for variable reference: " + varType, id.getPosition());
};
} else throw new ParseException("Unexpected token \" " + id.getContent() + "\"", id.getPosition()); } else throw new ParseException("Unexpected token \" " + id.getContent() + "\"", id.getPosition());
} }
@@ -221,7 +238,7 @@ public class Parser {
} }
if(full && tokens.get().isBinaryOperator()) { // Parse binary operations if(full && tokens.get().isBinaryOperator()) { // Parse binary operations
return parseBinaryOperation(expression, tokens, variableMap); return parseBinaryOperation(expression, tokens, scopeBuilder);
} }
return expression; return expression;
} }
@@ -243,25 +260,25 @@ public class Parser {
} }
} }
private Returnable<?> parseGroup(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) { private Returnable<?> parseGroup(Tokenizer tokens, ScopeBuilder scopeBuilder) {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
Returnable<?> expression = parseExpression(tokens, true, variableMap); // Parse inside of group as a separate expression Returnable<?> expression = parseExpression(tokens, true, scopeBuilder); // Parse inside of group as a separate expression
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
return expression; return expression;
} }
private BinaryOperation<?, ?> parseBinaryOperation(Returnable<?> left, Tokenizer tokens, private BinaryOperation<?, ?> parseBinaryOperation(Returnable<?> left, Tokenizer tokens,
Map<String, Returnable.ReturnType> variableMap) { ScopeBuilder scopeBuilder) {
Token binaryOperator = tokens.consume(); Token binaryOperator = tokens.consume();
ParserUtil.checkBinaryOperator(binaryOperator); ParserUtil.checkBinaryOperator(binaryOperator);
Returnable<?> right = parseExpression(tokens, false, variableMap); Returnable<?> right = parseExpression(tokens, false, scopeBuilder);
Token other = tokens.get(); Token other = tokens.get();
if(ParserUtil.hasPrecedence(binaryOperator.getType(), other.getType())) { if(ParserUtil.hasPrecedence(binaryOperator.getType(), other.getType())) {
return assemble(left, parseBinaryOperation(right, tokens, variableMap), binaryOperator); return assemble(left, parseBinaryOperation(right, tokens, scopeBuilder), binaryOperator);
} else if(other.isBinaryOperator()) { } else if(other.isBinaryOperator()) {
return parseBinaryOperation(assemble(left, right, binaryOperator), tokens, variableMap); return parseBinaryOperation(assemble(left, right, binaryOperator), tokens, scopeBuilder);
} }
return assemble(left, right, binaryOperator); return assemble(left, right, binaryOperator);
} }
@@ -306,7 +323,7 @@ public class Parser {
} }
} }
private VariableDeclarationNode<?> parseVariableDeclaration(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) { private VariableAssignmentNode<?> parseVariableDeclaration(Tokenizer tokens, ScopeBuilder scopeBuilder) {
Token type = tokens.consume(); Token type = tokens.consume();
ParserUtil.checkType(type, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE); ParserUtil.checkType(type, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE);
@@ -315,30 +332,34 @@ public class Parser {
ParserUtil.checkVarType(type, returnType); // Check for type mismatch ParserUtil.checkVarType(type, returnType); // Check for type mismatch
Token identifier = tokens.consume(); Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
if(functions.containsKey(identifier.getContent()) || variableMap.containsKey(identifier.getContent())) if(functions.containsKey(identifier.getContent()) || scopeBuilder.contains(identifier.getContent()))
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition()); throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT); ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT);
Returnable<?> value = parseExpression(tokens, true, variableMap); Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(value, returnType); ParserUtil.checkReturnType(value, returnType);
variableMap.put(identifier.getContent(), returnType); String id = identifier.getContent();
return new VariableDeclarationNode<>(tokens.get().getPosition(), identifier.getContent(), value, returnType); return switch(value.returnType()) {
case NUMBER -> new NumAssignmentNode((Returnable<Number>) value, identifier.getPosition(), scopeBuilder.num(id));
case STRING -> new StrAssignmentNode((Returnable<String>) value, identifier.getPosition(), scopeBuilder.str(id));
case BOOLEAN -> new BoolAssignmentNode((Returnable<Boolean>) value, identifier.getPosition(), scopeBuilder.bool(id));
default -> throw new ParseException("Illegal type for variable declaration: " + type, value.getPosition());
};
} }
private Block parseBlock(Tokenizer tokens, Map<String, Returnable.ReturnType> superVars, boolean loop) { private Block parseBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
List<Item<?>> parsedItems = new ArrayList<>(); List<Item<?>> parsedItems = new ArrayList<>();
Map<String, Returnable.ReturnType> parsedVariables = new HashMap<>( scopeBuilder = scopeBuilder.sub();
superVars); // New hashmap as to not mutate parent scope's declarations.
Token first = tokens.get(); Token first = tokens.get();
while(tokens.hasNext()) { while(tokens.hasNext()) {
Token token = tokens.get(); Token token = tokens.get();
if(token.getType().equals(Token.Type.BLOCK_END)) break; // Stop parsing at block end. if(token.getType().equals(Token.Type.BLOCK_END)) break; // Stop parsing at block end.
Item<?> parsedItem = parseItem(tokens, parsedVariables, loop); Item<?> parsedItem = parseItem(tokens, loop, scopeBuilder);
if(parsedItem != Function.NULL) { if(parsedItem != Function.NULL) {
parsedItems.add(parsedItem); parsedItems.add(parsedItem);
} }
@@ -347,7 +368,7 @@ public class Parser {
return new Block(parsedItems, first.getPosition()); return new Block(parsedItems, first.getPosition());
} }
private Item<?> parseItem(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, boolean loop) { private Item<?> parseItem(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
Token token = tokens.get(); Token token = tokens.get();
if(loop) ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP, if(loop) ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP,
Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE,
@@ -357,14 +378,14 @@ public class Parser {
Token.Type.FAIL); Token.Type.FAIL);
if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc) if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc)
return parseLoopLike(tokens, variableMap, loop); return parseLoopLike(tokens, loop, scopeBuilder);
} else if(token.isIdentifier()) { // Parse identifiers } else if(token.isIdentifier()) { // Parse identifiers
if(variableMap.containsKey(token.getContent())) { // Assume variable assignment if(scopeBuilder.contains(token.getContent())) { // Assume variable assignment
return parseAssignment(tokens, variableMap); return parseAssignment(tokens, scopeBuilder);
} else return parseFunction(tokens, true, variableMap); } else return parseFunction(tokens, true, scopeBuilder);
} else if(token.isVariableDeclaration()) { } else if(token.isVariableDeclaration()) {
return parseVariableDeclaration(tokens, variableMap); return parseVariableDeclaration(tokens, scopeBuilder);
} else if(token.getType().equals(Token.Type.RETURN)) return new ReturnKeyword(tokens.consume().getPosition()); } else if(token.getType().equals(Token.Type.RETURN)) return new ReturnKeyword(tokens.consume().getPosition());
else if(token.getType().equals(Token.Type.BREAK)) return new BreakKeyword(tokens.consume().getPosition()); else if(token.getType().equals(Token.Type.BREAK)) return new BreakKeyword(tokens.consume().getPosition());
@@ -373,21 +394,30 @@ public class Parser {
else throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition()); else throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition());
} }
private VariableAssignmentNode<?> parseAssignment(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) { private VariableAssignmentNode<?> parseAssignment(Tokenizer tokens, ScopeBuilder scopeBuilder) {
Token identifier = tokens.consume(); Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT); ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT);
Returnable<?> value = parseExpression(tokens, true, variableMap); Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(value, variableMap.get(identifier.getContent())); String id = identifier.getContent();
return new VariableAssignmentNode<>(value, identifier.getContent(), identifier.getPosition()); ParserUtil.checkReturnType(value, scopeBuilder.getType(id));
ReturnType type = value.returnType();
return switch(type) {
case NUMBER -> new NumAssignmentNode((Returnable<Number>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
case STRING -> new StrAssignmentNode((Returnable<String>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
case BOOLEAN -> new BoolAssignmentNode((Returnable<Boolean>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
default -> throw new ParseException("Illegal type for variable assignment: " + type, value.getPosition());
};
} }
private Function<?> parseFunction(Tokenizer tokens, boolean fullStatement, Map<String, Returnable.ReturnType> variableMap) { private Function<?> parseFunction(Tokenizer tokens, boolean fullStatement, ScopeBuilder scopeBuilder) {
Token identifier = tokens.consume(); Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier
@@ -397,7 +427,7 @@ public class Parser {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); // Second is body begin ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); // Second is body begin
List<Returnable<?>> args = getArgs(tokens, variableMap); // Extract arguments, consume the rest. List<Returnable<?>> args = getArgs(tokens, scopeBuilder); // Extract arguments, consume the rest.
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); // Remove body end ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); // Remove body end
@@ -425,11 +455,11 @@ public class Parser {
throw new UnsupportedOperationException("Unsupported function: " + identifier.getContent()); throw new UnsupportedOperationException("Unsupported function: " + identifier.getContent());
} }
private List<Returnable<?>> getArgs(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) { private List<Returnable<?>> getArgs(Tokenizer tokens, ScopeBuilder scopeBuilder) {
List<Returnable<?>> args = new ArrayList<>(); List<Returnable<?>> args = new ArrayList<>();
while(!tokens.get().getType().equals(Token.Type.GROUP_END)) { while(!tokens.get().getType().equals(Token.Type.GROUP_END)) {
args.add(parseExpression(tokens, true, variableMap)); args.add(parseExpression(tokens, true, scopeBuilder));
ParserUtil.checkType(tokens.get(), Token.Type.SEPARATOR, Token.Type.GROUP_END); ParserUtil.checkType(tokens.get(), Token.Type.SEPARATOR, Token.Type.GROUP_END);
if(tokens.get().getType().equals(Token.Type.SEPARATOR)) tokens.consume(); if(tokens.get().getType().equals(Token.Type.SEPARATOR)) tokens.consume();
} }
@@ -21,23 +21,8 @@ public class Block implements Item<Block.ReturnInfo<?>> {
this.position = position; this.position = position;
} }
public ReturnInfo<?> apply(ImplementationArguments implementationArguments) {
return apply(implementationArguments, new Scope());
}
@Override @Override
public ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) { public ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
Scope sub = scope.sub();
for(Item<?> item : items) {
Object result = item.apply(implementationArguments, sub);
if(result instanceof ReturnInfo<?> level) {
if(!level.getLevel().equals(ReturnLevel.NONE)) return level;
}
}
return new ReturnInfo<>(ReturnLevel.NONE, null);
}
public ReturnInfo<?> applyNoNewScope(ImplementationArguments implementationArguments, Scope scope) {
for(Item<?> item : items) { for(Item<?> item : items) {
Object result = item.apply(implementationArguments, scope); Object result = item.apply(implementationArguments, scope);
if(result instanceof ReturnInfo<?> level) { if(result instanceof ReturnInfo<?> level) {
@@ -52,10 +37,6 @@ public class Block implements Item<Block.ReturnInfo<?>> {
return position; return position;
} }
public List<Item<?>> getItems() {
return items;
}
public enum ReturnLevel { public enum ReturnLevel {
NONE(false), NONE(false),
BREAK(false), BREAK(false),
@@ -0,0 +1,19 @@
package com.dfsek.terra.addons.terrascript.parser.lang;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope.ScopeBuilder;
public class Executable {
private final Block script;
private final ThreadLocal<Scope> scope;
public Executable(Block script, ScopeBuilder scopeBuilder) {
this.script = script;
this.scope = ThreadLocal.withInitial(scopeBuilder::build);
}
public boolean execute(ImplementationArguments arguments) {
return script.apply(arguments, scope.get()).getLevel() != Block.ReturnLevel.FAIL;
}
}
@@ -13,5 +13,13 @@ import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public interface Item<T> { public interface Item<T> {
T apply(ImplementationArguments implementationArguments, Scope scope); T apply(ImplementationArguments implementationArguments, Scope scope);
default double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
throw new UnsupportedOperationException("Cannot apply " + this + " as double");
}
default boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
throw new UnsupportedOperationException("Cannot apply " + this + " as double");
}
Position getPosition(); Position getPosition();
} }
@@ -1,46 +1,138 @@
package com.dfsek.terra.addons.terrascript.parser.lang; package com.dfsek.terra.addons.terrascript.parser.lang;
import net.jafama.FastMath;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.Variable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable.ReturnType;
import com.dfsek.terra.api.util.generic.pair.Pair;
public class Scope { public class Scope {
private static final Scope NULL = new Scope() { private final double[] num;
@Override private final boolean[] bool;
public Variable<?> get(String id) { private final String[] str;
throw new IllegalStateException("Cannot get variable from null scope: " + id);
private Scope(int numSize, int boolSize, int strSize) {
this.num = new double[numSize];
this.bool = new boolean[boolSize];
this.str = new String[strSize];
}
public double getNum(int index) {
return num[index];
}
public boolean getBool(int index) {
return bool[index];
}
public String getStr(int index) {
return str[index];
}
public void setNum(int index, double value) {
num[index] = value;
}
public void setBool(int index, boolean value) {
bool[index] = value;
}
public void setStr(int index, String value) {
str[index] = value;
}
public static final class ScopeBuilder {
private final Map<String, Pair<Integer, ReturnType>> indices;
private int numSize, boolSize, strSize = 0;
private ScopeBuilder parent;
public ScopeBuilder() {
this.indices = new HashMap<>();
} }
@Override private ScopeBuilder(ScopeBuilder parent) {
public void put(String id, Variable<?> variable) { this.parent = parent;
throw new IllegalStateException("Cannot set variable in null scope: " + id); this.numSize = parent.numSize;
this.boolSize = parent.boolSize;
this.strSize = parent.strSize;
this.indices = new HashMap<>(parent.indices);
}
public Scope build() {
return new Scope(numSize, boolSize, strSize);
}
public ScopeBuilder sub() {
return new ScopeBuilder(this);
}
private String check(String id) {
if(indices.containsKey(id)) {
throw new IllegalArgumentException("Variable with ID " + id + " already registered.");
}
return id;
}
public int num(String id) {
int num = numSize;
indices.put(check(id), Pair.of(num, ReturnType.NUMBER));
numSize++;
updateNumSize(numSize);
return num;
}
public int str(String id) {
int str = strSize;
indices.put(check(id), Pair.of(str, ReturnType.STRING));
strSize++;
updateStrSize(strSize);
return str;
}
public int bool(String id) {
int bool = boolSize;
indices.put(check(id), Pair.of(bool, ReturnType.BOOLEAN));
boolSize++;
updateBoolSize(boolSize);
return bool;
}
private void updateBoolSize(int size) {
this.boolSize = FastMath.max(boolSize, size);
if(parent != null) {
parent.updateBoolSize(size);
}
}
private void updateNumSize(int size) {
this.numSize = FastMath.max(numSize, size);
if(parent != null) {
parent.updateNumSize(size);
}
}
private void updateStrSize(int size) {
this.strSize = FastMath.max(strSize, size);
if(parent != null) {
parent.updateStrSize(size);
}
}
public int getIndex(String id) {
return indices.get(id).getLeft();
}
public ReturnType getType(String id) {
return indices.get(id).getRight();
}
public boolean contains(String id) {
return indices.containsKey(id);
} }
};
private final Scope parent;
private final Map<String, Variable<?>> variableMap = new HashMap<>();
public Scope(Scope parent) {
this.parent = parent;
}
public Scope() {
this.parent = NULL;
}
public Variable<?> get(String id) {
Variable<?> var = variableMap.get(id);
return var == null ? parent.get(id) : var;
}
public void put(String id, Variable<?> variable) {
variableMap.put(id, variable);
}
public Scope sub() {
return new Scope(this);
} }
} }
@@ -7,12 +7,22 @@
package com.dfsek.terra.addons.terrascript.parser.lang.constants; package com.dfsek.terra.addons.terrascript.parser.lang.constants;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BooleanConstant extends ConstantExpression<Boolean> { public class BooleanConstant extends ConstantExpression<Boolean> {
private final boolean constant;
public BooleanConstant(Boolean constant, Position position) { public BooleanConstant(Boolean constant, Position position) {
super(constant, position); super(constant, position);
this.constant = constant;
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return constant;
} }
@Override @Override
@@ -7,13 +7,23 @@
package com.dfsek.terra.addons.terrascript.parser.lang.constants; package com.dfsek.terra.addons.terrascript.parser.lang.constants;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumericConstant extends ConstantExpression<Number> { public class NumericConstant extends ConstantExpression<Number> {
private final double constant;
public NumericConstant(Number constant, Position position) { public NumericConstant(Number constant, Position position) {
super(constant, position); super(constant, position);
this.constant = constant.doubleValue();
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return constant;
} }
@Override @Override
@@ -19,15 +19,25 @@ public interface Function<T> extends Returnable<T> {
public ReturnType returnType() { public ReturnType returnType() {
return null; return null;
} }
@Override @Override
public Object apply(ImplementationArguments implementationArguments, Scope scope) { public Object apply(ImplementationArguments implementationArguments, Scope scope) {
return null; return null;
} }
@Override @Override
public Position getPosition() { public Position getPosition() {
return null; return null;
} }
}; };
@Override
default double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return ((Number) apply(implementationArguments, scope)).doubleValue();
}
@Override
default boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return (Boolean) apply(implementationArguments, scope);
}
} }
@@ -33,11 +33,10 @@ public class ForKeyword implements Keyword<Block.ReturnInfo<?>> {
@Override @Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) { public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
Scope sub = scope.sub(); for(initializer.apply(implementationArguments, scope);
for(initializer.apply(implementationArguments, sub); statement.apply(implementationArguments, scope);
statement.apply(implementationArguments, sub); incrementer.apply(implementationArguments, scope)) {
incrementer.apply(implementationArguments, sub)) { Block.ReturnInfo<?> level = conditional.apply(implementationArguments, scope);
Block.ReturnInfo<?> level = conditional.applyNoNewScope(implementationArguments, sub);
if(level.getLevel().equals(Block.ReturnLevel.BREAK)) break; if(level.getLevel().equals(Block.ReturnLevel.BREAK)) break;
if(level.getLevel().isReturnFast()) return level; if(level.getLevel().isReturnFast()) return level;
} }
@@ -17,6 +17,7 @@ import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope; import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.util.generic.pair.Pair;
public class IfKeyword implements Keyword<Block.ReturnInfo<?>> { public class IfKeyword implements Keyword<Block.ReturnInfo<?>> {
@@ -58,23 +59,4 @@ public class IfKeyword implements Keyword<Block.ReturnInfo<?>> {
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.VOID; return ReturnType.VOID;
} }
public static class Pair<L, R> {
private final L left;
private final R right;
public Pair(L left, R right) {
this.left = left;
this.right = right;
}
public L getLeft() {
return left;
}
public R getRight() {
return right;
}
}
} }
@@ -7,17 +7,13 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class BinaryOperation<I, O> implements Returnable<O> { public abstract class BinaryOperation<I, O> implements Returnable<O> {
private final Returnable<I> left; protected final Returnable<I> left;
private final Returnable<I> right; protected final Returnable<I> right;
private final Position start; private final Position start;
public BinaryOperation(Returnable<I> left, Returnable<I> right, Position start) { public BinaryOperation(Returnable<I> left, Returnable<I> right, Position start) {
@@ -26,13 +22,6 @@ public abstract class BinaryOperation<I, O> implements Returnable<O> {
this.start = start; this.start = start;
} }
public abstract O apply(Supplier<I> left, Supplier<I> right);
@Override
public O apply(ImplementationArguments implementationArguments, Scope scope) {
return apply(() -> left.apply(implementationArguments, scope), () -> right.apply(implementationArguments, scope));
}
@Override @Override
public Position getPosition() { public Position getPosition() {
return start; return start;
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -18,13 +18,18 @@ public class BooleanAndOperation extends BinaryOperation<Boolean, Boolean> {
super(left, right, start); super(left, right, start);
} }
@Override
public Boolean apply(Supplier<Boolean> left, Supplier<Boolean> right) {
return left.get() && right.get();
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.BOOLEAN; return ReturnType.BOOLEAN;
} }
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyBoolean(implementationArguments, scope) && right.applyBoolean(implementationArguments, scope);
}
} }
@@ -7,7 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -17,8 +19,13 @@ public class BooleanNotOperation extends UnaryOperation<Boolean> {
} }
@Override @Override
public Boolean apply(Boolean input) { public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return !input; return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return !input.applyBoolean(implementationArguments, scope);
} }
@Override @Override
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,10 +19,15 @@ public class BooleanOrOperation extends BinaryOperation<Boolean, Boolean> {
} }
@Override @Override
public Boolean apply(Supplier<Boolean> left, Supplier<Boolean> right) { public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get() || right.get(); return applyBoolean(implementationArguments, scope);
} }
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyBoolean(implementationArguments, scope) || right.applyBoolean(implementationArguments, scope);
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.BOOLEAN; return ReturnType.BOOLEAN;
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -18,13 +18,24 @@ public class ConcatenationOperation extends BinaryOperation<Object, Object> {
super(left, right, position); super(left, right, position);
} }
@Override private static String toString(Object object) {
public String apply(Supplier<Object> left, Supplier<Object> right) { String s = object.toString();
return left.get().toString() + right.get().toString(); if(object instanceof Double) {
int l = s.length();
if(s.charAt(l - 2) == '.' && s.charAt(l - 1) == '0') {
s = s.substring(0, s.length() - 2);
}
}
return s;
} }
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
return Returnable.ReturnType.STRING; return Returnable.ReturnType.STRING;
} }
@Override
public Object apply(ImplementationArguments implementationArguments, Scope scope) {
return toString(left.apply(implementationArguments, scope)) + toString(right.apply(implementationArguments, scope));
}
} }
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -18,13 +18,18 @@ public class DivisionOperation extends BinaryOperation<Number, Number> {
super(left, right, position); super(left, right, position);
} }
@Override
public Number apply(Supplier<Number> left, Supplier<Number> right) {
return left.get().doubleValue() / right.get().doubleValue();
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
return Returnable.ReturnType.NUMBER; return Returnable.ReturnType.NUMBER;
} }
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) / right.applyDouble(implementationArguments, scope);
}
} }
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,10 +19,15 @@ public class ModuloOperation extends BinaryOperation<Number, Number> {
} }
@Override @Override
public Number apply(Supplier<Number> left, Supplier<Number> right) { public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() % right.get().doubleValue(); return applyDouble(implementationArguments, scope);
} }
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) % right.applyDouble(implementationArguments, scope);
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.NUMBER; return ReturnType.NUMBER;
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,10 +19,15 @@ public class MultiplicationOperation extends BinaryOperation<Number, Number> {
} }
@Override @Override
public Number apply(Supplier<Number> left, Supplier<Number> right) { public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() * right.get().doubleValue(); return applyDouble(implementationArguments, scope);
} }
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) * right.applyDouble(implementationArguments, scope);
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.NUMBER; return ReturnType.NUMBER;
@@ -7,7 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -16,13 +18,18 @@ public class NegationOperation extends UnaryOperation<Number> {
super(input, position); super(input, position);
} }
@Override
public Number apply(Number input) {
return -input.doubleValue();
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.NUMBER; return ReturnType.NUMBER;
} }
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return -input.applyDouble(implementationArguments, scope);
}
} }
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,10 +19,15 @@ public class NumberAdditionOperation extends BinaryOperation<Number, Number> {
} }
@Override @Override
public Number apply(Supplier<Number> left, Supplier<Number> right) { public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() + right.get().doubleValue(); return applyDouble(implementationArguments, scope);
} }
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) + right.applyDouble(implementationArguments, scope);
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.NUMBER; return ReturnType.NUMBER;
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,10 +19,15 @@ public class SubtractionOperation extends BinaryOperation<Number, Number> {
} }
@Override @Override
public Number apply(Supplier<Number> left, Supplier<Number> right) { public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() - right.get().doubleValue(); return applyDouble(implementationArguments, scope);
} }
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) - right.applyDouble(implementationArguments, scope);
}
@Override @Override
public ReturnType returnType() { public ReturnType returnType() {
return ReturnType.NUMBER; return ReturnType.NUMBER;
@@ -7,14 +7,12 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations; package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class UnaryOperation<T> implements Returnable<T> { public abstract class UnaryOperation<T> implements Returnable<T> {
private final Returnable<T> input; protected final Returnable<T> input;
private final Position position; private final Position position;
public UnaryOperation(Returnable<T> input, Position position) { public UnaryOperation(Returnable<T> input, Position position) {
@@ -22,13 +20,6 @@ public abstract class UnaryOperation<T> implements Returnable<T> {
this.position = position; this.position = position;
} }
public abstract T apply(T input);
@Override
public T apply(ImplementationArguments implementationArguments, Scope scope) {
return apply(input.apply(implementationArguments, scope));
}
@Override @Override
public Position getPosition() { public Position getPosition() {
return position; return position;
@@ -9,9 +9,9 @@ package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import net.jafama.FastMath; import net.jafama.FastMath;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation; import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -24,20 +24,25 @@ public class EqualsStatement extends BinaryOperation<Object, Boolean> {
super(left, right, position); super(left, right, position);
} }
@Override
public Boolean apply(Supplier<Object> left, Supplier<Object> right) {
Object leftUnwrapped = left.get();
Object rightUnwrapped = right.get();
if(leftUnwrapped instanceof Number l && rightUnwrapped instanceof Number r) {
return FastMath.abs(l.doubleValue() - r.doubleValue()) <= EPSILON;
}
return leftUnwrapped.equals(rightUnwrapped);
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN; return Returnable.ReturnType.BOOLEAN;
} }
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
Object leftValue = left.apply(implementationArguments, scope);
Object rightValue = right.apply(implementationArguments, scope);
if(leftValue instanceof Number l && rightValue instanceof Number r) {
return FastMath.abs(l.doubleValue() - r.doubleValue()) <= EPSILON;
}
return leftValue.equals(rightValue);
}
} }
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements; package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation; import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,14 +19,18 @@ public class GreaterOrEqualsThanStatement extends BinaryOperation<Number, Boolea
super(left, right, position); super(left, right, position);
} }
@Override
public Boolean apply(Supplier<Number> left, Supplier<Number> right) {
return left.get().doubleValue() >= right.get().doubleValue();
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN; return Returnable.ReturnType.BOOLEAN;
} }
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) >= right.applyDouble(implementationArguments, scope);
}
} }
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements; package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation; import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,11 +19,16 @@ public class GreaterThanStatement extends BinaryOperation<Number, Boolean> {
super(left, right, position); super(left, right, position);
} }
@Override @Override
public Boolean apply(Supplier<Number> left, Supplier<Number> right) { public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() > right.get().doubleValue(); return applyBoolean(implementationArguments, scope);
} }
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) > right.applyDouble(implementationArguments, scope);
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements; package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation; import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,12 +19,17 @@ public class LessThanOrEqualsStatement extends BinaryOperation<Number, Boolean>
super(left, right, position); super(left, right, position);
} }
@Override @Override
public Boolean apply(Supplier<Number> left, Supplier<Number> right) { public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() <= right.get().doubleValue(); return applyBoolean(implementationArguments, scope);
} }
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) <= right.applyDouble(implementationArguments, scope);
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN; return Returnable.ReturnType.BOOLEAN;
@@ -7,9 +7,9 @@
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements; package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation; import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -19,12 +19,17 @@ public class LessThanStatement extends BinaryOperation<Number, Boolean> {
super(left, right, position); super(left, right, position);
} }
@Override @Override
public Boolean apply(Supplier<Number> left, Supplier<Number> right) { public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return left.get().doubleValue() < right.get().doubleValue(); return applyBoolean(implementationArguments, scope);
} }
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) < right.applyDouble(implementationArguments, scope);
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN; return Returnable.ReturnType.BOOLEAN;
@@ -9,9 +9,9 @@ package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import net.jafama.FastMath; import net.jafama.FastMath;
import java.util.function.Supplier; import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable; import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation; import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position; import com.dfsek.terra.addons.terrascript.tokenizer.Position;
@@ -24,16 +24,20 @@ public class NotEqualsStatement extends BinaryOperation<Object, Boolean> {
} }
@Override @Override
public Boolean apply(Supplier<Object> left, Supplier<Object> right) { public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
Object leftUnwrapped = left.get(); return applyBoolean(implementationArguments, scope);
Object rightUnwrapped = right.get();
if(leftUnwrapped instanceof Number l && rightUnwrapped instanceof Number r) {
return FastMath.abs(l.doubleValue() - r.doubleValue()) > EPSILON;
}
return !leftUnwrapped.equals(rightUnwrapped);
} }
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
Object leftValue = left.apply(implementationArguments, scope);
Object rightValue = right.apply(implementationArguments, scope);
if(leftValue instanceof Number l && rightValue instanceof Number r) {
return FastMath.abs(l.doubleValue() - r.doubleValue()) > EPSILON;
}
return !leftValue.equals(rightValue);
}
@Override @Override
public Returnable.ReturnType returnType() { public Returnable.ReturnType returnType() {
@@ -1,40 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class VariableAssignmentNode<T> implements Item<T> {
private final Returnable<T> value;
private final Position position;
private final String identifier;
public VariableAssignmentNode(Returnable<T> value, String identifier, Position position) {
this.value = value;
this.identifier = identifier;
this.position = position;
}
@SuppressWarnings("unchecked")
@Override
public synchronized T apply(ImplementationArguments implementationArguments, Scope scope) {
T val = value.apply(implementationArguments, scope);
((Variable<T>) scope.get(identifier)).setValue(val);
return val;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class VariableDeclarationNode<T> implements Item<T> {
private final Position position;
private final String identifier;
private final Returnable<T> value;
private final Returnable.ReturnType type;
public VariableDeclarationNode(Position position, String identifier, Returnable<T> value, Returnable.ReturnType type) {
switch(type) {
case STRING:
case BOOLEAN:
case NUMBER:
break;
default:
throw new IllegalArgumentException("Invalid variable type: " + type);
}
this.position = position;
this.identifier = identifier;
this.value = value;
this.type = type;
}
@Override
public T apply(ImplementationArguments implementationArguments, Scope scope) {
T result = value.apply(implementationArguments, scope);
scope.put(identifier, switch(type) {
case NUMBER -> new NumberVariable((Number) result, position);
case BOOLEAN -> new BooleanVariable((Boolean) result, position);
case STRING -> new StringVariable((String) result, position);
default -> throw new IllegalStateException("Unexpected value: " + type);
});
return result;
}
@Override
public Position getPosition() {
return position;
}
public Returnable.ReturnType getType() {
return type;
}
public String getIdentifier() {
return identifier;
}
}
@@ -0,0 +1,25 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BoolAssignmentNode extends VariableAssignmentNode<Boolean> {
public BoolAssignmentNode(Returnable<Boolean> value, Position position, int index) {
super(value, position, index);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
boolean val = value.applyBoolean(implementationArguments, scope);
scope.setBool(index, val);
return val;
}
}
@@ -0,0 +1,25 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumAssignmentNode extends VariableAssignmentNode<Number> {
public NumAssignmentNode(Returnable<Number> value, Position position, int index) {
super(value, position, index);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
double val = value.applyDouble(implementationArguments, scope);
scope.setNum(index, val);
return val;
}
}
@@ -0,0 +1,21 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class StrAssignmentNode extends VariableAssignmentNode<String> {
public StrAssignmentNode(Returnable<String> value, Position position, int index) {
super(value, position, index);
}
@Override
public String apply(ImplementationArguments implementationArguments, Scope scope) {
String val = value.apply(implementationArguments, scope);
scope.setStr(index, val);
return val;
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class VariableAssignmentNode<T> implements Item<T> {
protected final Returnable<T> value;
protected final int index;
private final Position position;
public VariableAssignmentNode(Returnable<T> value, Position position, int index) {
this.value = value;
this.index = index;
this.position = position;
}
@Override
public Position getPosition() {
return position;
}
}

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