From 4daae925c65aa39f7f6fb6644cc95190b2e0d709 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Fri, 16 Oct 2026 11:07:09 -0400 Subject: [PATCH] Added 26.1.2 and 26.2 mod targets - Mod targets 26.2 and 26.1.2 (inheriting 26.2) with Fabric 0.19.5, Forge 65.1.3 and 64.1.4, NeoForge 26.2.0.89 and 26.1.2.116 - The 26.3 target reads the minecraft26_3 native access rules - Per-version pack.mcmeta ranges for 26.2 and 26.1.2 - 26.2 and 26.1.2 test overlays and replacements for the pre-26.3 biome, spawn, carver and sign APIs - NeoForge compiles against the loader's own ASM while the mod jar keeps the bundled ASM --- .../forge/NativeForgeBiomeDefinitionTest.java | 101 +++++++ .../src/versions/26.1.2/resources/pack.mcmeta | 7 + .../26.1.2/test/source-replacements.json | 32 +++ .../src/versions/26.2/resources/pack.mcmeta | 7 + ...dedStructureSetFrequencyOverridesTest.java | 253 ++++++++++++++++++ .../26.2/test/source-replacements.json | 78 ++++++ adapters/neoforge/build.gradle | 7 +- .../NativeNeoForgeServerAccessTest.java | 89 ++++++ gradle/mod-targets.json | 52 +++- 9 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 adapters/forge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/forge/NativeForgeBiomeDefinitionTest.java create mode 100644 adapters/modded-common/src/versions/26.1.2/resources/pack.mcmeta create mode 100644 adapters/modded-common/src/versions/26.1.2/test/source-replacements.json create mode 100644 adapters/modded-common/src/versions/26.2/resources/pack.mcmeta create mode 100644 adapters/modded-common/src/versions/26.2/test/java/art/arcane/iris/modded/ModdedStructureSetFrequencyOverridesTest.java create mode 100644 adapters/modded-common/src/versions/26.2/test/source-replacements.json create mode 100644 adapters/neoforge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/neoforge/NativeNeoForgeServerAccessTest.java diff --git a/adapters/forge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/forge/NativeForgeBiomeDefinitionTest.java b/adapters/forge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/forge/NativeForgeBiomeDefinitionTest.java new file mode 100644 index 000000000..71d2bb272 --- /dev/null +++ b/adapters/forge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/forge/NativeForgeBiomeDefinitionTest.java @@ -0,0 +1,101 @@ +package art.arcane.volmlib.nativelib.minecraft26_2.forge; + +import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeRegistryDefinitions; +import art.arcane.volmlib.nativelib.modded.NativeLoaderOptions; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; +import net.minecraft.SharedConstants; +import net.minecraft.core.Holder; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.RegistrationInfo; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.Bootstrap; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraftforge.common.world.BiomeModifier; +import net.minecraftforge.common.world.ModifiableBiomeInfo; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.List; +import java.util.function.BooleanSupplier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class NativeForgeBiomeDefinitionTest { + private static final String BIOME_REGISTRY = "minecraft:worldgen/biome"; + private static final String BIOME_KEY = "iris:biomes/forge_modified"; + private static final String SOURCE_JSON = "{\"has_precipitation\":true,\"temperature\":0.8,\"downfall\":0.4," + + "\"effects\":{\"water_color\":\"#3f76e4\"},\"carvers\":[],\"features\":[]," + + "\"spawners\":{},\"spawn_costs\":{}}"; + + @BeforeClass + public static void bootstrap() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + } + + @Test + public void generatedBiomeDefinitionExcludesForgeBiomeModifierOutput() { + MappedRegistry features = new MappedRegistry<>(Registries.PLACED_FEATURE, Lifecycle.stable()); + Holder.Reference ore = features.register( + ResourceKey.create(Registries.PLACED_FEATURE, Identifier.parse("iris:modded_ore")), + new PlacedFeature(Holder.direct(mock(ConfiguredFeature.class)), List.of()), + RegistrationInfo.BUILT_IN); + features.freeze(); + MappedRegistry> carvers = new MappedRegistry<>(Registries.CONFIGURED_CARVER, Lifecycle.stable()); + carvers.freeze(); + MappedRegistry biomes = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable()); + RegistryAccess access = new RegistryAccess.ImmutableRegistryAccess(List.of(biomes, features, carvers)); + Biome source = Biome.DIRECT_CODEC.parse(RegistryOps.create(JsonOps.INSTANCE, access), + JsonParser.parseString(SOURCE_JSON)).getOrThrow(); + Holder.Reference biome = biomes.register( + ResourceKey.create(Registries.BIOME, Identifier.parse(BIOME_KEY)), source, RegistrationInfo.BUILT_IN); + biomes.freeze(); + biome.value().modifiableBiomeInfo().applyBiomeModifiers(biome, List.of(new ModdedContent(ore))); + NativeRegistryDefinitions definitions = new NativeRegistryDefinitions(() -> access, + new NativeForgeLoader(new NativeLoaderOptions("irisworldgen", "treefeller", + BooleanSupplier::getAsBoolean))::unmodifiedBiome); + + assertEquals(0.1F, biome.value().modifiableBiomeInfo().get().climateSettings().temperature(), 0.0F); + assertEquals(0x102030, biome.value().modifiableBiomeInfo().get().effects().waterColor() & 0xFFFFFF); + assertTrue(biome.value().getGenerationSettings().hasFeature(ore.value())); + assertTrue(biome.value().getMobSettings().getMobs(MobCategory.MONSTER).unwrap().stream() + .anyMatch(spawn -> spawn.value().type() == EntityTypes.ZOMBIE)); + assertEquals(JsonParser.parseString(definitions.canonicalDefinition(BIOME_REGISTRY, BIOME_KEY, SOURCE_JSON).value()), + JsonParser.parseString(definitions.generatedDefinition(BIOME_REGISTRY, BIOME_KEY).value())); + } + + private record ModdedContent(Holder ore) implements BiomeModifier { + @Override + public void modify(Holder biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) { + if (phase != Phase.ADD) { + return; + } + builder.getClimateSettings().setTemperature(0.1F); + builder.getSpecialEffects().waterColor(0x102030); + builder.getMobSpawnSettings().addSpawn(MobCategory.MONSTER, 5, + new MobSpawnSettings.SpawnerData(EntityTypes.ZOMBIE, 1, 2)); + builder.getGenerationSettings().addFeature(GenerationStep.Decoration.UNDERGROUND_ORES, ore); + } + + @Override + public MapCodec codec() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/adapters/modded-common/src/versions/26.1.2/resources/pack.mcmeta b/adapters/modded-common/src/versions/26.1.2/resources/pack.mcmeta new file mode 100644 index 000000000..f88341123 --- /dev/null +++ b/adapters/modded-common/src/versions/26.1.2/resources/pack.mcmeta @@ -0,0 +1,7 @@ +{ + "pack": { + "description": "Iris client resources and mod data", + "min_format": [84, 0], + "max_format": [101, 1] + } +} diff --git a/adapters/modded-common/src/versions/26.1.2/test/source-replacements.json b/adapters/modded-common/src/versions/26.1.2/test/source-replacements.json new file mode 100644 index 000000000..b2b3cefb5 --- /dev/null +++ b/adapters/modded-common/src/versions/26.1.2/test/source-replacements.json @@ -0,0 +1,32 @@ +{ + "global": [ + { + "pattern": "\\bEntityTypes\\b", + "replacement": "EntityType" + }, + { + "pattern": "\\bSpeleothemBlock\\b", + "replacement": "PointedDripstoneBlock" + }, + { + "pattern": "\\bSpeleothemThickness\\b", + "replacement": "DripstoneThickness" + } + ], + "files": { + "art/arcane/iris/modded/ModdedBlockResolutionContractTest.java": [ + { + "pattern": "Blocks\\.SULFUR_SPIKE, ", + "replacement": "" + }, + { + "pattern": "\"minecraft:sulfur_spike\", ", + "replacement": "" + }, + { + "pattern": "sulfurAndDripstoneTipsReceivePostLoadUpdates", + "replacement": "dripstoneTipsReceivePostLoadUpdates" + } + ] + } +} diff --git a/adapters/modded-common/src/versions/26.2/resources/pack.mcmeta b/adapters/modded-common/src/versions/26.2/resources/pack.mcmeta new file mode 100644 index 000000000..1189047ea --- /dev/null +++ b/adapters/modded-common/src/versions/26.2/resources/pack.mcmeta @@ -0,0 +1,7 @@ +{ + "pack": { + "description": "Iris client resources and mod data", + "min_format": [88, 0], + "max_format": [107, 1] + } +} diff --git a/adapters/modded-common/src/versions/26.2/test/java/art/arcane/iris/modded/ModdedStructureSetFrequencyOverridesTest.java b/adapters/modded-common/src/versions/26.2/test/java/art/arcane/iris/modded/ModdedStructureSetFrequencyOverridesTest.java new file mode 100644 index 000000000..93f2336dd --- /dev/null +++ b/adapters/modded-common/src/versions/26.2/test/java/art/arcane/iris/modded/ModdedStructureSetFrequencyOverridesTest.java @@ -0,0 +1,253 @@ +package art.arcane.iris.modded; + +import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeStructureSetFrequencyOverrides; + +import art.arcane.iris.structure.nativegen.IrisImportedStructureControl; +import art.arcane.iris.structure.placement.IrisStructureSetFrequencyOverride; +import art.arcane.volmlib.util.collection.KList; +import net.minecraft.SharedConstants; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderOwner; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Vec3i; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.Bootstrap; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType; +import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.StructurePlacementType; +import com.mojang.serialization.MapCodec; +import java.util.stream.Stream; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; + +public class ModdedStructureSetFrequencyOverridesTest { + @BeforeClass + public static void bootstrapMinecraftRegistries() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + } + + @Test + public void createStateReplacementScalesOnlyTheExactSetAndRetainsEntries() throws Exception { + Holder complexes = structureSet( + "minecraft:nether_complexes", 27, 4, 30084232); + Holder fossils = structureSet( + "minecraft:nether_fossils", 2, 1, 14357921); + ChunkGeneratorStructureState state = state(List.of(complexes, fossils)); + KList overrides = new KList<>(); + overrides.add(new IrisStructureSetFrequencyOverride() + .setStructureSet("minecraft:nether_complexes") + .setMultiplier(1.1D)); + IrisImportedStructureControl control = new IrisImportedStructureControl() + .setFrequencyOverrides(overrides); + + ChunkGeneratorStructureState returned = + NativeStructureSetFrequencyOverrides.apply(state, control); + + assertSame(state, returned); + List> scaledSets = state.possibleStructureSets(); + RandomSpreadStructurePlacement scaledComplexes = + (RandomSpreadStructurePlacement) scaledSets.get(0).value().placement(); + assertEquals(26, scaledComplexes.spacing()); + assertEquals(4, scaledComplexes.separation()); + assertSame(complexes.value().structures(), scaledSets.get(0).value().structures()); + assertSame(fossils, scaledSets.get(1)); + } + + @Test + public void unrelatedCustomPlacementTypeRemainsUntouched() { + Holder custom = new BoundStructureSetHolder( + "example:custom", + new StructureSet(List.of(), new UnsupportedPlacement())); + KList overrides = new KList<>(); + overrides.add(new IrisStructureSetFrequencyOverride() + .setStructureSet("minecraft:nether_complexes") + .setMultiplier(1.1D)); + IrisImportedStructureControl control = new IrisImportedStructureControl() + .setFrequencyOverrides(overrides); + + List> scaled = + NativeStructureSetFrequencyOverrides.scaleSets(List.of(custom), control); + + assertSame(custom, scaled.getFirst()); + } + + @Test + public void unrelatedExclusionCycleRemainsUntouched() { + BoundStructureSetHolder first = new BoundStructureSetHolder("example:first"); + BoundStructureSetHolder second = new BoundStructureSetHolder("example:second"); + first.bind(new StructureSet(List.of(), placement(32, 8, 1, second))); + second.bind(new StructureSet(List.of(), placement(40, 10, 2, first))); + KList overrides = new KList<>(); + overrides.add(new IrisStructureSetFrequencyOverride() + .setStructureSet("minecraft:nether_complexes") + .setMultiplier(1.1D)); + IrisImportedStructureControl control = new IrisImportedStructureControl() + .setFrequencyOverrides(overrides); + + List> scaled = NativeStructureSetFrequencyOverrides.scaleSets( + List.of(first, second), control); + + assertSame(first, scaled.get(0)); + assertSame(second, scaled.get(1)); + } + + @Test + public void affectedExclusionCycleUsesBoundScaledHolders() { + BoundStructureSetHolder first = new BoundStructureSetHolder("example:first"); + BoundStructureSetHolder second = new BoundStructureSetHolder("example:second"); + first.bind(new StructureSet(List.of(), placement(32, 8, 1, second))); + second.bind(new StructureSet(List.of(), placement(40, 10, 2, first))); + KList overrides = new KList<>(); + overrides.add(new IrisStructureSetFrequencyOverride() + .setStructureSet("example:first") + .setMultiplier(1.1D)); + IrisImportedStructureControl control = new IrisImportedStructureControl() + .setFrequencyOverrides(overrides); + + List> scaled = NativeStructureSetFrequencyOverrides.scaleSets( + List.of(first, second), control); + + assertEquals(31, ((RandomSpreadStructurePlacement) + scaled.get(0).value().placement()).spacing()); + assertEquals(40, ((RandomSpreadStructurePlacement) + scaled.get(1).value().placement()).spacing()); + } + + @Test + public void affectedRandomSpreadSubclassFailsInsteadOfLosingSubtypeBehavior() { + Holder custom = new BoundStructureSetHolder( + "example:custom", + new StructureSet(List.of(), new CustomRandomSpreadPlacement())); + KList overrides = new KList<>(); + overrides.add(new IrisStructureSetFrequencyOverride() + .setStructureSet("example:custom") + .setMultiplier(1.1D)); + IrisImportedStructureControl control = new IrisImportedStructureControl() + .setFrequencyOverrides(overrides); + + assertThrows(IllegalStateException.class, () -> + NativeStructureSetFrequencyOverrides.scaleSets(List.of(custom), control)); + } + + private static Holder structureSet( + String key, + int spacing, + int separation, + int salt + ) { + StructureSet value = new StructureSet( + List.of(), + new RandomSpreadStructurePlacement( + spacing, separation, RandomSpreadType.LINEAR, salt)); + return new BoundStructureSetHolder(key, value); + } + + private static RandomSpreadStructurePlacement placement( + int spacing, + int separation, + int salt, + Holder exclusionTarget + ) { + return new RandomSpreadStructurePlacement( + Vec3i.ZERO, + StructurePlacement.FrequencyReductionMethod.DEFAULT, + 1F, + salt, + Optional.of(new StructurePlacement.ExclusionZone(exclusionTarget, 1)), + spacing, + separation, + RandomSpreadType.LINEAR); + } + + private static ChunkGeneratorStructureState state( + List> sets + ) throws Exception { + Constructor constructor = + ChunkGeneratorStructureState.class.getDeclaredConstructor( + RandomState.class, + BiomeSource.class, + long.class, + long.class, + List.class); + constructor.setAccessible(true); + return constructor.newInstance( + null, + new EmptyBiomeSource(), + 1L, + 1L, + sets); + } + + private static final class BoundStructureSetHolder extends Holder.Reference { + private BoundStructureSetHolder(String key) { + this(key, null); + } + + private BoundStructureSetHolder(String key, StructureSet value) { + super(Type.STAND_ALONE, new HolderOwner<>() { + }, ResourceKey.create(Registries.STRUCTURE_SET, Identifier.parse(key)), value); + } + + private void bind(StructureSet value) { + bindValue(value); + } + } + + private static final class EmptyBiomeSource extends BiomeSource { + @Override + public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { + return null; + } + + @Override + protected MapCodec codec() { + throw new UnsupportedOperationException(); + } + + @Override + protected Stream> collectPossibleBiomes() { + return HolderSet.empty().stream(); + } + } + + private static final class UnsupportedPlacement extends StructurePlacement { + private UnsupportedPlacement() { + super(Vec3i.ZERO, FrequencyReductionMethod.DEFAULT, 1F, 0, Optional.empty()); + } + + @Override + protected boolean isPlacementChunk(ChunkGeneratorStructureState state, int x, int z) { + return false; + } + + @Override + public StructurePlacementType type() { + return null; + } + } + + private static final class CustomRandomSpreadPlacement extends RandomSpreadStructurePlacement { + private CustomRandomSpreadPlacement() { + super(27, 4, RandomSpreadType.LINEAR, 30084232); + } + } +} diff --git a/adapters/modded-common/src/versions/26.2/test/source-replacements.json b/adapters/modded-common/src/versions/26.2/test/source-replacements.json new file mode 100644 index 000000000..d5882728b --- /dev/null +++ b/adapters/modded-common/src/versions/26.2/test/source-replacements.json @@ -0,0 +1,78 @@ +{ + "global": [ + { + "pattern": "new MobSpawnSettings\\.SpawnerData\\((EntityTypes\\.[A-Z_]+), UniformInt\\.of\\((\\d+), (\\d+)\\)\\)", + "replacement": "new MobSpawnSettings.SpawnerData($1, $2, $3)" + } + ], + "files": { + "art/arcane/volmlib/nativelib/minecraft26_2/fabric/NativeFabricBiomeDefinitionTest.java": [ + { + "pattern": "import net\\.minecraft\\.world\\.level\\.levelgen\\.carver\\.WorldCarver;", + "replacement": "import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;" + }, + { + "pattern": "import net\\.minecraft\\.world\\.level\\.levelgen\\.feature\\.Feature;", + "replacement": "import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;" + }, + { + "pattern": "mock\\(Feature\\.class\\)", + "replacement": "mock(ConfiguredFeature.class)" + }, + { + "pattern": "MappedRegistry carvers = new MappedRegistry<>\\(Registries\\.CARVER,", + "replacement": "MappedRegistry> carvers = new MappedRegistry<>(Registries.CONFIGURED_CARVER," + }, + { + "pattern": "\\\\\"features\\\\\":\\[\\]\\}\";", + "replacement": "\\\\\"features\\\\\":[],\\\\\"spawners\\\\\":{},\\\\\"spawn_costs\\\\\":{}}\";" + } + ], + "art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeWorldgenScanTest.java": [ + { + "pattern": "import net\\.minecraft\\.world\\.level\\.levelgen\\.carver\\.WorldCarver;", + "replacement": "import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;" + }, + { + "pattern": "import net\\.minecraft\\.world\\.level\\.levelgen\\.feature\\.Feature;", + "replacement": "import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;" + }, + { + "pattern": "mock\\(Feature\\.class\\)", + "replacement": "mock(ConfiguredFeature.class)" + }, + { + "pattern": "MappedRegistry carvers = new MappedRegistry<>\\(Registries\\.CARVER,", + "replacement": "MappedRegistry> carvers = new MappedRegistry<>(Registries.CONFIGURED_CARVER," + }, + { + "pattern": "\\]\\]\\}\";", + "replacement": "]],\\\\\"spawners\\\\\":{},\\\\\"spawn_costs\\\\\":{}}\";" + } + ], + "art/arcane/iris/modded/ModdedTileParityTest.java": [ + { + "pattern": "import net\\.minecraft\\.world\\.level\\.block\\.entity\\.SignTextSlot;\\n", + "replacement": "" + }, + { + "pattern": "\\.getText\\(SignTextSlot\\.FRONT\\)", + "replacement": ".getFrontText()" + }, + { + "pattern": "\\.getText\\(SignTextSlot\\.BACK\\)", + "replacement": ".getBackText()" + }, + { + "pattern": "\\.getMessages\\(false\\)\\.get\\((\\d+)\\)", + "replacement": ".getMessages(false)[$1]" + } + ], + "art/arcane/iris/modded/ModdedWaterEntitySpawnTest.java": [ + { + "pattern": "(?ms)^ BuiltInRegistries\\.BLOCK\\.prepareTagReload\\(new TagLoader\\.LoadResult<>.*?\\)\\)\\)\\)\\.apply\\(\\);\\n", + "replacement": "" + } + ] + } +} diff --git a/adapters/neoforge/build.gradle b/adapters/neoforge/build.gradle index 7343c7031..a9bc3cda1 100644 --- a/adapters/neoforge/build.gradle +++ b/adapters/neoforge/build.gradle @@ -168,13 +168,18 @@ dependencies { "art.arcane:core:${irisVersion}".toString(), "art.arcane:spi:${irisVersion}".toString(), libs.paralithic, - libs.asm, libs.lru, libs.caffeine, 'com.fulcrumgenomics:jlibdeflate:0.2.0', libs.dom4j, libs.jaxen ] + add('bundle', libs.asm) + add('devBundle', 'org.ow2.asm:asm') { + version { + prefer(libs.versions.asm.get()) + } + } shared.each { Object notation -> ['bundle', 'devBundle'].each { String configuration -> add(configuration, notation) { diff --git a/adapters/neoforge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/neoforge/NativeNeoForgeServerAccessTest.java b/adapters/neoforge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/neoforge/NativeNeoForgeServerAccessTest.java new file mode 100644 index 000000000..e899c97ff --- /dev/null +++ b/adapters/neoforge/src/versions/26.2/test/java/art/arcane/volmlib/nativelib/minecraft26_2/neoforge/NativeNeoForgeServerAccessTest.java @@ -0,0 +1,89 @@ +package art.arcane.volmlib.nativelib.minecraft26_2.neoforge; + +import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedServerAccess; +import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedServerLevels; +import art.arcane.volmlib.nativelib.modded.NativeLoaderOptions; +import net.minecraft.SharedConstants; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.Bootstrap; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.function.BooleanSupplier; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NativeNeoForgeServerAccessTest { + private static final ResourceKey RUNTIME_DIMENSION = + ResourceKey.create(Registries.DIMENSION, Identifier.fromNamespaceAndPath("irisworldgen", "runtime")); + + @BeforeClass + public static void bootstrap() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + } + + @After + public void forgetLevelSnapshot() { + ModdedServerLevels.forget(); + } + + @Test + public void runtimeLevelsJoinAndLeaveTheServerTickView() throws ReflectiveOperationException { + ModdedServerAccess access = new NativeNeoForgeLoader( + new NativeLoaderOptions("irisworldgen", "treefeller", BooleanSupplier::getAsBoolean)).serverAccess(); + MinecraftServer server = serverWithLiveLevelMap(); + ServerLevel level = mock(ServerLevel.class); + when(level.dimension()).thenReturn(RUNTIME_DIMENSION); + + assertNull(access.putLevelIfAbsent(server, RUNTIME_DIMENSION, level)); + assertTrue(access.hasLevel(server, RUNTIME_DIMENSION)); + assertTrue(ticks(server, level)); + assertSame(level, ModdedServerLevels.level(server, RUNTIME_DIMENSION)); + + assertSame(level, access.removeLevel(server, RUNTIME_DIMENSION)); + assertFalse(access.hasLevel(server, RUNTIME_DIMENSION)); + assertFalse(ticks(server, level)); + assertNull(ModdedServerLevels.level(server, RUNTIME_DIMENSION)); + + assertNull(access.putLevel(server, RUNTIME_DIMENSION, level)); + assertTrue(ticks(server, level)); + assertSame(level, ModdedServerLevels.level(server, RUNTIME_DIMENSION)); + } + + private static MinecraftServer serverWithLiveLevelMap() throws ReflectiveOperationException { + MinecraftServer server = mock(MinecraftServer.class); + Field levels = MinecraftServer.class.getDeclaredField("levels"); + levels.setAccessible(true); + levels.set(server, new LinkedHashMap, ServerLevel>()); + doCallRealMethod().when(server).getAllLevels(); + doCallRealMethod().when(server).markWorldsDirty(); + return server; + } + + private static boolean ticks(MinecraftServer server, ServerLevel expected) throws ReflectiveOperationException { + Method worldArray = MinecraftServer.class.getDeclaredMethod("getWorldArray"); + worldArray.setAccessible(true); + for (ServerLevel level : (ServerLevel[]) worldArray.invoke(server)) { + if (level == expected) { + return true; + } + } + return false; + } +} diff --git a/gradle/mod-targets.json b/gradle/mod-targets.json index e1df32669..3d6209e01 100644 --- a/gradle/mod-targets.json +++ b/gradle/mod-targets.json @@ -22,6 +22,56 @@ "fabricPermissions": "0.3.1", "slf4j": "2.0.9" }, + "26.1.2": { + "java": 25, + "fabricRemap": false, + "fabricLoader": "0.19.5", + "forge": "26.1.2-64.1.4", + "forgeRange": "[64,65)", + "neoForge": "26.1.2.116", + "neoForgeRange": "[26.1.2,26.2)", + "nativeAccess": "minecraft26_1_2", + "overlays": [ + "26.2", + "26.1.2" + ], + "fabricModules": { + "fabric-api-base": "2.0.3+ece063234c", + "fabric-registry-sync-v0": "7.1.1+2fa62b4e4c", + "fabric-resource-loader-v1": "2.0.10+7c44c7324c", + "fabric-lifecycle-events-v1": "4.1.1+df84eb3d4c", + "fabric-command-api-v2": "3.0.5+e2bdee784c", + "fabric-events-interaction-v0": "5.2.3+07b380be4c", + "fabric-networking-api-v1": "6.3.2+a217cccf4c", + "fabric-rendering-v1": "23.3.1+e9207d814c", + "fabric-key-mapping-api-v1": "2.0.4+e2bdee784c", + "fabric-permission-api-v1": "1.0.3+5730ecca4c" + }, + "slf4j": "2.0.17" + }, + "26.2": { + "java": 25, + "fabricRemap": false, + "fabricLoader": "0.19.5", + "forge": "26.2-65.1.3", + "forgeRange": "[65,66)", + "neoForge": "26.2.0.89", + "neoForgeRange": "[26.2,26.3)", + "nativeAccess": "minecraft26_2", + "fabricModules": { + "fabric-api-base": "2.0.4+ece063239e", + "fabric-registry-sync-v0": "7.1.1+c7bd5b8e9e", + "fabric-resource-loader-v1": "2.0.13+9edec1269e", + "fabric-lifecycle-events-v1": "4.1.4+29b6eb019e", + "fabric-command-api-v2": "3.1.0+00cb03469e", + "fabric-events-interaction-v0": "5.2.8+515ac5339e", + "fabric-networking-api-v1": "6.3.4+2989c6a09e", + "fabric-rendering-v1": "25.3.3+515ac5339e", + "fabric-key-mapping-api-v1": "2.0.5+e2bdee789e", + "fabric-permission-api-v1": "1.0.5+e442d8d09e" + }, + "slf4j": "2.0.17" + }, "26.3": { "java": 25, "fabricRemap": false, @@ -30,7 +80,7 @@ "forgeRange": "[66,67)", "neoForge": "26.3.0.83", "neoForgeRange": "[26.3.0.65-beta,26.4)", - "nativeAccess": "minecraft26_2", + "nativeAccess": "minecraft26_3", "slf4j": "2.0.17" } }