Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,7 @@ project(':forge') {
'--mod', 'custom_transformtype_test',
'--mod', 'data_pack_registries_test',
'--mod', 'biome_modifiers_test',
'--mod', 'structure_modifiers_test',
'--output', rootProject.file('src/generated_test/resources/'),
'--existing', sourceSets.main.resources.srcDirs[0],
'--existing', sourceSets.test.resources.srcDirs[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
return;
}

@@ -266,7 +_,7 @@
}

private static WeightedRandomList<MobSpawnSettings.SpawnerData> m_220443_(ServerLevel p_220444_, StructureManager p_220445_, ChunkGenerator p_220446_, MobCategory p_220447_, BlockPos p_220448_, @Nullable Holder<Biome> p_220449_) {
- return m_220455_(p_220448_, p_220444_, p_220447_, p_220445_) ? NetherFortressStructure.f_228517_ : p_220446_.m_223133_(p_220449_ != null ? p_220449_ : p_220444_.m_204166_(p_220448_), p_220445_, p_220447_, p_220448_);
+ return m_220455_(p_220448_, p_220444_, p_220447_, p_220445_) ? p_220445_.m_220521_().m_175515_(Registry.f_235725_).m_123013_(BuiltinStructures.f_209859_).m_226612_().get(MobCategory.MONSTER).f_210044_() : p_220446_.m_223133_(p_220449_ != null ? p_220449_ : p_220444_.m_204166_(p_220448_), p_220445_, p_220447_, p_220448_);
}

