mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 20:48:54 +00:00
dwa
This commit is contained in:
+152
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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));
|
||||
}
|
||||
}
|
||||
+193
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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));
|
||||
}
|
||||
}
|
||||
+76
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+184
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -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));
|
||||
}
|
||||
}
|
||||
+35
@@ -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()));
|
||||
}
|
||||
}
|
||||
+30
@@ -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));
|
||||
}
|
||||
}
|
||||
+39
@@ -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));
|
||||
}
|
||||
}
|
||||
+165
@@ -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);
|
||||
}
|
||||
}
|
||||
+87
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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));
|
||||
}
|
||||
}
|
||||
+218
@@ -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;
|
||||
}
|
||||
}
|
||||
+215
-21
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -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));
|
||||
}
|
||||
}
|
||||
+81
@@ -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();
|
||||
}
|
||||
}
|
||||
+29
@@ -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)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user