Modded 26.3

- Fabric, Forge and NeoForge target Minecraft 26.3
- Fabric uses loader 0.19.5 with Fabric API modules from 0.161.0+26.3
- Forge uses 26.3-66.0.8 with ForgeGradle 7.0.40
- NeoForge uses 26.3.0.33-beta with ModDevGradle 2.0.147
- Modded forced datapacks use the 26.3 data fixer and pack format 121
- Biome mob spawns are written as vanilla overlays
- Stronghold ring biome searches use the natural biome stream
- Chunk generation mob spawns go through the loader spawn position event
- README and the bug template list the supported versions
This commit is contained in:
Brian Neumann-Fopiano
2026-09-29 04:11:57 -04:00
33 changed files with 339 additions and 94 deletions
+4 -2
View File
@@ -49,9 +49,11 @@ body:
id: mcversion
attributes:
label: Minecraft Version
description: What version of Minecraft is the server on? Iris 4.x targets 26.2 only; older versions are not supported.
description: What version of Minecraft is the server on? Iris 4.x supports 26.1.2 to 26.3 on Bukkit-family servers and 26.3 on Fabric, Forge and NeoForge; older versions are not supported.
options:
- '26.3'
- '26.2'
- '26.1.2'
- Other (unsupported)
validations:
required: true
@@ -59,7 +61,7 @@ body:
id: loaderversion
attributes:
label: Platform / Loader Version
description: Exact server or loader build (see console). For example "Paper 26.2-60", "Fabric Loader 0.19.3", "NeoForge 26.2.0.12-beta".
description: Exact server or loader build (see console). For example "Paper 26.3-133", "Fabric Loader 0.19.5", "NeoForge 26.3.0.33-beta".
placeholder: DO NOT SAY "LATEST"
validations:
required: true
+7 -7
View File
@@ -4,7 +4,7 @@ Iris is a world generation engine for Minecraft servers and mod loaders. It gene
biomes, caves, structures, objects, and entities from editable JSON packs, with a full in-game
studio authoring workflow. The same engine runs as a Bukkit-family plugin and as a Fabric, Forge,
or NeoForge server mod. Cross-platform generation targets deterministic parity for identical
artifacts, pack bytes, seeds, and test areas. The master branch targets Minecraft 26.2.
artifacts, pack bytes, seeds, and test areas. The master branch targets Minecraft 26.3.
# [Support](https://discord.gg/3xxPTpT) **|** [Documentation](https://github.com/VolmitSoftware/docs/blob/master/iris/00-overview.md) **|** [Git](https://github.com/IrisDimensions)
@@ -20,12 +20,12 @@ Canonical English is defined in the typed Java catalogs under `core/src/main/jav
| Platform | Artifact | Minecraft | Notes |
|---|---|---|---|
| Paper / Purpur / Leaf / Canvas | plugin jar | 26.1.2 - 26.2 | Full feature set |
| Folia | plugin jar | 26.1.2 - 26.2 | Region-safe scheduling throughout |
| Spigot / CraftBukkit | plugin jar | 26.1.2 - 26.2 | Managed `iris:*` creation and generation; exact vanilla-slot `/iris replace` is unavailable |
| Fabric | mod jar | 26.2 | Server worldgen + client HUD; requires Fabric Loader 0.19.3+ |
| Forge | mod jar | 26.2 | Server worldgen + client HUD; current target is Forge 26.2-65.1.1 |
| NeoForge | mod jar | 26.2 | Server worldgen + client HUD; current target is NeoForge 26.2.0.59 |
| Paper / Purpur / Leaf / Canvas | plugin jar | 26.1.2 - 26.3 | Full feature set |
| Folia | plugin jar | 26.1.2 - 26.3 | Region-safe scheduling throughout |
| Spigot / CraftBukkit | plugin jar | 26.1.2 - 26.3 | Managed `iris:*` creation and generation; exact vanilla-slot `/iris replace` is unavailable |
| Fabric | mod jar | 26.3 | Server worldgen + client HUD; requires Fabric Loader 0.19.5+ |
| Forge | mod jar | 26.3 | Server worldgen + client HUD; current target is Forge 26.3-66.0.8 |
| NeoForge | mod jar | 26.3 | Server worldgen + client HUD; current target is NeoForge 26.3.0.33-beta |
Java 25 is required on every platform.
@@ -13,6 +13,7 @@ import net.minecraft.data.registries.VanillaRegistries;
import net.minecraft.resources.RegistryOps;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.attribute.EnvironmentAttributes;
import net.minecraft.world.attribute.modifier.MobSpawnSettingsModifier;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.MobSpawnSettings;
@@ -53,5 +54,7 @@ public class CustomBiomeCodecTest {
assertEquals(2, decoded.count().minInclusive());
assertEquals(4, decoded.count().maxInclusive());
assertEquals(0x102030, biome.getWaterColor() & 0xFFFFFF);
assertEquals(MobSpawnSettingsModifier.overlay(),
biome.getAttributes().get(EnvironmentAttributes.NATURAL_MOB_SPAWNS).modifier());
}
}
+2 -2
View File
@@ -60,8 +60,8 @@ apply from: file('../../gradle/modded-generation-build-revision.gradle')
Properties rootProperties = new Properties()
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.1.2-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.3'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3'))
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.5'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT'))
Closure<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
@@ -70,7 +70,7 @@
}
],
"depends": {
"fabricloader": ">=0.19.3",
"fabricloader": ">=0.19.5",
"minecraft": "~${minecraftVersion}",
"java": ">=25"
}
@@ -0,0 +1,43 @@
package art.arcane.volmlib.nativelib.minecraft26_2.fabric;
import art.arcane.volmlib.nativelib.modded.NativeLoaderOptions;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.ServerLevelAccessor;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.function.BooleanSupplier;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NativeFabricSpawnPositionTest {
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void chunkGenerationSpawnsUseTheVanillaRulesAndObstructionChecks() {
NativeFabricLoader loader = new NativeFabricLoader(
new NativeLoaderOptions("irisworldgen", "treefeller", BooleanSupplier::getAsBoolean));
ServerLevelAccessor level = mock(ServerLevelAccessor.class);
Mob mob = mock(Mob.class);
when(mob.checkSpawnRules(level, EntitySpawnReason.CHUNK_GENERATION)).thenReturn(true);
when(mob.checkSpawnObstruction(level)).thenReturn(true);
assertTrue(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
when(mob.checkSpawnObstruction(level)).thenReturn(false);
assertFalse(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
when(mob.checkSpawnObstruction(level)).thenReturn(true);
when(mob.checkSpawnRules(level, EntitySpawnReason.CHUNK_GENERATION)).thenReturn(false);
assertFalse(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
}
}
+4 -4
View File
@@ -42,7 +42,7 @@ buildscript {
plugins {
id 'java'
id 'net.minecraftforge.gradle' version '7.0.31'
id 'net.minecraftforge.gradle' version '7.0.40'
alias(libs.plugins.shadow)
}
@@ -60,8 +60,8 @@ apply from: file('../../gradle/modded-generation-build-revision.gradle')
Properties rootProperties = new Properties()
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.1.2-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.2-65.1.1'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3'))
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.3-66.0.8'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT'))
Closure<String> loaderDisplayVersion = { String loaderVersion ->
String coordinatePrefix = "${minecraftVersion}-"
@@ -272,7 +272,7 @@ tasks.named('shadowJar', ShadowJar).configure {
}
}
manifest {
// FML 26.2 has no TOML mixin handling at all (ModFileParser reads neither [[mixins]] nor
// FML 26.3 has no TOML mixin handling at all (ModFileParser reads neither [[mixins]] nor
// accessTransformers on Forge). This manifest attribute is the only registration mechanism;
// do not move it into mods.toml.
attributes('MixinConfigs': 'volmlib.entity.mixins.json,volmlib.client.mixins.json')
@@ -1,5 +1,5 @@
modLoader = "javafml"
loaderVersion = "[65,66)"
loaderVersion = "[66,67)"
license = "GPL-3.0"
issueTrackerURL = "https://github.com/VolmitSoftware/Iris/issues"
@@ -16,7 +16,7 @@ logoFile = "assets/irisworldgen/icon.png"
[[dependencies.irisworldgen]]
modId = "forge"
mandatory = true
versionRange = "[65,66)"
versionRange = "[66,67)"
ordering = "NONE"
side = "BOTH"
@@ -0,0 +1,52 @@
package art.arcane.volmlib.nativelib.minecraft26_2.forge;
import art.arcane.volmlib.nativelib.modded.NativeLoaderOptions;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraftforge.common.util.Result;
import net.minecraftforge.event.entity.living.MobSpawnEvent;
import net.minecraftforge.eventbus.api.listener.EventListener;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NativeForgeSpawnPositionTest {
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void chunkGenerationSpawnsFireThePositionCheckEventAndHonorItsDenial() {
NativeForgeLoader loader = new NativeForgeLoader(
new NativeLoaderOptions("irisworldgen", "treefeller", BooleanSupplier::getAsBoolean));
ServerLevelAccessor level = mock(ServerLevelAccessor.class);
Mob mob = mock(Mob.class);
when(mob.checkSpawnRules(level, EntitySpawnReason.CHUNK_GENERATION)).thenReturn(true);
when(mob.checkSpawnObstruction(level)).thenReturn(true);
AtomicReference<EntitySpawnReason> observed = new AtomicReference<>();
EventListener listener = MobSpawnEvent.PositionCheck.BUS.addListener((MobSpawnEvent.PositionCheck event) -> {
observed.set(event.getSpawnReason());
event.setResult(Result.DENY);
});
try {
assertFalse(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
} finally {
MobSpawnEvent.PositionCheck.BUS.removeListener(listener);
}
assertEquals(EntitySpawnReason.CHUNK_GENERATION, observed.get());
assertTrue(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
}
}
@@ -44,6 +44,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeFeatureBiomeSourc
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeDimensionRuntime;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeGeneratorOwner;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeGeneratorHandle;
@@ -1011,6 +1012,11 @@ public final class IrisModdedChunkGenerator implements NativeGeneratorOwner, Nat
return ModdedEngineBootstrap.currentServer();
}
@Override
public NativeModdedLoader loader() {
return ModdedEngineBootstrap.loader();
}
@Override
public Engine current() {
return engine();
@@ -72,6 +72,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
private volatile BiomeHolderTable<H> visibleBiomeCache = new BiomeHolderTable<>();
private volatile BiomeHolderTable<H> structureBiomeCache = new BiomeHolderTable<>();
private volatile BiomeHolderTable<H> surfaceStructureBiomeCache = new BiomeHolderTable<>();
private volatile BiomeHolderTable<H> naturalStructureBiomeCache = new BiomeHolderTable<>();
private volatile RuntimeCallbacks runtime;
private volatile Set<String> possibleStructureBiomeKeys;
private volatile BiomeKeySets biomeKeySets;
@@ -94,6 +95,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
visibleBiomeCache = new BiomeHolderTable<>();
structureBiomeCache = new BiomeHolderTable<>();
surfaceStructureBiomeCache = new BiomeHolderTable<>();
naturalStructureBiomeCache = new BiomeHolderTable<>();
warnedUnresolvedBiomeKeys.clear();
possibleStructureBiomeKeys = null;
biomeKeySets = null;
@@ -104,6 +106,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
visibleBiomeCache.evictRuntime(runtimeIdentity);
structureBiomeCache.evictRuntime(runtimeIdentity);
surfaceStructureBiomeCache.evictRuntime(runtimeIdentity);
naturalStructureBiomeCache.evictRuntime(runtimeIdentity);
}
/**
@@ -791,11 +794,40 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
if (!isReady(engine)) {
throw new IllegalStateException("Iris structure biome lookup has no active engine runtime");
}
return getNoiseBiome(engine, quartX, quartY, quartZ, sampler);
return getNaturalStructureBiome(engine, quartX, quartZ);
}
}
}
// The structure state only samples biomes for the concentric-ring search, which reaches ~24k blocks out for
// strongholds. The natural surface answers there without planning hydrology tiles, the same as the Bukkit ring search.
private H getNaturalStructureBiome(Engine engine, int quartX, int quartZ) {
long key = packColumnKey(quartX, quartZ);
int runtimeIdentity = engine.getCacheID();
BiomeHolderTable<H> cache = naturalStructureBiomeCache;
H cached = cache.get(runtimeIdentity, key);
if (cached != null) {
return cached;
}
NativeBiomeSourceAccess.RegistryView<H> registry = biomeRegistry();
if (registry == null) {
throw new IllegalStateException("Iris structure biome lookup has no biome registry");
}
int blockX = quartX << 2;
int blockZ = quartZ << 2;
IrisBiome irisBiome = engine.getComplex().getNaturalTrueBiomeStream().get(blockX, blockZ);
if (irisBiome == null) {
throw new IllegalStateException("Iris returned no natural structure biome at block " + blockX + "," + blockZ);
}
H resolved = resolveHolder(registry, irisBiome.getStructureDerivativeKey());
if (resolved == null) {
throw new IllegalStateException("Iris natural structure biome derivative '"
+ irisBiome.getStructureDerivativeKey() + "' is not registered at block " + blockX + "," + blockZ);
}
cache.put(runtimeIdentity, key, resolved);
return resolved;
}
static long biomeResolutionSeed(long worldSeed, int blockX, int blockY, int blockZ) {
return worldSeed
^ ((long) blockX * 341873128712L)
@@ -315,11 +315,11 @@ public final class ModdedForcedDatapack {
throw new IllegalStateException("SHA-256 is unavailable", missing);
}
digest.update((HASH_SALT + '|' + ModdedEngineBootstrap.loader().platformName()
+ '|' + DataVersion.V26_2.getPackFormat() + '\n').getBytes(StandardCharsets.UTF_8));
+ '|' + DataVersion.V26_3.getPackFormat() + '\n').getBytes(StandardCharsets.UTF_8));
String inputFingerprint = IrisDatapackCompiler.computeInputFingerprint(
compilationPackRoots(),
List.of(),
DataVersion.V26_2.get(),
DataVersion.V26_3.get(),
false
);
digest.update(inputFingerprint.getBytes(StandardCharsets.UTF_8));
@@ -328,7 +328,7 @@ public final class ModdedForcedDatapack {
private static void writeStagedPack(Path stagingDirectory) throws IOException {
Map<String, KSet<String>> seenBiomes = new LinkedHashMap<>();
IDataFixer fixer = DataVersion.V26_2.get();
IDataFixer fixer = DataVersion.V26_3.get();
int packCount = 0;
KList<String> presetIds = new KList<>();
@@ -679,7 +679,7 @@ public final class ModdedForcedDatapack {
}
static void writePackMeta(Path packDirectory) throws IOException {
int packFormat = DataVersion.V26_2.getPackFormat();
int packFormat = DataVersion.V26_3.getPackFormat();
String json = "{\n"
+ " \"pack\": {\n"
+ " \"description\": \"Iris world generation biomes and dimension types for installed packs.\",\n"
@@ -55,7 +55,7 @@ final class WorldCheckDimensionContract {
}
static Dimension expectedDimensionContract(IrisDimension dimension) {
JSONObject json = new JSONObject(dimension.getDimensionType().toJson(DataVersion.V26_2.get()));
JSONObject json = new JSONObject(dimension.getDimensionType().toJson(DataVersion.V26_3.get()));
return new Dimension(
json.getInt("min_y"),
json.getInt("height"),
@@ -165,7 +165,7 @@ public final class ModdedDatapackCommands {
IrisDimension dimension = engine.getDimension();
String json;
try {
json = dimension.getDimensionType().toJson(DataVersion.V26_2.get());
json = dimension.getDimensionType().toJson(DataVersion.V26_3.get());
} catch (Throwable e) {
ModdedIrisLog.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_DIMENSION_TYPE_GENERATION_FAILED, MessageArgument.untrusted("value", level.name()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
@@ -187,7 +187,7 @@ public final class ModdedDatapackCommands {
}
File mcmeta = new File(worldDatapacksFolder(server), WORLD_PACK_NAME + "/pack.mcmeta");
int packFormat = DataVersion.V26_2.getPackFormat();
int packFormat = DataVersion.V26_3.getPackFormat();
String meta = "{\n"
+ " \"pack\": {\n"
+ " \"description\": \"Iris dimension types derived from the installed Iris packs.\",\n"
@@ -503,7 +503,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
/**
* A dedicated server with {@code pause-when-empty-seconds > 0} returns from
* {@code MinecraftServer#tickServer} before {@code tickChildren} once it has been empty for that
* long (26.2 only keeps {@code tickConnection} plus the task/chunk-poll window alive). That
* long (26.3 only keeps {@code tickConnection} plus the task/chunk-poll window alive). That
* freezes every per-tick Iris service - world manager, scheduler, protocol sync, pregen HUD - and
* the loader's own generation hooks for the whole job, and console pregen on a default
* server.properties is always empty. The guard zeroes the setting for the duration of the job
@@ -1,7 +1,7 @@
{
"pack": {
"description": "Iris client resources and mod data",
"min_format": [88, 0],
"max_format": [107, 1]
"min_format": [97, 1],
"max_format": [121, 0]
}
}
@@ -6,6 +6,7 @@ import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.random.Weighted;
import net.minecraft.util.random.WeightedList;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.level.biome.MobSpawnSettings;
import org.junit.Test;
@@ -24,10 +25,10 @@ public class IrisModdedChunkGeneratorSpawnTest {
@Test
public void explicitSpawnsReplaceMatchingVanillaTypesAndPreserveOthers() {
MobSpawnSettings.SpawnerData zombie = new MobSpawnSettings.SpawnerData(EntityTypes.ZOMBIE, 1, 4);
MobSpawnSettings.SpawnerData vanillaSlime = new MobSpawnSettings.SpawnerData(EntityTypes.SLIME, 1, 1);
MobSpawnSettings.SpawnerData explicitSlime = new MobSpawnSettings.SpawnerData(EntityTypes.SLIME, 2, 5);
MobSpawnSettings.SpawnerData explicitCow = new MobSpawnSettings.SpawnerData(EntityTypes.COW, 2, 4);
MobSpawnSettings.SpawnerData zombie = new MobSpawnSettings.SpawnerData(EntityTypes.ZOMBIE, UniformInt.of(1, 4));
MobSpawnSettings.SpawnerData vanillaSlime = new MobSpawnSettings.SpawnerData(EntityTypes.SLIME, UniformInt.of(1, 1));
MobSpawnSettings.SpawnerData explicitSlime = new MobSpawnSettings.SpawnerData(EntityTypes.SLIME, UniformInt.of(2, 5));
MobSpawnSettings.SpawnerData explicitCow = new MobSpawnSettings.SpawnerData(EntityTypes.COW, UniformInt.of(2, 4));
WeightedList<MobSpawnSettings.SpawnerData> vanilla = WeightedList.of(List.of(
new Weighted<>(zombie, 100),
new Weighted<>(vanillaSlime, 1)));
@@ -1,6 +1,12 @@
package art.arcane.iris.modded;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.GenerationSessionLease;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.volmlib.nativelib.terrain.NativeBiomeSourceAccess;
import art.arcane.volmlib.util.stream.ProceduralStream;
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
import org.junit.Test;
import java.util.ArrayList;
@@ -13,6 +19,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ModdedBiomePolicyTest {
@Test
@@ -52,6 +61,29 @@ public class ModdedBiomePolicyTest {
assertTrue(failure.getMessage().contains("example:missing"));
}
@Test
public void structureRingBiomesResolveFromTheNaturalSurfaceWithoutPlanningHydrology() throws Exception {
BiomeToken forest = new BiomeToken("minecraft:forest");
IrisBiome natural = mock(IrisBiome.class);
when(natural.getStructureDerivativeKey()).thenReturn(forest.key());
IrisComplex complex = mock(IrisComplex.class);
when(complex.getNaturalTrueBiomeStream()).thenReturn(
ProceduralStream.of((x, z) -> natural, Interpolated.of(value -> 0D, value -> null)));
when(complex.getTrueBiomeStream()).thenThrow(
new AssertionError("Stronghold ring lookups must not plan hydrology"));
Engine engine = mock(Engine.class);
when(engine.getComplex()).thenReturn(complex);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
ModdedBiomePolicy<BiomeToken, Object> policy = new ModdedBiomePolicy<>(
new Access(new ArrayList<>(List.of(forest))));
policy.bind(new ModdedBiomePolicy.RuntimeCallbacks(() -> engine, () -> engine, bound -> true,
() -> Set.of(forest.key()), bound -> Set.of(), () -> 0L));
assertSame(forest, policy.requiredStructureBiome(6000, 0, -6000, null));
assertSame(forest, policy.requiredStructureBiome(6000, -30, -6000, null));
}
private static ModdedBiomePolicy<BiomeToken, Object> policy(List<BiomeToken> entries,
AtomicReference<Set<String>> configured) {
ModdedBiomePolicy<BiomeToken, Object> policy = new ModdedBiomePolicy<>(new Access(entries));
@@ -79,7 +79,7 @@ public class ModdedDimensionTypeParityTest {
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.V26_2.get();
IDataFixer fixer = DataVersion.V26_3.get();
Path packDirectory = Files.createTempDirectory("iris-dimension-contracts");
KList<File> roots = new KList<>();
roots.add(packDirectory.toFile());
@@ -51,15 +51,15 @@ import static org.junit.Assert.assertTrue;
public class ModdedForcedDatapackTest {
@Test
public void metadataRetains262FormatWhenBukkitAlsoSupports263() throws IOException {
public void metadataUses263DataFormat() throws IOException {
Path packDirectory = Files.createTempDirectory("iris-modded-pack-format");
try {
ModdedForcedDatapack.writePackMeta(packDirectory);
JSONObject pack = new JSONObject(Files.readString(packDirectory.resolve("pack.mcmeta")))
.getJSONObject("pack");
assertEquals(107, pack.getInt("pack_format"));
assertEquals(107, pack.getInt("min_format"));
assertEquals(107, pack.getInt("max_format"));
assertEquals(121, pack.getInt("pack_format"));
assertEquals(121, pack.getInt("min_format"));
assertEquals(121, pack.getInt("max_format"));
} finally {
deleteTree(packDirectory);
}
@@ -9,6 +9,9 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedServerAccess;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedServerLevels;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
import org.junit.Rule;
import org.junit.Test;
@@ -33,7 +36,7 @@ public class ModdedPlatformPathsTest {
@Override
public String minecraftVersion() {
return "26.2";
return "26.3";
}
@Override
@@ -83,6 +86,11 @@ public class ModdedPlatformPathsTest {
public boolean canBreakBlock(ServerLevel level, ServerPlayer player, BlockPos position, BlockState state) {
return false;
}
@Override
public boolean checkSpawnPosition(Mob mob, ServerLevelAccessor level, EntitySpawnReason reason) {
return false;
}
});
}
@@ -14,16 +14,18 @@ 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.BiomeResolver;
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.AbstractSpreadingStructurePlacement;
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 org.junit.BeforeClass;
import org.junit.Test;
@@ -166,10 +168,10 @@ public class ModdedStructureSetFrequencyOverridesTest {
) {
return new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
AbstractSpreadingStructurePlacement.FrequencyReductionMethod.DEFAULT,
1F,
salt,
Optional.of(new StructurePlacement.ExclusionZone(exclusionTarget, 1)),
Optional.of(new AbstractSpreadingStructurePlacement.ExclusionZone(exclusionTarget, 1)),
spacing,
separation,
RandomSpreadType.LINEAR);
@@ -183,6 +185,7 @@ public class ModdedStructureSetFrequencyOverridesTest {
RandomState.class,
BiomeSource.class,
long.class,
ChunkPos.class,
long.class,
List.class);
constructor.setAccessible(true);
@@ -190,6 +193,7 @@ public class ModdedStructureSetFrequencyOverridesTest {
null,
new EmptyBiomeSource(),
1L,
ChunkPos.ZERO,
1L,
sets);
}
@@ -211,8 +215,8 @@ public class ModdedStructureSetFrequencyOverridesTest {
private static final class EmptyBiomeSource extends BiomeSource {
@Override
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
return null;
public BiomeResolver createResolver(Climate.Sampler sampler) {
return (x, y, z) -> null;
}
@Override
@@ -226,18 +230,14 @@ public class ModdedStructureSetFrequencyOverridesTest {
}
}
private static final class UnsupportedPlacement extends StructurePlacement {
private UnsupportedPlacement() {
super(Vec3i.ZERO, FrequencyReductionMethod.DEFAULT, 1F, 1, Optional.empty());
}
private static final class UnsupportedPlacement implements StructurePlacement {
@Override
protected boolean isPlacementChunk(ChunkGeneratorStructureState state, int x, int z) {
public boolean isStructureChunk(ChunkGeneratorStructureState state, int x, int z) {
return false;
}
@Override
public StructurePlacementType<?> type() {
public com.mojang.serialization.MapCodec<? extends StructurePlacement> codec() {
return null;
}
}
@@ -19,6 +19,7 @@ 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.SignTextSlot;
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
import net.minecraft.world.level.storage.TagValueInput;
import org.junit.BeforeClass;
@@ -61,9 +62,9 @@ public class ModdedTileParityTest {
sign.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING, registries, tile.nativeData().payload()));
assertEquals("Iris", sign.getFrontText().getMessage(0, false).getString());
assertEquals("parity", sign.getBackText().getMessage(3, false).getString());
assertEquals(DyeColor.BLUE, sign.getFrontText().getColor());
assertEquals("Iris", sign.getText(SignTextSlot.FRONT).getMessages(false).get(0).getString());
assertEquals("parity", sign.getText(SignTextSlot.BACK).getMessages(false).get(3).getString());
assertEquals(DyeColor.BLUE, sign.getText(SignTextSlot.FRONT).getColor());
}
@Test
@@ -12,8 +12,11 @@ import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
@@ -22,6 +25,8 @@ import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
@@ -42,6 +47,8 @@ public class ModdedWaterEntitySpawnTest {
state.initCache();
}
}
BuiltInRegistries.BLOCK.prepareTagReload(new TagLoader.LoadResult<>(Registries.BLOCK, Map.of(
BlockTags.BLOCKS_MOTION, List.of(Blocks.OAK_FENCE.builtInRegistryHolder())))).apply();
}
@Test
@@ -6,7 +6,11 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedPlatformWorld;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FallenTreeFeature;
import net.minecraft.world.level.levelgen.feature.HugeBrownMushroomFeature;
import net.minecraft.world.level.levelgen.feature.HugeRedMushroomFeature;
import net.minecraft.world.level.levelgen.feature.OreFeature;
import net.minecraft.world.level.levelgen.feature.TreeFeature;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -32,11 +36,11 @@ public class NativeStructureOperationsTest {
@Test
public void objectFeaturesMatchPaperGroups() {
assertEquals("trees", NativeStructureOperations.classifyFeature(Feature.TREE));
assertEquals("fallen_trees", NativeStructureOperations.classifyFeature(Feature.FALLEN_TREE));
assertEquals("mushrooms", NativeStructureOperations.classifyFeature(Feature.HUGE_BROWN_MUSHROOM));
assertEquals("mushrooms", NativeStructureOperations.classifyFeature(Feature.HUGE_RED_MUSHROOM));
assertNull(NativeStructureOperations.classifyFeature(Feature.ORE));
assertEquals("trees", NativeStructureOperations.classifyFeature(mock(TreeFeature.class)));
assertEquals("fallen_trees", NativeStructureOperations.classifyFeature(mock(FallenTreeFeature.class)));
assertEquals("mushrooms", NativeStructureOperations.classifyFeature(mock(HugeBrownMushroomFeature.class)));
assertEquals("mushrooms", NativeStructureOperations.classifyFeature(mock(HugeRedMushroomFeature.class)));
assertNull(NativeStructureOperations.classifyFeature(mock(OreFeature.class)));
}
@Test
+3 -3
View File
@@ -42,7 +42,7 @@ buildscript {
plugins {
id 'java'
id 'net.neoforged.moddev' version '2.0.141'
id 'net.neoforged.moddev' version '2.0.147'
alias(libs.plugins.shadow)
}
@@ -60,8 +60,8 @@ apply from: file('../../gradle/modded-generation-build-revision.gradle')
Properties rootProperties = new Properties()
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.1.2-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.2.0.59'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3'))
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.3.0.33-beta'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT'))
Closure<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
@@ -25,7 +25,7 @@ logoFile = "assets/irisworldgen/icon.png"
[[dependencies.irisworldgen]]
modId = "neoforge"
type = "required"
versionRange = "[26.2,26.3)"
versionRange = "[26.3,26.4)"
ordering = "NONE"
side = "BOTH"
@@ -0,0 +1,58 @@
package art.arcane.volmlib.nativelib.minecraft26_2.neoforge;
import art.arcane.volmlib.nativelib.modded.NativeLoaderOptions;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.ServerLevelAccessor;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.living.MobSpawnEvent;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NativeNeoForgeSpawnPositionTest {
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void chunkGenerationSpawnsFireThePositionCheckEventAndHonorItsDenial() {
NativeNeoForgeLoader loader = new NativeNeoForgeLoader(
new NativeLoaderOptions("irisworldgen", "treefeller", BooleanSupplier::getAsBoolean));
ServerLevelAccessor level = mock(ServerLevelAccessor.class);
Mob mob = mock(Mob.class);
when(mob.checkSpawnRules(level, EntitySpawnReason.CHUNK_GENERATION)).thenReturn(true);
when(mob.checkSpawnObstruction(level)).thenReturn(true);
AtomicBoolean deny = new AtomicBoolean(true);
AtomicReference<EntitySpawnReason> observed = new AtomicReference<>();
Consumer<MobSpawnEvent.PositionCheck> listener = (MobSpawnEvent.PositionCheck event) -> {
if (event.getEntity() == mob && deny.get()) {
observed.set(event.getSpawnType());
event.setResult(MobSpawnEvent.PositionCheck.Result.FAIL);
}
};
NeoForge.EVENT_BUS.addListener(MobSpawnEvent.PositionCheck.class, listener);
try {
assertFalse(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
assertEquals(EntitySpawnReason.CHUNK_GENERATION, observed.get());
deny.set(false);
assertTrue(loader.checkSpawnPosition(mob, level, EntitySpawnReason.CHUNK_GENERATION));
} finally {
NeoForge.EVENT_BUS.unregister(listener);
}
}
}
+5 -12
View File
@@ -70,12 +70,12 @@ project(':core') {
group = 'art.arcane'
version = rootProject.version
}
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.2')
// The Bukkit jar retains 26.1.2 and 26.2 alongside 26.3; loader jars stay 26.2-only.
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.3')
// The Bukkit jar retains 26.1.2 and 26.2 alongside 26.3; loader jars are 26.3-only.
String bukkitMinecraftRange = providers.gradleProperty('bukkitMinecraftRange').getOrElse('26.1.2-26.3')
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.3')
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.2-65.1.1')
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.2.0.59')
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.5')
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.3-66.0.8')
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.3.0.33-beta')
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
.get()
@@ -997,15 +997,8 @@ project(':adapters:bukkit:nms:v26_3_R1') {
TaskProvider<Sync> unpackHeadlessNativeSources = tasks.register('unpackHeadlessNativeSources', Sync) {
dependsOn(headlessNativeSources)
inputs.property('nativeTypes', headlessNativeTypes)
inputs.property('nativeVersion', '26.3')
from(provider { headlessNativeSources.collect { File artifact -> zipTree(artifact) } }) {
headlessNativeTypes.each { String type -> include("art/arcane/volmlib/nativelib/minecraft26_2/modded/${type}.java") }
include('versions/26.3/**')
eachFile { FileCopyDetails source ->
if (source.path.startsWith('versions/26.3/')) {
source.path = source.path.substring('versions/26.3/'.length())
}
}
}
from(rootProject.file('adapters/modded-common/src/main/java')) {
include('art/arcane/iris/modded/ModdedDecoratorHooks.java',
@@ -34,9 +34,12 @@ public final class DataFixerV263 extends DataFixerV1217 {
.put("max_inclusive", maximum));
}
}
// Vanilla writes spawns as an overlay. A bare value decodes as an override, which NeoForge biome modifiers refuse to edit.
attributes.put("minecraft:gameplay/natural_mob_spawns", new JSONObject()
.put("spawns_by_category", spawners)
.put("spawn_costs", fixed.getJSONObject("spawn_costs")));
.put("argument", new JSONObject()
.put("spawns_by_category", spawners)
.put("spawn_costs", fixed.getJSONObject("spawn_costs")))
.put("modifier", "overlay"));
Object probability = fixed.remove("creature_spawn_probability");
if (probability != null) {
attributes.put("minecraft:gameplay/creature_world_gen_spawn_probability", probability);
+5 -5
View File
@@ -22,10 +22,10 @@ org.gradle.caching=true
org.gradle.configureondemand=false
irisVersion=4.1.2-26.2
minecraftVersion=26.2
# Bukkit plugin api-version: lowest supported Minecraft release line so one artifact loads on 26.1.2 and 26.2
minecraftVersion=26.3
# Bukkit plugin api-version: lowest supported Minecraft release line so one artifact loads on 26.1.2 through 26.3
apiVersion=26.1
fabricLoaderVersion=0.19.3
forgeVersion=26.2-65.1.1
neoForgeVersion=26.2.0.59
fabricLoaderVersion=0.19.5
forgeVersion=26.3-66.0.8
neoForgeVersion=26.3.0.33-beta
volmLibCoordinate=com.github.VolmitSoftware.VolmLib:volmlib-shared:d24d28acddbf3555c0c30ac99569ebacd476ce27
+10 -10
View File
@@ -49,16 +49,16 @@ multiverseCore = "5.8.0"
craftengine = "26.8.2" # https://github.com/Xiao-MoMi/craft-engine/releases
# Fabric API (net.fabricmc.fabric-api) - each module is independently versioned
fabricApi-base = "2.0.4+ece063239e"
fabricApi-registrySync = "7.1.0+c7bd5b8e9e"
fabricApi-resourceLoader = "2.0.13+9edec1269e"
fabricApi-lifecycleEvents = "4.1.3+4575b05f9e"
fabricApi-commandApi = "3.1.0+00cb03469e"
fabricApi-eventsInteraction = "5.2.6+8f57f7ee9e"
fabricApi-networking = "6.3.3+72073ef09e"
fabricApi-rendering = "25.3.1+6988455e9e"
fabricApi-keyMapping = "2.0.5+e2bdee789e"
fabricApi-permission = "1.0.3+b7020ca29e"
fabricApi-base = "2.0.6+fcdff87f5d"
fabricApi-registrySync = "8.0.1+fcdff87f5d"
fabricApi-resourceLoader = "3.0.4+fcdff87f5d"
fabricApi-lifecycleEvents = "4.1.9+ffef5f675d"
fabricApi-commandApi = "3.1.2+fcdff87f5d"
fabricApi-eventsInteraction = "5.3.6+3434d6d95d"
fabricApi-networking = "6.3.8+fcdff87f5d"
fabricApi-rendering = "27.0.14+901a437c5d"
fabricApi-keyMapping = "2.0.8+3434d6d95d"
fabricApi-permission = "1.0.8+fcdff87f5d"
[libraries]
# Core Libraries
+1 -1
View File
@@ -9,7 +9,7 @@ String nativeLibraryCoordinate = providers.gradleProperty('volmLibCoordinate')
.getOrElse(nativeLibraryProperties.getProperty('volmLibCoordinate'))
String nativeSourceLoader = extensions.extraProperties.get('nativeSourceLoader')
String nativeSourceImplementation = 'minecraft26_2'
String nativeSourceMinecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.2')
String nativeSourceMinecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.3')
extensions.extraProperties.set('nativeSourceImplementation', nativeSourceImplementation)
dependencies.add(nativeMinecraftSources.name,
"com.github.VolmitSoftware.VolmLib:native-${nativeSourceImplementation}-sources:${nativeLibraryCoordinate.tokenize(':').last()}")