- improved performance of chunk-gen operations
- optimized caching for heightmap & rivermap regions - hopefully fixed some deadlock issues - reverted custom mushrooms for now
This commit is contained in:
parent
f7f1c17779
commit
eea88bd0e2
@ -30,24 +30,25 @@ import com.terraforged.core.world.climate.Climate;
|
||||
import com.terraforged.core.world.heightmap.Levels;
|
||||
import com.terraforged.core.world.terrain.Terrains;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.GenerationSettings;
|
||||
|
||||
public class SurfaceContext extends DecoratorContext {
|
||||
|
||||
public final long seed;
|
||||
public final int seaLevel;
|
||||
public final BlockState solid;
|
||||
public final BlockState fluid;
|
||||
public final int seaLevel;
|
||||
public final long seed;
|
||||
public final ChunkSurfaceBuffer buffer;
|
||||
public final CachedSurface cached = new CachedSurface();
|
||||
|
||||
public double noise;
|
||||
|
||||
public SurfaceContext(IChunk chunk, Levels levels, Terrains terrain, Climate climate, GenerationSettings settings, long seed) {
|
||||
super(chunk, levels, terrain, climate);
|
||||
public SurfaceContext(ChunkSurfaceBuffer buffer, Levels levels, Terrains terrain, Climate climate, GenerationSettings settings, long seed) {
|
||||
super(buffer, levels, terrain, climate);
|
||||
this.solid = settings.getDefaultBlock();
|
||||
this.fluid = settings.getDefaultFluid();
|
||||
this.seed = seed;
|
||||
this.buffer = buffer;
|
||||
this.seaLevel = levels.waterLevel;
|
||||
this.seed = seed;
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,11 @@ public class Cell<T extends Tag> {
|
||||
void visit(Cell<T> cell, int dx, int dz);
|
||||
}
|
||||
|
||||
public interface ContextVisitor<C, T extends Tag> {
|
||||
|
||||
void visit(Cell<T> cell, int dx, int dz, C ctx);
|
||||
}
|
||||
|
||||
public interface ZoomVisitor<T extends Tag> {
|
||||
|
||||
void visit(Cell<T> cell, float x, float z);
|
||||
|
@ -27,14 +27,15 @@ package com.terraforged.core.region;
|
||||
|
||||
import com.terraforged.core.cell.Cell;
|
||||
import com.terraforged.core.cell.Extent;
|
||||
import com.terraforged.core.world.decorator.Decorator;
|
||||
import com.terraforged.core.filter.Filterable;
|
||||
import com.terraforged.core.region.chunk.ChunkGenTask;
|
||||
import com.terraforged.core.region.chunk.ChunkReader;
|
||||
import com.terraforged.core.region.chunk.ChunkWriter;
|
||||
import com.terraforged.core.region.chunk.ChunkZoomTask;
|
||||
import com.terraforged.core.util.concurrent.batcher.Batcher;
|
||||
import com.terraforged.core.world.decorator.Decorator;
|
||||
import com.terraforged.core.world.heightmap.Heightmap;
|
||||
import com.terraforged.core.world.river.RiverRegionList;
|
||||
import com.terraforged.core.world.terrain.Terrain;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -137,6 +138,41 @@ public class Region implements Extent {
|
||||
}
|
||||
}
|
||||
|
||||
public void generateBase(Heightmap heightmap) {
|
||||
for (int cz = 0; cz < chunkSize.total; cz++) {
|
||||
for (int cx = 0; cx < chunkSize.total; cx++) {
|
||||
int index = chunkSize.indexOf(cx, cz);
|
||||
GenChunk chunk = computeChunk(index, cx, cz);
|
||||
for (int dz = 0; dz < 16; dz++) {
|
||||
for (int dx = 0; dx < 16; dx++) {
|
||||
float x = chunk.getBlockX() + dx;
|
||||
float z = chunk.getBlockZ() + dz;
|
||||
Cell<Terrain> cell = chunk.genCell(dx, dz);
|
||||
heightmap.applyBase(cell, x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generateRivers(Heightmap heightmap, RiverRegionList rivers) {
|
||||
for (int cz = 0; cz < chunkSize.total; cz++) {
|
||||
for (int cx = 0; cx < chunkSize.total; cx++) {
|
||||
int index = chunkSize.indexOf(cx, cz);
|
||||
GenChunk chunk = computeChunk(index, cx, cz);
|
||||
for (int dz = 0; dz < 16; dz++) {
|
||||
for (int dx = 0; dx < 16; dx++) {
|
||||
float x = chunk.getBlockX() + dx;
|
||||
float z = chunk.getBlockZ() + dz;
|
||||
Cell<Terrain> cell = chunk.genCell(dx, dz);
|
||||
heightmap.applyRivers(cell, x, z, rivers);
|
||||
heightmap.applyClimate(cell, x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generate(Heightmap heightmap, Batcher batcher) {
|
||||
for (int cz = 0; cz < chunkSize.total; cz++) {
|
||||
for (int cx = 0; cx < chunkSize.total; cx++) {
|
||||
|
@ -26,7 +26,8 @@
|
||||
package com.terraforged.core.region;
|
||||
|
||||
import com.terraforged.core.region.chunk.ChunkReader;
|
||||
import com.terraforged.core.util.Cache;
|
||||
import com.terraforged.core.util.concurrent.cache.Cache;
|
||||
import com.terraforged.core.util.concurrent.cache.CacheEntry;
|
||||
import com.terraforged.core.world.heightmap.RegionExtent;
|
||||
import me.dags.noise.util.NoiseUtil;
|
||||
|
||||
@ -37,13 +38,12 @@ public class RegionCache implements RegionExtent {
|
||||
|
||||
private final boolean queuing;
|
||||
private final RegionGenerator renderer;
|
||||
private final Cache<Long, CompletableFuture<Region>> cache;
|
||||
private final ThreadLocal<Region> cachedRegion = new ThreadLocal<>();
|
||||
private final Cache<Long, CacheEntry<Region>> cache;
|
||||
|
||||
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
|
||||
this.renderer = renderer;
|
||||
this.queuing = queueNeighbours;
|
||||
this.cache = Cache.concurrent(180, 60, TimeUnit.SECONDS);
|
||||
this.cache = new Cache<>(80, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -53,13 +53,7 @@ public class RegionCache implements RegionExtent {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Region> getRegionAsync(int regionX, int regionZ) {
|
||||
long id = NoiseUtil.seed(regionX, regionZ);
|
||||
CompletableFuture<Region> future = cache.get(id);
|
||||
if (future == null) {
|
||||
future = renderer.getRegionAsync(regionX, regionZ);
|
||||
cache.put(id, future);
|
||||
}
|
||||
return future;
|
||||
return renderer.generate(regionX, regionZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -72,28 +66,18 @@ public class RegionCache implements RegionExtent {
|
||||
|
||||
@Override
|
||||
public Region getRegion(int regionX, int regionZ) {
|
||||
Region cached = cachedRegion.get();
|
||||
if (cached != null && regionX == cached.getRegionX() && regionZ == cached.getRegionZ()) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
long id = NoiseUtil.seed(regionX, regionZ);
|
||||
CompletableFuture<Region> futureRegion = cache.get(id);
|
||||
|
||||
if (futureRegion == null) {
|
||||
cached = renderer.generateRegion(regionX, regionZ);
|
||||
cache.put(id, CompletableFuture.completedFuture(cached));
|
||||
} else {
|
||||
cached = futureRegion.join();
|
||||
}
|
||||
Region region = queueRegion(regionX, regionZ).get();
|
||||
|
||||
if (queuing) {
|
||||
queueNeighbours(regionX, regionZ);
|
||||
}
|
||||
|
||||
cachedRegion.set(cached);
|
||||
return region;
|
||||
}
|
||||
|
||||
return cached;
|
||||
public CacheEntry<Region> queueRegion(int regionX, int regionZ) {
|
||||
long id = NoiseUtil.seed(regionX, regionZ);
|
||||
return cache.computeIfAbsent(id, l -> renderer.generateCached(regionX, regionZ));
|
||||
}
|
||||
|
||||
private void queueNeighbours(int regionX, int regionZ) {
|
||||
@ -102,7 +86,7 @@ public class RegionCache implements RegionExtent {
|
||||
if (x == 0 && z == 0) {
|
||||
continue;
|
||||
}
|
||||
getRegionAsync(regionX + x, regionZ + z);
|
||||
queueRegion(regionX + x, regionZ + z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,12 @@
|
||||
package com.terraforged.core.region;
|
||||
|
||||
import com.terraforged.core.region.legacy.LegacyRegion;
|
||||
import com.terraforged.core.util.concurrent.ObjectPool;
|
||||
import com.terraforged.core.util.concurrent.ThreadPool;
|
||||
import com.terraforged.core.util.concurrent.batcher.Batcher;
|
||||
import com.terraforged.core.util.concurrent.cache.CacheEntry;
|
||||
import com.terraforged.core.world.WorldGenerator;
|
||||
import com.terraforged.core.world.WorldGeneratorFactory;
|
||||
import com.terraforged.core.world.heightmap.RegionExtent;
|
||||
import com.terraforged.core.world.river.RiverRegionList;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@ -41,14 +41,14 @@ public class RegionGenerator implements RegionExtent {
|
||||
private final int border;
|
||||
private final RegionFactory regions;
|
||||
private final ThreadPool threadPool;
|
||||
private final ObjectPool<WorldGenerator> genPool;
|
||||
private final ThreadLocal<WorldGenerator> genPool;
|
||||
|
||||
private RegionGenerator(Builder builder) {
|
||||
this.factor = builder.factor;
|
||||
this.border = builder.border;
|
||||
this.threadPool = builder.threadPool;
|
||||
this.regions = builder.regionFactory;
|
||||
this.genPool = new ObjectPool<>(50, builder.factory);
|
||||
this.genPool = ThreadLocal.withInitial(builder.factory);
|
||||
}
|
||||
|
||||
public RegionCache toCache() {
|
||||
@ -75,24 +75,26 @@ public class RegionGenerator implements RegionExtent {
|
||||
}
|
||||
|
||||
public CompletableFuture<Region> generate(int regionX, int regionZ) {
|
||||
return CompletableFuture.supplyAsync(() -> generateRegion(regionX, regionZ));
|
||||
return CompletableFuture.supplyAsync(() -> generateRegion(regionX, regionZ), threadPool);
|
||||
}
|
||||
|
||||
public CompletableFuture<Region> generate(float centerX, float centerZ, float zoom, boolean filter) {
|
||||
return CompletableFuture.supplyAsync(() -> generateRegion(centerX, centerZ, zoom, filter));
|
||||
return CompletableFuture.supplyAsync(() -> generateRegion(centerX, centerZ, zoom, filter), threadPool);
|
||||
}
|
||||
|
||||
public CacheEntry<Region> generateCached(int regionX, int regionZ) {
|
||||
return CacheEntry.supplyAsync(() -> generateRegion(regionX, regionZ), threadPool);
|
||||
}
|
||||
|
||||
public Region generateRegion(int regionX, int regionZ) {
|
||||
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
|
||||
WorldGenerator generator = item.getValue();
|
||||
WorldGenerator generator = genPool.get();
|
||||
Region region = regions.create(regionX, regionZ, factor, border);
|
||||
try (Batcher batcher = threadPool.batcher(region.getChunkCount())) {
|
||||
region.generate(generator.getHeightmap(), batcher);
|
||||
}
|
||||
RiverRegionList rivers = generator.getHeightmap().getRiverManager().getRivers(region);
|
||||
region.generateBase(generator.getHeightmap());
|
||||
region.generateRivers(generator.getHeightmap(), rivers);
|
||||
postProcess(region, generator);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
private void postProcess(Region region, WorldGenerator generator) {
|
||||
generator.getFilters().apply(region);
|
||||
@ -100,17 +102,20 @@ public class RegionGenerator implements RegionExtent {
|
||||
}
|
||||
|
||||
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
|
||||
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
|
||||
WorldGenerator generator = item.getValue();
|
||||
WorldGenerator generator = genPool.get();
|
||||
Region region = regions.create(0, 0, factor, border);
|
||||
try (Batcher batcher = threadPool.batcher(region.getChunkCount())) {
|
||||
region.generateZoom(generator.getHeightmap(), centerX, centerZ, zoom, batcher);
|
||||
}
|
||||
region.check();
|
||||
float translateX = centerX - ((region.getBlockSize().size * zoom) / 2F);
|
||||
float translateZ = centerZ - ((region.getBlockSize().size * zoom) / 2F);
|
||||
region.generate(chunk -> {
|
||||
chunk.generate((cell, dx, dz) -> {
|
||||
float x = ((chunk.getBlockX() + dx) * zoom) + translateX;
|
||||
float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ;
|
||||
generator.getHeightmap().apply(cell, x, z);
|
||||
});
|
||||
});
|
||||
postProcess(region, generator, centerX, centerZ, zoom, filter);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
private void postProcess(Region region, WorldGenerator generator, float centerX, float centerZ, float zoom, boolean filter) {
|
||||
if (filter) {
|
||||
|
@ -65,4 +65,12 @@ public interface ChunkReader extends ChunkHolder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default <C> void iterate(C context, Cell.ContextVisitor<C, Terrain> visitor) {
|
||||
for (int dz = 0; dz < 16; dz++) {
|
||||
for (int dx = 0; dx < 16; dx++) {
|
||||
visitor.visit(getCell(dx, dz), dx, dz, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 TerraForged
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.terraforged.core.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Cache<K, V> {
|
||||
|
||||
private final long lifespan;
|
||||
private final long interval;
|
||||
private final Map<K, CachedValue<V>> cache;
|
||||
|
||||
private long lastUpdate = 0L;
|
||||
|
||||
public Cache(long lifespan, long interval, TimeUnit unit) {
|
||||
this(lifespan, interval, unit, () -> new HashMap<>(100));
|
||||
}
|
||||
|
||||
public Cache(long lifespan, long interval, TimeUnit unit, Supplier<Map<K, CachedValue<V>>> backing) {
|
||||
this.lifespan = unit.toMillis(lifespan);
|
||||
this.interval = unit.toMillis(interval);
|
||||
this.cache = backing.get();
|
||||
}
|
||||
|
||||
public V get(K id) {
|
||||
update();
|
||||
CachedValue<V> value = cache.get(id);
|
||||
if (value != null) {
|
||||
return value.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void put(K id, V region) {
|
||||
update();
|
||||
cache.put(id, new CachedValue<>(region));
|
||||
}
|
||||
|
||||
public void drop(K id) {
|
||||
update();
|
||||
}
|
||||
|
||||
public boolean contains(K id) {
|
||||
return cache.containsKey(id);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
long time = System.currentTimeMillis();
|
||||
if (time - lastUpdate < interval) {
|
||||
return;
|
||||
}
|
||||
cache.values().removeIf(value -> time - value.time >= lifespan);
|
||||
lastUpdate = time;
|
||||
}
|
||||
|
||||
public static class CachedValue<T> {
|
||||
|
||||
private final T value;
|
||||
private long time;
|
||||
|
||||
private CachedValue(T value) {
|
||||
this.value = value;
|
||||
this.time = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private T getValue() {
|
||||
time = System.currentTimeMillis();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static <K, V> Cache<K, V> concurrent(long lifespan, long interval, TimeUnit unit) {
|
||||
return new Cache<>(lifespan, interval, unit, () -> new ConcurrentHashMap<>(100));
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@ public class ThreadPool implements Executor {
|
||||
|
||||
public ThreadPool(int size) {
|
||||
this.poolSize = size;
|
||||
this.service = ThreadPool.createService(size);
|
||||
this.service = ThreadPool.createService(size, "TerraForged");
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@ -81,6 +81,12 @@ public class ThreadPool implements Executor {
|
||||
return new AsyncBatcher(service, size);
|
||||
}
|
||||
|
||||
public static ThreadPool getCurrent() {
|
||||
synchronized (lock) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static ThreadPool getFixed(int size) {
|
||||
synchronized (lock) {
|
||||
if (instance.poolSize != size) {
|
||||
@ -120,17 +126,17 @@ public class ThreadPool implements Executor {
|
||||
|
||||
private static int defaultPoolSize() {
|
||||
int threads = Runtime.getRuntime().availableProcessors();
|
||||
return Math.max(2, (int) (threads / 3F) * 2);
|
||||
return Math.max(1, (int) ((threads / 3F) * 2F));
|
||||
}
|
||||
|
||||
private static ExecutorService createService(int size) {
|
||||
public static ExecutorService createService(int size, String name) {
|
||||
ThreadPoolExecutor service = new ThreadPoolExecutor(
|
||||
size,
|
||||
size,
|
||||
30,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadPoolFactory()
|
||||
new WorkerFactory(name)
|
||||
);
|
||||
service.allowCoreThreadTimeOut(true);
|
||||
return service;
|
||||
|
@ -4,20 +4,22 @@ import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
// As DefaultThreadPool but with custom thread names
|
||||
public class ThreadPoolFactory implements ThreadFactory {
|
||||
public class WorkerFactory implements ThreadFactory {
|
||||
|
||||
private final String prefix;
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
|
||||
public ThreadPoolFactory() {
|
||||
public WorkerFactory(String name) {
|
||||
group = Thread.currentThread().getThreadGroup();
|
||||
prefix = name + "-Worker-";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable task) {
|
||||
Thread thread = new Thread(group, task);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("TerraForged-Worker-" + threadNumber.getAndIncrement());
|
||||
thread.setName(prefix + threadNumber.getAndIncrement());
|
||||
return thread;
|
||||
}
|
||||
}
|
54
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java
vendored
Normal file
54
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package com.terraforged.core.util.concurrent.cache;
|
||||
|
||||
import com.terraforged.core.util.concurrent.ThreadPool;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Cache<K, V extends ExpiringEntry> implements Runnable {
|
||||
|
||||
private final long expireMS;
|
||||
private final long intervalMS;
|
||||
private final Executor executor;
|
||||
private final Map<K, V> map = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile long timestamp = 0L;
|
||||
|
||||
public Cache(long expireTime, long interval, TimeUnit unit) {
|
||||
this.expireMS = unit.toMillis(expireTime);
|
||||
this.intervalMS = unit.toMillis(interval);
|
||||
this.executor = ThreadPool.getCurrent();
|
||||
}
|
||||
|
||||
public V computeIfAbsent(K k, Supplier<V> supplier) {
|
||||
return computeIfAbsent(k, o -> supplier.get());
|
||||
}
|
||||
|
||||
public V computeIfAbsent(K k, Function<K, V> func) {
|
||||
V v = map.computeIfAbsent(k, func);
|
||||
queueUpdate();
|
||||
return v;
|
||||
}
|
||||
|
||||
private void queueUpdate() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - timestamp > intervalMS) {
|
||||
timestamp = now;
|
||||
executor.execute(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final long now = timestamp;
|
||||
map.forEach((key, val) -> {
|
||||
if (now - val.getTimestamp() > expireMS) {
|
||||
map.remove(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
37
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java
vendored
Normal file
37
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package com.terraforged.core.util.concurrent.cache;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
public class CacheEntry<T> extends FutureTask<T> implements ExpiringEntry {
|
||||
|
||||
private volatile long timestamp;
|
||||
|
||||
private CacheEntry(Callable<T> supplier) {
|
||||
super(supplier);
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
try {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
return super.get();
|
||||
} catch (Throwable t) {
|
||||
throw new CompletionException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> CacheEntry<T> supplyAsync(Callable<T> supplier, Executor executor) {
|
||||
CacheEntry<T> entry = new CacheEntry<>(supplier);
|
||||
executor.execute(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
6
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java
vendored
Normal file
6
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package com.terraforged.core.util.concurrent.cache;
|
||||
|
||||
public interface ExpiringEntry {
|
||||
|
||||
long getTimestamp();
|
||||
}
|
@ -31,14 +31,24 @@ import com.terraforged.core.cell.Populator;
|
||||
import com.terraforged.core.region.Size;
|
||||
import com.terraforged.core.util.concurrent.ObjectPool;
|
||||
import com.terraforged.core.world.climate.Climate;
|
||||
import com.terraforged.core.world.river.RiverManager;
|
||||
import com.terraforged.core.world.river.RiverRegionList;
|
||||
import com.terraforged.core.world.terrain.Terrain;
|
||||
|
||||
public interface Heightmap extends Populator, Extent {
|
||||
|
||||
Climate getClimate();
|
||||
|
||||
RiverManager getRiverManager();
|
||||
|
||||
void visit(Cell<Terrain> cell, float x, float z);
|
||||
|
||||
void applyBase(Cell<Terrain> cell, float x, float z);
|
||||
|
||||
void applyRivers(Cell<Terrain> cell, float x, float z, RiverRegionList rivers);
|
||||
|
||||
void applyClimate(Cell<Terrain> cell, float x, float z);
|
||||
|
||||
@Override
|
||||
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
|
||||
int chunkSize = Size.chunkToBlock(1);
|
||||
|
@ -37,6 +37,7 @@ import com.terraforged.core.world.continent.ContinentLerper2;
|
||||
import com.terraforged.core.world.continent.ContinentLerper3;
|
||||
import com.terraforged.core.world.continent.ContinentModule;
|
||||
import com.terraforged.core.world.river.RiverManager;
|
||||
import com.terraforged.core.world.river.RiverRegionList;
|
||||
import com.terraforged.core.world.terrain.Terrain;
|
||||
import com.terraforged.core.world.terrain.TerrainPopulator;
|
||||
import com.terraforged.core.world.terrain.Terrains;
|
||||
@ -165,19 +166,29 @@ public class WorldHeightmap implements Heightmap {
|
||||
|
||||
@Override
|
||||
public void apply(Cell<Terrain> cell, float x, float z) {
|
||||
applyBase(cell, x, z);
|
||||
applyRivers(cell, x, z, riverManager.getRivers((int) x, (int) z));
|
||||
applyClimate(cell, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyBase(Cell<Terrain> cell, float x, float z) {
|
||||
// initial type
|
||||
cell.tag = terrain.steppe;
|
||||
|
||||
// basic shapes
|
||||
continentModule.apply(cell, x, z);
|
||||
regionModule.apply(cell, x, z);
|
||||
|
||||
// apply actuall heightmap
|
||||
// apply actual heightmap
|
||||
root.apply(cell, x, z);
|
||||
}
|
||||
|
||||
// apply rivers
|
||||
riverManager.apply(cell, x, z);
|
||||
@Override
|
||||
public void applyRivers(Cell<Terrain> cell, float x, float z, RiverRegionList rivers) {
|
||||
rivers.apply(cell, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyClimate(Cell<Terrain> cell, float x, float z) {
|
||||
// apply climate data
|
||||
if (cell.value <= levels.water) {
|
||||
climate.apply(cell, x, z, false);
|
||||
@ -207,6 +218,11 @@ public class WorldHeightmap implements Heightmap {
|
||||
return climate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RiverManager getRiverManager() {
|
||||
return riverManager;
|
||||
}
|
||||
|
||||
public Populator getPopulator(Terrain terrain) {
|
||||
return terrainProvider.getPopulator(terrain);
|
||||
}
|
||||
|
@ -26,13 +26,15 @@
|
||||
package com.terraforged.core.world.river;
|
||||
|
||||
import com.terraforged.core.cell.Cell;
|
||||
import com.terraforged.core.util.Cache;
|
||||
import com.terraforged.core.region.Region;
|
||||
import com.terraforged.core.util.concurrent.cache.Cache;
|
||||
import com.terraforged.core.util.concurrent.cache.CacheEntry;
|
||||
import com.terraforged.core.world.GeneratorContext;
|
||||
import com.terraforged.core.world.heightmap.Heightmap;
|
||||
import com.terraforged.core.world.terrain.Terrain;
|
||||
import me.dags.noise.util.NoiseUtil;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RiverManager {
|
||||
@ -42,7 +44,7 @@ public class RiverManager {
|
||||
private final Heightmap heightmap;
|
||||
private final GeneratorContext context;
|
||||
private final RiverContext riverContext;
|
||||
private final Cache<Long, RiverRegion> cache = new Cache<>(60, 60, TimeUnit.SECONDS, () -> new ConcurrentHashMap<>());
|
||||
private final Cache<Long, CacheEntry<RiverRegion>> cache = new Cache<>(120, 60, TimeUnit.SECONDS);
|
||||
|
||||
public RiverManager(Heightmap heightmap, GeneratorContext context) {
|
||||
RiverConfig primary = RiverConfig.builder(context.levels)
|
||||
@ -77,6 +79,35 @@ public class RiverManager {
|
||||
this.riverContext = new RiverContext(context.settings.rivers.riverFrequency, primary, secondary, tertiary, lakes);
|
||||
}
|
||||
|
||||
public RiverRegionList getRivers(Region region) {
|
||||
return getRivers(region.getBlockX(), region.getBlockZ());
|
||||
}
|
||||
|
||||
public RiverRegionList getRivers(int blockX, int blockZ) {
|
||||
int rx = RiverRegion.blockToRegion(blockX);
|
||||
int rz = RiverRegion.blockToRegion(blockZ);
|
||||
|
||||
// check which quarter of the region pos (x,y) is in & get the neighbouring regions' relative coords
|
||||
int qx = blockX < RiverRegion.regionToBlock(rx) + QUAD_SIZE ? -1 : 1;
|
||||
int qz = blockZ < RiverRegion.regionToBlock(rz) + QUAD_SIZE ? -1 : 1;
|
||||
|
||||
// relative positions of neighbouring regions
|
||||
int minX = Math.min(0, qx);
|
||||
int minZ = Math.min(0, qz);
|
||||
int maxX = Math.max(0, qx);
|
||||
int maxZ = Math.max(0, qz);
|
||||
|
||||
RiverRegionList list = new RiverRegionList();
|
||||
for (int dz = minZ; dz <= maxZ; dz++) {
|
||||
for (int dx = minX; dx <= maxX; dx++) {
|
||||
CacheEntry<RiverRegion> entry = getRegion(rx + dx, rz + dz);
|
||||
list.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void apply(Cell<Terrain> cell, float x, float z) {
|
||||
int rx = RiverRegion.blockToRegion((int) x);
|
||||
int rz = RiverRegion.blockToRegion((int) z);
|
||||
@ -91,20 +122,32 @@ public class RiverManager {
|
||||
int maxX = Math.max(0, qx);
|
||||
int maxZ = Math.max(0, qz);
|
||||
|
||||
// queue up the 4 nearest reiver regions
|
||||
int index = 0;
|
||||
CacheEntry<RiverRegion>[] entries = new CacheEntry[4];
|
||||
for (int dz = minZ; dz <= maxZ; dz++) {
|
||||
for (int dx = minX; dx <= maxX; dx++) {
|
||||
getRegion(rx + dx, rz + dz).apply(cell, x, z);
|
||||
entries[index++] = getRegion(rx + dx, rz + dz);
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
while (count < index) {
|
||||
for (CacheEntry<RiverRegion> entry : entries) {
|
||||
if (entry.isDone()) {
|
||||
count++;
|
||||
entry.get().apply(cell, x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RiverRegion getRegion(int rx, int rz) {
|
||||
private CacheEntry<RiverRegion> getRegion(int rx, int rz) {
|
||||
long id = NoiseUtil.seed(rx, rz);
|
||||
RiverRegion region = cache.get(id);
|
||||
if (region == null) {
|
||||
region = new RiverRegion(rx, rz, heightmap, context, riverContext);
|
||||
cache.put(id, region);
|
||||
return cache.computeIfAbsent(id, l -> generateRegion(rx, rz));
|
||||
}
|
||||
return region;
|
||||
|
||||
private CacheEntry<RiverRegion> generateRegion(int rx, int rz) {
|
||||
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverContext), ForkJoinPool.commonPool());
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ package com.terraforged.core.world.river;
|
||||
|
||||
import com.terraforged.core.cell.Cell;
|
||||
import com.terraforged.core.util.concurrent.ObjectPool;
|
||||
import com.terraforged.core.util.concurrent.cache.ExpiringEntry;
|
||||
import com.terraforged.core.world.GeneratorContext;
|
||||
import com.terraforged.core.world.heightmap.Heightmap;
|
||||
import com.terraforged.core.world.terrain.Terrain;
|
||||
@ -40,12 +41,14 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RiverRegion {
|
||||
public class RiverRegion implements ExpiringEntry {
|
||||
|
||||
public static final int SCALE = 12;
|
||||
private static final double MIN_FORK_ANGLE = 20D;
|
||||
private static final double MAX_FORK_ANGLE = 60D;
|
||||
private static final long EXPIRE_TIME = TimeUnit.SECONDS.toMillis(60);
|
||||
|
||||
private final Domain domain;
|
||||
private final Terrains terrains;
|
||||
@ -68,6 +71,8 @@ public class RiverRegion {
|
||||
private final int forkAttempts;
|
||||
private final int tertiaryAttempts;
|
||||
|
||||
private final long timestamp = System.currentTimeMillis() + EXPIRE_TIME;
|
||||
|
||||
public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverContext riverContext) {
|
||||
int seed = new Random(NoiseUtil.seed(regionX, regionZ)).nextInt();
|
||||
this.lake = riverContext.lakes;
|
||||
@ -96,6 +101,11 @@ public class RiverRegion {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void apply(Cell<Terrain> cell, float x, float z) {
|
||||
float px = domain.getX(x, z);
|
||||
float pz = domain.getY(x, z);
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.terraforged.core.world.river;
|
||||
|
||||
import com.terraforged.core.cell.Cell;
|
||||
import com.terraforged.core.util.concurrent.cache.CacheEntry;
|
||||
import com.terraforged.core.world.terrain.Terrain;
|
||||
|
||||
public class RiverRegionList {
|
||||
|
||||
private int index = 0;
|
||||
private final CacheEntry<RiverRegion>[] regions = new CacheEntry[4];
|
||||
|
||||
protected void add(CacheEntry<RiverRegion> entry) {
|
||||
regions[index++] = entry;
|
||||
}
|
||||
|
||||
public void apply(Cell<Terrain> cell, float x, float z) {
|
||||
int complete = 0;
|
||||
while (complete < regions.length) {
|
||||
for (CacheEntry<RiverRegion> entry : regions) {
|
||||
if (entry.isDone()) {
|
||||
complete++;
|
||||
entry.get().apply(cell, x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package com.terraforged.mod.chunk;
|
||||
|
||||
import com.terraforged.api.chunk.ChunkDelegate;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.biome.BiomeContainer;
|
||||
import net.minecraft.world.chunk.ChunkPrimer;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
|
||||
/**
|
||||
* A ChunkPrimer wrapper that handles setting BlockStates within the chunk & updating heightmaps accordingly
|
||||
*/
|
||||
public class FastChunk implements ChunkDelegate {
|
||||
|
||||
private final int blockX;
|
||||
private final int blockZ;
|
||||
private final ChunkPrimer primer;
|
||||
private final Heightmap worldSurface;
|
||||
private final Heightmap oceanSurface;
|
||||
private final BlockPos.Mutable mutable = new BlockPos.Mutable();
|
||||
|
||||
private FastChunk(ChunkPrimer primer) {
|
||||
this.primer = primer;
|
||||
this.blockX = primer.getPos().getXStart();
|
||||
this.blockZ = primer.getPos().getZStart();
|
||||
this.worldSurface = primer.getHeightmap(Heightmap.Type.WORLD_SURFACE_WG);
|
||||
this.oceanSurface = primer.getHeightmap(Heightmap.Type.OCEAN_FLOOR_WG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChunk getDelegate() {
|
||||
return primer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState setBlockState(BlockPos pos, BlockState state, boolean falling) {
|
||||
ChunkSection section = primer.getSection(pos.getY() >> 4);
|
||||
section.lock();
|
||||
int dx = pos.getX() & 15;
|
||||
int dy = pos.getY() & 15;
|
||||
int dz = pos.getZ() & 15;
|
||||
BlockState replaced = section.setBlockState(dx, dy, dz, state, false);
|
||||
if (state.getBlock() != Blocks.AIR) {
|
||||
mutable.setPos(blockX + dx, pos.getY(), blockZ + dz);
|
||||
if (state.getLightValue(primer, mutable) != 0) {
|
||||
primer.addLightPosition(mutable);
|
||||
}
|
||||
worldSurface.update(dx, pos.getY(), dz, state);
|
||||
oceanSurface.update(dx, pos.getY(), dz, state);
|
||||
}
|
||||
section.unlock();
|
||||
return replaced;
|
||||
}
|
||||
|
||||
public void setBiomes(BiomeContainer biomes) {
|
||||
primer.func_225548_a_(biomes);
|
||||
}
|
||||
|
||||
public static IChunk wrap(IChunk chunk) {
|
||||
if (chunk instanceof FastChunk) {
|
||||
return chunk;
|
||||
}
|
||||
if (chunk instanceof ChunkPrimer) {
|
||||
return new FastChunk((ChunkPrimer) chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
}
|
@ -118,6 +118,11 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
||||
|
||||
@Override
|
||||
public void generateStructures(BiomeManager unused, IChunk chunk, ChunkGenerator<?> generator, TemplateManager templates) {
|
||||
ChunkPos pos = chunk.getPos();
|
||||
int regionX = regionCache.chunkToRegion(pos.x);
|
||||
int regionZ = regionCache.chunkToRegion(pos.z);
|
||||
// start generating the heightmap as early as possible
|
||||
regionCache.queueRegion(regionX, regionZ);
|
||||
super.generateStructures(unused, chunk, this, templates);
|
||||
}
|
||||
|
||||
@ -145,40 +150,41 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
||||
|
||||
@Override
|
||||
public final void generateBase(IWorld world, IChunk chunk) {
|
||||
DecoratorContext context = new DecoratorContext(chunk, getContext().levels, getContext().terrain, getContext().factory.getClimate());
|
||||
TerraContainer container = getBiomeContainer(chunk);
|
||||
container.getChunkReader().iterate((cell, dx, dz) -> {
|
||||
int px = context.blockX + dx;
|
||||
int pz = context.blockZ + dz;
|
||||
DecoratorContext context = new DecoratorContext(FastChunk.wrap(chunk), getContext().levels, getContext().terrain, getContext().factory.getClimate());
|
||||
container.getChunkReader().iterate(context, (cell, dx, dz, ctx) -> {
|
||||
int px = ctx.blockX + dx;
|
||||
int pz = ctx.blockZ + dz;
|
||||
int py = (int) (cell.value * getMaxHeight());
|
||||
context.cell = cell;
|
||||
context.biome = container.getBiome(dx, dz);
|
||||
ChunkPopulator.INSTANCE.decorate(chunk, context, px, py, pz);
|
||||
ctx.cell = cell;
|
||||
ctx.biome = container.getBiome(dx, dz);
|
||||
ChunkPopulator.INSTANCE.decorate(ctx.chunk, ctx, px, py, pz);
|
||||
});
|
||||
terrainHelper.flatten(world, chunk, context.blockX, context.blockZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void generateSurface(WorldGenRegion world, IChunk chunk) {
|
||||
ChunkSurfaceBuffer buffer = new ChunkSurfaceBuffer(chunk);
|
||||
SurfaceContext context = getContext().surface(buffer, getSettings());
|
||||
TerraContainer container = getBiomeContainer(chunk);
|
||||
container.getChunkReader().iterate((cell, dx, dz) -> {
|
||||
int px = context.blockX + dx;
|
||||
int pz = context.blockZ + dz;
|
||||
int top = chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz) + 1;
|
||||
ChunkSurfaceBuffer buffer = new ChunkSurfaceBuffer(FastChunk.wrap(chunk));
|
||||
SurfaceContext context = getContext().surface(buffer, getSettings());
|
||||
|
||||
buffer.setSurfaceLevel(top);
|
||||
container.getChunkReader().iterate(context, (cell, dx, dz, ctx) -> {
|
||||
int px = ctx.blockX + dx;
|
||||
int pz = ctx.blockZ + dz;
|
||||
int top = ctx.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz) + 1;
|
||||
|
||||
context.cell = cell;
|
||||
context.biome = container.getBiome(dx, dz);
|
||||
context.noise = getSurfaceNoise(px, pz) * 15D;
|
||||
ctx.buffer.setSurfaceLevel(top);
|
||||
|
||||
getSurfaceManager().getSurface(context).buildSurface(px, pz, top, context);
|
||||
ctx.cell = cell;
|
||||
ctx.biome = container.getBiome(dx, dz);
|
||||
ctx.noise = getSurfaceNoise(px, pz) * 15D;
|
||||
|
||||
getSurfaceManager().getSurface(ctx).buildSurface(px, pz, top, ctx);
|
||||
|
||||
int py = (int) (cell.value * getMaxHeight());
|
||||
for (ColumnDecorator processor : getBaseDecorators()) {
|
||||
processor.decorate(buffer, context, px, py, pz);
|
||||
processor.decorate(ctx.buffer, ctx, px, py, pz);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -214,14 +220,14 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
||||
|
||||
@Override
|
||||
public final void postProcess(ChunkReader chunk, TerraContainer container, DecoratorContext context) {
|
||||
chunk.iterate((cell, dx, dz) -> {
|
||||
int px = context.blockX + dx;
|
||||
int pz = context.blockZ + dz;
|
||||
int py = context.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz);
|
||||
context.cell = cell;
|
||||
context.biome = container.getBiome(dx, dz);
|
||||
chunk.iterate(context, (cell, dx, dz, ctx) -> {
|
||||
int px = ctx.blockX + dx;
|
||||
int pz = ctx.blockZ + dz;
|
||||
int py = ctx.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz);
|
||||
ctx.cell = cell;
|
||||
ctx.biome = container.getBiome(dx, dz);
|
||||
for (ColumnDecorator decorator : getPostProcessors()) {
|
||||
decorator.decorate(context.chunk, context, px, py, pz);
|
||||
decorator.decorate(ctx.chunk, ctx, px, py, pz);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -292,6 +298,8 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
||||
TerraContainer container = getBiomeProvider().createBiomeContainer(view);
|
||||
if (chunk instanceof ChunkPrimer) {
|
||||
((ChunkPrimer) chunk).func_225548_a_(container);
|
||||
} else if (chunk instanceof FastChunk) {
|
||||
((FastChunk) chunk).setBiomes(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
@ -306,8 +314,12 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
||||
modifiers = new FeatureModifiers();
|
||||
}
|
||||
|
||||
// block ugly features
|
||||
if (context.terraSettings.features.strataDecorator) {
|
||||
// block stone blobs if strata enabled
|
||||
modifiers.getPredicates().add(Matchers.stoneBlobs(), FeaturePredicate.DENY);
|
||||
}
|
||||
|
||||
// block ugly features
|
||||
modifiers.getPredicates().add(Matchers.sedimentDisks(), FeaturePredicate.DENY);
|
||||
modifiers.getPredicates().add(FeatureMatcher.of(Feature.LAKE), FeaturePredicate.DENY);
|
||||
modifiers.getPredicates().add(FeatureMatcher.of(Feature.SPRING_FEATURE), FeaturePredicate.DENY);
|
||||
|
@ -26,6 +26,7 @@
|
||||
package com.terraforged.mod.chunk;
|
||||
|
||||
import com.terraforged.api.chunk.column.DecoratorContext;
|
||||
import com.terraforged.api.chunk.surface.ChunkSurfaceBuffer;
|
||||
import com.terraforged.api.chunk.surface.SurfaceContext;
|
||||
import com.terraforged.core.world.GeneratorContext;
|
||||
import com.terraforged.core.world.WorldGeneratorFactory;
|
||||
@ -62,7 +63,7 @@ public class TerraContext extends GeneratorContext {
|
||||
return new DecoratorContext(chunk, levels, terrain, factory.getClimate());
|
||||
}
|
||||
|
||||
public SurfaceContext surface(IChunk chunk, GenerationSettings settings) {
|
||||
return new SurfaceContext(chunk, levels, terrain, factory.getClimate(), settings, world.getSeed());
|
||||
public SurfaceContext surface(ChunkSurfaceBuffer buffer, GenerationSettings settings) {
|
||||
return new SurfaceContext(buffer, levels, terrain, factory.getClimate(), settings, world.getSeed());
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,73 @@
|
||||
"config": {
|
||||
"features": [
|
||||
{
|
||||
"name": "terraforged:brown_mushroom",
|
||||
"config": {},
|
||||
"name": "minecraft:huge_brown_mushroom",
|
||||
"config": {
|
||||
"cap_provider": {
|
||||
"type": "minecraft:simple_state_provider",
|
||||
"state": {
|
||||
"Name": "minecraft:brown_mushroom_block",
|
||||
"Properties": {
|
||||
"east": "true",
|
||||
"south": "true",
|
||||
"north": "true",
|
||||
"west": "true",
|
||||
"up": "true",
|
||||
"down": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stem_provider": {
|
||||
"type": "minecraft:simple_state_provider",
|
||||
"state": {
|
||||
"Name": "minecraft:mushroom_stem",
|
||||
"Properties": {
|
||||
"east": "true",
|
||||
"south": "true",
|
||||
"north": "true",
|
||||
"west": "true",
|
||||
"up": "false",
|
||||
"down": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
"foliage_radius": 3
|
||||
},
|
||||
"chance": 0.025
|
||||
},
|
||||
{
|
||||
"name": "terraforged:red_mushroom",
|
||||
"config": {},
|
||||
"name": "minecraft:huge_red_mushroom",
|
||||
"config": {
|
||||
"cap_provider": {
|
||||
"type": "minecraft:simple_state_provider",
|
||||
"state": {
|
||||
"Name": "minecraft:red_mushroom_block",
|
||||
"Properties": {
|
||||
"east": "true",
|
||||
"south": "true",
|
||||
"north": "true",
|
||||
"west": "true",
|
||||
"up": "true",
|
||||
"down": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stem_provider": {
|
||||
"type": "minecraft:simple_state_provider",
|
||||
"state": {
|
||||
"Name": "minecraft:mushroom_stem",
|
||||
"Properties": {
|
||||
"east": "true",
|
||||
"south": "true",
|
||||
"north": "true",
|
||||
"west": "true",
|
||||
"up": "false",
|
||||
"down": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
"foliage_radius": 2
|
||||
},
|
||||
"chance": 0.05
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user