First Test MV

This commit is contained in:
Brian Neumann-Fopiano
2026-08-07 14:04:17 -06:00
parent 42b8db4856
commit 68b842d5dc
24 changed files with 237 additions and 128 deletions
+3 -3
View File
@@ -18,9 +18,9 @@ Canonical English is defined in the typed Java catalogs under `core/src/main/jav
| Platform | Artifact | Minecraft | Notes | | Platform | Artifact | Minecraft | Notes |
|---|---|---|---| |---|---|---|---|
| Paper / Purpur / Leaf / Canvas | plugin jar | 26.2 | Full feature set | | Paper / Purpur / Leaf / Canvas | plugin jar | 26.1.2 - 26.2 | Full feature set |
| Folia | plugin jar | 26.2 | Region-safe scheduling throughout | | Folia | plugin jar | 26.1.2 - 26.2 | Region-safe scheduling throughout |
| Spigot / CraftBukkit | plugin jar | 26.2 | Full feature set | | Spigot / CraftBukkit | plugin jar | 26.1.2 - 26.2 | Full feature set |
| Fabric | mod jar | 26.2 | Server worldgen + client HUD; requires Fabric Loader 0.19.3+ | | Fabric | mod jar | 26.2 | Server worldgen + client HUD; requires Fabric Loader 0.19.3+ |
| Forge | mod jar | 26.2 | Server worldgen + client HUD; requires Forge 65.0.4+ | | Forge | mod jar | 26.2 | Server worldgen + client HUD; requires Forge 65.0.4+ |
| NeoForge | mod jar | 26.2 | Server worldgen + client HUD; requires NeoForge 26.2.0.12-beta+ | | NeoForge | mod jar | 26.2 | Server worldgen + client HUD; requires NeoForge 26.2.0.12-beta+ |
@@ -6,6 +6,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.nms.INMSBinding; import art.arcane.iris.core.nms.INMSBinding;
import art.arcane.iris.core.nms.MinecraftVersion;
import art.arcane.iris.core.nms.container.BiomeColor; import art.arcane.iris.core.nms.container.BiomeColor;
import art.arcane.iris.core.nms.container.Pair; import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.nms.container.BlockProperty; import art.arcane.iris.core.nms.container.BlockProperty;
@@ -161,6 +162,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class NMSBinding implements INMSBinding { public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>(); private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
private volatile DataVersion dataVersion;
private final BlockData AIR = Material.AIR.createBlockData(); private final BlockData AIR = Material.AIR.createBlockData();
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>(); private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
private final AtomicBoolean injected = new AtomicBoolean(); private final AtomicBoolean injected = new AtomicBoolean();
@@ -1238,23 +1240,19 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
String descriptionId = "entity.minecraft." + entity.name().toLowerCase(Locale.ROOT); // Registry lookup instead of an EntityType static-field scan: 26.2 moved the constants
Field[] fields = EntityType.class.getDeclaredFields(); // to a separate EntityTypes holder class that 26.1.2 does not have, while the registry
for (Field field : fields) { // resolves identically on both. ENTITY_TYPE is a DefaultedRegistry, so guard containsKey
if (!Modifier.isStatic(field.getModifiers()) || !field.getType().equals(EntityType.class)) { // to avoid silently resolving unknown keys to the default entry.
continue; Identifier key = Identifier.fromNamespaceAndPath(entity.getKey().getNamespace(), entity.getKey().getKey());
} if (!BuiltInRegistries.ENTITY_TYPE.containsKey(key)) {
EntityType entityType = (EntityType) field.get(null);
if (entityType == null) {
continue;
}
if (descriptionId.equals(entityType.getDescriptionId())) {
return new Vector3d(entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
}
}
return null; return null;
}
EntityType<?> entityType = BuiltInRegistries.ENTITY_TYPE.getValue(key);
if (entityType == null) {
return null;
}
return new Vector3d(entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.error("Unable to get entity dimensions for " + entity + "!"); IrisLogging.error("Unable to get entity dimensions for " + entity + "!");
IrisLogging.reportError(e); IrisLogging.reportError(e);
@@ -1362,7 +1360,13 @@ public class NMSBinding implements INMSBinding {
@Override @Override
public DataVersion getDataVersion() { public DataVersion getDataVersion() {
return DataVersion.V26_2; DataVersion cached = dataVersion;
if (cached == null) {
MinecraftVersion detected = MinecraftVersion.detect(Bukkit.getServer());
cached = detected != null && detected.isSameRelease(26, 1, 2) ? DataVersion.V26_1_2 : DataVersion.V26_2;
dataVersion = cached;
}
return cached;
} }
@Override @Override
@@ -6,7 +6,6 @@ import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode; import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants; import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
@@ -17,7 +16,6 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap; import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource; import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
@@ -46,8 +44,11 @@ import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement; import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement; import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.AlwaysTrueTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockStateMatchTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings; import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor; import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorRule;
import net.minecraft.world.level.levelgen.structure.templatesystem.RuleProcessor;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece; import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure; import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
@@ -267,7 +268,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()), block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(15, 0, 0, Blocks.STONE.defaultBlockState()))); block(15, 0, 0, Blocks.STONE.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement( InlineSinglePoolElement element = new InlineSinglePoolElement(
sparseTemplate, List.of(new ReplaceBlockProcessor( sparseTemplate, List.of(replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState()))); Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece( PoolElementStructurePiece piece = rigidTemplatePiece(
element, new BoundingBox(0, 64, 0, 15, 70, 3), 1, Rotation.NONE); element, new BoundingBox(0, 64, 0, 15, 70, 3), 1, Rotation.NONE);
@@ -369,7 +370,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(2, 0, 0, Blocks.AIR.defaultBlockState()), block(2, 0, 0, Blocks.AIR.defaultBlockState()),
block(2, 2, 0, Blocks.COBBLESTONE.defaultBlockState()))); block(2, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement( InlineSinglePoolElement element = new InlineSinglePoolElement(
template, List.of(new ReplaceBlockProcessor( template, List.of(replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState()))); Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece( PoolElementStructurePiece piece = rigidTemplatePiece(
element, new BoundingBox(0, 65, 0, 2, 72, 0), 1, Rotation.NONE); element, new BoundingBox(0, 65, 0, 2, 72, 0), 1, Rotation.NONE);
@@ -551,7 +552,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.STONE.defaultBlockState()), block(0, 0, 0, Blocks.STONE.defaultBlockState()),
block(0, 2, 0, Blocks.COBBLESTONE.defaultBlockState()))); block(0, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece( PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template, List.of(new ReplaceBlockProcessor( new InlineSinglePoolElement(template, List.of(replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState()))), Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState()))),
new BoundingBox(0, 66, 0, 7, 72, 7), 1, Rotation.NONE); new BoundingBox(0, 66, 0, 7, 72, 7), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE); StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
@@ -1081,10 +1082,10 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(1, 0, 0, Blocks.STONE.defaultBlockState()))); block(1, 0, 0, Blocks.STONE.defaultBlockState())));
InlineSinglePoolElement upperElement = new InlineSinglePoolElement( InlineSinglePoolElement upperElement = new InlineSinglePoolElement(
upperTemplate, List.of( upperTemplate, List.of(
new ReplaceBlockProcessor( replaceBlockProcessor(
Blocks.COBBLESTONE.defaultBlockState(), Blocks.COBBLESTONE.defaultBlockState(),
Blocks.AIR.defaultBlockState()), Blocks.AIR.defaultBlockState()),
new ReplaceBlockProcessor( replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.STONE.defaultBlockState(),
Blocks.WATER.defaultBlockState()))); Blocks.WATER.defaultBlockState())));
PoolElementStructurePiece lower = rigidTemplatePiece( PoolElementStructurePiece lower = rigidTemplatePiece(
@@ -2131,32 +2132,13 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
} }
} }
private static final class ReplaceBlockProcessor implements StructureProcessor { private static StructureProcessor replaceBlockProcessor(BlockState source, BlockState replacement) {
private final BlockState source; // Vanilla RuleProcessor instead of a custom StructureProcessor subtype: StructureProcessor
private final BlockState replacement; // is an abstract class on 26.1.2 and an interface on 26.2, so a direct subtype cannot
// compile against both dev bundles. BlockStateMatchTest matches the exact source state,
private ReplaceBlockProcessor(BlockState source, BlockState replacement) { // matching the previous custom processor's identity-equality semantics.
this.source = source; return new RuleProcessor(List.of(new ProcessorRule(
this.replacement = replacement; new BlockStateMatchTest(source), AlwaysTrueTest.INSTANCE, replacement)));
}
@Override
public StructureTemplate.StructureBlockInfo processBlock(
LevelReader level, BlockPos targetPosition, BlockPos referencePos,
BlockPos templateRelativePos,
StructureTemplate.StructureBlockInfo processedBlockInfo,
StructurePlaceSettings settings) {
if (!processedBlockInfo.state().equals(source)) {
return processedBlockInfo;
}
return new StructureTemplate.StructureBlockInfo(
processedBlockInfo.pos(), replacement, processedBlockInfo.nbt());
}
@Override
public MapCodec<? extends StructureProcessor> codec() {
return BlockIgnoreProcessor.STRUCTURE_BLOCK.codec();
}
} }
private static StructureStart desertStart() { private static StructureStart desertStart() {
@@ -1,15 +1,16 @@
package art.arcane.iris.nativegen; package art.arcane.iris.nativegen;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants; import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet; import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap; import net.minecraft.server.Bootstrap;
import net.minecraft.tags.TagKey;
import net.minecraft.util.valueproviders.ConstantInt; import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.Rotation;
@@ -34,11 +35,13 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProc
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.Assume;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -62,38 +65,33 @@ public class NativeStructureTemplateOccupancyTest {
} }
@Test @Test
public void straddlingGravityPieceReadsHeightsOnlyInsideTheProcessingArea() throws Exception { public void straddlingGravityPieceHeightReadsHonorTheVanillaClipContract() throws Exception {
ColumnRecorder recorder = new ColumnRecorder(); ColumnRecorder recorder = new ColumnRecorder();
resolve(straddlingPiece( resolve(straddlingPiece(
List.of(new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0))), recorder); List.of(new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0))), recorder);
assertEquals(columns(0, 15), recorder.heightColumns); // 26.2 vanilla clips processor evaluation to the placement bounding box; 26.1.2 vanilla
} // runs processors across the whole piece. Iris mirrors the pinned version's semantics.
assertEquals(expectedClippedColumns(), recorder.heightColumns);
@Test
public void straddlingPieceRunsProcessorsOnlyInsideTheProcessingArea() throws Exception {
CountingProcessor counter = new CountingProcessor();
resolve(straddlingPiece(List.of(counter)), new ColumnRecorder());
assertEquals(columns(0, 15), counter.columns);
assertTrue(counter.columns.size() < TEMPLATE_WIDTH);
} }
@Test @Test
public void cappedStraddlingPieceRunsProcessorsAcrossTheWholePiece() throws Exception { public void cappedStraddlingPieceRunsProcessorsAcrossTheWholePiece() throws Exception {
CountingProcessor counter = new CountingProcessor(); ColumnRecorder recorder = new ColumnRecorder();
resolve(straddlingPiece(List.of(counter, resolve(straddlingPiece(List.of(
new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(0)))), new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0),
new ColumnRecorder()); new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(0)))), recorder);
assertEquals(columns(0, TEMPLATE_WIDTH - 1), counter.columns); assertEquals(columns(0, TEMPLATE_WIDTH - 1), recorder.heightColumns);
} }
@Test @Test
public void cappedProcessorIsTheOnlyProcessorThatDisablesTheProcessingAreaClip() { public void cappedProcessorIsTheOnlyProcessorThatDisablesTheProcessingAreaClip() throws Exception {
Method contract = clipContractMethod();
Assume.assumeTrue("evaluatesEntirePieceState only exists on 26.2+", contract != null);
for (StructureProcessor processor : List.of( for (StructureProcessor processor : List.of(
BlockIgnoreProcessor.STRUCTURE_BLOCK, BlockIgnoreProcessor.STRUCTURE_BLOCK,
JigsawReplacementProcessor.INSTANCE, JigsawReplacementProcessor.INSTANCE,
@@ -103,12 +101,12 @@ public class NativeStructureTemplateOccupancyTest {
new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0), new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0),
new RuleProcessor(List.of()), new RuleProcessor(List.of()),
new BlockAgeProcessor(0.5F), new BlockAgeProcessor(0.5F),
new ProtectedBlockProcessor(HolderSet.empty()))) { protectedBlockProcessor())) {
assertFalse(processor.getClass().getName(), assertFalse(processor.getClass().getName(),
processor.evaluatesEntirePieceState()); (Boolean) contract.invoke(processor));
} }
assertTrue(new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(4)) assertTrue((Boolean) contract.invoke(
.evaluatesEntirePieceState()); new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(4))));
} }
@Test @Test
@@ -146,6 +144,30 @@ public class NativeStructureTemplateOccupancyTest {
return columns; return columns;
} }
private static Method clipContractMethod() {
try {
return StructureProcessor.class.getMethod("evaluatesEntirePieceState");
} catch (NoSuchMethodException absent) {
return null;
}
}
private static Set<Integer> expectedClippedColumns() {
return clipContractMethod() != null
? columns(PROCESSING_AREA.minX(), PROCESSING_AREA.maxX())
: columns(0, TEMPLATE_WIDTH - 1);
}
private static StructureProcessor protectedBlockProcessor() throws Exception {
// Constructed reflectively: the constructor takes HolderSet on 26.2 and TagKey on 26.1.2.
Constructor<?> constructor = ProtectedBlockProcessor.class.getConstructors()[0];
Class<?> parameter = constructor.getParameterTypes()[0];
Object argument = parameter == HolderSet.class
? HolderSet.empty()
: TagKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath("minecraft", "air"));
return (StructureProcessor) constructor.newInstance(argument);
}
private static StructureTemplateManager forbiddenTemplateManager() { private static StructureTemplateManager forbiddenTemplateManager() {
throw new AssertionError("Inline templates must not resolve a template manager"); throw new AssertionError("Inline templates must not resolve a template manager");
} }
@@ -202,25 +224,6 @@ public class NativeStructureTemplateOccupancyTest {
private final Set<Integer> stateColumns = new TreeSet<>(); private final Set<Integer> stateColumns = new TreeSet<>();
} }
private static final class CountingProcessor implements StructureProcessor {
private final Set<Integer> columns = new TreeSet<>();
@Override
public StructureTemplate.StructureBlockInfo processBlock(
LevelReader level, BlockPos targetPosition, BlockPos referencePos,
BlockPos templateRelativePos,
StructureTemplate.StructureBlockInfo processedBlockInfo,
StructurePlaceSettings settings) {
columns.add(processedBlockInfo.pos().getX());
return processedBlockInfo;
}
@Override
public MapCodec<? extends StructureProcessor> codec() {
return BlockIgnoreProcessor.STRUCTURE_BLOCK.codec();
}
}
private static final class InlineSinglePoolElement extends SinglePoolElement { private static final class InlineSinglePoolElement extends SinglePoolElement {
private InlineSinglePoolElement( private InlineSinglePoolElement(
StructureTemplate template, List<StructureProcessor> processors) { StructureTemplate template, List<StructureProcessor> processors) {
+1 -1
View File
@@ -1,4 +1,4 @@
String apiVersion = providers.gradleProperty('minecraftVersion').get() String apiVersion = providers.gradleProperty('apiVersion').get()
def mainClass = 'art.arcane.iris.Iris' def mainClass = 'art.arcane.iris.Iris'
def bootstrapperClass = 'art.arcane.iris.IrisBootstrap' def bootstrapperClass = 'art.arcane.iris.IrisBootstrap'
+1 -1
View File
@@ -87,7 +87,7 @@ String forgeConsumerPath = "${consumerLocation}/forge-mod-consumers/dropins/mods
String neoForgeConsumerPath = "${consumerLocation}/neoforge-mod-consumers/dropins/mods" String neoForgeConsumerPath = "${consumerLocation}/neoforge-mod-consumers/dropins/mods"
def nmsBindings = [ def nmsBindings = [
v26_2_R1: '26.2.build.60-beta', v26_2_R1: '26.1.2.build.74-stable',
] ]
Class nmsTypeClass = Class.forName('NMSBinding$Type') Class nmsTypeClass = Class.forName('NMSBinding$Type')
nmsBindings.each { key, value -> nmsBindings.each { key, value ->
@@ -46,7 +46,6 @@ public final class IrisDatapackCompiler {
List<File> packRoots, List<File> packRoots,
KList<File> datapackRoots, KList<File> datapackRoots,
IDataFixer fixer, IDataFixer fixer,
int packFormat,
boolean adjustVanillaHeight boolean adjustVanillaHeight
) throws IOException { ) throws IOException {
Objects.requireNonNull(packRoots, "packRoots"); Objects.requireNonNull(packRoots, "packRoots");
@@ -98,7 +97,7 @@ public final class IrisDatapackCompiler {
} }
} }
IrisDimension.writeShared(datapackRoots, height, packFormat, adjustVanillaHeight); IrisDimension.writeShared(datapackRoots, height, adjustVanillaHeight);
validateOutputs(datapackRoots, dimensionCount); validateOutputs(datapackRoots, dimensionCount);
return new CompilationResult(packCount, dimensionCount, countBiomes(biomes)); return new CompilationResult(packCount, dimensionCount, countBiomes(biomes));
} }
@@ -200,7 +200,6 @@ public class ServerConfigurator {
packRoots, packRoots,
stagedRoots, stagedRoots,
fixer, fixer,
BukkitPlatform.dataPackFormat(),
IrisSettings.get().getGeneral().adjustVanillaHeight IrisSettings.get().getGeneral().adjustVanillaHeight
); );
for (int i = 0; i < liveRoots.size(); i++) { for (int i = 0; i < liveRoots.size(); i++) {
@@ -257,7 +257,7 @@ public interface INMSBinding {
if (!supportsIrisWorldGeneration()) { if (!supportsIrisWorldGeneration()) {
throw new IllegalStateException("Iris world '" + c.name() + "' cannot be created with limited NMS binding " throw new IllegalStateException("Iris world '" + c.name() + "' cannot be created with limited NMS binding "
+ getClass().getSimpleName() + getClass().getSimpleName()
+ "; set general.disableNMS=false and use the supported Minecraft 26.2 server runtime"); + "; set general.disableNMS=false and use a supported Minecraft server runtime (26.1.2 or 26.2)");
} }
if (missingDimensionTypes(generator.getTarget().getDimension().getDimensionTypeKey())) { if (missingDimensionTypes(generator.getTarget().getDimension().getDimensionTypeKey())) {
throw new IllegalStateException("Missing dimension types to create world"); throw new IllegalStateException("Missing dimension types to create world");
@@ -1,7 +1,7 @@
package art.arcane.iris.core.nms; package art.arcane.iris.core.nms;
final class NmsBindingSelector { final class NmsBindingSelector {
private static final String SUPPORTED_VERSION = "26.2"; private static final String SUPPORTED_VERSIONS = "26.1.2 or 26.2";
private static final String SUPPORTED_TAG = "v26_2_R1"; private static final String SUPPORTED_TAG = "v26_2_R1";
private NmsBindingSelector() { private NmsBindingSelector() {
@@ -11,8 +11,8 @@ final class NmsBindingSelector {
if (version == null) { if (version == null) {
throw new IllegalStateException("Iris requires an exact Minecraft version before selecting NMS"); throw new IllegalStateException("Iris requires an exact Minecraft version before selecting NMS");
} }
if (!version.isSameRelease(26, 2, 0)) { if (!version.isSameRelease(26, 2, 0) && !version.isSameRelease(26, 1, 2)) {
throw new IllegalStateException("Iris requires Minecraft " + SUPPORTED_VERSION throw new IllegalStateException("Iris requires Minecraft " + SUPPORTED_VERSIONS
+ ". Detected server version: " + version.value()); + ". Detected server version: " + version.value());
} }
return SUPPORTED_TAG; return SUPPORTED_TAG;
@@ -13,6 +13,7 @@ import java.util.function.Supplier;
@Getter @Getter
public enum DataVersion { public enum DataVersion {
UNSUPPORTED("0.0.0", 0, () -> null), UNSUPPORTED("0.0.0", 0, () -> null),
V26_1_2("26.1.2", 101, DataFixerV1217::new),
V26_2("26.2", 107, DataFixerV1217::new); V26_2("26.2", 107, DataFixerV1217::new);
private static final KMap<DataVersion, IDataFixer> cache = new KMap<>(); private static final KMap<DataVersion, IDataFixer> cache = new KMap<>();
@Getter(AccessLevel.NONE) @Getter(AccessLevel.NONE)
@@ -51,4 +52,15 @@ public enum DataVersion {
public static DataVersion getLatest() { public static DataVersion getLatest() {
return values()[values().length - 1]; return values()[values().length - 1];
} }
public static int minSupportedPackFormat() {
int minimum = Integer.MAX_VALUE;
for (DataVersion version : values()) {
if (version == UNSUPPORTED) {
continue;
}
minimum = Math.min(minimum, version.packFormat);
}
return minimum;
}
} }
@@ -293,6 +293,6 @@ public class NMSBinding1X implements INMSBinding {
private IllegalStateException unsupportedStructureHook(String operation) { private IllegalStateException unsupportedStructureHook(String operation) {
return new IllegalStateException("Iris cannot " + operation + " with limited NMS binding " return new IllegalStateException("Iris cannot " + operation + " with limited NMS binding "
+ getClass().getSimpleName() + getClass().getSimpleName()
+ "; set general.disableNMS=false and use the supported Minecraft 26.2 server runtime"); + "; set general.disableNMS=false and use a supported Minecraft server runtime (26.1.2 or 26.2)");
} }
} }
@@ -41,7 +41,6 @@ import java.util.zip.ZipInputStream;
public final class DefaultPackBootstrapProvisioner { public final class DefaultPackBootstrapProvisioner {
private static final URI DEFAULT_SOURCE = URI.create("https://github.com/IrisDimensions/overworld/releases/download/beta/overworld.zip"); private static final URI DEFAULT_SOURCE = URI.create("https://github.com/IrisDimensions/overworld/releases/download/beta/overworld.zip");
private static final String WORLD_DATAPACK_DIRECTORY = "iris"; private static final String WORLD_DATAPACK_DIRECTORY = "iris";
private static final int PACK_FORMAT = 107;
private static final int MARKER_SCHEMA = 2; private static final int MARKER_SCHEMA = 2;
private static final int MAX_ARCHIVE_ENTRIES = 100_000; private static final int MAX_ARCHIVE_ENTRIES = 100_000;
private static final long MAX_ARCHIVE_BYTES = 512L * 1024L * 1024L; private static final long MAX_ARCHIVE_BYTES = 512L * 1024L * 1024L;
@@ -189,7 +188,7 @@ public final class DefaultPackBootstrapProvisioner {
if (fixer == null) { if (fixer == null) {
throw new IOException("Latest Iris datapack fixer is unavailable during bootstrap"); throw new IOException("Latest Iris datapack fixer is unavailable during bootstrap");
} }
IrisDatapackCompiler.compile(packRoots, outputFolders, fixer, PACK_FORMAT, false); IrisDatapackCompiler.compile(packRoots, outputFolders, fixer, false);
if (!isDatapackRoot(compileContainer)) { if (!isDatapackRoot(compileContainer)) {
throw new IOException("Canonical Iris datapack compiler produced incomplete output at " + compileContainer); throw new IOException("Canonical Iris datapack compiler produced incomplete output at " + compileContainer);
} }
@@ -23,6 +23,7 @@ import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.core.loader.IrisRegistrant; import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer; import art.arcane.iris.core.nms.datapack.IDataFixer;
import art.arcane.iris.core.nms.datapack.IDataFixer.Dimension; import art.arcane.iris.core.nms.datapack.IDataFixer.Dimension;
import art.arcane.iris.engine.data.cache.AtomicCache; import art.arcane.iris.engine.data.cache.AtomicCache;
@@ -763,7 +764,6 @@ public class IrisDimension extends IrisRegistrant {
public static void writeShared( public static void writeShared(
KList<File> datapackRoots, KList<File> datapackRoots,
DimensionHeight height, DimensionHeight height,
int packFormat,
boolean adjustVanillaHeight boolean adjustVanillaHeight
) throws IOException { ) throws IOException {
IrisLogging.debug(" Installing Data Pack Vanilla Dimension Types"); IrisLogging.debug(" Installing Data Pack Vanilla Dimension Types");
@@ -774,16 +774,22 @@ public class IrisDimension extends IrisRegistrant {
write(datapackRoot, "the_end", jsonStrings[2], adjustVanillaHeight); write(datapackRoot, "the_end", jsonStrings[2], adjustVanillaHeight);
} }
// Ranged formats spanning every supported runtime (26.1.2 = 101, 26.2 = 107). The bootstrap
// provisioner stages this datapack before the server (and INMS) exists, so the runtime
// version cannot be resolved there; a range keeps the emitted bytes identical on both
// install paths and both Paper versions parse ranged min_format/max_format.
int minFormat = DataVersion.minSupportedPackFormat();
int maxFormat = DataVersion.getLatest().getPackFormat();
String raw = """ String raw = """
{ {
"pack": { "pack": {
"description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.",
"pack_format": {}, "pack_format": %d,
"min_format": {}, "min_format": %d,
"max_format": {} "max_format": %d
} }
} }
""".replace("{}", Integer.toString(packFormat)); """.formatted(maxFormat, minFormat, maxFormat);
for (File datapackRoot : datapackRoots) { for (File datapackRoot : datapackRoots) {
File mcm = new File(datapackRoot, "pack.mcmeta"); File mcm = new File(datapackRoot, "pack.mcmeta");
@@ -242,10 +242,6 @@ public final class BukkitPlatform implements IrisPlatform {
return INMS.get().applyCustomNbt(itemStack, customNbt); return INMS.get().applyCustomNbt(itemStack, customNbt);
} }
public static int dataPackFormat() {
return INMS.get().getDataVersion().getPackFormat();
}
public static java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Entity entity, Location destination) { public static java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Entity entity, Location destination) {
return io.papermc.lib.PaperLib.teleportAsync(entity, destination); return io.papermc.lib.PaperLib.teleportAsync(entity, destination);
} }
@@ -36,6 +36,11 @@ public interface DirectorExecutor extends DirectorExecutorBase {
return sender == null ? null : sender.player(); return sender == null ? null : sender.player();
} }
default boolean playerWorldGeneratesStructures() {
Player activePlayer = player();
return activePlayer != null && activePlayer.getWorld().canGenerateStructures();
}
default IrisData data() { default IrisData data() {
var access = access(); var access = access();
if (access != null) { if (access != null) {
@@ -29,7 +29,6 @@ import art.arcane.iris.spi.PlatformStructureHooks;
import art.arcane.iris.util.common.director.DirectorParameterHandler; import art.arcane.iris.util.common.director.DirectorParameterHandler;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.bukkit.entity.Player;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -100,8 +99,7 @@ public class StructureHandler implements DirectorParameterHandler<String> {
} }
protected boolean nativeStructureGenerationEnabled() { protected boolean nativeStructureGenerationEnabled() {
Player activePlayer = player(); return playerWorldGeneratesStructures();
return activePlayer != null && activePlayer.getWorld().canGenerateStructures();
} }
private static Map<String, String> distinctKeys(List<String> keys) { private static Map<String, String> distinctKeys(List<String> keys) {
@@ -1,7 +1,9 @@
package art.arcane.iris.core; package art.arcane.iris.core;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.v1217.DataFixerV1217; import art.arcane.iris.core.nms.datapack.v1217.DataFixerV1217;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.json.JSONObject;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.Assume; import org.junit.Assume;
@@ -41,7 +43,6 @@ public class IrisDatapackCompilerTest {
packRoots, packRoots,
new KList<File>().qadd(datapackRoot.toFile()), new KList<File>().qadd(datapackRoot.toFile()),
new DataFixerV1217(), new DataFixerV1217(),
107,
false false
); );
@@ -72,7 +73,6 @@ public class IrisDatapackCompilerTest {
List.of(packRoot.toFile()), List.of(packRoot.toFile()),
new KList<File>().qadd(datapackRoot.toFile()), new KList<File>().qadd(datapackRoot.toFile()),
new DataFixerV1217(), new DataFixerV1217(),
107,
false false
); );
@@ -99,6 +99,33 @@ public class IrisDatapackCompilerTest {
IrisDatapackCompiler.collectPackRoots(dataDirectory, serverRoot)); IrisDatapackCompiler.collectPackRoots(dataDirectory, serverRoot));
} }
@Test
public void packMcmetaSpansEverySupportedRuntimeFormat() throws Exception {
Path datapackRoot = temporaryFolder.newFolder("mcmeta-datapack").toPath();
IrisDatapackCompiler.compile(
List.of(),
new KList<File>().qadd(datapackRoot.toFile()),
new DataFixerV1217(),
false
);
JSONObject pack = new JSONObject(Files.readString(datapackRoot.resolve("pack.mcmeta"), StandardCharsets.UTF_8))
.getJSONObject("pack");
int minFormat = pack.getInt("min_format");
int maxFormat = pack.getInt("max_format");
assertEquals(101, minFormat);
assertEquals(107, maxFormat);
assertEquals(maxFormat, pack.getInt("pack_format"));
// One artifact serves both runtimes: each supported DataVersion's format must be in range.
assertTrue(minFormat <= DataVersion.V26_1_2.getPackFormat()
&& DataVersion.V26_1_2.getPackFormat() <= maxFormat);
assertTrue(minFormat <= DataVersion.V26_2.getPackFormat()
&& DataVersion.V26_2.getPackFormat() <= maxFormat);
}
@Test @Test
public void compilingNoPacksPublishesCleanEmptyDatapack() throws Exception { public void compilingNoPacksPublishesCleanEmptyDatapack() throws Exception {
Path datapackRoot = temporaryFolder.newFolder("empty-datapack").toPath(); Path datapackRoot = temporaryFolder.newFolder("empty-datapack").toPath();
@@ -110,7 +137,6 @@ public class IrisDatapackCompilerTest {
List.of(), List.of(),
new KList<File>().qadd(datapackRoot.toFile()), new KList<File>().qadd(datapackRoot.toFile()),
new DataFixerV1217(), new DataFixerV1217(),
107,
false false
); );
@@ -57,6 +57,30 @@ public class MinecraftVersionTest {
assertEquals(0, version.patch()); assertEquals(0, version.patch());
} }
@Test
public void parsesThreePartNewSchemeVersion() {
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.1.2-R0.1-SNAPSHOT");
assertEquals("26.1.2", version.value());
assertEquals(26, version.major());
assertEquals(1, version.minor());
assertEquals(2, version.patch());
assertTrue(version.isSameRelease(26, 1, 2));
assertFalse(version.isSameRelease(26, 2, 0));
}
@Test
public void detectsThreePartVersionFromDecoratedVersion() {
Server server = mock(Server.class);
doReturn("git-Paper-74 (MC: 26.1.2)").when(server).getVersion();
doReturn("26.1.2-R0.1-SNAPSHOT").when(server).getBukkitVersion();
MinecraftVersion version = MinecraftVersion.detect(server);
assertEquals("26.1.2", version.value());
assertEquals(26, version.major());
assertEquals(1, version.minor());
assertEquals(2, version.patch());
}
@Test @Test
public void comparesMajorBeforeMinor() { public void comparesMajorBeforeMinor() {
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.2-R0.1-SNAPSHOT"); MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.2-R0.1-SNAPSHOT");
@@ -14,6 +14,13 @@ public class NmsBindingSelectorTest {
assertEquals("v26_2_R1", NmsBindingSelector.select(version)); assertEquals("v26_2_R1", NmsBindingSelector.select(version));
} }
@Test
public void selectsSharedRevisionFor2612() {
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.1.2-R0.1-SNAPSHOT");
assertEquals("v26_2_R1", NmsBindingSelector.select(version));
}
@Test @Test
public void rejectsUnsupportedVersionsWithoutProbingAnotherRevision() { public void rejectsUnsupportedVersionsWithoutProbingAnotherRevision() {
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.3-R0.1-SNAPSHOT"); MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.3-R0.1-SNAPSHOT");
@@ -24,6 +31,16 @@ public class NmsBindingSelectorTest {
assertTrue(failure.getMessage().contains("26.3")); assertTrue(failure.getMessage().contains("26.3"));
} }
@Test
public void rejectsBasePatchOfSupportedMinorLine() {
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.1-R0.1-SNAPSHOT");
IllegalStateException failure = assertThrows(IllegalStateException.class,
() -> NmsBindingSelector.select(version));
assertTrue(failure.getMessage().contains("26.1"));
}
@Test @Test
public void rejectsMissingVersionDetection() { public void rejectsMissingVersionDetection() {
assertThrows(IllegalStateException.class, () -> NmsBindingSelector.select(null)); assertThrows(IllegalStateException.class, () -> NmsBindingSelector.select(null));
@@ -0,0 +1,32 @@
package art.arcane.iris.core.nms.datapack;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
public class DataVersionTest {
@Test
public void latestRemainsNewestSupportedRelease() {
assertSame(DataVersion.V26_2, DataVersion.getLatest());
}
@Test
public void packFormatsMatchServerVersionJson() {
assertEquals(101, DataVersion.V26_1_2.getPackFormat());
assertEquals(107, DataVersion.V26_2.getPackFormat());
}
@Test
public void minSupportedPackFormatIsTheOldestSupportedRuntime() {
assertEquals(101, DataVersion.minSupportedPackFormat());
}
@Test
public void bothSupportedReleasesShareAFixer() {
assertNotNull(DataVersion.V26_1_2.get());
assertNotNull(DataVersion.V26_2.get());
assertSame(DataVersion.V26_1_2.get().getClass(), DataVersion.V26_2.get().getClass());
}
}
+8 -3
View File
@@ -8,9 +8,13 @@ to a new Minecraft version, in order.
`gradle.properties`: `gradle.properties`:
- `minecraftVersion` — target MC version (e.g. `26.2`). Drives the Bukkit plugin `api-version`, - `minecraftVersion` — target MC version (e.g. `26.2`). Drives
`BuildConstants.MINECRAFT_VERSION`, the `com.mojang:minecraft` coordinate, all mod-metadata `BuildConstants.MINECRAFT_VERSION`, the `com.mojang:minecraft` coordinate, all mod-metadata
minecraft ranges, and every dist/jar artifact name. minecraft ranges, and every dist/jar artifact name.
- `apiVersion` — Bukkit plugin `api-version` (e.g. `26.1`). Deliberately decoupled from
`minecraftVersion`: it is the lowest Minecraft release line the single plugin artifact loads on
(currently `26.1` so one jar serves 26.1.2 and 26.2). Bump it only when dropping support for the
older line.
- `fabricLoaderVersion` — Fabric Loader version. - `fabricLoaderVersion` — Fabric Loader version.
- `forgeVersion` — Forge version (`<mc>-<forge>`). - `forgeVersion` — Forge version (`<mc>-<forge>`).
- `neoForgeVersion` — NeoForge version. - `neoForgeVersion` — NeoForge version.
@@ -19,7 +23,8 @@ to a new Minecraft version, in order.
## Ordered steps ## Ordered steps
1. Edit `gradle.properties`: update `minecraftVersion`, `fabricLoaderVersion`, `forgeVersion`, 1. Edit `gradle.properties`: update `minecraftVersion`, `fabricLoaderVersion`, `forgeVersion`,
`neoForgeVersion`, and the `irisVersion` suffix. `neoForgeVersion`, and the `irisVersion` suffix. Revisit `apiVersion` only if the bump drops
support for the oldest Minecraft line the plugin artifact still loads on.
2. Edit `gradle/libs.versions.toml`: 2. Edit `gradle/libs.versions.toml`:
- `spigot` — the Spigot/Paper API pin used to compile against (`<mc>-R0.1-SNAPSHOT`). - `spigot` — the Spigot/Paper API pin used to compile against (`<mc>-R0.1-SNAPSHOT`).
@@ -148,7 +153,7 @@ to a new Minecraft version, in order.
## Derived automatically (do not hand-edit on a version bump) ## Derived automatically (do not hand-edit on a version bump)
- Bukkit plugin `api-version``adapters/bukkit/plugin/build.gradle` reads `minecraftVersion`. - Bukkit plugin `api-version``adapters/bukkit/plugin/build.gradle` reads `apiVersion`.
- `BuildConstants.MINECRAFT_VERSION` — stamped by the `generateTemplates` task in - `BuildConstants.MINECRAFT_VERSION` — stamped by the `generateTemplates` task in
`core/build.gradle` from `minecraftVersion`; consumed by `Tasks.supportedVersions`. `core/build.gradle` from `minecraftVersion`; consumed by `Tasks.supportedVersions`.
- Mod-metadata `minecraft` version ranges — templated from `minecraftVersion` at `processResources`. - Mod-metadata `minecraft` version ranges — templated from `minecraftVersion` at `processResources`.
+2
View File
@@ -25,6 +25,8 @@ nmsTools.repo-url=https://repo.codemc.org/repository/nms/
nmsTools.specialSourceVersion=1.11.4 nmsTools.specialSourceVersion=1.11.4
irisVersion=4.0.0-26.2 irisVersion=4.0.0-26.2
minecraftVersion=26.2 minecraftVersion=26.2
# Bukkit plugin api-version: lowest supported Minecraft release line so one artifact loads on 26.1.2 and 26.2
apiVersion=26.1
fabricLoaderVersion=0.19.3 fabricLoaderVersion=0.19.3
forgeVersion=26.2-65.0.4 forgeVersion=26.2-65.0.4
neoForgeVersion=26.2.0.12-beta neoForgeVersion=26.2.0.12-beta
+1 -1
View File
@@ -12,7 +12,7 @@ sentryPlugin = "6.14.0" # https://github.com/getsentry/sentry-android-gradle-plu
# Core Libraries # Core Libraries
lombok = "1.18.46" lombok = "1.18.46"
spigot = "26.2-R0.1-SNAPSHOT" # https://hub.spigotmc.org/nexus/repository/snapshots/org/spigotmc/spigot-api/maven-metadata.xml spigot = "26.2-R0.1-SNAPSHOT" # https://hub.spigotmc.org/nexus/repository/snapshots/org/spigotmc/spigot-api/maven-metadata.xml
paper-api = "26.2.build.60-beta" paper-api = "26.1.2.build.74-stable"
log4j = "2.26.1" # https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-api log4j = "2.26.1" # https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-api
adventure-api = "4.26.1" # https://github.com/KyoriPowered/adventure adventure-api = "4.26.1" # https://github.com/KyoriPowered/adventure
adventure-platform = "4.4.1" # https://github.com/KyoriPowered/adventure-platform adventure-platform = "4.4.1" # https://github.com/KyoriPowered/adventure-platform