mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
content
This commit is contained in:
+19
-4
@@ -26,6 +26,7 @@ import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||
import art.arcane.iris.nativegen.NativeStructureStartInjector;
|
||||
import art.arcane.iris.nativegen.NativeStructureReferenceRepair;
|
||||
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
|
||||
@@ -284,8 +285,21 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
|
||||
@Override
|
||||
public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSets, RandomState randomState, long seed) {
|
||||
return ChunkGeneratorStructureState.createForNormal(
|
||||
ChunkGeneratorStructureState state = ChunkGeneratorStructureState.createForNormal(
|
||||
randomState, seed, structureBiomeSource.forStructureState(structureSets), structureSets);
|
||||
return ModdedStructureSetFrequencyOverrides.apply(state, configuredImportedStructures());
|
||||
}
|
||||
|
||||
private IrisImportedStructureControl configuredImportedStructures() {
|
||||
Engine current = engine;
|
||||
if (current != null && !current.isClosed() && !current.isClosing()) {
|
||||
IrisDimension dimension = current.getDimension();
|
||||
if (dimension != null && dimension.getImportedStructures() != null) {
|
||||
return dimension.getImportedStructures();
|
||||
}
|
||||
}
|
||||
IrisImportedStructureControl importedStructures = configuredPack().dimension().getImportedStructures();
|
||||
return importedStructures == null ? new IrisImportedStructureControl() : importedStructures;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -444,10 +458,11 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
return;
|
||||
}
|
||||
String remedy = integratedEnvironment()
|
||||
? "enable 'Generate Structures' for this world; Iris requires it, and individual structures "
|
||||
+ "are denied through importedStructures.disabled"
|
||||
? "enable 'Generate Structures' for this world; Iris requires it, then deny families through "
|
||||
+ "importedStructures.disabled or complete keys through importedStructures.disabledExact"
|
||||
: "set generate-structures=true in server.properties, restart the server, "
|
||||
+ "and deny individual structures through importedStructures.disabled";
|
||||
+ "then deny families through importedStructures.disabled or complete keys through "
|
||||
+ "importedStructures.disabledExact";
|
||||
throw new IllegalStateException("Iris generator '" + dimensionKey
|
||||
+ "' cannot bind while generate-structures=false; " + remedy);
|
||||
}
|
||||
|
||||
+2
-1
@@ -287,7 +287,8 @@ final class ModdedNativeStructureStage {
|
||||
+ " because generate-structures=false disables them outside the pack. That flag is fixed when "
|
||||
+ "the world is created (server.properties generate-structures, or the Generate Structures "
|
||||
+ "toggle in singleplayer), so it cannot be changed for this world: create a new world with "
|
||||
+ "structures enabled, and deny individual structures through importedStructures.disabled");
|
||||
+ "structures enabled, then deny families through importedStructures.disabled or complete keys "
|
||||
+ "through importedStructures.disabledExact");
|
||||
}
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
SectionPos sectionPos = SectionPos.of(chunkPos, world.getMinSectionY());
|
||||
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* 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.engine.framework.NativeStructureFrequencyScale;
|
||||
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderOwner;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
final class ModdedStructureSetFrequencyOverrides {
|
||||
private ModdedStructureSetFrequencyOverrides() {
|
||||
}
|
||||
|
||||
static ChunkGeneratorStructureState apply(
|
||||
ChunkGeneratorStructureState state,
|
||||
IrisImportedStructureControl importedStructures
|
||||
) {
|
||||
if (!importedStructures.hasFrequencyOverrides()) {
|
||||
return state;
|
||||
}
|
||||
List<Holder<StructureSet>> original = state.possibleStructureSets();
|
||||
List<Holder<StructureSet>> scaled = scaleSets(original, importedStructures);
|
||||
if (scaled == original) {
|
||||
return state;
|
||||
}
|
||||
Field possibleSetsField = declaredField(ChunkGeneratorStructureState.class, List.class);
|
||||
try {
|
||||
possibleSetsField.set(state, scaled);
|
||||
return state;
|
||||
} catch (IllegalAccessException error) {
|
||||
throw new IllegalStateException(
|
||||
"Could not apply native structure-set frequency overrides", error);
|
||||
}
|
||||
}
|
||||
|
||||
static List<Holder<StructureSet>> scaleSets(
|
||||
List<Holder<StructureSet>> structureSets,
|
||||
IrisImportedStructureControl importedStructures
|
||||
) {
|
||||
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
|
||||
for (Holder<StructureSet> holder : structureSets) {
|
||||
String key = structureSetKey(holder);
|
||||
if (key != null) {
|
||||
holdersByKey.putIfAbsent(key, holder);
|
||||
}
|
||||
}
|
||||
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity = new IdentityHashMap<>();
|
||||
List<Holder<StructureSet>> scaledSets = new ArrayList<>(structureSets.size());
|
||||
boolean changed = false;
|
||||
for (Holder<StructureSet> holder : structureSets) {
|
||||
Holder<StructureSet> scaled = scaleHolder(
|
||||
holder, importedStructures, holdersByKey, scaledByIdentity);
|
||||
scaledSets.add(scaled);
|
||||
changed |= scaled != holder;
|
||||
}
|
||||
return changed ? List.copyOf(scaledSets) : structureSets;
|
||||
}
|
||||
|
||||
private static Holder<StructureSet> scaleHolder(
|
||||
Holder<StructureSet> holder,
|
||||
IrisImportedStructureControl importedStructures,
|
||||
Map<String, Holder<StructureSet>> holdersByKey,
|
||||
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity
|
||||
) {
|
||||
if (scaledByIdentity.containsKey(holder)) {
|
||||
return scaledByIdentity.get(holder);
|
||||
}
|
||||
if (!dependsOnOverride(holder, importedStructures, holdersByKey)) {
|
||||
scaledByIdentity.put(holder, holder);
|
||||
return holder;
|
||||
}
|
||||
ResourceKey<StructureSet> holderKey = holder.unwrapKey().orElseThrow(() ->
|
||||
new IllegalStateException("An affected native structure-set exclusion graph has an unkeyed holder"));
|
||||
ScaledStructureSetHolder scaledHolder = new ScaledStructureSetHolder(holderKey);
|
||||
scaledByIdentity.put(holder, scaledHolder);
|
||||
|
||||
StructureSet originalSet = holder.value();
|
||||
StructurePlacement originalPlacement = originalSet.placement();
|
||||
Optional<StructurePlacement.ExclusionZone> originalZone = exclusionZone(originalPlacement);
|
||||
Optional<StructurePlacement.ExclusionZone> scaledZone = originalZone;
|
||||
if (originalZone.isPresent()) {
|
||||
Holder<StructureSet> target = canonicalHolder(
|
||||
originalZone.get().otherSet(), holdersByKey);
|
||||
Holder<StructureSet> scaledTarget = scaleHolder(
|
||||
target, importedStructures, holdersByKey, scaledByIdentity);
|
||||
if (scaledTarget != originalZone.get().otherSet()) {
|
||||
scaledZone = Optional.of(new StructurePlacement.ExclusionZone(
|
||||
scaledTarget, originalZone.get().chunkCount()));
|
||||
}
|
||||
}
|
||||
double multiplier = importedStructures.frequencyMultiplier(structureSetKey(holder));
|
||||
StructurePlacement scaledPlacement = multiplier == 1D && scaledZone.equals(originalZone)
|
||||
? originalPlacement
|
||||
: scalePlacement(originalPlacement, originalZone, scaledZone, multiplier);
|
||||
scaledHolder.bind(new StructureSet(originalSet.structures(), scaledPlacement));
|
||||
return scaledHolder;
|
||||
}
|
||||
|
||||
private static boolean dependsOnOverride(
|
||||
Holder<StructureSet> holder,
|
||||
IrisImportedStructureControl importedStructures,
|
||||
Map<String, Holder<StructureSet>> holdersByKey
|
||||
) {
|
||||
Set<Holder<StructureSet>> visited = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
Holder<StructureSet> current = holder;
|
||||
while (visited.add(current)) {
|
||||
if (importedStructures.frequencyMultiplier(structureSetKey(current)) != 1D) {
|
||||
return true;
|
||||
}
|
||||
Optional<StructurePlacement.ExclusionZone> zone =
|
||||
exclusionZone(current.value().placement());
|
||||
if (zone.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
current = canonicalHolder(zone.get().otherSet(), holdersByKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Holder<StructureSet> canonicalHolder(
|
||||
Holder<StructureSet> holder,
|
||||
Map<String, Holder<StructureSet>> holdersByKey
|
||||
) {
|
||||
String key = structureSetKey(holder);
|
||||
return key == null ? holder : holdersByKey.getOrDefault(key, holder);
|
||||
}
|
||||
|
||||
private static StructurePlacement scalePlacement(
|
||||
StructurePlacement placement,
|
||||
Optional<StructurePlacement.ExclusionZone> originalZone,
|
||||
Optional<StructurePlacement.ExclusionZone> scaledZone,
|
||||
double multiplier
|
||||
) {
|
||||
Vec3i locateOffset = (Vec3i) declaredFieldValue(
|
||||
StructurePlacement.class, placement, Vec3i.class);
|
||||
StructurePlacement.FrequencyReductionMethod reductionMethod =
|
||||
(StructurePlacement.FrequencyReductionMethod) declaredFieldValue(
|
||||
StructurePlacement.class,
|
||||
placement,
|
||||
StructurePlacement.FrequencyReductionMethod.class);
|
||||
float frequency = (float) declaredFieldValue(
|
||||
StructurePlacement.class, placement, float.class);
|
||||
int salt = (int) declaredFieldValue(
|
||||
StructurePlacement.class, placement, int.class);
|
||||
|
||||
if (placement.getClass() == RandomSpreadStructurePlacement.class) {
|
||||
RandomSpreadStructurePlacement randomSpread =
|
||||
(RandomSpreadStructurePlacement) placement;
|
||||
NativeStructureFrequencyScale scale = NativeStructureFrequencyScale.randomSpread(
|
||||
frequency, randomSpread.spacing(), randomSpread.separation(), multiplier);
|
||||
if (scale.frequency() == frequency
|
||||
&& scale.spacing() == randomSpread.spacing()
|
||||
&& scaledZone.equals(originalZone)) {
|
||||
return placement;
|
||||
}
|
||||
return new RandomSpreadStructurePlacement(
|
||||
locateOffset,
|
||||
reductionMethod,
|
||||
scale.frequency(),
|
||||
salt,
|
||||
scaledZone,
|
||||
scale.spacing(),
|
||||
randomSpread.separation(),
|
||||
randomSpread.spreadType());
|
||||
}
|
||||
if (placement.getClass() == ConcentricRingsStructurePlacement.class) {
|
||||
ConcentricRingsStructurePlacement rings =
|
||||
(ConcentricRingsStructurePlacement) placement;
|
||||
float scaledFrequency = NativeStructureFrequencyScale.probability(frequency, multiplier);
|
||||
if (scaledFrequency == frequency && scaledZone.equals(originalZone)) {
|
||||
return placement;
|
||||
}
|
||||
return new ConcentricRingsStructurePlacement(
|
||||
locateOffset,
|
||||
reductionMethod,
|
||||
scaledFrequency,
|
||||
salt,
|
||||
scaledZone,
|
||||
rings.distance(),
|
||||
rings.spread(),
|
||||
rings.count(),
|
||||
rings.preferredBiomes());
|
||||
}
|
||||
throw new IllegalStateException("Unsupported native structure placement: "
|
||||
+ placement.getClass().getName());
|
||||
}
|
||||
|
||||
private static Optional<StructurePlacement.ExclusionZone> exclusionZone(
|
||||
StructurePlacement placement
|
||||
) {
|
||||
Object value = declaredFieldValue(StructurePlacement.class, placement, Optional.class);
|
||||
if (value instanceof Optional<?> optional && (optional.isEmpty()
|
||||
|| optional.get() instanceof StructurePlacement.ExclusionZone)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<StructurePlacement.ExclusionZone> resolved =
|
||||
(Optional<StructurePlacement.ExclusionZone>) optional;
|
||||
return resolved;
|
||||
}
|
||||
throw new IllegalStateException("Could not read native structure exclusion zone from "
|
||||
+ placement.getClass().getName());
|
||||
}
|
||||
|
||||
private static String structureSetKey(Holder<StructureSet> holder) {
|
||||
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
|
||||
return key.map(resourceKey -> resourceKey.identifier().toString()).orElse(null);
|
||||
}
|
||||
|
||||
private static Object declaredFieldValue(
|
||||
Class<?> declaringType,
|
||||
Object target,
|
||||
Class<?> fieldType
|
||||
) {
|
||||
Field field = declaredField(declaringType, fieldType);
|
||||
try {
|
||||
return field.get(target);
|
||||
} catch (IllegalAccessException error) {
|
||||
throw new IllegalStateException("Could not read " + fieldType.getName()
|
||||
+ " field on " + declaringType.getName(), error);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field declaredField(Class<?> declaringType, Class<?> fieldType) {
|
||||
Field match = null;
|
||||
for (Field field : declaringType.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers()) || field.getType() != fieldType) {
|
||||
continue;
|
||||
}
|
||||
if (match != null) {
|
||||
throw new IllegalStateException("Ambiguous " + fieldType.getName() + " field on "
|
||||
+ declaringType.getName());
|
||||
}
|
||||
match = field;
|
||||
}
|
||||
if (match == null || !match.trySetAccessible()) {
|
||||
throw new IllegalStateException("Missing accessible " + fieldType.getName()
|
||||
+ " field on " + declaringType.getName());
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
private static final class ScaledStructureSetHolder extends Holder.Reference<StructureSet> {
|
||||
private ScaledStructureSetHolder(ResourceKey<StructureSet> key) {
|
||||
super(Type.STAND_ALONE, new HolderOwner<>() {
|
||||
}, key, null);
|
||||
}
|
||||
|
||||
private void bind(StructureSet structureSet) {
|
||||
bindValue(structureSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||
import art.arcane.iris.engine.object.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.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 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<StructureSet> complexes = structureSet(
|
||||
"minecraft:nether_complexes", 27, 4, 30084232);
|
||||
Holder<StructureSet> fossils = structureSet(
|
||||
"minecraft:nether_fossils", 2, 1, 14357921);
|
||||
ChunkGeneratorStructureState state = state(List.of(complexes, fossils));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("minecraft:nether_complexes")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
ChunkGeneratorStructureState returned =
|
||||
ModdedStructureSetFrequencyOverrides.apply(state, control);
|
||||
|
||||
assertSame(state, returned);
|
||||
List<Holder<StructureSet>> 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<StructureSet> custom = new BoundStructureSetHolder(
|
||||
"example:custom",
|
||||
new StructureSet(List.of(), new UnsupportedPlacement()));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("minecraft:nether_complexes")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
List<Holder<StructureSet>> scaled =
|
||||
ModdedStructureSetFrequencyOverrides.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<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("minecraft:nether_complexes")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
List<Holder<StructureSet>> scaled = ModdedStructureSetFrequencyOverrides.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<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("example:first")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
List<Holder<StructureSet>> scaled = ModdedStructureSetFrequencyOverrides.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<StructureSet> custom = new BoundStructureSetHolder(
|
||||
"example:custom",
|
||||
new StructureSet(List.of(), new CustomRandomSpreadPlacement()));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("example:custom")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
ModdedStructureSetFrequencyOverrides.scaleSets(List.of(custom), control));
|
||||
}
|
||||
|
||||
private static Holder<StructureSet> 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<StructureSet> 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<Holder<StructureSet>> sets
|
||||
) throws Exception {
|
||||
Constructor<ChunkGeneratorStructureState> 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<StructureSet> {
|
||||
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<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected com.mojang.serialization.MapCodec<? extends BiomeSource> codec() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected java.util.stream.Stream<Holder<Biome>> collectPossibleBiomes() {
|
||||
return HolderSet.<Biome>empty().stream();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UnsupportedPlacement extends StructurePlacement {
|
||||
private UnsupportedPlacement() {
|
||||
super(Vec3i.ZERO, FrequencyReductionMethod.DEFAULT, 1F, 1, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -53,6 +53,7 @@ public class NativeStructureFailureContractTest {
|
||||
assertTrue(error.getMessage().contains("overworld:overworld"));
|
||||
assertTrue(error.getMessage().contains("generate-structures=false"));
|
||||
assertTrue(error.getMessage().contains("importedStructures.disabled"));
|
||||
assertTrue(error.getMessage().contains("importedStructures.disabledExact"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user