mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Terrain is important
- Fixed Ores generating all over the surface. - fixed UI bug
This commit is contained in:
15 files changed
+408
-88
No files matched your search
@@ -42,13 +42,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public final class WorldRuntimeControlService {
|
public final class WorldRuntimeControlService {
|
||||||
private static final int STUDIO_ENTRY_VIEW_DISTANCE = 2;
|
|
||||||
private static final int MAX_SAFE_ENTRY_HORIZONTAL_RADIUS = 15;
|
private static final int MAX_SAFE_ENTRY_HORIZONTAL_RADIUS = 15;
|
||||||
private static final int SAFE_ENTRY_UPWARD_ALLOWANCE = 32;
|
private static final int SAFE_ENTRY_UPWARD_ALLOWANCE = 32;
|
||||||
private static final double BLOCK_CENTER = 0.5D;
|
private static final double BLOCK_CENTER = 0.5D;
|
||||||
private static final double COLLISION_EPSILON = 0.000001D;
|
private static final double COLLISION_EPSILON = 0.000001D;
|
||||||
private static final Method PLAYER_GET_VIEW_DISTANCE_METHOD = findPlayerMethod("getViewDistance");
|
|
||||||
private static final Method PLAYER_SET_VIEW_DISTANCE_METHOD = findPlayerMethod("setViewDistance", int.class);
|
|
||||||
private static final Set<Material> UNSAFE_ENTRY_MATERIALS = Set.of(
|
private static final Set<Material> UNSAFE_ENTRY_MATERIALS = Set.of(
|
||||||
Material.CACTUS,
|
Material.CACTUS,
|
||||||
Material.CAMPFIRE,
|
Material.CAMPFIRE,
|
||||||
@@ -370,7 +367,6 @@ public final class WorldRuntimeControlService {
|
|||||||
|
|
||||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||||
GameModeRestore modeRestore = new GameModeRestore(player);
|
GameModeRestore modeRestore = new GameModeRestore(player);
|
||||||
PlayerViewDistanceRestore viewDistanceRestore = new PlayerViewDistanceRestore(player);
|
|
||||||
AtomicReference<CompletableFuture<Boolean>> activeTeleport = new AtomicReference<>();
|
AtomicReference<CompletableFuture<Boolean>> activeTeleport = new AtomicReference<>();
|
||||||
future.whenComplete((success, failure) -> {
|
future.whenComplete((success, failure) -> {
|
||||||
if (Boolean.TRUE.equals(success)) {
|
if (Boolean.TRUE.equals(success)) {
|
||||||
@@ -380,7 +376,6 @@ public final class WorldRuntimeControlService {
|
|||||||
if (teleport != null && !teleport.isDone()) {
|
if (teleport != null && !teleport.isDone()) {
|
||||||
teleport.cancel(false);
|
teleport.cancel(false);
|
||||||
}
|
}
|
||||||
viewDistanceRestore.restore();
|
|
||||||
modeRestore.restore();
|
modeRestore.restore();
|
||||||
});
|
});
|
||||||
boolean scheduled = J.runEntity(player, () -> {
|
boolean scheduled = J.runEntity(player, () -> {
|
||||||
@@ -390,9 +385,7 @@ public final class WorldRuntimeControlService {
|
|||||||
}
|
}
|
||||||
if (gameMode != null) {
|
if (gameMode != null) {
|
||||||
modeRestore.apply(gameMode);
|
modeRestore.apply(gameMode);
|
||||||
viewDistanceRestore.apply();
|
|
||||||
if (future.isDone()) {
|
if (future.isDone()) {
|
||||||
viewDistanceRestore.restore();
|
|
||||||
modeRestore.restore();
|
modeRestore.restore();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -417,7 +410,6 @@ public final class WorldRuntimeControlService {
|
|||||||
|
|
||||||
if (Boolean.TRUE.equals(success)) {
|
if (Boolean.TRUE.equals(success)) {
|
||||||
if (future.complete(true)) {
|
if (future.complete(true)) {
|
||||||
viewDistanceRestore.restore();
|
|
||||||
J.runEntity(player, () -> IrisServices.get(BoardSVC.class).updatePlayer(player));
|
J.runEntity(player, () -> IrisServices.get(BoardSVC.class).updatePlayer(player));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -804,14 +796,6 @@ public final class WorldRuntimeControlService {
|
|||||||
return method.invoke(instance);
|
return method.invoke(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Method findPlayerMethod(String methodName, Class<?>... parameterTypes) {
|
|
||||||
try {
|
|
||||||
return Player.class.getMethod(methodName, parameterTypes);
|
|
||||||
} catch (NoSuchMethodException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface TeleportExecutor {
|
interface TeleportExecutor {
|
||||||
CompletableFuture<Boolean> teleport(Player player, Location location);
|
CompletableFuture<Boolean> teleport(Player player, Location location);
|
||||||
@@ -857,56 +841,4 @@ public final class WorldRuntimeControlService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class PlayerViewDistanceRestore {
|
|
||||||
private final Player player;
|
|
||||||
private final AtomicBoolean changed;
|
|
||||||
private final AtomicBoolean restored;
|
|
||||||
private int previousViewDistance;
|
|
||||||
|
|
||||||
private PlayerViewDistanceRestore(Player player) {
|
|
||||||
this.player = player;
|
|
||||||
changed = new AtomicBoolean(false);
|
|
||||||
restored = new AtomicBoolean(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apply() {
|
|
||||||
if (PLAYER_GET_VIEW_DISTANCE_METHOD == null || PLAYER_SET_VIEW_DISTANCE_METHOD == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Object currentValue = PLAYER_GET_VIEW_DISTANCE_METHOD.invoke(player);
|
|
||||||
if (!(currentValue instanceof Number)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int currentViewDistance = ((Number) currentValue).intValue();
|
|
||||||
if (currentViewDistance <= STUDIO_ENTRY_VIEW_DISTANCE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PLAYER_SET_VIEW_DISTANCE_METHOD.invoke(player, STUDIO_ENTRY_VIEW_DISTANCE);
|
|
||||||
previousViewDistance = currentViewDistance;
|
|
||||||
changed.set(true);
|
|
||||||
} catch (ReflectiveOperationException failure) {
|
|
||||||
IrisLogging.reportError("Failed to apply a temporary player view distance for a studio teleport.", failure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restore() {
|
|
||||||
if (!changed.get() || !restored.compareAndSet(false, true)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Runnable restoration = () -> {
|
|
||||||
try {
|
|
||||||
PLAYER_SET_VIEW_DISTANCE_METHOD.invoke(player, previousViewDistance);
|
|
||||||
} catch (ReflectiveOperationException failure) {
|
|
||||||
IrisLogging.reportError("Failed to restore a player's view distance after a studio teleport.", failure);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (!J.runEntity(player, restoration)) {
|
|
||||||
IrisLogging.reportError(
|
|
||||||
"Failed to restore a player's view distance after a studio teleport.",
|
|
||||||
new IllegalStateException("The player entity scheduler rejected the view-distance restoration."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -518,7 +518,7 @@ public class BoardSVC implements IrisService, BoardProvider {
|
|||||||
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_REGION, MessageArgument.untrusted("region", engine.getRegion(x, z).getName())));
|
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_REGION, MessageArgument.untrusted("region", engine.getRegion(x, z).getName())));
|
||||||
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_BIOME, MessageArgument.untrusted("biome", engine.getBiomeOrMantle(x, y, z).getName())));
|
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_BIOME, MessageArgument.untrusted("biome", engine.getBiomeOrMantle(x, y, z).getName())));
|
||||||
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_HEIGHT, MessageArgument.trusted("height", Math.round(engine.getHeight(x, z)))));
|
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_HEIGHT, MessageArgument.trusted("height", Math.round(engine.getHeight(x, z)))));
|
||||||
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_SLOPE, MessageArgument.trusted("slope", Form.f(engine.getComplex().getSlopeStream().get(x, z), 2))));
|
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_SLOPE, MessageArgument.trusted("slope", Form.f(engine.getSlope(x, z), 2))));
|
||||||
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_BLOCK_UPDATES, MessageArgument.trusted("updates", Form.f(engine.getBlockUpdatesPerSecond()))));
|
lines.add(IrisLanguage.text(BukkitUiMessages.SCOREBOARD_BLOCK_UPDATES, MessageArgument.trusted("updates", Form.f(engine.getBlockUpdatesPerSecond()))));
|
||||||
lines.add("&7&m ");
|
lines.add("&7&m ");
|
||||||
this.lines = lines;
|
this.lines = lines;
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
private ProceduralStream<Double> overlayStream;
|
private ProceduralStream<Double> overlayStream;
|
||||||
private ProceduralStream<Double> heightFluidStream;
|
private ProceduralStream<Double> heightFluidStream;
|
||||||
private ProceduralStream<Double> slopeStream;
|
private ProceduralStream<Double> slopeStream;
|
||||||
|
private ProceduralStream<Double> naturalSlopeStream;
|
||||||
private ProceduralStream<Double> riverDistanceStream;
|
private ProceduralStream<Double> riverDistanceStream;
|
||||||
private ProceduralStream<Double> riverFlowStream;
|
private ProceduralStream<Double> riverFlowStream;
|
||||||
private ProceduralStream<Double> riverCarveWeightStream;
|
private ProceduralStream<Double> riverCarveWeightStream;
|
||||||
@@ -316,7 +317,8 @@ public class IrisComplex implements DataProvider {
|
|||||||
(x, z) -> naturalHeightStream.getDouble(x, z),
|
(x, z) -> naturalHeightStream.getDouble(x, z),
|
||||||
(x, z) -> describeNaturalHeight(engine, x, z),
|
(x, z) -> describeNaturalHeight(engine, x, z),
|
||||||
this::sampleNaturalOcean,
|
this::sampleNaturalOcean,
|
||||||
footprint -> new MantleHydrologyCaveVoxelView(engine, this, footprint)
|
footprint -> new MantleHydrologyCaveVoxelView(engine, this, footprint),
|
||||||
|
() -> engine.getPlatformHooks().isMainThread()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
heightStream = ProceduralStream.ofDouble((x, z) -> resolveHydrologyTerrainHeight(x, z))
|
heightStream = ProceduralStream.ofDouble((x, z) -> resolveHydrologyTerrainHeight(x, z))
|
||||||
@@ -325,6 +327,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
.round();
|
.round();
|
||||||
slopeStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
|
slopeStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
|
||||||
.slope(3).cache2DDouble("slopeStream", engine, cacheSize);
|
.slope(3).cache2DDouble("slopeStream", engine, cacheSize);
|
||||||
|
naturalSlopeStream = naturalHeightStream.slope(3).cache2DDouble("naturalSlopeStream", engine, cacheSize);
|
||||||
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
|
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
|
||||||
b -> focusBiome))
|
b -> focusBiome))
|
||||||
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
|
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
|
||||||
@@ -704,6 +707,11 @@ public class IrisComplex implements DataProvider {
|
|||||||
return (int) Math.round(naturalHeightStream.getDouble(x, z));
|
return (int) Math.round(naturalHeightStream.getDouble(x, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The terrain slope of a column from the natural height stream only: never touches hydrology or its caches. */
|
||||||
|
public double naturalSlope(int x, int z) {
|
||||||
|
return naturalSlopeStream.getDouble(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
/** The surface biome a column would have without any river content, from natural streams only. */
|
/** The surface biome a column would have without any river content, from natural streams only. */
|
||||||
public IrisBiome naturalSurfaceBiome(int x, int z) {
|
public IrisBiome naturalSurfaceBiome(int x, int z) {
|
||||||
IrisBiome mapped = imageMapRuntime.sampleBiome(x, z);
|
IrisBiome mapped = imageMapRuntime.sampleBiome(x, z);
|
||||||
|
|||||||
@@ -348,6 +348,15 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
return getPlatformHooks().isMainThread() && !getComplex().isHydrologyPlanned(x, z);
|
return getPlatformHooks().isMainThread() && !getComplex().isHydrologyPlanned(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The terrain slope at a column, answered from natural terrain when the server thread may not wait. */
|
||||||
|
@BlockCoordinates
|
||||||
|
default double getSlope(int x, int z) {
|
||||||
|
if (answersFromNaturalTerrain(x, z)) {
|
||||||
|
return getComplex().naturalSlope(x, z);
|
||||||
|
}
|
||||||
|
return getComplex().getSlopeStream().getDouble(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
default int getHeight(int x, int z) {
|
default int getHeight(int x, int z) {
|
||||||
return getHeight(x, z, true);
|
return getHeight(x, z, true);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ForkJoinWorkerThread;
|
import java.util.concurrent.ForkJoinWorkerThread;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
public final class HydrologyTileCache implements AutoCloseable {
|
public final class HydrologyTileCache implements AutoCloseable {
|
||||||
private static final int DEFAULT_MAXIMUM_ENTRIES = 64;
|
private static final int DEFAULT_MAXIMUM_ENTRIES = 64;
|
||||||
@@ -31,6 +32,7 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
private final ThreadLocal<LocalChunkColumns> localChunkColumns;
|
private final ThreadLocal<LocalChunkColumns> localChunkColumns;
|
||||||
private final Executor prefetchExecutor;
|
private final Executor prefetchExecutor;
|
||||||
private final ConcurrentHashMap<HydrologyTileKey, CompletableFuture<HydrologyTile>> planning;
|
private final ConcurrentHashMap<HydrologyTileKey, CompletableFuture<HydrologyTile>> planning;
|
||||||
|
private final BooleanSupplier waitingForbidden;
|
||||||
|
|
||||||
public HydrologyTileCache(HydrologyPlanner planner) {
|
public HydrologyTileCache(HydrologyPlanner planner) {
|
||||||
this(planner, DEFAULT_MAXIMUM_ENTRIES);
|
this(planner, DEFAULT_MAXIMUM_ENTRIES);
|
||||||
@@ -40,13 +42,26 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
this(planner, maximumEntries, null);
|
this(planner, maximumEntries, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HydrologyTileCache(HydrologyPlanner planner, int maximumEntries, Executor prefetchExecutor) {
|
||||||
|
this(planner, maximumEntries, prefetchExecutor, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param prefetchExecutor runs neighbour tile planning ahead of generation, or null to plan
|
* @param prefetchExecutor runs neighbour tile planning ahead of generation, or null to plan
|
||||||
* tiles only when a chunk needs them
|
* tiles only when a chunk needs them
|
||||||
|
* @param waitingForbidden true on a thread that must never wait for a cold plan, so a sample
|
||||||
|
* from it is answered as "no hydrology here" while the tiles it needs
|
||||||
|
* are handed to the prefetch executor; null lets every caller wait
|
||||||
*/
|
*/
|
||||||
public HydrologyTileCache(HydrologyPlanner planner, int maximumEntries, Executor prefetchExecutor) {
|
public HydrologyTileCache(
|
||||||
|
HydrologyPlanner planner,
|
||||||
|
int maximumEntries,
|
||||||
|
Executor prefetchExecutor,
|
||||||
|
BooleanSupplier waitingForbidden
|
||||||
|
) {
|
||||||
this.planner = Objects.requireNonNull(planner, "planner");
|
this.planner = Objects.requireNonNull(planner, "planner");
|
||||||
this.prefetchExecutor = prefetchExecutor;
|
this.prefetchExecutor = prefetchExecutor;
|
||||||
|
this.waitingForbidden = waitingForbidden;
|
||||||
this.planning = new ConcurrentHashMap<>();
|
this.planning = new ConcurrentHashMap<>();
|
||||||
if (maximumEntries < 1) {
|
if (maximumEntries < 1) {
|
||||||
throw new IllegalArgumentException("maximumEntries must be positive.");
|
throw new IllegalArgumentException("maximumEntries must be positive.");
|
||||||
@@ -149,17 +164,31 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
if (composedChunks.getIfPresent(CacheKey.mix(RiverFootprint.pack(chunkX, chunkZ))) != null) {
|
if (composedChunks.getIfPresent(CacheKey.mix(RiverFootprint.pack(chunkX, chunkZ))) != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
boolean planned = true;
|
return !requestUnplanned(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hands every tile the chunk needs and does not have to the prefetch executor and says whether
|
||||||
|
* any was missing. Nothing is planned on the caller, so this is safe from a thread that must
|
||||||
|
* never wait for a cold plan.
|
||||||
|
*/
|
||||||
|
private boolean requestUnplanned(int chunkX, int chunkZ) {
|
||||||
|
boolean missing = false;
|
||||||
for (HydrologyTileKey key : relevantKeys(chunkX, chunkZ)) {
|
for (HydrologyTileKey key : relevantKeys(chunkX, chunkZ)) {
|
||||||
if (tiles.getIfPresent(key) != null) {
|
if (tiles.getIfPresent(key) != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
planned = false;
|
missing = true;
|
||||||
if (prefetchExecutor != null) {
|
if (prefetchExecutor != null) {
|
||||||
prefetch(key);
|
prefetch(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return planned;
|
return missing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the calling thread must never wait for a cold hydrology plan. */
|
||||||
|
private boolean waitsForbidden() {
|
||||||
|
return waitingForbidden != null && waitingForbidden.getAsBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HydrologyRenderSample renderAt(int blockX, int blockZ) {
|
public HydrologyRenderSample renderAt(int blockX, int blockZ) {
|
||||||
@@ -206,6 +235,14 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
return local.columns();
|
return local.columns();
|
||||||
}
|
}
|
||||||
long packedChunk = CacheKey.mix(RiverFootprint.pack(chunkX, chunkZ));
|
long packedChunk = CacheKey.mix(RiverFootprint.pack(chunkX, chunkZ));
|
||||||
|
if (composedChunks.getIfPresent(packedChunk) == null
|
||||||
|
&& waitsForbidden()
|
||||||
|
&& requestUnplanned(chunkX, chunkZ)) {
|
||||||
|
// A thread that must never wait (the server thread answering a query) gets a riverless
|
||||||
|
// answer while the tiles it would have waited for are planned on the prefetch executor.
|
||||||
|
// The empty result is never cached, so the next query composes the real columns.
|
||||||
|
return ChunkColumns.empty(chunkX, chunkZ);
|
||||||
|
}
|
||||||
ChunkColumns columns = composedChunks.get(
|
ChunkColumns columns = composedChunks.get(
|
||||||
packedChunk,
|
packedChunk,
|
||||||
ignored -> composeChunkColumns(chunkX, chunkZ)
|
ignored -> composeChunkColumns(chunkX, chunkZ)
|
||||||
@@ -467,6 +504,11 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private record ChunkColumns(int chunkX, int chunkZ, HydrologyColumnSample[] columns) {
|
private record ChunkColumns(int chunkX, int chunkZ, HydrologyColumnSample[] columns) {
|
||||||
|
/** A chunk with no hydrology at all, the answer for a caller that may not wait for its plan. */
|
||||||
|
private static ChunkColumns empty(int chunkX, int chunkZ) {
|
||||||
|
return new ChunkColumns(chunkX, chunkZ, new HydrologyColumnSample[CHUNK_COLUMN_COUNT]);
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<HydrologyColumnSample> columnAt(int blockX, int blockZ) {
|
private Optional<HydrologyColumnSample> columnAt(int blockX, int blockZ) {
|
||||||
int localX = Math.floorMod(blockX, CHUNK_SIZE);
|
int localX = Math.floorMod(blockX, CHUNK_SIZE);
|
||||||
int localZ = Math.floorMod(blockZ, CHUNK_SIZE);
|
int localZ = Math.floorMod(blockZ, CHUNK_SIZE);
|
||||||
|
|||||||
+6
-1
@@ -125,7 +125,12 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
|
|||||||
0,
|
0,
|
||||||
context.caveViewFactory()
|
context.caveViewFactory()
|
||||||
);
|
);
|
||||||
this.cache = new HydrologyTileCache(planner, MAXIMUM_CACHE_TILES, IrisPlatforms.isBound() ? MultiBurst.hydrology : null);
|
this.cache = new HydrologyTileCache(
|
||||||
|
planner,
|
||||||
|
MAXIMUM_CACHE_TILES,
|
||||||
|
IrisPlatforms.isBound() ? MultiBurst.hydrology : null,
|
||||||
|
context.waitingForbidden()
|
||||||
|
);
|
||||||
IrisLogging.debug("Hydrology runtime: tileSize=%d publicationRadius=%d planningThreads=%d",
|
IrisLogging.debug("Hydrology runtime: tileSize=%d publicationRadius=%d planningThreads=%d",
|
||||||
settings.routing().tileSize(), settings.publicationRadius(), MultiBurst.hydrology.parallelism());
|
settings.routing().tileSize(), settings.publicationRadius(), MultiBurst.hydrology.parallelism());
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -5,6 +5,7 @@ import art.arcane.iris.engine.hydrology.HydrologyCaveVoxelViewFactory;
|
|||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
public record IrisHydrologyRuntimeContext(
|
public record IrisHydrologyRuntimeContext(
|
||||||
long seed,
|
long seed,
|
||||||
@@ -15,8 +16,14 @@ public record IrisHydrologyRuntimeContext(
|
|||||||
IrisHydrologyNaturalHeightProvider naturalHeightProvider,
|
IrisHydrologyNaturalHeightProvider naturalHeightProvider,
|
||||||
IrisHydrologyNaturalHeightDescriber naturalHeightDescriber,
|
IrisHydrologyNaturalHeightDescriber naturalHeightDescriber,
|
||||||
IrisHydrologyNaturalOceanClassifier naturalOceanClassifier,
|
IrisHydrologyNaturalOceanClassifier naturalOceanClassifier,
|
||||||
HydrologyCaveVoxelViewFactory caveViewFactory
|
HydrologyCaveVoxelViewFactory caveViewFactory,
|
||||||
|
BooleanSupplier waitingForbidden
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* {@code waitingForbidden} is true on a thread that must never wait for a cold hydrology plan,
|
||||||
|
* the server thread above all: a sample taken there is answered as "no hydrology here" while the
|
||||||
|
* tiles it needs are planned on the prefetch pool.
|
||||||
|
*/
|
||||||
public IrisHydrologyRuntimeContext {
|
public IrisHydrologyRuntimeContext {
|
||||||
if (worldHeight < 3) {
|
if (worldHeight < 3) {
|
||||||
throw new IllegalArgumentException("worldHeight must be at least three");
|
throw new IllegalArgumentException("worldHeight must be at least three");
|
||||||
@@ -28,5 +35,6 @@ public record IrisHydrologyRuntimeContext(
|
|||||||
Objects.requireNonNull(naturalHeightDescriber);
|
Objects.requireNonNull(naturalHeightDescriber);
|
||||||
Objects.requireNonNull(naturalOceanClassifier);
|
Objects.requireNonNull(naturalOceanClassifier);
|
||||||
Objects.requireNonNull(caveViewFactory);
|
Objects.requireNonNull(caveViewFactory);
|
||||||
|
Objects.requireNonNull(waitingForbidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,8 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
if (k.getSpawnChance() < rng.d())
|
if (k.getSpawnChance() < rng.d())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
boolean oreDeposit = k.isOre(getData());
|
||||||
|
|
||||||
for (int l = 0; l < rng.i(k.getMinPerChunk(), k.getMaxPerChunk() + 1); l++) {
|
for (int l = 0; l < rng.i(k.getMinPerChunk(), k.getMaxPerChunk() + 1); l++) {
|
||||||
if (k.getPerClumpSpawnChance() < rng.d())
|
if (k.getPerClumpSpawnChance() < rng.d())
|
||||||
continue;
|
continue;
|
||||||
@@ -137,7 +139,6 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
int biomeY = Math.max(0, Math.min(getEngine().getHeight() - 1, y));
|
int biomeY = Math.max(0, Math.min(getEngine().getHeight() - 1, y));
|
||||||
boolean oreDeposit = k.isOre(getData());
|
|
||||||
IrisBiome surfaceBiome = context.getBiome().get(x, z);
|
IrisBiome surfaceBiome = context.getBiome().get(x, z);
|
||||||
IrisBiome depositBiome = oreDeposit || k.usesCaveBiomeFilter()
|
IrisBiome depositBiome = oreDeposit || k.usesCaveBiomeFilter()
|
||||||
? getEngine().getCaveBiome(
|
? getEngine().getCaveBiome(
|
||||||
@@ -189,14 +190,23 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
if (!canReplaceDepositTarget(current)) {
|
if (!canReplaceDepositTarget(current)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!k.canReplace(current)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!k.isReplaceBedrock() && IrisProceduralBlocks.materialKey(current).equals("minecraft:bedrock")) {
|
if (!k.isReplaceBedrock() && IrisProceduralBlocks.materialKey(current).equals("minecraft:bedrock")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (shouldDiscardExposed(
|
|
||||||
k.getDiscardChanceOnAirExposure(), rng.d(), isAdjacentToAir(data, nx, ny, nz))) {
|
IrisBiome candidateSurfaceBiome = null;
|
||||||
|
boolean exteriorSurface = false;
|
||||||
|
if (oreDeposit) {
|
||||||
|
candidateSurfaceBiome = context.getBiome().get(nx, nz);
|
||||||
|
exteriorSurface = k.hasSurfaceReplaceableBlocks(candidateSurfaceBiome)
|
||||||
|
&& isTerrainSurface(data, nx, ny, nz, columnSurface);
|
||||||
|
}
|
||||||
|
if (!canReplaceDepositHost(k, current, candidateSurfaceBiome, exteriorSurface)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean adjacentToAir = k.getDiscardChanceOnAirExposure() > 0D
|
||||||
|
&& isAdjacentToAir(data, nx, ny, nz);
|
||||||
|
if (shouldDiscardExposed(k.getDiscardChanceOnAirExposure(), rng.d(), adjacentToAir)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +257,15 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
return state != null && !state.isAir() && !state.isFluid();
|
return state != null && !state.isAir() && !state.isFluid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean canReplaceDepositHost(
|
||||||
|
IrisDepositGenerator generator, PlatformBlockState state,
|
||||||
|
IrisBiome surfaceBiome, boolean terrainSurface) {
|
||||||
|
if (terrainSurface && generator.hasSurfaceReplaceableBlocks(surfaceBiome)) {
|
||||||
|
return generator.canReplaceSurface(state, surfaceBiome);
|
||||||
|
}
|
||||||
|
return generator.canReplace(state);
|
||||||
|
}
|
||||||
|
|
||||||
static int sampleHeight(
|
static int sampleHeight(
|
||||||
IrisDepositHeightDistribution distribution, RNG rng,
|
IrisDepositHeightDistribution distribution, RNG rng,
|
||||||
int configuredMinimum, int configuredMaximum, int clippedMaximum) {
|
int configuredMinimum, int configuredMaximum, int clippedMaximum) {
|
||||||
@@ -276,6 +295,17 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
return adjacentToAir && chance > 0D && sample < Math.min(1D, chance);
|
return adjacentToAir && chance > 0D && sample < Math.min(1D, chance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isTerrainSurface(
|
||||||
|
Hunk<PlatformBlockState> data, int x, int y, int z, int columnSurface) {
|
||||||
|
return y == columnSurface
|
||||||
|
|| isExteriorAirAt(data, x - 1, y, z)
|
||||||
|
|| isExteriorAirAt(data, x + 1, y, z)
|
||||||
|
|| isExteriorAirAt(data, x, y - 1, z)
|
||||||
|
|| isExteriorAirAt(data, x, y + 1, z)
|
||||||
|
|| isExteriorAirAt(data, x, y, z - 1)
|
||||||
|
|| isExteriorAirAt(data, x, y, z + 1);
|
||||||
|
}
|
||||||
|
|
||||||
static boolean isAdjacentToAir(Hunk<PlatformBlockState> data, int x, int y, int z) {
|
static boolean isAdjacentToAir(Hunk<PlatformBlockState> data, int x, int y, int z) {
|
||||||
return isAirAt(data, x - 1, y, z)
|
return isAirAt(data, x - 1, y, z)
|
||||||
|| isAirAt(data, x + 1, y, z)
|
|| isAirAt(data, x + 1, y, z)
|
||||||
@@ -295,6 +325,18 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
return state == null || state.isAir();
|
return state == null || state.isAir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isExteriorAirAt(Hunk<PlatformBlockState> data, int x, int y, int z) {
|
||||||
|
if (x < 0 || x >= data.getWidth()
|
||||||
|
|| y < 0 || y >= data.getHeight()
|
||||||
|
|| z < 0 || z >= data.getDepth()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PlatformBlockState state = data.getRaw(x, y, z);
|
||||||
|
return state == null
|
||||||
|
|| (state.isAir()
|
||||||
|
&& !"minecraft:cave_air".equals(IrisProceduralBlocks.materialKey(state)));
|
||||||
|
}
|
||||||
|
|
||||||
static boolean passesOreFrequency(double multiplier, double sample) {
|
static boolean passesOreFrequency(double multiplier, double sample) {
|
||||||
return multiplier >= 1D || sample < Math.max(0D, multiplier);
|
return multiplier >= 1D || sample < Math.max(0D, multiplier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
|||||||
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
|
||||||
|
private final transient AtomicCache<KSet<String>> surfaceOreReplaceableBlockData = new AtomicCache<>();
|
||||||
private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>();
|
private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>();
|
||||||
private final transient AtomicCache<Biome> derivativeResolved = new AtomicCache<>();
|
private final transient AtomicCache<Biome> derivativeResolved = new AtomicCache<>();
|
||||||
private final transient AtomicCache<Biome> vanillaDerivativeResolved = new AtomicCache<>();
|
private final transient AtomicCache<Biome> vanillaDerivativeResolved = new AtomicCache<>();
|
||||||
@@ -207,6 +208,9 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
|||||||
@ArrayType(min = 1, type = IrisDepositGenerator.class)
|
@ArrayType(min = 1, type = IrisDepositGenerator.class)
|
||||||
@Desc("Define biome deposit generators that add onto the existing regional and global deposit generators")
|
@Desc("Define biome deposit generators that add onto the existing regional and global deposit generators")
|
||||||
private KList<IrisDepositGenerator> deposits = new KList<>();
|
private KList<IrisDepositGenerator> deposits = new KList<>();
|
||||||
|
@ArrayType(type = String.class)
|
||||||
|
@Desc("Optional exact host block ids that replace each ore deposit's surfaceReplaceableBlocks on exterior terrain inside this biome. Omit to inherit the deposit setting; an empty list forbids surface ore.")
|
||||||
|
private KList<String> surfaceOreReplaceableBlocks = null;
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
@MaxNumber(1)
|
@MaxNumber(1)
|
||||||
@Desc("Multiplier applied to the frequency of every ore deposit whose center is inside this biome. A value of 0.4 keeps 40% of ore veins while leaving non-ore deposits unchanged.")
|
@Desc("Multiplier applied to the frequency of every ore deposit whose center is inside this biome. A value of 0.4 keeps 40% of ore veins while leaving non-ore deposits unchanged.")
|
||||||
@@ -226,6 +230,14 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
|||||||
@ArrayType(type = IrisOreGenerator.class, min = 1)
|
@ArrayType(type = IrisOreGenerator.class, min = 1)
|
||||||
private KList<IrisOreGenerator> ores = new KList<>();
|
private KList<IrisOreGenerator> ores = new KList<>();
|
||||||
|
|
||||||
|
public boolean hasSurfaceOreReplaceableBlocks() {
|
||||||
|
return IrisBiomeOres.hasSurfaceOreReplaceableBlocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canReplaceSurfaceOre(PlatformBlockState state) {
|
||||||
|
return IrisBiomeOres.canReplaceSurfaceOre(this, state);
|
||||||
|
}
|
||||||
|
|
||||||
public PlatformBlockState generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) {
|
public PlatformBlockState generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) {
|
||||||
return IrisBiomeOres.generateOres(this, x, y, z, rng, data, surface);
|
return IrisBiomeOres.generateOres(this, x, y, z, rng, data, surface);
|
||||||
}
|
}
|
||||||
@@ -299,6 +311,12 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
|||||||
undergroundOreBoundsCache.reset();
|
undergroundOreBoundsCache.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IrisBiome setSurfaceOreReplaceableBlocks(KList<String> surfaceOreReplaceableBlocks) {
|
||||||
|
this.surfaceOreReplaceableBlocks = surfaceOreReplaceableBlocks;
|
||||||
|
surfaceOreReplaceableBlockData.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getSurfaceOreGenerators() {
|
public KList<IrisOreGenerator> getSurfaceOreGenerators() {
|
||||||
return IrisBiomeOres.getSurfaceOreGenerators(this);
|
return IrisBiomeOres.getSurfaceOreGenerators(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ package art.arcane.iris.engine.object;
|
|||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.util.common.data.B;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.collection.KSet;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,6 +56,19 @@ final class IrisBiomeOres {
|
|||||||
return !getUndergroundOres(biome).isEmpty();
|
return !getUndergroundOres(biome).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean hasSurfaceOreReplaceableBlocks(IrisBiome biome) {
|
||||||
|
return biome.getSurfaceOreReplaceableBlocks() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean canReplaceSurfaceOre(IrisBiome biome, PlatformBlockState state) {
|
||||||
|
if (!hasSurfaceOreReplaceableBlocks(biome)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return biome.getSurfaceOreReplaceableBlockData()
|
||||||
|
.aquire(() -> resolveSurfaceOreReplaceableBlocks(biome))
|
||||||
|
.contains(IrisProceduralBlocks.materialKey(state));
|
||||||
|
}
|
||||||
|
|
||||||
static KList<IrisOreGenerator> getSurfaceOreGenerators(IrisBiome biome) {
|
static KList<IrisOreGenerator> getSurfaceOreGenerators(IrisBiome biome) {
|
||||||
return getOres(biome, true);
|
return getOres(biome, true);
|
||||||
}
|
}
|
||||||
@@ -126,4 +141,15 @@ final class IrisBiomeOres {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static KSet<String> resolveSurfaceOreReplaceableBlocks(IrisBiome biome) {
|
||||||
|
KSet<String> resolved = new KSet<>();
|
||||||
|
for (String key : biome.getSurfaceOreReplaceableBlocks()) {
|
||||||
|
PlatformBlockState state = B.getStateOrNull(key, false);
|
||||||
|
if (state != null) {
|
||||||
|
resolved.add(IrisProceduralBlocks.materialKey(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,7 @@ public class IrisDepositGenerator {
|
|||||||
private final transient AtomicCache<KList<PlatformBlockState>> blockData = new AtomicCache<>();
|
private final transient AtomicCache<KList<PlatformBlockState>> blockData = new AtomicCache<>();
|
||||||
private final transient AtomicCache<Boolean> ore = new AtomicCache<>();
|
private final transient AtomicCache<Boolean> ore = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KSet<String>> replaceableBlockData = new AtomicCache<>();
|
private final transient AtomicCache<KSet<String>> replaceableBlockData = new AtomicCache<>();
|
||||||
|
private final transient AtomicCache<KSet<String>> surfaceReplaceableBlockData = new AtomicCache<>();
|
||||||
private final transient ConcurrentMap<ClumpCacheKey, KList<IrisObject>> scaledObjects = new ConcurrentHashMap<>();
|
private final transient ConcurrentMap<ClumpCacheKey, KList<IrisObject>> scaledObjects = new ConcurrentHashMap<>();
|
||||||
@Required
|
@Required
|
||||||
@MinNumber(-8192)
|
@MinNumber(-8192)
|
||||||
@@ -120,6 +121,9 @@ public class IrisDepositGenerator {
|
|||||||
@ArrayType(min = 1, type = String.class)
|
@ArrayType(min = 1, type = String.class)
|
||||||
@Desc("Optional block ids this deposit may replace. An empty list retains Iris's any-solid behavior.")
|
@Desc("Optional block ids this deposit may replace. An empty list retains Iris's any-solid behavior.")
|
||||||
private KList<String> replaceableBlocks = new KList<>();
|
private KList<String> replaceableBlocks = new KList<>();
|
||||||
|
@ArrayType(min = 1, type = String.class)
|
||||||
|
@Desc("Optional exact host block ids required when an ore candidate occupies an exterior terrain surface. Cave-air walls are unaffected. An empty list retains the normal replaceableBlocks behavior.")
|
||||||
|
private KList<String> surfaceReplaceableBlocks = new KList<>();
|
||||||
@Desc("Chooses whether includedBiomes and excludedBiomes inspect the surface biome or the cave biome at the deposit origin.")
|
@Desc("Chooses whether includedBiomes and excludedBiomes inspect the surface biome or the cave biome at the deposit origin.")
|
||||||
private IrisDepositBiomeScope biomeScope = IrisDepositBiomeScope.CAVE;
|
private IrisDepositBiomeScope biomeScope = IrisDepositBiomeScope.CAVE;
|
||||||
@ArrayType(min = 1, type = String.class)
|
@ArrayType(min = 1, type = String.class)
|
||||||
@@ -476,6 +480,36 @@ public class IrisDepositGenerator {
|
|||||||
.contains(IrisProceduralBlocks.materialKey(state));
|
.contains(IrisProceduralBlocks.materialKey(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IrisDepositGenerator setSurfaceReplaceableBlocks(KList<String> surfaceReplaceableBlocks) {
|
||||||
|
this.surfaceReplaceableBlocks = surfaceReplaceableBlocks == null
|
||||||
|
? new KList<>()
|
||||||
|
: surfaceReplaceableBlocks;
|
||||||
|
surfaceReplaceableBlockData.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canReplaceSurface(PlatformBlockState state) {
|
||||||
|
if (surfaceReplaceableBlocks == null || surfaceReplaceableBlocks.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return surfaceReplaceableBlockData.aquire(this::resolveSurfaceReplaceableBlocks)
|
||||||
|
.contains(IrisProceduralBlocks.materialKey(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSurfaceReplaceableBlocks(IrisBiome surfaceBiome) {
|
||||||
|
if (surfaceBiome != null && surfaceBiome.hasSurfaceOreReplaceableBlocks()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return surfaceReplaceableBlocks != null && !surfaceReplaceableBlocks.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canReplaceSurface(PlatformBlockState state, IrisBiome surfaceBiome) {
|
||||||
|
if (surfaceBiome != null && surfaceBiome.hasSurfaceOreReplaceableBlocks()) {
|
||||||
|
return surfaceBiome.canReplaceSurfaceOre(state);
|
||||||
|
}
|
||||||
|
return canReplaceSurface(state);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean matchesBiome(IrisBiome surfaceBiome, IrisBiome caveBiome) {
|
public boolean matchesBiome(IrisBiome surfaceBiome, IrisBiome caveBiome) {
|
||||||
IrisBiome selected = biomeScope == IrisDepositBiomeScope.SURFACE ? surfaceBiome : caveBiome;
|
IrisBiome selected = biomeScope == IrisDepositBiomeScope.SURFACE ? surfaceBiome : caveBiome;
|
||||||
if (includedBiomes != null && !includedBiomes.isEmpty() && !matchesAnyBiome(selected, includedBiomes)) {
|
if (includedBiomes != null && !includedBiomes.isEmpty() && !matchesAnyBiome(selected, includedBiomes)) {
|
||||||
@@ -491,8 +525,16 @@ public class IrisDepositGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private KSet<String> resolveReplaceableBlocks() {
|
private KSet<String> resolveReplaceableBlocks() {
|
||||||
|
return resolveBlockKeys(replaceableBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KSet<String> resolveSurfaceReplaceableBlocks() {
|
||||||
|
return resolveBlockKeys(surfaceReplaceableBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KSet<String> resolveBlockKeys(KList<String> keys) {
|
||||||
KSet<String> resolved = new KSet<>();
|
KSet<String> resolved = new KSet<>();
|
||||||
for (String key : replaceableBlocks) {
|
for (String key : keys) {
|
||||||
PlatformBlockState state = B.getStateOrNull(key, false);
|
PlatformBlockState state = B.getStateOrNull(key, false);
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
resolved.add(IrisProceduralBlocks.materialKey(state));
|
resolved.add(IrisProceduralBlocks.materialKey(state));
|
||||||
|
|||||||
+5
-5
@@ -16,9 +16,11 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.mockStatic;
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -91,12 +93,11 @@ public class WorldRuntimeControlServiceTeleportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void successfulModeTeleportTemporarilyReducesAndRestoresViewDistance() {
|
public void teleportNeverTouchesThePlayersViewDistance() {
|
||||||
Player player = mock(Player.class);
|
Player player = mock(Player.class);
|
||||||
Location destination = mock(Location.class);
|
Location destination = mock(Location.class);
|
||||||
CompletableFuture<Boolean> nativeTeleport = new CompletableFuture<>();
|
CompletableFuture<Boolean> nativeTeleport = new CompletableFuture<>();
|
||||||
when(player.getGameMode()).thenReturn(GameMode.SURVIVAL);
|
when(player.getGameMode()).thenReturn(GameMode.SURVIVAL);
|
||||||
when(player.getViewDistance()).thenReturn(12);
|
|
||||||
|
|
||||||
try (MockedStatic<J> scheduling = immediateEntityScheduling()) {
|
try (MockedStatic<J> scheduling = immediateEntityScheduling()) {
|
||||||
CompletableFuture<Boolean> result = WorldRuntimeControlService.scheduleTeleport(
|
CompletableFuture<Boolean> result = WorldRuntimeControlService.scheduleTeleport(
|
||||||
@@ -107,10 +108,9 @@ public class WorldRuntimeControlServiceTeleportTest {
|
|||||||
nativeTeleport.complete(true);
|
nativeTeleport.complete(true);
|
||||||
|
|
||||||
assertTrue(result.join());
|
assertTrue(result.join());
|
||||||
InOrder viewDistances = inOrder(player);
|
|
||||||
viewDistances.verify(player).setViewDistance(2);
|
|
||||||
viewDistances.verify(player).setViewDistance(12);
|
|
||||||
verify(player).setGameMode(GameMode.SPECTATOR);
|
verify(player).setGameMode(GameMode.SPECTATOR);
|
||||||
|
verify(player, never()).setViewDistance(anyInt());
|
||||||
|
verify(player, never()).getViewDistance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -661,4 +662,44 @@ public class HydrologyTileCacheTest {
|
|||||||
cache.columnAt(40, 40);
|
cache.columnAt(40, 40);
|
||||||
assertTrue(cache.isPlanned(40, 40));
|
assertTrue(cache.isPlanned(40, 40));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aThreadThatMayNotWaitSamplesRiverlesslyAndAsksThePrefetchExecutorInstead() {
|
||||||
|
HydrologyPlanner planner = mock(HydrologyPlanner.class);
|
||||||
|
HydrologyTile tile = mock(HydrologyTile.class);
|
||||||
|
when(planner.settings()).thenReturn(emptySettings());
|
||||||
|
when(planner.plan(any(HydrologyTileKey.class))).thenReturn(tile);
|
||||||
|
when(tile.columnAt(anyInt(), anyInt())).thenReturn(Optional.empty());
|
||||||
|
List<Runnable> queued = new ArrayList<>();
|
||||||
|
AtomicBoolean forbidden = new AtomicBoolean(true);
|
||||||
|
HydrologyTileCache cache = new HydrologyTileCache(planner, 64, queued::add, forbidden::get);
|
||||||
|
|
||||||
|
assertTrue(cache.columnAt(40, 40).isEmpty());
|
||||||
|
verify(planner, org.mockito.Mockito.never()).plan(any(HydrologyTileKey.class));
|
||||||
|
assertFalse(queued.isEmpty());
|
||||||
|
|
||||||
|
for (Runnable task : List.copyOf(queued)) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the plan lands the same thread composes the real columns instead of the empty answer.
|
||||||
|
assertTrue(cache.isPlanned(40, 40));
|
||||||
|
assertTrue(cache.columnAt(40, 40).isEmpty());
|
||||||
|
verify(tile, org.mockito.Mockito.atLeastOnce()).columnAt(anyInt(), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aThreadThatMayWaitStillPlansTheTilesItNeeds() {
|
||||||
|
HydrologyPlanner planner = mock(HydrologyPlanner.class);
|
||||||
|
HydrologyTile tile = mock(HydrologyTile.class);
|
||||||
|
when(planner.settings()).thenReturn(emptySettings());
|
||||||
|
when(planner.plan(any(HydrologyTileKey.class))).thenReturn(tile);
|
||||||
|
when(tile.columnAt(anyInt(), anyInt())).thenReturn(Optional.empty());
|
||||||
|
AtomicBoolean forbidden = new AtomicBoolean(false);
|
||||||
|
HydrologyTileCache cache = new HydrologyTileCache(planner, 64, null, forbidden::get);
|
||||||
|
|
||||||
|
assertTrue(cache.columnAt(40, 40).isEmpty());
|
||||||
|
verify(planner, org.mockito.Mockito.atLeastOnce()).plan(any(HydrologyTileKey.class));
|
||||||
|
assertTrue(cache.isPlanned(40, 40));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+47
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.modifier;
|
package art.arcane.iris.engine.modifier;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisDepositGenerator;
|
||||||
import art.arcane.iris.engine.object.IrisDepositHeightDistribution;
|
import art.arcane.iris.engine.object.IrisDepositHeightDistribution;
|
||||||
import art.arcane.iris.engine.object.IrisDepositPlacementScope;
|
import art.arcane.iris.engine.object.IrisDepositPlacementScope;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
@@ -75,6 +77,24 @@ public class IrisDepositModifierContainmentTest {
|
|||||||
assertFalse(IrisDepositModifier.canReplaceDepositTarget(null));
|
assertFalse(IrisDepositModifier.canReplaceDepositTarget(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void surfacePolicyReplacesBuriedHostPolicyWithoutAffectingCaves() {
|
||||||
|
IrisDepositGenerator generator = mock(IrisDepositGenerator.class);
|
||||||
|
IrisBiome biome = mock(IrisBiome.class);
|
||||||
|
PlatformBlockState sand = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState basalt = mock(PlatformBlockState.class);
|
||||||
|
when(generator.hasSurfaceReplaceableBlocks(biome)).thenReturn(true);
|
||||||
|
when(generator.canReplaceSurface(sand, biome)).thenReturn(true);
|
||||||
|
when(generator.canReplace(sand)).thenReturn(false);
|
||||||
|
when(generator.canReplaceSurface(basalt, biome)).thenReturn(false);
|
||||||
|
when(generator.canReplace(basalt)).thenReturn(true);
|
||||||
|
|
||||||
|
assertTrue(IrisDepositModifier.canReplaceDepositHost(generator, sand, biome, true));
|
||||||
|
assertFalse(IrisDepositModifier.canReplaceDepositHost(generator, sand, biome, false));
|
||||||
|
assertFalse(IrisDepositModifier.canReplaceDepositHost(generator, basalt, biome, true));
|
||||||
|
assertTrue(IrisDepositModifier.canReplaceDepositHost(generator, basalt, biome, false));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void oreFrequencyMultiplierKeepsConfiguredShareOfVeins() {
|
public void oreFrequencyMultiplierKeepsConfiguredShareOfVeins() {
|
||||||
assertTrue(IrisDepositModifier.passesOreFrequency(0.4D, 0.399D));
|
assertTrue(IrisDepositModifier.passesOreFrequency(0.4D, 0.399D));
|
||||||
@@ -153,6 +173,33 @@ public class IrisDepositModifierContainmentTest {
|
|||||||
assertFalse(IrisDepositModifier.shouldDiscardExposed(0D, 0D, true));
|
assertFalse(IrisDepositModifier.shouldDiscardExposed(0D, 0D, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void terrainSurfaceIncludesExteriorAirButExcludesCaveAir() {
|
||||||
|
Hunk<PlatformBlockState> data = Hunk.newHunk(3, 3, 3);
|
||||||
|
PlatformBlockState solid = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState exteriorAir = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState caveAir = mock(PlatformBlockState.class);
|
||||||
|
when(exteriorAir.isAir()).thenReturn(true);
|
||||||
|
when(exteriorAir.key()).thenReturn("minecraft:air");
|
||||||
|
when(caveAir.isAir()).thenReturn(true);
|
||||||
|
when(caveAir.key()).thenReturn("minecraft:cave_air");
|
||||||
|
for (int x = 0; x < 3; x++) {
|
||||||
|
for (int y = 0; y < 3; y++) {
|
||||||
|
for (int z = 0; z < 3; z++) {
|
||||||
|
data.setRaw(x, y, z, solid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(IrisDepositModifier.isTerrainSurface(data, 1, 1, 1, 1));
|
||||||
|
assertFalse(IrisDepositModifier.isTerrainSurface(data, 1, 1, 1, 2));
|
||||||
|
data.setRaw(2, 1, 1, exteriorAir);
|
||||||
|
assertTrue(IrisDepositModifier.isTerrainSurface(data, 1, 1, 1, 2));
|
||||||
|
data.setRaw(2, 1, 1, caveAir);
|
||||||
|
assertFalse(IrisDepositModifier.isTerrainSurface(data, 1, 1, 1, 2));
|
||||||
|
assertTrue(IrisDepositModifier.isAdjacentToAir(data, 1, 1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void exposureProbeChecksOnlyInBoundsOrthogonalNeighbors() {
|
public void exposureProbeChecksOnlyInBoundsOrthogonalNeighbors() {
|
||||||
Hunk<PlatformBlockState> data = Hunk.newHunk(3, 3, 3);
|
Hunk<PlatformBlockState> data = Hunk.newHunk(3, 3, 3);
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ package art.arcane.iris.engine.object;
|
|||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
import art.arcane.iris.engine.object.annotations.Desc;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.util.common.data.B;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
@@ -12,8 +16,10 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class IrisDepositTuningTest {
|
public class IrisDepositTuningTest {
|
||||||
@@ -53,6 +59,100 @@ public class IrisDepositTuningTest {
|
|||||||
|
|
||||||
assertEquals(1D, biome.getOreDepositFrequencyMultiplier(), 0D);
|
assertEquals(1D, biome.getOreDepositFrequencyMultiplier(), 0D);
|
||||||
assertEquals(1D, biome.getOreDepositSizeMultiplier(), 0D);
|
assertEquals(1D, biome.getOreDepositSizeMultiplier(), 0D);
|
||||||
|
assertNull(biome.getSurfaceOreReplaceableBlocks());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void surfaceHostFilterDefaultsToUnrestricted() {
|
||||||
|
IrisDepositGenerator generator = new IrisDepositGenerator();
|
||||||
|
|
||||||
|
assertTrue(generator.getSurfaceReplaceableBlocks().isEmpty());
|
||||||
|
assertTrue(generator.canReplaceSurface(mock(PlatformBlockState.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void surfaceHostFilterAllowsOnlyConfiguredMaterials() {
|
||||||
|
IrisDepositGenerator generator = new IrisDepositGenerator();
|
||||||
|
generator.setSurfaceReplaceableBlocks(new KList<String>().qadd("minecraft:stone"));
|
||||||
|
PlatformBlockState stone = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState dirt = mock(PlatformBlockState.class);
|
||||||
|
when(stone.key()).thenReturn("minecraft:stone");
|
||||||
|
when(dirt.key()).thenReturn("minecraft:dirt");
|
||||||
|
|
||||||
|
try (MockedStatic<B> blocks = mockStatic(B.class)) {
|
||||||
|
blocks.when(() -> B.getStateOrNull("minecraft:stone", false)).thenReturn(stone);
|
||||||
|
blocks.when(() -> B.getStateOrNull("minecraft:dirt", false)).thenReturn(dirt);
|
||||||
|
|
||||||
|
assertTrue(generator.canReplaceSurface(stone));
|
||||||
|
assertFalse(generator.canReplaceSurface(dirt));
|
||||||
|
|
||||||
|
generator.setSurfaceReplaceableBlocks(new KList<String>().qadd("minecraft:dirt"));
|
||||||
|
assertFalse(generator.canReplaceSurface(stone));
|
||||||
|
assertTrue(generator.canReplaceSurface(dirt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void biomeSurfaceHostPolicyOverridesDepositPolicy() {
|
||||||
|
IrisDepositGenerator generator = new IrisDepositGenerator();
|
||||||
|
generator.setSurfaceReplaceableBlocks(new KList<String>().qadd("minecraft:stone"));
|
||||||
|
IrisBiome inherited = new IrisBiome();
|
||||||
|
IrisBiome desert = new IrisBiome();
|
||||||
|
desert.setSurfaceOreReplaceableBlocks(
|
||||||
|
new KList<String>().qadd("minecraft:stone").qadd("minecraft:sand"));
|
||||||
|
IrisBiome disabled = new IrisBiome();
|
||||||
|
disabled.setSurfaceOreReplaceableBlocks(new KList<>());
|
||||||
|
PlatformBlockState stone = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState sand = mock(PlatformBlockState.class);
|
||||||
|
when(stone.key()).thenReturn("minecraft:stone");
|
||||||
|
when(sand.key()).thenReturn("minecraft:sand");
|
||||||
|
|
||||||
|
try (MockedStatic<B> blocks = mockStatic(B.class)) {
|
||||||
|
blocks.when(() -> B.getStateOrNull("minecraft:stone", false)).thenReturn(stone);
|
||||||
|
blocks.when(() -> B.getStateOrNull("minecraft:sand", false)).thenReturn(sand);
|
||||||
|
|
||||||
|
assertTrue(generator.canReplaceSurface(stone, inherited));
|
||||||
|
assertFalse(generator.canReplaceSurface(sand, inherited));
|
||||||
|
assertTrue(generator.canReplaceSurface(sand, desert));
|
||||||
|
assertTrue(generator.hasSurfaceReplaceableBlocks(disabled));
|
||||||
|
assertFalse(generator.canReplaceSurface(stone, disabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void biomeSurfaceHostPolicyDistinguishesOmittedAndEmptyJson() {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
IrisBiome inherited = gson.fromJson("{\"name\":\"Inherited\"}", IrisBiome.class);
|
||||||
|
IrisBiome disabled = gson.fromJson(
|
||||||
|
"{\"name\":\"Disabled\",\"surfaceOreReplaceableBlocks\":[]}", IrisBiome.class);
|
||||||
|
|
||||||
|
assertNull(inherited.getSurfaceOreReplaceableBlocks());
|
||||||
|
assertNotNull(disabled.getSurfaceOreReplaceableBlocks());
|
||||||
|
assertTrue(disabled.getSurfaceOreReplaceableBlocks().isEmpty());
|
||||||
|
assertFalse(inherited.hasSurfaceOreReplaceableBlocks());
|
||||||
|
assertTrue(disabled.hasSurfaceOreReplaceableBlocks());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void biomeSurfaceHostPolicySetterInvalidatesResolvedHosts() {
|
||||||
|
IrisBiome biome = new IrisBiome();
|
||||||
|
PlatformBlockState stone = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState sand = mock(PlatformBlockState.class);
|
||||||
|
when(stone.key()).thenReturn("minecraft:stone");
|
||||||
|
when(sand.key()).thenReturn("minecraft:sand");
|
||||||
|
|
||||||
|
try (MockedStatic<B> blocks = mockStatic(B.class)) {
|
||||||
|
blocks.when(() -> B.getStateOrNull("minecraft:stone", false)).thenReturn(stone);
|
||||||
|
blocks.when(() -> B.getStateOrNull("minecraft:sand", false)).thenReturn(sand);
|
||||||
|
|
||||||
|
biome.setSurfaceOreReplaceableBlocks(new KList<String>().qadd("minecraft:stone"));
|
||||||
|
assertTrue(biome.canReplaceSurfaceOre(stone));
|
||||||
|
assertFalse(biome.canReplaceSurfaceOre(sand));
|
||||||
|
|
||||||
|
biome.setSurfaceOreReplaceableBlocks(new KList<String>().qadd("minecraft:sand"));
|
||||||
|
assertFalse(biome.canReplaceSurfaceOre(stone));
|
||||||
|
assertTrue(biome.canReplaceSurfaceOre(sand));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in new issue
Block a user