public static boolean m_220455_(BlockPos p_220456_, ServerLevel p_220457_, MobCategory p_220458_, StructureManager p_220459_) {
@@ -305,6 +_,13 @@
if (p_47052_ == SpawnPlacements.Type.NO_RESTRICTIONS) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--- a/net/minecraft/world/level/levelgen/structure/Structure.java
+++ b/net/minecraft/world/level/levelgen/structure/Structure.java
@@ -40,11 +_,12 @@
public abstract class Structure {
public static final Codec<Structure> f_226553_ = Registry.f_235740_.m_194605_().dispatch(Structure::m_213658_, StructureType::m_226884_);
public static final Codec<Holder<Structure>> f_226554_ = RegistryFileCodec.m_135589_(Registry.f_235725_, f_226553_);
- protected final Structure.StructureSettings f_226555_;
+ //Forge: Make this field private so that the redirect coremod can target it
+ private final Structure.StructureSettings f_226555_;

public static <S extends Structure> RecordCodecBuilder<S, Structure.StructureSettings> m_226567_(RecordCodecBuilder.Instance<S> p_226568_) {
return Structure.StructureSettings.f_226688_.forGetter((p_226595_) -> {
- return p_226595_.f_226555_;
+ return p_226595_.modifiableStructureInfo().getOriginalStructureInfo().structureSettings(); // FORGE: Patch codec to ignore field redirect coremods.
});
}

@@ -56,6 +_,7 @@

protected Structure(Structure.StructureSettings p_226558_) {
this.f_226555_ = p_226558_;
+ this.modifiableStructureInfo = new net.minecraftforge.common.world.ModifiableStructureInfo(new net.minecraftforge.common.world.ModifiableStructureInfo.StructureInfo(p_226558_)); // FORGE: cache original structure info on construction so we can bypass our field read coremods where necessary
}

public HolderSet<Biome> m_226559_() {
@@ -149,6 +_,29 @@
public abstract Optional<Structure.GenerationStub> m_214086_(Structure.GenerationContext p_226571_);

public abstract StructureType<?> m_213658_();
+
+ // FORGE START
+
+ private final net.minecraftforge.common.world.ModifiableStructureInfo modifiableStructureInfo;
+
+ /**
+ * {@return Cache of original structure data and structure data modified by structure modifiers}
+ * Modified structure data is set by server after datapacks and serverconfigs load.
+ * Settings field reads are coremodded to redirect to this.
+ **/
+ public net.minecraftforge.common.world.ModifiableStructureInfo modifiableStructureInfo()
+ {
+ return this.modifiableStructureInfo;
+ }
+
+ /**
+ * {@return The structure's settings, with modifications if called after modifiers are applied in server init.}
+ */
+ public StructureSettings getModifiedStructureSettings() {
+ return this.modifiableStructureInfo().get().structureSettings();
+ }
+
+ // FORGE END

public static record GenerationContext(RegistryAccess f_226621_, ChunkGenerator f_226622_, BiomeSource f_226623_, RandomState f_226624_, StructureTemplateManager f_226625_, WorldgenRandom f_226626_, long f_226627_, ChunkPos f_226628_, LevelHeightAccessor f_226629_, Predicate<Holder<Biome>> f_226630_) {
public GenerationContext(RegistryAccess p_226632_, ChunkGenerator p_226633_, BiomeSource p_226634_, RandomState p_226635_, StructureTemplateManager p_226636_, long p_226637_, ChunkPos p_226638_, LevelHeightAccessor p_226639_, Predicate<Holder<Biome>> p_226640_) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "structure_modifiers_test:test",
"category": "monster",
"spawn": {
"type": "minecraft:wither_skeleton",
"maxCount": 15,
"minCount": 5,
"weight": 100
},
"structures": "minecraft:stronghold"
}
8 changes: 8 additions & 0 deletions src/main/java/net/minecraftforge/common/ForgeMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import net.minecraftforge.common.loot.LootTableIdCondition;
import net.minecraftforge.common.world.BiomeModifier;
import net.minecraftforge.common.world.NoneBiomeModifier;
import net.minecraftforge.common.world.NoneStructureModifier;
import net.minecraftforge.common.world.StructureModifier;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fluids.FluidAttributes;
import net.minecraftforge.fluids.ForgeFlowingFluid;
Expand Down Expand Up @@ -93,6 +95,7 @@ public class ForgeMod
private static final DeferredRegister<Attribute> ATTRIBUTES = DeferredRegister.create(ForgeRegistries.Keys.ATTRIBUTES, "forge");
private static final DeferredRegister<ArgumentTypeInfo<?, ?>> COMMAND_ARGUMENT_TYPES = DeferredRegister.create(Registry.COMMAND_ARGUMENT_TYPE_REGISTRY, "forge");
private static final DeferredRegister<Codec<? extends BiomeModifier>> BIOME_MODIFIER_SERIALIZERS = DeferredRegister.create(ForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS, "forge");
private static final DeferredRegister<Codec<? extends StructureModifier>> STRUCTURE_MODIFIER_SERIALIZERS = DeferredRegister.create(ForgeRegistries.Keys.STRUCTURE_MODIFIER_SERIALIZERS, "forge");

@SuppressWarnings({ "unchecked", "rawtypes" })
private static final RegistryObject<EnumArgument.Info> ENUM_COMMAND_ARGUMENT_TYPE = COMMAND_ARGUMENT_TYPES.register("enum", () ->
Expand Down Expand Up @@ -130,6 +133,10 @@ public class ForgeMod
* Noop biome modifier. Can be used in a biome modifier json with "type": "forge:none".
*/
public static final RegistryObject<Codec<NoneBiomeModifier>> NONE_BIOME_MODIFIER_TYPE = BIOME_MODIFIER_SERIALIZERS.register("none", () -> Codec.unit(NoneBiomeModifier.INSTANCE));
/**
* Noop structure modifier. Can be used in a structure modifier json with "type": "forge:none".
*/
public static final RegistryObject<Codec<NoneStructureModifier>> NONE_STRUCTURE_MODIFIER_TYPE = STRUCTURE_MODIFIER_SERIALIZERS.register("none", () -> Codec.unit(NoneStructureModifier.INSTANCE));


private static boolean enableMilkFluid = false;
Expand Down Expand Up @@ -177,6 +184,7 @@ public ForgeMod()
ATTRIBUTES.register(modEventBus);
COMMAND_ARGUMENT_TYPES.register(modEventBus);
BIOME_MODIFIER_SERIALIZERS.register(modEventBus);
STRUCTURE_MODIFIER_SERIALIZERS.register(modEventBus);
MinecraftForge.EVENT_BUS.addListener(this::serverStopping);
MinecraftForge.EVENT_BUS.addListener(this::missingSoundMapping);
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ForgeConfig.clientSpec);
Expand Down
130 changes: 130 additions & 0 deletions src/main/java/net/minecraftforge/common/data/JsonCodecProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.minecraftforge.common.data;

import com.mojang.logging.LogUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;

import com.google.gson.JsonElement;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.JsonOps;

import java.util.function.BiConsumer;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraftforge.common.data.ExistingFileHelper.ResourceType;
import net.minecraftforge.forge.event.lifecycle.GatherDataEvent;
import org.slf4j.Logger;

/**
* <p>Dataprovider for using a Codec to generate jsons.
* Path names for jsons are derived from the given registry folder and each entry's namespaced id, in the format:</p>
* <pre>
* {@code <assets/data>/entryid/registryfolder/entrypath.json }
* </pre>
*
* @param <T> the type of thing being generated.
*/
public final class JsonCodecProvider<T> implements DataProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
protected final DataGenerator dataGenerator;
protected final ExistingFileHelper existingFileHelper;
protected final String modid;
protected final DynamicOps<JsonElement> dynamicOps;
protected final PackType packType;
protected final String directory;
protected final Codec<T> codec;
protected final Map<ResourceLocation, T> entries;

/**
* @param dataGenerator DataGenerator provided by {@link GatherDataEvent}.
* @param dynamicOps DynamicOps to encode values to jsons with using the provided Codec, e.g. {@link JsonOps.INSTANCE}.
* @param packType PackType specifying whether to generate entries in assets or data.
* @param directory String representing the directory to generate jsons in, e.g. "dimension" or "cheesemod/cheese".
* @param codec Codec to encode values to jsons with using the provided DynamicOps.
* @param entries Map of named entries to serialize to jsons. Paths for values are derived from the ResourceLocation's entryid:entrypath as specified above.
*/
public JsonCodecProvider(DataGenerator dataGenerator, ExistingFileHelper existingFileHelper, String modid, DynamicOps<JsonElement> dynamicOps, PackType packType,
String directory, Codec<T> codec, Map<ResourceLocation, T> entries)
{
// Track generated data so other dataproviders can validate if needed.
final ResourceType resourceType = new ResourceType(packType, ".json", directory);
for (ResourceLocation id : entries.keySet())
{
existingFileHelper.trackGenerated(id, resourceType);
}
this.dataGenerator = dataGenerator;
this.existingFileHelper = existingFileHelper;
this.modid = modid;
this.dynamicOps = dynamicOps;
this.packType = packType;
this.directory = directory;
this.codec = codec;
this.entries = entries;
}

/**
* {@return DatapackRegistryProvider that encodes using the registered loading codec for the provided registry key}
* Ensures the correct directory and codec are used.
* Only vanilla datapack registries enumerated in {@link RegistryAccess} and custom forge datapack registries can
* be generated this way.
*
* @param <T> Registry element type, e.g. Biome
* @param dataGenerator DataGenerator provided by {@link GatherDataEvent}.
* @param modid namespace of the mod adding this DataProvider, for logging purposes.
* @param registryOps RegistryOps to encode values to json with.
* @param registryKey ResourceKey identifying the registry and its directory.
* @param entries Map of entries to encode and their ResourceLocations. Paths for values are derived from the ResourceLocation's entryid:entrypath.
*/
public static <T> JsonCodecProvider<T> forDatapackRegistry(DataGenerator dataGenerator, ExistingFileHelper existingFileHelper, String modid,
RegistryOps<JsonElement> registryOps, ResourceKey<Registry<T>> registryKey, Map<ResourceLocation, T> entries)
{
final ResourceLocation registryId = registryKey.location();
// Minecraft datapack registry folders are in data/json-namespace/registry-name/
// Non-vanilla registry folders are data/json-namespace/registry-namespace/registry-name/
final String registryFolder = registryId.getNamespace().equals("minecraft")
? registryId.getPath()
: registryId.getNamespace() + "/" + registryId.getPath();
final Codec<T> codec = (Codec<T>) RegistryAccess.REGISTRIES.get(registryKey).codec();
return new JsonCodecProvider<>(dataGenerator, existingFileHelper, modid, registryOps, PackType.SERVER_DATA, registryFolder, codec, entries);
}

@Override
public void run(final CachedOutput cache) throws IOException
{
final Path outputFolder = this.dataGenerator.getOutputFolder();
final String dataFolder = this.packType.getDirectory();
gather(LamdbaExceptionUtils.rethrowBiConsumer((id, value) -> {
final Path path = outputFolder.resolve(String.join("/", dataFolder, id.getNamespace(), this.directory, id.getPath() + ".json"));
JsonElement encoded = this.codec.encodeStart(this.dynamicOps, value)
.getOrThrow(false, msg -> LOGGER.error("Failed to encode {}: {}", path, msg));
DataProvider.saveStable(cache, encoded, path);
}));
}

protected void gather(BiConsumer<ResourceLocation, T> consumer)
{
this.entries.forEach(consumer);
}

@Override
public String getName()
{
return String.format("%s generator for %s", this.directory, this.modid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public interface BiomeModifier
* Codec for (de)serializing biome modifiers inline.
* Mods can use this for data generation.
*/
@SuppressWarnings("unchecked")
Codec<BiomeModifier> DIRECT_CODEC = ExtraCodecs.lazyInitializedCodec(() -> ForgeRegistries.BIOME_MODIFIER_SERIALIZERS.get().getCodec())
.dispatch(BiomeModifier::codec, Function.identity());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.minecraftforge.common.world;

import java.util.List;
import java.util.Locale;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.Structure.StructureSettings;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Holds lazy-evaluable modified structure info.
* Memoizers are not used because it's important to return null
* without evaluating the structure info if it's accessed outside of a server context.
*/
public class ModifiableStructureInfo
{
@NotNull
private final StructureInfo originalStructureInfo;
@Nullable
private StructureInfo modifiedStructureInfo = null;

/**
* @param originalStructureInfo StructureInfo representing the original state of a structure when the structure was constructed.
*/
public ModifiableStructureInfo(@NotNull final StructureInfo originalStructureInfo)
{
this.originalStructureInfo = originalStructureInfo;
}

/**
* {@return The modified structure info if modified structure info has been generated, otherwise gets original structure info}
*/
@NotNull
public StructureInfo get()
{
return this.modifiedStructureInfo == null
? originalStructureInfo
: modifiedStructureInfo;
}

/**
* {@return The original structure info that the associated structure was created with}
*/
@NotNull
public StructureInfo getOriginalStructureInfo()
{
return this.originalStructureInfo;
}

/**
* {@return Modified structure info; null if it hasn't been set yet}
*/
@Nullable
public StructureInfo getModifiedStructureInfo()
{
return this.modifiedStructureInfo;
}

/**
* Internal forge method; the game will crash if mods invoke this.
* Creates and caches the modified structure info.
* @param structure named structure with original data.
* @param structureModifiers structure modifiers to apply.
*
* @throws IllegalStateException if invoked more than once.
*/
@ApiStatus.Internal
public void applyStructureModifiers(final Holder<Structure> structure, final List<StructureModifier> structureModifiers)
{
if (this.modifiedStructureInfo != null)
throw new IllegalStateException(String.format(Locale.ENGLISH, "Structure %s already modified", structure));

StructureInfo original = this.getOriginalStructureInfo();
final StructureInfo.Builder builder = StructureInfo.Builder.copyOf(original);
for (StructureModifier.Phase phase : StructureModifier.Phase.values())
{
for (StructureModifier modifier : structureModifiers)
{
modifier.modify(structure, phase, builder);
}
}
this.modifiedStructureInfo = builder.build();
}

/**
* Record containing raw structure data.
* @param structureSettings Structure settings.
*/
public record StructureInfo(StructureSettings structureSettings)
{
public static class Builder
{
private StructureSettingsBuilder structureSettings;

/**
* @param original Original structure information
* @return A ModifiedStructureInfo.StructureInfo.Builder with a copy of the structure's data
*/
public static Builder copyOf(final StructureInfo original)
{
final StructureSettingsBuilder structureBuilder = StructureSettingsBuilder.copyOf(original.structureSettings());
return new Builder(structureBuilder);
}

private Builder(final StructureSettingsBuilder structureSettings)
{
this.structureSettings = structureSettings;
}

public StructureInfo build()
{
return new StructureInfo(this.structureSettings.build());
}

public StructureSettingsBuilder getStructureSettings()
{
return structureSettings;
}
}
}
}
Loading