mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Merged biome retention
- Saved biome behavior is integrated with current Iris changes
This commit is contained in:
commit
80843114fc
57 files changed
+4460
-385
No files matched your search
+16
-2
@@ -20,6 +20,7 @@ package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.nativegen.NativeTransitionColumn;
|
||||
import art.arcane.iris.engine.history.TerrainBoundarySignature;
|
||||
import art.arcane.iris.engine.history.NativeBiomeSpawnSelection;
|
||||
import java.util.Optional;
|
||||
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
@@ -920,6 +921,11 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(
|
||||
Holder<Biome> biome, StructureManager structureManager, MobCategory category, BlockPos pos) {
|
||||
Engine current = engine();
|
||||
NativeBiomeSpawnSelection selection = NativeBiomeSpawnSelection.at(current, pos.getX(), pos.getY(), pos.getZ(),
|
||||
biome.unwrapKey().map(key -> key.identifier().toString()).orElse(""));
|
||||
if (selection.mode() == NativeBiomeSpawnSelection.Mode.LOADING) {
|
||||
return WeightedList.of(List.of());
|
||||
}
|
||||
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope = openHistoryCoordinateScope(
|
||||
current, pos.getX(), pos.getZ(), "modded_mob_spawn_table");
|
||||
GenerationSessionLease lease = requireGenerationLease(current, "modded_mob_spawn_table");
|
||||
@@ -933,8 +939,15 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
}
|
||||
|
||||
Registry<Biome> registry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
spawnTables.initializeVanillaSpawnBiomes(registry);
|
||||
Holder<Biome> vanillaSpawnBiome = spawnTables.vanillaSpawnBiome(biome.value());
|
||||
Holder<Biome> vanillaSpawnBiome;
|
||||
if (selection.mode() == NativeBiomeSpawnSelection.Mode.RETAINED) {
|
||||
vanillaSpawnBiome = spawnTables.resolveBiomeHolder(registry, selection.derivativeKey());
|
||||
} else if (selection.mode() == NativeBiomeSpawnSelection.Mode.CURRENT) {
|
||||
spawnTables.initializeVanillaSpawnBiomes(registry);
|
||||
vanillaSpawnBiome = spawnTables.vanillaSpawnBiome(biome.value());
|
||||
} else {
|
||||
vanillaSpawnBiome = null;
|
||||
}
|
||||
if (vanillaSpawnBiome == null) {
|
||||
return explicitSpawns;
|
||||
}
|
||||
@@ -951,6 +964,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
return spawnTables.mergedSpawnTable(
|
||||
current,
|
||||
biome.value(),
|
||||
vanillaSpawnBiome.value(),
|
||||
category,
|
||||
vanillaSpawns,
|
||||
explicitSpawns
|
||||
|
||||
+25
-35
@@ -20,13 +20,13 @@ package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.BiomeEnvironment;
|
||||
import art.arcane.iris.engine.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedComponent;
|
||||
import art.arcane.iris.engine.framework.EngineEffects;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisCommand;
|
||||
import art.arcane.iris.engine.object.IrisCommandRegistry;
|
||||
import art.arcane.iris.engine.object.IrisEffect;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import net.minecraft.core.Holder;
|
||||
@@ -36,6 +36,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
@@ -193,18 +194,21 @@ public final class ModdedEngineEffects extends EngineAssignedComponent implement
|
||||
return;
|
||||
}
|
||||
|
||||
samplePlayer(state);
|
||||
try {
|
||||
samplePlayer(state);
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
state.environment = null;
|
||||
state.sampled(false);
|
||||
return;
|
||||
}
|
||||
if (!IrisSettings.get().getWorld().isEffectSystem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IrisRegion region = state.region();
|
||||
if (region != null) {
|
||||
applyEffects(level, player, region.getEffects());
|
||||
}
|
||||
IrisBiome biome = state.biome();
|
||||
if (biome != null) {
|
||||
applyEffects(level, player, biome.getEffects());
|
||||
BiomeEnvironment environment = state.environment;
|
||||
try (BiomeEnvironment.Scope ignored = getEngine().openBiomeEnvironmentScope(environment)) {
|
||||
applyEffects(level, player, environment.region().getEffects());
|
||||
applyEffects(level, player, environment.biome().getEffects());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,14 +223,14 @@ public final class ModdedEngineEffects extends EngineAssignedComponent implement
|
||||
return;
|
||||
}
|
||||
|
||||
state.sampled(true);
|
||||
state.lastPosition(player.getX(), player.getY(), player.getZ());
|
||||
state.lastSample(now);
|
||||
int blockX = floor(player.getX());
|
||||
int blockY = floor(player.getY()) - getEngine().getWorld().minHeight();
|
||||
int blockZ = floor(player.getZ());
|
||||
state.biome(getEngine().getBiome(blockX, blockY, blockZ));
|
||||
state.region(getEngine().getRegion(blockX, blockY, blockZ));
|
||||
BiomeEnvironment environment = getEngine().getBiomeEnvironment(blockX, blockY, blockZ);
|
||||
state.environment = environment;
|
||||
state.sampled(true);
|
||||
state.lastPosition(player.getX(), player.getY(), player.getZ());
|
||||
state.lastSample(now);
|
||||
}
|
||||
|
||||
private void applyEffects(ServerLevel level, ServerPlayer player, Iterable<IrisEffect> effects) {
|
||||
@@ -299,9 +303,12 @@ public final class ModdedEngineEffects extends EngineAssignedComponent implement
|
||||
double sideways = RNG.r.d(-effect.getParticleDistanceWidth(), effect.getParticleDistanceWidth());
|
||||
double surfaceX = player.getX() + direction.x * forward + direction.z * sideways;
|
||||
double surfaceZ = player.getZ() + direction.z * forward - direction.x * sideways;
|
||||
int surfaceY = getEngine().getHeight(floor(surfaceX), floor(surfaceZ));
|
||||
if (level.getChunkSource().getChunkNow(floor(surfaceX) >> 4, floor(surfaceZ) >> 4) == null) {
|
||||
return;
|
||||
}
|
||||
int surfaceY = level.getHeight(Heightmap.Types.OCEAN_FLOOR, floor(surfaceX), floor(surfaceZ));
|
||||
double x = surfaceX + RNG.r.d();
|
||||
double y = surfaceY + 1.0D + level.getMinY() + RNG.r.i(effect.getParticleOffset());
|
||||
double y = surfaceY + RNG.r.i(effect.getParticleOffset());
|
||||
double z = surfaceZ + RNG.r.d();
|
||||
double altX = randomized(effect.getParticleAltX(), effect.isRandomAltX());
|
||||
double altY = randomized(effect.getParticleAltY(), effect.isRandomAltY());
|
||||
@@ -421,8 +428,7 @@ public final class ModdedEngineEffects extends EngineAssignedComponent implement
|
||||
|
||||
private static final class PlayerState {
|
||||
private ServerPlayer player;
|
||||
private IrisBiome biome;
|
||||
private IrisRegion region;
|
||||
private BiomeEnvironment environment;
|
||||
private double lastX;
|
||||
private double lastY;
|
||||
private double lastZ;
|
||||
@@ -446,22 +452,6 @@ public final class ModdedEngineEffects extends EngineAssignedComponent implement
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
private IrisBiome biome() {
|
||||
return biome;
|
||||
}
|
||||
|
||||
private void biome(IrisBiome biome) {
|
||||
this.biome = biome;
|
||||
}
|
||||
|
||||
private IrisRegion region() {
|
||||
return region;
|
||||
}
|
||||
|
||||
private void region(IrisRegion region) {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
private double lastX() {
|
||||
return lastX;
|
||||
}
|
||||
|
||||
+6
-5
@@ -175,7 +175,7 @@ final class ModdedGenerationHistoryStorage {
|
||||
GenerationEpoch epoch = history.activeEpoch();
|
||||
Path packRoot = normalize(history.activePackRoot());
|
||||
validatePack(packRoot, epoch.packFingerprint());
|
||||
validateDimension(packRoot, epoch.dimensionContract());
|
||||
validateDimension(packRoot, epoch);
|
||||
return new ActivePack(
|
||||
packRoot,
|
||||
epoch.dimensionContract().dimensionKey(),
|
||||
@@ -194,8 +194,9 @@ final class ModdedGenerationHistoryStorage {
|
||||
|
||||
private static void validateDimension(
|
||||
Path packRoot,
|
||||
GenerationEpoch.DimensionContract recordedContract
|
||||
GenerationEpoch epoch
|
||||
) throws IOException {
|
||||
GenerationEpoch.DimensionContract recordedContract = epoch.dimensionContract();
|
||||
IrisData data = IrisData.openDatapackCompiler(packRoot.toFile());
|
||||
try {
|
||||
IrisDimension dimension = data.getDimensionLoader().load(recordedContract.dimensionKey(), false);
|
||||
@@ -203,10 +204,10 @@ final class ModdedGenerationHistoryStorage {
|
||||
throw new IOException("Immutable generation pack does not contain dimension '"
|
||||
+ recordedContract.dimensionKey() + "'.");
|
||||
}
|
||||
GenerationEpoch.DimensionContract actualContract = GenerationEpochContractFactory.create(
|
||||
GenerationEpoch.DimensionContract actualContract = GenerationEpochContractFactory.createForEpoch(
|
||||
dimension,
|
||||
dimension.getLoadKey(),
|
||||
recordedContract.dimensionTypeKey()
|
||||
recordedContract.dimensionTypeKey(),
|
||||
epoch
|
||||
);
|
||||
if (!recordedContract.equals(actualContract)) {
|
||||
throw new IOException("Immutable generation pack no longer matches its recorded dimension contract.");
|
||||
|
||||
+4
-3
@@ -137,10 +137,11 @@ final class ModdedSpawnTableMerger {
|
||||
WeightedList<MobSpawnSettings.SpawnerData> mergedSpawnTable(
|
||||
Engine engine,
|
||||
Biome biome,
|
||||
Biome vanillaBiome,
|
||||
MobCategory category,
|
||||
WeightedList<MobSpawnSettings.SpawnerData> vanillaSpawns,
|
||||
WeightedList<MobSpawnSettings.SpawnerData> explicitSpawns) {
|
||||
SpawnTableKey key = new SpawnTableKey(engine.getCacheID(), biome, category);
|
||||
SpawnTableKey key = new SpawnTableKey(engine.getCacheID(), biome, vanillaBiome, category);
|
||||
return mergedSpawnTables.computeIfAbsent(
|
||||
key,
|
||||
ignored -> NativeSpawnTableMerger.merge(vanillaSpawns, explicitSpawns)
|
||||
@@ -153,7 +154,7 @@ final class ModdedSpawnTableMerger {
|
||||
initializedRuntimeIdentities.remove(runtimeIdentity);
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveBiomeHolder(Registry<Biome> registry, String key) {
|
||||
Holder<Biome> resolveBiomeHolder(Registry<Biome> registry, String key) {
|
||||
if (key == null || key.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
@@ -176,6 +177,6 @@ final class ModdedSpawnTableMerger {
|
||||
private record SpawnBiomeKey(int runtimeIdentity, Biome biome) {
|
||||
}
|
||||
|
||||
private record SpawnTableKey(int runtimeIdentity, Biome biome, MobCategory category) {
|
||||
private record SpawnTableKey(int runtimeIdentity, Biome biome, Biome vanillaBiome, MobCategory category) {
|
||||
}
|
||||
}
|
||||
+88
-38
@@ -22,6 +22,8 @@ import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.BiomeEnvironment;
|
||||
import art.arcane.iris.engine.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.engine.framework.EngineLifecycleTasks;
|
||||
import art.arcane.iris.engine.framework.EngineWorldManager;
|
||||
import art.arcane.iris.engine.framework.LootResolver;
|
||||
@@ -32,7 +34,6 @@ import art.arcane.iris.engine.object.IrisEntitySpawn;
|
||||
import art.arcane.iris.engine.object.IrisMarker;
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.iris.engine.object.IrisRange;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisSpawnGroup;
|
||||
import art.arcane.iris.engine.object.IrisSpawner;
|
||||
import art.arcane.iris.engine.object.IrisSurface;
|
||||
@@ -53,12 +54,16 @@ import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.level.NaturalSpawner;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -152,7 +157,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
|
||||
private void recoverLoadedInitialSpawns(ServerLevel level) {
|
||||
if (!markerSystemEnabled()) {
|
||||
if (!markerSystemEnabled() && !ambientSystemEnabled()) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -194,7 +199,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
if (initialSpawnQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!markerSystemEnabled()) {
|
||||
if (!markerSystemEnabled() && !ambientSystemEnabled()) {
|
||||
initialSpawnQueue.clear();
|
||||
return;
|
||||
}
|
||||
@@ -217,6 +222,8 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
retry = true;
|
||||
warmupMantleChunkAsync(key, chunkX, chunkZ);
|
||||
}
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
retry = unavailable.isLoading();
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
retry = true;
|
||||
@@ -230,7 +237,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initialSpawnChunk(ServerLevel level, int chunkX, int chunkZ) {
|
||||
boolean initialSpawnChunk(ServerLevel level, int chunkX, int chunkZ) {
|
||||
if (!ModdedEntitySpawner.chunksSafe(level, chunkX, chunkZ)) {
|
||||
return false;
|
||||
}
|
||||
@@ -244,9 +251,15 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
|
||||
MantleChunk<Matter> chunk = mantle.getChunk(chunkX, chunkZ).use();
|
||||
try {
|
||||
List<PreparedMarkerSpawn> markers = markerSystemEnabled()
|
||||
? prepareMarkerSpawns(level, chunkX, chunkZ, chunk) : List.of();
|
||||
Optional<BiomeEnvironment> environment = ambientSystemEnabled()
|
||||
? resolveSurfaceEnvironment(chunkX, chunkZ) : Optional.empty();
|
||||
chunk.raiseFlagUnchecked(INITIAL_SPAWN_COMPLETION_FLAG, () -> {
|
||||
spawnMarkerSpawners(level, chunkX, chunkZ, chunk, true);
|
||||
scheduleInitialFollowUp(level, chunkX, chunkZ);
|
||||
spawnPreparedMarkers(level, markers, true);
|
||||
if (environment.isPresent()) {
|
||||
scheduleInitialFollowUp(level, chunkX, chunkZ, environment.get());
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
chunk.release();
|
||||
@@ -254,7 +267,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void scheduleInitialFollowUp(ServerLevel level, int chunkX, int chunkZ) {
|
||||
private void scheduleInitialFollowUp(ServerLevel level, int chunkX, int chunkZ, BiomeEnvironment environment) {
|
||||
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
|
||||
if (scheduler == null) {
|
||||
IrisLogging.error("Iris could not schedule the initial entity-spawn follow-up because the modded scheduler is unavailable.");
|
||||
@@ -264,11 +277,11 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
() -> EngineLifecycleTasks.run(
|
||||
engine,
|
||||
"modded_world_manager_initial_spawn_followup",
|
||||
() -> runInitialFollowUp(level, chunkX, chunkZ)),
|
||||
() -> runInitialFollowUp(level, chunkX, chunkZ, environment)),
|
||||
RNG.r.i(5, 200));
|
||||
}
|
||||
|
||||
private void runInitialFollowUp(ServerLevel level, int chunkX, int chunkZ) {
|
||||
private void runInitialFollowUp(ServerLevel level, int chunkX, int chunkZ, BiomeEnvironment environment) {
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
if (closed || engine.isClosed() || mantle.isClosed() || !isEntitySpawningEnabledForCurrentWorld()) {
|
||||
return;
|
||||
@@ -280,16 +293,10 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return;
|
||||
}
|
||||
|
||||
MantleChunk<Matter> chunk = mantle.getChunk(chunkX, chunkZ).use();
|
||||
try {
|
||||
if (markerSystemEnabled()) {
|
||||
spawnMarkerSpawners(level, chunkX, chunkZ, chunk, false);
|
||||
if (ambientSystemEnabled()) {
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(environment)) {
|
||||
spawnAmbient(level, chunkX, chunkZ, true, environment);
|
||||
}
|
||||
if (ambientSystemEnabled()) {
|
||||
spawnAmbient(level, chunkX, chunkZ, true);
|
||||
}
|
||||
} finally {
|
||||
chunk.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +367,8 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
long key = ambientChunkSample[RNG.r.nextInt(sampled)];
|
||||
try {
|
||||
ambientSpawnChunk(level, unpackX(key), unpackZ(key));
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
continue;
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
@@ -378,7 +387,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
MantleChunk<Matter> chunk = mantle.getChunk(chunkX, chunkZ).use();
|
||||
try {
|
||||
if (markerSystemEnabled()) {
|
||||
spawnMarkerSpawners(level, chunkX, chunkZ, chunk, false);
|
||||
spawnPreparedMarkers(level, prepareMarkerSpawns(level, chunkX, chunkZ, chunk), false);
|
||||
}
|
||||
if (ambientSystemEnabled()) {
|
||||
spawnAmbient(level, chunkX, chunkZ, false);
|
||||
@@ -388,29 +397,38 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnMarkerSpawners(ServerLevel level, int chunkX, int chunkZ, MantleChunk<Matter> chunk, boolean initial) {
|
||||
private List<PreparedMarkerSpawn> prepareMarkerSpawns(ServerLevel level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
|
||||
int minHeight = engine.getWorld().minHeight();
|
||||
KList<IrisPosition> obstructed = new KList<>();
|
||||
List<PreparedMarkerSpawn> prepared = new ArrayList<>();
|
||||
chunk.iterate(MatterMarker.class, (Integer x, Integer yf, Integer z, MatterMarker marker) -> {
|
||||
String tag = marker.getTag();
|
||||
if (tag.equals("cave_floor") || tag.equals("cave_ceiling")) {
|
||||
return;
|
||||
}
|
||||
IrisMarker resolved = engine.getData().getMarkerLoader().load(tag);
|
||||
if (resolved == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int worldX = (chunkX << 4) + (x & 15);
|
||||
int worldZ = (chunkZ << 4) + (z & 15);
|
||||
int worldY = yf + minHeight;
|
||||
BiomeEnvironment environment;
|
||||
try {
|
||||
environment = engine.getBiomeEnvironment(worldX, yf, worldZ);
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
if (unavailable.isLoading()) {
|
||||
throw unavailable;
|
||||
}
|
||||
return;
|
||||
}
|
||||
IrisMarker resolved = environment.data().getMarkerLoader().load(tag);
|
||||
if (resolved == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolved.isEmptyAbove() && aboveObstructed(level, worldX, worldY, worldZ)) {
|
||||
obstructed.add(new IrisPosition(worldX, yf, worldZ));
|
||||
return;
|
||||
}
|
||||
|
||||
KList<IrisSpawner> spawners = resolveMarkerSpawners(resolved);
|
||||
KList<IrisSpawner> spawners = resolveMarkerSpawners(resolved, environment);
|
||||
if (spawners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -418,18 +436,19 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
if (chosen == null) {
|
||||
return;
|
||||
}
|
||||
spawnFromSpawner(level, new IrisPosition(worldX, worldY, worldZ), chosen, initial);
|
||||
prepared.add(new PreparedMarkerSpawn(new IrisPosition(worldX, worldY, worldZ), chosen, environment));
|
||||
});
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
for (IrisPosition position : obstructed) {
|
||||
mantle.remove(position.getX(), position.getY(), position.getZ(), MatterMarker.class);
|
||||
}
|
||||
return List.copyOf(prepared);
|
||||
}
|
||||
|
||||
private KList<IrisSpawner> resolveMarkerSpawners(IrisMarker marker) {
|
||||
private KList<IrisSpawner> resolveMarkerSpawners(IrisMarker marker, BiomeEnvironment environment) {
|
||||
KList<IrisSpawner> spawners = new KList<>();
|
||||
for (String key : marker.getSpawners()) {
|
||||
IrisSpawner spawner = engine.getData().getSpawnerLoader().load(key);
|
||||
IrisSpawner spawner = environment.data().getSpawnerLoader().load(key);
|
||||
if (spawner == null) {
|
||||
IrisLogging.error("Cannot load spawner: " + key + " for marker on " + engine.getName());
|
||||
continue;
|
||||
@@ -440,6 +459,14 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return spawners;
|
||||
}
|
||||
|
||||
private void spawnPreparedMarkers(ServerLevel level, List<PreparedMarkerSpawn> markers, boolean initial) {
|
||||
for (PreparedMarkerSpawn marker : markers) {
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(marker.environment())) {
|
||||
spawnFromSpawner(level, marker.position(), marker.spawner(), initial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnFromSpawner(ServerLevel level, IrisPosition position, IrisSpawner spawner, boolean initial) {
|
||||
KList<IrisEntitySpawn> spawns = initial ? spawner.getInitialSpawns() : spawner.getSpawns();
|
||||
if (spawns.isEmpty()) {
|
||||
@@ -465,22 +492,41 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<BiomeEnvironment> resolveSurfaceEnvironment(int chunkX, int chunkZ) {
|
||||
try {
|
||||
return Optional.of(engine.getSurfaceBiomeEnvironment((chunkX << 4) + 8, (chunkZ << 4) + 8));
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
if (unavailable.isLoading()) {
|
||||
throw unavailable;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnAmbient(ServerLevel level, int chunkX, int chunkZ, boolean initial) {
|
||||
Optional<BiomeEnvironment> resolved = resolveSurfaceEnvironment(chunkX, chunkZ);
|
||||
if (resolved.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
BiomeEnvironment environment = resolved.get();
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(environment)) {
|
||||
spawnAmbient(level, chunkX, chunkZ, initial, environment);
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnAmbient(ServerLevel level, int chunkX, int chunkZ, boolean initial, BiomeEnvironment environment) {
|
||||
IrisComplex complex = engine.getComplex();
|
||||
if (complex == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int blockX = chunkX << 4;
|
||||
int blockZ = chunkZ << 4;
|
||||
IrisBiome biome = engine.getSurfaceBiome(blockX, blockZ);
|
||||
IrisRegion region = engine.getRegion(blockX, blockZ);
|
||||
IrisBiome biome = environment.biome();
|
||||
int chunkMobs = countChunkLivingEntities(level, chunkX, chunkZ);
|
||||
|
||||
KList<IrisEntitySpawn> pool = new KList<>();
|
||||
collectSpawns(pool, engine.getData().getSpawnerLoader().loadAll(engine.getDimension().getEntitySpawners()), biome, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, engine.getData().getSpawnerLoader().loadAll(region.getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, engine.getData().getSpawnerLoader().loadAll(biome.getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.dimension().getEntitySpawners()), biome, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.region().getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(biome.getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial);
|
||||
if (pool.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -556,8 +602,8 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
} else {
|
||||
worldX = (chunkX << 4) + RNG.r.i(16);
|
||||
worldZ = (chunkZ << 4) + RNG.r.i(16);
|
||||
int surfaceY = level.getMinY() + engine.getHeight(worldX, worldZ, false);
|
||||
int solidY = level.getMinY() + engine.getHeight(worldX, worldZ, true);
|
||||
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, worldX, worldZ) - 1;
|
||||
int solidY = level.getHeight(Heightmap.Types.OCEAN_FLOOR, worldX, worldZ) - 1;
|
||||
worldY = group == IrisSpawnGroup.NORMAL
|
||||
? surfaceY + 1
|
||||
: RNG.r.i(solidY + 1, surfaceY);
|
||||
@@ -864,4 +910,8 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
return failure;
|
||||
}
|
||||
|
||||
private record PreparedMarkerSpawn(IrisPosition position, IrisSpawner spawner, BiomeEnvironment environment) {
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -24,6 +24,7 @@ import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
@@ -194,6 +195,13 @@ public final class ModdedWhatCommands {
|
||||
MessageArgument.untrusted("biome", nativeBiome.key()),
|
||||
MessageArgument.trusted("id", nativeBiome.id())));
|
||||
return 1;
|
||||
} catch (SavedBiomeUnavailableException error) {
|
||||
if (error.getCause() != null) {
|
||||
ModdedIrisLog.error("Iris saved biome lookup failed in {}",
|
||||
source.getLevel().dimension().identifier(), error);
|
||||
}
|
||||
IrisModdedCommands.fail(source, error.getMessage());
|
||||
return 0;
|
||||
} catch (Throwable error) {
|
||||
logLookupFailure(source, "biome", error,
|
||||
ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME_LOOKUP_FAILED_2);
|
||||
@@ -223,6 +231,13 @@ public final class ModdedWhatCommands {
|
||||
MessageArgument.untrusted("region", region.getLoadKey()),
|
||||
MessageArgument.untrusted("name", region.getName())));
|
||||
return 1;
|
||||
} catch (SavedBiomeUnavailableException error) {
|
||||
if (error.getCause() != null) {
|
||||
ModdedIrisLog.error("Iris saved biome lookup failed in {}",
|
||||
source.getLevel().dimension().identifier(), error);
|
||||
}
|
||||
IrisModdedCommands.fail(source, error.getMessage());
|
||||
return 0;
|
||||
} catch (Throwable error) {
|
||||
logLookupFailure(source, "region", error,
|
||||
ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION_LOOKUP_FAILED_2);
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.BiomeEnvironment;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.junit.Test;
|
||||
import org.junit.BeforeClass;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedSavedBiomeConsumersTest {
|
||||
@BeforeClass
|
||||
public static void initializeRuntimeTypes() throws Exception {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
PlatformBlockState air = mock(PlatformBlockState.class);
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class)) {
|
||||
blocks.when(() -> B.getState("AIR")).thenReturn(air);
|
||||
Class.forName(EngineMantle.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadingDefinitionsLeaveInitialFlagUnclaimed() {
|
||||
Engine engine = mock(Engine.class);
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
when(engine.getMantle()).thenReturn(engineMantle);
|
||||
when(engine.getWorld()).thenReturn(mock(IrisWorld.class));
|
||||
when(engineMantle.getMantle()).thenReturn(mantle);
|
||||
when(mantle.isChunkLoaded(2, -1)).thenReturn(true);
|
||||
when(mantle.getChunk(2, -1)).thenReturn(chunk);
|
||||
when(chunk.use()).thenReturn(chunk);
|
||||
BiomeEnvironment environment = environment();
|
||||
when(engine.getSurfaceBiomeEnvironment(40, -8))
|
||||
.thenThrow(new SavedBiomeUnavailableException("Loading", true))
|
||||
.thenReturn(environment);
|
||||
IrisSettings settings = new IrisSettings();
|
||||
settings.getWorld().setAmbientEntitySpawningSystem(true);
|
||||
settings.getWorld().setMarkerEntitySpawningSystem(false);
|
||||
ModdedWorldManager manager = new ModdedWorldManager(engine);
|
||||
|
||||
try (MockedStatic<IrisSettings> configured = mockStatic(IrisSettings.class);
|
||||
MockedStatic<ModdedEntitySpawner> spawns = mockStatic(ModdedEntitySpawner.class)) {
|
||||
configured.when(IrisSettings::get).thenReturn(settings);
|
||||
spawns.when(() -> ModdedEntitySpawner.chunksSafe(level, 2, -1)).thenReturn(true);
|
||||
assertThrows(SavedBiomeUnavailableException.class, () -> manager.initialSpawnChunk(level, 2, -1));
|
||||
verify(chunk, never()).raiseFlagUnchecked(eq(ModdedWorldManager.INITIAL_SPAWN_COMPLETION_FLAG), any());
|
||||
assertTrue(manager.initialSpawnChunk(level, 2, -1));
|
||||
verify(chunk).raiseFlagUnchecked(eq(ModdedWorldManager.INITIAL_SPAWN_COMPLETION_FLAG), any());
|
||||
} finally {
|
||||
manager.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playerEffectsRetryLoadingWithoutMovement() throws Exception {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisWorld world = mock(IrisWorld.class);
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
ServerPlayer player = mock(ServerPlayer.class);
|
||||
when(engine.getWorld()).thenReturn(world);
|
||||
when(world.minHeight()).thenReturn(-64);
|
||||
when(player.isAlive()).thenReturn(true);
|
||||
when(player.level()).thenReturn(level);
|
||||
BiomeEnvironment environment = environment();
|
||||
when(engine.getBiomeEnvironment(0, 64, 0))
|
||||
.thenThrow(new SavedBiomeUnavailableException("Loading", true))
|
||||
.thenReturn(environment);
|
||||
IrisSettings settings = new IrisSettings();
|
||||
settings.getWorld().setEffectSystem(false);
|
||||
Class<?> stateType = Class.forName(ModdedEngineEffects.class.getName() + "$PlayerState");
|
||||
Constructor<?> constructor = stateType.getDeclaredConstructor(ServerPlayer.class);
|
||||
constructor.setAccessible(true);
|
||||
Object state = constructor.newInstance(player);
|
||||
Method tick = ModdedEngineEffects.class.getDeclaredMethod("tickPlayer", ServerLevel.class, stateType);
|
||||
tick.setAccessible(true);
|
||||
Field selected = stateType.getDeclaredField("environment");
|
||||
selected.setAccessible(true);
|
||||
|
||||
try (MockedStatic<IrisSettings> configured = mockStatic(IrisSettings.class)) {
|
||||
configured.when(IrisSettings::get).thenReturn(settings);
|
||||
ModdedEngineEffects effects = new ModdedEngineEffects(engine);
|
||||
tick.invoke(effects, level, state);
|
||||
assertNull(selected.get(state));
|
||||
tick.invoke(effects, level, state);
|
||||
assertSame(environment, selected.get(state));
|
||||
verify(engine, times(2)).getBiomeEnvironment(0, 64, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static BiomeEnvironment environment() {
|
||||
return new BiomeEnvironment(3L, mock(IrisBiome.class), mock(IrisRegion.class),
|
||||
mock(IrisDimension.class), mock(IrisData.class));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user