This commit is contained in:
Brian Neumann-Fopiano
2026-07-13 21:33:20 -04:00
parent 4dea984d77
commit 45230c0689
234 changed files with 13818 additions and 2979 deletions
@@ -0,0 +1,152 @@
package art.arcane.iris.modded;
import art.arcane.volmlib.util.math.RNG;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.IdMapper;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.Container;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeSpecialEffects;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BannerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerRO;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.Strategy;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.storage.TagValueInput;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class IrisModdedBlockEntityParityTest {
private static RegistryAccess registries;
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
registries = RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY);
if (!Items.DIAMOND.builtInRegistryHolder().areComponentsBound()) {
Items.DIAMOND.builtInRegistryHolder().bindComponents(DataComponentMap.EMPTY);
}
}
@Test
public void generatedContainerGetsBlockEntityAndPersistsFilledLoot() {
ProtoChunk chunk = newChunk();
BlockPos position = new BlockPos(3, 70, 5);
chunk.setBlockState(position, Blocks.CHEST.defaultBlockState(), 0);
IrisModdedChunkGenerator.createDefaultBlockEntity(chunk, position, Blocks.CHEST.defaultBlockState());
BlockEntity blockEntity = chunk.getBlockEntity(position);
assertNotNull(blockEntity);
assertTrue(blockEntity instanceof Container);
Container container = (Container) blockEntity;
ModdedLootApplier.fillContainer(container, List.of(new ItemStack(Items.DIAMOND)), new RNG(17L));
assertEquals(1, countItem(container, Items.DIAMOND));
CompoundTag saved = blockEntity.saveWithoutMetadata(registries);
ChestBlockEntity restored = new ChestBlockEntity(position, Blocks.CHEST.defaultBlockState());
restored.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, saved));
assertEquals(1, countItem(restored, Items.DIAMOND));
}
@Test
public void generatedSignSpawnerAndBannerGetNativeBlockEntities() {
ProtoChunk chunk = newChunk();
BlockState signState = Blocks.OAK_SIGN.defaultBlockState();
BlockState spawnerState = Blocks.SPAWNER.defaultBlockState();
BlockState bannerState = BuiltInRegistries.BLOCK.getValue(
Identifier.parse("minecraft:white_banner")).defaultBlockState();
BlockPos signPosition = new BlockPos(1, 70, 1);
BlockPos spawnerPosition = new BlockPos(2, 70, 1);
BlockPos bannerPosition = new BlockPos(3, 70, 1);
chunk.setBlockState(signPosition, signState, 0);
chunk.setBlockState(spawnerPosition, spawnerState, 0);
chunk.setBlockState(bannerPosition, bannerState, 0);
IrisModdedChunkGenerator.createDefaultBlockEntity(chunk, signPosition, signState);
IrisModdedChunkGenerator.createDefaultBlockEntity(chunk, spawnerPosition, spawnerState);
IrisModdedChunkGenerator.createDefaultBlockEntity(chunk, bannerPosition, bannerState);
assertTrue(chunk.getBlockEntity(signPosition) instanceof SignBlockEntity);
assertTrue(chunk.getBlockEntity(spawnerPosition) instanceof SpawnerBlockEntity);
assertTrue(chunk.getBlockEntity(bannerPosition) instanceof BannerBlockEntity);
}
private static ProtoChunk newChunk() {
return new ProtoChunk(
new ChunkPos(0, 0),
UpgradeData.EMPTY,
LevelHeightAccessor.create(-64, 384),
palettedContainerFactory(),
null);
}
private static PalettedContainerFactory palettedContainerFactory() {
Strategy<BlockState> blockStrategy = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
Codec<PalettedContainer<BlockState>> blockCodec = PalettedContainer.codecRW(
BlockState.CODEC, blockStrategy, Blocks.AIR.defaultBlockState());
Biome biome = new Biome.BiomeBuilder()
.hasPrecipitation(false)
.temperature(0.8F)
.downfall(0.4F)
.specialEffects(new BiomeSpecialEffects.Builder().waterColor(0x3F76E4).build())
.mobSpawnSettings(MobSpawnSettings.EMPTY)
.generationSettings(BiomeGenerationSettings.EMPTY)
.build();
Holder<Biome> biomeHolder = Holder.direct(biome);
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>(1);
biomeIds.add(biomeHolder);
Strategy<Holder<Biome>> biomeStrategy = Strategy.createForBiomes(biomeIds);
Codec<PalettedContainerRO<Holder<Biome>>> biomeCodec = PalettedContainer.codecRO(
Biome.CODEC, biomeStrategy, biomeHolder);
return new PalettedContainerFactory(
blockStrategy,
Blocks.AIR.defaultBlockState(),
blockCodec,
biomeStrategy,
biomeHolder,
biomeCodec);
}
private static int countItem(Container container, Item item) {
int count = 0;
for (int slot = 0; slot < container.getContainerSize(); slot++) {
ItemStack stack = container.getItem(slot);
if (stack.getItem() == item) {
count += stack.getCount();
}
}
return count;
}
}
@@ -0,0 +1,33 @@
package art.arcane.iris.modded;
import net.minecraft.util.Mth;
import net.minecraft.util.SimpleBitStorage;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IrisModdedHeightmapParityTest {
@Test
public void terrainHeightmapEncodesPaperRelativeSurfaceHeights() {
int height = 384;
long[] rawData = ModdedHeightmaps.terrainRawData(height,
(x, z) -> x + z * 16 + 1);
SimpleBitStorage storage = new SimpleBitStorage(Mth.ceillog2(height + 1), 256, rawData);
assertEquals(1, storage.get(0));
assertEquals(16, storage.get(15));
assertEquals(17, storage.get(16));
assertEquals(256, storage.get(255));
}
@Test
public void terrainHeightmapClampsToDimensionStorageRange() {
int height = 384;
long[] rawData = ModdedHeightmaps.terrainRawData(height,
(x, z) -> x == 0 ? -20 : 900);
SimpleBitStorage storage = new SimpleBitStorage(Mth.ceillog2(height + 1), 256, rawData);
assertEquals(0, storage.get(0));
assertEquals(height, storage.get(1));
}
}
@@ -0,0 +1,193 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.server.Bootstrap;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class IrisModdedStructureParityTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void surfaceBiomeFastPathBeginsAboveTheCaveSwitch() {
assertFalse(IrisModdedBiomeSource.isGuaranteedSurfaceBiome(-2, -256));
assertTrue(IrisModdedBiomeSource.isGuaranteedSurfaceBiome(-1, -256));
assertFalse(IrisModdedBiomeSource.isGuaranteedSurfaceBiome(10, 0));
assertTrue(IrisModdedBiomeSource.isGuaranteedSurfaceBiome(11, 0));
}
@Test
public void visibleCaveBiomeBeginsEightBlocksBelowTheSurface() {
assertFalse(IrisModdedBiomeSource.isUnderground(93, 100));
assertTrue(IrisModdedBiomeSource.isUnderground(92, 100));
assertTrue(IrisModdedBiomeSource.isUnderground(-20, 100));
}
@Test
public void monumentBiomeCubeUsesSurfaceBiomesAtShiftedSeaLevel() {
assertTrue(IrisModdedBiomeSource.isMonumentSurfaceBiomeQuery(50, 29, -256, 306));
assertFalse(IrisModdedBiomeSource.isMonumentSurfaceBiomeQuery(51, 29, -256, 306));
assertFalse(IrisModdedBiomeSource.isMonumentSurfaceBiomeQuery(50, 28, -256, 306));
}
@Test
public void spawnHeightMatchesPaperFixedSpawnClamp() {
assertEquals(96, IrisModdedChunkGenerator.clampSpawnHeight(-64, 384));
assertEquals(96, IrisModdedChunkGenerator.clampSpawnHeight(0, 128));
assertEquals(88, IrisModdedChunkGenerator.clampSpawnHeight(80, 10));
assertEquals(101, IrisModdedChunkGenerator.clampSpawnHeight(100, 20));
}
@Test
public void freshAndStudioWorldsReconcileToOriginWhileCustomSpawnsRemain() {
assertTrue(ModdedEngineBootstrap.shouldReconcileSpawn(true, false, 120, -64));
assertTrue(ModdedEngineBootstrap.shouldReconcileSpawn(false, true, 120, -64));
assertTrue(ModdedEngineBootstrap.shouldReconcileSpawn(false, false, 0, 0));
assertFalse(ModdedEngineBootstrap.shouldReconcileSpawn(false, false, 1, 0));
assertFalse(ModdedEngineBootstrap.shouldReconcileSpawn(false, false, 0, -1));
}
@Test
public void reconciledSpawnUsesOriginAndClampedSurfaceHeight() {
assertEquals(new BlockPos(0, 73, 0), ModdedEngineBootstrap.reconciledSpawnPosition(73, -64, 384));
assertEquals(new BlockPos(0, -63, 0), ModdedEngineBootstrap.reconciledSpawnPosition(-100, -64, 384));
assertEquals(new BlockPos(0, 318, 0), ModdedEngineBootstrap.reconciledSpawnPosition(400, -64, 384));
}
@Test
public void biomeResolutionUsesRawPlatformSeedFormula() {
long worldSeed = 998877665544L;
int blockX = -124;
int blockY = 48;
int blockZ = 712;
long expected = worldSeed
^ ((long) blockX * 341873128712L)
^ ((long) blockY * 132897987541L)
^ ((long) blockZ * 42317861L);
assertEquals(expected, IrisModdedBiomeSource.biomeResolutionSeed(worldSeed, blockX, blockY, blockZ));
}
@Test
public void configuredBiomeKeysContainOnlyPackDerivativesAndCustomBiomes() {
IrisBiome ocean = new IrisBiome()
.setDerivative("minecraft:desert")
.setVanillaDerivative("minecraft:deep_ocean");
IrisBiome custom = new IrisBiome()
.setDerivative("forest")
.setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId("Aurora")));
Set<String> keys = IrisModdedChunkGenerator.collectConfiguredBiomeKeys(
List.of(ocean, custom), "OverWorld");
assertEquals(Set.of("minecraft:deep_ocean", "minecraft:forest", "overworld:aurora"), keys);
assertFalse(keys.contains("minecraft:desert"));
assertFalse(keys.contains("minecraft:plains"));
}
@Test
public void structureStateRejectsBiomesOutsideThePackContract() {
Set<String> generated = Set.of("minecraft:deep_ocean", "minecraft:dark_forest");
assertTrue(IrisModdedBiomeSource.isGeneratedBiomeKey("minecraft:deep_ocean", generated));
assertTrue(IrisModdedBiomeSource.isGeneratedBiomeKey("MINECRAFT:DARK_FOREST", generated));
assertFalse(IrisModdedBiomeSource.isGeneratedBiomeKey("minecraft:desert", generated));
assertFalse(IrisModdedBiomeSource.isGeneratedBiomeKey(null, generated));
}
@Test
public void possibleBiomeFallbackIsOnlyRequiredForMissingOrEmptyConfigurations() {
Set<String> registered = Set.of("minecraft:deep_ocean", "minecraft:dark_forest", "minecraft:plains");
assertFalse(IrisModdedBiomeSource.requiresPossibleBiomeFallback(
Set.of("minecraft:deep_ocean", "minecraft:dark_forest"), registered));
assertTrue(IrisModdedBiomeSource.requiresPossibleBiomeFallback(
Set.of("minecraft:deep_ocean", "overworld:missing"), registered));
assertTrue(IrisModdedBiomeSource.requiresPossibleBiomeFallback(Set.of(), registered));
}
@Test
public void configuredDimensionMetadataIsExactBeforeEngineBinding() {
IrisDimension dimension = new IrisDimension()
.setDimensionHeight(new IrisRange(-256, 512))
.setFluidHeight(50);
dimension.setLoadKey("bootstrap_contract");
IrisModdedChunkGenerator.DimensionMetadata metadata =
IrisModdedChunkGenerator.dimensionMetadata(dimension);
assertEquals(-256, metadata.minY());
assertEquals(512, metadata.maxY());
assertEquals(768, metadata.depth());
assertEquals(50, metadata.seaLevel());
}
@Test
public void structureRingWorkersWaitWithoutBlockingLifecycleBinding() throws Exception {
IrisModdedChunkGenerator.EngineBinding<String> binding =
new IrisModdedChunkGenerator.EngineBinding<>(5L, TimeUnit.SECONDS);
String exactEngine = "exact-engine";
CountDownLatch workerStarted = new CountDownLatch(1);
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<String> ringWorker = executor.submit(() -> {
workerStarted.countDown();
return binding.await("overworld:overworld");
});
assertTrue(workerStarted.await(1L, TimeUnit.SECONDS));
assertFalse(ringWorker.isDone());
binding.complete(exactEngine);
assertSame(exactEngine, ringWorker.get(1L, TimeUnit.SECONDS));
} finally {
executor.shutdownNow();
}
}
@Test
public void structureRingBindingPropagatesBootstrapFailure() {
IrisModdedChunkGenerator.EngineBinding<String> binding =
new IrisModdedChunkGenerator.EngineBinding<>(1L, TimeUnit.SECONDS);
IllegalArgumentException failure = new IllegalArgumentException("broken pack");
binding.fail(failure);
try {
binding.await("overworld:overworld");
} catch (IllegalStateException error) {
assertSame(failure, error.getCause());
return;
}
throw new AssertionError("Expected failed engine binding to propagate");
}
@Test
public void initialEntitySpawnsUseThePaperCompletionMarker() {
assertSame(MantleFlag.INITIAL_SPAWNED_MARKER, ModdedWorldManager.INITIAL_SPAWN_COMPLETION_FLAG);
}
}
@@ -0,0 +1,41 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.StairBlock;
import net.minecraft.world.level.block.state.BlockState;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ModdedBlockBreakHandlerTest {
@Test
public void exactMatchingIncludesPropertiesWhileTypeMatchingDoesNot() {
BlockState north = Blocks.OAK_STAIRS.defaultBlockState().setValue(StairBlock.FACING, Direction.NORTH);
BlockState east = Blocks.OAK_STAIRS.defaultBlockState().setValue(StairBlock.FACING, Direction.EAST);
assertTrue(ModdedBlockBreakHandler.matchesState(north, north, true));
assertFalse(ModdedBlockBreakHandler.matchesState(north, east, true));
assertTrue(ModdedBlockBreakHandler.matchesState(north, east, false));
assertFalse(ModdedBlockBreakHandler.matchesState(north, Blocks.COBBLESTONE.defaultBlockState(), false));
}
}
@@ -0,0 +1,76 @@
package art.arcane.iris.modded;
import art.arcane.iris.modded.api.ModdedBlockData;
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
import art.arcane.iris.modded.api.ModdedDataProvider;
import art.arcane.iris.modded.api.ModdedDataType;
import art.arcane.iris.spi.PlatformBlockState;
import net.minecraft.SharedConstants;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.StairBlock;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class ModdedDeferredBlockParityTest {
private static final Identifier BLOCK_ID = Identifier.parse("iris_deferred_test:oak_stairs");
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
ModdedCustomContentRegistry.register(new DeferredProvider());
}
@Test
public void deferredProviderStateCarriesPlacementMetadataThroughMutation() {
ModdedBlockState resolved = ModdedBlockResolution.getOrNull(
"iris_deferred_test:oak_stairs[facing=north,half=bottom]");
assertNotNull(resolved);
assertTrue(resolved.isCustom());
assertEquals("iris_deferred_test:oak_stairs[facing=north,half=bottom]", resolved.deferredPlacementKey());
assertEquals(Blocks.OAK_STAIRS, resolved.handle().getBlock());
PlatformBlockState mutated = resolved.withProperty("facing", "west");
assertTrue(mutated.isCustom());
assertEquals(resolved.deferredPlacementKey(), mutated.deferredPlacementKey());
assertEquals("west", ((ModdedBlockState) mutated).handle().getValue(StairBlock.FACING).getName());
PlatformBlockState base = mutated.placementBaseState();
assertFalse(base.isCustom());
assertEquals(Blocks.OAK_STAIRS, ((ModdedBlockState) base).handle().getBlock());
}
private static final class DeferredProvider implements ModdedDataProvider {
@Override
public String modId() {
return "iris_deferred_test";
}
@Override
public Collection<Identifier> getTypes(ModdedDataType type) {
return type == ModdedDataType.BLOCK ? List.of(BLOCK_ID) : List.of();
}
@Override
public boolean isValidProvider(Identifier id, ModdedDataType type) {
return type == ModdedDataType.BLOCK && BLOCK_ID.equals(id);
}
@Override
public ModdedBlockData getBlockData(Identifier blockId, Map<String, String> state) {
return ModdedBlockData.deferred(Blocks.OAK_STAIRS.defaultBlockState());
}
}
}
@@ -0,0 +1,184 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
import art.arcane.iris.engine.object.IrisDimensionTypeOptions;
import art.arcane.iris.engine.object.IrisEnvironment;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.json.JSONObject;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.FALSE;
import static art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.TRUE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class ModdedDimensionTypeParityTest {
@Test
public void everyPackDimensionUsesItsExactTypeReference() {
IrisDimension overworld = dimension("overworld", IrisEnvironment.NORMAL, -64, 320, 320, new IrisDimensionTypeOptions());
IrisDimension nether = dimension("nether", IrisEnvironment.NETHER, 0, 256, 256, new IrisDimensionTypeOptions());
IrisDimension end = dimension("the_end", IrisEnvironment.THE_END, 0, 256, 256, new IrisDimensionTypeOptions());
assertEquals("irisworldgen:overworld", ModdedForcedDatapack.dimensionTypeRef(overworld));
assertEquals("irisworldgen:nether", ModdedForcedDatapack.dimensionTypeRef(nether));
assertEquals("irisworldgen:the_end", ModdedForcedDatapack.dimensionTypeRef(end));
}
@Test
public void writesExactOverworldNetherEndAndCustomContracts() throws IOException {
IrisDimensionTypeOptions customOptions = new IrisDimensionTypeOptions()
.coordinateScale(3.5D)
.ambientLight(0.4F)
.skylight(FALSE)
.ceiling(TRUE);
IrisDimension overworld = dimension("overworld", IrisEnvironment.NORMAL, -64, 320, 320, new IrisDimensionTypeOptions());
IrisDimension nether = dimension("nether", IrisEnvironment.NETHER, 0, 256, 256, new IrisDimensionTypeOptions());
IrisDimension end = dimension("the_end", IrisEnvironment.THE_END, 0, 256, 256, new IrisDimensionTypeOptions());
IrisDimension custom = dimension("custom_contract", IrisEnvironment.CUSTOM, -128, 384, 384, customOptions);
List<IrisDimension> dimensions = List.of(overworld, nether, end, custom);
IDataFixer fixer = DataVersion.getLatest().get();
Path packDirectory = Files.createTempDirectory("iris-dimension-contracts");
KList<File> roots = new KList<>();
roots.add(packDirectory.toFile());
try {
for (IrisDimension dimension : dimensions) {
ModdedForcedDatapack.writeDimensionType(roots, fixer, dimension);
Path output = packDirectory.resolve("data/irisworldgen/dimension_type/"
+ dimension.getDimensionTypeKey() + ".json");
assertTrue(Files.isRegularFile(output));
assertEquals(dimension.getDimensionType().toJson(fixer),
Files.readString(output, StandardCharsets.UTF_8));
}
JSONObject overworldJson = readType(packDirectory, overworld);
JSONObject netherJson = readType(packDirectory, nether);
JSONObject endJson = readType(packDirectory, end);
JSONObject customJson = readType(packDirectory, custom);
assertTrue(overworldJson.getBoolean("has_skylight"));
assertFalse(overworldJson.getBoolean("has_ceiling"));
assertEquals(1D, overworldJson.getDouble("coordinate_scale"), 0D);
assertFalse(netherJson.getBoolean("has_skylight"));
assertTrue(netherJson.getBoolean("has_ceiling"));
assertEquals(8D, netherJson.getDouble("coordinate_scale"), 0D);
assertTrue(endJson.getBoolean("has_ender_dragon_fight"));
assertFalse(endJson.getBoolean("has_skylight"));
assertEquals(-128, customJson.getInt("min_y"));
assertEquals(512, customJson.getInt("height"));
assertEquals(384, customJson.getInt("logical_height"));
assertEquals(3.5D, customJson.getDouble("coordinate_scale"), 0D);
assertEquals(0.4D, customJson.getDouble("ambient_light"), 0.000001D);
assertFalse(customJson.getBoolean("has_skylight"));
assertTrue(customJson.getBoolean("has_ceiling"));
} finally {
deleteTree(packDirectory);
}
}
@Test
public void missingExactDimensionTypeIsRejected() {
try {
ModdedForcedDatapack.requireRegisteredDimensionType(
"irisworldgen:overworld", Optional.empty(), "overworld", "overworld");
fail("Missing dimension type must be rejected");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().contains("irisworldgen:overworld"));
assertTrue(e.getMessage().contains("Restart the server"));
}
}
@Test
public void loadedLevelMustMatchExactPackHeightRange() {
IrisDimension dimension = dimension("tall", IrisEnvironment.NORMAL, -128, 384, 384, new IrisDimensionTypeOptions());
IrisDimensionRuntimeContract contract = IrisDimensionRuntimeContract.expected(dimension, "irisworldgen");
contract.requireHeight("test level", -128, 512);
try {
contract.requireHeight("test level", -256, 768);
fail("Oversized fallback height must be rejected");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().contains("terrain clipping is not allowed"));
}
}
@Test
public void worldCheckRejectsFallbackHeightAndSemantics() {
IrisDimensionTypeOptions options = new IrisDimensionTypeOptions()
.coordinateScale(2.5D)
.ambientLight(0.3F)
.skylight(FALSE)
.ceiling(TRUE);
IrisDimension dimension = dimension("runtime_contract", IrisEnvironment.CUSTOM,
-128, 384, 384, options);
ModdedWorldCheck.DimensionContract expected = ModdedWorldCheck.expectedDimensionContract(dimension);
ModdedWorldCheck.DimensionContract fallback = new ModdedWorldCheck.DimensionContract(
-256, 768, 512, 1D, 0F, true, false, false, 0);
assertTrue(ModdedWorldCheck.matchesDimensionContract(-128, 512, expected, expected));
assertFalse(ModdedWorldCheck.matchesDimensionContract(-256, 768, expected, fallback));
assertFalse(ModdedWorldCheck.matchesDimensionContract(-128, 512, expected, fallback));
}
private static IrisDimension dimension(String key, IrisEnvironment environment, int minY, int maxY,
int logicalHeight, IrisDimensionTypeOptions options) {
IrisDimension dimension = new IrisDimension();
dimension.setLoadKey(key);
dimension.setEnvironment(environment);
dimension.setDimensionHeight(new IrisRange(minY, maxY));
dimension.setLogicalHeight(logicalHeight);
dimension.setDimensionOptions(options);
return dimension;
}
private static JSONObject readType(Path packDirectory, IrisDimension dimension) throws IOException {
Path output = packDirectory.resolve("data/irisworldgen/dimension_type/"
+ dimension.getDimensionTypeKey() + ".json");
return new JSONObject(Files.readString(output, StandardCharsets.UTF_8));
}
private static void deleteTree(Path root) throws IOException {
List<Path> paths = new ArrayList<>();
try (Stream<Path> walk = Files.walk(root)) {
walk.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).forEach(paths::add);
}
for (Path path : paths) {
Files.deleteIfExists(path);
}
}
}
@@ -0,0 +1,62 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ModdedEngineEffectsTest {
@Test
public void normalizesRegistryKeys() {
assertNull(ModdedEngineEffects.normalizeRegistryKey(null));
assertNull(ModdedEngineEffects.normalizeRegistryKey(" "));
assertEquals("minecraft:block_amethyst_block_chime",
ModdedEngineEffects.normalizeRegistryKey("BLOCK AMETHYST BLOCK CHIME"));
assertEquals("example:custom_sound",
ModdedEngineEffects.normalizeRegistryKey("Example:Custom_Sound"));
}
@Test
public void normalizesLegacyPotionAliases() {
assertEquals("minecraft:luck", ModdedEngineEffects.normalizePotionEffectKey(null));
assertEquals("minecraft:slowness", ModdedEngineEffects.normalizePotionEffectKey("SLOW"));
assertEquals("minecraft:mining_fatigue", ModdedEngineEffects.normalizePotionEffectKey("SLOW_DIGGING"));
assertEquals("minecraft:instant_health", ModdedEngineEffects.normalizePotionEffectKey("minecraft:HEAL"));
assertEquals("example:resistance", ModdedEngineEffects.normalizePotionEffectKey("example:DAMAGE_RESISTANCE"));
}
@Test
public void samplesInitiallyAndAfterMovingPastThreshold() {
assertTrue(ModdedEngineEffects.needsSample(false, 0L, 0.0D));
assertFalse(ModdedEngineEffects.needsSample(true, 56L, 81.0D));
assertFalse(ModdedEngineEffects.needsSample(true, 55L, 82.0D));
assertTrue(ModdedEngineEffects.needsSample(true, 56L, 82.0D));
}
@Test
public void preservesOnlyStrictlyStrongerPotionEffects() {
assertFalse(ModdedEngineEffects.shouldReplacePotionEffect(3, 2));
assertTrue(ModdedEngineEffects.shouldReplacePotionEffect(2, 2));
assertTrue(ModdedEngineEffects.shouldReplacePotionEffect(1, 2));
}
}
@@ -0,0 +1,35 @@
package art.arcane.iris.modded;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ModdedEntityAwarenessTest {
@Test
public void unawareConfigurationAddsThePartialAiTag() {
Set<String> tags = new HashSet<>();
ModdedEntityAwareness.configureTags(tags, false);
assertFalse(ModdedEntityAwareness.isAware(tags));
}
@Test
public void awareConfigurationRemovesThePartialAiTag() {
Set<String> tags = new HashSet<>();
ModdedEntityAwareness.configureTags(tags, false);
ModdedEntityAwareness.configureTags(tags, true);
assertTrue(ModdedEntityAwareness.isAware(tags));
}
@Test
public void mobsAreAwareWithoutAnIrisTag() {
assertTrue(ModdedEntityAwareness.isAware(Set.of()));
}
}
@@ -0,0 +1,30 @@
package art.arcane.iris.modded;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class ModdedEntityCommandRunnerTest {
@Test
public void commandPreparationRemovesOneLeadingSlashAndExpandsCoordinates() {
String command = ModdedEntityCommandRunner.prepareCommand(
"/summon pig {x} {y} {z} {x}", -12, 65, 99);
assertEquals("summon pig -12 65 99 -12", command);
}
@Test
public void blankCommandsAreIgnored() {
assertNull(ModdedEntityCommandRunner.prepareCommand(" ", 0, 0, 0));
assertNull(ModdedEntityCommandRunner.prepareCommand(null, 0, 0, 0));
}
@Test
public void delaysMatchPaperClampingRules() {
assertEquals(0, ModdedEntityCommandRunner.clampDelay(-10L, 0));
assertEquals(1, ModdedEntityCommandRunner.clampDelay(-10L, 1));
assertEquals(73, ModdedEntityCommandRunner.clampDelay(73L, 0));
assertEquals(Integer.MAX_VALUE, ModdedEntityCommandRunner.clampDelay(Long.MAX_VALUE, 0));
}
}
@@ -0,0 +1,39 @@
package art.arcane.iris.modded;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ModdedEntityPersistenceTest {
@Test
public void generatedNonPersistentEntityIsExcludedFromVanillaSaves() {
Set<String> tags = new HashSet<>();
ModdedEntityPersistence.configureTags(tags, false);
assertFalse(ModdedEntityPersistence.shouldSave(tags, true));
}
@Test
public void positivePersistenceRemovesTheSaveExclusion() {
Set<String> tags = new HashSet<>();
ModdedEntityPersistence.configureTags(tags, false);
ModdedEntityPersistence.configureTags(tags, true);
assertTrue(ModdedEntityPersistence.shouldSave(tags, true));
}
@Test
public void interceptionNeverOverridesVanillaSaveRejection() {
Set<String> tags = new HashSet<>();
ModdedEntityPersistence.configureTags(tags, true);
assertFalse(ModdedEntityPersistence.shouldSave(tags, false));
}
}
@@ -0,0 +1,165 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.object.IrisEffect;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.monster.Zoglin;
import net.minecraft.world.entity.monster.piglin.Piglin;
import org.junit.Test;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ModdedEntitySpawnerParityTest {
@Test
public void collisionCheckUsesPaperInclusiveIntegerDimensions() {
Set<BlockPos> checked = new HashSet<>();
boolean clear = ModdedEntitySpawner.isAreaClearForSpawn(
10, 64, 20, 0.6F, 1.95F, position -> {
checked.add(position);
return true;
});
assertTrue(clear);
assertEquals(Set.of(new BlockPos(10, 64, 20), new BlockPos(10, 65, 20)), checked);
}
@Test
public void collisionCheckCoversWideEntityVolumeAndRejectsOneBlockedCell() {
AtomicInteger checked = new AtomicInteger();
boolean fullyClear = ModdedEntitySpawner.isAreaClearForSpawn(
0, 10, 0, 4F, 3.2F, position -> {
checked.incrementAndGet();
return true;
});
assertTrue(fullyClear);
assertEquals(100, checked.get());
BlockPos obstruction = new BlockPos(2, 12, -2);
boolean clear = ModdedEntitySpawner.isAreaClearForSpawn(
0, 10, 0, 4F, 3.2F, position -> !position.equals(obstruction));
assertFalse(clear);
}
@Test
public void spawnSafetyRequiresTheTargetChunkAndAllEightNeighbors() {
Set<Long> loaded = new HashSet<>();
for (int x = 4; x <= 6; x++) {
for (int z = -3; z <= -1; z++) {
loaded.add(pack(x, z));
}
}
assertTrue(ModdedEntitySpawner.allNeighborChunksLoaded(5, -2,
(x, z) -> loaded.contains(pack(x, z))));
loaded.remove(pack(4, -3));
assertFalse(ModdedEntitySpawner.allNeighborChunksLoaded(5, -2,
(x, z) -> loaded.contains(pack(x, z))));
}
@Test
public void onlyAiFalseUsesVanillaNoAi() {
assertFalse(ModdedEntitySpawner.shouldDisableAi(true));
assertTrue(ModdedEntitySpawner.shouldDisableAi(false));
}
@Test
public void piglinAndZoglinUseTheVirtualMobBabyContract() throws NoSuchMethodException {
assertTrue(Mob.class.isAssignableFrom(Piglin.class));
assertTrue(Mob.class.isAssignableFrom(Zoglin.class));
assertEquals(Piglin.class, Piglin.class.getMethod("setBaby", boolean.class).getDeclaringClass());
assertEquals(Zoglin.class, Zoglin.class.getMethod("setBaby", boolean.class).getDeclaringClass());
}
@Test
public void everyAcceptedBukkitSpawnReasonHasAnExplicitNmsMapping() {
Map<String, EntitySpawnReason> expected = Map.ofEntries(
Map.entry("NATURAL", EntitySpawnReason.NATURAL),
Map.entry("JOCKEY", EntitySpawnReason.JOCKEY),
Map.entry("CHUNK_GEN", EntitySpawnReason.CHUNK_GENERATION),
Map.entry("SPAWNER", EntitySpawnReason.SPAWNER),
Map.entry("TRIAL_SPAWNER", EntitySpawnReason.TRIAL_SPAWNER),
Map.entry("EGG", EntitySpawnReason.TRIGGERED),
Map.entry("SPAWNER_EGG", EntitySpawnReason.SPAWN_ITEM_USE),
Map.entry("LIGHTNING", EntitySpawnReason.EVENT),
Map.entry("BUILD_SNOWMAN", EntitySpawnReason.TRIGGERED),
Map.entry("BUILD_IRONGOLEM", EntitySpawnReason.TRIGGERED),
Map.entry("BUILD_COPPERGOLEM", EntitySpawnReason.TRIGGERED),
Map.entry("BUILD_WITHER", EntitySpawnReason.TRIGGERED),
Map.entry("VILLAGE_DEFENSE", EntitySpawnReason.MOB_SUMMONED),
Map.entry("VILLAGE_INVASION", EntitySpawnReason.EVENT),
Map.entry("BREEDING", EntitySpawnReason.BREEDING),
Map.entry("SLIME_SPLIT", EntitySpawnReason.TRIGGERED),
Map.entry("REINFORCEMENTS", EntitySpawnReason.REINFORCEMENT),
Map.entry("NETHER_PORTAL", EntitySpawnReason.STRUCTURE),
Map.entry("DISPENSE_EGG", EntitySpawnReason.DISPENSER),
Map.entry("INFECTION", EntitySpawnReason.CONVERSION),
Map.entry("CURED", EntitySpawnReason.CONVERSION),
Map.entry("OCELOT_BABY", EntitySpawnReason.BREEDING),
Map.entry("SILVERFISH_BLOCK", EntitySpawnReason.TRIGGERED),
Map.entry("MOUNT", EntitySpawnReason.JOCKEY),
Map.entry("TRAP", EntitySpawnReason.TRIGGERED),
Map.entry("ENDER_PEARL", EntitySpawnReason.TRIGGERED),
Map.entry("SHOULDER_ENTITY", EntitySpawnReason.LOAD),
Map.entry("DROWNED", EntitySpawnReason.CONVERSION),
Map.entry("SHEARED", EntitySpawnReason.CONVERSION),
Map.entry("EXPLOSION", EntitySpawnReason.TRIGGERED),
Map.entry("RAID", EntitySpawnReason.EVENT),
Map.entry("PATROL", EntitySpawnReason.PATROL),
Map.entry("BEEHIVE", EntitySpawnReason.LOAD),
Map.entry("PIGLIN_ZOMBIFIED", EntitySpawnReason.CONVERSION),
Map.entry("SPELL", EntitySpawnReason.MOB_SUMMONED),
Map.entry("FROZEN", EntitySpawnReason.CONVERSION),
Map.entry("METAMORPHOSIS", EntitySpawnReason.CONVERSION),
Map.entry("DUPLICATION", EntitySpawnReason.BREEDING),
Map.entry("COMMAND", EntitySpawnReason.COMMAND),
Map.entry("ENCHANTMENT", EntitySpawnReason.TRIGGERED),
Map.entry("OMINOUS_ITEM_SPAWNER", EntitySpawnReason.TRIGGERED),
Map.entry("BUCKET", EntitySpawnReason.BUCKET),
Map.entry("POTION_EFFECT", EntitySpawnReason.TRIGGERED),
Map.entry("REANIMATE", EntitySpawnReason.TRIGGERED),
Map.entry("REHYDRATION", EntitySpawnReason.BREEDING),
Map.entry("CUSTOM", EntitySpawnReason.EVENT),
Map.entry("DEFAULT", EntitySpawnReason.NATURAL));
assertEquals(47, expected.size());
for (Map.Entry<String, EntitySpawnReason> entry : expected.entrySet()) {
assertEquals(entry.getKey(), entry.getValue(), ModdedEntitySpawner.reasonFor(entry.getKey()));
assertEquals(entry.getKey(), entry.getValue(), ModdedEntitySpawner.reasonFor(entry.getKey().toLowerCase()));
}
}
@Test
public void invalidConfiguredSpawnReasonIntentionallyDefaultsToNatural() {
assertEquals(EntitySpawnReason.NATURAL, ModdedEntitySpawner.reasonFor(null));
assertEquals(EntitySpawnReason.NATURAL, ModdedEntitySpawner.reasonFor(""));
assertEquals(EntitySpawnReason.NATURAL, ModdedEntitySpawner.reasonFor("not_a_spawn_reason"));
}
@Test
public void entityEffectExposesNeutralRegistryKeys() {
IrisEffect effect = new IrisEffect()
.setSound("minecraft:block.amethyst_block.chime")
.setParticleEffect("minecraft:flame");
assertEquals("minecraft:block.amethyst_block.chime", effect.getSoundKey());
assertEquals("minecraft:flame", effect.getParticleEffectKey());
}
private static long pack(int x, int z) {
return (((long) x) & 0xFFFFFFFFL) | ((((long) z) & 0xFFFFFFFFL) << 32);
}
}
@@ -0,0 +1,87 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.volmlib.util.collection.KSet;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
public class ModdedForcedDatapackTest {
@Test
public void scopesSharedCustomBiomeIdsByNamespace() {
Map<String, KSet<String>> seenBiomes = new LinkedHashMap<>();
KSet<String> firstNamespace = ModdedForcedDatapack.biomesForNamespace(seenBiomes, "first_dimension");
KSet<String> secondNamespace = ModdedForcedDatapack.biomesForNamespace(seenBiomes, "second_dimension");
assertTrue(firstNamespace.add("shared_biome"));
assertTrue(secondNamespace.add("shared_biome"));
assertFalse(firstNamespace.add("shared_biome"));
assertNotSame(firstNamespace, secondNamespace);
assertEquals(2, seenBiomes.size());
}
@Test
public void writesForgeBlockLootModifierListAndInstance() throws IOException {
Path packDirectory = Files.createTempDirectory("iris-forge-loot-modifier");
try {
ModdedForcedDatapack.writeForgeBlockLootModifier(packDirectory);
Path list = packDirectory.resolve("data/forge/loot_modifiers/global_loot_modifiers.json");
Path modifier = packDirectory.resolve("data/irisworldgen/loot_modifiers/block_drops.json");
assertTrue(Files.isRegularFile(list));
assertTrue(Files.isRegularFile(modifier));
assertEquals("{\n"
+ " \"replace\": false,\n"
+ " \"entries\": [\"irisworldgen:block_drops\"]\n"
+ "}\n", Files.readString(list, StandardCharsets.UTF_8));
assertEquals("{\n"
+ " \"type\": \"irisworldgen:block_drops\",\n"
+ " \"conditions\": []\n"
+ "}\n", Files.readString(modifier, StandardCharsets.UTF_8));
} finally {
deleteTree(packDirectory);
}
}
private void deleteTree(Path root) throws IOException {
List<Path> paths = new ArrayList<>();
try (Stream<Path> walk = Files.walk(root)) {
walk.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).forEach(paths::add);
}
for (Path path : paths) {
Files.deleteIfExists(path);
}
}
}
@@ -0,0 +1,158 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.object.IrisLootMode;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.loot.LootTable;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ModdedLootApplierTest {
@Test
public void addAppendsSourcesInOrder() {
List<String> sources = new ArrayList<>(List.of("placement-native"));
ModdedLootApplier.injectSources(sources, List.of("dimension-iris", "region-iris"), IrisLootMode.ADD, false);
assertEquals(List.of("placement-native", "dimension-iris", "region-iris"), sources);
}
@Test
public void clearRemovesNativeAndIrisSourcesBeforeAdding() {
List<String> sources = new ArrayList<>(List.of("placement-native", "dimension-iris"));
ModdedLootApplier.injectSources(sources, List.of("biome-iris"), IrisLootMode.CLEAR, false);
assertEquals(List.of("biome-iris"), sources);
}
@Test
public void replaceRemovesNativeAndIrisSourcesBeforeAdding() {
List<String> sources = new ArrayList<>(List.of("placement-native", "dimension-iris"));
ModdedLootApplier.injectSources(sources, List.of("biome-iris"), IrisLootMode.REPLACE, false);
assertEquals(List.of("biome-iris"), sources);
}
@Test
public void entityLootBindingTargetsOnlyTheBaseLootInjection() {
assertTrue(ModdedDeathLoot.hasBindingTag(Set.of("unrelated", "iris_loot|17|1|2|3|test")));
assertFalse(ModdedDeathLoot.hasBindingTag(Set.of("unrelated", "iris_other")));
}
@Test
public void fallbackDoesNotOverrideExistingNativeOrIrisSources() {
List<String> sources = new ArrayList<>(List.of("placement-native", "dimension-iris"));
ModdedLootApplier.injectSources(sources, List.of("fallback-iris"), IrisLootMode.FALLBACK, false);
assertEquals(List.of("placement-native", "dimension-iris"), sources);
}
@Test
public void fallbackAddsSourcesWhenPlacementIsEmpty() {
List<String> sources = new ArrayList<>();
ModdedLootApplier.injectSources(sources, List.of("fallback-iris"), IrisLootMode.FALLBACK, true);
assertEquals(List.of("fallback-iris"), sources);
}
@Test
public void zeroMultiplierRemovesNativeAndIrisSources() {
List<String> sources = new ArrayList<>(List.of("placement-native", "dimension-iris"));
ModdedLootApplier.scaleSources(sources, 0D, new RNG(17L));
assertTrue(sources.isEmpty());
}
@Test
public void unitMultiplierPreservesNativeAndIrisSources() {
List<String> sources = new ArrayList<>(List.of("placement-native", "dimension-iris"));
ModdedLootApplier.scaleSources(sources, 1D, new RNG(17L));
assertEquals(List.of("placement-native", "dimension-iris"), sources);
}
@Test
public void doubleMultiplierScalesTheUnifiedSourceList() {
List<String> sources = new ArrayList<>(List.of("placement-native", "dimension-iris"));
ModdedLootApplier.scaleSources(sources, 2D, new RNG(17L));
assertEquals(4, sources.size());
assertEquals("placement-native", sources.get(0));
assertEquals("dimension-iris", sources.get(1));
Set<String> expectedSources = Set.of("placement-native", "dimension-iris");
for (String source : sources) {
assertTrue(expectedSources.contains(source));
}
}
@Test
public void nativeKeyIsReturnedWhenTheLiveRegistryContainsIt() {
ResourceKey<LootTable> key = ModdedLootApplier.resolveNativeKey("minecraft:chests/simple_dungeon", candidate -> true);
assertNotNull(key);
assertEquals(Identifier.parse("minecraft:chests/simple_dungeon"), key.identifier());
}
@Test
public void nativeKeyIsRejectedWhenTheLiveRegistryDoesNotContainIt() {
ResourceKey<LootTable> key = ModdedLootApplier.resolveNativeKey("minecraft:chests/missing", candidate -> false);
assertNull(key);
}
@Test
public void malformedNativeKeyIsRejectedBeforeRegistryLookup() {
AtomicBoolean registryConsulted = new AtomicBoolean(false);
ResourceKey<LootTable> key = ModdedLootApplier.resolveNativeKey("not a valid identifier", candidate -> {
registryConsulted.set(true);
return true;
});
assertNull(key);
assertFalse(registryConsulted.get());
}
@Test
public void nativeOnlyFillMarksTheContainerChanged() {
TrackingContainer container = new TrackingContainer();
ModdedLootApplier.fillContainer(container, List.<ItemStack>of(), new RNG(17L));
assertTrue(container.changed);
}
private static final class TrackingContainer extends SimpleContainer {
private boolean changed;
private TrackingContainer() {
super(0);
}
@Override
public void setChanged() {
changed = true;
super.setChanged();
}
}
}
@@ -0,0 +1,54 @@
package art.arcane.iris.modded;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ModdedStructureHooksTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void objectFeaturesMatchPaperGroups() {
assertEquals("trees", ModdedStructureHooks.classifyFeature(Feature.TREE));
assertEquals("fallen_trees", ModdedStructureHooks.classifyFeature(Feature.FALLEN_TREE));
assertEquals("mushrooms", ModdedStructureHooks.classifyFeature(Feature.HUGE_BROWN_MUSHROOM));
assertEquals("mushrooms", ModdedStructureHooks.classifyFeature(Feature.HUGE_RED_MUSHROOM));
assertNull(ModdedStructureHooks.classifyFeature(Feature.ORE));
}
@Test
public void structureSpanGuardUsesInclusiveBoundingBoxDimensions() {
BoundingBox box = new BoundingBox(-16, -64, 32, 15, 63, 47);
assertTrue(ModdedStructureHooks.isWithinSpan(box, 128));
assertFalse(ModdedStructureHooks.isWithinSpan(box, 127));
assertTrue(ModdedStructureHooks.isWithinSpan(box, 0));
assertTrue(ModdedStructureHooks.isWithinSpan(box, -1));
}
@Test
public void structurePlacementReturnsPaperOrderedBounds() {
BoundingBox box = new BoundingBox(-16, -64, 32, 15, 63, 47);
assertArrayEquals(new int[]{-16, -64, 32, 15, 63, 47}, ModdedStructureHooks.bounds(box));
}
@Test
public void platformWorldMaximumHeightIsExclusive() {
assertEquals(320, ModdedPlatformWorld.exclusiveMaxHeight(-64, 384));
assertEquals(384, ModdedPlatformWorld.exclusiveMaxHeight(0, 384));
}
}
@@ -0,0 +1,218 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.object.TileData;
import art.arcane.volmlib.util.collection.KMap;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.RandomizableContainer;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BarrelBlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
import net.minecraft.world.level.storage.TagValueInput;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class ModdedTileParityTest {
private static RegistryAccess registries;
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
registries = RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY);
TileData.bindFallbackReader(new ModdedTileReader(() -> null));
TileData.bindFallbackFactory(ModdedTileData::fromProperties);
}
@Test
public void legacySignPayloadLoadsIntoNativeSignBlockEntity() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(0);
out.writeUTF("Iris");
out.writeUTF("modded");
out.writeUTF("tile");
out.writeUTF("parity");
out.writeByte(DyeColor.BLUE.getId());
});
SignBlockEntity sign = new SignBlockEntity(BlockPos.ZERO, Blocks.OAK_SIGN.defaultBlockState());
sign.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, tile.payload()));
assertEquals("Iris", sign.getFrontText().getMessage(0, false).getString());
assertEquals("parity", sign.getBackText().getMessage(3, false).getString());
assertEquals(DyeColor.BLUE, sign.getFrontText().getColor());
}
@Test
public void legacySpawnerPayloadLoadsNamespacedEntity() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(1);
out.writeUTF("minecraft:zombie");
});
SpawnerBlockEntity spawner = new SpawnerBlockEntity(BlockPos.ZERO, Blocks.SPAWNER.defaultBlockState());
spawner.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, tile.payload()));
CompoundTag saved = spawner.saveWithoutMetadata(registries);
assertTrue(saved.toString().contains("minecraft:zombie"));
}
@Test
public void legacySpawnerOrdinalUsesPaperEntityTypeOrdering() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(1);
out.writeShort(28);
});
SpawnerBlockEntity spawner = new SpawnerBlockEntity(BlockPos.ZERO, Blocks.SPAWNER.defaultBlockState());
spawner.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, tile.payload()));
CompoundTag saved = spawner.saveWithoutMetadata(registries);
assertTrue(saved.toString().contains("minecraft:command_block_minecart"));
}
@Test
public void invalidLegacySpawnerOrdinalFallsBackToPig() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(1);
out.writeShort(-1);
});
SpawnerBlockEntity spawner = new SpawnerBlockEntity(BlockPos.ZERO, Blocks.SPAWNER.defaultBlockState());
spawner.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, tile.payload()));
CompoundTag saved = spawner.saveWithoutMetadata(registries);
assertTrue(saved.toString().contains("minecraft:pig"));
}
@Test
public void legacyLootablePayloadLoadsTableAndSeed() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(3);
out.writeUTF("minecraft:chest");
out.writeUTF("minecraft:chests/simple_dungeon");
out.writeLong(4123L);
});
ChestBlockEntity chest = new ChestBlockEntity(BlockPos.ZERO, Blocks.CHEST.defaultBlockState());
chest.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, tile.payload()));
RandomizableContainer lootable = chest;
assertNotNull(lootable.getLootTable());
assertEquals("minecraft:chests/simple_dungeon", lootable.getLootTable().identifier().toString());
assertEquals(4123L, lootable.getLootTableSeed());
}
@Test
public void legacyBannerBaseColorChangesTheGeneratedBlockState() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(2);
out.writeByte(DyeColor.RED.getId());
out.writeByte(0);
});
assertEquals(
BuiltInRegistries.BLOCK.getValue(Identifier.parse("minecraft:red_banner")),
tile.adjustBlockState(BuiltInRegistries.BLOCK.getValue(Identifier.parse("minecraft:white_banner")).defaultBlockState()).getBlock());
assertEquals(Blocks.BARREL, tile.adjustBlockState(Blocks.BARREL.defaultBlockState()).getBlock());
}
@Test
public void paper26_2LegacyBannerFirstOrdinalIsSmallStripes() {
assertEquals(
Identifier.parse("minecraft:small_stripes"),
ModdedTileReader.legacyBannerPatternKey(0));
}
@Test
public void paper26_2LegacyBannerMiddleOrdinalIsStripeLeft() {
assertEquals(
Identifier.parse("minecraft:stripe_left"),
ModdedTileReader.legacyBannerPatternKey(21));
}
@Test
public void paper26_2LegacyBannerLastOrdinalIsHalfVerticalRight() {
assertEquals(
Identifier.parse("minecraft:half_vertical_right"),
ModdedTileReader.legacyBannerPatternKey(42));
}
@Test
public void invalidLegacyBannerOrdinalFallsBackToBase() {
assertEquals(Identifier.parse("minecraft:base"), ModdedTileReader.legacyBannerPatternKey(-1));
assertEquals(Identifier.parse("minecraft:base"), ModdedTileReader.legacyBannerPatternKey(43));
}
@Test
public void modernPackTilePropertiesUseTheModdedFactoryAndLoadNatively() throws Exception {
KMap<String, Object> properties = new KMap<>();
properties.put("LootTable", "minecraft:chests/abandoned_mineshaft");
properties.put("LootTableSeed", 9124L);
TileData tile = TileData.of(ModdedBlockState.of(Blocks.CHEST.defaultBlockState(), null), properties);
assertTrue(tile instanceof ModdedTileData);
ChestBlockEntity chest = new ChestBlockEntity(BlockPos.ZERO, Blocks.CHEST.defaultBlockState());
chest.loadWithComponents(TagValueInput.create(
ProblemReporter.DISCARDING, registries, ((ModdedTileData) tile).payload()));
assertNotNull(chest.getLootTable());
assertEquals("minecraft:chests/abandoned_mineshaft", chest.getLootTable().identifier().toString());
assertEquals(9124L, chest.getLootTableSeed());
assertTrue(((ModdedTileData) tile).isApplicable(Blocks.CHEST.defaultBlockState(), chest));
assertFalse(((ModdedTileData) tile).isApplicable(
Blocks.BARREL.defaultBlockState(),
new BarrelBlockEntity(BlockPos.ZERO, Blocks.BARREL.defaultBlockState())));
}
@Test
public void legacyTileFamiliesRejectUnrelatedBlockEntities() throws Exception {
ModdedTileData tile = legacyTile(out -> {
out.writeShort(0);
out.writeUTF("one");
out.writeUTF("two");
out.writeUTF("three");
out.writeUTF("four");
out.writeByte(DyeColor.BLACK.getId());
});
SignBlockEntity sign = new SignBlockEntity(BlockPos.ZERO, Blocks.OAK_SIGN.defaultBlockState());
ChestBlockEntity chest = new ChestBlockEntity(BlockPos.ZERO, Blocks.CHEST.defaultBlockState());
assertTrue(tile.isApplicable(Blocks.OAK_SIGN.defaultBlockState(), sign));
assertFalse(tile.isApplicable(Blocks.CHEST.defaultBlockState(), chest));
}
private static ModdedTileData legacyTile(TileWriter writer) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream out = new DataOutputStream(bytes)) {
writer.write(out);
}
TileData tile = TileData.read(new DataInputStream(new ByteArrayInputStream(bytes.toByteArray())));
assertTrue(tile instanceof ModdedTileData);
return (ModdedTileData) tile;
}
@FunctionalInterface
private interface TileWriter {
void write(DataOutputStream out) throws Exception;
}
}
@@ -1,5 +1,8 @@
package art.arcane.iris.modded;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import org.junit.Test;
import java.util.ArrayList;
@@ -21,38 +24,224 @@ public class ModdedWorldCheckTest {
}
@Test
public void passStopsServerBeforeZeroExit() {
public void validStructureStartIsGenerationEvidence() {
assertTrue(ModdedWorldCheck.hasNativeStructureEvidence(true, 0));
}
@Test
public void structureReferenceIsGenerationEvidence() {
assertTrue(ModdedWorldCheck.hasNativeStructureEvidence(false, 1));
}
@Test
public void absentStartAndReferencesFailGenerationEvidence() {
assertFalse(ModdedWorldCheck.hasNativeStructureEvidence(false, 0));
}
@Test
public void characteristicMaterialsCoverEveryNativeStructureFamily() {
assertTrue(characteristic("stronghold", "minecraft:stronghold", "minecraft:cracked_stone_bricks"));
assertTrue(characteristic("trial_chambers", "minecraft:trial_chambers", "minecraft:oxidized_copper_grate"));
assertTrue(characteristic("mansion", "minecraft:mansion", "minecraft:dark_oak_planks"));
assertTrue(characteristic("mansion", "minecraft:mansion", "minecraft:birch_planks"));
assertTrue(characteristic("village", "minecraft:village_plains", "minecraft:oak_planks"));
assertTrue(characteristic("village", "minecraft:village_desert", "minecraft:cut_sandstone"));
assertTrue(characteristic("village", "minecraft:village_savanna", "minecraft:acacia_stairs"));
assertTrue(characteristic("village", "minecraft:village_snowy", "minecraft:spruce_planks"));
assertTrue(characteristic("village", "minecraft:village_snowy", "minecraft:stripped_spruce_log"));
assertTrue(characteristic("village", "minecraft:village_taiga", "minecraft:cobblestone"));
assertTrue(characteristic("monument", "minecraft:monument", "minecraft:dark_prismarine"));
}
@Test
public void naturalTerrainAndWrongVillageWoodAreNotCharacteristic() {
assertFalse(characteristic("stronghold", "minecraft:stronghold", "minecraft:stone"));
assertFalse(characteristic("trial_chambers", "minecraft:trial_chambers", "minecraft:tuff"));
assertFalse(characteristic("mansion", "minecraft:mansion", "minecraft:dark_oak_leaves"));
assertFalse(characteristic("village", "minecraft:village_desert", "minecraft:sandstone"));
assertFalse(characteristic("village", "minecraft:village_savanna", "minecraft:oak_planks"));
assertFalse(characteristic("monument", "minecraft:monument", "minecraft:water"));
}
@Test
public void materialEvidenceMustExist() {
assertFalse(ModdedWorldCheck.hasCharacteristicMaterialEvidence(0, 0, 1));
assertFalse(ModdedWorldCheck.hasCharacteristicMaterialEvidence(8, 0, 1));
assertFalse(ModdedWorldCheck.hasCharacteristicMaterialEvidence(8, 1, 0));
assertFalse(ModdedWorldCheck.hasCharacteristicMaterialEvidence(8, 2, 1));
}
@Test
public void singleChunkStructureAcceptsMaterialInItsOnlyChunk() {
assertTrue(ModdedWorldCheck.hasCharacteristicMaterialEvidence(8, 1, 1));
}
@Test
public void multiChunkStructureRejectsMaterialConfinedToOneChunk() {
assertFalse(ModdedWorldCheck.hasCharacteristicMaterialEvidence(8, 1, 4));
assertTrue(ModdedWorldCheck.hasCharacteristicMaterialEvidence(8, 2, 4));
}
@Test
public void configuredVerticalShiftRequiresSafetyClampedGenerationEvidence() {
assertTrue(ModdedWorldCheck.verticalShiftMatches(0, null, -32, 20, -64, 320));
assertFalse(ModdedWorldCheck.verticalShiftMatches(0, null, -112, -80, -64, 320));
assertTrue(ModdedWorldCheck.verticalShiftMatches(0, 0, -32, 20, -64, 320));
assertTrue(ModdedWorldCheck.verticalShiftMatches(0, 48, -64, -32, -64, 320));
assertTrue(ModdedWorldCheck.verticalShiftMatches(-64, -64, -48, 4, -64, 320));
assertTrue(ModdedWorldCheck.verticalShiftMatches(-64, -16, -64, -12, -64, 320));
assertFalse(ModdedWorldCheck.verticalShiftMatches(-64, null, -48, 4, -64, 320));
assertFalse(ModdedWorldCheck.verticalShiftMatches(-64, -15, -63, -11, -64, 320));
assertFalse(ModdedWorldCheck.verticalShiftMatches(0, -1, -33, 19, -64, 320));
}
@Test
public void mansionVegetationGateRejectsRemainingLeaves() {
assertTrue(ModdedWorldCheck.mansionVegetationPass(0));
assertFalse(ModdedWorldCheck.mansionVegetationPass(1));
}
@Test
public void mansionVegetationAuditIgnoresTemplateBlocksAndRejectsVegetationAbovePieces() {
assertFalse(ModdedWorldCheck.mansionVegetationAbovePiece(true, 80, 80));
assertFalse(ModdedWorldCheck.mansionVegetationAbovePiece(true, 79, 80));
assertTrue(ModdedWorldCheck.mansionVegetationAbovePiece(true, 81, 80));
assertFalse(ModdedWorldCheck.mansionVegetationAbovePiece(false, 81, 80));
}
@Test
public void villageFoundationGateRejectsUnsupportedColumns() {
assertTrue(ModdedWorldCheck.villageFoundationPass(0));
assertFalse(ModdedWorldCheck.villageFoundationPass(1));
}
@Test
public void villageFoundationAuditRejectsMissingBaseAndInvalidSupport() {
assertTrue(ModdedWorldCheck.villageFoundationSupported(true, false, true, false));
assertTrue(ModdedWorldCheck.villageFoundationSupported(true, true, false, false));
assertFalse(ModdedWorldCheck.villageFoundationSupported(false, false, true, false));
assertFalse(ModdedWorldCheck.villageFoundationSupported(true, false, false, false));
assertFalse(ModdedWorldCheck.villageFoundationSupported(true, false, true, true));
}
@Test
public void villagePoiGateRequiresInBoundsPoiWithoutOutOfBoundsRecords() {
assertTrue(ModdedWorldCheck.villagePoiPass(1, 0));
assertFalse(ModdedWorldCheck.villagePoiPass(0, 0));
assertFalse(ModdedWorldCheck.villagePoiPass(1, 1));
}
@Test
public void smallStructureFootprintIncludesEveryChunk() {
BoundingBox bounds = new BoundingBox(-16, -20, -16, 31, 120, 31);
List<ChunkPos> chunks = ModdedWorldCheck.boundedFootprintChunks(bounds, ChunkPos.ZERO, 96);
assertEquals(9, chunks.size());
assertTrue(chunks.contains(new ChunkPos(-1, -1)));
assertTrue(chunks.contains(new ChunkPos(1, 1)));
}
@Test
public void largeStructureFootprintIsBoundedAndSamplesEdges() {
BoundingBox bounds = new BoundingBox(-512, -64, -512, 511, 300, 511);
List<ChunkPos> chunks = ModdedWorldCheck.boundedFootprintChunks(bounds, ChunkPos.ZERO, 20);
assertTrue(chunks.size() <= 20);
assertTrue(chunks.size() >= 16);
assertTrue(chunks.contains(ChunkPos.ZERO));
assertTrue(chunks.contains(new ChunkPos(-32, -32)));
assertTrue(chunks.contains(new ChunkPos(31, 31)));
}
@Test
public void qaEventsEscapeStructuredValues() {
String event = ModdedWorldCheck.qaEventJson("locate\"", "village\n", false, "x\\y\t");
assertEquals("QA_EVT {\"event\":\"locate\\\"\",\"structure\":\"village\\n\","
+ "\"pass\":false,\"detail\":\"x\\\\y\\t\"}", event);
}
@Test
public void passRequestsStopAfterValidation() {
List<String> events = new ArrayList<>();
ModdedWorldCheck.stopAndExit(
() -> events.add("stop"),
int status = ModdedWorldCheck.runAndRequestStop(
() -> {
events.add("check");
return true;
},
() -> events.add("request-stop")
);
assertEquals(0, status);
assertEquals(List.of("check", "request-stop"), events);
}
@Test
public void failureStillRequestsStop() {
List<String> events = new ArrayList<>();
int status = ModdedWorldCheck.runAndRequestStop(
() -> {
events.add("check");
return false;
},
() -> events.add("request-stop")
);
assertEquals(1, status);
assertEquals(List.of("check", "request-stop"), events);
}
@Test
public void thrownCheckStillRequestsStop() {
AtomicBoolean stopRequested = new AtomicBoolean(false);
int status = ModdedWorldCheck.runAndRequestStop(
() -> {
throw new IllegalStateException("check failed");
},
() -> stopRequested.set(true)
);
assertEquals(1, status);
assertTrue(stopRequested.get());
}
@Test
public void stopRequestFailureForcesNonzeroResult() {
int status = ModdedWorldCheck.runAndRequestStop(
() -> true,
() -> {
throw new IllegalStateException("stop request failed");
}
);
assertEquals(1, status);
}
@Test
public void coordinatorAwaitsServerBeforeExit() {
List<String> events = new ArrayList<>();
ModdedWorldCheck.awaitStopAndExit(
() -> events.add("await-stop"),
0,
status -> events.add("exit:" + status)
);
assertEquals(List.of("stop", "exit:0"), events);
assertEquals(List.of("await-stop", "exit:0"), events);
}
@Test
public void failureStopsServerBeforeNonzeroExit() {
List<String> events = new ArrayList<>();
ModdedWorldCheck.stopAndExit(
() -> events.add("stop"),
1,
status -> events.add("exit:" + status)
);
assertEquals(List.of("stop", "exit:1"), events);
}
@Test
public void shutdownFailureForcesNonzeroExit() {
public void shutdownWaitFailureForcesNonzeroExit() {
AtomicInteger status = new AtomicInteger(-1);
ModdedWorldCheck.stopAndExit(
ModdedWorldCheck.awaitStopAndExit(
() -> {
throw new IllegalStateException("shutdown failed");
throw new IllegalStateException("shutdown wait failed");
},
0,
status::set
@@ -68,7 +257,7 @@ public class ModdedWorldCheckTest {
AtomicInteger status = new AtomicInteger(-1);
Thread.currentThread().interrupt();
try {
ModdedWorldCheck.stopAndExit(
ModdedWorldCheck.awaitStopAndExit(
() -> interruptedDuringStop.set(Thread.currentThread().isInterrupted()),
0,
exitStatus -> {
@@ -84,4 +273,9 @@ public class ModdedWorldCheckTest {
Thread.interrupted();
}
}
private static boolean characteristic(String structureLabel, String structureKey, String blockKey) {
return ModdedWorldCheck.isCharacteristicMaterial(structureLabel,
Identifier.parse(structureKey), Identifier.parse(blockKey));
}
}
@@ -0,0 +1,20 @@
package art.arcane.iris.modded;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ModdedWorldManagerParityTest {
@Test
public void normalWorldAlwaysAllowsEntitySpawning() {
assertTrue(ModdedWorldManager.entitySpawningEnabled(false, false));
assertTrue(ModdedWorldManager.entitySpawningEnabled(false, true));
}
@Test
public void studioWorldRequiresItsEntitySpawningSetting() {
assertFalse(ModdedWorldManager.entitySpawningEnabled(true, false));
assertTrue(ModdedWorldManager.entitySpawningEnabled(true, true));
}
}
@@ -0,0 +1,81 @@
package art.arcane.iris.modded.command;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisModdedStructureCommandTest {
@Test
public void gotoStructureSupportsIrisAndNativeRegistryTargets() throws IOException {
String source = source("IrisModdedCommands.java");
assertTrue(source.contains("IrisStructureLocator.isPlaced(engine, key)"));
assertTrue(source.contains("registry.get(identifier)"));
assertTrue(source.contains("getPlacementsForStructure(holder)"));
assertTrue(source.contains("generator.findNearestMapStructure("));
assertTrue(source.contains("NATIVE_STRUCTURE_LOCATE_RADIUS = 100"));
assertTrue(source.contains("HolderSet.direct(target.holder())"));
assertTrue(source.contains("boolean teleported = player.teleportTo("));
assertTrue(source.contains("combineStructureKeys(irisKeys, nativeKeys)"));
assertTrue(source.contains("irisGenerator.isNativeStructureReachable(holder)"));
assertTrue(source.contains("LocateStatus.SEARCH_LIMIT_REACHED"));
assertTrue(source.contains("the density search safety limit was reached"));
assertTrue(source.contains("int targetX = result.originX()"));
assertTrue(source.contains("int targetY = result.baseY() + 2"));
assertTrue(source.contains("int targetZ = result.originZ()"));
assertFalse(source.contains("at[0] + 8"));
assertFalse(source.contains("at[2] + 8"));
}
@Test
public void generatorLocatePrefersIrisPlacementsAndRejectsDormantNativeStarts() throws IOException {
String source = moddedSource("IrisModdedChunkGenerator.java");
assertTrue(source.contains("public Pair<BlockPos, Holder<Structure>> findNearestMapStructure("));
assertTrue(source.contains("findNearestIrisStructure("));
assertTrue(source.contains("filterReachableNativeStructures("));
assertTrue(source.contains("IrisStructureLocator.suppressesVanilla(current, key)"));
assertTrue(source.contains("structureBiomeSource.isStructureReachable(holder)"));
assertTrue(source.contains("LocateStatus.SEARCH_LIMIT_REACHED"));
assertTrue(source.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
}
@Test
public void structureVerificationNoLongerClaimsNativeGenerationIsMissing() throws IOException {
String source = source("ModdedStructureCommands.java");
assertTrue(source.contains("verifyTree(\"verify\")"));
assertTrue(source.contains("verifyTree(\"locateall\")"));
assertTrue(source.contains("IrisModdedCommands.verifyStructures("));
assertFalse(source.contains("Vanilla structure locate is meaningless here"));
}
private String source(String fileName) throws IOException {
Path source = commonSourceRoot().resolve("art/arcane/iris/modded/command/")
.resolve(fileName)
.normalize();
return Files.readString(source);
}
private String moddedSource(String fileName) throws IOException {
Path source = commonSourceRoot().resolve("art/arcane/iris/modded/")
.resolve(fileName)
.normalize();
return Files.readString(source);
}
private Path commonSourceRoot() {
String configuredRoot = System.getProperty("iris.moddedCommonSources");
if (configuredRoot != null && !configuredRoot.isBlank()) {
return Path.of(configuredRoot);
}
return Path.of(System.getProperty("user.dir"))
.resolve("../modded-common/src/main/java")
.normalize();
}
}
@@ -0,0 +1,29 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.volmlib.util.collection.KList;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ModdedStudioHotloadServiceTest {
@Test
public void detectsDatapackImportsAcrossLoadedDimensions() {
IrisDimension empty = new IrisDimension();
IrisDimension imported = new IrisDimension();
imported.setDatapackImports(new KList<String>().qadd("https://modrinth.com/datapack/example"));
assertTrue(ModdedStudioHotloadService.hasDatapackImports(List.of(empty, imported)));
}
@Test
public void rejectsMissingOrEmptyDatapackImports() {
IrisDimension empty = new IrisDimension();
assertFalse(ModdedStudioHotloadService.hasDatapackImports(null));
assertFalse(ModdedStudioHotloadService.hasDatapackImports(List.of(empty)));
}
}