mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-29 05:20:40 +00:00
dwa
This commit is contained in:
+139
-21
@@ -1,14 +1,17 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.QuartPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -26,12 +29,14 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CustomBiomeSource extends BiomeSource {
|
||||
@@ -42,11 +47,13 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
private final Registry<Biome> biomeCustomRegistry;
|
||||
private final Registry<Biome> biomeRegistry;
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final KMap<String, Holder<Biome>> customBiomes;
|
||||
private final Map<Biome, Holder<Biome>> vanillaSpawnBiomes;
|
||||
private final Holder<Biome> fallbackBiome;
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> structureBiomeCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> surfaceStructureBiomeCache = new ConcurrentHashMap<>();
|
||||
private volatile KMap<String, Holder<Biome>> customBiomes;
|
||||
private volatile Map<Biome, Holder<Biome>> vanillaSpawnBiomes;
|
||||
private volatile IrisDimension cacheDimension;
|
||||
|
||||
public CustomBiomeSource(long seed, Engine engine, World world) {
|
||||
this.engine = engine;
|
||||
@@ -56,20 +63,19 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
||||
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
|
||||
this.vanillaSpawnBiomes = fillVanillaSpawnBiomes(this.biomeCustomRegistry, this.biomeRegistry, engine);
|
||||
this.cacheDimension = engine.getDimension();
|
||||
}
|
||||
|
||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine, Holder<Biome> fallback) {
|
||||
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
|
||||
if (fallback != null) {
|
||||
biomes.add(fallback);
|
||||
}
|
||||
boolean resolutionFailed = false;
|
||||
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
Holder<Biome> vanillaHolder = NMSBinding.biomeToBiomeBase(registry, i.getVanillaDerivative());
|
||||
if (vanillaHolder != null) {
|
||||
biomes.add(vanillaHolder);
|
||||
} else if (!i.isCustom() && fallback != null) {
|
||||
biomes.add(fallback);
|
||||
} else if (!i.isCustom()) {
|
||||
resolutionFailed = true;
|
||||
}
|
||||
|
||||
if (i.isCustom()) {
|
||||
@@ -77,13 +83,17 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
|
||||
if (customHolder != null) {
|
||||
biomes.add(customHolder);
|
||||
} else if (fallback != null) {
|
||||
biomes.add(fallback);
|
||||
} else {
|
||||
resolutionFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((resolutionFailed || biomes.isEmpty()) && fallback != null) {
|
||||
biomes.add(fallback);
|
||||
}
|
||||
|
||||
return new ArrayList<>(biomes);
|
||||
}
|
||||
|
||||
@@ -135,12 +145,21 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
|
||||
@Override
|
||||
protected Stream<Holder<Biome>> collectPossibleBiomes() {
|
||||
return getAllBiomes(
|
||||
((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()))
|
||||
.lookup(Registries.BIOME).orElse(null),
|
||||
((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null),
|
||||
engine,
|
||||
fallbackBiome).stream();
|
||||
return possibleStructureBiomes().stream();
|
||||
}
|
||||
|
||||
Set<Holder<Biome>> possibleStructureBiomes() {
|
||||
ensureCachesCurrent();
|
||||
World world = BukkitWorldBinding.world(engine.getWorld());
|
||||
if (world == null) {
|
||||
throw new IllegalStateException("Iris biome source has no bound Bukkit world");
|
||||
}
|
||||
Registry<Biome> customRegistry = ((RegistryAccess) getFor(
|
||||
RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()))
|
||||
.lookup(Registries.BIOME).orElse(null);
|
||||
Registry<Biome> worldRegistry = ((CraftWorld) world).getHandle().registryAccess()
|
||||
.lookup(Registries.BIOME).orElse(null);
|
||||
return Set.copyOf(getAllBiomes(customRegistry, worldRegistry, engine, fallbackBiome));
|
||||
}
|
||||
|
||||
private KMap<String, Holder<Biome>> fillCustomBiomes(Registry<Biome> customRegistry, Engine engine, Holder<Biome> fallback) {
|
||||
@@ -194,6 +213,7 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
}
|
||||
|
||||
Holder<Biome> getVanillaSpawnBiome(Holder<Biome> biome) {
|
||||
ensureCachesCurrent();
|
||||
if (biome == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -211,6 +231,11 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
|
||||
@Override
|
||||
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||
ensureCachesCurrent();
|
||||
if (isGuaranteedSurfaceBiome(y)) {
|
||||
return getSurfaceStructureBiomeHolder(x, z);
|
||||
}
|
||||
|
||||
long cacheKey = packNoiseKey(x, y, z);
|
||||
Holder<Biome> cachedHolder = structureBiomeCache.get(cacheKey);
|
||||
if (cachedHolder != null) {
|
||||
@@ -230,7 +255,69 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return resolvedHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Holder<Biome>> getBiomesWithin(int x, int y, int z, int radius, Climate.Sampler sampler) {
|
||||
ensureCachesCurrent();
|
||||
int minQuartY = QuartPos.fromBlock(y - radius);
|
||||
boolean monumentQuery = radius == 29
|
||||
&& y == engine.getMinHeight() + engine.getDimension().getFluidHeight();
|
||||
if (!monumentQuery && !isGuaranteedSurfaceBiome(minQuartY)) {
|
||||
return super.getBiomesWithin(x, y, z, radius, sampler);
|
||||
}
|
||||
int minQuartX = QuartPos.fromBlock(x - radius);
|
||||
int maxQuartX = QuartPos.fromBlock(x + radius);
|
||||
int minQuartZ = QuartPos.fromBlock(z - radius);
|
||||
int maxQuartZ = QuartPos.fromBlock(z + radius);
|
||||
int columns = (maxQuartX - minQuartX + 1) * (maxQuartZ - minQuartZ + 1);
|
||||
Set<Holder<Biome>> biomes = new HashSet<>(columns);
|
||||
for (int quartZ = minQuartZ; quartZ <= maxQuartZ; quartZ++) {
|
||||
for (int quartX = minQuartX; quartX <= maxQuartX; quartX++) {
|
||||
biomes.add(getSurfaceStructureBiomeHolder(quartX, quartZ));
|
||||
}
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
|
||||
private Holder<Biome> getSurfaceStructureBiomeHolder(int x, int z) {
|
||||
long columnKey = packColumnKey(x, z);
|
||||
Holder<Biome> surfaceHolder = surfaceStructureBiomeCache.get(columnKey);
|
||||
if (surfaceHolder != null) {
|
||||
return surfaceHolder;
|
||||
}
|
||||
Holder<Biome> resolvedSurfaceHolder = resolveSurfaceStructureBiomeHolder(x, z);
|
||||
Holder<Biome> existingSurfaceHolder = surfaceStructureBiomeCache.putIfAbsent(columnKey, resolvedSurfaceHolder);
|
||||
if (existingSurfaceHolder != null) {
|
||||
return existingSurfaceHolder;
|
||||
}
|
||||
if (surfaceStructureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||
surfaceStructureBiomeCache.clear();
|
||||
}
|
||||
return resolvedSurfaceHolder;
|
||||
}
|
||||
|
||||
private boolean isGuaranteedSurfaceBiome(int quartY) {
|
||||
if (engine == null || engine.isClosed() || engine.getComplex() == null) {
|
||||
return false;
|
||||
}
|
||||
int worldMinHeight = engine.getWorld().minHeight();
|
||||
int internalY = (quartY << 2) - worldMinHeight;
|
||||
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||
return internalY > caveSwitchInternalY;
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveSurfaceStructureBiomeHolder(int x, int z) {
|
||||
int blockX = x << 2;
|
||||
int blockZ = z << 2;
|
||||
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
if (irisBiome == null) {
|
||||
return getFallbackBiome();
|
||||
}
|
||||
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, irisBiome.getVanillaDerivative());
|
||||
return holder == null ? getFallbackBiome() : holder;
|
||||
}
|
||||
|
||||
public Holder<Biome> getVisibleNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||
ensureCachesCurrent();
|
||||
long cacheKey = packNoiseKey(x, y, z);
|
||||
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
|
||||
if (cachedHolder != null) {
|
||||
@@ -250,6 +337,28 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return resolvedHolder;
|
||||
}
|
||||
|
||||
private void ensureCachesCurrent() {
|
||||
IrisDimension dimension = engine.getDimension();
|
||||
if (cacheDimension == dimension) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (cacheDimension == dimension) {
|
||||
return;
|
||||
}
|
||||
KMap<String, Holder<Biome>> refreshedCustomBiomes = fillCustomBiomes(
|
||||
biomeCustomRegistry, engine, fallbackBiome);
|
||||
Map<Biome, Holder<Biome>> refreshedSpawnBiomes = fillVanillaSpawnBiomes(
|
||||
biomeCustomRegistry, biomeRegistry, engine);
|
||||
noiseBiomeCache.clear();
|
||||
structureBiomeCache.clear();
|
||||
surfaceStructureBiomeCache.clear();
|
||||
customBiomes = refreshedCustomBiomes;
|
||||
vanillaSpawnBiomes = refreshedSpawnBiomes;
|
||||
cacheDimension = dimension;
|
||||
}
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveStructureBiomeHolder(int x, int y, int z) {
|
||||
BiomeResolution resolution = resolveBiomeResolution(x, y, z);
|
||||
if (resolution == null) {
|
||||
@@ -315,14 +424,19 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
int blockY = y << 2;
|
||||
int worldMinHeight = engine.getWorld().minHeight();
|
||||
int internalY = blockY - worldMinHeight;
|
||||
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
|
||||
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
||||
boolean belowSurface = internalY <= surfaceInternalY - 8;
|
||||
boolean underground = deepUnderground && belowSurface;
|
||||
IrisBiome irisBiome = underground
|
||||
? engine.getCaveBiome(blockX, internalY, blockZ)
|
||||
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
boolean underground = false;
|
||||
IrisBiome irisBiome;
|
||||
if (deepUnderground) {
|
||||
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
|
||||
underground = internalY <= surfaceInternalY - 8;
|
||||
irisBiome = underground
|
||||
? engine.getCaveBiome(blockX, internalY, blockZ)
|
||||
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
} else {
|
||||
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
}
|
||||
if (irisBiome == null && underground) {
|
||||
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
}
|
||||
@@ -357,6 +471,10 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
| ((long) y & 4095L);
|
||||
}
|
||||
|
||||
private static long packColumnKey(int x, int z) {
|
||||
return ((long) x << 32) ^ ((long) z & 4294967295L);
|
||||
}
|
||||
|
||||
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
|
||||
if (customRegistry == null || engine == null || customBiomeId == null || customBiomeId.isBlank()) {
|
||||
return null;
|
||||
|
||||
+227
-86
@@ -1,5 +1,20 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisVanillaStructureStiltSettings;
|
||||
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||
import art.arcane.iris.util.common.reflect.WrappedReturningMethod;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
@@ -7,6 +22,13 @@ import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.util.random.Weighted;
|
||||
import net.minecraft.util.random.WeightedList;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
@@ -23,26 +45,6 @@ import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||
import art.arcane.iris.util.common.reflect.WrappedReturningMethod;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.util.random.Weighted;
|
||||
import net.minecraft.util.random.WeightedList;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
@@ -52,35 +54,44 @@ import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import java.util.stream.Collectors;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
private static final String NATIVE_MONUMENT_KEY = "minecraft:monument";
|
||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
||||
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
||||
private final ChunkGenerator delegate;
|
||||
private final Engine engine;
|
||||
private final CustomBiomeSource customBiomeSource;
|
||||
private final int runtimeMinY;
|
||||
private final int runtimeHeight;
|
||||
private final ConcurrentHashMap<SpawnTableKey, WeightedList<MobSpawnSettings.SpawnerData>> mergedSpawnTables = new ConcurrentHashMap<>();
|
||||
private volatile Set<String> reachableStructureKeysCache;
|
||||
private volatile ReachableStructureCache reachableStructureCache;
|
||||
private volatile StructureStepCache structureStepCache;
|
||||
|
||||
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
||||
this(delegate, engine, world, new CustomBiomeSource(seed, engine, world));
|
||||
@@ -91,6 +102,9 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
this.delegate = delegate;
|
||||
this.engine = engine;
|
||||
this.customBiomeSource = customBiomeSource;
|
||||
ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
this.runtimeMinY = level.getMinY();
|
||||
this.runtimeHeight = level.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,16 +119,20 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
int[] at = IrisStructureLocator.locate(engine, id.toString(), pos.getX(), pos.getZ(), Math.max(1, radius));
|
||||
if (at == null) {
|
||||
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
|
||||
engine, id.toString(), pos.getX(), pos.getZ(), Math.max(1, radius));
|
||||
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) at[0] - pos.getX();
|
||||
long dz = (long) at[2] - pos.getZ();
|
||||
if (!result.found()) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) result.originX() - pos.getX();
|
||||
long dz = (long) result.originZ() - pos.getZ();
|
||||
long d = dx * dx + dz * dz;
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = new BlockPos(at[0], at[1], at[2]);
|
||||
best = new BlockPos(result.originX(), result.baseY(), result.originZ());
|
||||
bestHolder = holder;
|
||||
}
|
||||
}
|
||||
@@ -122,7 +140,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
return Pair.of(best, bestHolder);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
IrisLogging.reportError("Iris-placed structure lookup failed near "
|
||||
+ pos.getX() + ", " + pos.getZ() + ".", e);
|
||||
}
|
||||
if (!importedControl().active()) {
|
||||
return null;
|
||||
@@ -134,28 +153,30 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
try {
|
||||
return delegate.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.error("Vanilla structure locate failed near " + pos.getX() + ", " + pos.getZ() + ": " + e);
|
||||
IrisLogging.reportError(e);
|
||||
IrisLogging.reportError("Vanilla structure locate failed near "
|
||||
+ pos.getX() + ", " + pos.getZ() + ".", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private HolderSet<Structure> filterReachableStructures(ServerLevel level, HolderSet<Structure> holders) {
|
||||
Set<String> reachable = reachableStructureKeysCache;
|
||||
if (reachable == null) {
|
||||
reachable = VanillaStructureBiomes.reachableStructureKeys(level, delegate.getBiomeSource());
|
||||
reachableStructureKeysCache = reachable;
|
||||
}
|
||||
if (reachable.isEmpty()) {
|
||||
return holders;
|
||||
}
|
||||
Set<String> reachable = reachableStructureKeys(level);
|
||||
IrisImportedStructureControl control = importedControl();
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
List<Holder<Structure>> kept = new ArrayList<>();
|
||||
List<Holder<Structure>> kept = new ArrayList<>(holders.size());
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id != null && reachable.contains(id.toString())) {
|
||||
kept.add(holder);
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
String key = id.toString();
|
||||
IrisNativeStructureDecision decision = control.resolve(
|
||||
key, NativeStructurePostProcessor.isUndergroundStep(holder.value().step()));
|
||||
if (NATIVE_MONUMENT_KEY.equals(key) || !decision.generate()
|
||||
|| IrisStructureLocator.suppressesVanilla(engine, key) || !reachable.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
kept.add(holder);
|
||||
}
|
||||
if (kept.size() == holders.size()) {
|
||||
return holders;
|
||||
@@ -163,6 +184,30 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
return HolderSet.direct(kept);
|
||||
}
|
||||
|
||||
private Set<String> reachableStructureKeys(ServerLevel level) {
|
||||
IrisDimension dimension = engine.getDimension();
|
||||
ReachableStructureCache cached = reachableStructureCache;
|
||||
if (cached != null && cached.dimension() == dimension) {
|
||||
return cached.keys();
|
||||
}
|
||||
synchronized (this) {
|
||||
cached = reachableStructureCache;
|
||||
if (cached != null && cached.dimension() == dimension) {
|
||||
return cached.keys();
|
||||
}
|
||||
Set<String> reachable;
|
||||
try {
|
||||
reachable = Set.copyOf(VanillaStructureBiomes.reachableStructureKeys(level, customBiomeSource));
|
||||
} catch (Throwable error) {
|
||||
IrisLogging.reportError("Iris could not resolve native structure biome reachability; "
|
||||
+ "native locate is disabled until the next hotload.", error);
|
||||
reachable = Set.of();
|
||||
}
|
||||
reachableStructureCache = new ReachableStructureCache(dimension, reachable);
|
||||
return reachable;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends ChunkGenerator> codec() {
|
||||
return MapCodec.unit(null);
|
||||
@@ -177,12 +222,12 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public int getMinY() {
|
||||
return delegate.getMinY();
|
||||
return runtimeMinY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSeaLevel() {
|
||||
return delegate.getSeaLevel();
|
||||
return runtimeMinY + engine.getDimension().getFluidHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -190,7 +235,42 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
if (!importedControl().active()) {
|
||||
return;
|
||||
}
|
||||
Map<Structure, StructureStart> previousStarts = new HashMap<>(access.getAllStarts());
|
||||
super.createStructures(registryAccess, structureState, structureManager, access, templateManager, levelKey);
|
||||
adjustGeneratedStructures(registryAccess, access, previousStarts);
|
||||
}
|
||||
|
||||
private void adjustGeneratedStructures(RegistryAccess registryAccess, ChunkAccess access, Map<Structure, StructureStart> previousStarts) {
|
||||
Registry<Structure> registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
|
||||
IrisImportedStructureControl control = importedControl();
|
||||
for (Map.Entry<Structure, StructureStart> entry : access.getAllStarts().entrySet()) {
|
||||
Structure structure = entry.getKey();
|
||||
StructureStart start = entry.getValue();
|
||||
if (!start.isValid() || previousStarts.get(structure) == start) {
|
||||
continue;
|
||||
}
|
||||
Identifier id = registry.getKey(structure);
|
||||
String structureId = id == null ? null : id.toString();
|
||||
IrisNativeStructureDecision decision = control.resolve(
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(structure.step()));
|
||||
if (!decision.generate() || IrisStructureLocator.suppressesVanilla(engine, structureId)) {
|
||||
access.setStartForStructure(structure, StructureStart.INVALID_START);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.applyVerticalShift(
|
||||
start,
|
||||
decision.yShift(),
|
||||
access.getMinY(),
|
||||
access.getMinY() + access.getHeight());
|
||||
} catch (RuntimeException error) {
|
||||
access.setStartForStructure(structure, StructureStart.INVALID_START);
|
||||
IrisLogging.reportError("Iris rejected native structure " + structureId + " in chunk "
|
||||
+ access.getPos().x() + "," + access.getPos().z()
|
||||
+ " because its vertical bounds are invalid.", error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IrisImportedStructureControl importedControl() {
|
||||
@@ -281,68 +361,119 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
SectionPos sectionPos = SectionPos.of(chunkPos, world.getMinSectionY());
|
||||
BlockPos origin = sectionPos.origin();
|
||||
Registry<Structure> registry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
Map<Integer, List<Structure>> byStep = registry.stream().collect(Collectors.groupingBy(s -> s.step().ordinal()));
|
||||
List<List<Structure>> byStep = structuresByStep(registry);
|
||||
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
long decoSeed = random.setDecorationSeed(world.getSeed(), origin.getX(), origin.getZ());
|
||||
BoundingBox area = writableArea(chunk);
|
||||
int steps = GenerationStep.Decoration.values().length;
|
||||
IrisImportedStructureControl control = importedControl();
|
||||
List<NativePlacementGroup> placementGroups = new ArrayList<>();
|
||||
List<NativeStructurePostProcessor.VegetationTarget> vegetationTargets = new ArrayList<>();
|
||||
for (int step = 0; step < steps; step++) {
|
||||
int index = 0;
|
||||
for (Structure structure : byStep.getOrDefault(step, List.of())) {
|
||||
for (Structure structure : byStep.get(step)) {
|
||||
Object id = registry.getKey(structure);
|
||||
String structureId = id == null ? null : id.toString();
|
||||
if (control.shouldGenerate(structureId) && !IrisStructureLocator.suppressesVanilla(engine, structureId)) {
|
||||
random.setFeatureSeed(decoSeed, index, step);
|
||||
int[] offset = control.resolveOffset(structureId, isUndergroundStep(structure.step()));
|
||||
boolean shifted = offset[0] != 0 || offset[1] != 0 || offset[2] != 0;
|
||||
WorldGenLevel target = shifted ? shiftedLevel(world, offset[0], offset[1], offset[2]) : world;
|
||||
BoundingBox placeArea = shifted
|
||||
? new BoundingBox(area.minX() - offset[0], area.minY(), area.minZ() - offset[2], area.maxX() - offset[0], area.maxY(), area.maxZ() - offset[2])
|
||||
: area;
|
||||
IrisNativeStructureDecision decision = control.resolve(
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(structure.step()));
|
||||
if (decision.generate() && !IrisStructureLocator.suppressesVanilla(engine, structureId)) {
|
||||
try {
|
||||
structureManager.startsForStructure(sectionPos, structure)
|
||||
.forEach(start -> start.placeInChunk(target, structureManager, this, random, placeArea, chunkPos));
|
||||
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
|
||||
if (!starts.isEmpty()) {
|
||||
List<StructureStart> resolvedStarts = List.copyOf(starts);
|
||||
placementGroups.add(new NativePlacementGroup(
|
||||
structureId, decision, index, step, resolvedStarts));
|
||||
for (StructureStart start : resolvedStarts) {
|
||||
vegetationTargets.add(new NativeStructurePostProcessor.VegetationTarget(
|
||||
start, decision.clearVegetation()));
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
IrisLogging.reportError("Iris failed to resolve native structure " + structureId
|
||||
+ " in chunk " + chunkPos.x() + "," + chunkPos.z() + ".", e);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.clearIntersectingVegetation(
|
||||
world, chunk, area, vegetationTargets);
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Iris failed to clear vegetation from native structures in chunk "
|
||||
+ chunkPos.x() + "," + chunkPos.z() + ".", e);
|
||||
}
|
||||
for (NativePlacementGroup group : placementGroups) {
|
||||
random.setFeatureSeed(decoSeed, group.featureIndex(), group.step());
|
||||
try {
|
||||
for (StructureStart start : group.starts()) {
|
||||
placeVanillaStructure(world, structureManager, random, area, chunkPos,
|
||||
group.structureId(), start, group.decision());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Iris failed to place native structure " + group.structureId()
|
||||
+ " in chunk " + chunkPos.x() + "," + chunkPos.z() + ".", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isUndergroundStep(GenerationStep.Decoration step) {
|
||||
return step == GenerationStep.Decoration.UNDERGROUND_STRUCTURES
|
||||
|| step == GenerationStep.Decoration.STRONGHOLDS;
|
||||
private void placeVanillaStructure(WorldGenLevel world, StructureManager structureManager, WorldgenRandom random,
|
||||
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
|
||||
IrisNativeStructureDecision decision) {
|
||||
NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos,
|
||||
structureId, start, decision, this::resolveStiltBlock);
|
||||
}
|
||||
|
||||
private WorldGenLevel shiftedLevel(WorldGenLevel world, int dx, int dy, int dz) {
|
||||
return (WorldGenLevel) Proxy.newProxyInstance(
|
||||
WorldGenLevel.class.getClassLoader(),
|
||||
new Class<?>[]{WorldGenLevel.class},
|
||||
(proxy, method, args) -> {
|
||||
if (args != null) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] instanceof BlockPos bp) {
|
||||
args[i] = new BlockPos(bp.getX() + dx, bp.getY() + dy, bp.getZ() + dz);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return method.invoke(world, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
});
|
||||
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
|
||||
StructureStepCache cached = structureStepCache;
|
||||
if (cached != null && cached.registry() == registry) {
|
||||
return cached.structures();
|
||||
}
|
||||
synchronized (this) {
|
||||
cached = structureStepCache;
|
||||
if (cached != null && cached.registry() == registry) {
|
||||
return cached.structures();
|
||||
}
|
||||
int steps = GenerationStep.Decoration.values().length;
|
||||
List<List<Structure>> grouped = new ArrayList<>(steps);
|
||||
for (int step = 0; step < steps; step++) {
|
||||
grouped.add(new ArrayList<>());
|
||||
}
|
||||
for (Structure structure : registry) {
|
||||
grouped.get(structure.step().ordinal()).add(structure);
|
||||
}
|
||||
for (int step = 0; step < steps; step++) {
|
||||
grouped.set(step, List.copyOf(grouped.get(step)));
|
||||
}
|
||||
List<List<Structure>> resolved = List.copyOf(grouped);
|
||||
structureStepCache = new StructureStepCache(registry, resolved);
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
private BlockState resolveStiltBlock(IrisVanillaStructureStiltSettings settings, RNG rng, int x, int y, int z) {
|
||||
if (settings.getPalette() == null) {
|
||||
return Blocks.COBBLESTONE.defaultBlockState();
|
||||
}
|
||||
PlatformBlockState platformState = settings.getPalette().get(rng, x, y, z, engine.getData());
|
||||
if (platformState == null || !(platformState.nativeHandle() instanceof BlockData blockData)) {
|
||||
return Blocks.COBBLESTONE.defaultBlockState();
|
||||
}
|
||||
if (blockData instanceof IrisCustomData customData) {
|
||||
blockData = customData.getBase();
|
||||
}
|
||||
if (blockData instanceof CraftBlockData craftBlockData) {
|
||||
return craftBlockData.getState();
|
||||
}
|
||||
return Blocks.COBBLESTONE.defaultBlockState();
|
||||
}
|
||||
|
||||
private BoundingBox writableArea(ChunkAccess chunk) {
|
||||
ChunkPos cp = chunk.getPos();
|
||||
int i = cp.getMinBlockX();
|
||||
int j = cp.getMinBlockZ();
|
||||
int minY = getMinY() + 1;
|
||||
int maxY = getMinY() + engine.getHeight() - 1;
|
||||
int minY = chunk.getMinY();
|
||||
int maxY = minY + chunk.getHeight() - 1;
|
||||
return new BoundingBox(i, minY, j, i + 15, maxY, j + 15);
|
||||
}
|
||||
|
||||
@@ -403,7 +534,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public int getGenDepth() {
|
||||
return delegate.getGenDepth();
|
||||
return runtimeHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -447,7 +578,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
Method setHeight = null;
|
||||
for (Method method : Heightmap.class.getDeclaredMethods()) {
|
||||
var types = method.getParameterTypes();
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|
||||
|| !method.getReturnType().equals(void.class))
|
||||
continue;
|
||||
@@ -475,4 +606,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
private record SpawnTableKey(Biome biome, MobCategory category) {
|
||||
}
|
||||
|
||||
private record ReachableStructureCache(IrisDimension dimension, Set<String> keys) {
|
||||
}
|
||||
|
||||
private record StructureStepCache(Registry<Structure> registry, List<List<Structure>> structures) {
|
||||
}
|
||||
|
||||
private record NativePlacementGroup(String structureId, IrisNativeStructureDecision decision,
|
||||
int featureIndex, int step, List<StructureStart> starts) {
|
||||
}
|
||||
}
|
||||
|
||||
+69
-50
@@ -13,6 +13,7 @@ import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.data.chunk.TerrainChunk;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.util.project.agent.Agent;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -71,6 +72,7 @@ import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||
import net.minecraft.tags.TagKey;
|
||||
@@ -93,6 +95,7 @@ import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
@@ -1019,30 +1022,39 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException {
|
||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
||||
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
||||
ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
validateDimensionContract(engine, world, level);
|
||||
|
||||
ChunkMap chunkMap = level.getChunkSource().chunkMap;
|
||||
Field worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
||||
worldGenContextField.setAccessible(true);
|
||||
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
||||
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||
String expectedDimensionType = "iris:" + engine.getDimension().getDimensionTypeKey();
|
||||
if (dimensionType != null) {
|
||||
String actualDimensionType = dimensionType.identifier().toString();
|
||||
if (!dimensionType.identifier().getNamespace().equals("iris")) {
|
||||
IrisLogging.error("Loaded world %s with invalid dimension type! expected=%s actual=%s", world.getName(), expectedDimensionType, actualDimensionType);
|
||||
} else {
|
||||
IrisLogging.debug("Loaded world " + world.getName() + " with Iris dimension type " + actualDimensionType);
|
||||
}
|
||||
} else {
|
||||
IrisLogging.error("Loaded world %s with unknown dimension type! expected=%s", world.getName(), expectedDimensionType);
|
||||
}
|
||||
WorldGenContext worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
||||
|
||||
IrisChunkGenerator irisGenerator = new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world);
|
||||
var newContext = new WorldGenContext(
|
||||
WorldGenContext newContext = new WorldGenContext(
|
||||
worldGenContext.level(), irisGenerator,
|
||||
worldGenContext.structureManager(), worldGenContext.lightEngine(), worldGenContext.mainThreadExecutor(), worldGenContext.unsavedListener());
|
||||
|
||||
worldGenContextField.set(chunkMap, newContext);
|
||||
retargetStructureCheck(((CraftWorld) world).getHandle(), irisGenerator);
|
||||
retargetStructureCheck(level, irisGenerator);
|
||||
}
|
||||
|
||||
private void validateDimensionContract(Engine engine, World world, ServerLevel level) {
|
||||
DimensionType actualType = level.dimensionType();
|
||||
String actualTypeKey = level.dimensionTypeRegistration().unwrapKey()
|
||||
.map(key -> key.identifier().toString())
|
||||
.orElse("<unregistered>");
|
||||
IrisDimensionRuntimeContract expected = IrisDimensionRuntimeContract.expected(engine.getDimension(), "iris");
|
||||
IrisDimensionRuntimeContract actual = new IrisDimensionRuntimeContract(
|
||||
actualTypeKey,
|
||||
actualType.minY(),
|
||||
actualType.height(),
|
||||
actualType.logicalHeight());
|
||||
String runtimeName = "Bukkit world '" + world.getName() + "'";
|
||||
expected.requireExact(runtimeName, actual);
|
||||
expected.requireHeight(runtimeName, level.getMinY(), level.getHeight());
|
||||
expected.requireHeight(runtimeName, world.getMinHeight(), world.getMaxHeight() - world.getMinHeight());
|
||||
IrisLogging.debug("Loaded world " + world.getName() + " with exact Iris dimension type " + actualTypeKey);
|
||||
}
|
||||
|
||||
private static void retargetStructureCheck(ServerLevel level, IrisChunkGenerator generator) throws NoSuchFieldException, IllegalAccessException {
|
||||
@@ -1208,33 +1220,38 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
IrisLogging.info("Injecting Bukkit");
|
||||
new AgentBuilder.Default()
|
||||
.disableClassFormatChanges()
|
||||
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
|
||||
.type(ElementMatchers.is(ServerLevel.class))
|
||||
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
|
||||
builder.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor()
|
||||
.and(ElementMatchers.takesArgument(0, MinecraftServer.class))
|
||||
.and(ElementMatchers.takesArgument(5, LevelStem.class)))))
|
||||
.installOn(Agent.getInstrumentation());
|
||||
ByteBuddy buddy = new ByteBuddy();
|
||||
for (Class<?> clazz : List.of(ChunkAccess.class, ProtoChunk.class)) {
|
||||
buddy.redefine(clazz)
|
||||
.visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class))))
|
||||
.make()
|
||||
.load(clazz.getClassLoader(), Agent.installed());
|
||||
synchronized (injected) {
|
||||
if (injected.get()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
IrisLogging.info("Injecting Bukkit");
|
||||
new AgentBuilder.Default()
|
||||
.disableClassFormatChanges()
|
||||
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
|
||||
.type(ElementMatchers.is(ServerLevel.class))
|
||||
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
|
||||
builder.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor()
|
||||
.and(ElementMatchers.takesArgument(0, MinecraftServer.class))
|
||||
.and(ElementMatchers.takesArgument(5, LevelStem.class))
|
||||
.and(ElementMatchers.takesArgument(12, ChunkGenerator.class)))))
|
||||
.installOn(Agent.getInstrumentation());
|
||||
ByteBuddy buddy = new ByteBuddy();
|
||||
for (Class<?> clazz : List.of(ChunkAccess.class, ProtoChunk.class)) {
|
||||
buddy.redefine(clazz)
|
||||
.visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class))))
|
||||
.make()
|
||||
.load(clazz.getClassLoader(), Agent.installed());
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
injected.set(true);
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1304,7 +1321,8 @@ public class NMSBinding implements INMSBinding {
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(4) ResourceKey<Level> dimensionKey,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) ChunkGenerator constructorGenerator
|
||||
) {
|
||||
if (dimensionKey == null)
|
||||
return;
|
||||
@@ -1315,18 +1333,19 @@ public class NMSBinding implements INMSBinding {
|
||||
return;
|
||||
}
|
||||
|
||||
Object generator = Class.forName("art.arcane.iris.core.lifecycle.WorldLifecycleStaging", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("consumeStemGenerator", String.class)
|
||||
.invoke(null, levelId);
|
||||
ClassLoader pluginClassLoader = Bukkit.getPluginManager().getPlugin("Iris").getClass().getClassLoader();
|
||||
Class<?> generatorType = Class.forName("art.arcane.iris.engine.platform.PlatformChunkGenerator", true, pluginClassLoader);
|
||||
Object generator = generatorType.isInstance(constructorGenerator) ? constructorGenerator : null;
|
||||
if (generator == null) {
|
||||
generator = Class.forName("art.arcane.iris.core.lifecycle.WorldLifecycleStaging", true, pluginClassLoader)
|
||||
.getDeclaredMethod("consumeStemGenerator", String.class)
|
||||
.invoke(null, levelId);
|
||||
}
|
||||
if (!(generator instanceof ChunkGenerator gen) || !gen.getClass().getPackageName().startsWith("art.arcane.iris")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object bindings = Class.forName("art.arcane.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
Object bindings = Class.forName("art.arcane.iris.core.nms.INMS", true, pluginClassLoader)
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
if (bindings == null) {
|
||||
|
||||
+4
-1
@@ -25,7 +25,10 @@ final class VanillaStructureBiomes {
|
||||
if (source == null) {
|
||||
return keys;
|
||||
}
|
||||
for (Holder<Biome> holder : source.possibleBiomes()) {
|
||||
Set<Holder<Biome>> possibleBiomes = source instanceof CustomBiomeSource customBiomeSource
|
||||
? customBiomeSource.possibleStructureBiomes()
|
||||
: source.possibleBiomes();
|
||||
for (Holder<Biome> holder : possibleBiomes) {
|
||||
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||
if (key.isPresent()) {
|
||||
keys.add(key.get().identifier().toString());
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IrisChunkGeneratorMonumentLocateContractTest {
|
||||
@Test
|
||||
public void irisPlacementRunsBeforeExactNativeMonumentIsRemovedFromDelegateLookup() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
int findStart = source.indexOf("findNearestMapStructure(ServerLevel level");
|
||||
assertTrue(findStart >= 0);
|
||||
int filterStart = source.indexOf("private HolderSet<Structure> filterReachableStructures", findStart);
|
||||
assertTrue(filterStart > findStart);
|
||||
String findMethod = source.substring(findStart, filterStart);
|
||||
int irisLocate = findMethod.indexOf("IrisStructureLocator.locate(");
|
||||
int searchLimit = findMethod.indexOf("LocateStatus.SEARCH_LIMIT_REACHED", irisLocate);
|
||||
int limitSkip = findMethod.indexOf("continue;", searchLimit);
|
||||
int nativeFilter = findMethod.indexOf("filterReachableStructures(level, holders)");
|
||||
int delegateLocate = findMethod.indexOf("delegate.findNearestMapStructure(level, reachable");
|
||||
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys", filterStart);
|
||||
assertTrue(reachabilityStart > filterStart);
|
||||
String filterMethod = source.substring(filterStart, reachabilityStart);
|
||||
int monumentReject = filterMethod.indexOf("if (NATIVE_MONUMENT_KEY.equals(key)");
|
||||
int rejectContinue = filterMethod.indexOf("continue;", monumentReject);
|
||||
|
||||
assertTrue(irisLocate >= 0);
|
||||
assertTrue(searchLimit > irisLocate);
|
||||
assertTrue(limitSkip > searchLimit);
|
||||
assertTrue(nativeFilter > limitSkip);
|
||||
assertTrue(delegateLocate > nativeFilter);
|
||||
assertTrue(monumentReject >= 0);
|
||||
assertTrue(rejectContinue > monumentReject);
|
||||
assertTrue(findMethod.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stiltSupportUsesPlacedSolidOccupancyWithoutSnapshotDifferenceRequirement() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")));
|
||||
int placement = source.indexOf("start.placeInChunk(world, structureManager, generator");
|
||||
int stiltPlacement = source.indexOf("placeStilts(world, area, structureId, start", placement);
|
||||
int occupancyCheck = source.indexOf("if (state.isSolid())", stiltPlacement);
|
||||
|
||||
assertTrue(placement >= 0);
|
||||
assertTrue(stiltPlacement > placement);
|
||||
assertTrue(occupancyCheck > stiltPlacement);
|
||||
assertFalse(source.contains("state.equals("));
|
||||
assertFalse(source.contains("snapshot.states"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalShiftMovesPiecesJigsawJunctionsAndCachedBoundsTogether() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")));
|
||||
int method = source.indexOf("public static int applyVerticalShift");
|
||||
int clamp = source.indexOf("StructureVerticalBounds.clampOffset", method);
|
||||
int pieceMove = source.indexOf("piece.move(0, offsetY, 0)", clamp);
|
||||
int junctionMove = source.indexOf("junction.getSourceGroundY() + offsetY", pieceMove);
|
||||
int boundsMove = source.indexOf("bounds.move(0, offsetY, 0)", junctionMove);
|
||||
|
||||
assertTrue(method >= 0);
|
||||
assertTrue(clamp > method);
|
||||
assertTrue(pieceMove > clamp);
|
||||
assertTrue(junctionMove > pieceMove);
|
||||
assertTrue(boundsMove > junctionMove);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructurePostProcessorVegetationTest {
|
||||
@Test
|
||||
public void surfaceStructuresClearTreeColumnsAutomatically() {
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearVegetationColumn(100, 100, false));
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearVegetationColumn(116, 100, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buriedStructuresPreserveUnrelatedSurfaceForest() {
|
||||
assertFalse(NativeStructurePostProcessor.shouldClearVegetationColumn(99, 100, false));
|
||||
assertFalse(NativeStructurePostProcessor.shouldClearVegetationColumn(20, 100, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitVegetationOptionForcesUnusualPlacementCleanup() {
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearVegetationColumn(20, 100, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndergroundGenerationStepsShareOneClassification() {
|
||||
assertTrue(NativeStructurePostProcessor.isUndergroundStep(
|
||||
GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
|
||||
assertTrue(NativeStructurePostProcessor.isUndergroundStep(
|
||||
GenerationStep.Decoration.UNDERGROUND_DECORATION));
|
||||
assertTrue(NativeStructurePostProcessor.isUndergroundStep(
|
||||
GenerationStep.Decoration.STRONGHOLDS));
|
||||
assertFalse(NativeStructurePostProcessor.isUndergroundStep(
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user