diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java index b90b7d4d8..83aff14d5 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java @@ -68,7 +68,12 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; public final class IrisModdedChunkGenerator extends ChunkGenerator { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); @@ -77,6 +82,41 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { Codec.STRING.fieldOf("dimension").forGetter((IrisModdedChunkGenerator generator) -> generator.dimensionKey) ).apply(instance, IrisModdedChunkGenerator::new)); + private static final AtomicInteger GEN_THREAD_SEQ = new AtomicInteger(); + private static final boolean PARALLEL_CHUNK_SYSTEM = detectParallelChunkSystem(); + private static final ExecutorService GEN_POOL = createGenPool(); + + private static boolean detectParallelChunkSystem() { + String[] markers = { + "com.ishland.c2me.base.ModProperties", + "com.ishland.c2me.base.common.config.C2MEConfig", + "com.ishland.c2me.opts.chunkio.ModProperties", + "ca.spottedleaf.moonrise.common.util.MoonriseCommon" + }; + for (String marker : markers) { + try { + Class.forName(marker, false, IrisModdedChunkGenerator.class.getClassLoader()); + return true; + } catch (Throwable ignored) { + } + } + return false; + } + + private static ExecutorService createGenPool() { + int threads = Math.max(2, Runtime.getRuntime().availableProcessors()); + ThreadPoolExecutor pool = new ThreadPoolExecutor( + threads, threads, 30L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + runnable -> { + Thread thread = new Thread(runnable, "Iris ModGen-" + GEN_THREAD_SEQ.incrementAndGet()); + thread.setDaemon(true); + return thread; + }); + pool.allowCoreThreadTimeOut(true); + return pool; + } + private final String dimensionKey; private final String defaultPack; private final String defaultDimensionKey; @@ -221,6 +261,20 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { int dimMaxY = generationEngine.getMaxHeight(); int height = dimMaxY - dimMinY; PlatformBlockState air = IrisPlatforms.get().registries().air(); + Registry biomeRegistry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME); + + if (PARALLEL_CHUNK_SYSTEM) { + return CompletableFuture.completedFuture( + generateTerrain(chunk, generationEngine, pos, dimMinY, height, air, biomeRegistry, randomState)); + } + return CompletableFuture.supplyAsync( + () -> generateTerrain(chunk, generationEngine, pos, dimMinY, height, air, biomeRegistry, randomState), + GEN_POOL); + } + + private ChunkAccess generateTerrain(ChunkAccess chunk, Engine generationEngine, ChunkPos pos, + int dimMinY, int height, PlatformBlockState air, + Registry biomeRegistry, RandomState randomState) { ModdedBlockBuffer blocks = new ModdedBlockBuffer(height, air); Hunk biomes = Hunk.newArrayHunk(16, height, 16); try { @@ -228,7 +282,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { } catch (GenerationSessionException e) { if (e.isExpectedTeardown()) { LOGGER.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z()); - return CompletableFuture.completedFuture(chunk); + return chunk; } LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e); throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e); @@ -239,9 +293,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { writeBlocks(chunk, blocks, dimMinY, height); Heightmap.primeHeightmaps(chunk, EnumSet.of(Heightmap.Types.WORLD_SURFACE_WG, Heightmap.Types.OCEAN_FLOOR_WG)); - Registry biomeRegistry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME); chunk.fillBiomesFromNoise(new HunkBiomeResolver(this, biomes, biomeRegistry, pos, dimMinY, height), randomState.sampler()); - return CompletableFuture.completedFuture(chunk); + return chunk; } private Holder fallbackBiome(Registry registry) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java index 4ed5b1b1a..22e6571c9 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java @@ -24,6 +24,7 @@ import art.arcane.iris.core.pregenerator.PregenMantleBackpressure; import art.arcane.iris.core.pregenerator.PregeneratorMethod; import art.arcane.iris.engine.framework.Engine; import art.arcane.volmlib.util.mantle.runtime.Mantle; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; @@ -31,21 +32,36 @@ import net.minecraft.world.level.ChunkPos; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Field; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; public final class ModdedPregenMethod implements PregeneratorMethod { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final TicketType PREGEN_TICKET = new TicketType(TicketType.NO_TIMEOUT, TicketType.FLAG_LOADING | TicketType.FLAG_KEEP_DIMENSION_ACTIVE); + private static final int ADAPTIVE_TIMEOUT_STEP = 3; + private static final long ADAPTIVE_RECOVERY_INTERVAL = 64L; + private static final boolean PARALLEL_CHUNK_SYSTEM = detectParallelChunkSystem(); private final ServerLevel level; private final Engine engine; private final boolean sync; + private final int maxInFlight; + private final int minInFlight; private final Semaphore semaphore; - private final int permits; + private final Object permitMonitor = new Object(); + private final AtomicInteger inFlight = new AtomicInteger(); + private final AtomicInteger inFlightPeak = new AtomicInteger(); + private final AtomicInteger adaptiveLimit; + private final AtomicInteger timeoutStreak = new AtomicInteger(); + private final AtomicLong completed = new AtomicLong(); private final int timeoutSeconds; private final PregenMantleBackpressure backpressure; @@ -57,9 +73,11 @@ public final class ModdedPregenMethod implements PregeneratorMethod { this.level = level; this.engine = engine; this.sync = sync; - this.permits = Math.min(96, Math.max(8, Runtime.getRuntime().availableProcessors() * 2)); - this.semaphore = new Semaphore(permits, true); IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen(); + this.maxInFlight = Math.max(8, pregen.getModdedPregenInFlight()); + this.minInFlight = Math.max(4, Math.min(16, maxInFlight / 4)); + this.semaphore = new Semaphore(maxInFlight, true); + this.adaptiveLimit = new AtomicInteger(maxInFlight); this.timeoutSeconds = Math.max(120, pregen.getChunkLoadTimeoutSeconds()); this.backpressure = new PregenMantleBackpressure( this::getMantle, @@ -73,19 +91,29 @@ public final class ModdedPregenMethod implements PregeneratorMethod { @Override public void init() { - LOGGER.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s", - level.dimension().identifier(), sync ? "sync" : "async", sync ? 1 : permits, timeoutSeconds); + LOGGER.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s workerPool={} parallelChunkSystem={}", + level.dimension().identifier(), + sync ? "sync" : "async", + sync ? 1 : maxInFlight, + timeoutSeconds, + describeWorkerPool(), + PARALLEL_CHUNK_SYSTEM ? "yes" : "no"); + if (!sync && !PARALLEL_CHUNK_SYSTEM) { + LOGGER.info("Iris pregen note: this loader uses the vanilla main-thread chunk system, which caps pregen throughput. For Bukkit-level speed on Fabric install C2ME (Concurrent Chunk Management Engine); on servers use Paper."); + } } @Override public void close() { if (!sync) { try { - semaphore.tryAcquire(permits, 5, TimeUnit.SECONDS); + semaphore.tryAcquire(maxInFlight, 5, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } + LOGGER.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}", + level.dimension().identifier(), completed.get(), inFlightPeak.get(), adaptiveLimit.get()); saveLevel(true); } @@ -174,11 +202,17 @@ public final class ModdedPregenMethod implements PregeneratorMethod { private void generateChunkAsync(int x, int z, PregenListener listener) { listener.onChunkGenerating(x, z); try { + synchronized (permitMonitor) { + while (inFlight.get() >= adaptiveLimit.get()) { + permitMonitor.wait(500L); + } + } semaphore.acquire(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } + markSubmitted(); ChunkPos pos = new ChunkPos(x, z); CompletableFuture loadFuture = CompletableFuture @@ -189,6 +223,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod { level.getServer().execute(() -> level.getChunkSource().removeTicketWithRadius(PREGEN_TICKET, pos, 0)); try { if (error != null) { + if (unwrap(error) instanceof TimeoutException) { + onTimeout(); + } LOGGER.warn("Iris pregen chunk {},{} failed: {}", x, z, error.toString()); listener.onChunkFailed(x, z); return; @@ -198,15 +235,70 @@ public final class ModdedPregenMethod implements PregeneratorMethod { listener.onChunkFailed(x, z); return; } + onSuccess(); listener.onChunkGenerated(x, z); cleanupMantleChunk(x, z); listener.onChunkCleaned(x, z); } finally { + markFinished(); semaphore.release(); } }); } + private void markSubmitted() { + int current = inFlight.incrementAndGet(); + inFlightPeak.accumulateAndGet(current, Math::max); + } + + private void markFinished() { + inFlight.decrementAndGet(); + completed.incrementAndGet(); + synchronized (permitMonitor) { + permitMonitor.notifyAll(); + } + } + + private void onTimeout() { + if (timeoutStreak.incrementAndGet() % ADAPTIVE_TIMEOUT_STEP == 0) { + adjustAdaptiveLimit(-1); + } + } + + private void onSuccess() { + int streak = timeoutStreak.get(); + if (streak > 0) { + timeoutStreak.compareAndSet(streak, Math.max(0, streak - 2)); + return; + } + if ((completed.get() & (ADAPTIVE_RECOVERY_INTERVAL - 1L)) == 0L) { + adjustAdaptiveLimit(1); + } + } + + private void adjustAdaptiveLimit(int direction) { + while (true) { + int current = adaptiveLimit.get(); + int next; + if (direction < 0) { + next = Math.max(minInFlight, current - 1); + } else { + int deficit = maxInFlight - current; + int step = deficit > (maxInFlight / 2) ? Math.max(2, maxInFlight / 8) : 1; + next = Math.min(maxInFlight, current + step); + } + if (next == current) { + return; + } + if (adaptiveLimit.compareAndSet(current, next)) { + synchronized (permitMonitor) { + permitMonitor.notifyAll(); + } + return; + } + } + } + private void cleanupMantleChunk(int x, int z) { try { engine.getMantle().forceCleanupChunk(x, z); @@ -214,6 +306,47 @@ public final class ModdedPregenMethod implements PregeneratorMethod { } } + private String describeWorkerPool() { + try { + Field field = MinecraftServer.class.getDeclaredField("executor"); + field.setAccessible(true); + Object exec = field.get(level.getServer()); + if (exec == null) { + return "unknown"; + } + if (exec instanceof ThreadPoolExecutor tpe) { + return "ThreadPoolExecutor(core=" + tpe.getCorePoolSize() + ",max=" + tpe.getMaximumPoolSize() + ")"; + } + if (exec instanceof ForkJoinPool fjp) { + return "ForkJoinPool(parallelism=" + fjp.getParallelism() + ")"; + } + return exec.getClass().getSimpleName(); + } catch (Throwable e) { + return "unknown"; + } + } + + private static Throwable unwrap(Throwable error) { + return error != null && error.getCause() != null ? error.getCause() : error; + } + + private static boolean detectParallelChunkSystem() { + String[] markers = { + "com.ishland.c2me.base.ModProperties", + "com.ishland.c2me.base.common.config.C2MEConfig", + "com.ishland.c2me.opts.chunkio.ModProperties", + "ca.spottedleaf.moonrise.common.util.MoonriseCommon" + }; + for (String marker : markers) { + try { + Class.forName(marker, false, ModdedPregenMethod.class.getClassLoader()); + return true; + } catch (Throwable ignored) { + } + } + return false; + } + @Override public Mantle getMantle() { return engine.getMantle().getMantle(); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java index e9b345745..ddfc84b7c 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java @@ -119,6 +119,9 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi if (engine == null || engine.isClosed() || engine.getMantle().getMantle().isClosed()) { return; } + if (pregenTargets(engine)) { + return; + } if (flush) { try { engine.save(); diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index b05b37496..dd2e0fe72 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -154,11 +154,21 @@ public class IrisSettings { public int maxResidentTectonicPlates = 96; public int mantleBackpressureWaitMs = 25; public int mantleBackpressureTimeoutMs = 60_000; + public int moddedPregenInFlight = 0; public int getChunkLoadTimeoutSeconds() { return Math.max(5, Math.min(chunkLoadTimeoutSeconds, 120)); } + public int getModdedPregenInFlight() { + if (moddedPregenInFlight > 0) { + return Math.min(512, moddedPregenInFlight); + } + + int cpu = Math.max(1, Runtime.getRuntime().availableProcessors()); + return Math.max(16, Math.min(48, cpu * 2)); + } + public int getMaxResidentTectonicPlates() { return Math.max(16, maxResidentTectonicPlates); } diff --git a/core/src/main/java/art/arcane/iris/engine/IrisComplex.java b/core/src/main/java/art/arcane/iris/engine/IrisComplex.java index f0776d343..154cad12c 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisComplex.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisComplex.java @@ -23,7 +23,7 @@ import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.engine.data.cache.Cache; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.object.*; -import art.arcane.iris.platform.bukkit.BukkitBiome; +import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.PlatformBiome; import art.arcane.iris.spi.PlatformBlockState; @@ -215,7 +215,7 @@ public class IrisComplex implements DataProvider { regionStream.contextInjecting((c, xx, zz) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(xx, zz)).get(x, z), x, z, fluidHeight)) .cache2D("trueBiomeStream", engine, cacheSize).waste("True Biome Stream"); trueBiomeDerivativeStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z)) - .convert((b) -> (PlatformBiome) BukkitBiome.of(b.getDerivative())).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream"); + .convert((b) -> IrisPlatforms.get().registries().biome(b.getDerivativeKey())).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream"); heightFluidStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z)) .max(fluidHeight).cache2DDouble("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream"); maxHeightStream = ProceduralStream.ofDouble((x, z) -> height).waste("Max Height Stream"); diff --git a/core/src/main/java/art/arcane/iris/engine/actuator/IrisBiomeActuator.java b/core/src/main/java/art/arcane/iris/engine/actuator/IrisBiomeActuator.java index 73b1d5616..b77728ebd 100644 --- a/core/src/main/java/art/arcane/iris/engine/actuator/IrisBiomeActuator.java +++ b/core/src/main/java/art/arcane/iris/engine/actuator/IrisBiomeActuator.java @@ -22,7 +22,6 @@ import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.EngineAssignedActuator; import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiomeCustom; -import art.arcane.iris.platform.bukkit.BukkitBiome; import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.volmlib.util.documentation.BlockCoordinates; import art.arcane.iris.util.project.hunk.Hunk; @@ -33,7 +32,6 @@ import art.arcane.volmlib.util.scheduling.ChronoLatch; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.PlatformBiome; -import org.bukkit.block.Biome; public class IrisBiomeActuator extends EngineAssignedActuator { private final RNG rng; @@ -62,11 +60,9 @@ public class IrisBiomeActuator extends EngineAssignedActuator { biome = IrisPlatforms.get().registries().biome(key); matter = BiomeInjectMatter.get(IrisPlatforms.get().biomeWriter().biomeIdFor(key)); } else { - Biome v = ib.getSkyBiome(rng, x, 0, z); - PlatformBiome fallback = BukkitBiome.of(v); - PlatformBiome resolved = IrisPlatforms.get().registries().biome(fallback.key()); - biome = resolved == null ? fallback : resolved; - matter = BiomeInjectMatter.get(v); + String skyKey = ib.getSkyBiomeKey(rng, x, 0, z); + biome = IrisPlatforms.get().registries().biome(skyKey); + matter = BiomeInjectMatter.get(IrisPlatforms.get().biomeWriter().biomeIdFor(skyKey)); } if (biome != null) { diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java index a5fd419fa..79aecfc4c 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java @@ -18,7 +18,6 @@ package art.arcane.iris.engine.modifier; -import art.arcane.iris.platform.bukkit.BukkitBlockResolution; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.engine.IrisComplex; @@ -46,11 +45,8 @@ import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.matter.MatterBiomeInject; import art.arcane.volmlib.util.matter.slices.BiomeInjectMatter; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; -import art.arcane.iris.platform.bukkit.BukkitBlockState; import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.PlatformBlockState; -import org.bukkit.block.Biome; -import org.bukkit.block.data.BlockData; import java.util.IdentityHashMap; @@ -261,11 +257,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier 0) { - BlockData rawFluid = BukkitBlockResolution.get(entry.getFluidBlock()); - if (rawFluid == null) { - rawFluid = BukkitBlockResolution.get("minecraft:water"); + PlatformBlockState fluid = B.getStateOrNull(entry.getFluidBlock()); + if (fluid == null) { + fluid = B.getState("minecraft:water"); } - PlatformBlockState fluid = BukkitBlockState.of(rawFluid); int fluidCap = Math.min(sample.thickness - 1, localFluidHeight); for (int k = 1; k <= fluidCap; k++) { if (sample.solidMask[k]) { @@ -324,7 +319,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier 0 && fluidTopY + 1 < chunkHeight && BukkitBlockResolution.isAir(unwrap(output.get(xf, fluidTopY + 1, zf)))) { + if (fluidTopY > 0 && fluidTopY + 1 < chunkHeight && B.isAir(output.get(xf, fluidTopY + 1, zf))) { try { seaSurfaceDecorator.decorate(xf, zf, wx, wx + 1, wx - 1, wz, wz + 1, wz - 1, output, target, fluidTopY, chunkHeight); } catch (Throwable e) { @@ -397,17 +392,12 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier { if (this.color == null) { RandomColor randomColor = new RandomColor(getName().hashCode()); - if (this.getVanillaDerivative() == null) { + String vanillaKey = this.getVanillaDerivativeKey(); + RandomColor.Color col = vanillaKey == null ? null : VanillaBiomeColors.getColorType(vanillaKey); + if (col == null) { IrisLogging.warn("No vanilla biome found for " + getName()); return new Color(randomColor.randomColor()); } - RandomColor.Color col = VanillaBiomeMap.getColorType(this.getVanillaDerivative()); - RandomColor.Luminosity lum = VanillaBiomeMap.getColorLuminosity(this.getVanillaDerivative()); - RandomColor.SaturationType sat = VanillaBiomeMap.getColorSaturatiom(this.getVanillaDerivative()); + RandomColor.Luminosity lum = VanillaBiomeColors.getColorLuminosity(vanillaKey); + RandomColor.SaturationType sat = VanillaBiomeColors.getColorSaturation(vanillaKey); int newColorI = randomColor.randomColor(col, col == RandomColor.Color.MONOCHROME ? RandomColor.SaturationType.MONOCHROME : sat, lum); return new Color(newColorI); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java index 11e7bb7b9..7cd3cce36 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java @@ -29,7 +29,7 @@ import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KSet; import art.arcane.iris.util.common.data.DataProvider; -import art.arcane.volmlib.util.data.VanillaBiomeMap; +import art.arcane.volmlib.util.data.VanillaBiomeColors; import art.arcane.volmlib.util.inventorygui.RandomColor; import art.arcane.volmlib.util.json.JSONObject; import art.arcane.volmlib.util.math.RNG; @@ -448,10 +448,11 @@ public class IrisRegion extends IrisRegistrant implements IRare { int index = rand.nextInt(biomes.size()); IrisBiome biome = biomes.get(index); - if (biome.getVanillaDerivative() != null) { - RandomColor.Color col = VanillaBiomeMap.getColorType(biome.getVanillaDerivative()); - RandomColor.Luminosity lum = VanillaBiomeMap.getColorLuminosity(biome.getVanillaDerivative()); - RandomColor.SaturationType sat = VanillaBiomeMap.getColorSaturatiom(biome.getVanillaDerivative()); + String vanillaKey = biome.getVanillaDerivativeKey(); + RandomColor.Color col = vanillaKey == null ? null : VanillaBiomeColors.getColorType(vanillaKey); + if (col != null) { + RandomColor.Luminosity lum = VanillaBiomeColors.getColorLuminosity(vanillaKey); + RandomColor.SaturationType sat = VanillaBiomeColors.getColorSaturation(vanillaKey); int newColorI = randomColor.randomColor(col, col == RandomColor.Color.MONOCHROME ? RandomColor.SaturationType.MONOCHROME : sat, lum); return new Color(newColorI); }